mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 02:24:10 +08:00
fix(provider): send Anthropic's dashed native slug through the AI Gateway (#44281)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -844,7 +844,12 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
|||||||
// The passthrough wrappers inject a CF_TEMP_TOKEN sentinel that the gateway strips before
|
// The passthrough wrappers inject a CF_TEMP_TOKEN sentinel that the gateway strips before
|
||||||
// dispatch, so upstream billing stays on the gateway (Unified Billing / stored BYOK).
|
// dispatch, so upstream billing stays on the gateway (Unified Billing / stored BYOK).
|
||||||
if (modelID.startsWith("openai/")) return aigateway(createOpenAI()(modelID.slice("openai/".length)))
|
if (modelID.startsWith("openai/")) return aigateway(createOpenAI()(modelID.slice("openai/".length)))
|
||||||
if (modelID.startsWith("anthropic/")) return aigateway(createAnthropic()(modelID.slice("anthropic/".length)))
|
// models.dev lists Anthropic ids with dotted versions (claude-haiku-4.5); Anthropic's
|
||||||
|
// Messages API expects dashed native slugs (claude-haiku-4-5), so translate before passing.
|
||||||
|
// No native Anthropic slug contains a dot, so the blanket replacement is lossless here -
|
||||||
|
// unlike OpenAI above, whose native ids (e.g. gpt-4.1) keep their dots and must not be touched.
|
||||||
|
if (modelID.startsWith("anthropic/"))
|
||||||
|
return aigateway(createAnthropic()(modelID.slice("anthropic/".length).replaceAll(".", "-")))
|
||||||
// Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is
|
// Workers AI is the only first-party provider whose upstream is Cloudflare itself, so it is
|
||||||
// the only one that should receive the Cloudflare token as its upstream Authorization header.
|
// the only one that should receive the Cloudflare token as its upstream Authorization header.
|
||||||
// The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as
|
// The Unified API addresses Workers AI both with the explicit "workers-ai/" prefix and as
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ function extractUpstreamHeaders(body: unknown): Record<string, unknown> | undefi
|
|||||||
function gatewayModel(apiId: string, gatewayToken = "test") {
|
function gatewayModel(apiId: string, gatewayToken = "test") {
|
||||||
const aigateway = createAiGateway({ accountId: "test", gateway: "test", apiKey: gatewayToken })
|
const aigateway = createAiGateway({ accountId: "test", gateway: "test", apiKey: gatewayToken })
|
||||||
if (apiId.startsWith("openai/")) return aigateway(createOpenAI()(apiId.slice("openai/".length)))
|
if (apiId.startsWith("openai/")) return aigateway(createOpenAI()(apiId.slice("openai/".length)))
|
||||||
if (apiId.startsWith("anthropic/")) return aigateway(createAnthropic()(apiId.slice("anthropic/".length)))
|
if (apiId.startsWith("anthropic/"))
|
||||||
|
return aigateway(createAnthropic()(apiId.slice("anthropic/".length).replaceAll(".", "-")))
|
||||||
const isWorkersAi = apiId.startsWith("workers-ai/") || apiId.startsWith("@cf/")
|
const isWorkersAi = apiId.startsWith("workers-ai/") || apiId.startsWith("@cf/")
|
||||||
const unified = createUnified(isWorkersAi ? { apiKey: gatewayToken } : {})
|
const unified = createUnified(isWorkersAi ? { apiKey: gatewayToken } : {})
|
||||||
return aigateway(unified(apiId))
|
return aigateway(unified(apiId))
|
||||||
@@ -195,6 +196,17 @@ describe("cf-ai-gateway routing", () => {
|
|||||||
expect(upstream?.model).toBe("claude-sonnet-4-6")
|
expect(upstream?.model).toBe("claude-sonnet-4-6")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("anthropic/* with a dotted models.dev id reaches Anthropic as a dashed native slug", async () => {
|
||||||
|
// models.dev ids are dotted (claude-haiku-4.5); Anthropic's Messages API 404s unless the
|
||||||
|
// version is dashed (claude-haiku-4-5). Regression guard for the dotted-id translation.
|
||||||
|
await callThroughGateway("anthropic/claude-haiku-4.5", {})
|
||||||
|
const step = firstStep(captured?.outerBody)
|
||||||
|
expect(step?.provider).toBe("anthropic")
|
||||||
|
expect(step?.endpoint).toBe("v1/messages")
|
||||||
|
const upstream = extractUpstreamQuery(captured?.outerBody)
|
||||||
|
expect(upstream?.model).toBe("claude-haiku-4-5")
|
||||||
|
})
|
||||||
|
|
||||||
test("workers-ai models stay on the unified /compat route", async () => {
|
test("workers-ai models stay on the unified /compat route", async () => {
|
||||||
await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", {})
|
await callThroughGateway("workers-ai/@cf/moonshotai/kimi-k2.6", {})
|
||||||
const step = firstStep(captured?.outerBody)
|
const step = firstStep(captured?.outerBody)
|
||||||
|
|||||||
Reference in New Issue
Block a user