From 6f72ec754daf347c135e4b0cee9afa6dc727ae40 Mon Sep 17 00:00:00 2001 From: Matan Kushner Date: Mon, 13 Apr 2026 11:26:30 +0900 Subject: [PATCH] test: add integration tests for Vercel AI Gateway model resolution --- .../__snapshots__/model-fallback.test.ts.snap | 402 ++++++++++++++++++ src/cli/model-fallback.test.ts | 69 +++ 2 files changed, 471 insertions(+) diff --git a/src/cli/__snapshots__/model-fallback.test.ts.snap b/src/cli/__snapshots__/model-fallback.test.ts.snap index 3e1b2f50c..83ae744e6 100644 --- a/src/cli/__snapshots__/model-fallback.test.ts.snap +++ b/src/cli/__snapshots__/model-fallback.test.ts.snap @@ -4137,3 +4137,405 @@ 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/openai/gpt-5.4", + "variant": "medium", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "explore": { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + "hephaestus": { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "librarian": { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + "metis": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + ], + "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/openai/gpt-5.4", + "variant": "xhigh", + }, + "multimodal-looker": { + "fallback_models": [ + { + "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/openai/gpt-5.4", + "variant": "high", + }, + "prometheus": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + }, + ], + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + "sisyphus": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + ], + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + "sisyphus-junior": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + ], + "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-preview", + }, + ], + "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/openai/gpt-5.4", + "variant": "xhigh", + }, + "unspecified-high": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.3-codex", + "variant": "medium", + }, + { + "model": "vercel/google/gemini-3-flash-preview", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "unspecified-low": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.3-codex", + "variant": "medium", + }, + { + "model": "vercel/google/gemini-3-flash-preview", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "visual-engineering": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + ], + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + "writing": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + ], + "model": "vercel/google/gemini-3-flash-preview", + }, + }, +} +`; + +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/openai/gpt-5.4", + "variant": "medium", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "explore": { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + "hephaestus": { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + "librarian": { + "model": "vercel/anthropic/claude-haiku-4.5", + }, + "metis": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + ], + "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/openai/gpt-5.4", + "variant": "xhigh", + }, + "multimodal-looker": { + "fallback_models": [ + { + "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/openai/gpt-5.4", + "variant": "high", + }, + "prometheus": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + { + "model": "vercel/google/gemini-3.1-pro-preview", + }, + ], + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + "sisyphus": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + ], + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + "sisyphus-junior": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "medium", + }, + ], + "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-preview", + }, + ], + "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/openai/gpt-5.4", + "variant": "xhigh", + }, + "unspecified-high": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.4", + "variant": "high", + }, + ], + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + "unspecified-low": { + "fallback_models": [ + { + "model": "vercel/openai/gpt-5.3-codex", + "variant": "medium", + }, + { + "model": "vercel/google/gemini-3-flash-preview", + }, + ], + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + "visual-engineering": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-opus-4.6", + "variant": "max", + }, + ], + "model": "vercel/google/gemini-3.1-pro-preview", + "variant": "high", + }, + "writing": { + "fallback_models": [ + { + "model": "vercel/anthropic/claude-sonnet-4.6", + }, + ], + "model": "vercel/google/gemini-3-flash-preview", + }, + }, +} +`; diff --git a/src/cli/model-fallback.test.ts b/src/cli/model-fallback.test.ts index 09d002bcb..1a6a6c797 100644 --- a/src/cli/model-fallback.test.ts +++ b/src/cli/model-fallback.test.ts @@ -16,6 +16,7 @@ function createConfig(overrides: Partial = {}): InstallConfig { hasZaiCodingPlan: false, hasKimiForCoding: false, hasOpencodeGo: false, + hasVercelAiGateway: false, ...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// 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/anthropic/claude-haiku-4.5 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 claude-haiku + expect(result.agents?.explore?.model).toBe("vercel/anthropic/claude-haiku-4.5") + }) + + test("librarian uses vercel/anthropic/claude-haiku-4.5 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 claude-haiku + expect(result.agents?.librarian?.model).toBe("vercel/anthropic/claude-haiku-4.5") + }) + + 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", () => { test("always includes correct schema URL", () => { // #given any config