Skip to content

Commit f6948d0

Browse files
authored
fix: variant logic for anthropic models through openai compat endpoint (anomalyco#11665)
1 parent d52ee41 commit f6948d0

File tree

2 files changed

+2
-170
lines changed

2 files changed

+2
-170
lines changed

packages/opencode/src/provider/transform.ts

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -395,31 +395,6 @@ export namespace ProviderTransform {
395395
case "@ai-sdk/deepinfra":
396396
// https://v5.ai-sdk.dev/providers/ai-sdk-providers/deepinfra
397397
case "@ai-sdk/openai-compatible":
398-
// When using openai-compatible SDK with Claude/Anthropic models,
399-
// we must use snake_case (budget_tokens) as the SDK doesn't convert parameter names
400-
// and the OpenAI-compatible API spec uses snake_case
401-
if (
402-
model.providerID === "anthropic" ||
403-
model.api.id.includes("anthropic") ||
404-
model.api.id.includes("claude") ||
405-
model.id.includes("anthropic") ||
406-
model.id.includes("claude")
407-
) {
408-
return {
409-
high: {
410-
thinking: {
411-
type: "enabled",
412-
budget_tokens: 16000,
413-
},
414-
},
415-
max: {
416-
thinking: {
417-
type: "enabled",
418-
budget_tokens: 31999,
419-
},
420-
},
421-
}
422-
}
423398
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { reasoningEffort: effort }]))
424399

425400
case "@ai-sdk/azure":
@@ -719,21 +694,9 @@ export namespace ProviderTransform {
719694
const modelCap = modelLimit || globalLimit
720695
const standardLimit = Math.min(modelCap, globalLimit)
721696

722-
// Handle thinking mode for @ai-sdk/anthropic, @ai-sdk/google-vertex/anthropic (budgetTokens)
723-
// and @ai-sdk/openai-compatible with Claude (budget_tokens)
724-
if (
725-
npm === "@ai-sdk/anthropic" ||
726-
npm === "@ai-sdk/google-vertex/anthropic" ||
727-
npm === "@ai-sdk/openai-compatible"
728-
) {
697+
if (npm === "@ai-sdk/anthropic" || npm === "@ai-sdk/google-vertex/anthropic") {
729698
const thinking = options?.["thinking"]
730-
// Support both camelCase (for @ai-sdk/anthropic) and snake_case (for openai-compatible)
731-
const budgetTokens =
732-
typeof thinking?.["budgetTokens"] === "number"
733-
? thinking["budgetTokens"]
734-
: typeof thinking?.["budget_tokens"] === "number"
735-
? thinking["budget_tokens"]
736-
: 0
699+
const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
737700
const enabled = thinking?.["type"] === "enabled"
738701
if (enabled && budgetTokens > 0) {
739702
// Return text tokens so that text + thinking <= model cap, preferring 32k text when possible.

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

Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -267,76 +267,6 @@ describe("ProviderTransform.maxOutputTokens", () => {
267267
expect(result).toBe(OUTPUT_TOKEN_MAX)
268268
})
269269
})
270-
271-
describe("openai-compatible with thinking options (snake_case)", () => {
272-
test("returns 32k when budget_tokens + 32k <= modelLimit", () => {
273-
const modelLimit = 100000
274-
const options = {
275-
thinking: {
276-
type: "enabled",
277-
budget_tokens: 10000,
278-
},
279-
}
280-
const result = ProviderTransform.maxOutputTokens(
281-
"@ai-sdk/openai-compatible",
282-
options,
283-
modelLimit,
284-
OUTPUT_TOKEN_MAX,
285-
)
286-
expect(result).toBe(OUTPUT_TOKEN_MAX)
287-
})
288-
289-
test("returns modelLimit - budget_tokens when budget_tokens + 32k > modelLimit", () => {
290-
const modelLimit = 50000
291-
const options = {
292-
thinking: {
293-
type: "enabled",
294-
budget_tokens: 30000,
295-
},
296-
}
297-
const result = ProviderTransform.maxOutputTokens(
298-
"@ai-sdk/openai-compatible",
299-
options,
300-
modelLimit,
301-
OUTPUT_TOKEN_MAX,
302-
)
303-
expect(result).toBe(20000)
304-
})
305-
306-
test("returns 32k when thinking type is not enabled", () => {
307-
const modelLimit = 100000
308-
const options = {
309-
thinking: {
310-
type: "disabled",
311-
budget_tokens: 10000,
312-
},
313-
}
314-
const result = ProviderTransform.maxOutputTokens(
315-
"@ai-sdk/openai-compatible",
316-
options,
317-
modelLimit,
318-
OUTPUT_TOKEN_MAX,
319-
)
320-
expect(result).toBe(OUTPUT_TOKEN_MAX)
321-
})
322-
323-
test("returns 32k when budget_tokens is 0", () => {
324-
const modelLimit = 100000
325-
const options = {
326-
thinking: {
327-
type: "enabled",
328-
budget_tokens: 0,
329-
},
330-
}
331-
const result = ProviderTransform.maxOutputTokens(
332-
"@ai-sdk/openai-compatible",
333-
options,
334-
modelLimit,
335-
OUTPUT_TOKEN_MAX,
336-
)
337-
expect(result).toBe(OUTPUT_TOKEN_MAX)
338-
})
339-
})
340270
})
341271

