diff --git a/packages/types/src/__tests__/opencode-go.test.ts b/packages/types/src/__tests__/opencode-go.test.ts index adacbe274c..ee8d68e0c6 100644 --- a/packages/types/src/__tests__/opencode-go.test.ts +++ b/packages/types/src/__tests__/opencode-go.test.ts @@ -11,6 +11,7 @@ import { describe("opencode-go registry", () => { const anthropicFormatModels = [ "qwen3.8-max", + "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", diff --git a/packages/types/src/providers/opencode-go.ts b/packages/types/src/providers/opencode-go.ts index a7ae0de259..36e6a7ca16 100644 --- a/packages/types/src/providers/opencode-go.ts +++ b/packages/types/src/providers/opencode-go.ts @@ -3,9 +3,9 @@ import type { ModelInfo } from "../model.js" // Opencode "Go" plan โ€” OpenAI-compatible gateway. // https://opencode.ai/docs/go/ ยท base URL: https://opencode.ai/zen/go/v1 // -// The full model list (and metadata) is fetched dynamically from -// `https://opencode.ai/zen/go/v1/models`, so models can be switched on the fly. -// The values below are only a fallback used before the live list resolves. +// Model IDs are fetched dynamically from `https://opencode.ai/zen/go/v1/models` +// so models can be switched on the fly. The endpoint currently omits metadata, +// so Zoo Code supplies limits and capabilities from the registries below. export const opencodeGoDefaultModelId = "glm-5.2" export const opencodeGoDefaultModelInfo: ModelInfo = { @@ -16,7 +16,7 @@ export const opencodeGoDefaultModelInfo: ModelInfo = { // Pricing is intentionally omitted: ModelInfoView renders a `0` field as "$0.00 / 1M tokens" // (implying the service is free), so we leave it unknown โ€” consistent with the dynamically // fetched models, which also leave price fields absent. See PR #319 review. - description: "Opencode Go plan model. Available models and metadata are resolved dynamically from /v1/models.", + description: "Opencode Go plan model. Model IDs are fetched dynamically; metadata uses Zoo Code's model registry.", } export const OPENCODE_GO_DEFAULT_TEMPERATURE = 0 @@ -101,6 +101,21 @@ export const opencodeGoModels: Record = { description: "GLM-5.3 is Zhipu's flagship coding and agent model with a 1M context window, 128k max output, and always-on reasoning with configurable effort (Low/High/Max). Available via the Opencode Go plan.", }, + "glm-5.3-flash": { + maxTokens: 131_072, + contextWindow: 1_000_000, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["low", "high", "max"], + reasoningEffort: "max", + preserveReasoning: true, + inputPrice: 0.075, + outputPrice: 0.25, + cacheReadsPrice: 0.015, + description: + "GLM-5.3 Flash is Zhipu's fast multimodal coding and agent model with a 1M context window and configurable reasoning effort. Available via the Opencode Go plan.", + }, "glm-5.2": { maxTokens: 131_072, contextWindow: 1_000_000, @@ -162,6 +177,36 @@ export const opencodeGoModels: Record = { description: "Kimi K2.6 is Moonshot AI's native multimodal agentic MoE model with a 256k context window, built for long-horizon coding and tool use. Available via the Opencode Go plan.", }, + "kimi-k2.7-code": { + maxTokens: 262_144, + contextWindow: 262_144, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + preserveReasoning: true, + defaultTemperature: 1.0, + inputPrice: 0.95, + outputPrice: 4.0, + cacheReadsPrice: 0.19, + description: + "Kimi K2.7 Code is Moonshot AI's coding model for long-context programming tasks, with multimodal input and a 256k context window. Available via the Opencode Go plan.", + }, + + // --- Meituan LongCat --- + "longcat-2.0": { + maxTokens: 131_072, + contextWindow: 1_000_000, + supportsImages: false, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningBinary: true, + preserveReasoning: true, + inputPrice: 0.3, + outputPrice: 1.2, + cacheReadsPrice: 0.006, + description: + "LongCat 2.0 is Meituan's long-context reasoning and coding model with a 1M context window. Available via the Opencode Go plan.", + }, // --- Xiaomi MiMo --- "mimo-v2.5": { @@ -200,6 +245,36 @@ export const opencodeGoModels: Record = { description: "MiMo V2.5 Pro - Xiaomi's flagship reasoning model with 1M context, deep thinking, and tool calling. Available via the Opencode Go plan.", }, + "mimo-v2-pro": { + maxTokens: 128_000, + contextWindow: 1_048_576, + supportsImages: false, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 1.0, + outputPrice: 3.0, + cacheReadsPrice: 0.2, + longContextPricing: { + thresholdTokens: 256_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 2, + cacheReadsPriceMultiplier: 2, + }, + description: + "MiMo V2 Pro is Xiaomi's text reasoning and coding model with a 1M context window. Available via the Opencode Go plan.", + }, + "mimo-v2-omni": { + maxTokens: 128_000, + contextWindow: 262_144, + supportsImages: true, + supportsPromptCache: false, + preserveReasoning: true, + inputPrice: 0.4, + outputPrice: 2.0, + cacheReadsPrice: 0.08, + description: + "MiMo V2 Omni is Xiaomi's multimodal reasoning model with a 256k context window. Available via the Opencode Go plan.", + }, // --- MiniMax --- "minimax-m2.5": { @@ -254,6 +329,21 @@ export const opencodeGoModels: Record = { }, // --- Alibaba Qwen --- + "qwen3.5-plus": { + maxTokens: 65_536, + contextWindow: 262_144, + supportsImages: true, + supportsPromptCache: true, + supportsReasoningBudget: true, + supportsReasoningBinary: true, + preserveReasoning: true, + inputPrice: 0.2, + outputPrice: 1.2, + cacheReadsPrice: 0.02, + cacheWritesPrice: 0.25, + description: + "Qwen3.5 Plus is Alibaba's multimodal reasoning model with a 256k context window. Available via the Opencode Go plan.", + }, "qwen3.6-plus": { maxTokens: 65_536, contextWindow: 1_000_000, @@ -320,6 +410,22 @@ export const opencodeGoModels: Record = { description: "Qwen3.8 Max - Alibaba's flagship multimodal reasoning model with a 1M context window, 128k max output, and long-horizon coding and agentic capabilities. Available via the Opencode Go plan.", }, + "qwen3.8-flash": { + maxTokens: 131_072, + contextWindow: 1_000_000, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningBudget: true, + supportsReasoningBinary: true, + preserveReasoning: true, + inputPrice: 0.15, + outputPrice: 0.47, + cacheReadsPrice: 0.016, + cacheWritesPrice: 0.2, + description: + "Qwen3.8 Flash is Alibaba's fast multimodal reasoning model with a 1M context window. Available via the Opencode Go plan.", + }, // --- DeepSeek --- "deepseek-v4-pro": { @@ -358,6 +464,147 @@ export const opencodeGoModels: Record = { description: "DeepSeek-V4-Flash is DeepSeek's fast, cost-efficient V4 model supporting thinking and non-thinking modes. Available via the Opencode Go plan.", }, + "deepseek-v4-flash-vision-exp": { + maxTokens: 384_000, + contextWindow: 1_000_000, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["disable", "low", "high", "max"], + reasoningEffort: "high", + preserveReasoning: true, + inputPrice: 0.22, + outputPrice: 0.66, + cacheReadsPrice: 0.007, + description: + "DeepSeek V4 Flash Vision Experimental is a fast multimodal reasoning model with a 1M context window. Available via the Opencode Go plan.", + }, + + // --- Tencent Hunyuan --- + "hy4-preview": { + maxTokens: 64_000, + contextWindow: 1_024_000, + supportsImages: false, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["disable", "high"], + reasoningEffort: "high", + preserveReasoning: true, + inputPrice: 0.834, + outputPrice: 2.501, + cacheReadsPrice: 0.042, + description: + "Hunyuan 4 Preview is Tencent's long-context reasoning and coding model with a 1M context window. Available via the Opencode Go plan.", + }, + hy3: { + maxTokens: 64_000, + contextWindow: 256_000, + supportsImages: false, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["disable", "low", "high"], + reasoningEffort: "high", + preserveReasoning: true, + inputPrice: 0.0175, + outputPrice: 0.0725, + cacheReadsPrice: 0.004375, + description: + "Hunyuan 3 is Tencent's reasoning and coding model with a 256k context window. Available via the Opencode Go plan.", + }, + "hy3-preview": { + // The live endpoint still lists this historical alias; it shares HY3's limits and pricing. + maxTokens: 64_000, + contextWindow: 256_000, + supportsImages: false, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["disable", "low", "high"], + reasoningEffort: "high", + preserveReasoning: true, + inputPrice: 0.0175, + outputPrice: 0.0725, + cacheReadsPrice: 0.004375, + description: + "Hunyuan 3 Preview is Tencent's preview reasoning and coding model with a 256k context window. Available via the Opencode Go plan.", + }, + + // --- Responses API models --- + "gpt-5.6-luna": { + maxTokens: 128_000, + contextWindow: 1_050_000, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["none", "low", "medium", "high", "xhigh", "max"], + reasoningEffort: "medium", + inputPrice: 0.2, + outputPrice: 1.2, + cacheWritesPrice: 0.25, + cacheReadsPrice: 0.02, + longContextPricing: { + thresholdTokens: 272_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 1.5, + cacheWritesPriceMultiplier: 2, + cacheReadsPriceMultiplier: 2, + }, + description: + "GPT-5.6 Luna is OpenAI's fast reasoning model with a 1M context window. Available via the Opencode Go plan.", + }, + "grok-4.5": { + maxTokens: 500_000, + contextWindow: 500_000, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["low", "medium", "high"], + reasoningEffort: "high", + inputPrice: 2.0, + outputPrice: 6.0, + cacheReadsPrice: 0.3, + longContextPricing: { + thresholdTokens: 200_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 2, + cacheReadsPriceMultiplier: 2, + }, + description: + "Grok 4.5 is xAI's multimodal reasoning and agent model with a 500k context window. Available via the Opencode Go plan.", + }, + "grok-4.6": { + maxTokens: 500_000, + contextWindow: 500_000, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["low", "medium", "high", "xhigh"], + reasoningEffort: "high", + inputPrice: 2.0, + outputPrice: 6.0, + cacheReadsPrice: 0.5, + longContextPricing: { + thresholdTokens: 200_000, + inputPriceMultiplier: 2, + outputPriceMultiplier: 2, + cacheReadsPriceMultiplier: 2, + }, + description: + "Grok 4.6 is xAI's multimodal reasoning and agent model with a 500k context window. Available via the Opencode Go plan.", + }, + "muse-spark-1.2-contributor": { + maxTokens: 131_072, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: true, + supportsMaxTokens: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high", "xhigh"], + reasoningEffort: "medium", + inputPrice: 0.1, + outputPrice: 0.2, + cacheReadsPrice: 0.002, + description: + "Muse Spark 1.2 Contributor is Meta's multimodal coding model with a 1M context window. Available via the Opencode Go plan.", + }, } /** @@ -379,6 +626,7 @@ export const opencodeGoModels: Record = { export const OPENCODE_GO_ANTHROPIC_FORMAT_MODELS = new Set([ // --- Alibaba Qwen --- "qwen3.8-max", + "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", diff --git a/src/api/providers/__tests__/opencode-go.spec.ts b/src/api/providers/__tests__/opencode-go.spec.ts index 75ad5ff077..f98eabaf73 100644 --- a/src/api/providers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/__tests__/opencode-go.spec.ts @@ -799,6 +799,7 @@ describe("OpencodeGoHandler", () => { describe("isOpencodeGoAnthropicFormatModel", () => { it("classifies Qwen and MiniMax Go models as Anthropic-format", () => { + expect(isOpencodeGoAnthropicFormatModel("qwen3.8-flash")).toBe(true) expect(isOpencodeGoAnthropicFormatModel("qwen3.7-max")).toBe(true) expect(isOpencodeGoAnthropicFormatModel("qwen3.7-plus")).toBe(true) expect(isOpencodeGoAnthropicFormatModel("qwen3.6-plus")).toBe(true) @@ -812,6 +813,7 @@ describe("OpencodeGoHandler", () => { expect(isOpencodeGoAnthropicFormatModel("kimi-k2.6")).toBe(false) expect(isOpencodeGoAnthropicFormatModel("deepseek-v4-pro")).toBe(false) expect(isOpencodeGoAnthropicFormatModel("mimo-v2.5")).toBe(false) + expect(isOpencodeGoAnthropicFormatModel("qwen3.5-plus")).toBe(false) }) it("defaults unknown model IDs to the OpenAI-compatible format", () => { diff --git a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts index 20040ffb22..3e1f1eb1fc 100644 --- a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts @@ -87,6 +87,23 @@ describe("Opencode Go Fetchers", () => { }) }) + it("uses native model info when the endpoint returns only an OpenCode Go model ID", async () => { + mockedAxios.get.mockResolvedValue({ + data: { + data: [{ id: "gpt-5.6-luna" }, { id: "hy4-preview" }, { id: "deepseek-v4-flash-vision-exp" }], + }, + }) + + const models = await getOpencodeGoModels("k") + + expect(models["gpt-5.6-luna"]).toMatchObject({ contextWindow: 1_050_000, maxTokens: 128_000 }) + expect(models["hy4-preview"]).toMatchObject({ contextWindow: 1_024_000, maxTokens: 64_000 }) + expect(models["deepseek-v4-flash-vision-exp"]).toMatchObject({ + contextWindow: 1_000_000, + maxTokens: 384_000, + }) + }) + it("returns an empty map on network error", async () => { mockedAxios.get.mockRejectedValue(new Error("network")) expect(await getOpencodeGoModels("k")).toEqual({}) @@ -121,6 +138,58 @@ describe("Opencode Go Fetchers", () => { }) describe("parseOpencodeGoModel", () => { + it("has complete model info for every model returned by OpenCode Go", () => { + const modelIds = [ + "minimax-m3", + "minimax-m2.7", + "minimax-m2.5", + "kimi-k3", + "kimi-k2.7-code", + "kimi-k2.6", + "longcat-2.0", + "kimi-k2.5", + "glm-5.2", + "glm-5.3-flash", + "glm-5.3", + "glm-5.1", + "glm-5", + "deepseek-v4-pro", + "deepseek-v4-flash", + "deepseek-v4-flash-vision-exp", + "qwen3.7-max", + "qwen3.8-max", + "qwen3.8-flash", + "qwen3.7-plus", + "qwen3.6-plus", + "qwen3.5-plus", + "mimo-v2-pro", + "mimo-v2-omni", + "mimo-v2.5-pro", + "mimo-v2.5", + "hy4-preview", + "hy3", + "hy3-preview", + "gpt-5.6-luna", + "grok-4.5", + "grok-4.6", + "muse-spark-1.2-contributor", + ] + expect(Object.keys(opencodeGoModels).sort()).toEqual([...modelIds].sort()) + + for (const modelId of modelIds) { + const info = getOpencodeGoModelInfo(modelId) + expect(info, modelId).toBeDefined() + expect(info?.contextWindow, modelId).toBeGreaterThan(0) + expect(info?.maxTokens, modelId).toBeGreaterThan(0) + expect(info?.supportsImages, modelId).toEqual(expect.any(Boolean)) + expect(info?.supportsPromptCache, modelId).toEqual(expect.any(Boolean)) + expect(info?.inputPrice, modelId).toEqual(expect.any(Number)) + expect(info?.outputPrice, modelId).toEqual(expect.any(Number)) + expect(info?.cacheReadsPrice, modelId).toEqual(expect.any(Number)) + expect(info?.description, modelId).toBeTruthy() + } + }) + it("merges live endpoint values over the native registry for a curated model", () => { const info = parseOpencodeGoModel({ id: "glm-5.1", context_window: 150000, max_output_tokens: 8000 }) // Live values win for volatile fields. diff --git a/src/api/providers/fetchers/opencode-go.ts b/src/api/providers/fetchers/opencode-go.ts index d28b8872f0..6b2ad361b2 100644 --- a/src/api/providers/fetchers/opencode-go.ts +++ b/src/api/providers/fetchers/opencode-go.ts @@ -41,15 +41,12 @@ const opencodeGoModelsResponseSchema = z.object({ * slider, and accurate cost reporting. * * Resolution order for a fully-populated {@link ModelInfo}: - * 1. Start from the native registry ({@link getOpencodeGoModelInfo}) when the - * model ID is curated โ€” this supplies correct context lengths, max tokens, - * capability flags, and pricing sourced from vendor specs. - * 2. Override `contextWindow`, `maxTokens`, and `supportsImages` with values - * from the live `/models` payload when present, so the gateway stays the - * source of truth for those volatile fields. - * 3. Fall back to {@link opencodeGoDefaultModelInfo} for any field still - * missing on an unknown (non-curated) model, ensuring downstream consumers - * always receive a fully-populated object. + * 1. Use the native registry ({@link getOpencodeGoModelInfo}) when the model + * is curated, including its capabilities and pricing. + * 2. Override static limits and image support with live `/models` values when + * present, keeping the gateway authoritative for volatile fields. + * 3. Fall back to {@link opencodeGoDefaultModelInfo} for an unknown model, + * ensuring downstream consumers always receive a fully-populated object. * * @param model - Validated model entry from the `/models` response. * @returns Normalised model metadata suitable for the model picker.