diff --git a/src/services/llm/adapters/google/GoogleModels.ts b/src/services/llm/adapters/google/GoogleModels.ts index 72bc20952..de085997c 100644 --- a/src/services/llm/adapters/google/GoogleModels.ts +++ b/src/services/llm/adapters/google/GoogleModels.ts @@ -1,12 +1,12 @@ /** * Google Model Specifications - * Updated March 2026 with Gemini 3.1 models + * Updated May 2026 with Gemini 3.5 models */ import { ModelSpec } from '../modelTypes'; export const GOOGLE_MODELS: ModelSpec[] = [ - // Gemini 3.1 models (latest) + // Gemini 3.1 models { provider: 'google', name: 'Gemini 3.1 Pro Preview', @@ -40,6 +40,24 @@ export const GOOGLE_MODELS: ModelSpec[] = [ } }, + // Gemini 3.5 models + { + provider: 'google', + name: 'Gemini 3.5 Flash', + apiName: 'gemini-3.5-flash', + contextWindow: 1048576, + maxTokens: 65536, + inputCostPerMillion: 1.50, + outputCostPerMillion: 9.00, + capabilities: { + supportsJSON: true, + supportsImages: true, + supportsFunctions: true, + supportsStreaming: true, + supportsThinking: true + } + }, + // Gemini 3.0 models { provider: 'google', @@ -108,4 +126,4 @@ export const GOOGLE_MODELS: ModelSpec[] = [ } ]; -export const GOOGLE_DEFAULT_MODEL = 'gemini-3-pro-preview'; \ No newline at end of file +export const GOOGLE_DEFAULT_MODEL = 'gemini-3-pro-preview'; diff --git a/src/services/llm/adapters/openrouter/OpenRouterModels.ts b/src/services/llm/adapters/openrouter/OpenRouterModels.ts index 0ea0fd1ee..cceb93dec 100644 --- a/src/services/llm/adapters/openrouter/OpenRouterModels.ts +++ b/src/services/llm/adapters/openrouter/OpenRouterModels.ts @@ -281,6 +281,22 @@ export const OPENROUTER_MODELS: ModelSpec[] = [ supportsThinking: true } }, + { + provider: 'openrouter', + name: 'Gemini 3.5 Flash', + apiName: 'google/gemini-3.5-flash', + contextWindow: 1048576, + maxTokens: 65536, + inputCostPerMillion: 1.50, + outputCostPerMillion: 9.00, + capabilities: { + supportsJSON: true, + supportsImages: true, + supportsFunctions: true, + supportsStreaming: true, + supportsThinking: true + } + }, { provider: 'openrouter', name: 'GPT-5', diff --git a/tests/debug/provider-model-live-smoke.test.ts b/tests/debug/provider-model-live-smoke.test.ts index 8540a4e31..2d35bc274 100644 --- a/tests/debug/provider-model-live-smoke.test.ts +++ b/tests/debug/provider-model-live-smoke.test.ts @@ -30,11 +30,12 @@ import { DEFAULT_MODELS } from '../../src/services/llm/adapters/ModelRegistry'; import { OpenAIAdapter } from '../../src/services/llm/adapters/openai/OpenAIAdapter'; import { OpenRouterAdapter } from '../../src/services/llm/adapters/openrouter/OpenRouterAdapter'; import { OpenAICodexAdapter, type CodexOAuthTokens } from '../../src/services/llm/adapters/openai-codex/OpenAICodexAdapter'; +import { GoogleAdapter } from '../../src/services/llm/adapters/google/GoogleAdapter'; import type { GenerateOptions, LLMResponse } from '../../src/services/llm/adapters/types'; jest.setTimeout(240_000); -type SmokeProvider = 'openai' | 'openrouter' | 'openai-codex'; +type SmokeProvider = 'openai' | 'openrouter' | 'openai-codex' | 'google'; interface ProviderSettingsShape { llmProviders?: { @@ -58,7 +59,7 @@ interface SmokeTarget { } const RUN_LIVE = process.env.RUN_MODEL_SMOKE === '1'; -const PROVIDERS: SmokeProvider[] = ['openai', 'openrouter', 'openai-codex']; +const PROVIDERS: SmokeProvider[] = ['openai', 'openrouter', 'openai-codex', 'google']; function readDotEnv(): Map { const envPath = path.join(process.cwd(), '.env'); @@ -123,6 +124,10 @@ function normalizeModelForProvider(provider: SmokeProvider, model: string): stri return model.slice('openai/'.length); } + if (provider === 'google' && model.startsWith('google/')) { + return model.slice('google/'.length); + } + return model; } @@ -132,6 +137,7 @@ function getProviderModel(provider: SmokeProvider): string { openai: getEnv('OPENAI_SMOKE_MODEL'), openrouter: getEnv('OPENROUTER_SMOKE_MODEL'), 'openai-codex': getEnv('CODEX_SMOKE_MODEL'), + google: getEnv('GOOGLE_SMOKE_MODEL'), }[provider]; const model = providerOverride || sharedOverride || DEFAULT_MODELS[provider]; @@ -184,7 +190,7 @@ function setRequestUrlToRealFetch(): void { }); } -function createAdapter(provider: SmokeProvider): OpenAIAdapter | OpenRouterAdapter | OpenAICodexAdapter { +function createAdapter(provider: SmokeProvider): OpenAIAdapter | OpenRouterAdapter | OpenAICodexAdapter | GoogleAdapter { if (provider === 'openai') { const apiKey = getEnv('OPENAI_API_KEY'); if (!apiKey) { @@ -201,6 +207,14 @@ function createAdapter(provider: SmokeProvider): OpenAIAdapter | OpenRouterAdapt return new OpenRouterAdapter(apiKey); } + if (provider === 'google') { + const apiKey = getEnv('GEMINI_API_KEY'); + if (!apiKey) { + throw new Error('GEMINI_API_KEY is required for Google smoke tests'); + } + return new GoogleAdapter(apiKey); + } + const tokens = loadCodexTokensFromLocalDataJson(); if (!tokens) { throw new Error('Codex OAuth tokens are required in data.json for Codex smoke tests'); @@ -217,7 +231,7 @@ async function callModel(target: SmokeTarget): Promise { // Codex currently rejects max_output_tokens on the OAuth endpoint. if (target.provider !== 'openai-codex') { - options.maxTokens = Number(getEnv('MODEL_SMOKE_MAX_TOKENS') || 16); + options.maxTokens = Number(getEnv('MODEL_SMOKE_MAX_TOKENS') || 64); } return adapter.generateUncached( diff --git a/tests/eval/configs/gemini-3.5-flash.yaml b/tests/eval/configs/gemini-3.5-flash.yaml new file mode 100644 index 000000000..b1f6f0acf --- /dev/null +++ b/tests/eval/configs/gemini-3.5-flash.yaml @@ -0,0 +1,23 @@ +mode: live +testVaultPath: tests/eval/test-vault/ + +providers: + openrouter: + apiKeyEnv: OPENROUTER_API_KEY + models: + - google/gemini-3.5-flash + enabled: true + +defaults: + temperature: 0 + maxRetries: 1 + retryDelayMs: 2000 + timeout: 120000 + systemPrompt: default + +capture: + enabled: true + dumpOnFailure: true + artifactsDir: test-artifacts/ + +scenarios: tests/eval/scenarios/**/*.eval.yaml diff --git a/tests/unit/ModelRegistry.test.ts b/tests/unit/ModelRegistry.test.ts index 23fdcdb79..973354d8d 100644 --- a/tests/unit/ModelRegistry.test.ts +++ b/tests/unit/ModelRegistry.test.ts @@ -49,3 +49,25 @@ describe('ModelRegistry GPT-5.5 models', () => { expect(DEFAULT_MODELS['openai-codex']).toBe('gpt-5.5'); }); }); + +describe('ModelRegistry Gemini 3.5 Flash models', () => { + it('registers Gemini 3.5 Flash for Google', () => { + expect(ModelRegistry.findModel('google', 'gemini-3.5-flash')).toEqual(expect.objectContaining({ + name: 'Gemini 3.5 Flash', + contextWindow: 1048576, + maxTokens: 65536, + inputCostPerMillion: 1.5, + outputCostPerMillion: 9 + })); + }); + + it('registers Gemini 3.5 Flash for OpenRouter with the provider namespace', () => { + expect(ModelRegistry.findModel('openrouter', 'google/gemini-3.5-flash')).toEqual(expect.objectContaining({ + name: 'Gemini 3.5 Flash', + contextWindow: 1048576, + maxTokens: 65536, + inputCostPerMillion: 1.5, + outputCostPerMillion: 9 + })); + }); +});