fix(opencode): support sap-ai-core anthropic opus 4.7+ adaptive reasoning (#29991)
This commit is contained in:
parent
30f9780561
commit
f4f508e659
2 changed files with 81 additions and 123 deletions
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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({})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue