diff --git a/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch b/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch deleted file mode 100644 index 75c418e59..000000000 --- a/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/dist/index.js b/dist/index.js -index ff305b112779b718f21a636a27b1196125a332d9..cf32ff5086d4d9e56f8fe90c98724559083bafc3 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -471,7 +471,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { - - // src/get-model-path.ts - function getModelPath(modelId) { -- return modelId.includes("/") ? modelId : `models/${modelId}`; -+ return modelId.includes("models/") ? modelId : `models/${modelId}`; - } - - // src/google-generative-ai-options.ts -diff --git a/dist/index.mjs b/dist/index.mjs -index 57659290f1cec74878a385626ad75b2a4d5cd3fc..d04e5927ec3725b6ffdb80868bfa1b5a48849537 100644 ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -477,7 +477,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { - - // src/get-model-path.ts - function getModelPath(modelId) { -- return modelId.includes("/") ? modelId : `models/${modelId}`; -+ return modelId.includes("models/") ? modelId : `models/${modelId}`; - } - - // src/google-generative-ai-options.ts diff --git a/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch b/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch new file mode 100644 index 000000000..18570d5ce --- /dev/null +++ b/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch @@ -0,0 +1,152 @@ +diff --git a/dist/index.js b/dist/index.js +index c2ef089c42e13a8ee4a833899a415564130e5d79..75efa7baafb0f019fb44dd50dec1641eee8879e7 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -471,7 +471,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { + + // src/get-model-path.ts + function getModelPath(modelId) { +- return modelId.includes("/") ? modelId : `models/${modelId}`; ++ return modelId.includes("models/") ? modelId : `models/${modelId}`; + } + + // src/google-generative-ai-options.ts +diff --git a/dist/index.mjs b/dist/index.mjs +index d75c0cc13c41192408c1f3f2d29d76a7bffa6268..ada730b8cb97d9b7d4cb32883a1d1ff416404d9b 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -477,7 +477,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { + + // src/get-model-path.ts + function getModelPath(modelId) { +- return modelId.includes("/") ? modelId : `models/${modelId}`; ++ return modelId.includes("models/") ? modelId : `models/${modelId}`; + } + + // src/google-generative-ai-options.ts +diff --git a/dist/internal/index.js b/dist/internal/index.js +index 277cac8dc734bea2fb4f3e9a225986b402b24f48..bb704cd79e602eb8b0cee1889e42497d59ccdb7a 100644 +--- a/dist/internal/index.js ++++ b/dist/internal/index.js +@@ -432,7 +432,15 @@ function prepareTools({ + var _a; + tools = (tools == null ? void 0 : tools.length) ? tools : void 0; + const toolWarnings = []; +- const isGemini2 = modelId.includes("gemini-2"); ++ // These changes could be safely removed when @ai-sdk/google v3 released. ++ const isLatest = ( ++ [ ++ 'gemini-flash-latest', ++ 'gemini-flash-lite-latest', ++ 'gemini-pro-latest', ++ ] ++ ).some(id => id === modelId); ++ const isGemini2OrNewer = modelId.includes("gemini-2") || modelId.includes("gemini-3") || isLatest; + const supportsDynamicRetrieval = modelId.includes("gemini-1.5-flash") && !modelId.includes("-8b"); + const supportsFileSearch = modelId.includes("gemini-2.5"); + if (tools == null) { +@@ -458,7 +466,7 @@ function prepareTools({ + providerDefinedTools.forEach((tool) => { + switch (tool.id) { + case "google.google_search": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ googleSearch: {} }); + } else if (supportsDynamicRetrieval) { + googleTools2.push({ +@@ -474,7 +482,7 @@ function prepareTools({ + } + break; + case "google.url_context": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ urlContext: {} }); + } else { + toolWarnings.push({ +@@ -485,7 +493,7 @@ function prepareTools({ + } + break; + case "google.code_execution": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ codeExecution: {} }); + } else { + toolWarnings.push({ +@@ -507,7 +515,7 @@ function prepareTools({ + } + break; + case "google.vertex_rag_store": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ + retrieval: { + vertex_rag_store: { +diff --git a/dist/internal/index.mjs b/dist/internal/index.mjs +index 03b7cc591be9b58bcc2e775a96740d9f98862a10..347d2c12e1cee79f0f8bb258f3844fb0522a6485 100644 +--- a/dist/internal/index.mjs ++++ b/dist/internal/index.mjs +@@ -424,7 +424,15 @@ function prepareTools({ + var _a; + tools = (tools == null ? void 0 : tools.length) ? tools : void 0; + const toolWarnings = []; +- const isGemini2 = modelId.includes("gemini-2"); ++ // These changes could be safely removed when @ai-sdk/google v3 released. ++ const isLatest = ( ++ [ ++ 'gemini-flash-latest', ++ 'gemini-flash-lite-latest', ++ 'gemini-pro-latest', ++ ] ++ ).some(id => id === modelId); ++ const isGemini2OrNewer = modelId.includes("gemini-2") || modelId.includes("gemini-3") || isLatest; + const supportsDynamicRetrieval = modelId.includes("gemini-1.5-flash") && !modelId.includes("-8b"); + const supportsFileSearch = modelId.includes("gemini-2.5"); + if (tools == null) { +@@ -450,7 +458,7 @@ function prepareTools({ + providerDefinedTools.forEach((tool) => { + switch (tool.id) { + case "google.google_search": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ googleSearch: {} }); + } else if (supportsDynamicRetrieval) { + googleTools2.push({ +@@ -466,7 +474,7 @@ function prepareTools({ + } + break; + case "google.url_context": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ urlContext: {} }); + } else { + toolWarnings.push({ +@@ -477,7 +485,7 @@ function prepareTools({ + } + break; + case "google.code_execution": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ codeExecution: {} }); + } else { + toolWarnings.push({ +@@ -499,7 +507,7 @@ function prepareTools({ + } + break; + case "google.vertex_rag_store": +- if (isGemini2) { ++ if (isGemini2OrNewer) { + googleTools2.push({ + retrieval: { + vertex_rag_store: { +@@ -1434,9 +1442,7 @@ var googleTools = { + vertexRagStore + }; + export { +- GoogleGenerativeAILanguageModel, + getGroundingMetadataSchema, +- getUrlContextMetadataSchema, +- googleTools ++ getUrlContextMetadataSchema, GoogleGenerativeAILanguageModel, googleTools + }; + //# sourceMappingURL=index.mjs.map +\ No newline at end of file diff --git a/package.json b/package.json index ea14d7d5f..04ce32555 100644 --- a/package.json +++ b/package.json @@ -111,8 +111,8 @@ "@ai-sdk/anthropic": "^2.0.44", "@ai-sdk/cerebras": "^1.0.31", "@ai-sdk/gateway": "^2.0.9", - "@ai-sdk/google": "^2.0.32", - "@ai-sdk/google-vertex": "^3.0.62", + "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch", + "@ai-sdk/google-vertex": "^3.0.68", "@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch", "@ai-sdk/mistral": "^2.0.23", "@ai-sdk/perplexity": "^2.0.17", @@ -410,7 +410,7 @@ "@langchain/openai@npm:>=0.2.0 <0.7.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch", "@ai-sdk/openai@npm:2.0.64": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch", "@ai-sdk/openai@npm:^2.0.42": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch", - "@ai-sdk/google@npm:2.0.31": "patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch" + "@ai-sdk/google@npm:2.0.36": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch" }, "packageManager": "yarn@4.9.1", "lint-staged": { diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index bb673392a..12249cfb7 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -39,7 +39,7 @@ "@ai-sdk/anthropic": "^2.0.43", "@ai-sdk/azure": "^2.0.66", "@ai-sdk/deepseek": "^1.0.27", - "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch", + "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch", "@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch", "@ai-sdk/openai-compatible": "^1.0.26", "@ai-sdk/provider": "^2.0.0", diff --git a/src/renderer/src/config/__test__/reasoning.test.ts b/src/renderer/src/config/__test__/reasoning.test.ts index ff66e76b6..006fc79d4 100644 --- a/src/renderer/src/config/__test__/reasoning.test.ts +++ b/src/renderer/src/config/__test__/reasoning.test.ts @@ -1,6 +1,12 @@ import { describe, expect, it, vi } from 'vitest' -import { isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel, isLingReasoningModel } from '../models/reasoning' +import { + isDoubaoSeedAfter251015, + isDoubaoThinkingAutoModel, + isGeminiReasoningModel, + isLingReasoningModel, + isSupportedThinkingTokenGeminiModel +} from '../models/reasoning' vi.mock('@renderer/store', () => ({ default: { @@ -231,3 +237,284 @@ describe('Ling Models', () => { }) }) }) + +describe('Gemini Models', () => { + describe('isSupportedThinkingTokenGeminiModel', () => { + it('should return true for gemini 2.5 models', () => { + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-2.5-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-2.5-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-2.5-flash-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-2.5-pro-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini latest models', () => { + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-flash-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-pro-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-flash-lite-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini 3 models', () => { + // Preview versions + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-pro-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'google/gemini-3-pro-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + // Future stable versions + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'google/gemini-3-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'google/gemini-3-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return false for image and tts models', () => { + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-2.5-flash-image', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-2.5-flash-preview-tts', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + }) + + it('should return false for older gemini models', () => { + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-1.5-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-1.5-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-1.0-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + }) + }) + + describe('isGeminiReasoningModel', () => { + it('should return true for gemini thinking models', () => { + expect( + isGeminiReasoningModel({ + id: 'gemini-2.0-flash-thinking', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isGeminiReasoningModel({ + id: 'gemini-thinking-exp', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for supported thinking token gemini models', () => { + expect( + isGeminiReasoningModel({ + id: 'gemini-2.5-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isGeminiReasoningModel({ + id: 'gemini-2.5-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini-3 models', () => { + // Preview versions + expect( + isGeminiReasoningModel({ + id: 'gemini-3-pro-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isGeminiReasoningModel({ + id: 'google/gemini-3-pro-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + // Future stable versions + expect( + isGeminiReasoningModel({ + id: 'gemini-3-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isGeminiReasoningModel({ + id: 'gemini-3-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isGeminiReasoningModel({ + id: 'google/gemini-3-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isGeminiReasoningModel({ + id: 'google/gemini-3-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return false for older gemini models without thinking', () => { + expect( + isGeminiReasoningModel({ + id: 'gemini-1.5-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isGeminiReasoningModel({ + id: 'gemini-1.5-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + }) + + it('should return false for undefined model', () => { + expect(isGeminiReasoningModel(undefined)).toBe(false) + }) + }) +}) diff --git a/src/renderer/src/config/__test__/vision.test.ts b/src/renderer/src/config/__test__/vision.test.ts new file mode 100644 index 000000000..79bcd629c --- /dev/null +++ b/src/renderer/src/config/__test__/vision.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, it, vi } from 'vitest' + +import { isVisionModel } from '../models/vision' + +vi.mock('@renderer/store', () => ({ + default: { + getState: () => ({ + llm: { + settings: {} + } + }) + } +})) + +// FIXME: Idk why it's imported. Maybe circular dependency somewhere +vi.mock('@renderer/services/AssistantService.ts', () => ({ + getDefaultAssistant: () => { + return { + id: 'default', + name: 'default', + emoji: '😀', + prompt: '', + topics: [], + messages: [], + type: 'assistant', + regularPhrases: [], + settings: {} + } + }, + getProviderByModel: () => null +})) + +describe('isVisionModel', () => { + describe('Gemini Models', () => { + it('should return true for gemini 1.5 models', () => { + expect( + isVisionModel({ + id: 'gemini-1.5-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-1.5-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini 2.x models', () => { + expect( + isVisionModel({ + id: 'gemini-2.0-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-2.0-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-2.5-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-2.5-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini latest models', () => { + expect( + isVisionModel({ + id: 'gemini-flash-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-pro-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-flash-lite-latest', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini 3 models', () => { + // Preview versions + expect( + isVisionModel({ + id: 'gemini-3-pro-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + // Future stable versions + expect( + isVisionModel({ + id: 'gemini-3-flash', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-3-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return true for gemini exp models', () => { + expect( + isVisionModel({ + id: 'gemini-exp-1206', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return false for gemini 1.0 models', () => { + expect( + isVisionModel({ + id: 'gemini-1.0-pro', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + }) + }) +}) diff --git a/src/renderer/src/config/__test__/websearch.test.ts b/src/renderer/src/config/__test__/websearch.test.ts new file mode 100644 index 000000000..be18505a4 --- /dev/null +++ b/src/renderer/src/config/__test__/websearch.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it, vi } from 'vitest' + +import { GEMINI_SEARCH_REGEX } from '../models/websearch' + +vi.mock('@renderer/store', () => ({ + default: { + getState: () => ({ + llm: { + settings: {} + } + }) + } +})) + +// FIXME: Idk why it's imported. Maybe circular dependency somewhere +vi.mock('@renderer/services/AssistantService.ts', () => ({ + getDefaultAssistant: () => { + return { + id: 'default', + name: 'default', + emoji: '😀', + prompt: '', + topics: [], + messages: [], + type: 'assistant', + regularPhrases: [], + settings: {} + } + }, + getProviderByModel: () => null +})) + +describe('Gemini Search Models', () => { + describe('GEMINI_SEARCH_REGEX', () => { + it('should match gemini 2.x models', () => { + expect(GEMINI_SEARCH_REGEX.test('gemini-2.0-flash')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-2.0-pro')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-flash')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-pro')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-flash-latest')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-2.5-pro-latest')).toBe(true) + }) + + it('should match gemini latest models', () => { + expect(GEMINI_SEARCH_REGEX.test('gemini-flash-latest')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-pro-latest')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-flash-lite-latest')).toBe(true) + }) + + it('should match gemini 3 models', () => { + // Preview versions + expect(GEMINI_SEARCH_REGEX.test('gemini-3-pro-preview')).toBe(true) + // Future stable versions + expect(GEMINI_SEARCH_REGEX.test('gemini-3-flash')).toBe(true) + expect(GEMINI_SEARCH_REGEX.test('gemini-3-pro')).toBe(true) + }) + + it('should not match older gemini models', () => { + expect(GEMINI_SEARCH_REGEX.test('gemini-1.5-flash')).toBe(false) + expect(GEMINI_SEARCH_REGEX.test('gemini-1.5-pro')).toBe(false) + expect(GEMINI_SEARCH_REGEX.test('gemini-1.0-pro')).toBe(false) + }) + }) +}) diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index cc5449f81..0d4c65284 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -254,7 +254,7 @@ export function isGeminiReasoningModel(model?: Model): boolean { // Gemini 支持思考模式的模型正则 export const GEMINI_THINKING_MODEL_REGEX = - /gemini-(?:2\.5.*(?:-latest)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\w-]+)*$/i + /gemini-(?:2\.5.*(?:-latest)?|3-(?:flash|pro)(?:-preview)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\w-]+)*$/i export const isSupportedThinkingTokenGeminiModel = (model: Model): boolean => { const modelId = getLowerBaseModelName(model.id, '/') diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index 18b348071..21d553d24 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -12,6 +12,7 @@ const visionAllowedModels = [ 'gemini-1\\.5', 'gemini-2\\.0', 'gemini-2\\.5', + 'gemini-3-(?:flash|pro)(?:-preview)?', 'gemini-(flash|pro|flash-lite)-latest', 'gemini-exp', 'claude-3', @@ -64,13 +65,13 @@ const visionExcludedModels = [ 'o1-preview', 'AIDC-AI/Marco-o1' ] -export const VISION_REGEX = new RegExp( +const VISION_REGEX = new RegExp( `\\b(?!(?:${visionExcludedModels.join('|')})\\b)(${visionAllowedModels.join('|')})\\b`, 'i' ) // For middleware to identify models that must use the dedicated Image API -export const DEDICATED_IMAGE_MODELS = [ +const DEDICATED_IMAGE_MODELS = [ 'grok-2-image', 'grok-2-image-1212', 'grok-2-image-latest', @@ -79,7 +80,7 @@ export const DEDICATED_IMAGE_MODELS = [ 'gpt-image-1' ] -export const IMAGE_ENHANCEMENT_MODELS = [ +const IMAGE_ENHANCEMENT_MODELS = [ 'grok-2-image(?:-[\\w-]+)?', 'qwen-image-edit', 'gpt-image-1', @@ -90,9 +91,9 @@ export const IMAGE_ENHANCEMENT_MODELS = [ const IMAGE_ENHANCEMENT_MODELS_REGEX = new RegExp(IMAGE_ENHANCEMENT_MODELS.join('|'), 'i') // Models that should auto-enable image generation button when selected -export const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image', ...DEDICATED_IMAGE_MODELS] +const AUTO_ENABLE_IMAGE_MODELS = ['gemini-2.5-flash-image', ...DEDICATED_IMAGE_MODELS] -export const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [ +const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [ 'o3', 'gpt-4o', 'gpt-4o-mini', @@ -102,9 +103,9 @@ export const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [ 'gpt-5' ] -export const OPENAI_IMAGE_GENERATION_MODELS = [...OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS, 'gpt-image-1'] +const OPENAI_IMAGE_GENERATION_MODELS = [...OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS, 'gpt-image-1'] -export const GENERATE_IMAGE_MODELS = [ +const GENERATE_IMAGE_MODELS = [ 'gemini-2.0-flash-exp', 'gemini-2.0-flash-exp-image-generation', 'gemini-2.0-flash-preview-image-generation', @@ -169,22 +170,23 @@ export function isPureGenerateImageModel(model: Model): boolean { } // Text to image models -export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus|midjourney|mj-|image|gpt-image/i +const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus|midjourney|mj-|image|gpt-image/i export function isTextToImageModel(model: Model): boolean { const modelId = getLowerBaseModelName(model.id) return TEXT_TO_IMAGE_REGEX.test(modelId) } -export function isNotSupportedImageSizeModel(model?: Model): boolean { - if (!model) { - return false - } +// It's not used now +// export function isNotSupportedImageSizeModel(model?: Model): boolean { +// if (!model) { +// return false +// } - const baseName = getLowerBaseModelName(model.id, '/') +// const baseName = getLowerBaseModelName(model.id, '/') - return baseName.includes('grok-2-image') -} +// return baseName.includes('grok-2-image') +// } /** * 判断模型是否支持图片增强(包括编辑、增强、修复等) diff --git a/src/renderer/src/config/models/websearch.ts b/src/renderer/src/config/models/websearch.ts index f012be7cf..f7bca774b 100644 --- a/src/renderer/src/config/models/websearch.ts +++ b/src/renderer/src/config/models/websearch.ts @@ -3,7 +3,13 @@ import type { Model } from '@renderer/types' import { SystemProviderIds } from '@renderer/types' import { getLowerBaseModelName, isUserSelectedModelType } from '@renderer/utils' -import { isGeminiProvider, isNewApiProvider, isOpenAICompatibleProvider, isOpenAIProvider } from '../providers' +import { + isGeminiProvider, + isNewApiProvider, + isOpenAICompatibleProvider, + isOpenAIProvider, + isVertexAiProvider +} from '../providers' import { isEmbeddingModel, isRerankModel } from './embedding' import { isAnthropicModel } from './utils' import { isPureGenerateImageModel, isTextToImageModel } from './vision' @@ -16,7 +22,7 @@ export const CLAUDE_SUPPORTED_WEBSEARCH_REGEX = new RegExp( export const GEMINI_FLASH_MODEL_REGEX = new RegExp('gemini.*-flash.*$') export const GEMINI_SEARCH_REGEX = new RegExp( - 'gemini-(?:2.*(?:-latest)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\\w-]+)*$', + 'gemini-(?:2.*(?:-latest)?|3-(?:flash|pro)(?:-preview)?|flash-latest|pro-latest|flash-lite-latest)(?:-[\\w-]+)*$', 'i' ) @@ -107,7 +113,7 @@ export function isWebSearchModel(model: Model): boolean { } } - if (isGeminiProvider(provider) || provider.id === SystemProviderIds.vertexai) { + if (isGeminiProvider(provider) || isVertexAiProvider(provider)) { return GEMINI_SEARCH_REGEX.test(modelId) } diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 92ac37e9f..c4c280e66 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -1571,6 +1571,10 @@ export function isGeminiProvider(provider: Provider): boolean { return provider.type === 'gemini' } +export function isVertexAiProvider(provider: Provider): boolean { + return provider.type === 'vertexai' +} + export function isAIGatewayProvider(provider: Provider): boolean { return provider.type === 'ai-gateway' } diff --git a/yarn.lock b/yarn.lock index dc6f25823..0ee7c9401 100644 --- a/yarn.lock +++ b/yarn.lock @@ -102,7 +102,19 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/anthropic@npm:2.0.44, @ai-sdk/anthropic@npm:^2.0.44": +"@ai-sdk/anthropic@npm:2.0.45": + version: 2.0.45 + resolution: "@ai-sdk/anthropic@npm:2.0.45" + dependencies: + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.17" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/ef0e54f032e3b8324c278f3b25d9b388308204d753404c49fd880709a796c2343aee36d335c99f50e683edd39d5b8b6f42b2e9034e1725d8e0db514e2233d104 + languageName: node + linkType: hard + +"@ai-sdk/anthropic@npm:^2.0.44": version: 2.0.44 resolution: "@ai-sdk/anthropic@npm:2.0.44" dependencies: @@ -179,54 +191,42 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/google-vertex@npm:^3.0.62": - version: 3.0.62 - resolution: "@ai-sdk/google-vertex@npm:3.0.62" +"@ai-sdk/google-vertex@npm:^3.0.68": + version: 3.0.68 + resolution: "@ai-sdk/google-vertex@npm:3.0.68" dependencies: - "@ai-sdk/anthropic": "npm:2.0.44" - "@ai-sdk/google": "npm:2.0.31" + "@ai-sdk/anthropic": "npm:2.0.45" + "@ai-sdk/google": "npm:2.0.36" "@ai-sdk/provider": "npm:2.0.0" "@ai-sdk/provider-utils": "npm:3.0.17" google-auth-library: "npm:^9.15.0" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/673bb51e3e0cbe5235ad5e65379b1cb8f099dbc690ab8552e208553a9f1cc6026d2588e956e73468bc6d267066be276e7a9aba98e32e905809dfbeab4ac0e352 + checksum: 10c0/6a3f4cb1e649313b46a0c349c717757071f8b012b0a28e59ab7a55fd35d9600f0043f0a4f57417c4cc49e0d3734e89a1e4fb248fc88795b5286c83395d3f617a languageName: node linkType: hard -"@ai-sdk/google@npm:2.0.31": - version: 2.0.31 - resolution: "@ai-sdk/google@npm:2.0.31" +"@ai-sdk/google@npm:2.0.36": + version: 2.0.36 + resolution: "@ai-sdk/google@npm:2.0.36" dependencies: "@ai-sdk/provider": "npm:2.0.0" "@ai-sdk/provider-utils": "npm:3.0.17" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/d8f143f058fb62e6e67e30564ec92530d7389c22ad91b1e4bbe781c8570bf718cd417e44dcd4855e347e85c4174538a9a884eac666109e17f20d21467ab3e749 + checksum: 10c0/2c6de5e1cf0703b6b932a3f313bf4bc9439897af39c805169ab04bba397185d99b2b1306f3b817f991ca41fdced0365b072ee39e76382c045930256bce47e0e4 languageName: node linkType: hard -"@ai-sdk/google@npm:^2.0.32": - version: 2.0.32 - resolution: "@ai-sdk/google@npm:2.0.32" +"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch": + version: 2.0.36 + resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch::version=2.0.36&hash=2da8c3" dependencies: "@ai-sdk/provider": "npm:2.0.0" "@ai-sdk/provider-utils": "npm:3.0.17" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/052de16f1f66188e126168c8a9cc903448104528c7e44d6867bbf555c9067b9d6d44a4c4e0e014838156ba39095cb417f1b76363eb65212ca4d005f3651e58d2 - languageName: node - linkType: hard - -"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch": - version: 2.0.31 - resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch::version=2.0.31&hash=9f3835" - dependencies: - "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.17" - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/dd37dfb7abf402caaae3edb2f1a8dab018fddad6ba3190376723e03a2a0c352329c8e41e60df3fb8436b717d9c2ee4b82dff091848f50d026f62565cbdb158f8 + checksum: 10c0/ce99a497360377d2917cf3a48278eb6f4337623ce3738ba743cf048c8c2a7731ec4fc27605a50e461e716ed49b3690206ca8e4078f27cb7be162b684bfc2fc22 languageName: node linkType: hard @@ -1898,7 +1898,7 @@ __metadata: "@ai-sdk/anthropic": "npm:^2.0.43" "@ai-sdk/azure": "npm:^2.0.66" "@ai-sdk/deepseek": "npm:^1.0.27" - "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch" + "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch" "@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch" "@ai-sdk/openai-compatible": "npm:^1.0.26" "@ai-sdk/provider": "npm:^2.0.0" @@ -9906,8 +9906,8 @@ __metadata: "@ai-sdk/anthropic": "npm:^2.0.44" "@ai-sdk/cerebras": "npm:^1.0.31" "@ai-sdk/gateway": "npm:^2.0.9" - "@ai-sdk/google": "npm:^2.0.32" - "@ai-sdk/google-vertex": "npm:^3.0.62" + "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.36#~/.yarn/patches/@ai-sdk-google-npm-2.0.36-6f3cc06026.patch" + "@ai-sdk/google-vertex": "npm:^3.0.68" "@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch" "@ai-sdk/mistral": "npm:^2.0.23" "@ai-sdk/perplexity": "npm:^2.0.17"