From 9f0b3447580c3705c128c9a7195f095d572c974a Mon Sep 17 00:00:00 2001 From: myk1yt Date: Fri, 30 Oct 2026 17:12:31 +0900 Subject: [PATCH] fix(opencode-go): add MiMo V2.6 Flash and Pro model configurations What: add mimo-v2.6-flash and mimo-v2.6-pro to the curated OpenCode Go registry in packages/types, with their own context/output limits, input modalities, reasoning preservation, and Go pricing. Why: both IDs are served by the live Go catalog but were uncurated, so the fetcher fell back to 200,000 context / 32,768 output, no image support, no pricing, and no reasoning preservation. Issue #1997 asks for independently confirmed V2.6 metadata rather than a copy of the V2.5 limits. Impact: the two models now report 1,048,576 context / 131,072 output, accept image input, preserve reasoning across tool-call continuations, and price at 0.14/0.28/0.0028 (Flash) and 0.435/0.87/0.003625 (Pro) per 1M tokens. Routing is unchanged: both stay on Chat Completions. No other model is affected. Fixes #1997 --- .../types/src/__tests__/opencode-go.test.ts | 28 ++++++++ packages/types/src/providers/opencode-go.ts | 24 +++++++ .../providers/__tests__/opencode-go.spec.ts | 72 +++++++++++++++++++ .../fetchers/__tests__/opencode-go.spec.ts | 55 ++++++++++++++ 4 files changed, 179 insertions(+) diff --git a/packages/types/src/__tests__/opencode-go.test.ts b/packages/types/src/__tests__/opencode-go.test.ts index cfcde4089c..a0fe7ccb6a 100644 --- a/packages/types/src/__tests__/opencode-go.test.ts +++ b/packages/types/src/__tests__/opencode-go.test.ts @@ -34,6 +34,8 @@ describe("opencode-go registry", () => { "kimi-k2.6", "mimo-v2.5", "mimo-v2.5-pro", + "mimo-v2.6-flash", + "mimo-v2.6-pro", "deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4.1-flash", @@ -361,6 +363,32 @@ describe("opencode-go registry", () => { cacheReadsPrice: 0.08, }, }, + { + id: "mimo-v2.6-flash", + expected: { + maxTokens: 131_072, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.14, + outputPrice: 0.28, + cacheReadsPrice: 0.0028, + }, + }, + { + id: "mimo-v2.6-pro", + expected: { + maxTokens: 131_072, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.435, + outputPrice: 0.87, + cacheReadsPrice: 0.003625, + }, + }, { id: "qwen3.5-plus", expected: { diff --git a/packages/types/src/providers/opencode-go.ts b/packages/types/src/providers/opencode-go.ts index 827cac1c58..2a5019d5ff 100644 --- a/packages/types/src/providers/opencode-go.ts +++ b/packages/types/src/providers/opencode-go.ts @@ -276,6 +276,30 @@ export const opencodeGoModels: Record = { description: "MiMo V2 Omni is Xiaomi's multimodal reasoning model with a 256k context window. Available via the Opencode Go plan.", }, + "mimo-v2.6-flash": { + maxTokens: 131_072, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.14, + outputPrice: 0.28, + cacheReadsPrice: 0.0028, + description: + "MiMo V2.6 Flash is Xiaomi's fast full-modal model (text, image, audio, video) with 1M context and deep thinking. Available via the Opencode Go plan.", + }, + "mimo-v2.6-pro": { + maxTokens: 131_072, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.435, + outputPrice: 0.87, + cacheReadsPrice: 0.003625, + description: + "MiMo V2.6 Pro is Xiaomi's stronger full-modal reasoning model with 1M context and deep thinking. Available via the Opencode Go plan.", + }, // --- MiniMax --- "minimax-m2.5": { diff --git a/src/api/providers/__tests__/opencode-go.spec.ts b/src/api/providers/__tests__/opencode-go.spec.ts index b7d708ab94..b96cf2acce 100644 --- a/src/api/providers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/__tests__/opencode-go.spec.ts @@ -378,6 +378,78 @@ describe("OpencodeGoHandler", () => { expect(callArgs.messages.filter((m) => m.role === "user")).toHaveLength(1) }) + it("preserves reasoning across a tool-call continuation for MiMo V2.6 Flash", async () => { + // Xiaomi's deep-thinking guide requires the reasoning from the assistant + // turn to be passed back on the tool-call continuation. MiMo V2.6 must + // therefore convert through convertToR1Format (preserveReasoning) and + // keep the reasoning -> tool_call -> tool_result history intact. + vitest.mocked(getModels).mockImplementationOnce(async () => ({ + "mimo-v2.6-flash": { ...opencodeGoModels["mimo-v2.6-flash"] }, + })) + mockCreate.mockImplementationOnce(async () => + asyncStreamFrom([ + { choices: [{ delta: { content: "Done" }, index: 0 }] }, + { + choices: [{ delta: {}, index: 0 }], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, + }, + ]), + ) + + const handler = new OpencodeGoHandler({ ...mockOptions, opencodeGoModelId: "mimo-v2.6-flash" }) + const messages: Anthropic.Messages.MessageParam[] = [ + { role: "user", content: "Read the config file" }, + { + role: "assistant", + content: [ + { type: "text", text: "I will read it." }, + { type: "tool_use", id: "call_mimo_1", name: "read_file", input: { path: "config.json" } }, + ], + reasoning_content: "The user wants the config contents.", + } as Anthropic.Messages.MessageParam, + { + role: "user", + content: [ + { type: "tool_result", tool_use_id: "call_mimo_1", content: '{"a":1}' }, + { type: "text", text: "cwd: /repo" }, + ], + }, + ] + + await collectStream(handler.createMessage("sys", messages)) + + const callArgs = mockCreate.mock.calls[0][0] as { + model: string + messages: Array> + } + expect(callArgs.model).toBe("mimo-v2.6-flash") + + // Index 0 is the system prompt; the converted history follows it. + expect(callArgs.messages[0]).toEqual({ role: "system", content: "sys" }) + const [, userMessage, assistantMessage, toolMessage] = callArgs.messages + expect(userMessage).toEqual({ role: "user", content: "Read the config file" }) + // The assistant turn keeps both its reasoning and its tool call. + expect(assistantMessage).toMatchObject({ + role: "assistant", + reasoning_content: "The user wants the config contents.", + }) + expect(assistantMessage.tool_calls).toEqual([ + { + id: "call_mimo_1", + type: "function", + function: { name: "read_file", arguments: '{"path":"config.json"}' }, + }, + ]) + // The tool result follows as a tool message, with the trailing + // environment details merged in so no user turn drops the reasoning. + expect(toolMessage).toEqual({ + role: "tool", + tool_call_id: "call_mimo_1", + content: '{"a":1}\n\ncwd: /repo', + }) + expect(callArgs.messages.filter((message) => message.role === "user")).toHaveLength(1) + }) + it("emits a usage chunk with zeroed tokens when the stream reports no usage", async () => { mockCreate.mockImplementationOnce(async () => asyncStreamFrom([ diff --git a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts index 62f17ab900..9133d8d2cb 100644 --- a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts @@ -163,6 +163,37 @@ describe("Opencode Go Fetchers", () => { }) }) + it("resolves MiMo V2.6 Flash and Pro from an ID-only catalog response", async () => { + mockedAxios.get.mockResolvedValue({ + data: { data: [{ id: "mimo-v2.6-flash" }, { id: "mimo-v2.6-pro" }] }, + }) + + const models = await getOpencodeGoModels("k") + + // The generic fallback would report 200_000 context / 32_768 output and + // no pricing, so these values prove the native registry was used. + expect(models["mimo-v2.6-flash"]).toMatchObject({ + contextWindow: 1_048_576, + maxTokens: 131_072, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.14, + outputPrice: 0.28, + cacheReadsPrice: 0.0028, + }) + expect(models["mimo-v2.6-pro"]).toMatchObject({ + contextWindow: 1_048_576, + maxTokens: 131_072, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.435, + outputPrice: 0.87, + cacheReadsPrice: 0.003625, + }) + }) + it("returns an empty map on network error", async () => { mockedAxios.get.mockRejectedValue(new Error("network")) expect(await getOpencodeGoModels("k")).toEqual({}) @@ -253,6 +284,8 @@ describe("Opencode Go Fetchers", () => { "mimo-v2-omni", "mimo-v2.5-pro", "mimo-v2.5", + "mimo-v2.6-flash", + "mimo-v2.6-pro", "hy4-preview", "hy3", "hy3-preview", @@ -330,6 +363,28 @@ describe("Opencode Go Fetchers", () => { expect(info.cacheReadsPrice).toBe(0.006) }) + it("resolves MiMo V2.6 Flash and Pro to their 1M context when live metadata is absent", () => { + const flash = parseOpencodeGoModel({ id: "mimo-v2.6-flash" }) + expect(flash.contextWindow).toBe(1_048_576) + expect(flash.maxTokens).toBe(131_072) + expect(flash.supportsImages).toBe(true) + expect(flash.supportsPromptCache).toBe(false) + expect(flash.preserveReasoning).toBe(true) + expect(flash.inputPrice).toBe(0.14) + expect(flash.outputPrice).toBe(0.28) + expect(flash.cacheReadsPrice).toBe(0.0028) + + const pro = parseOpencodeGoModel({ id: "mimo-v2.6-pro" }) + expect(pro.contextWindow).toBe(1_048_576) + expect(pro.maxTokens).toBe(131_072) + expect(pro.supportsImages).toBe(true) + expect(pro.supportsPromptCache).toBe(false) + expect(pro.preserveReasoning).toBe(true) + expect(pro.inputPrice).toBe(0.435) + expect(pro.outputPrice).toBe(0.87) + expect(pro.cacheReadsPrice).toBe(0.003625) + }) + it("resolves GLM-5.2 with its 1M context and High/Max reasoning effort", () => { const info = parseOpencodeGoModel({ id: "glm-5.2" }) expect(info.contextWindow).toBe(1_000_000)