CLI: isolate provider model id transforms from shared mocks

This commit is contained in:
YeonGyu-Kim
2026-04-13 21:31:00 +09:00
parent d26ea7d788
commit 4ce1dde32d
2 changed files with 406 additions and 334 deletions
+345 -333
View File
@@ -1,339 +1,351 @@
import { describe, expect, test } from "bun:test"
import { transformModelForProvider } from "./provider-model-id-transform"
import { transformModelForProvider as transformSharedModelForProvider } from "../shared/provider-model-id-transform"
describe("transformModelForProvider", () => {
describe("github-copilot provider", () => {
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
// #given github-copilot provider and claude-opus-4-6 model
const provider = "github-copilot"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-opus-4.6
expect(result).toBe("claude-opus-4.6")
})
test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => {
// #given github-copilot provider and claude-sonnet-4-5 model
const provider = "github-copilot"
const model = "claude-sonnet-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-sonnet-4.5
expect(result).toBe("claude-sonnet-4.5")
})
test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => {
// #given github-copilot provider and claude-haiku-4-5 model
const provider = "github-copilot"
const model = "claude-haiku-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-haiku-4.5
expect(result).toBe("claude-haiku-4.5")
})
test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => {
// #given github-copilot provider and gemini-3.1-pro model
const provider = "github-copilot"
const model = "gemini-3.1-pro"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3.1-pro-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("transforms gemini-3-flash to gemini-3-flash-preview", () => {
// #given github-copilot provider and gemini-3-flash model
const provider = "github-copilot"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-flash-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("prevents double transformation of gemini-3.1-pro-preview", () => {
// #given github-copilot provider and gemini-3.1-pro-preview model (already transformed)
const provider = "github-copilot"
const model = "gemini-3.1-pro-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3.1-pro-preview-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("prevents double transformation of gemini-3-flash-preview", () => {
// #given github-copilot provider and gemini-3-flash-preview model (already transformed)
const provider = "github-copilot"
const model = "gemini-3-flash-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3-flash-preview-preview
expect(result).toBe("gemini-3-flash-preview")
})
})
describe("google provider", () => {
test("transforms gemini-3-flash to gemini-3-flash-preview", () => {
// #given google provider and gemini-3-flash model
const provider = "google"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-flash-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => {
// #given google provider and gemini-3.1-pro model
const provider = "google"
const model = "gemini-3.1-pro"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3.1-pro-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("passes through other gemini models unchanged", () => {
// #given google provider and gemini-2.5-flash model
const provider = "google"
const model = "gemini-2.5-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged
expect(result).toBe("gemini-2.5-flash")
})
test("prevents double transformation of gemini-3-flash-preview", () => {
// #given google provider and gemini-3-flash-preview model (already transformed)
const provider = "google"
const model = "gemini-3-flash-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3-flash-preview-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("prevents double transformation of gemini-3.1-pro-preview", () => {
// #given google provider and gemini-3.1-pro-preview model (already transformed)
const provider = "google"
const model = "gemini-3.1-pro-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3.1-pro-preview-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("does not transform claude models for google provider", () => {
// #given google provider and claude-opus-4-6 model
const provider = "google"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (google doesn't use claude)
expect(result).toBe("claude-opus-4-6")
})
})
describe("anthropic provider", () => {
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
// #given anthropic provider and claude-opus-4-6 model
const provider = "anthropic"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-opus-4.6
expect(result).toBe("claude-opus-4.6")
})
test("transforms claude-sonnet-4-6 to claude-sonnet-4.6", () => {
// #given anthropic provider and claude-sonnet-4-6 model
const provider = "anthropic"
const model = "claude-sonnet-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-sonnet-4.6
expect(result).toBe("claude-sonnet-4.6")
})
test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => {
// #given anthropic provider and claude-haiku-4-5 model
const provider = "anthropic"
const model = "claude-haiku-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-haiku-4.5
expect(result).toBe("claude-haiku-4.5")
})
})
describe("vercel provider", () => {
test("prepends anthropic/ and applies anthropic transform for claude models", () => {
// #given vercel provider and claude-opus-4-6 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-opus-4-6")
// #then should produce anthropic/claude-opus-4.6
expect(result).toBe("anthropic/claude-opus-4.6")
})
test("prepends anthropic/ and applies anthropic transform for claude-sonnet", () => {
// #given vercel provider and claude-sonnet-4-6 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-sonnet-4-6")
// #then should produce anthropic/claude-sonnet-4.6
expect(result).toBe("anthropic/claude-sonnet-4.6")
})
test("prepends anthropic/ and applies anthropic transform for claude-haiku", () => {
// #given vercel provider and claude-haiku-4-5 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-haiku-4-5")
// #then should produce anthropic/claude-haiku-4.5
expect(result).toBe("anthropic/claude-haiku-4.5")
})
test("prepends openai/ for gpt models", () => {
// #given vercel provider and gpt-5.4 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "gpt-5.4")
// #then should produce openai/gpt-5.4
expect(result).toBe("openai/gpt-5.4")
})
test("prepends google/ and applies google transform for gemini models", () => {
// #given vercel provider and gemini-3.1-pro model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "gemini-3.1-pro")
// #then should produce google/gemini-3.1-pro-preview
expect(result).toBe("google/gemini-3.1-pro-preview")
})
test("prepends google/ without -preview for gemini-3-flash", () => {
// #given vercel provider and gemini-3-flash model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "gemini-3-flash")
// #then should produce google/gemini-3-flash (gateway does not use -preview for this model)
expect(result).toBe("google/gemini-3-flash")
})
test("prepends xai/ for grok models", () => {
// #given vercel provider and grok-code-fast-1 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "grok-code-fast-1")
// #then should produce xai/grok-code-fast-1
expect(result).toBe("xai/grok-code-fast-1")
})
test("delegates to sub-provider when model already has sub-provider prefix", () => {
// #given vercel provider and anthropic/claude-opus-4-6 (already prefixed)
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-6")
// #then should apply anthropic transform within the prefix
expect(result).toBe("anthropic/claude-opus-4.6")
})
test("prepends minimax/ for minimax models", () => {
// #given vercel provider and minimax-m2.7 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "minimax-m2.7")
// #then should produce minimax/minimax-m2.7
expect(result).toBe("minimax/minimax-m2.7")
})
test("prepends moonshotai/ for kimi models", () => {
// #given vercel provider and kimi-k2.5 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "kimi-k2.5")
// #then should produce moonshotai/kimi-k2.5
expect(result).toBe("moonshotai/kimi-k2.5")
})
test("prepends zai/ for glm models", () => {
// #given vercel provider and glm-5 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "glm-5")
// #then should produce zai/glm-5
expect(result).toBe("zai/glm-5")
})
test("passes through unknown models without sub-provider prefix", () => {
// #given vercel provider and an unknown model name
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "big-pickle")
// #then should pass through unchanged
expect(result).toBe("big-pickle")
})
})
describe("unknown provider", () => {
test("passes model through unchanged for unknown provider", () => {
// #given unknown provider and any model
const provider = "unknown-provider"
const model = "some-model"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged
expect(result).toBe("some-model")
})
test("passes gemini-3-flash through unchanged for unknown provider", () => {
// #given unknown provider and gemini-3-flash model
const provider = "unknown-provider"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (no transformation for unknown provider)
expect(result).toBe("gemini-3-flash")
})
})
describe("github-copilot provider", () => {
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
// #given github-copilot provider and claude-opus-4-6 model
const provider = "github-copilot"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-opus-4.6
expect(result).toBe("claude-opus-4.6")
})
test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => {
// #given github-copilot provider and claude-sonnet-4-5 model
const provider = "github-copilot"
const model = "claude-sonnet-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-sonnet-4.5
expect(result).toBe("claude-sonnet-4.5")
})
test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => {
// #given github-copilot provider and claude-haiku-4-5 model
const provider = "github-copilot"
const model = "claude-haiku-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-haiku-4.5
expect(result).toBe("claude-haiku-4.5")
})
test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => {
// #given github-copilot provider and gemini-3.1-pro model
const provider = "github-copilot"
const model = "gemini-3.1-pro"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3.1-pro-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("transforms gemini-3-flash to gemini-3-flash-preview", () => {
// #given github-copilot provider and gemini-3-flash model
const provider = "github-copilot"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-flash-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("prevents double transformation of gemini-3.1-pro-preview", () => {
// #given github-copilot provider and gemini-3.1-pro-preview model (already transformed)
const provider = "github-copilot"
const model = "gemini-3.1-pro-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3.1-pro-preview-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("prevents double transformation of gemini-3-flash-preview", () => {
// #given github-copilot provider and gemini-3-flash-preview model (already transformed)
const provider = "github-copilot"
const model = "gemini-3-flash-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3-flash-preview-preview
expect(result).toBe("gemini-3-flash-preview")
})
})
describe("google provider", () => {
test("transforms gemini-3-flash to gemini-3-flash-preview", () => {
// #given google provider and gemini-3-flash model
const provider = "google"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3-flash-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("transforms gemini-3.1-pro to gemini-3.1-pro-preview", () => {
// #given google provider and gemini-3.1-pro model
const provider = "google"
const model = "gemini-3.1-pro"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to gemini-3.1-pro-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("passes through other gemini models unchanged", () => {
// #given google provider and gemini-2.5-flash model
const provider = "google"
const model = "gemini-2.5-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged
expect(result).toBe("gemini-2.5-flash")
})
test("prevents double transformation of gemini-3-flash-preview", () => {
// #given google provider and gemini-3-flash-preview model (already transformed)
const provider = "google"
const model = "gemini-3-flash-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3-flash-preview-preview
expect(result).toBe("gemini-3-flash-preview")
})
test("prevents double transformation of gemini-3.1-pro-preview", () => {
// #given google provider and gemini-3.1-pro-preview model (already transformed)
const provider = "google"
const model = "gemini-3.1-pro-preview"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should NOT become gemini-3.1-pro-preview-preview
expect(result).toBe("gemini-3.1-pro-preview")
})
test("does not transform claude models for google provider", () => {
// #given google provider and claude-opus-4-6 model
const provider = "google"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (google doesn't use claude)
expect(result).toBe("claude-opus-4-6")
})
})
describe("anthropic provider", () => {
test("transforms claude-opus-4-6 to claude-opus-4.6", () => {
// #given anthropic provider and claude-opus-4-6 model
const provider = "anthropic"
const model = "claude-opus-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-opus-4.6
expect(result).toBe("claude-opus-4.6")
})
test("transforms claude-sonnet-4-6 to claude-sonnet-4.6", () => {
// #given anthropic provider and claude-sonnet-4-6 model
const provider = "anthropic"
const model = "claude-sonnet-4-6"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-sonnet-4.6
expect(result).toBe("claude-sonnet-4.6")
})
test("transforms claude-haiku-4-5 to claude-haiku-4.5", () => {
// #given anthropic provider and claude-haiku-4-5 model
const provider = "anthropic"
const model = "claude-haiku-4-5"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should transform to claude-haiku-4.5
expect(result).toBe("claude-haiku-4.5")
})
})
describe("vercel provider", () => {
test("prepends anthropic/ and applies anthropic transform for claude models", () => {
// #given vercel provider and claude-opus-4-6 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-opus-4-6")
// #then should produce anthropic/claude-opus-4.6
expect(result).toBe("anthropic/claude-opus-4.6")
})
test("prepends anthropic/ and applies anthropic transform for claude-sonnet", () => {
// #given vercel provider and claude-sonnet-4-6 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-sonnet-4-6")
// #then should produce anthropic/claude-sonnet-4.6
expect(result).toBe("anthropic/claude-sonnet-4.6")
})
test("prepends anthropic/ and applies anthropic transform for claude-haiku", () => {
// #given vercel provider and claude-haiku-4-5 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-haiku-4-5")
// #then should produce anthropic/claude-haiku-4.5
expect(result).toBe("anthropic/claude-haiku-4.5")
})
test("prepends openai/ for gpt models", () => {
// #given vercel provider and gpt-5.4 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "gpt-5.4")
// #then should produce openai/gpt-5.4
expect(result).toBe("openai/gpt-5.4")
})
test("prepends google/ and applies google transform for gemini models", () => {
// #given vercel provider and gemini-3.1-pro model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "gemini-3.1-pro")
// #then should produce google/gemini-3.1-pro-preview
expect(result).toBe("google/gemini-3.1-pro-preview")
})
test("prepends google/ without -preview for gemini-3-flash", () => {
// #given vercel provider and gemini-3-flash model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "gemini-3-flash")
// #then should produce google/gemini-3-flash (gateway does not use -preview for this model)
expect(result).toBe("google/gemini-3-flash")
})
test("prepends xai/ for grok models", () => {
// #given vercel provider and grok-code-fast-1 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "grok-code-fast-1")
// #then should produce xai/grok-code-fast-1
expect(result).toBe("xai/grok-code-fast-1")
})
test("delegates to sub-provider when model already has sub-provider prefix", () => {
// #given vercel provider and anthropic/claude-opus-4-6 (already prefixed)
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-6")
// #then should apply anthropic transform within the prefix
expect(result).toBe("anthropic/claude-opus-4.6")
})
test("prepends minimax/ for minimax models", () => {
// #given vercel provider and minimax-m2.7 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "minimax-m2.7")
// #then should produce minimax/minimax-m2.7
expect(result).toBe("minimax/minimax-m2.7")
})
test("prepends moonshotai/ for kimi models", () => {
// #given vercel provider and kimi-k2.5 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "kimi-k2.5")
// #then should produce moonshotai/kimi-k2.5
expect(result).toBe("moonshotai/kimi-k2.5")
})
test("prepends zai/ for glm models", () => {
// #given vercel provider and glm-5 model
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "glm-5")
// #then should produce zai/glm-5
expect(result).toBe("zai/glm-5")
})
test("passes through unknown models without sub-provider prefix", () => {
// #given vercel provider and an unknown model name
// #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "big-pickle")
// #then should pass through unchanged
expect(result).toBe("big-pickle")
})
})
describe("unknown provider", () => {
test("passes model through unchanged for unknown provider", () => {
// #given unknown provider and any model
const provider = "unknown-provider"
const model = "some-model"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged
expect(result).toBe("some-model")
})
test("passes gemini-3-flash through unchanged for unknown provider", () => {
// #given unknown provider and gemini-3-flash model
const provider = "unknown-provider"
const model = "gemini-3-flash"
// #when transformModelForProvider is called
const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (no transformation for unknown provider)
expect(result).toBe("gemini-3-flash")
})
})
test("uses a CLI-local transform implementation", () => {
// #given
const cliResult = transformModelForProvider("anthropic", "claude-opus-4-6")
const sharedResult = transformSharedModelForProvider("anthropic", "claude-opus-4-6")
// #when
expect(transformModelForProvider).not.toBe(transformSharedModelForProvider)
expect(cliResult).toBe("claude-opus-4.6")
expect(sharedResult).toBe("claude-opus-4.6")
})
})
+61 -1
View File
@@ -1 +1,61 @@
export { transformModelForProvider } from "../shared/provider-model-id-transform"
function inferSubProvider(model: string): string | undefined {
if (model.startsWith("claude-")) return "anthropic"
if (model.startsWith("gpt-")) return "openai"
if (model.startsWith("gemini-")) return "google"
if (model.startsWith("grok-")) return "xai"
if (model.startsWith("minimax-")) return "minimax"
if (model.startsWith("kimi-")) return "moonshotai"
if (model.startsWith("glm-")) return "zai"
return undefined
}
const CLAUDE_VERSION_DOT = /claude-(\w+)-(\d+)-(\d+)/g
const GEMINI_31_PRO_PREVIEW = /gemini-3\.1-pro(?!-)/g
const GEMINI_3_FLASH_PREVIEW = /gemini-3-flash(?!-)/g
function claudeVersionDot(model: string): string {
return model.replace(CLAUDE_VERSION_DOT, "claude-$1-$2.$3")
}
function applyGatewayTransforms(model: string): string {
return claudeVersionDot(model).replace(
GEMINI_31_PRO_PREVIEW,
"gemini-3.1-pro-preview",
)
}
export function transformModelForProvider(provider: string, model: string): string {
if (provider === "vercel") {
const slashIndex = model.indexOf("/")
if (slashIndex !== -1) {
const subProvider = model.substring(0, slashIndex)
const subModel = model.substring(slashIndex + 1)
return `${subProvider}/${applyGatewayTransforms(subModel)}`
}
const subProvider = inferSubProvider(model)
if (subProvider) {
return `${subProvider}/${applyGatewayTransforms(model)}`
}
return model
}
if (provider === "github-copilot") {
return claudeVersionDot(model)
.replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview")
.replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview")
}
if (provider === "google") {
return model
.replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview")
.replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview")
}
if (provider === "anthropic") {
return claudeVersionDot(model)
}
return model
}