feat: add Vercel AI Gateway as a provider

Adds 'vercel' as a recognized provider throughout the model resolution
system, enabling users with Vercel AI Gateway to use it as a universal
fallback for OpenAI, Anthropic, and Google models.

The gateway transform infers the sub-provider from canonical model names
(claude->anthropic, gpt->openai, gemini->google) and delegates to the
appropriate provider-specific transform, producing model strings like
vercel/anthropic/claude-opus-4.6.
This commit is contained in:
Matan Kushner
2026-04-13 11:22:22 +09:00
parent 42df1c3cbb
commit dac0c99e8c
10 changed files with 116 additions and 58 deletions
@@ -12,6 +12,7 @@ function detectProvidersFromOmoConfig(): {
hasZaiCodingPlan: boolean hasZaiCodingPlan: boolean
hasKimiForCoding: boolean hasKimiForCoding: boolean
hasOpencodeGo: boolean hasOpencodeGo: boolean
hasVercelAiGateway: boolean
} { } {
const omoConfigPath = getOmoConfigPath() const omoConfigPath = getOmoConfigPath()
if (!existsSync(omoConfigPath)) { if (!existsSync(omoConfigPath)) {
@@ -21,6 +22,7 @@ function detectProvidersFromOmoConfig(): {
hasZaiCodingPlan: false, hasZaiCodingPlan: false,
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false,
} }
} }
@@ -34,6 +36,7 @@ function detectProvidersFromOmoConfig(): {
hasZaiCodingPlan: false, hasZaiCodingPlan: false,
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false,
} }
} }
@@ -43,8 +46,9 @@ function detectProvidersFromOmoConfig(): {
const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/') const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/')
const hasKimiForCoding = configStr.includes('"kimi-for-coding/') const hasKimiForCoding = configStr.includes('"kimi-for-coding/')
const hasOpencodeGo = configStr.includes('"opencode-go/') const hasOpencodeGo = configStr.includes('"opencode-go/')
const hasVercelAiGateway = configStr.includes('"vercel/')
return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo } return { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo, hasVercelAiGateway }
} catch { } catch {
return { return {
hasOpenAI: true, hasOpenAI: true,
@@ -52,6 +56,7 @@ function detectProvidersFromOmoConfig(): {
hasZaiCodingPlan: false, hasZaiCodingPlan: false,
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false,
} }
} }
} }
@@ -78,6 +83,7 @@ export function detectCurrentConfig(): DetectedConfig {
hasZaiCodingPlan: false, hasZaiCodingPlan: false,
hasKimiForCoding: false, hasKimiForCoding: false,
hasOpencodeGo: false, hasOpencodeGo: false,
hasVercelAiGateway: false,
} }
const { format, path } = detectConfigFormat() const { format, path } = detectConfigFormat()
@@ -106,12 +112,13 @@ export function detectCurrentConfig(): DetectedConfig {
const providers = openCodeConfig.provider as Record<string, unknown> | undefined const providers = openCodeConfig.provider as Record<string, unknown> | undefined
result.hasGemini = providers ? "google" in providers : false result.hasGemini = providers ? "google" in providers : false
const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo } = detectProvidersFromOmoConfig() const { hasOpenAI, hasOpencodeZen, hasZaiCodingPlan, hasKimiForCoding, hasOpencodeGo, hasVercelAiGateway } = detectProvidersFromOmoConfig()
result.hasOpenAI = hasOpenAI result.hasOpenAI = hasOpenAI
result.hasOpencodeZen = hasOpencodeZen result.hasOpencodeZen = hasOpencodeZen
result.hasZaiCodingPlan = hasZaiCodingPlan result.hasZaiCodingPlan = hasZaiCodingPlan
result.hasKimiForCoding = hasKimiForCoding result.hasKimiForCoding = hasKimiForCoding
result.hasOpencodeGo = hasOpencodeGo result.hasOpencodeGo = hasOpencodeGo
result.hasVercelAiGateway = hasVercelAiGateway
return result return result
} }
+8
View File
@@ -40,6 +40,7 @@ export function formatConfigSummary(config: InstallConfig): string {
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models")) lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
lines.push(formatProvider("Z.ai Coding Plan", config.hasZaiCodingPlan, "Librarian/Multimodal")) lines.push(formatProvider("Z.ai Coding Plan", config.hasZaiCodingPlan, "Librarian/Multimodal"))
lines.push(formatProvider("Kimi For Coding", config.hasKimiForCoding, "Sisyphus/Prometheus fallback")) lines.push(formatProvider("Kimi For Coding", config.hasKimiForCoding, "Sisyphus/Prometheus fallback"))
lines.push(formatProvider("Vercel AI Gateway", config.hasVercelAiGateway, "universal proxy"))
lines.push("") lines.push("")
lines.push(color.dim("─".repeat(40))) lines.push(color.dim("─".repeat(40)))
@@ -153,6 +154,10 @@ export function validateNonTuiArgs(args: InstallArgs): { valid: boolean; errors:
errors.push(`Invalid --kimi-for-coding value: ${args.kimiForCoding} (expected: no, yes)`) errors.push(`Invalid --kimi-for-coding value: ${args.kimiForCoding} (expected: no, yes)`)
} }
if (args.vercelAiGateway !== undefined && !["no", "yes"].includes(args.vercelAiGateway)) {
errors.push(`Invalid --vercel-ai-gateway value: ${args.vercelAiGateway} (expected: no, yes)`)
}
return { valid: errors.length === 0, errors } return { valid: errors.length === 0, errors }
} }
@@ -167,6 +172,7 @@ export function argsToConfig(args: InstallArgs): InstallConfig {
hasZaiCodingPlan: args.zaiCodingPlan === "yes", hasZaiCodingPlan: args.zaiCodingPlan === "yes",
hasKimiForCoding: args.kimiForCoding === "yes", hasKimiForCoding: args.kimiForCoding === "yes",
hasOpencodeGo: args.opencodeGo === "yes", hasOpencodeGo: args.opencodeGo === "yes",
hasVercelAiGateway: args.vercelAiGateway === "yes",
} }
} }
@@ -179,6 +185,7 @@ export function detectedToInitialValues(detected: DetectedConfig): {
zaiCodingPlan: BooleanArg zaiCodingPlan: BooleanArg
kimiForCoding: BooleanArg kimiForCoding: BooleanArg
opencodeGo: BooleanArg opencodeGo: BooleanArg
vercelAiGateway: BooleanArg
} { } {
let claude: ClaudeSubscription = "no" let claude: ClaudeSubscription = "no"
if (detected.hasClaude) { if (detected.hasClaude) {
@@ -194,5 +201,6 @@ kimiForCoding: BooleanArg
zaiCodingPlan: detected.hasZaiCodingPlan ? "yes" : "no", zaiCodingPlan: detected.hasZaiCodingPlan ? "yes" : "no",
kimiForCoding: detected.hasKimiForCoding ? "yes" : "no", kimiForCoding: detected.hasKimiForCoding ? "yes" : "no",
opencodeGo: detected.hasOpencodeGo ? "yes" : "no", opencodeGo: detected.hasOpencodeGo ? "yes" : "no",
vercelAiGateway: detected.hasVercelAiGateway ? "yes" : "no",
} }
} }
+1
View File
@@ -11,6 +11,7 @@ export interface ProviderAvailability {
zai: boolean zai: boolean
kimiForCoding: boolean kimiForCoding: boolean
opencodeGo: boolean opencodeGo: boolean
vercelAiGateway: boolean
isMaxPlan: boolean isMaxPlan: boolean
} }
+6 -1
View File
@@ -105,7 +105,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
avail.copilot || avail.copilot ||
avail.zai || avail.zai ||
avail.kimiForCoding || avail.kimiForCoding ||
avail.opencodeGo avail.opencodeGo ||
avail.vercelAiGateway
if (!hasAnyProvider) { if (!hasAnyProvider) {
return { return {
$schema: SCHEMA_URL, $schema: SCHEMA_URL,
@@ -130,6 +131,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
agentConfig = { model: "opencode-go/minimax-m2.7" } agentConfig = { model: "opencode-go/minimax-m2.7" }
} else if (avail.zai) { } else if (avail.zai) {
agentConfig = { model: ZAI_MODEL } agentConfig = { model: ZAI_MODEL }
} else if (avail.vercelAiGateway) {
agentConfig = { model: "vercel/anthropic/claude-haiku-4.5" }
} }
if (agentConfig) { if (agentConfig) {
agents[role] = attachAllFallbackModels(agentConfig, req.fallbackChain, avail) agents[role] = attachAllFallbackModels(agentConfig, req.fallbackChain, avail)
@@ -145,6 +148,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
agentConfig = { model: "opencode/claude-haiku-4-5" } agentConfig = { model: "opencode/claude-haiku-4-5" }
} else if (avail.opencodeGo) { } else if (avail.opencodeGo) {
agentConfig = { model: "opencode-go/minimax-m2.7" } agentConfig = { model: "opencode-go/minimax-m2.7" }
} else if (avail.vercelAiGateway) {
agentConfig = { model: "vercel/anthropic/claude-haiku-4.5" }
} else if (avail.copilot) { } else if (avail.copilot) {
agentConfig = { model: "github-copilot/gpt-5-mini" } agentConfig = { model: "github-copilot/gpt-5-mini" }
} else { } else {
+2
View File
@@ -13,6 +13,7 @@ export function toProviderAvailability(config: InstallConfig): ProviderAvailabil
zai: config.hasZaiCodingPlan, zai: config.hasZaiCodingPlan,
kimiForCoding: config.hasKimiForCoding, kimiForCoding: config.hasKimiForCoding,
opencodeGo: config.hasOpencodeGo, opencodeGo: config.hasOpencodeGo,
vercelAiGateway: config.hasVercelAiGateway,
isMaxPlan: config.isMax20, isMaxPlan: config.isMax20,
} }
} }
@@ -27,6 +28,7 @@ export function isProviderAvailable(provider: string, availability: ProviderAvai
"zai-coding-plan": availability.zai, "zai-coding-plan": availability.zai,
"kimi-for-coding": availability.kimiForCoding, "kimi-for-coding": availability.kimiForCoding,
"opencode-go": availability.opencodeGo, "opencode-go": availability.opencodeGo,
vercel: availability.vercelAiGateway,
} }
return mapping[provider] ?? false return mapping[provider] ?? false
} }
+11
View File
@@ -110,6 +110,16 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
}) })
if (!opencodeGo) return null if (!opencodeGo) return null
const vercelAiGateway = await selectOrCancel({
message: "Do you have a Vercel AI Gateway API key?",
options: [
{ value: "no", label: "No", hint: "Will use other configured providers" },
{ value: "yes", label: "Yes", hint: "Universal proxy for OpenAI, Anthropic, Google, etc." },
],
initialValue: initial.vercelAiGateway,
})
if (!vercelAiGateway) return null
return { return {
hasClaude: claude !== "no", hasClaude: claude !== "no",
isMax20: claude === "max20", isMax20: claude === "max20",
@@ -120,5 +130,6 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
hasZaiCodingPlan: zaiCodingPlan === "yes", hasZaiCodingPlan: zaiCodingPlan === "yes",
hasKimiForCoding: kimiForCoding === "yes", hasKimiForCoding: kimiForCoding === "yes",
hasOpencodeGo: opencodeGo === "yes", hasOpencodeGo: opencodeGo === "yes",
hasVercelAiGateway: vercelAiGateway === "yes",
} }
} }
+3
View File
@@ -11,6 +11,7 @@ export interface InstallArgs {
zaiCodingPlan?: BooleanArg zaiCodingPlan?: BooleanArg
kimiForCoding?: BooleanArg kimiForCoding?: BooleanArg
opencodeGo?: BooleanArg opencodeGo?: BooleanArg
vercelAiGateway?: BooleanArg
skipAuth?: boolean skipAuth?: boolean
} }
@@ -24,6 +25,7 @@ export interface InstallConfig {
hasZaiCodingPlan: boolean hasZaiCodingPlan: boolean
hasKimiForCoding: boolean hasKimiForCoding: boolean
hasOpencodeGo: boolean hasOpencodeGo: boolean
hasVercelAiGateway: boolean
} }
export interface ConfigMergeResult { export interface ConfigMergeResult {
@@ -44,4 +46,5 @@ export interface DetectedConfig {
hasZaiCodingPlan: boolean hasZaiCodingPlan: boolean
hasKimiForCoding: boolean hasKimiForCoding: boolean
hasOpencodeGo: boolean hasOpencodeGo: boolean
hasVercelAiGateway: boolean
} }
+11 -11
View File
@@ -35,7 +35,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(sisyphus.requiresAnyModel).toBe(true) expect(sisyphus.requiresAnyModel).toBe(true)
const primary = sisyphus.fallbackChain[0] const primary = sisyphus.fallbackChain[0]
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-6")
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
@@ -132,7 +132,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(multimodalLooker.fallbackChain).toHaveLength(4) expect(multimodalLooker.fallbackChain).toHaveLength(4)
const primary = multimodalLooker.fallbackChain[0] const primary = multimodalLooker.fallbackChain[0]
expect(primary.providers).toEqual(["openai", "opencode"]) expect(primary.providers).toEqual(["openai", "opencode", "vercel"])
expect(primary.model).toBe("gpt-5.4") expect(primary.model).toBe("gpt-5.4")
expect(primary.variant).toBe("medium") expect(primary.variant).toBe("medium")
@@ -144,7 +144,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(tertiary.model).toBe("glm-4.6v") expect(tertiary.model).toBe("glm-4.6v")
const last = multimodalLooker.fallbackChain[3] const last = multimodalLooker.fallbackChain[3]
expect(last.providers).toEqual(["openai", "github-copilot", "opencode"]) expect(last.providers).toEqual(["openai", "github-copilot", "opencode", "vercel"])
expect(last.model).toBe("gpt-5-nano") expect(last.model).toBe("gpt-5-nano")
}) })
@@ -160,7 +160,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
const primary = prometheus.fallbackChain[0] const primary = prometheus.fallbackChain[0]
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-6")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
}) })
@@ -176,12 +176,12 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
const primary = metis.fallbackChain[0] const primary = metis.fallbackChain[0]
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-6")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
const openAiFallback = metis.fallbackChain.find((entry) => entry.providers.includes("openai")) const openAiFallback = metis.fallbackChain.find((entry) => entry.providers.includes("openai"))
expect(openAiFallback).toEqual({ expect(openAiFallback).toEqual({
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "high", variant: "high",
}) })
@@ -223,7 +223,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
const tertiary = atlas.fallbackChain[2] const tertiary = atlas.fallbackChain[2]
expect(tertiary).toEqual({ expect(tertiary).toEqual({
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "medium", variant: "medium",
}) })
@@ -245,7 +245,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
// then // then
expect(openAiFallback).toEqual({ expect(openAiFallback).toEqual({
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "medium", variant: "medium",
}) })
@@ -261,7 +261,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
// #when - accessing hephaestus requirement // #when - accessing hephaestus requirement
// #then - requiresProvider includes openai, github-copilot, venice, and opencode // #then - requiresProvider includes openai, github-copilot, venice, and opencode
expect(hephaestus).toBeDefined() expect(hephaestus).toBeDefined()
expect(hephaestus.requiresProvider).toEqual(["openai", "github-copilot", "venice", "opencode"]) expect(hephaestus.requiresProvider).toEqual(["openai", "github-copilot", "venice", "opencode", "vercel"])
expect(hephaestus.requiresModel).toBeUndefined() expect(hephaestus.requiresModel).toBeUndefined()
}) })
@@ -415,12 +415,12 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
const primary = unspecifiedHigh.fallbackChain[0] const primary = unspecifiedHigh.fallbackChain[0]
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-6")
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
const secondary = unspecifiedHigh.fallbackChain[1] const secondary = unspecifiedHigh.fallbackChain[1]
expect(secondary.model).toBe("gpt-5.4") expect(secondary.model).toBe("gpt-5.4")
expect(secondary.variant).toBe("high") expect(secondary.variant).toBe("high")
expect(secondary.providers).toEqual(["openai", "github-copilot", "opencode"]) expect(secondary.providers).toEqual(["openai", "github-copilot", "opencode", "vercel"])
}) })
test("artistry has valid fallbackChain with gemini-3.1-pro as primary", () => { test("artistry has valid fallbackChain with gemini-3.1-pro as primary", () => {
+44 -44
View File
@@ -21,7 +21,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
sisyphus: { sisyphus: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
@@ -38,7 +38,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
], ],
model: "kimi-k2.5", model: "kimi-k2.5",
}, },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "medium" }, { providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4", variant: "medium" },
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" }, { providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
{ providers: ["opencode"], model: "big-pickle" }, { providers: ["opencode"], model: "big-pickle" },
], ],
@@ -47,27 +47,27 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
hephaestus: { hephaestus: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["openai", "github-copilot", "venice", "opencode"], providers: ["openai", "github-copilot", "venice", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "medium", variant: "medium",
}, },
], ],
requiresProvider: ["openai", "github-copilot", "venice", "opencode"], requiresProvider: ["openai", "github-copilot", "venice", "opencode", "vercel"],
}, },
oracle: { oracle: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "high", variant: "high",
}, },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
variant: "high", variant: "high",
}, },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
@@ -78,7 +78,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
fallbackChain: [ fallbackChain: [
{ providers: ["opencode-go"], model: "minimax-m2.7" }, { providers: ["opencode-go"], model: "minimax-m2.7" },
{ providers: ["opencode"], model: "minimax-m2.7-highspeed" }, { providers: ["opencode"], model: "minimax-m2.7-highspeed" },
{ providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" }, { providers: ["anthropic", "opencode", "vercel"], model: "claude-haiku-4-5" },
{ providers: ["opencode"], model: "gpt-5-nano" }, { providers: ["opencode"], model: "gpt-5-nano" },
], ],
}, },
@@ -87,33 +87,33 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
{ providers: ["github-copilot", "xai"], model: "grok-code-fast-1" }, { providers: ["github-copilot", "xai"], model: "grok-code-fast-1" },
{ providers: ["opencode-go"], model: "minimax-m2.7-highspeed" }, { providers: ["opencode-go"], model: "minimax-m2.7-highspeed" },
{ providers: ["opencode"], model: "minimax-m2.7" }, { providers: ["opencode"], model: "minimax-m2.7" },
{ providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" }, { providers: ["anthropic", "opencode", "vercel"], model: "claude-haiku-4-5" },
{ providers: ["opencode"], model: "gpt-5-nano" }, { providers: ["opencode"], model: "gpt-5-nano" },
], ],
}, },
"multimodal-looker": { "multimodal-looker": {
fallbackChain: [ fallbackChain: [
{ providers: ["openai", "opencode"], model: "gpt-5.4", variant: "medium" }, { providers: ["openai", "opencode", "vercel"], model: "gpt-5.4", variant: "medium" },
{ providers: ["opencode-go"], model: "kimi-k2.5" }, { providers: ["opencode-go"], model: "kimi-k2.5" },
{ providers: ["zai-coding-plan"], model: "glm-4.6v" }, { providers: ["zai-coding-plan"], model: "glm-4.6v" },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5-nano" }, { providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5-nano" },
], ],
}, },
prometheus: { prometheus: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "high", variant: "high",
}, },
{ providers: ["opencode-go"], model: "glm-5" }, { providers: ["opencode-go"], model: "glm-5" },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
}, },
], ],
@@ -121,12 +121,12 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
metis: { metis: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "high", variant: "high",
}, },
@@ -137,17 +137,17 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
momus: { momus: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "xhigh", variant: "xhigh",
}, },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
variant: "high", variant: "high",
}, },
@@ -156,10 +156,10 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
}, },
atlas: { atlas: {
fallbackChain: [ fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" }, { providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-sonnet-4-6" },
{ providers: ["opencode-go"], model: "kimi-k2.5" }, { providers: ["opencode-go"], model: "kimi-k2.5" },
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "medium", variant: "medium",
}, },
@@ -168,10 +168,10 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
}, },
"sisyphus-junior": { "sisyphus-junior": {
fallbackChain: [ fallbackChain: [
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" }, { providers: ["anthropic", "github-copilot", "opencode", "vercel"], model: "claude-sonnet-4-6" },
{ providers: ["opencode-go"], model: "kimi-k2.5" }, { providers: ["opencode-go"], model: "kimi-k2.5" },
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "medium", variant: "medium",
}, },
@@ -185,13 +185,13 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
"visual-engineering": { "visual-engineering": {
fallbackChain: [ fallbackChain: [
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
variant: "high", variant: "high",
}, },
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" }, { providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
@@ -202,17 +202,17 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
ultrabrain: { ultrabrain: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["openai", "opencode"], providers: ["openai", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "xhigh", variant: "xhigh",
}, },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
variant: "high", variant: "high",
}, },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
@@ -222,17 +222,17 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
deep: { deep: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["openai", "github-copilot", "venice", "opencode"], providers: ["openai", "github-copilot", "venice", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "medium", variant: "medium",
}, },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
variant: "high", variant: "high",
}, },
@@ -241,31 +241,31 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
artistry: { artistry: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3.1-pro", model: "gemini-3.1-pro",
variant: "high", variant: "high",
}, },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4" }, { providers: ["openai", "github-copilot", "opencode", "vercel"], model: "gpt-5.4" },
], ],
requiresModel: "gemini-3.1-pro", requiresModel: "gemini-3.1-pro",
}, },
quick: { quick: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4-mini", model: "gpt-5.4-mini",
}, },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-haiku-4-5", model: "claude-haiku-4-5",
}, },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3-flash", model: "gemini-3-flash",
}, },
{ providers: ["opencode-go"], model: "minimax-m2.7" }, { providers: ["opencode-go"], model: "minimax-m2.7" },
@@ -275,17 +275,17 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
"unspecified-low": { "unspecified-low": {
fallbackChain: [ fallbackChain: [
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-sonnet-4-6", model: "claude-sonnet-4-6",
}, },
{ {
providers: ["openai", "opencode"], providers: ["openai", "opencode", "vercel"],
model: "gpt-5.3-codex", model: "gpt-5.3-codex",
variant: "medium", variant: "medium",
}, },
{ providers: ["opencode-go"], model: "kimi-k2.5" }, { providers: ["opencode-go"], model: "kimi-k2.5" },
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3-flash", model: "gemini-3-flash",
}, },
{ providers: ["opencode-go"], model: "minimax-m2.7" }, { providers: ["opencode-go"], model: "minimax-m2.7" },
@@ -294,12 +294,12 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
"unspecified-high": { "unspecified-high": {
fallbackChain: [ fallbackChain: [
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-opus-4-6", model: "claude-opus-4-6",
variant: "max", variant: "max",
}, },
{ {
providers: ["openai", "github-copilot", "opencode"], providers: ["openai", "github-copilot", "opencode", "vercel"],
model: "gpt-5.4", model: "gpt-5.4",
variant: "high", variant: "high",
}, },
@@ -323,12 +323,12 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
writing: { writing: {
fallbackChain: [ fallbackChain: [
{ {
providers: ["google", "github-copilot", "opencode"], providers: ["google", "github-copilot", "opencode", "vercel"],
model: "gemini-3-flash", model: "gemini-3-flash",
}, },
{ providers: ["opencode-go"], model: "kimi-k2.5" }, { providers: ["opencode-go"], model: "kimi-k2.5" },
{ {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode", "vercel"],
model: "claude-sonnet-4-6", model: "claude-sonnet-4-6",
}, },
{ providers: ["opencode-go"], model: "minimax-m2.7" }, { providers: ["opencode-go"], model: "minimax-m2.7" },
+21
View File
@@ -1,4 +1,25 @@
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"
return undefined
}
export function transformModelForProvider(provider: string, model: string): string { 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}/${transformModelForProvider(subProvider, subModel)}`
}
const subProvider = inferSubProvider(model)
if (subProvider) {
return `${subProvider}/${transformModelForProvider(subProvider, model)}`
}
return model
}
if (provider === "github-copilot") { if (provider === "github-copilot") {
return model return model
.replace("claude-opus-4-6", "claude-opus-4.6") .replace("claude-opus-4-6", "claude-opus-4.6")