From 206e082ca335288bba48801eb8ee077238a4d127 Mon Sep 17 00:00:00 2001 From: Roomote Date: Fri, 28 Aug 2026 17:42:57 +0000 Subject: [PATCH 1/2] fix(opencode-go): use explicit model limits --- packages/types/src/providers/opencode-go.ts | 41 ++++++++++-- .../fetchers/__tests__/opencode-go.spec.ts | 66 ++++++++++++++++++- src/api/providers/fetchers/opencode-go.ts | 24 +++---- 3 files changed, 114 insertions(+), 17 deletions(-) diff --git a/packages/types/src/providers/opencode-go.ts b/packages/types/src/providers/opencode-go.ts index a7ae0de259..b9c4bd7113 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,11 +16,36 @@ 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 +/** + * Limits for Go models that do not yet have a full native configuration below. + * + * The Go `/v1/models` endpoint currently returns only model IDs, so these + * values prevent newly listed models from inheriting the generic 200k context + * window. Keep this table aligned with the endpoint and models.dev metadata. + */ +export const opencodeGoModelLimits: Record> = { + "kimi-k2.7-code": { contextWindow: 262_144, maxTokens: 262_144 }, + "longcat-2.0": { contextWindow: 1_000_000, maxTokens: 131_072 }, + "glm-5.3-flash": { contextWindow: 1_000_000, maxTokens: 131_072 }, + "deepseek-v4-flash-vision-exp": { contextWindow: 1_000_000, maxTokens: 384_000 }, + "qwen3.8-flash": { contextWindow: 1_000_000, maxTokens: 131_072 }, + "qwen3.5-plus": { contextWindow: 262_144, maxTokens: 65_536 }, + "mimo-v2-pro": { contextWindow: 1_048_576, maxTokens: 128_000 }, + "mimo-v2-omni": { contextWindow: 262_144, maxTokens: 128_000 }, + "hy4-preview": { contextWindow: 1_024_000, maxTokens: 64_000 }, + hy3: { contextWindow: 256_000, maxTokens: 64_000 }, + "hy3-preview": { contextWindow: 256_000, maxTokens: 64_000 }, + "gpt-5.6-luna": { contextWindow: 1_050_000, maxTokens: 128_000 }, + "grok-4.5": { contextWindow: 500_000, maxTokens: 500_000 }, + "grok-4.6": { contextWindow: 500_000, maxTokens: 500_000 }, + "muse-spark-1.2-contributor": { contextWindow: 1_048_576, maxTokens: 131_072 }, +} + /** * Native per-model configuration for the Opencode Go plan. * @@ -406,3 +431,11 @@ export function isOpencodeGoAnthropicFormatModel(modelId: string): boolean { export function getOpencodeGoModelInfo(modelId: string): ModelInfo | undefined { return opencodeGoModels[modelId] } + +/** Returns explicit model limits when Go omits them from `/v1/models`. */ +export function getOpencodeGoModelLimits(modelId: string): Pick | undefined { + const native = getOpencodeGoModelInfo(modelId) + return native + ? { contextWindow: native.contextWindow, maxTokens: native.maxTokens } + : opencodeGoModelLimits[modelId] +} diff --git a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts index 20040ffb22..dbd5731513 100644 --- a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts @@ -2,7 +2,12 @@ import axios from "axios" -import { opencodeGoDefaultModelInfo, opencodeGoModels, getOpencodeGoModelInfo } from "@roo-code/types" +import { + opencodeGoDefaultModelInfo, + opencodeGoModels, + getOpencodeGoModelInfo, + getOpencodeGoModelLimits, +} from "@roo-code/types" import { getOpencodeGoModels, parseOpencodeGoModel } from "../opencode-go" @@ -87,6 +92,23 @@ describe("Opencode Go Fetchers", () => { }) }) + it("uses explicit limits 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 +143,48 @@ describe("Opencode Go Fetchers", () => { }) describe("parseOpencodeGoModel", () => { + it("has explicit limits 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", + ] + + for (const modelId of modelIds) { + expect(getOpencodeGoModelLimits(modelId), modelId).toBeDefined() + } + }) + 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..907a93bde7 100644 --- a/src/api/providers/fetchers/opencode-go.ts +++ b/src/api/providers/fetchers/opencode-go.ts @@ -2,7 +2,7 @@ import axios from "axios" import { z } from "zod" import type { ModelInfo } from "@roo-code/types" -import { opencodeGoDefaultModelInfo, getOpencodeGoModelInfo } from "@roo-code/types" +import { opencodeGoDefaultModelInfo, getOpencodeGoModelInfo, getOpencodeGoModelLimits } from "@roo-code/types" const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1" @@ -41,21 +41,21 @@ 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. Otherwise use the limit-only registry ({@link getOpencodeGoModelLimits}) + * for models whose endpoint entry omits context and output limits. + * 3. Override static limits and image support with live `/models` values when + * present, keeping the gateway authoritative for volatile fields. + * 4. 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. */ export const parseOpencodeGoModel = (model: OpencodeGoModel): ModelInfo => { const native = getOpencodeGoModelInfo(model.id) + const limits = getOpencodeGoModelLimits(model.id) // Live endpoint values take precedence over the registry for volatile fields. const liveContextWindow = model.context_window ?? model.context_length @@ -73,8 +73,8 @@ export const parseOpencodeGoModel = (model: OpencodeGoModel): ModelInfo => { } return { - maxTokens: liveMaxTokens ?? opencodeGoDefaultModelInfo.maxTokens, - contextWindow: liveContextWindow ?? opencodeGoDefaultModelInfo.contextWindow, + maxTokens: liveMaxTokens ?? limits?.maxTokens ?? opencodeGoDefaultModelInfo.maxTokens, + contextWindow: liveContextWindow ?? limits?.contextWindow ?? opencodeGoDefaultModelInfo.contextWindow, supportsImages: liveSupportsImages ?? false, supportsPromptCache: false, description: model.description ?? model.name, From db24d2f3b83a057c75cc47f79349067e1d3383df Mon Sep 17 00:00:00 2001 From: "@navedmerchant" <14171946+navedmerchant@users.noreply.github.com> Date: Fri, 28 Aug 2026 18:29:58 +0000 Subject: [PATCH 2/2] refactor(opencode-go): consolidate model metadata --- .../types/src/__tests__/opencode-go.test.ts | 1 + packages/types/src/providers/opencode-go.ts | 281 ++++++++++++++++-- .../providers/__tests__/opencode-go.spec.ts | 2 + .../fetchers/__tests__/opencode-go.spec.ts | 23 +- src/api/providers/fetchers/opencode-go.ts | 13 +- 5 files changed, 270 insertions(+), 50 deletions(-) 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 b9c4bd7113..36e6a7ca16 100644 --- a/packages/types/src/providers/opencode-go.ts +++ b/packages/types/src/providers/opencode-go.ts @@ -21,31 +21,6 @@ export const opencodeGoDefaultModelInfo: ModelInfo = { export const OPENCODE_GO_DEFAULT_TEMPERATURE = 0 -/** - * Limits for Go models that do not yet have a full native configuration below. - * - * The Go `/v1/models` endpoint currently returns only model IDs, so these - * values prevent newly listed models from inheriting the generic 200k context - * window. Keep this table aligned with the endpoint and models.dev metadata. - */ -export const opencodeGoModelLimits: Record> = { - "kimi-k2.7-code": { contextWindow: 262_144, maxTokens: 262_144 }, - "longcat-2.0": { contextWindow: 1_000_000, maxTokens: 131_072 }, - "glm-5.3-flash": { contextWindow: 1_000_000, maxTokens: 131_072 }, - "deepseek-v4-flash-vision-exp": { contextWindow: 1_000_000, maxTokens: 384_000 }, - "qwen3.8-flash": { contextWindow: 1_000_000, maxTokens: 131_072 }, - "qwen3.5-plus": { contextWindow: 262_144, maxTokens: 65_536 }, - "mimo-v2-pro": { contextWindow: 1_048_576, maxTokens: 128_000 }, - "mimo-v2-omni": { contextWindow: 262_144, maxTokens: 128_000 }, - "hy4-preview": { contextWindow: 1_024_000, maxTokens: 64_000 }, - hy3: { contextWindow: 256_000, maxTokens: 64_000 }, - "hy3-preview": { contextWindow: 256_000, maxTokens: 64_000 }, - "gpt-5.6-luna": { contextWindow: 1_050_000, maxTokens: 128_000 }, - "grok-4.5": { contextWindow: 500_000, maxTokens: 500_000 }, - "grok-4.6": { contextWindow: 500_000, maxTokens: 500_000 }, - "muse-spark-1.2-contributor": { contextWindow: 1_048_576, maxTokens: 131_072 }, -} - /** * Native per-model configuration for the Opencode Go plan. * @@ -126,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, @@ -187,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": { @@ -225,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": { @@ -279,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, @@ -345,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": { @@ -383,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.", + }, } /** @@ -404,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", @@ -431,11 +654,3 @@ export function isOpencodeGoAnthropicFormatModel(modelId: string): boolean { export function getOpencodeGoModelInfo(modelId: string): ModelInfo | undefined { return opencodeGoModels[modelId] } - -/** Returns explicit model limits when Go omits them from `/v1/models`. */ -export function getOpencodeGoModelLimits(modelId: string): Pick | undefined { - const native = getOpencodeGoModelInfo(modelId) - return native - ? { contextWindow: native.contextWindow, maxTokens: native.maxTokens } - : opencodeGoModelLimits[modelId] -} 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 dbd5731513..3e1f1eb1fc 100644 --- a/src/api/providers/fetchers/__tests__/opencode-go.spec.ts +++ b/src/api/providers/fetchers/__tests__/opencode-go.spec.ts @@ -2,12 +2,7 @@ import axios from "axios" -import { - opencodeGoDefaultModelInfo, - opencodeGoModels, - getOpencodeGoModelInfo, - getOpencodeGoModelLimits, -} from "@roo-code/types" +import { opencodeGoDefaultModelInfo, opencodeGoModels, getOpencodeGoModelInfo } from "@roo-code/types" import { getOpencodeGoModels, parseOpencodeGoModel } from "../opencode-go" @@ -92,7 +87,7 @@ describe("Opencode Go Fetchers", () => { }) }) - it("uses explicit limits when the endpoint returns only an OpenCode Go model ID", async () => { + 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" }], @@ -143,7 +138,7 @@ describe("Opencode Go Fetchers", () => { }) describe("parseOpencodeGoModel", () => { - it("has explicit limits for every model returned by OpenCode Go", () => { + it("has complete model info for every model returned by OpenCode Go", () => { const modelIds = [ "minimax-m3", "minimax-m2.7", @@ -179,9 +174,19 @@ describe("Opencode Go Fetchers", () => { "grok-4.6", "muse-spark-1.2-contributor", ] + expect(Object.keys(opencodeGoModels).sort()).toEqual([...modelIds].sort()) for (const modelId of modelIds) { - expect(getOpencodeGoModelLimits(modelId), modelId).toBeDefined() + 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() } }) diff --git a/src/api/providers/fetchers/opencode-go.ts b/src/api/providers/fetchers/opencode-go.ts index 907a93bde7..6b2ad361b2 100644 --- a/src/api/providers/fetchers/opencode-go.ts +++ b/src/api/providers/fetchers/opencode-go.ts @@ -2,7 +2,7 @@ import axios from "axios" import { z } from "zod" import type { ModelInfo } from "@roo-code/types" -import { opencodeGoDefaultModelInfo, getOpencodeGoModelInfo, getOpencodeGoModelLimits } from "@roo-code/types" +import { opencodeGoDefaultModelInfo, getOpencodeGoModelInfo } from "@roo-code/types" const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1" @@ -43,11 +43,9 @@ const opencodeGoModelsResponseSchema = z.object({ * Resolution order for a fully-populated {@link ModelInfo}: * 1. Use the native registry ({@link getOpencodeGoModelInfo}) when the model * is curated, including its capabilities and pricing. - * 2. Otherwise use the limit-only registry ({@link getOpencodeGoModelLimits}) - * for models whose endpoint entry omits context and output limits. - * 3. Override static limits and image support with live `/models` values when + * 2. Override static limits and image support with live `/models` values when * present, keeping the gateway authoritative for volatile fields. - * 4. Fall back to {@link opencodeGoDefaultModelInfo} for an unknown model, + * 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. @@ -55,7 +53,6 @@ const opencodeGoModelsResponseSchema = z.object({ */ export const parseOpencodeGoModel = (model: OpencodeGoModel): ModelInfo => { const native = getOpencodeGoModelInfo(model.id) - const limits = getOpencodeGoModelLimits(model.id) // Live endpoint values take precedence over the registry for volatile fields. const liveContextWindow = model.context_window ?? model.context_length @@ -73,8 +70,8 @@ export const parseOpencodeGoModel = (model: OpencodeGoModel): ModelInfo => { } return { - maxTokens: liveMaxTokens ?? limits?.maxTokens ?? opencodeGoDefaultModelInfo.maxTokens, - contextWindow: liveContextWindow ?? limits?.contextWindow ?? opencodeGoDefaultModelInfo.contextWindow, + maxTokens: liveMaxTokens ?? opencodeGoDefaultModelInfo.maxTokens, + contextWindow: liveContextWindow ?? opencodeGoDefaultModelInfo.contextWindow, supportsImages: liveSupportsImages ?? false, supportsPromptCache: false, description: model.description ?? model.name,