diff --git a/src/providers/registry.ts b/src/providers/registry.ts index c81735ba15..ce82dc512e 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -2470,6 +2470,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ // The gateway validates the ladder strictly and rejects anything above `high`. reasoningEfforts: ["low", "medium", "high"], reasoningEffortMap: { xhigh: "high", max: "high", ultra: "high" }, + // Live token-plan verification (#1927): the Pro route rejects image input while + // mimo-v2.5 accepts it natively. Keep this provider-scoped so a hand-rolled + // provider with the same id but another destination does not inherit the claim. + noVisionModels: ["mimo-v2.5-pro"], // A user may already have hand-rolled a provider under this id against a different host; // without this, routedProviderConfig() would canonicalize their base URL onto ours and send // their key somewhere they did not choose. diff --git a/structure/03_catalog-and-subagents.md b/structure/03_catalog-and-subagents.md index fe347602d4..2c0a8345e5 100644 --- a/structure/03_catalog-and-subagents.md +++ b/structure/03_catalog-and-subagents.md @@ -255,6 +255,21 @@ the request, and they never raise it. `preserveCustomDestination`으로 같은 이름의 다른 host/key를 보호한다. 대신 새 preset 표면을 문서와 registry parity에서 함께 유지해야 한다. +[Decision Log] +- 목적과 의도: Xiaomi token-plan에서 image input을 거부하는 `mimo-v2.5-pro`만 vision + sidecar로 우회하고, 실제 image input을 받는 `mimo-v2.5`는 native vision 경로에 남긴다. +- 기존 구현 및 제약 조건: upstream `/v1/models`는 input modality를 제공하지 않으며, + `noVisionModels`는 text-only 모델을 sidecar로 보내면서 Codex catalog에는 image input을 + 광고하는 provider-scoped 계약이다. +- 검토한 주요 대안: MiMo 전체를 text-only로 분류하기, live discovery에서 modality를 + 추측하기, `mimo-v2.5-pro` 하나만 registry에 고정 분류하기. +- 선택한 방식: canonical `mimo` preset의 `noVisionModels`에 `mimo-v2.5-pro`만 추가한다. +- 다른 대안 대신 이 방식을 선택한 이유: live endpoint 검증으로 확인된 최소 범위만 + 적용하며, 정상 동작하는 `mimo-v2.5`의 native image 경로를 훼손하지 않는다. +- 장점, 단점 및 영향: Pro image 요청의 404를 sidecar 설명 경로로 바꾸고 base 모델은 + 그대로 유지한다. `preserveCustomDestination` guard 때문에 같은 provider id를 다른 host에 + 연결한 사용자 설정에는 이 capability 분류가 전파되지 않는다. + [Decision Log] - 목적과 의도: bare `defaultModel` selectors that route into third-party providers must keep their adapter-owned effort ladder; only true ChatGPT-native requests should receive the mock-max repair. diff --git a/tests/catalog-vision-sidecar-modalities.test.ts b/tests/catalog-vision-sidecar-modalities.test.ts index c49347025e..a9e4f1cd85 100644 --- a/tests/catalog-vision-sidecar-modalities.test.ts +++ b/tests/catalog-vision-sidecar-modalities.test.ts @@ -45,6 +45,32 @@ describe("vision-sidecar catalog modalities", () => { const hinted = applyProviderConfigHints("opencode-go", prov, { id: "glm-5.2", provider: "opencode-go" }); expect(hinted.inputModalities).toEqual(["text", "image"]); }); + + test("MiMo token-plan sends only the Pro model through the sidecar (#1927)", () => { + const canonical: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://token-plan-cn.xiaomimimo.com/v1", + authMode: "key", + }; + enrichProviderFromRegistry("mimo", canonical); + expect(canonical.noVisionModels).toEqual(["mimo-v2.5-pro"]); + expect(applyProviderConfigHints("mimo", canonical, { + id: "mimo-v2.5-pro", + provider: "mimo", + }).inputModalities).toEqual(["text", "image"]); + expect(applyProviderConfigHints("mimo", canonical, { + id: "mimo-v2.5", + provider: "mimo", + }).inputModalities).toBeUndefined(); + + const customDestination: OcxProviderConfig = { + adapter: "openai-chat", + baseUrl: "https://mimo-compatible.example/v1", + authMode: "key", + }; + enrichProviderFromRegistry("mimo", customDestination); + expect(customDestination.noVisionModels).toBeUndefined(); + }); }); describe("vision-sidecar custom-model override (#349/#344)", () => { diff --git a/tests/provider-registry-parity.test.ts b/tests/provider-registry-parity.test.ts index 850409d8df..f06a8953c3 100644 --- a/tests/provider-registry-parity.test.ts +++ b/tests/provider-registry-parity.test.ts @@ -67,6 +67,8 @@ describe("provider registry parity", () => { "qwen3.7-max", ]); expect(KEY_LOGIN_PROVIDERS["opencode-go"].noVisionModels).not.toContain("kimi-k2.7-code"); + expect(KEY_LOGIN_PROVIDERS.mimo.noVisionModels).toEqual(["mimo-v2.5-pro"]); + expect(KEY_LOGIN_PROVIDERS.mimo.noVisionModels).not.toContain("mimo-v2.5"); expect(KEY_LOGIN_PROVIDERS["opencode-go"]).toMatchObject({ modelContextWindows: { "kimi-k3": 262_144 }, modelInputModalities: { "kimi-k3": ["text", "image"] },