diff --git a/README.md b/README.md index 17b9e7a..90be565 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ If you want a fast mental model of where to start as an AI agent / contributor: |----------|--------| | Anthropic | `provider: anthropic`, `api_key` or `auth_token` | | OpenAI | `provider: openai`, `api_key`, optional `endpoint` | +| Vercel AI Gateway | `provider: vercel`, `api_key` or `AI_GATEWAY_API_KEY`, optional `endpoint` | | GitHub Copilot | `provider: github`, auto-login via OAuth device flow | | Gemini | `provider: gemini`, `api_key` | | Bedrock | `provider: bedrock`, AWS credentials | diff --git a/src/cli/doctor.test.ts b/src/cli/doctor.test.ts index 0b4879d..59ebfc7 100644 --- a/src/cli/doctor.test.ts +++ b/src/cli/doctor.test.ts @@ -331,6 +331,37 @@ models: } }); + it('reports WARN when Vercel AI Gateway has no available API key sources', async () => { + const originalKey = process.env.AI_GATEWAY_API_KEY; + delete process.env.AI_GATEWAY_API_KEY; + + try { + mkdirSync(testDir, { recursive: true }); + const configPath = join(testDir, 'vercel-missing.yaml'); + writeFileSync(configPath, ` +telegram: + bot_token: "test-token" + allowed_chat_ids: [123] +models: + default: + provider: vercel + model: openai/gpt-4.1 +`); + + const ctx: DoctorContext = { configPath, dataDir: testDir }; + const results = await runChecks(ctx); + const modelCheck = results.find(r => r.label.includes('Model connectivity')) as CheckResult | undefined; + expect(modelCheck?.status).toBe('warn'); + expect(modelCheck?.detail).toContain('AI_GATEWAY_API_KEY'); + } finally { + if (originalKey) { + process.env.AI_GATEWAY_API_KEY = originalKey; + } else { + delete process.env.AI_GATEWAY_API_KEY; + } + } + }); + it('reports FAIL when Anthropic auth_mode=oauth has no available auth token sources', async () => { const originalHome = process.env.HOME; const originalToken = process.env.ANTHROPIC_AUTH_TOKEN; diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index da65f09..28d9da3 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -280,11 +280,12 @@ const checkModelConnectivity: Check = async (ctx) => { } // Providers with API-key style auth (no auth store integration yet) - const needsKey = ['gemini', 'openrouter', 'xai', 'github']; + const needsKey = ['gemini', 'openrouter', 'vercel', 'xai', 'github']; if (needsKey.includes(provider)) { const envVarMap: Record = { gemini: 'GEMINI_API_KEY', openrouter: 'OPENROUTER_API_KEY', + vercel: 'AI_GATEWAY_API_KEY', xai: 'XAI_API_KEY', github: 'GITHUB_TOKEN', }; diff --git a/src/cli/setup/providers.test.ts b/src/cli/setup/providers.test.ts index 5eab1bd..9f0c433 100644 --- a/src/cli/setup/providers.test.ts +++ b/src/cli/setup/providers.test.ts @@ -61,4 +61,18 @@ describe('setupProviders', () => { expect(config.models.fast).toBeDefined(); expect(config.models.fast.provider).toBe('anthropic'); }); + + it('configures vercel gateway as default provider (second tier)', async () => { + // Pick "More providers..." then pick "Vercel AI Gateway". + // Prompts: api key, endpoint, model, then confirm fast tier. + const rl = mockReadline(['4', '3', 'sk-vercel-test123', '', '', 'n']); + const p = createPrompter(rl); + const builder = new ConfigBuilder(); + await setupProviders(p, builder); + const config = builder.build(); + expect(config.models.default.provider).toBe('vercel'); + expect(config.models.default.api_key).toBe('sk-vercel-test123'); + expect(config.models.default.endpoint).toBe('https://ai-gateway.vercel.sh/v1'); + expect(config.models.default.model).toBe('openai/gpt-4.1'); + }); }); diff --git a/src/cli/setup/providers.ts b/src/cli/setup/providers.ts index bc56181..371e350 100644 --- a/src/cli/setup/providers.ts +++ b/src/cli/setup/providers.ts @@ -21,6 +21,7 @@ const TOP_TIER: ProviderDef[] = [ const SECOND_TIER: ProviderDef[] = [ { name: 'Gemini', provider: 'gemini', defaultModel: 'gemini-2.5-flash', fastModel: 'gemini-2.0-flash-lite', needsApiKey: true, needsEndpoint: false, apiKeyLabel: 'Gemini API key' }, { name: 'OpenRouter', provider: 'openrouter', defaultModel: 'anthropic/claude-sonnet-4', needsApiKey: true, needsEndpoint: false, apiKeyLabel: 'OpenRouter API key' }, + { name: 'Vercel AI Gateway', provider: 'vercel', defaultModel: 'openai/gpt-4.1', fastModel: 'openai/gpt-4.1-mini', needsApiKey: true, needsEndpoint: true, defaultEndpoint: 'https://ai-gateway.vercel.sh/v1', apiKeyLabel: 'Vercel AI Gateway API key' }, { name: 'Z.AI (GLM)', provider: 'zhipuai', defaultModel: 'glm-4.7', needsApiKey: true, needsEndpoint: true, defaultEndpoint: 'https://api.z.ai/api/paas/v4', apiKeyLabel: 'Z.AI API key' }, { name: 'xAI (Grok)', provider: 'xai', defaultModel: 'grok-3', fastModel: 'grok-3-mini', needsApiKey: true, needsEndpoint: false, apiKeyLabel: 'xAI API key' }, { name: 'Amazon Bedrock', provider: 'bedrock', defaultModel: 'anthropic.claude-sonnet-4-20250514-v1:0', needsApiKey: false, needsEndpoint: false }, @@ -33,6 +34,7 @@ const PROVIDER_HELP: Record = { ollama: 'Ollama runs locally — install from https://ollama.com and run: ollama serve', gemini: 'Get your API key at https://aistudio.google.com/apikey', openrouter: 'Get your API key at https://openrouter.ai/keys (supports 200+ models)', + vercel: 'Vercel AI Gateway uses an API key (AI_GATEWAY_API_KEY) and an OpenAI-compatible base URL (default: https://ai-gateway.vercel.sh/v1)', zhipuai: 'Get your API key at https://z.ai/manage-apikey/apikey-list (Coding Plan endpoint: https://api.z.ai/api/coding/paas/v4)', xai: 'Get your API key at https://console.x.ai', bedrock: 'Uses AWS credentials from environment (~/.aws/credentials or IAM role)',