fix(opencode): support sap-ai-core anthropic opus 4.7+ adaptive reasoning (#29991)

This commit is contained in:
Jérôme Benoit 2026-05-31 01:30:21 +02:00 committed by GitHub
commit f4f508e659
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 81 additions and 123 deletions

View file

@ -597,10 +597,12 @@ function openaiCompatibleReasoningEfforts(id: string) {
} }
function anthropicOpus47OrLater(apiId: string) { function anthropicOpus47OrLater(apiId: string) {
const version = /opus-(\d+)[.-](\d+)(?:[.@-]|$)/i.exec(apiId) // Matches "opus-4.7" (Anthropic/Bedrock/Vertex) and "claude-4.7-opus" (SAP AI Core inverted).
// Greedy \d+ correctly extends to multi-digit majors (e.g. "claude-10.0-opus") for forward compatibility.
const version = /opus-(\d+)[.-](\d+)(?:[.@-]|$)|claude-(\d+)[.-](\d+)-opus(?:[.@-]|$)/i.exec(apiId)
if (!version) return false if (!version) return false
const major = Number(version[1]) const major = Number(version[1] ?? version[3])
const minor = Number(version[2]) const minor = Number(version[2] ?? version[4])
return major > 4 || (major === 4 && minor >= 7) return major > 4 || (major === 4 && minor >= 7)
} }
@ -608,7 +610,18 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
if (anthropicOpus47OrLater(apiId)) { if (anthropicOpus47OrLater(apiId)) {
return ["low", "medium", "high", "xhigh", "max"] return ["low", "medium", "high", "xhigh", "max"]
} }
if (["opus-4-6", "opus-4.6", "sonnet-4-6", "sonnet-4.6"].some((v) => apiId.includes(v))) { if (
[
"opus-4-6",
"opus-4.6",
"4-6-opus",
"4.6-opus",
"sonnet-4-6",
"sonnet-4.6",
"4-6-sonnet",
"4.6-sonnet",
].some((v) => apiId.includes(v))
) {
return ["low", "medium", "high", "max"] return ["low", "medium", "high", "max"]
} }
return null return null
@ -999,6 +1012,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
{ {
thinking: { thinking: {
type: "adaptive", type: "adaptive",
...(adaptiveOpus ? { display: "summarized" } : {}),
}, },
effort, effort,
}, },

View file

