From 44a149a8eaab8f190fd6d582a268f03dfd36bf7c Mon Sep 17 00:00:00 2001 From: cherryai002 Date: Mon, 9 Mar 2026 19:58:08 +0800 Subject: [PATCH 1/4] fix: support gemini-3.1-flash-image-preview image generation in chat --- .../src/aiCore/utils/__tests__/image.test.ts | 22 +++++++++++++++++++ src/renderer/src/aiCore/utils/image.ts | 4 +++- .../config/models/__tests__/vision.test.ts | 12 ++++++++++ src/renderer/src/config/models/default.ts | 12 ++++++++++ src/renderer/src/config/models/vision.ts | 17 +++++++++----- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/aiCore/utils/__tests__/image.test.ts b/src/renderer/src/aiCore/utils/__tests__/image.test.ts index 1c5381a5efb..45d58f07529 100644 --- a/src/renderer/src/aiCore/utils/__tests__/image.test.ts +++ b/src/renderer/src/aiCore/utils/__tests__/image.test.ts @@ -56,6 +56,17 @@ describe('image utils', () => { expect(result).toBe(true) }) + it('should return true for OpenRouter Gemini 3.1 Flash Image model', () => { + const model: Model = { + id: 'google/gemini-3.1-flash-image-preview', + name: 'Gemini 3.1 Flash Image Preview', + provider: SystemProviderIds.openrouter + } as Model + + const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) + expect(result).toBe(true) + }) + it('should return false for non-Gemini model on OpenRouter', () => { const model: Model = { id: 'openai/gpt-4', @@ -89,6 +100,17 @@ describe('image utils', () => { expect(result).toBe(false) }) + it('should return false for Gemini 3.1 Flash model without image suffix', () => { + const model: Model = { + id: 'google/gemini-3.1-flash-preview', + name: 'Gemini 3.1 Flash Preview', + provider: SystemProviderIds.openrouter + } as Model + + const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) + expect(result).toBe(false) + }) + it('should handle model ID with partial match', () => { const model: Model = { id: 'google/gemini-2.5-flash-image-generation', diff --git a/src/renderer/src/aiCore/utils/image.ts b/src/renderer/src/aiCore/utils/image.ts index 37dbe76a2c4..77651cbd107 100644 --- a/src/renderer/src/aiCore/utils/image.ts +++ b/src/renderer/src/aiCore/utils/image.ts @@ -1,6 +1,8 @@ import type { Model, Provider } from '@renderer/types' import { isSystemProvider, SystemProviderIds } from '@renderer/types' +const OPENROUTER_GEMINI_IMAGE_MODEL_REGEX = /^google\/gemini-(?:2\.5-flash|3(?:\.\d+)?-flash)-image(?:-[\w-]+)?$/i + export function buildGeminiGenerateImageParams(): Record { return { responseModalities: ['TEXT', 'IMAGE'] @@ -9,7 +11,7 @@ export function buildGeminiGenerateImageParams(): Record { export function isOpenRouterGeminiGenerateImageModel(model: Model, provider: Provider): boolean { return ( - model.id.includes('gemini-2.5-flash-image') && + OPENROUTER_GEMINI_IMAGE_MODEL_REGEX.test(model.id) && isSystemProvider(provider) && provider.id === SystemProviderIds.openrouter ) diff --git a/src/renderer/src/config/models/__tests__/vision.test.ts b/src/renderer/src/config/models/__tests__/vision.test.ts index a70e39adcdf..2878dca539f 100644 --- a/src/renderer/src/config/models/__tests__/vision.test.ts +++ b/src/renderer/src/config/models/__tests__/vision.test.ts @@ -99,6 +99,7 @@ describe('vision helpers', () => { providerMock.mockReturnValue({ type: 'custom' } as any) expect(isGenerateImageModel(createModel({ id: 'gemini-2.5-flash-image' }))).toBe(true) + expect(isGenerateImageModel(createModel({ id: 'gemini-3.1-flash-image-preview' }))).toBe(true) }) it('returns false when openai-response model is not on allow list', () => { @@ -111,6 +112,7 @@ describe('vision helpers', () => { expect(isPureGenerateImageModel(createModel({ id: 'gpt-image-1' }))).toBe(true) expect(isPureGenerateImageModel(createModel({ id: 'gpt-4o' }))).toBe(false) expect(isPureGenerateImageModel(createModel({ id: 'gemini-2.5-flash-image-preview' }))).toBe(true) + expect(isPureGenerateImageModel(createModel({ id: 'gemini-3.1-flash-image-preview' }))).toBe(false) }) }) @@ -122,12 +124,14 @@ describe('vision helpers', () => { it('detects models with restricted image size support and enhancement', () => { expect(isImageEnhancementModel(createModel({ id: 'qwen-image-edit' }))).toBe(true) + expect(isImageEnhancementModel(createModel({ id: 'gemini-3.1-flash-image-preview' }))).toBe(true) expect(isImageEnhancementModel(createModel({ id: 'gpt-4o' }))).toBe(false) }) it('identifies dedicated and auto-enabled image generation models', () => { expect(isDedicatedImageGenerationModel(createModel({ id: 'grok-2-image-1212' }))).toBe(true) expect(isAutoEnableImageGenerationModel(createModel({ id: 'gemini-2.5-flash-image-ultra' }))).toBe(true) + expect(isAutoEnableImageGenerationModel(createModel({ id: 'gemini-3.1-flash-image-preview' }))).toBe(true) }) it('returns false when models are not in dedicated or auto-enable sets', () => { @@ -314,6 +318,14 @@ describe('isVisionModel', () => { group: '' }) ).toBe(true) + expect( + isVisionModel({ + id: 'gemini-3.1-flash-image-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) }) it('should return true for gemini exp models', () => { diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index 73a131fa392..594f097290b 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -360,6 +360,12 @@ export const SYSTEM_MODELS: Record = name: 'Gemini 2.5 Flash Image', group: 'Gemini 2.5' }, + { + id: 'gemini-3.1-flash-image-preview', + provider: 'gemini', + name: 'Gemini 3.1 Flash Image Preview', + group: 'Gemini 3' + }, { id: 'gemini-3-pro-image-preview', provider: 'gemini', @@ -1346,6 +1352,12 @@ export const SYSTEM_MODELS: Record = name: 'Google: Gemini 2.5 Flash Image', group: 'google' }, + { + id: 'google/gemini-3.1-flash-image-preview', + provider: 'openrouter', + name: 'Google: Gemini 3.1 Flash Image Preview', + group: 'google' + }, { id: 'google/gemini-2.5-flash-preview', provider: 'openrouter', diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index a655a5e2d69..e68e906f6d7 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -14,6 +14,7 @@ const visionAllowedModels = [ 'gemini-2\\.0', 'gemini-2\\.5', 'gemini-3(?:\\.\\d)?-(?:flash|pro)(?:-preview)?', + 'gemini-3(?:\\.\\d+)?-(?:flash|pro)-image(?:-[\\w-]+)?', 'gemini-(flash|pro|flash-lite)-latest', 'gemini-exp', 'claude-3', @@ -111,13 +112,17 @@ const DEDICATED_IMAGE_MODELS = [ 'kandinsky(?:-[\\w-]+)?' ] +const GEMINI_FLASH_IMAGE_MODELS = ['gemini-2.5-flash-image(?:-[\\w-]+)?', 'gemini-3(?:\\.\\d+)?-flash-image(?:-[\\w-]+)?'] + +const GEMINI_PRO_IMAGE_MODELS = ['gemini-3(?:\\.\\d+)?-pro-image(?:-[\\w-]+)?'] + const IMAGE_ENHANCEMENT_MODELS = [ 'grok-2-image(?:-[\\w-]+)?', 'qwen-image-edit', 'gpt-image-1', - 'gemini-2.5-flash-image(?:-[\\w-]+)?', + ...GEMINI_FLASH_IMAGE_MODELS, 'gemini-2.0-flash-preview-image-generation', - 'gemini-3(?:\\.\\d+)?-pro-image(?:-[\\w-]+)?' + ...GEMINI_PRO_IMAGE_MODELS ] const IMAGE_ENHANCEMENT_MODELS_REGEX = new RegExp(IMAGE_ENHANCEMENT_MODELS.join('|'), 'i') @@ -126,8 +131,8 @@ const DEDICATED_IMAGE_MODEL_REGEX = new RegExp(DEDICATED_IMAGE_MODELS.join('|'), // Models that should auto-enable image generation button when selected const AUTO_ENABLE_IMAGE_MODELS = [ - 'gemini-2.5-flash-image(?:-[\\w-]+)?', - 'gemini-3(?:\\.\\d+)?-pro-image(?:-[\\w-]+)?', + ...GEMINI_FLASH_IMAGE_MODELS, + ...GEMINI_PRO_IMAGE_MODELS, ...DEDICATED_IMAGE_MODELS ] @@ -145,11 +150,11 @@ const OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS = [ const OPENAI_IMAGE_GENERATION_MODELS = [...OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS, 'gpt-image-1'] -const MODERN_IMAGE_MODELS = ['gemini-3(?:\\.\\d+)?-pro-image(?:-[\\w-]+)?'] +const MODERN_IMAGE_MODELS = [...GEMINI_FLASH_IMAGE_MODELS.slice(1), ...GEMINI_PRO_IMAGE_MODELS] const GENERATE_IMAGE_MODELS = [ 'gemini-2.0-flash-exp(?:-[\\w-]+)?', - 'gemini-2.5-flash-image(?:-[\\w-]+)?', + ...GEMINI_FLASH_IMAGE_MODELS, 'gemini-2.0-flash-preview-image-generation', ...MODERN_IMAGE_MODELS, ...DEDICATED_IMAGE_MODELS From ccb4e538264941d7e2f80b621e164cfcecb6424b Mon Sep 17 00:00:00 2001 From: cherryai002 Date: Mon, 9 Mar 2026 20:30:40 +0800 Subject: [PATCH 2/4] refactor: remove openrouter image model special-casing --- .../src/aiCore/plugins/PluginBuilder.ts | 5 +- .../plugins/__tests__/PluginBuilder.test.ts | 169 ++++++++++++++++++ .../src/aiCore/utils/__tests__/image.test.ts | 117 +----------- src/renderer/src/aiCore/utils/image.ts | 13 -- 4 files changed, 172 insertions(+), 132 deletions(-) create mode 100644 src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts diff --git a/src/renderer/src/aiCore/plugins/PluginBuilder.ts b/src/renderer/src/aiCore/plugins/PluginBuilder.ts index 60fb3d7351b..3abb52da709 100644 --- a/src/renderer/src/aiCore/plugins/PluginBuilder.ts +++ b/src/renderer/src/aiCore/plugins/PluginBuilder.ts @@ -8,7 +8,6 @@ import { SystemProviderIds } from '@renderer/types' import { isOllamaProvider, isSupportEnableThinkingProvider } from '@renderer/utils/provider' import type { AiSdkMiddlewareConfig } from '../types/middlewareConfig' -import { isOpenRouterGeminiGenerateImageModel } from '../utils/image' import { getReasoningTagName } from '../utils/reasoning' import { createAnthropicCachePlugin } from './anthropicCachePlugin' import { createNoThinkPlugin } from './noThinkPlugin' @@ -96,8 +95,8 @@ export function buildPlugins({ provider, model, config }: BuildPluginsContext): plugins.push(createQwenThinkingPlugin(enableThinking)) } - // 0.6 OpenRouter Gemini image generation - if (isOpenRouterGeminiGenerateImageModel(model, provider)) { + // 0.6 OpenRouter image generation + if (provider.id === SystemProviderIds.openrouter && config.enableGenerateImage) { plugins.push(createOpenrouterGenerateImagePlugin()) } diff --git a/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts b/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts new file mode 100644 index 00000000000..1b8a366894f --- /dev/null +++ b/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts @@ -0,0 +1,169 @@ +import type { Assistant, Model, Provider } from '@renderer/types' +import { SystemProviderIds } from '@renderer/types' +import { describe, expect, it, vi } from 'vitest' + +import { buildPlugins } from '../PluginBuilder' + +vi.mock('@logger', () => ({ + loggerService: { + withContext: () => ({ + debug: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + info: vi.fn() + }) + } +})) + +vi.mock('@renderer/hooks/useSettings', () => ({ + getEnableDeveloperMode: vi.fn(() => false) +})) + +vi.mock('@renderer/config/models', () => ({ + isGemini3Model: vi.fn(() => false), + isQwen35Model: vi.fn(() => false), + isSupportedThinkingTokenQwenModel: vi.fn(() => false) +})) + +vi.mock('@renderer/utils/provider', () => ({ + isOllamaProvider: vi.fn(() => false), + isSupportEnableThinkingProvider: vi.fn(() => false) +})) + +vi.mock('@cherrystudio/ai-core/built-in/plugins', () => ({ + createPromptToolUsePlugin: vi.fn(() => ({ name: 'promptToolUse' })), + webSearchPlugin: vi.fn(() => ({ name: 'webSearch' })) +})) + +vi.mock('../anthropicCachePlugin', () => ({ + createAnthropicCachePlugin: vi.fn(() => ({ name: 'anthropicCache' })) +})) + +vi.mock('../noThinkPlugin', () => ({ + createNoThinkPlugin: vi.fn(() => ({ name: 'noThink' })) +})) + +vi.mock('../openrouterGenerateImagePlugin', () => ({ + createOpenrouterGenerateImagePlugin: vi.fn(() => ({ name: 'openrouterGenerateImage' })) +})) + +vi.mock('../openrouterReasoningPlugin', () => ({ + createOpenrouterReasoningPlugin: vi.fn(() => ({ name: 'openrouterReasoning' })) +})) + +vi.mock('../qwenThinkingPlugin', () => ({ + createQwenThinkingPlugin: vi.fn(() => ({ name: 'qwenThinking' })) +})) + +vi.mock('../reasoningExtractionPlugin', () => ({ + createReasoningExtractionPlugin: vi.fn(() => ({ name: 'reasoningExtraction' })) +})) + +vi.mock('../searchOrchestrationPlugin', () => ({ + searchOrchestrationPlugin: vi.fn(() => ({ name: 'searchOrchestration' })) +})) + +vi.mock('../simulateStreamingPlugin', () => ({ + createSimulateStreamingPlugin: vi.fn(() => ({ name: 'simulateStreaming' })) +})) + +vi.mock('../skipGeminiThoughtSignaturePlugin', () => ({ + createSkipGeminiThoughtSignaturePlugin: vi.fn(() => ({ name: 'skipGeminiThoughtSignature' })) +})) + +vi.mock('../telemetryPlugin', () => ({ + createTelemetryPlugin: vi.fn(() => ({ name: 'telemetry' })) +})) + +vi.mock('../../utils/reasoning', () => ({ + getReasoningTagName: vi.fn(() => 'think') +})) + +function createAssistant(): Assistant { + return { + id: 'assistant-1', + name: 'Test Assistant', + prompt: '', + topics: [], + type: 'assistant', + settings: {} + } +} + +function createModel(provider = SystemProviderIds.openrouter): Model { + return { + id: 'google/gemini-2.5-flash-image-preview', + name: 'Gemini 2.5 Flash Image', + provider + } as Model +} + +function createProvider(id = SystemProviderIds.openrouter): Provider { + return { + id, + name: 'Test Provider', + type: id === SystemProviderIds.openrouter ? 'openai' : 'google' + } as Provider +} + +describe('PluginBuilder', () => { + it('mounts openrouterGenerateImage plugin when provider is openrouter and generate image is enabled', () => { + const plugins = buildPlugins({ + provider: createProvider(), + model: createModel(), + config: { + assistant: createAssistant(), + streamOutput: true, + enableReasoning: false, + isPromptToolUse: false, + isSupportedToolUse: false, + isImageGenerationEndpoint: false, + enableWebSearch: false, + enableGenerateImage: true, + enableUrlContext: false + } + }) + + expect(plugins.map((plugin) => plugin.name)).toContain('openrouterGenerateImage') + }) + + it('does not mount openrouterGenerateImage plugin when generate image is disabled', () => { + const plugins = buildPlugins({ + provider: createProvider(), + model: createModel(), + config: { + assistant: createAssistant(), + streamOutput: true, + enableReasoning: false, + isPromptToolUse: false, + isSupportedToolUse: false, + isImageGenerationEndpoint: false, + enableWebSearch: false, + enableGenerateImage: false, + enableUrlContext: false + } + }) + + expect(plugins.map((plugin) => plugin.name)).not.toContain('openrouterGenerateImage') + }) + + it('does not mount openrouterGenerateImage plugin for non-openrouter providers', () => { + const plugins = buildPlugins({ + provider: createProvider(SystemProviderIds.gemini), + model: createModel(SystemProviderIds.gemini), + config: { + assistant: createAssistant(), + streamOutput: true, + enableReasoning: false, + isPromptToolUse: false, + isSupportedToolUse: false, + isImageGenerationEndpoint: false, + enableWebSearch: false, + enableGenerateImage: true, + enableUrlContext: false + } + }) + + expect(plugins.map((plugin) => plugin.name)).not.toContain('openrouterGenerateImage') + }) +}) diff --git a/src/renderer/src/aiCore/utils/__tests__/image.test.ts b/src/renderer/src/aiCore/utils/__tests__/image.test.ts index 45d58f07529..7e9d0aec126 100644 --- a/src/renderer/src/aiCore/utils/__tests__/image.test.ts +++ b/src/renderer/src/aiCore/utils/__tests__/image.test.ts @@ -3,11 +3,9 @@ * Tests for Gemini image generation utilities */ -import type { Model, Provider } from '@renderer/types' -import { SystemProviderIds } from '@renderer/types' import { describe, expect, it } from 'vitest' -import { buildGeminiGenerateImageParams, isOpenRouterGeminiGenerateImageModel } from '../image' +import { buildGeminiGenerateImageParams } from '../image' describe('image utils', () => { describe('buildGeminiGenerateImageParams', () => { @@ -27,117 +25,4 @@ describe('image utils', () => { expect(result.responseModalities).toHaveLength(2) }) }) - - describe('isOpenRouterGeminiGenerateImageModel', () => { - const mockOpenRouterProvider: Provider = { - id: SystemProviderIds.openrouter, - name: 'OpenRouter', - apiKey: 'test-key', - apiHost: 'https://openrouter.ai/api/v1', - isSystem: true - } as Provider - - const mockOtherProvider: Provider = { - id: SystemProviderIds.openai, - name: 'OpenAI', - apiKey: 'test-key', - apiHost: 'https://api.openai.com/v1', - isSystem: true - } as Provider - - it('should return true for OpenRouter Gemini 2.5 Flash Image model', () => { - const model: Model = { - id: 'google/gemini-2.5-flash-image-preview', - name: 'Gemini 2.5 Flash Image', - provider: SystemProviderIds.openrouter - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) - expect(result).toBe(true) - }) - - it('should return true for OpenRouter Gemini 3.1 Flash Image model', () => { - const model: Model = { - id: 'google/gemini-3.1-flash-image-preview', - name: 'Gemini 3.1 Flash Image Preview', - provider: SystemProviderIds.openrouter - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) - expect(result).toBe(true) - }) - - it('should return false for non-Gemini model on OpenRouter', () => { - const model: Model = { - id: 'openai/gpt-4', - name: 'GPT-4', - provider: SystemProviderIds.openrouter - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) - expect(result).toBe(false) - }) - - it('should return false for Gemini model on non-OpenRouter provider', () => { - const model: Model = { - id: 'gemini-2.5-flash-image-preview', - name: 'Gemini 2.5 Flash Image', - provider: SystemProviderIds.gemini - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOtherProvider) - expect(result).toBe(false) - }) - - it('should return false for Gemini model without image suffix', () => { - const model: Model = { - id: 'google/gemini-2.5-flash', - name: 'Gemini 2.5 Flash', - provider: SystemProviderIds.openrouter - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) - expect(result).toBe(false) - }) - - it('should return false for Gemini 3.1 Flash model without image suffix', () => { - const model: Model = { - id: 'google/gemini-3.1-flash-preview', - name: 'Gemini 3.1 Flash Preview', - provider: SystemProviderIds.openrouter - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) - expect(result).toBe(false) - }) - - it('should handle model ID with partial match', () => { - const model: Model = { - id: 'google/gemini-2.5-flash-image-generation', - name: 'Gemini Image Gen', - provider: SystemProviderIds.openrouter - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, mockOpenRouterProvider) - expect(result).toBe(true) - }) - - it('should return false for custom provider', () => { - const customProvider: Provider = { - id: 'custom-provider-123', - name: 'Custom Provider', - apiKey: 'test-key', - apiHost: 'https://custom.com' - } as Provider - - const model: Model = { - id: 'gemini-2.5-flash-image-preview', - name: 'Gemini 2.5 Flash Image', - provider: 'custom-provider-123' - } as Model - - const result = isOpenRouterGeminiGenerateImageModel(model, customProvider) - expect(result).toBe(false) - }) - }) }) diff --git a/src/renderer/src/aiCore/utils/image.ts b/src/renderer/src/aiCore/utils/image.ts index 77651cbd107..7691f9d4b19 100644 --- a/src/renderer/src/aiCore/utils/image.ts +++ b/src/renderer/src/aiCore/utils/image.ts @@ -1,18 +1,5 @@ -import type { Model, Provider } from '@renderer/types' -import { isSystemProvider, SystemProviderIds } from '@renderer/types' - -const OPENROUTER_GEMINI_IMAGE_MODEL_REGEX = /^google\/gemini-(?:2\.5-flash|3(?:\.\d+)?-flash)-image(?:-[\w-]+)?$/i - export function buildGeminiGenerateImageParams(): Record { return { responseModalities: ['TEXT', 'IMAGE'] } } - -export function isOpenRouterGeminiGenerateImageModel(model: Model, provider: Provider): boolean { - return ( - OPENROUTER_GEMINI_IMAGE_MODEL_REGEX.test(model.id) && - isSystemProvider(provider) && - provider.id === SystemProviderIds.openrouter - ) -} From 8c6302cc29469a8c829c43645d0c4c64109ac68d Mon Sep 17 00:00:00 2001 From: cherryai002 Date: Mon, 9 Mar 2026 22:54:38 +0800 Subject: [PATCH 3/4] style: format vision model patterns --- src/renderer/src/config/models/vision.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index e68e906f6d7..53d09246aa7 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -112,7 +112,10 @@ const DEDICATED_IMAGE_MODELS = [ 'kandinsky(?:-[\\w-]+)?' ] -const GEMINI_FLASH_IMAGE_MODELS = ['gemini-2.5-flash-image(?:-[\\w-]+)?', 'gemini-3(?:\\.\\d+)?-flash-image(?:-[\\w-]+)?'] +const GEMINI_FLASH_IMAGE_MODELS = [ + 'gemini-2.5-flash-image(?:-[\\w-]+)?', + 'gemini-3(?:\\.\\d+)?-flash-image(?:-[\\w-]+)?' +] const GEMINI_PRO_IMAGE_MODELS = ['gemini-3(?:\\.\\d+)?-pro-image(?:-[\\w-]+)?'] @@ -130,11 +133,7 @@ const IMAGE_ENHANCEMENT_MODELS_REGEX = new RegExp(IMAGE_ENHANCEMENT_MODELS.join( const DEDICATED_IMAGE_MODEL_REGEX = new RegExp(DEDICATED_IMAGE_MODELS.join('|'), 'i') // Models that should auto-enable image generation button when selected -const AUTO_ENABLE_IMAGE_MODELS = [ - ...GEMINI_FLASH_IMAGE_MODELS, - ...GEMINI_PRO_IMAGE_MODELS, - ...DEDICATED_IMAGE_MODELS -] +const AUTO_ENABLE_IMAGE_MODELS = [...GEMINI_FLASH_IMAGE_MODELS, ...GEMINI_PRO_IMAGE_MODELS, ...DEDICATED_IMAGE_MODELS] const AUTO_ENABLE_IMAGE_MODELS_REGEX = new RegExp(AUTO_ENABLE_IMAGE_MODELS.join('|'), 'i') From f80ee461b676a87485555e5df90d663f78ed8d56 Mon Sep 17 00:00:00 2001 From: cherryai002 Date: Tue, 10 Mar 2026 16:04:34 +0800 Subject: [PATCH 4/4] test: fix type error in PluginBuilder test for non-openrouter provider case --- .../src/aiCore/plugins/__tests__/PluginBuilder.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts b/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts index 1b8a366894f..2b9e0e0bd74 100644 --- a/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts +++ b/src/renderer/src/aiCore/plugins/__tests__/PluginBuilder.test.ts @@ -149,8 +149,8 @@ describe('PluginBuilder', () => { it('does not mount openrouterGenerateImage plugin for non-openrouter providers', () => { const plugins = buildPlugins({ - provider: createProvider(SystemProviderIds.gemini), - model: createModel(SystemProviderIds.gemini), + provider: { ...createProvider(SystemProviderIds.openrouter), id: 'gemini' } as any, + model: createModel(SystemProviderIds.openrouter), config: { assistant: createAssistant(), streamOutput: true,