Skip to content

Commit d02ef01

Browse files
author
Jongho Kim
committed
fix: guard Anthropic thinking budgetTokens for unknown output limits
Custom providers without an explicit output limit get limit.output = 0 (sentinel value). The budgetTokens calculation Math.floor(output / 2 - 1) becomes negative, causing "maxOutputTokens must be >= 1" from the API. Extract highBudget() to centralize the formula and its validation (result >= 1024, Anthropic's minimum). Both variants() and the kimi-k2.5 path share this helper, so the guard lives in one place. Fixes #22253
1 parent c98f616 commit d02ef01

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

packages/opencode/src/provider/transform.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,14 @@ export namespace ProviderTransform {
365365
const WIDELY_SUPPORTED_EFFORTS = ["low", "medium", "high"]
366366
const OPENAI_EFFORTS = ["none", "minimal", ...WIDELY_SUPPORTED_EFFORTS, "xhigh"]
367367

368+
// Returns safe budgetTokens for Anthropic's 'high' thinking variant, or undefined
369+
// when the output limit is unknown (0) or the result would be below Anthropic's
370+
// minimum of 1024. Both callers (variants() and kimi-k2.5) share this formula.
371+
function highBudget(output: number): number | undefined {
372+
const tokens = Math.floor(output / 2 - 1)
373+
return tokens >= 1024 ? Math.min(16_000, tokens) : undefined
374+
}
375+
368376
export function variants(model: Provider.Model): Record<string, Record<string, any>> {
369377
if (!model.capabilities.reasoning) return {}
370378

@@ -570,11 +578,14 @@ export namespace ProviderTransform {
570578
)
571579
}
572580

581+
const high = highBudget(model.limit.output)
582+
if (!high) return {}
583+
573584
return {
574585
high: {
575586
thinking: {
576587
type: "enabled",
577-
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
588+
budgetTokens: high,
578589
},
579590
},
580591
max: {
@@ -809,9 +820,12 @@ export namespace ProviderTransform {
809820
(input.model.api.npm === "@ai-sdk/anthropic" || input.model.api.npm === "@ai-sdk/google-vertex/anthropic") &&
810821
(modelId.includes("k2p5") || modelId.includes("kimi-k2.5") || modelId.includes("kimi-k2p5"))
811822
) {
812-
result["thinking"] = {
813-
type: "enabled",
814-
budgetTokens: Math.min(16_000, Math.floor(input.model.limit.output / 2 - 1)),
823+
const high = highBudget(input.model.limit.output)
824+
if (high) {
825+
result["thinking"] = {
826+
type: "enabled",
827+
budgetTokens: high,
828+
}
815829
}
816830
}
817831

packages/opencode/test/provider/transform.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2577,6 +2577,36 @@ describe("ProviderTransform.variants", () => {
25772577
},
25782578
})
25792579
})
2580+
2581+
test("returns empty object when limit.output is 0 (unknown)", () => {
2582+
const model = createMockModel({
2583+
id: "anthropic/claude-4",
2584+
providerID: "anthropic",
2585+
api: {
2586+
id: "claude-4",
2587+
url: "https://api.anthropic.com",
2588+
npm: "@ai-sdk/anthropic",
2589+
},
2590+
limit: { context: 200_000, output: 0 },
2591+
})
2592+
const result = ProviderTransform.variants(model)
2593+
expect(result).toEqual({})
2594+
})
2595+
2596+
test("returns empty object when limit.output is too small for valid budgetTokens", () => {
2597+
const model = createMockModel({
2598+
id: "anthropic/claude-4",
2599+
providerID: "anthropic",
2600+
api: {
2601+
id: "claude-4",
2602+
url: "https://api.anthropic.com",
2603+
npm: "@ai-sdk/anthropic",
2604+
},
2605+
limit: { context: 200_000, output: 2049 },
2606+
})
2607+
const result = ProviderTransform.variants(model)
2608+
expect(result).toEqual({})
2609+
})
25802610
})
25812611

25822612
describe("@ai-sdk/amazon-bedrock", () => {

0 commit comments

Comments
 (0)