@ -3581,142 +3581,86 @@ describe("ProviderTransform.variants", () => {
}) })
describe("@jerome-benoit/sap-ai-provider-v2", () => { describe("@jerome-benoit/sap-ai-provider-v2", () => {
test("anthropic models return thinking variants", () => { const sapModel = (apiId: string) =>
const model = createMockModel({ createMockModel({
id: "sap-ai-core/anthropic--claude-sonnet-4", id: `sap-ai-core/${apiId}`,
providerID: "sap-ai-core", providerID: "sap-ai-core",
api: { api: {
id: "anthropic--claude-sonnet-4", id: apiId,
url: "https://api.ai.sap", url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2", npm: "@jerome-benoit/sap-ai-provider-v2",
}, },
}) })
const result = ProviderTransform.variants(model)
for (const testCase of [
{
name: "sonnet 4.6",
apiIds: ["anthropic--claude-sonnet-4-6"],
efforts: ["low", "medium", "high", "max"],
expectedHigh: { thinking: { type: "adaptive" }, effort: "high" },
},
{
name: "opus 4.6",
apiIds: ["anthropic--claude-4.6-opus", "anthropic--claude-4-6-opus"],
efforts: ["low", "medium", "high", "max"],
expectedHigh: { thinking: { type: "adaptive" }, effort: "high" },
},
{
name: "opus 4.7",
apiIds: ["anthropic--claude-4.7-opus", "anthropic--claude-4-7-opus"],
efforts: ["low", "medium", "high", "xhigh", "max"],
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
},
{
name: "opus 4.8",
apiIds: ["anthropic--claude-4.8-opus", "anthropic--claude-4-8-opus"],
efforts: ["low", "medium", "high", "xhigh", "max"],
expectedHigh: { thinking: { type: "adaptive", display: "summarized" }, effort: "high" },
},
]) {
for (const apiId of testCase.apiIds) {
test(`${testCase.name} ${apiId} returns adaptive thinking variants`, () => {
const result = ProviderTransform.variants(sapModel(apiId))
expect(Object.keys(result)).toEqual(testCase.efforts)
expect(result.high).toEqual(testCase.expectedHigh)
if (testCase.efforts.includes("xhigh")) {
expect(result.xhigh).toEqual({ ...testCase.expectedHigh, effort: "xhigh" })
}
})
}
}
test("anthropic sonnet 4 returns budget-tokens variants", () => {
const result = ProviderTransform.variants(sapModel("anthropic--claude-sonnet-4"))
expect(Object.keys(result)).toEqual(["high", "max"]) expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({ expect(result.high).toEqual({ thinking: { type: "enabled", budgetTokens: 16000 } })
thinking: { expect(result.max).toEqual({ thinking: { type: "enabled", budgetTokens: 31999 } })
type: "enabled",
budgetTokens: 16000,
},
})
expect(result.max).toEqual({
thinking: {
type: "enabled",
budgetTokens: 31999,
},
})
}) })
test("anthropic 4.6 models return adaptive thinking variants", () => { test("gemini 2.5 returns thinkingConfig variants", () => {
const model = createMockModel({ const result = ProviderTransform.variants(sapModel("gcp--gemini-2.5-pro"))
id: "sap-ai-core/anthropic--claude-sonnet-4-6",
providerID: "sap-ai-core",
api: {
id: "anthropic--claude-sonnet-4-6",
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["low", "medium", "high", "max"])
expect(result.low).toEqual({
thinking: {
type: "adaptive",
},
effort: "low",
})
expect(result.max).toEqual({
thinking: {
type: "adaptive",
},
effort: "max",
})
})
test("gemini 2.5 models return thinkingConfig variants", () => {
const model = createMockModel({
id: "sap-ai-core/gcp--gemini-2.5-pro",
providerID: "sap-ai-core",
api: {
id: "gcp--gemini-2.5-pro",
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
})
const result = ProviderTransform.variants(model)
expect(Object.keys(result)).toEqual(["high", "max"]) expect(Object.keys(result)).toEqual(["high", "max"])
expect(result.high).toEqual({ expect(result.high).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } })
thinkingConfig: { expect(result.max).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 24576 } })
includeThoughts: true,
thinkingBudget: 16000,
},
})
expect(result.max).toEqual({
thinkingConfig: {
includeThoughts: true,
thinkingBudget: 24576,
},
})
}) })
test("gpt models return reasoningEffort variants", () => { for (const apiId of ["azure-openai--gpt-4o", "azure-openai--o3-mini"]) {
const model = createMockModel({ test(`${apiId} returns reasoningEffort variants`, () => {
id: "sap-ai-core/azure-openai--gpt-4o", const result = ProviderTransform.variants(sapModel(apiId))
providerID: "sap-ai-core", expect(Object.keys(result)).toEqual(["low", "medium", "high"])
api: { expect(result.low).toEqual({ reasoningEffort: "low" })
id: "azure-openai--gpt-4o", expect(result.high).toEqual({ reasoningEffort: "high" })
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
}) })
const result = ProviderTransform.variants(model) }
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.low).toEqual({ reasoningEffort: "low" })
expect(result.high).toEqual({ reasoningEffort: "high" })
})
test("o-series models return reasoningEffort variants", () => { for (const apiId of ["perplexity--sonar-pro", "mistral--mistral-large"]) {
const model = createMockModel({ test(`${apiId} returns empty object`, () => {
id: "sap-ai-core/azure-openai--o3-mini", expect(ProviderTransform.variants(sapModel(apiId))).toEqual({})
providerID: "sap-ai-core",
api: {
id: "azure-openai--o3-mini",
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
}) })
const result = ProviderTransform.variants(model) }
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
expect(result.low).toEqual({ reasoningEffort: "low" })
expect(result.high).toEqual({ reasoningEffort: "high" })
})
test("sonar models return empty object", () => { test("non-anthropic models with opus-like substrings do not get adaptive thinking", () => {
const model = createMockModel({ expect(ProviderTransform.variants(sapModel("aws--llama-opus-4.7-fake"))).toEqual({})
id: "sap-ai-core/perplexity--sonar-pro",
providerID: "sap-ai-core",
api: {
id: "perplexity--sonar-pro",
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
})
test("mistral models return empty object", () => {
const model = createMockModel({
id: "sap-ai-core/mistral--mistral-large",
providerID: "sap-ai-core",
api: {
id: "mistral--mistral-large",
url: "https://api.ai.sap",
npm: "@jerome-benoit/sap-ai-provider-v2",
},
})
const result = ProviderTransform.variants(model)
expect(result).toEqual({})
}) })
}) })