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:
@@ -12,6 +12,7 @@ function detectProvidersFromOmoConfig(): {
|
||||
hasZaiCodingPlan: boolean
|
||||
hasKimiForCoding: boolean
|
||||
hasOpencodeGo: boolean
|
||||
hasVercelAiGateway: boolean
|
||||
} {
|
||||
const omoConfigPath = getOmoConfigPath()
|
||||
if (!existsSync(omoConfigPath)) {
|
||||
@@ -21,6 +22,7 @@ function detectProvidersFromOmoConfig(): {
|
||||
hasZaiCodingPlan: false,
|
||||
hasKimiForCoding: false,
|
||||
hasOpencodeGo: false,
|
||||
hasVercelAiGateway: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +36,7 @@ function detectProvidersFromOmoConfig(): {
|
||||
hasZaiCodingPlan: false,
|
||||
hasKimiForCoding: false,
|
||||
hasOpencodeGo: false,
|
||||
hasVercelAiGateway: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +46,9 @@ function detectProvidersFromOmoConfig(): {
|
||||
const hasZaiCodingPlan = configStr.includes('"zai-coding-plan/')
|
||||
const hasKimiForCoding = configStr.includes('"kimi-for-coding/')
|
||||
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 {
|
||||
return {
|
||||
hasOpenAI: true,
|
||||
@@ -52,6 +56,7 @@ function detectProvidersFromOmoConfig(): {
|
||||
hasZaiCodingPlan: false,
|
||||
hasKimiForCoding: false,
|
||||
hasOpencodeGo: false,
|
||||
hasVercelAiGateway: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +83,7 @@ export function detectCurrentConfig(): DetectedConfig {
|
||||
hasZaiCodingPlan: false,
|
||||
hasKimiForCoding: false,
|
||||
hasOpencodeGo: false,
|
||||
hasVercelAiGateway: false,
|
||||
}
|
||||
|
||||
const { format, path } = detectConfigFormat()
|
||||
@@ -106,12 +112,13 @@ export function detectCurrentConfig(): DetectedConfig {
|
||||
const providers = openCodeConfig.provider as Record<string, unknown> | undefined
|
||||
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.hasOpencodeZen = hasOpencodeZen
|
||||
result.hasZaiCodingPlan = hasZaiCodingPlan
|
||||
result.hasKimiForCoding = hasKimiForCoding
|
||||
result.hasOpencodeGo = hasOpencodeGo
|
||||
result.hasVercelAiGateway = hasVercelAiGateway
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export function formatConfigSummary(config: InstallConfig): string {
|
||||
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
||||
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("Vercel AI Gateway", config.hasVercelAiGateway, "universal proxy"))
|
||||
|
||||
lines.push("")
|
||||
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)`)
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -167,6 +172,7 @@ export function argsToConfig(args: InstallArgs): InstallConfig {
|
||||
hasZaiCodingPlan: args.zaiCodingPlan === "yes",
|
||||
hasKimiForCoding: args.kimiForCoding === "yes",
|
||||
hasOpencodeGo: args.opencodeGo === "yes",
|
||||
hasVercelAiGateway: args.vercelAiGateway === "yes",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +185,7 @@ export function detectedToInitialValues(detected: DetectedConfig): {
|
||||
zaiCodingPlan: BooleanArg
|
||||
kimiForCoding: BooleanArg
|
||||
opencodeGo: BooleanArg
|
||||
vercelAiGateway: BooleanArg
|
||||
} {
|
||||
let claude: ClaudeSubscription = "no"
|
||||
if (detected.hasClaude) {
|
||||
@@ -194,5 +201,6 @@ kimiForCoding: BooleanArg
|
||||
zaiCodingPlan: detected.hasZaiCodingPlan ? "yes" : "no",
|
||||
kimiForCoding: detected.hasKimiForCoding ? "yes" : "no",
|
||||
opencodeGo: detected.hasOpencodeGo ? "yes" : "no",
|
||||
vercelAiGateway: detected.hasVercelAiGateway ? "yes" : "no",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface ProviderAvailability {
|
||||
zai: boolean
|
||||
kimiForCoding: boolean
|
||||
opencodeGo: boolean
|
||||
vercelAiGateway: boolean
|
||||
isMaxPlan: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
avail.copilot ||
|
||||
avail.zai ||
|
||||
avail.kimiForCoding ||
|
||||
avail.opencodeGo
|
||||
avail.opencodeGo ||
|
||||
avail.vercelAiGateway
|
||||
if (!hasAnyProvider) {
|
||||
return {
|
||||
$schema: SCHEMA_URL,
|
||||
@@ -130,6 +131,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
agentConfig = { model: "opencode-go/minimax-m2.7" }
|
||||
} else if (avail.zai) {
|
||||
agentConfig = { model: ZAI_MODEL }
|
||||
} else if (avail.vercelAiGateway) {
|
||||
agentConfig = { model: "vercel/anthropic/claude-haiku-4.5" }
|
||||
}
|
||||
if (agentConfig) {
|
||||
agents[role] = attachAllFallbackModels(agentConfig, req.fallbackChain, avail)
|
||||
@@ -145,6 +148,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||
agentConfig = { model: "opencode/claude-haiku-4-5" }
|
||||
} else if (avail.opencodeGo) {
|
||||
agentConfig = { model: "opencode-go/minimax-m2.7" }
|
||||
} else if (avail.vercelAiGateway) {
|
||||
agentConfig = { model: "vercel/anthropic/claude-haiku-4.5" }
|
||||
} else if (avail.copilot) {
|
||||
agentConfig = { model: "github-copilot/gpt-5-mini" }
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,7 @@ export function toProviderAvailability(config: InstallConfig): ProviderAvailabil
|
||||
zai: config.hasZaiCodingPlan,
|
||||
kimiForCoding: config.hasKimiForCoding,
|
||||
opencodeGo: config.hasOpencodeGo,
|
||||
vercelAiGateway: config.hasVercelAiGateway,
|
||||
isMaxPlan: config.isMax20,
|
||||
}
|
||||
}
|
||||
@@ -27,6 +28,7 @@ export function isProviderAvailable(provider: string, availability: ProviderAvai
|
||||
"zai-coding-plan": availability.zai,
|
||||
"kimi-for-coding": availability.kimiForCoding,
|
||||
"opencode-go": availability.opencodeGo,
|
||||
vercel: availability.vercelAiGateway,
|
||||
}
|
||||
return mapping[provider] ?? false
|
||||
}
|
||||
|
||||
@@ -110,6 +110,16 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
|
||||
})
|
||||
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 {
|
||||
hasClaude: claude !== "no",
|
||||
isMax20: claude === "max20",
|
||||
@@ -120,5 +130,6 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
|
||||
hasZaiCodingPlan: zaiCodingPlan === "yes",
|
||||
hasKimiForCoding: kimiForCoding === "yes",
|
||||
hasOpencodeGo: opencodeGo === "yes",
|
||||
hasVercelAiGateway: vercelAiGateway === "yes",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface InstallArgs {
|
||||
zaiCodingPlan?: BooleanArg
|
||||
kimiForCoding?: BooleanArg
|
||||
opencodeGo?: BooleanArg
|
||||
vercelAiGateway?: BooleanArg
|
||||
skipAuth?: boolean
|
||||
}
|
||||
|
||||
@@ -24,6 +25,7 @@ export interface InstallConfig {
|
||||
hasZaiCodingPlan: boolean
|
||||
hasKimiForCoding: boolean
|
||||
hasOpencodeGo: boolean
|
||||
hasVercelAiGateway: boolean
|
||||
}
|
||||
|
||||
export interface ConfigMergeResult {
|
||||
@@ -44,4 +46,5 @@ export interface DetectedConfig {
|
||||
hasZaiCodingPlan: boolean
|
||||
hasKimiForCoding: boolean
|
||||
hasOpencodeGo: boolean
|
||||
hasVercelAiGateway: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user