mirror of
https://github.com/anomalyco/opencode.git
synced 2026-06-01 22:10:06 +02:00
fix(opencode): support sap-ai-core anthropic opus 4.7+ adaptive reasoning (#29991)
This commit is contained in:
@@ -597,10 +597,12 @@ function openaiCompatibleReasoningEfforts(id: 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
|
||||
const major = Number(version[1])
|
||||
const minor = Number(version[2])
|
||||
const major = Number(version[1] ?? version[3])
|
||||
const minor = Number(version[2] ?? version[4])
|
||||
return major > 4 || (major === 4 && minor >= 7)
|
||||
}
|
||||
|
||||
@@ -608,7 +610,18 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||
if (anthropicOpus47OrLater(apiId)) {
|
||||
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 null
|
||||
@@ -999,6 +1012,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
{
|
||||
thinking: {
|
||||
type: "adaptive",
|
||||
...(adaptiveOpus ? { display: "summarized" } : {}),
|
||||
},
|
||||
effort,
|
||||
},
|
||||
|
||||
@@ -3581,142 +3581,86 @@ describe("ProviderTransform.variants", () => {
|
||||
})
|
||||
|
||||
describe("@jerome-benoit/sap-ai-provider-v2", () => {
|
||||
test("anthropic models return thinking variants", () => {
|
||||
const model = createMockModel({
|
||||
id: "sap-ai-core/anthropic--claude-sonnet-4",
|
||||
const sapModel = (apiId: string) =>
|
||||
createMockModel({
|
||||
id: `sap-ai-core/${apiId}`,
|
||||
providerID: "sap-ai-core",
|
||||
api: {
|
||||
id: "anthropic--claude-sonnet-4",
|
||||
id: apiId,
|
||||
url: "https://api.ai.sap",
|
||||
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(result.high).toEqual({
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budgetTokens: 16000,
|
||||
},
|
||||
})
|
||||
expect(result.max).toEqual({
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budgetTokens: 31999,
|
||||
},
|
||||
})
|
||||
expect(result.high).toEqual({ thinking: { type: "enabled", budgetTokens: 16000 } })
|
||||
expect(result.max).toEqual({ thinking: { type: "enabled", budgetTokens: 31999 } })
|
||||
})
|
||||
|
||||
test("anthropic 4.6 models return adaptive thinking variants", () => {
|
||||
const model = createMockModel({
|
||||
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)
|
||||
test("gemini 2.5 returns thinkingConfig variants", () => {
|
||||
const result = ProviderTransform.variants(sapModel("gcp--gemini-2.5-pro"))
|
||||
expect(Object.keys(result)).toEqual(["high", "max"])
|
||||
expect(result.high).toEqual({
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
thinkingBudget: 16000,
|
||||
},
|
||||
})
|
||||
expect(result.max).toEqual({
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
thinkingBudget: 24576,
|
||||
},
|
||||
})
|
||||
expect(result.high).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } })
|
||||
expect(result.max).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 24576 } })
|
||||
})
|
||||
|
||||
test("gpt models return reasoningEffort variants", () => {
|
||||
const model = createMockModel({
|
||||
id: "sap-ai-core/azure-openai--gpt-4o",
|
||||
providerID: "sap-ai-core",
|
||||
api: {
|
||||
id: "azure-openai--gpt-4o",
|
||||
url: "https://api.ai.sap",
|
||||
npm: "@jerome-benoit/sap-ai-provider-v2",
|
||||
},
|
||||
for (const apiId of ["azure-openai--gpt-4o", "azure-openai--o3-mini"]) {
|
||||
test(`${apiId} returns reasoningEffort variants`, () => {
|
||||
const result = ProviderTransform.variants(sapModel(apiId))
|
||||
expect(Object.keys(result)).toEqual(["low", "medium", "high"])
|
||||
expect(result.low).toEqual({ reasoningEffort: "low" })
|
||||
expect(result.high).toEqual({ reasoningEffort: "high" })
|
||||
})
|
||||
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", () => {
|
||||
const model = createMockModel({
|
||||
id: "sap-ai-core/azure-openai--o3-mini",
|
||||
providerID: "sap-ai-core",
|
||||
api: {
|
||||
id: "azure-openai--o3-mini",
|
||||
url: "https://api.ai.sap",
|
||||
npm: "@jerome-benoit/sap-ai-provider-v2",
|
||||
},
|
||||
for (const apiId of ["perplexity--sonar-pro", "mistral--mistral-large"]) {
|
||||
test(`${apiId} returns empty object`, () => {
|
||||
expect(ProviderTransform.variants(sapModel(apiId))).toEqual({})
|
||||
})
|
||||
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", () => {
|
||||
const model = createMockModel({
|
||||
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({})
|
||||
test("non-anthropic models with opus-like substrings do not get adaptive thinking", () => {
|
||||
expect(ProviderTransform.variants(sapModel("aws--llama-opus-4.7-fake"))).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user