diff --git a/src/background/index.mjs b/src/background/index.mjs index aa41ecd4..73059d31 100644 --- a/src/background/index.mjs +++ b/src/background/index.mjs @@ -30,6 +30,7 @@ import { isUsingOpenRouterApiModel, isUsingAimlApiModel, isUsingDeepSeekApiModel, + isUsingGoogleApiModel, } from '../config/index.mjs' import '../_locales/i18n' import { openUrl } from '../utils/open-url' @@ -345,6 +346,7 @@ function isUsingOpenAICompatibleApiSession(session) { isUsingOllamaApiModel(session) || isUsingOpenRouterApiModel(session) || isUsingAimlApiModel(session) || + isUsingGoogleApiModel(session) || isUsingGptCompletionApiModel(session) ) } diff --git a/src/config/index.mjs b/src/config/index.mjs index b7cf428a..e1a22f82 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -179,6 +179,14 @@ export const aimlApiModelKeys = [ 'aiml_deepseek_v4_pro', 'aiml_deepseek_v4_flash', ] +export const googleApiModelKeys = [ + 'googleGemini3_1Pro', + 'googleGemini3_5Flash', + 'googleGemini3Flash', + 'googleGemini2_5Pro', + 'googleGemini2_5Flash', + 'googleGemini2_5FlashLite', +] export const AlwaysCustomGroups = [ 'ollamaApiModelKeys', @@ -253,6 +261,10 @@ export const ModelGroups = { value: aimlApiModelKeys, desc: 'AI/ML (API)', }, + googleApiModelKeys: { + value: googleApiModelKeys, + desc: 'Google (API)', + }, customApiModelKeys: { value: customApiModelKeys, desc: 'Custom Model', @@ -605,6 +617,30 @@ export const Models = { value: 'deepseek/deepseek-v4-flash', desc: 'AIML (DeepSeek V4 Flash)', }, + googleGemini3_1Pro: { + value: 'gemini-3.1-pro-preview', + desc: 'Google (Gemini 3.1 Pro Preview)', + }, + googleGemini3_5Flash: { + value: 'gemini-3.5-flash', + desc: 'Google (Gemini 3.5 Flash)', + }, + googleGemini3Flash: { + value: 'gemini-3-flash-preview', + desc: 'Google (Gemini 3 Flash Preview)', + }, + googleGemini2_5Pro: { + value: 'gemini-2.5-pro', + desc: 'Google (Gemini 2.5 Pro)', + }, + googleGemini2_5Flash: { + value: 'gemini-2.5-flash', + desc: 'Google (Gemini 2.5 Flash)', + }, + googleGemini2_5FlashLite: { + value: 'gemini-2.5-flash-lite', + desc: 'Google (Gemini 2.5 Flash-Lite)', + }, } for (const modelName in Models) { @@ -673,6 +709,7 @@ export const defaultConfig = { openRouterApiKey: '', aimlApiKey: '', + googleApiKey: '', // advanced @@ -711,6 +748,8 @@ export const defaultConfig = { 'claudeOpus48Api', 'claudeSonnet5Api', 'claudeHaiku45Api', + 'googleGemini3_1Pro', + 'googleGemini3_5Flash', 'openRouter_auto', 'openRouter_free', ], @@ -868,6 +907,10 @@ export function isUsingAimlApiModel(configOrSession) { return isInApiModeGroup(aimlApiModelKeys, configOrSession) } +export function isUsingGoogleApiModel(configOrSession) { + return isInApiModeGroup(googleApiModelKeys, configOrSession) +} + export function isUsingChatGLMApiModel(configOrSession) { return isInApiModeGroup(chatglmApiModelKeys, configOrSession) } diff --git a/src/config/openai-provider-mappings.mjs b/src/config/openai-provider-mappings.mjs index b7a53487..57c0485a 100644 --- a/src/config/openai-provider-mappings.mjs +++ b/src/config/openai-provider-mappings.mjs @@ -6,6 +6,7 @@ export const LEGACY_API_KEY_FIELD_BY_PROVIDER_ID = { aiml: 'aimlApiKey', chatglm: 'chatglmApiKey', ollama: 'ollamaApiKey', + google: 'googleApiKey', 'legacy-custom-default': 'customApiKey', } @@ -26,5 +27,6 @@ export const OPENAI_COMPATIBLE_GROUP_TO_PROVIDER_ID = { aimlApiModelKeys: 'aiml', chatglmApiModelKeys: 'chatglm', ollamaApiModelKeys: 'ollama', + googleApiModelKeys: 'google', customApiModelKeys: 'legacy-custom-default', } diff --git a/src/services/apis/provider-registry.mjs b/src/services/apis/provider-registry.mjs index 249e59ae..72aa4766 100644 --- a/src/services/apis/provider-registry.mjs +++ b/src/services/apis/provider-registry.mjs @@ -70,6 +70,14 @@ const BUILTIN_PROVIDER_TEMPLATE = [ builtin: true, enabled: true, }, + { + id: 'google', + name: 'Google', + baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', + chatCompletionsPath: '/chat/completions', + builtin: true, + enabled: true, + }, { id: 'legacy-custom-default', name: 'Custom Model (Legacy)', @@ -111,6 +119,7 @@ function resolveProviderIdFromLegacyModelName(modelName) { return 'ollama' } if (preset.startsWith('chatglm') || preset === 'chatglmApiModelKeys') return 'chatglm' + if (preset.startsWith('googleGemini') || preset === 'googleApiModelKeys') return 'google' if (preset === 'customApiModelKeys') return 'legacy-custom-default' return null diff --git a/tests/unit/services/apis/provider-registry.test.mjs b/tests/unit/services/apis/provider-registry.test.mjs index 858f3e5a..29d94981 100644 --- a/tests/unit/services/apis/provider-registry.test.mjs +++ b/tests/unit/services/apis/provider-registry.test.mjs @@ -6,6 +6,7 @@ import { getProviderById, resolveEndpointTypeForSession, resolveOpenAICompatibleRequest, + resolveProviderIdForSession, } from '../../../../src/services/apis/provider-registry.mjs' test('resolveEndpointTypeForSession prefers apiMode when present', () => { @@ -40,6 +41,15 @@ test('resolveEndpointTypeForSession falls back to legacy modelName when apiMode assert.equal(resolveEndpointTypeForSession(session), 'completion') }) +test('resolveProviderIdForSession resolves legacy Google preset keys', () => { + assert.equal(resolveProviderIdForSession({ modelName: 'googleGemini2_5Flash' }), 'google') + assert.equal(resolveProviderIdForSession({ modelName: 'googleApiModelKeys-custom' }), 'google') +}) + +test('resolveProviderIdForSession does not treat raw Gemini model IDs as legacy presets', () => { + assert.equal(resolveProviderIdForSession({ modelName: 'gemini-2.5-flash' }), null) +}) + test('resolveOpenAICompatibleRequest resolves custom provider from normalized session provider id', () => { const config = { customOpenAIProviders: [