Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
26 changes: 26 additions & 0 deletions packages/types/src/__tests__/provider-settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ describe("getApiProtocol", () => {
})
})

describe("Opencode Go provider", () => {
it("should return 'anthropic' for opencode-go Anthropic-format models (Qwen/MiniMax)", () => {
expect(getApiProtocol("opencode-go", "qwen3.7-max")).toBe("anthropic")
expect(getApiProtocol("opencode-go", "qwen3.7-plus")).toBe("anthropic")
expect(getApiProtocol("opencode-go", "qwen3.6-plus")).toBe("anthropic")
expect(getApiProtocol("opencode-go", "minimax-m3")).toBe("anthropic")
expect(getApiProtocol("opencode-go", "minimax-m2.7")).toBe("anthropic")
expect(getApiProtocol("opencode-go", "minimax-m2.5")).toBe("anthropic")
})

it("should return 'openai' for opencode-go OpenAI-format models (GLM/DeepSeek/etc.)", () => {
expect(getApiProtocol("opencode-go", "glm-5.2")).toBe("openai")
expect(getApiProtocol("opencode-go", "deepseek-v4-pro")).toBe("openai")
expect(getApiProtocol("opencode-go", "kimi-k2.5")).toBe("openai")
expect(getApiProtocol("opencode-go", "mimo-v2.5")).toBe("openai")
})

it("should return 'openai' for opencode-go without a model", () => {
expect(getApiProtocol("opencode-go")).toBe("openai")
})

it("should return 'openai' for opencode-go with an unknown model id", () => {
expect(getApiProtocol("opencode-go", "some-future-model")).toBe("openai")
})
})

