Merge pull request #3376 from matchai/feat/vercel-ai-gateway
feat(provider): add Vercel AI Gateway support
This commit is contained in:
@@ -4137,3 +4137,587 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`generateModelConfig Vercel AI Gateway provider uses vercel/ model strings when only Vercel AI Gateway is available 1`] = `
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
|
||||||
|
"agents": {
|
||||||
|
"atlas": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
"explore": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/xai/grok-code-fast-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/minimax/minimax-m2.7-highspeed",
|
||||||
|
},
|
||||||
|
"hephaestus": {
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
"librarian": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7-highspeed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
"metis": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"momus": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "xhigh",
|
||||||
|
},
|
||||||
|
"multimodal-looker": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-4.6v",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
"oracle": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
"prometheus": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"sisyphus": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"sisyphus-junior": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"artistry": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
"deep": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
"quick": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4-mini",
|
||||||
|
},
|
||||||
|
"ultrabrain": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "xhigh",
|
||||||
|
},
|
||||||
|
"unspecified-high": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.3-codex",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
"unspecified-low": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.3-codex",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
"visual-engineering": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
"writing": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`generateModelConfig Vercel AI Gateway provider uses vercel/ model strings with isMax20 flag 1`] = `
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json",
|
||||||
|
"agents": {
|
||||||
|
"atlas": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
"explore": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/xai/grok-code-fast-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/minimax/minimax-m2.7-highspeed",
|
||||||
|
},
|
||||||
|
"hephaestus": {
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
"librarian": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7-highspeed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
"metis": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"momus": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "xhigh",
|
||||||
|
},
|
||||||
|
"multimodal-looker": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-4.6v",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
"oracle": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
"prometheus": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"sisyphus": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"sisyphus-junior": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"artistry": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
"deep": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
"quick": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-haiku-4.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5-nano",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4-mini",
|
||||||
|
},
|
||||||
|
"ultrabrain": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "xhigh",
|
||||||
|
},
|
||||||
|
"unspecified-high": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.4",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
"unspecified-low": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/openai/gpt-5.3-codex",
|
||||||
|
"variant": "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
"visual-engineering": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/zai/glm-5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-opus-4.6",
|
||||||
|
"variant": "max",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/google/gemini-3.1-pro-preview",
|
||||||
|
"variant": "high",
|
||||||
|
},
|
||||||
|
"writing": {
|
||||||
|
"fallback_models": [
|
||||||
|
{
|
||||||
|
"model": "vercel/moonshotai/kimi-k2.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/anthropic/claude-sonnet-4.6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "vercel/minimax/minimax-m2.7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"model": "vercel/google/gemini-3-flash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ describe("runCliInstaller telemetry isolation", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}),
|
}),
|
||||||
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
||||||
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
|
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ describe("runCliInstaller", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}),
|
}),
|
||||||
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
||||||
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"),
|
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"),
|
||||||
@@ -83,6 +84,7 @@ describe("runCliInstaller", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}),
|
}),
|
||||||
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
||||||
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
|
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ export async function runCliInstaller(args: InstallArgs, version: string): Promi
|
|||||||
!config.hasOpenAI &&
|
!config.hasOpenAI &&
|
||||||
!config.hasGemini &&
|
!config.hasGemini &&
|
||||||
!config.hasCopilot &&
|
!config.hasCopilot &&
|
||||||
!config.hasOpencodeZen
|
!config.hasOpencodeZen &&
|
||||||
|
!config.hasVercelAiGateway
|
||||||
) {
|
) {
|
||||||
printWarning("No model providers configured. Using opencode/big-pickle as fallback.")
|
printWarning("No model providers configured. Using opencode/big-pickle as fallback.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ program
|
|||||||
.option("--zai-coding-plan <value>", "Z.ai Coding Plan subscription: no, yes (default: no)")
|
.option("--zai-coding-plan <value>", "Z.ai Coding Plan subscription: no, yes (default: no)")
|
||||||
.option("--kimi-for-coding <value>", "Kimi For Coding subscription: no, yes (default: no)")
|
.option("--kimi-for-coding <value>", "Kimi For Coding subscription: no, yes (default: no)")
|
||||||
.option("--opencode-go <value>", "OpenCode Go subscription: no, yes (default: no)")
|
.option("--opencode-go <value>", "OpenCode Go subscription: no, yes (default: no)")
|
||||||
|
.option("--vercel-ai-gateway <value>", "Vercel AI Gateway: no, yes (default: no)")
|
||||||
.option("--skip-auth", "Skip authentication setup hints")
|
.option("--skip-auth", "Skip authentication setup hints")
|
||||||
.addHelpText("after", `
|
.addHelpText("after", `
|
||||||
Examples:
|
Examples:
|
||||||
@@ -40,14 +41,15 @@ Examples:
|
|||||||
$ bunx oh-my-opencode install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no
|
$ bunx oh-my-opencode install --no-tui --claude=max20 --openai=yes --gemini=yes --copilot=no
|
||||||
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes
|
$ bunx oh-my-opencode install --no-tui --claude=no --gemini=no --copilot=yes --opencode-zen=yes
|
||||||
|
|
||||||
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
|
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi > Vercel):
|
||||||
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
||||||
OpenAI Native openai/ models (GPT-5.4 for Oracle)
|
OpenAI Native openai/ models (GPT-5.4 for Oracle)
|
||||||
Gemini Native google/ models (Gemini 3.1 Pro, Flash)
|
Gemini Native google/ models (Gemini 3.1 Pro, Flash)
|
||||||
Copilot github-copilot/ models (fallback)
|
Copilot github-copilot/ models (fallback)
|
||||||
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
|
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
|
||||||
Z.ai zai-coding-plan/glm-5 (visual-engineering fallback)
|
Z.ai zai-coding-plan/glm-5 (visual-engineering fallback)
|
||||||
Kimi kimi-for-coding/k2p5 (Sisyphus/Prometheus fallback)
|
Kimi kimi-for-coding/k2p5 (Sisyphus/Prometheus fallback)
|
||||||
|
Vercel vercel/ models (universal proxy, always last fallback)
|
||||||
`)
|
`)
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const args: InstallArgs = {
|
const args: InstallArgs = {
|
||||||
@@ -60,6 +62,7 @@ Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
|
|||||||
zaiCodingPlan: options.zaiCodingPlan,
|
zaiCodingPlan: options.zaiCodingPlan,
|
||||||
kimiForCoding: options.kimiForCoding,
|
kimiForCoding: options.kimiForCoding,
|
||||||
opencodeGo: options.opencodeGo,
|
opencodeGo: options.opencodeGo,
|
||||||
|
vercelAiGateway: options.vercelAiGateway,
|
||||||
skipAuth: options.skipAuth ?? false,
|
skipAuth: options.skipAuth ?? false,
|
||||||
}
|
}
|
||||||
const exitCode = await install(args)
|
const exitCode = await install(args)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -42,6 +43,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -64,6 +66,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: true,
|
hasZaiCodingPlan: true,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -71,7 +74,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
|
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
|
||||||
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4.6")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("uses native OpenAI models when only ChatGPT available", () => {
|
test("uses native OpenAI models when only ChatGPT available", () => {
|
||||||
@@ -86,6 +89,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -110,6 +114,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -126,7 +131,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
}>
|
}>
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6")
|
||||||
expect(agents.sisyphus.fallback_models).toEqual([
|
expect(agents.sisyphus.fallback_models).toEqual([
|
||||||
{
|
{
|
||||||
model: "openai/gpt-5.4",
|
model: "openai/gpt-5.4",
|
||||||
@@ -136,7 +141,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
expect(categories.deep.model).toBe("openai/gpt-5.4")
|
expect(categories.deep.model).toBe("openai/gpt-5.4")
|
||||||
expect(categories.deep.fallback_models).toEqual([
|
expect(categories.deep.fallback_models).toEqual([
|
||||||
{
|
{
|
||||||
model: "anthropic/claude-opus-4-6",
|
model: "anthropic/claude-opus-4.6",
|
||||||
variant: "max",
|
variant: "max",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -154,6 +159,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -175,6 +181,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const installConfig: InstallConfig = {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRecord(value: unknown): Record<string, unknown> {
|
function getRecord(value: unknown): Record<string, unknown> {
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ function createConfig(overrides: Partial<InstallConfig> = {}): InstallConfig {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
...overrides,
|
...overrides,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,6 +608,74 @@ describe("generateModelConfig", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Vercel AI Gateway provider", () => {
|
||||||
|
test("uses vercel/ model strings when only Vercel AI Gateway is available", () => {
|
||||||
|
// #given only Vercel AI Gateway is available
|
||||||
|
const config = createConfig({ hasVercelAiGateway: true })
|
||||||
|
|
||||||
|
// #when generateModelConfig is called
|
||||||
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
|
// #then should use vercel/<sub-provider>/<model> format
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("uses vercel/ model strings with isMax20 flag", () => {
|
||||||
|
// #given Vercel AI Gateway is available with Max 20 plan
|
||||||
|
const config = createConfig({ hasVercelAiGateway: true, isMax20: true })
|
||||||
|
|
||||||
|
// #when generateModelConfig is called
|
||||||
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
|
// #then should use higher capability models via gateway
|
||||||
|
expect(result).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("explore uses vercel/minimax/minimax-m2.7-highspeed when only gateway available", () => {
|
||||||
|
// #given only Vercel AI Gateway is available
|
||||||
|
const config = createConfig({ hasVercelAiGateway: true })
|
||||||
|
|
||||||
|
// #when generateModelConfig is called
|
||||||
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
|
// #then explore should use gateway-routed minimax (preferred over claude-haiku)
|
||||||
|
expect(result.agents?.explore?.model).toBe("vercel/minimax/minimax-m2.7-highspeed")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("librarian uses vercel/minimax/minimax-m2.7 when only gateway available", () => {
|
||||||
|
// #given only Vercel AI Gateway is available
|
||||||
|
const config = createConfig({ hasVercelAiGateway: true })
|
||||||
|
|
||||||
|
// #when generateModelConfig is called
|
||||||
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
|
// #then librarian should use gateway-routed minimax (preferred over claude-haiku)
|
||||||
|
expect(result.agents?.librarian?.model).toBe("vercel/minimax/minimax-m2.7")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Hephaestus is created when only Vercel AI Gateway is available", () => {
|
||||||
|
// #given only Vercel AI Gateway is available
|
||||||
|
const config = createConfig({ hasVercelAiGateway: true })
|
||||||
|
|
||||||
|
// #when generateModelConfig is called
|
||||||
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
|
// #then hephaestus should be created with gateway-routed gpt-5.4
|
||||||
|
expect(result.agents?.hephaestus?.model).toBe("vercel/openai/gpt-5.4")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("native providers take priority over gateway", () => {
|
||||||
|
// #given Claude and Vercel AI Gateway are both available
|
||||||
|
const config = createConfig({ hasClaude: true, hasVercelAiGateway: true })
|
||||||
|
|
||||||
|
// #when generateModelConfig is called
|
||||||
|
const result = generateModelConfig(config)
|
||||||
|
|
||||||
|
// #then should prefer native anthropic over gateway
|
||||||
|
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4.6")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("schema URL", () => {
|
describe("schema URL", () => {
|
||||||
test("always includes correct schema URL", () => {
|
test("always includes correct schema URL", () => {
|
||||||
// #given any config
|
// #given any config
|
||||||
|
|||||||
@@ -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/minimax/minimax-m2.7" }
|
||||||
}
|
}
|
||||||
if (agentConfig) {
|
if (agentConfig) {
|
||||||
agents[role] = attachAllFallbackModels(agentConfig, req.fallbackChain, avail)
|
agents[role] = attachAllFallbackModels(agentConfig, req.fallbackChain, avail)
|
||||||
@@ -147,6 +150,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
|||||||
agentConfig = { model: "opencode-go/minimax-m2.7" }
|
agentConfig = { model: "opencode-go/minimax-m2.7" }
|
||||||
} else if (avail.copilot) {
|
} else if (avail.copilot) {
|
||||||
agentConfig = { model: "github-copilot/gpt-5-mini" }
|
agentConfig = { model: "github-copilot/gpt-5-mini" }
|
||||||
|
} else if (avail.vercelAiGateway) {
|
||||||
|
agentConfig = { model: "vercel/minimax/minimax-m2.7-highspeed" }
|
||||||
} else {
|
} else {
|
||||||
agentConfig = { model: "opencode/gpt-5-nano" }
|
agentConfig = { model: "opencode/gpt-5-nano" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ function createConfig(overrides: Partial<InstallConfig> = {}): InstallConfig {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
...overrides,
|
...overrides,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,6 +201,116 @@ describe("transformModelForProvider", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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", () => {
|
describe("unknown provider", () => {
|
||||||
test("passes model through unchanged for unknown provider", () => {
|
test("passes model through unchanged for unknown provider", () => {
|
||||||
// #given unknown provider and any model
|
// #given unknown provider and any model
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ describe("runTuiInstaller", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}),
|
}),
|
||||||
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
||||||
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"),
|
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.3.9"),
|
||||||
@@ -92,6 +93,7 @@ describe("runTuiInstaller", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}),
|
}),
|
||||||
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true),
|
||||||
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
|
spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0"),
|
||||||
@@ -105,6 +107,7 @@ describe("runTuiInstaller", () => {
|
|||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
hasOpencodeGo: false,
|
hasOpencodeGo: false,
|
||||||
|
hasVercelAiGateway: false,
|
||||||
}),
|
}),
|
||||||
spyOn(configManager, "addPluginToOpenCodeConfig").mockResolvedValue({
|
spyOn(configManager, "addPluginToOpenCodeConfig").mockResolvedValue({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export async function runTuiInstaller(args: InstallArgs, version: string): Promi
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.hasClaude && !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && !config.hasOpencodeZen) {
|
if (!config.hasClaude && !config.hasOpenAI && !config.hasGemini && !config.hasCopilot && !config.hasOpencodeZen && !config.hasVercelAiGateway) {
|
||||||
p.log.warn("No model providers configured. Using opencode/big-pickle as fallback.")
|
p.log.warn("No model providers configured. Using opencode/big-pickle as fallback.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ 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")
|
||||||
|
|
||||||
const second = sisyphus.fallbackChain[1]
|
const second = sisyphus.fallbackChain[1]
|
||||||
expect(second.providers).toEqual(["opencode-go"])
|
expect(second.providers).toEqual(["opencode-go", "vercel"])
|
||||||
expect(second.model).toBe("kimi-k2.5")
|
expect(second.model).toBe("kimi-k2.5")
|
||||||
|
|
||||||
const third = sisyphus.fallbackChain[2]
|
const third = sisyphus.fallbackChain[2]
|
||||||
@@ -132,19 +132,19 @@ 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")
|
||||||
|
|
||||||
const secondary = multimodalLooker.fallbackChain[1]
|
const secondary = multimodalLooker.fallbackChain[1]
|
||||||
expect(secondary.providers).toEqual(["opencode-go"])
|
expect(secondary.providers).toEqual(["opencode-go", "vercel"])
|
||||||
expect(secondary.model).toBe("kimi-k2.5")
|
expect(secondary.model).toBe("kimi-k2.5")
|
||||||
|
|
||||||
const tertiary = multimodalLooker.fallbackChain[2]
|
const tertiary = multimodalLooker.fallbackChain[2]
|
||||||
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", () => {
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ 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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "kimi-k2.5" },
|
{ providers: ["opencode-go", "vercel"], model: "kimi-k2.5" },
|
||||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||||
{
|
{
|
||||||
providers: [
|
providers: [
|
||||||
@@ -35,11 +35,12 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
"firmware",
|
"firmware",
|
||||||
"ollama-cloud",
|
"ollama-cloud",
|
||||||
"aihubmix",
|
"aihubmix",
|
||||||
|
"vercel",
|
||||||
],
|
],
|
||||||
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", "vercel"], model: "glm-5" },
|
||||||
{ providers: ["opencode"], model: "big-pickle" },
|
{ providers: ["opencode"], model: "big-pickle" },
|
||||||
],
|
],
|
||||||
requiresAnyModel: true,
|
requiresAnyModel: true,
|
||||||
@@ -47,73 +48,73 @@ 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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "glm-5" },
|
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
librarian: {
|
librarian: {
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["opencode-go"], model: "minimax-m2.7" },
|
{ providers: ["opencode-go", "vercel"], model: "minimax-m2.7" },
|
||||||
{ providers: ["opencode"], model: "minimax-m2.7-highspeed" },
|
{ providers: ["opencode", "vercel"], 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", "vercel"], model: "gpt-5-nano" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
explore: {
|
explore: {
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["github-copilot", "xai"], model: "grok-code-fast-1" },
|
{ providers: ["github-copilot", "xai", "vercel"], model: "grok-code-fast-1" },
|
||||||
{ providers: ["opencode-go"], model: "minimax-m2.7-highspeed" },
|
{ providers: ["opencode-go", "vercel"], model: "minimax-m2.7-highspeed" },
|
||||||
{ providers: ["opencode"], model: "minimax-m2.7" },
|
{ providers: ["opencode", "vercel"], 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", "vercel"], 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", "vercel"], model: "kimi-k2.5" },
|
||||||
{ providers: ["zai-coding-plan"], model: "glm-4.6v" },
|
{ providers: ["zai-coding-plan", "vercel"], 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", "vercel"], 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,61 +122,61 @@ 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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "glm-5" },
|
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "glm-5" },
|
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
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", "vercel"], 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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "minimax-m2.7" },
|
{ providers: ["opencode-go", "vercel"], model: "minimax-m2.7" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"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", "vercel"], 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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "minimax-m2.7" },
|
{ providers: ["opencode-go", "vercel"], model: "minimax-m2.7" },
|
||||||
{ providers: ["opencode"], model: "big-pickle" },
|
{ providers: ["opencode"], model: "big-pickle" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -185,54 +186,54 @@ 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", "vercel"], 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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "glm-5" },
|
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
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",
|
||||||
},
|
},
|
||||||
{ providers: ["opencode-go"], model: "glm-5" },
|
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
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,72 +242,72 @@ 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", "vercel"], model: "minimax-m2.7" },
|
||||||
{ providers: ["opencode"], model: "gpt-5-nano" },
|
{ providers: ["opencode", "vercel"], model: "gpt-5-nano" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"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", "vercel"], 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", "vercel"], model: "minimax-m2.7" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"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",
|
||||||
},
|
},
|
||||||
{ providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
|
{ providers: ["zai-coding-plan", "opencode", "vercel"], model: "glm-5" },
|
||||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||||
{ providers: ["opencode-go"], model: "glm-5" },
|
{ providers: ["opencode-go", "vercel"], model: "glm-5" },
|
||||||
{ providers: ["opencode"], model: "kimi-k2.5" },
|
{ providers: ["opencode", "vercel"], model: "kimi-k2.5" },
|
||||||
{
|
{
|
||||||
providers: [
|
providers: [
|
||||||
"opencode",
|
"opencode",
|
||||||
@@ -315,6 +316,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
"firmware",
|
"firmware",
|
||||||
"ollama-cloud",
|
"ollama-cloud",
|
||||||
"aihubmix",
|
"aihubmix",
|
||||||
|
"vercel",
|
||||||
],
|
],
|
||||||
model: "kimi-k2.5",
|
model: "kimi-k2.5",
|
||||||
},
|
},
|
||||||
@@ -323,15 +325,15 @@ 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", "vercel"], 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", "vercel"], model: "minimax-m2.7" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,58 @@
|
|||||||
|
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 {
|
export function transformModelForProvider(provider: string, model: string): string {
|
||||||
if (provider === "github-copilot") {
|
// Vercel AI Gateway expects <sub-provider>/<model> (e.g. anthropic/claude-opus-4.6).
|
||||||
|
// Canonical names in model-requirements.ts may be bare (claude-opus-4-6) or
|
||||||
|
// already prefixed (anthropic/claude-opus-4-6). Both need gateway-specific transforms.
|
||||||
|
if (provider === "vercel") {
|
||||||
|
// Already prefixed — transform only the model part
|
||||||
|
const slashIndex = model.indexOf("/")
|
||||||
|
if (slashIndex !== -1) {
|
||||||
|
const subProvider = model.substring(0, slashIndex)
|
||||||
|
const subModel = model.substring(slashIndex + 1)
|
||||||
|
return `${subProvider}/${applyGatewayTransforms(subModel)}`
|
||||||
|
}
|
||||||
|
// Bare name — infer sub-provider from model prefix (claude- → anthropic, etc.)
|
||||||
|
const subProvider = inferSubProvider(model)
|
||||||
|
if (subProvider) {
|
||||||
|
return `${subProvider}/${applyGatewayTransforms(model)}`
|
||||||
|
}
|
||||||
return model
|
return model
|
||||||
.replace("claude-opus-4-6", "claude-opus-4.6")
|
}
|
||||||
.replace("claude-sonnet-4-6", "claude-sonnet-4.6")
|
if (provider === "github-copilot") {
|
||||||
.replace("claude-sonnet-4-5", "claude-sonnet-4.5")
|
return claudeVersionDot(model)
|
||||||
.replace("claude-haiku-4-5", "claude-haiku-4.5")
|
.replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview")
|
||||||
.replace("claude-sonnet-4", "claude-sonnet-4")
|
.replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview")
|
||||||
.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview")
|
|
||||||
.replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview")
|
|
||||||
}
|
}
|
||||||
if (provider === "google") {
|
if (provider === "google") {
|
||||||
return model
|
return model
|
||||||
.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview")
|
.replace(GEMINI_31_PRO_PREVIEW, "gemini-3.1-pro-preview")
|
||||||
.replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview")
|
.replace(GEMINI_3_FLASH_PREVIEW, "gemini-3-flash-preview")
|
||||||
}
|
}
|
||||||
if (provider === "anthropic") {
|
if (provider === "anthropic") {
|
||||||
return model
|
return claudeVersionDot(model)
|
||||||
.replace("claude-opus-4-6", "claude-opus-4.6")
|
|
||||||
.replace("claude-sonnet-4-6", "claude-sonnet-4.6")
|
|
||||||
.replace("claude-haiku-4-5", "claude-haiku-4.5")
|
|
||||||
}
|
}
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user