Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions packages/types/src/__tests__/opencode-go.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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: {
Expand Down
24 changes: 24 additions & 0 deletions packages/types/src/providers/opencode-go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,30 @@
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.",

Check warning on line 289 in packages/types/src/providers/opencode-go.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/opencode-go.ts:289: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
},
"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.",

Check warning on line 301 in packages/types/src/providers/opencode-go.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

packages/types/src/providers/opencode-go.ts:301: Survived StringLiteral mutant (replacement: ""). See the job summary for the complete list and resolution guidance.
},

// --- MiniMax ---
"minimax-m2.5": {
Expand Down
72 changes: 72 additions & 0 deletions src/api/providers/__tests__/opencode-go.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "<environment_details>cwd: /repo</environment_details>" },
],
},
]

await collectStream(handler.createMessage("sys", messages))

const callArgs = mockCreate.mock.calls[0][0] as {
model: string
messages: Array<Record<string, unknown>>
}
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\n<environment_details>cwd: /repo</environment_details>',
})
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([
Expand Down
55 changes: 55 additions & 0 deletions src/api/providers/fetchers/__tests__/opencode-go.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({})
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
Loading