342272
describe("ProviderTransform.schema - gemini array items", () => {
@@ -1564,67 +1494,6 @@ describe("ProviderTransform.variants", () => {
15641494
expect(result.low).toEqual({ reasoningEffort: "low" })
15651495
expect(result.high).toEqual({ reasoningEffort: "high" })
15661496
})
1567-
1568-
test("Claude via LiteLLM returns thinking with snake_case budget_tokens", () => {
1569-
const model = createMockModel({
1570-
id: "anthropic/claude-sonnet-4-5",
1571-
providerID: "anthropic",
1572-
api: {
1573-
id: "claude-sonnet-4-5-20250929",
1574-
url: "http://localhost:4000",
1575-
npm: "@ai-sdk/openai-compatible",
1576-
},
1577-
})
1578-
const result = ProviderTransform.variants(model)
1579-
expect(Object.keys(result)).toEqual(["high", "max"])
1580-
expect(result.high).toEqual({
1581-
thinking: {
1582-
type: "enabled",
1583-
budget_tokens: 16000,
1584-
},
1585-
})
1586-
expect(result.max).toEqual({
1587-
thinking: {
1588-
type: "enabled",
1589-
budget_tokens: 31999,
1590-
},
1591-
})
1592-
})
1593-
1594-
test("Claude model (by model.id) via openai-compatible uses snake_case", () => {
1595-
const model = createMockModel({
1596-
id: "litellm/claude-3-opus",
1597-
providerID: "litellm",
1598-
api: {
1599-
id: "claude-3-opus-20240229",
1600-
url: "http://localhost:4000",
1601-
npm: "@ai-sdk/openai-compatible",
1602-
},
1603-
})
1604-
const result = ProviderTransform.variants(model)
1605-
expect(Object.keys(result)).toEqual(["high", "max"])
1606-
expect(result.high).toEqual({
1607-
thinking: {
1608-
type: "enabled",
1609-
budget_tokens: 16000,
1610-
},
1611-
})
1612-
})
1613-
1614-
test("Anthropic model (by model.api.id) via openai-compatible uses snake_case", () => {
1615-
const model = createMockModel({
1616-
id: "custom/my-model",
1617-
providerID: "custom",
1618-
api: {
1619-
id: "anthropic.claude-sonnet",
1620-
url: "http://localhost:4000",
1621-
npm: "@ai-sdk/openai-compatible",
1622-
},
1623-
})
1624-
const result = ProviderTransform.variants(model)
1625-
expect(Object.keys(result)).toEqual(["high", "max"])
1626-
expect(result.high.thinking.budget_tokens).toBe(16000)
1627-
})
16281497
})
16291498

16301499
describe("@ai-sdk/azure", () => {

0 commit comments

Comments
 (0)