describe("Other providers", () => {
it("should return 'openai' for non-anthropic providers regardless of model", () => {
expect(getApiProtocol("openrouter", "claude-3-opus")).toBe("openai")
Expand Down
12 changes: 12 additions & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
internationalZAiModels,
minimaxModels,
mimoModels,
isOpencodeGoAnthropicFormatModel,
} from "./providers/index.js"

/**
Expand Down Expand Up @@ -595,6 +596,17 @@ export const getApiProtocol = (provider: ProviderName | undefined, modelId?: str
return "anthropic"
}

// Opencode Go routes a subset of its models (Qwen, MiniMax) through the
// Anthropic Messages wire format (`/v1/messages`), which reports usage in
// Anthropic style: `input_tokens` excludes cache tokens, with separate
// `cache_creation_input_tokens` / `cache_read_input_tokens` fields. These
// models must use the anthropic protocol so token/cost aggregation adds the
// cache tokens back into the input total — otherwise the cached prefix is
// dropped from `contextTokens`, undercounting context-window usage.
if (provider && provider === "opencode-go" && modelId && isOpencodeGoAnthropicFormatModel(modelId)) {
return "anthropic"
}

return "openai"
}

Expand Down
317 changes: 316 additions & 1 deletion packages/types/src/providers/opencode-go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ModelInfo } from "../model.js"
// 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.
export const opencodeGoDefaultModelId = "glm-5.1"
export const opencodeGoDefaultModelId = "glm-5.2"

export const opencodeGoDefaultModelInfo: ModelInfo = {
maxTokens: 32_768,
Expand All @@ -20,3 +20,318 @@ export const opencodeGoDefaultModelInfo: ModelInfo = {
}

export const OPENCODE_GO_DEFAULT_TEMPERATURE = 0

/**
* Native per-model configuration for the Opencode Go plan.
*
* The Go `/v1/models` endpoint only reliably returns `id` and (sometimes)
* `context_window`/`max_tokens`. It does NOT advertise capability flags such
* as `supportsReasoningEffort`, `preserveReasoning`, `supportsMaxTokens`,
* `supportsPromptCache`, or pricing — all of which are required for the
* extension to drive reasoning controls, interleaved-thinking tool calls,
* the max-output-tokens slider, and accurate cost reporting.
*
* This registry encodes the native capabilities of each curated Go model,
* sourced from the same vendor specs used by the dedicated providers
* (zai/moonshot/mimo/minimax/deepseek/qwen) and the Go pricing table at
* https://opencode.ai/docs/go/#usage-limits. The fetcher merges the live
* `/models` payload on top of these defaults so that context-window and
* max-token values stay in sync with the gateway while capability flags and
* pricing remain correct.
*
* `supportsPromptCache` is intentionally `true` for models whose Go pricing
* table lists a "Cached Read" price: the gateway honours server-side caching
* and reports `cached_tokens` in usage, which the handler forwards for cost
* calculation. Client-side `cache_control` injection is not used on this path.
*/
export const opencodeGoModels: Record<string, ModelInfo> = {
// --- Zhipu GLM ---
"glm-5": {
maxTokens: 16_384,
contextWindow: 202_752,
supportsImages: false,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "medium"],
reasoningEffort: "medium",
preserveReasoning: true,
inputPrice: 1.0,
outputPrice: 3.2,
cacheReadsPrice: 0.2,
description:
"GLM-5 is Zhipu's next-generation model with a 202k context window and built-in thinking capabilities. Available via the Opencode Go plan.",
},
"glm-5.1": {
maxTokens: 131_072,
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["disable", "medium"],
reasoningEffort: "medium",
preserveReasoning: true,
inputPrice: 1.4,
outputPrice: 4.4,
cacheReadsPrice: 0.26,
description:
"GLM-5.1 is Zhipu's most capable model with a 200k context window, 128k max output, and built-in thinking capabilities. Available via the Opencode Go plan.",
},
"glm-5.2": {
maxTokens: 131_072,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
supportsMaxTokens: true,
supportsReasoningEffort: ["disable", "high", "max"],
reasoningEffort: "high",
preserveReasoning: true,
// Go pricing matches GLM-5.1 ($1.4 / $0.26 cache / $4.4 out per 1M tokens).
inputPrice: 1.4,
outputPrice: 4.4,
cacheReadsPrice: 0.26,
description:
"GLM-5.2 is Zhipu's flagship model with a 1M context window, 128k max output, and dual thinking-effort modes (High/Max). It delivers top-tier long-context reasoning, coding, and agentic performance. Available via the Opencode Go plan.",
},

// --- Moonshot Kimi ---
"kimi-k2.5": {
maxTokens: 16_384,
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: true,
supportsTemperature: true,
defaultTemperature: 1.0,
inputPrice: 0.6,
outputPrice: 3.0,
cacheReadsPrice: 0.1,
description:
"Kimi K2.5 is the latest generation of Moonshot AI's Kimi series, featuring improved reasoning capabilities. Available via the Opencode Go plan.",
},
"kimi-k2.6": {
maxTokens: 16_384,
contextWindow: 262_144,
supportsImages: false,
supportsPromptCache: true,
supportsTemperature: true,
defaultTemperature: 1.0,
inputPrice: 0.95,
outputPrice: 4.0,
cacheReadsPrice: 0.16,
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.",
},

// --- Xiaomi MiMo ---
"mimo-v2.5": {
maxTokens: 131_072,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
preserveReasoning: true,
inputPrice: 0.14,
outputPrice: 0.28,
cacheReadsPrice: 0.0028,
Comment on lines +139 to +143

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supportsPromptCache: false means no cache-control breakpoints are ever injected for MiMo, so cacheReadsPrice here can never be applied — the extension won't report cache reads for a model that doesn't advertise prompt cache support. Does the Go gateway actually bill and report cached_tokens for MiMo on the OA-compat path without client-side breakpoints?

longContextPricing: {
thresholdTokens: 256_000,
inputPriceMultiplier: 2,
outputPriceMultiplier: 2,
cacheReadsPriceMultiplier: 2,
},
description:
"MiMo V2.5 - Xiaomi's full-modal understanding model (text, image, audio, video) with 1M context, deep thinking, and tool calling. Available via the Opencode Go plan.",
},
"mimo-v2.5-pro": {
maxTokens: 131_072,
contextWindow: 1_048_576,
supportsImages: false,
supportsPromptCache: false,
preserveReasoning: true,
inputPrice: 1.74,
outputPrice: 3.48,
cacheReadsPrice: 0.0145,
longContextPricing: {
thresholdTokens: 256_000,
inputPriceMultiplier: 2,
outputPriceMultiplier: 2,
cacheReadsPriceMultiplier: 2,
},
description:
"MiMo V2.5 Pro - Xiaomi's flagship reasoning model with 1M context, deep thinking, and tool calling. Available via the Opencode Go plan.",
},

// --- MiniMax ---
"minimax-m2.5": {
maxTokens: 16_384,
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
includedTools: ["search_and_replace"],
excludedTools: ["apply_diff"],
preserveReasoning: true,
inputPrice: 0.3,
outputPrice: 1.2,
cacheWritesPrice: 0.375,
cacheReadsPrice: 0.06,
description:
"MiniMax M2.5, the latest MiniMax model with enhanced coding and agentic capabilities. Available via the Opencode Go plan.",
},
"minimax-m2.7": {
maxTokens: 16_384,
contextWindow: 204_800,
supportsImages: false,
supportsPromptCache: true,
includedTools: ["search_and_replace"],
excludedTools: ["apply_diff"],
preserveReasoning: true,
inputPrice: 0.3,
outputPrice: 1.2,
cacheWritesPrice: 0.375,
cacheReadsPrice: 0.06,
description:
"MiniMax M2.7, the latest MiniMax model with recursive self-improvement capabilities. Available via the Opencode Go plan.",
},
"minimax-m3": {
maxTokens: 131_072,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
includedTools: ["search_and_replace"],
excludedTools: ["apply_diff"],
preserveReasoning: true,
inputPrice: 0.3,
outputPrice: 1.2,
cacheReadsPrice: 0.06,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M2.5 and M2.7 both declare cacheWritesPrice: 0.375, and M3 has supportsPromptCache: true with cache injection active. Is M3 exempt from write charges, or is this a gap?

description:
"MiniMax M3, a frontier multimodal coding model with a 1M context window, agentic reasoning, and tool use. Available via the Opencode Go plan.",
},

// --- Alibaba Qwen ---
"qwen3.6-plus": {
maxTokens: 65_536,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
preserveReasoning: true,
inputPrice: 0.5,
outputPrice: 3.0,
cacheReadsPrice: 0.05,
cacheWritesPrice: 0.625,
longContextPricing: {
thresholdTokens: 256_000,
inputPriceMultiplier: 4,
outputPriceMultiplier: 2,
cacheReadsPriceMultiplier: 4,
cacheWritesPriceMultiplier: 4,
},
description:
"Qwen3.6 Plus - Alibaba's balanced coding and reasoning model with a 1M context window. Available via the Opencode Go plan.",
},
"qwen3.7-plus": {
maxTokens: 65_536,
contextWindow: 1_000_000,
supportsImages: true,
supportsPromptCache: true,
preserveReasoning: true,
inputPrice: 0.4,
outputPrice: 1.6,
cacheReadsPrice: 0.04,
cacheWritesPrice: 0.5,
longContextPricing: {
thresholdTokens: 256_000,
inputPriceMultiplier: 3,
outputPriceMultiplier: 3,
cacheReadsPriceMultiplier: 3,
cacheWritesPriceMultiplier: 3,
},
description:
"Qwen3.7 Plus - Alibaba's multimodal reasoning model with a 1M context window and low-cost agentic coding. Available via the Opencode Go plan.",
},
"qwen3.7-max": {
maxTokens: 65_536,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
preserveReasoning: true,
inputPrice: 2.5,
outputPrice: 7.5,
cacheReadsPrice: 0.5,
cacheWritesPrice: 3.125,
description:
"Qwen3.7 Max - Alibaba's flagship text-only reasoning agent model with a 1M context window, designed for long-horizon agent workflows. Available via the Opencode Go plan.",
},

// --- DeepSeek ---
"deepseek-v4-pro": {
maxTokens: 384_000,
contextWindow: 1_000_000,
Comment on lines +280 to +282

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without supportsMaxTokens: true, getModelMaxOutputTokens clamps the effective default to Math.min(384000, ceil(1000000 × 0.2)) = 200,000 and the UI slider is hidden. Should DeepSeek expose the slider like GLM does?

supportsImages: false,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "low", "medium", "high", "xhigh"],
preserveReasoning: true,
reasoningEffort: "high",
inputPrice: 1.74,
outputPrice: 3.48,
cacheReadsPrice: 0.0145,
description:
"DeepSeek-V4-Pro is DeepSeek's strongest V4 model for reasoning, coding, long-context, and agentic workloads. Available via the Opencode Go plan.",
},
"deepseek-v4-flash": {
maxTokens: 384_000,
contextWindow: 1_000_000,
supportsImages: false,
supportsPromptCache: true,
supportsReasoningEffort: ["disable", "low", "medium", "high", "xhigh"],
preserveReasoning: true,
reasoningEffort: "high",
inputPrice: 0.14,
outputPrice: 0.28,
cacheReadsPrice: 0.0028,
description:
"DeepSeek-V4-Flash is DeepSeek's fast, cost-efficient V4 model supporting thinking and non-thinking modes. Available via the Opencode Go plan.",
},
}

/**
* OpenCode Go models that are only reachable via the Anthropic Messages wire
* format (`/v1/messages`), not the OpenAI-compatible chat completions format
* (`/v1/chat/completions` — referred to by the gateway as "oa-compat").
*
* The Go gateway maps every model to exactly one wire format (see the model
* table at https://opencode.ai/docs/go). Models listed here use
* `@ai-sdk/anthropic`; every other curated model uses
* `@ai-sdk/openai-compatible`. Sending an Anthropic-format model to the
* OpenAI chat completions endpoint is rejected with:
*
* 401 Model <id> is not supported for format oa-compat
*
* This is the set that drives format routing in the handler — keep it in sync
* with the Go model table.
*/
export const OPENCODE_GO_ANTHROPIC_FORMAT_MODELS = new Set<string>([
// --- Alibaba Qwen ---
"qwen3.7-max",
"qwen3.7-plus",
"qwen3.6-plus",
// --- MiniMax ---
"minimax-m3",
"minimax-m2.7",
"minimax-m2.5",
])
Comment on lines +333 to +342

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a test asserting that every ID in this set also exists as a key in opencodeGoModels? A model added to the registry but omitted here will silently hit the OA-compat endpoint and get a 401 at runtime.


/**
* Returns `true` when the given Go-plan model ID must be requested via the
* Anthropic Messages format (`/v1/messages`) rather than the OpenAI-compatible
* chat completions format. Unknown (non-curated) model IDs default to the
* OpenAI-compatible format, matching the gateway's default routing.
*/
export function isOpencodeGoAnthropicFormatModel(modelId: string): boolean {
return OPENCODE_GO_ANTHROPIC_FORMAT_MODELS.has(modelId)
}

/**
* Returns the native {@link ModelInfo} for a Go-plan model ID, or `undefined`
* when the ID is not part of the curated registry. Callers should fall back to
* {@link opencodeGoDefaultModelInfo} when this returns `undefined`.
*/
export function getOpencodeGoModelInfo(modelId: string): ModelInfo | undefined {
return opencodeGoModels[modelId]
}
Loading
Loading