From 0cf0072b51a37ad638749818e5d2af7fac3c89b9 Mon Sep 17 00:00:00 2001 From: Phantom Date: Thu, 18 Dec 2025 13:00:23 +0800 Subject: [PATCH 1/8] feat: add default reasoning effort option to resolve confusion between undefined and none (#11942) * feat(reasoning): add default reasoning effort option and update i18n Add 'default' reasoning effort option to all reasoning models to represent no additional configuration. Update translations for new option and modify reasoning logic to handle default case. Also update store version and migration for new reasoning_effort field. Update test cases and reasoning configuration to include default option. Add new lightbulb question icon for default reasoning state. * fix(ThinkingButton): correct isThinkingEnabled condition to exclude 'default' The condition now properly disables thinking when effort is 'default' to match intended behavior. Click thinking button will not switch reasoning effort to 'none'. * refactor(types): improve reasoning_effort_cache documentation Update comments to clarify the purpose and future direction of reasoning_effort_cache Remove TODO and replace with FIXME suggesting external cache service * feat(i18n): add reasoning effort descriptions and update thinking button logic add descriptions for reasoning effort options in multiple languages move reasoning effort label maps to component for better maintainability * fix(aiCore): handle default reasoning_effort value consistently across providers Ensure consistent behavior when reasoning_effort is 'default' or undefined by returning empty object * test(reasoning): fix failing tests after 'default' option introduction Fixed two test cases that were failing after the introduction of the 'default' reasoning effort option: 1. getAnthropicReasoningParams test: Updated to explicitly set reasoning_effort to 'none' instead of empty settings, as undefined/empty now represents 'default' behavior (no configuration override) 2. getGeminiReasoningParams test: Similarly updated to set reasoning_effort to 'none' for the disabled thinking test case This aligns with the new semantic where: - undefined/'default' = use model's default behavior (returns {}) - 'none' = explicitly disable reasoning (returns disabled config) --- .../legacy/clients/openai/OpenAIApiClient.ts | 6 +- .../__tests__/model-parameters.test.ts | 2 +- .../aiCore/utils/__tests__/reasoning.test.ts | 12 +- src/renderer/src/aiCore/utils/reasoning.ts | 26 ++-- src/renderer/src/components/Icons/SVGIcon.tsx | 12 ++ .../config/models/__tests__/reasoning.test.ts | 116 ++++++++++++++---- src/renderer/src/config/models/reasoning.ts | 72 ++++++----- src/renderer/src/i18n/label.ts | 16 +-- src/renderer/src/i18n/locales/en-us.json | 11 +- src/renderer/src/i18n/locales/zh-cn.json | 11 +- src/renderer/src/i18n/locales/zh-tw.json | 11 +- src/renderer/src/i18n/translate/de-de.json | 11 +- src/renderer/src/i18n/translate/el-gr.json | 11 +- src/renderer/src/i18n/translate/es-es.json | 11 +- src/renderer/src/i18n/translate/fr-fr.json | 11 +- src/renderer/src/i18n/translate/ja-jp.json | 11 +- src/renderer/src/i18n/translate/pt-pt.json | 11 +- src/renderer/src/i18n/translate/ru-ru.json | 11 +- .../tools/components/ThinkingButton.tsx | 44 +++++-- src/renderer/src/services/AssistantService.ts | 5 +- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 14 +++ src/renderer/src/types/index.ts | 15 +-- 23 files changed, 341 insertions(+), 111 deletions(-) diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts index d839da8964..73a5bed4fe 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts @@ -142,6 +142,10 @@ export class OpenAIAPIClient extends OpenAIBaseClient< return { thinking: { type: reasoningEffort ? 'enabled' : 'disabled' } } } + if (reasoningEffort === 'default') { + return {} + } + if (!reasoningEffort) { // DeepSeek hybrid inference models, v3.1 and maybe more in the future // 不同的 provider 有不同的思考控制方式,在这里统一解决 @@ -303,7 +307,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< // Grok models/Perplexity models/OpenAI models if (isSupportedReasoningEffortModel(model)) { // 检查模型是否支持所选选项 - const supportedOptions = getModelSupportedReasoningEffortOptions(model) + const supportedOptions = getModelSupportedReasoningEffortOptions(model)?.filter((option) => option !== 'default') if (supportedOptions?.includes(reasoningEffort)) { return { reasoning_effort: reasoningEffort diff --git a/src/renderer/src/aiCore/prepareParams/__tests__/model-parameters.test.ts b/src/renderer/src/aiCore/prepareParams/__tests__/model-parameters.test.ts index 70b4ac84b7..a4f345e3e5 100644 --- a/src/renderer/src/aiCore/prepareParams/__tests__/model-parameters.test.ts +++ b/src/renderer/src/aiCore/prepareParams/__tests__/model-parameters.test.ts @@ -18,7 +18,7 @@ vi.mock('@renderer/services/AssistantService', () => ({ toolUseMode: assistant.settings?.toolUseMode ?? 'prompt', defaultModel: assistant.defaultModel, customParameters: assistant.settings?.customParameters ?? [], - reasoning_effort: assistant.settings?.reasoning_effort, + reasoning_effort: assistant.settings?.reasoning_effort ?? 'default', reasoning_effort_cache: assistant.settings?.reasoning_effort_cache, qwenThinkMode: assistant.settings?.qwenThinkMode }) diff --git a/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts b/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts index fec4d197e3..e5561f6fcf 100644 --- a/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts +++ b/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts @@ -596,7 +596,7 @@ describe('reasoning utils', () => { expect(result).toEqual({}) }) - it('should return disabled thinking when no reasoning effort', async () => { + it('should return disabled thinking when reasoning effort is none', async () => { const { isReasoningModel, isSupportedThinkingTokenClaudeModel } = await import('@renderer/config/models') vi.mocked(isReasoningModel).mockReturnValue(true) @@ -611,7 +611,9 @@ describe('reasoning utils', () => { const assistant: Assistant = { id: 'test', name: 'Test', - settings: {} + settings: { + reasoning_effort: 'none' + } } as Assistant const result = getAnthropicReasoningParams(assistant, model) @@ -675,7 +677,7 @@ describe('reasoning utils', () => { expect(result).toEqual({}) }) - it('should disable thinking for Flash models without reasoning effort', async () => { + it('should disable thinking for Flash models when reasoning effort is none', async () => { const { isReasoningModel, isSupportedThinkingTokenGeminiModel } = await import('@renderer/config/models') vi.mocked(isReasoningModel).mockReturnValue(true) @@ -690,7 +692,9 @@ describe('reasoning utils', () => { const assistant: Assistant = { id: 'test', name: 'Test', - settings: {} + settings: { + reasoning_effort: 'none' + } } as Assistant const result = getGeminiReasoningParams(assistant, model) diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index f182405714..10afbbaf5f 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -64,7 +64,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // reasoningEffort is not set, no extra reasoning setting // Generally, for every model which supports reasoning control, the reasoning effort won't be undefined. // It's for some reasoning models that don't support reasoning control, such as deepseek reasoner. - if (!reasoningEffort) { + if (!reasoningEffort || reasoningEffort === 'default') { return {} } @@ -329,7 +329,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // Grok models/Perplexity models/OpenAI models, use reasoning_effort if (isSupportedReasoningEffortModel(model)) { // 检查模型是否支持所选选项 - const supportedOptions = getModelSupportedReasoningEffortOptions(model) + const supportedOptions = getModelSupportedReasoningEffortOptions(model)?.filter((option) => option !== 'default') if (supportedOptions?.includes(reasoningEffort)) { return { reasoningEffort @@ -427,7 +427,7 @@ export function getOpenAIReasoningParams( let reasoningEffort = assistant?.settings?.reasoning_effort - if (!reasoningEffort) { + if (!reasoningEffort || reasoningEffort === 'default') { return {} } @@ -505,7 +505,11 @@ export function getAnthropicReasoningParams( const reasoningEffort = assistant?.settings?.reasoning_effort - if (reasoningEffort === undefined || reasoningEffort === 'none') { + if (!reasoningEffort || reasoningEffort === 'default') { + return {} + } + + if (reasoningEffort === 'none') { return { thinking: { type: 'disabled' @@ -560,6 +564,10 @@ export function getGeminiReasoningParams( const reasoningEffort = assistant?.settings?.reasoning_effort + if (!reasoningEffort || reasoningEffort === 'default') { + return {} + } + // Gemini 推理参数 if (isSupportedThinkingTokenGeminiModel(model)) { if (reasoningEffort === undefined || reasoningEffort === 'none') { @@ -620,10 +628,6 @@ export function getXAIReasoningParams(assistant: Assistant, model: Model): Pick< const { reasoning_effort: reasoningEffort } = getAssistantSettings(assistant) - if (!reasoningEffort || reasoningEffort === 'none') { - return {} - } - switch (reasoningEffort) { case 'auto': case 'minimal': @@ -634,6 +638,10 @@ export function getXAIReasoningParams(assistant: Assistant, model: Model): Pick< return { reasoningEffort } case 'xhigh': return { reasoningEffort: 'high' } + case 'default': + case 'none': + default: + return {} } } @@ -650,7 +658,7 @@ export function getBedrockReasoningParams( const reasoningEffort = assistant?.settings?.reasoning_effort - if (reasoningEffort === undefined) { + if (reasoningEffort === undefined || reasoningEffort === 'default') { return {} } diff --git a/src/renderer/src/components/Icons/SVGIcon.tsx b/src/renderer/src/components/Icons/SVGIcon.tsx index ad503f0e38..82be6b340e 100644 --- a/src/renderer/src/components/Icons/SVGIcon.tsx +++ b/src/renderer/src/components/Icons/SVGIcon.tsx @@ -113,6 +113,18 @@ export function MdiLightbulbOn(props: SVGProps) { ) } +export function MdiLightbulbQuestion(props: SVGProps) { + // {/* Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE */} + return ( + + + + ) +} + export function BingLogo(props: SVGProps) { return ( { it('restricts GPT-5 Pro reasoning to high effort only', () => { expect(MODEL_SUPPORTED_REASONING_EFFORT.gpt5pro).toEqual(['high']) - expect(MODEL_SUPPORTED_OPTIONS.gpt5pro).toEqual(['high']) + expect(MODEL_SUPPORTED_OPTIONS.gpt5pro).toEqual(['default', 'high']) }) }) @@ -1672,10 +1672,26 @@ describe('getModelSupportedReasoningEffortOptions', () => { describe('OpenAI models', () => { it('should return correct options for o-series models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o3' }))).toEqual(['low', 'medium', 'high']) - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o3-mini' }))).toEqual(['low', 'medium', 'high']) - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o4' }))).toEqual(['low', 'medium', 'high']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o3' }))).toEqual([ + 'default', + 'low', + 'medium', + 'high' + ]) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o3-mini' }))).toEqual([ + 'default', + 'low', + 'medium', + 'high' + ]) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'o4' }))).toEqual([ + 'default', + 'low', + 'medium', + 'high' + ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-oss-reasoning' }))).toEqual([ + 'default', 'low', 'medium', 'high' @@ -1685,17 +1701,22 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for deep research models', () => { // Note: Deep research models need to be actual OpenAI reasoning models to be detected // 'sonar-deep-research' from Perplexity is the primary deep research model - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'sonar-deep-research' }))).toEqual(['medium']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'sonar-deep-research' }))).toEqual([ + 'default', + 'medium' + ]) }) it('should return correct options for GPT-5 models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5' }))).toEqual([ + 'default', 'minimal', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-preview' }))).toEqual([ + 'default', 'minimal', 'low', 'medium', @@ -1704,17 +1725,22 @@ describe('getModelSupportedReasoningEffortOptions', () => { }) it('should return correct options for GPT-5 Pro models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-pro' }))).toEqual(['high']) - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-pro-preview' }))).toEqual(['high']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-pro' }))).toEqual(['default', 'high']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-pro-preview' }))).toEqual([ + 'default', + 'high' + ]) }) it('should return correct options for GPT-5 Codex models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-codex' }))).toEqual([ + 'default', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5-codex-mini' }))).toEqual([ + 'default', 'low', 'medium', 'high' @@ -1723,18 +1749,21 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for GPT-5.1 models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1' }))).toEqual([ + 'default', 'none', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-preview' }))).toEqual([ + 'default', 'none', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-mini' }))).toEqual([ + 'default', 'none', 'low', 'medium', @@ -1744,11 +1773,13 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for GPT-5.1 Codex models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-codex' }))).toEqual([ + 'default', 'none', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gpt-5.1-codex-mini' }))).toEqual([ + 'default', 'none', 'medium', 'high' @@ -1758,19 +1789,24 @@ describe('getModelSupportedReasoningEffortOptions', () => { describe('Grok models', () => { it('should return correct options for Grok 3 mini', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'grok-3-mini' }))).toEqual(['low', 'high']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'grok-3-mini' }))).toEqual([ + 'default', + 'low', + 'high' + ]) }) it('should return correct options for Grok 4 Fast', () => { expect( getModelSupportedReasoningEffortOptions(createModel({ id: 'grok-4-fast', provider: 'openrouter' })) - ).toEqual(['none', 'auto']) + ).toEqual(['default', 'none', 'auto']) }) }) describe('Gemini models', () => { it('should return correct options for Gemini Flash models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-flash-latest' }))).toEqual([ + 'default', 'none', 'low', 'medium', @@ -1778,6 +1814,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { 'auto' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-flash-latest' }))).toEqual([ + 'default', 'none', 'low', 'medium', @@ -1788,12 +1825,14 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for Gemini Pro models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-pro-latest' }))).toEqual([ + 'default', 'low', 'medium', 'high', 'auto' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-pro-latest' }))).toEqual([ + 'default', 'low', 'medium', 'high', @@ -1803,11 +1842,13 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for Gemini 3 models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-flash' }))).toEqual([ + 'default', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-pro-preview' }))).toEqual([ + 'default', 'low', 'medium', 'high' @@ -1818,24 +1859,28 @@ describe('getModelSupportedReasoningEffortOptions', () => { describe('Qwen models', () => { it('should return correct options for controllable Qwen models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen-plus' }))).toEqual([ + 'default', 'none', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen-turbo' }))).toEqual([ + 'default', 'none', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen-flash' }))).toEqual([ + 'default', 'none', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'qwen3-8b' }))).toEqual([ + 'default', 'none', 'low', 'medium', @@ -1853,11 +1898,13 @@ describe('getModelSupportedReasoningEffortOptions', () => { describe('Doubao models', () => { it('should return correct options for auto-thinking Doubao models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-seed-1.6' }))).toEqual([ + 'default', 'none', 'auto', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-1-5-thinking-pro-m' }))).toEqual([ + 'default', 'none', 'auto', 'high' @@ -1866,12 +1913,14 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for Doubao models after 251015', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-seed-1-6-251015' }))).toEqual([ + 'default', 'minimal', 'low', 'medium', 'high' ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-seed-1-6-lite-251015' }))).toEqual([ + 'default', 'minimal', 'low', 'medium', @@ -1881,6 +1930,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { it('should return correct options for other Doubao thinking models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'doubao-1.5-thinking-vision-pro' }))).toEqual([ + 'default', 'none', 'high' ]) @@ -1889,28 +1939,43 @@ describe('getModelSupportedReasoningEffortOptions', () => { describe('Other providers', () => { it('should return correct options for Hunyuan models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'hunyuan-a13b' }))).toEqual(['none', 'auto']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'hunyuan-a13b' }))).toEqual([ + 'default', + 'none', + 'auto' + ]) }) it('should return correct options for Zhipu models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'glm-4.5' }))).toEqual(['none', 'auto']) - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'glm-4.6' }))).toEqual(['none', 'auto']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'glm-4.5' }))).toEqual([ + 'default', + 'none', + 'auto' + ]) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'glm-4.6' }))).toEqual([ + 'default', + 'none', + 'auto' + ]) }) it('should return correct options for Perplexity models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'sonar-deep-research' }))).toEqual(['medium']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'sonar-deep-research' }))).toEqual([ + 'default', + 'medium' + ]) }) it('should return correct options for DeepSeek hybrid models', () => { expect( getModelSupportedReasoningEffortOptions(createModel({ id: 'deepseek-v3.1', provider: 'deepseek' })) - ).toEqual(['none', 'auto']) + ).toEqual(['default', 'none', 'auto']) expect( getModelSupportedReasoningEffortOptions(createModel({ id: 'deepseek-v3.2', provider: 'openrouter' })) - ).toEqual(['none', 'auto']) + ).toEqual(['default', 'none', 'auto']) expect( getModelSupportedReasoningEffortOptions(createModel({ id: 'deepseek-chat', provider: 'deepseek' })) - ).toEqual(['none', 'auto']) + ).toEqual(['default', 'none', 'auto']) }) }) @@ -1925,7 +1990,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { provider: 'openrouter' }) ) - ).toEqual(['none', 'auto']) + ).toEqual(['default', 'none', 'auto']) expect( getModelSupportedReasoningEffortOptions( @@ -1934,7 +1999,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { name: 'gpt-5.1' }) ) - ).toEqual(['none', 'low', 'medium', 'high']) + ).toEqual(['default', 'none', 'low', 'medium', 'high']) // Qwen models work well for name-based fallback expect( @@ -1944,7 +2009,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { name: 'qwen-plus' }) ) - ).toEqual(['none', 'low', 'medium', 'high']) + ).toEqual(['default', 'none', 'low', 'medium', 'high']) }) it('should use id result when id matches', () => { @@ -1955,7 +2020,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { name: 'Different Name' }) ) - ).toEqual(['none', 'low', 'medium', 'high']) + ).toEqual(['default', 'none', 'low', 'medium', 'high']) expect( getModelSupportedReasoningEffortOptions( @@ -1964,20 +2029,27 @@ describe('getModelSupportedReasoningEffortOptions', () => { name: 'Some other name' }) ) - ).toEqual(['low', 'medium', 'high']) + ).toEqual(['default', 'low', 'medium', 'high']) }) }) describe('Case sensitivity', () => { it('should handle case insensitive model IDs', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'GPT-5.1' }))).toEqual([ + 'default', 'none', 'low', 'medium', 'high' ]) - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'O3-MINI' }))).toEqual(['low', 'medium', 'high']) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'O3-MINI' }))).toEqual([ + 'default', + 'low', + 'medium', + 'high' + ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'Gemini-2.5-Flash-Latest' }))).toEqual([ + 'default', 'none', 'low', 'medium', diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index a5e47ef3b1..4b0e293f40 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -59,31 +59,31 @@ export const MODEL_SUPPORTED_REASONING_EFFORT = { // 模型类型到支持选项的映射表 export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { - default: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.default] as const, - o: MODEL_SUPPORTED_REASONING_EFFORT.o, - openai_deep_research: MODEL_SUPPORTED_REASONING_EFFORT.openai_deep_research, - gpt5: [...MODEL_SUPPORTED_REASONING_EFFORT.gpt5] as const, - gpt5pro: MODEL_SUPPORTED_REASONING_EFFORT.gpt5pro, - gpt5_codex: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_codex, - gpt5_1: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1, - gpt5_1_codex: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1_codex, - gpt5_2: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_2, - gpt5_1_codex_max: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1_codex_max, - gpt52pro: MODEL_SUPPORTED_REASONING_EFFORT.gpt52pro, - grok: MODEL_SUPPORTED_REASONING_EFFORT.grok, - grok4_fast: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const, - gemini: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const, - gemini_pro: MODEL_SUPPORTED_REASONING_EFFORT.gemini_pro, - gemini3: MODEL_SUPPORTED_REASONING_EFFORT.gemini3, - qwen: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const, - qwen_thinking: MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking, - doubao: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, - doubao_no_auto: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, - doubao_after_251015: MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015, - hunyuan: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, - zhipu: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, - perplexity: MODEL_SUPPORTED_REASONING_EFFORT.perplexity, - deepseek_hybrid: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.deepseek_hybrid] as const + default: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.default] as const, + o: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.o] as const, + openai_deep_research: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.openai_deep_research] as const, + gpt5: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5] as const, + gpt5pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5pro] as const, + gpt5_codex: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5_codex] as const, + gpt5_1: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1] as const, + gpt5_1_codex: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1_codex] as const, + gpt5_2: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5_2] as const, + gpt5_1_codex_max: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1_codex_max] as const, + gpt52pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt52pro] as const, + grok: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.grok] as const, + grok4_fast: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const, + gemini: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const, + gemini_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini_pro] as const, + gemini3: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3] as const, + qwen: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const, + qwen_thinking: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking] as const, + doubao: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, + doubao_no_auto: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, + doubao_after_251015: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015] as const, + hunyuan: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, + zhipu: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, + perplexity: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.perplexity] as const, + deepseek_hybrid: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.deepseek_hybrid] as const } as const const withModelIdAndNameAsId = (model: Model, fn: (model: Model) => T): { idResult: T; nameResult: T } => { @@ -191,20 +191,28 @@ const _getModelSupportedReasoningEffortOptions = (model: Model): ReasoningEffort * - The model is null/undefined * - The model doesn't support reasoning effort or thinking tokens * + * All reasoning models support the 'default' option (always the first element), + * which represents no additional configuration for thinking behavior. + * * @example - * // OpenAI o-series models support low, medium, high + * // OpenAI o-series models support default, low, medium, high * getModelSupportedReasoningEffortOptions({ id: 'o3-mini', ... }) - * // Returns: ['low', 'medium', 'high'] + * // Returns: ['default', 'low', 'medium', 'high'] + * // 'default' = no additional configuration for thinking behavior * * @example - * // GPT-5.1 models support none, low, medium, high + * // GPT-5.1 models support default, none, low, medium, high * getModelSupportedReasoningEffortOptions({ id: 'gpt-5.1', ... }) - * // Returns: ['none', 'low', 'medium', 'high'] + * // Returns: ['default', 'none', 'low', 'medium', 'high'] + * // 'default' = no additional configuration + * // 'none' = explicitly disable reasoning * * @example - * // Gemini Flash models support none, low, medium, high, auto + * // Gemini Flash models support default, none, low, medium, high, auto * getModelSupportedReasoningEffortOptions({ id: 'gemini-2.5-flash-latest', ... }) - * // Returns: ['none', 'low', 'medium', 'high', 'auto'] + * // Returns: ['default', 'none', 'low', 'medium', 'high', 'auto'] + * // 'default' = no additional configuration + * // 'auto' = let the model automatically decide * * @example * // Non-reasoning models return undefined @@ -214,7 +222,7 @@ const _getModelSupportedReasoningEffortOptions = (model: Model): ReasoningEffort * @example * // Name fallback when id doesn't match * getModelSupportedReasoningEffortOptions({ id: 'custom-id', name: 'gpt-5.1', ... }) - * // Returns: ['none', 'low', 'medium', 'high'] + * // Returns: ['default', 'none', 'low', 'medium', 'high'] */ export const getModelSupportedReasoningEffortOptions = ( model: Model | undefined | null diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 2830267088..8e2600a681 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -5,7 +5,7 @@ */ import { loggerService } from '@logger' -import type { AgentType, BuiltinMCPServerName, BuiltinOcrProviderId, ThinkingOption } from '@renderer/types' +import type { AgentType, BuiltinMCPServerName, BuiltinOcrProviderId } from '@renderer/types' import { BuiltinMCPServerNames } from '@renderer/types' import i18n from './index' @@ -310,20 +310,6 @@ export const getHttpMessageLabel = (key: string): string => { return getLabel(httpMessageKeyMap, key) } -const reasoningEffortOptionsKeyMap: Record = { - none: 'assistants.settings.reasoning_effort.off', - minimal: 'assistants.settings.reasoning_effort.minimal', - high: 'assistants.settings.reasoning_effort.high', - low: 'assistants.settings.reasoning_effort.low', - medium: 'assistants.settings.reasoning_effort.medium', - auto: 'assistants.settings.reasoning_effort.default', - xhigh: 'assistants.settings.reasoning_effort.xhigh' -} as const - -export const getReasoningEffortOptionsLabel = (key: string): string => { - return getLabel(reasoningEffortOptionsKeyMap, key) -} - const fileFieldKeyMap = { created_at: 'files.created_at', size: 'files.size', diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 7085f523ad..0085cef491 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -548,14 +548,23 @@ "more": "Assistant Settings", "prompt": "Prompt Settings", "reasoning_effort": { + "auto": "Auto", + "auto_description": "Flexibly determine reasoning effort", "default": "Default", + "default_description": "Depend on the model's default behavior, without any configuration.", "high": "High", + "high_description": "High level reasoning", "label": "Reasoning effort", "low": "Low", + "low_description": "Low level reasoning", "medium": "Medium", + "medium_description": "Medium level reasoning", "minimal": "Minimal", + "minimal_description": "Minimal reasoning", "off": "Off", - "xhigh": "Extra High" + "off_description": "Disable reasoning", + "xhigh": "Extra High", + "xhigh_description": "Extra high level reasoning" }, "regular_phrases": { "add": "Add Phrase", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 3abfe795d5..2ad73a19bf 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -548,14 +548,23 @@ "more": "助手设置", "prompt": "提示词设置", "reasoning_effort": { + "auto": "自动", + "auto_description": "灵活决定推理力度", "default": "默认", + "default_description": "依赖模型默认行为,不作任何配置", "high": "沉思", + "high_description": "高强度推理", "label": "思维链长度", "low": "浮想", + "low_description": "低强度推理", "medium": "斟酌", + "medium_description": "中强度推理", "minimal": "微念", + "minimal_description": "最小程度的思考", "off": "关闭", - "xhigh": "穷究" + "off_description": "禁用推理", + "xhigh": "穷究", + "xhigh_description": "超高强度推理" }, "regular_phrases": { "add": "添加短语", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index cbff7ccd76..67b56e9873 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -548,14 +548,23 @@ "more": "助手設定", "prompt": "提示詞設定", "reasoning_effort": { + "auto": "自動", + "auto_description": "彈性決定推理投入的心力", "default": "預設", + "default_description": "依賴模型的預設行為,無需任何配置。", "high": "盡力思考", + "high_description": "高級推理", "label": "思維鏈長度", "low": "稍微思考", + "low_description": "低階推理", "medium": "正常思考", + "medium_description": "中等程度推理", "minimal": "最少思考", + "minimal_description": "最少推理", "off": "關閉", - "xhigh": "極力思考" + "off_description": "禁用推理", + "xhigh": "極力思考", + "xhigh_description": "超高階推理" }, "regular_phrases": { "add": "新增短語", diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index d96cbf3de6..3fcd849548 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -548,14 +548,23 @@ "more": "Assistenteneinstellungen", "prompt": "Prompt-Einstellungen", "reasoning_effort": { + "auto": "Auto", + "auto_description": "Denkaufwand flexibel bestimmen", "default": "Standard", + "default_description": "Vom Standardverhalten des Modells abhängen, ohne Konfiguration.", "high": "Tiefes Nachdenken", + "high_description": "Ganzheitliches Denken", "label": "Gedankenkettenlänge", "low": "Spontan", + "low_description": "Geringfügige Argumentation", "medium": "Überlegt", + "medium_description": "Denken auf mittlerem Niveau", "minimal": "Minimal", + "minimal_description": "Minimales Denken", "off": "Aus", - "xhigh": "Extra hoch" + "off_description": "Denken deaktivieren", + "xhigh": "Extra hoch", + "xhigh_description": "Extra hohes Denkvermögen" }, "regular_phrases": { "add": "Phrase hinzufügen", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 55bdab9b35..aed40bc2db 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -548,14 +548,23 @@ "more": "Ρυθμίσεις Βοηθού", "prompt": "Ρυθμίσεις προκαλύμματος", "reasoning_effort": { + "auto": "Αυτοκίνητο", + "auto_description": "Ευέλικτος καθορισμός της προσπάθειας συλλογισμού", "default": "Προεπιλογή", + "default_description": "Εξαρτηθείτε από την προεπιλεγμένη συμπεριφορά του μοντέλου, χωρίς καμία διαμόρφωση.", "high": "Μεγάλο", + "high_description": "Υψηλού επιπέδου συλλογισμός", "label": "Μήκος λογισμικού αλυσίδας", "low": "Μικρό", + "low_description": "Χαμηλού επιπέδου συλλογιστική", "medium": "Μεσαίο", + "medium_description": "Αιτιολόγηση μεσαίου επιπέδου", "minimal": "ελάχιστος", + "minimal_description": "Ελάχιστος συλλογισμός", "off": "Απενεργοποίηση", - "xhigh": "Εξαιρετικά Υψηλή" + "off_description": "Απενεργοποίηση λογικής", + "xhigh": "Εξαιρετικά Υψηλή", + "xhigh_description": "Εξαιρετικά υψηλού επιπέδου συλλογισμός" }, "regular_phrases": { "add": "Προσθήκη φράσης", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index b872623104..b0f584b3b5 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -548,14 +548,23 @@ "more": "Configuración del Asistente", "prompt": "Configuración de Palabras Clave", "reasoning_effort": { + "auto": "Automóvil", + "auto_description": "Determinar flexiblemente el esfuerzo de razonamiento", "default": "Por defecto", + "default_description": "Depender del comportamiento predeterminado del modelo, sin ninguna configuración.", "high": "Largo", + "high_description": "Razonamiento de alto nivel", "label": "Longitud de Cadena de Razonamiento", "low": "Corto", + "low_description": "Razonamiento de bajo nivel", "medium": "Medio", + "medium_description": "Razonamiento de nivel medio", "minimal": "minimal", + "minimal_description": "Razonamiento mínimo", "off": "Apagado", - "xhigh": "Extra Alta" + "off_description": "Deshabilitar razonamiento", + "xhigh": "Extra Alta", + "xhigh_description": "Razonamiento de extra alto nivel" }, "regular_phrases": { "add": "Agregar frase", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 6b64e0041d..eea3ddb1c1 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -548,14 +548,23 @@ "more": "Paramètres de l'assistant", "prompt": "Paramètres de l'invite", "reasoning_effort": { + "auto": "Auto", + "auto_description": "Déterminer de manière flexible l'effort de raisonnement", "default": "Par défaut", + "default_description": "Dépendre du comportement par défaut du modèle, sans aucune configuration.", "high": "Long", + "high_description": "Raisonnement de haut niveau", "label": "Longueur de la chaîne de raisonnement", "low": "Court", + "low_description": "Raisonnement de bas niveau", "medium": "Moyen", + "medium_description": "Raisonnement de niveau moyen", "minimal": "minimal", + "minimal_description": "Réflexion minimale", "off": "Off", - "xhigh": "Très élevée" + "off_description": "Désactiver le raisonnement", + "xhigh": "Très élevée", + "xhigh_description": "Raisonnement de très haut niveau" }, "regular_phrases": { "add": "Добавить фразу", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 021ecf3eb9..ec72e3a3ab 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -548,14 +548,23 @@ "more": "アシスタント設定", "prompt": "プロンプト設定", "reasoning_effort": { + "auto": "自動", + "auto_description": "推論にかける労力を柔軟に調整する", "default": "デフォルト", + "default_description": "設定なしで、モデルの既定の動作に依存する。", "high": "最大限の思考", + "high_description": "高度な推論", "label": "思考連鎖の長さ", "low": "少しの思考", + "low_description": "低レベル推論", "medium": "普通の思考", + "medium_description": "中レベル推論", "minimal": "最小限の思考", + "minimal_description": "最小限の推論", "off": "オフ", - "xhigh": "超高" + "off_description": "推論を無効にする", + "xhigh": "超高", + "xhigh_description": "超高度な推論" }, "regular_phrases": { "add": "プロンプトを追加", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index f02caecb3f..5c3d2d1915 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -548,14 +548,23 @@ "more": "Configurações do Assistente", "prompt": "Configurações de Prompt", "reasoning_effort": { + "auto": "Automóvel", + "auto_description": "Determinar flexivelmente o esforço de raciocínio", "default": "Padrão", + "default_description": "Depender do comportamento padrão do modelo, sem qualquer configuração.", "high": "Longo", + "high_description": "Raciocínio de alto nível", "label": "Comprimento da Cadeia de Raciocínio", "low": "Curto", + "low_description": "Raciocínio de baixo nível", "medium": "Médio", + "medium_description": "Raciocínio de nível médio", "minimal": "mínimo", + "minimal_description": "Raciocínio mínimo", "off": "Desligado", - "xhigh": "Extra Alta" + "off_description": "Desabilitar raciocínio", + "xhigh": "Extra Alta", + "xhigh_description": "Raciocínio de altíssimo nível" }, "regular_phrases": { "add": "Adicionar Frase", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 9a3c0bfe6d..cba8a1e5eb 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -548,14 +548,23 @@ "more": "Настройки ассистента", "prompt": "Настройки промптов", "reasoning_effort": { + "auto": "Авто", + "auto_description": "Гибко определяйте усилие на рассуждение", "default": "По умолчанию", + "default_description": "Полагаться на поведение модели по умолчанию, без какой-либо конфигурации.", "high": "Стараюсь думать", + "high_description": "Высокоуровневое рассуждение", "label": "Настройки размышлений", "low": "Меньше думать", + "low_description": "Низкоуровневое рассуждение", "medium": "Среднее", + "medium_description": "Средний уровень рассуждения", "minimal": "минимальный", + "minimal_description": "Минимальное рассуждение", "off": "Выключить", - "xhigh": "Сверхвысокое" + "off_description": "Отключить рассуждение", + "xhigh": "Сверхвысокое", + "xhigh_description": "Высочайший уровень рассуждений" }, "regular_phrases": { "add": "Добавить подсказку", diff --git a/src/renderer/src/pages/home/Inputbar/tools/components/ThinkingButton.tsx b/src/renderer/src/pages/home/Inputbar/tools/components/ThinkingButton.tsx index bcc395c53b..10b4656bc0 100644 --- a/src/renderer/src/pages/home/Inputbar/tools/components/ThinkingButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/tools/components/ThinkingButton.tsx @@ -6,7 +6,8 @@ import { MdiLightbulbOn30, MdiLightbulbOn50, MdiLightbulbOn80, - MdiLightbulbOn90 + MdiLightbulbOn90, + MdiLightbulbQuestion } from '@renderer/components/Icons/SVGIcon' import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' import { @@ -18,7 +19,6 @@ import { MODEL_SUPPORTED_OPTIONS } from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' -import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label' import type { ToolQuickPanelApi } from '@renderer/pages/home/Inputbar/types' import type { Model, ThinkingOption } from '@renderer/types' import { Tooltip } from 'antd' @@ -88,19 +88,48 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle [updateAssistantSettings, assistant.enableWebSearch, model, t] ) + const reasoningEffortOptionLabelMap = { + default: t('assistants.settings.reasoning_effort.default'), + none: t('assistants.settings.reasoning_effort.off'), + minimal: t('assistants.settings.reasoning_effort.minimal'), + high: t('assistants.settings.reasoning_effort.high'), + low: t('assistants.settings.reasoning_effort.low'), + medium: t('assistants.settings.reasoning_effort.medium'), + auto: t('assistants.settings.reasoning_effort.auto'), + xhigh: t('assistants.settings.reasoning_effort.xhigh') + } as const satisfies Record + + const reasoningEffortDescriptionMap = { + default: t('assistants.settings.reasoning_effort.default_description'), + none: t('assistants.settings.reasoning_effort.off_description'), + minimal: t('assistants.settings.reasoning_effort.minimal_description'), + low: t('assistants.settings.reasoning_effort.low_description'), + medium: t('assistants.settings.reasoning_effort.medium_description'), + high: t('assistants.settings.reasoning_effort.high_description'), + xhigh: t('assistants.settings.reasoning_effort.xhigh_description'), + auto: t('assistants.settings.reasoning_effort.auto_description') + } as const satisfies Record + const panelItems = useMemo(() => { // 使用表中定义的选项创建UI选项 return supportedOptions.map((option) => ({ level: option, - label: getReasoningEffortOptionsLabel(option), - description: '', + label: reasoningEffortOptionLabelMap[option], + description: reasoningEffortDescriptionMap[option], icon: ThinkingIcon({ option }), isSelected: currentReasoningEffort === option, action: () => onThinkingChange(option) })) - }, [currentReasoningEffort, supportedOptions, onThinkingChange]) + }, [ + supportedOptions, + reasoningEffortOptionLabelMap, + reasoningEffortDescriptionMap, + currentReasoningEffort, + onThinkingChange + ]) - const isThinkingEnabled = currentReasoningEffort !== undefined && currentReasoningEffort !== 'none' + const isThinkingEnabled = + currentReasoningEffort !== undefined && currentReasoningEffort !== 'none' && currentReasoningEffort !== 'default' const disableThinking = useCallback(() => { onThinkingChange('none') @@ -197,8 +226,9 @@ const ThinkingIcon = (props: { option?: ThinkingOption; isFixedReasoning?: boole case 'none': IconComponent = MdiLightbulbOffOutline break + case 'default': default: - IconComponent = MdiLightbulbOffOutline + IconComponent = MdiLightbulbQuestion break } } diff --git a/src/renderer/src/services/AssistantService.ts b/src/renderer/src/services/AssistantService.ts index 233b3c19ca..91a95d691a 100644 --- a/src/renderer/src/services/AssistantService.ts +++ b/src/renderer/src/services/AssistantService.ts @@ -38,7 +38,8 @@ export const DEFAULT_ASSISTANT_SETTINGS = { enableTopP: false, // It would gracefully fallback to prompt if not supported by model. toolUseMode: 'function', - customParameters: [] + customParameters: [], + reasoning_effort: 'default' } as const satisfies AssistantSettings export function getDefaultAssistant(): Assistant { @@ -186,7 +187,7 @@ export const getAssistantSettings = (assistant: Assistant): AssistantSettings => streamOutput: assistant?.settings?.streamOutput ?? true, toolUseMode: assistant?.settings?.toolUseMode ?? 'function', defaultModel: assistant?.defaultModel ?? undefined, - reasoning_effort: assistant?.settings?.reasoning_effort ?? undefined, + reasoning_effort: assistant?.settings?.reasoning_effort ?? 'default', customParameters: assistant?.settings?.customParameters ?? [] } } diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index b29b4d1086..51d70ef6de 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -67,7 +67,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 186, + version: 187, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 03510133e1..f085db2302 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -3038,6 +3038,20 @@ const migrateConfig = { logger.error('migrate 186 error', error as Error) return state } + }, + '187': (state: RootState) => { + try { + state.assistants.assistants.forEach((assistant) => { + if (assistant.settings && assistant.settings.reasoning_effort === undefined) { + assistant.settings.reasoning_effort = 'default' + } + }) + logger.info('migrate 187 success') + return state + } catch (error) { + logger.error('migrate 187 error', error as Error) + return state + } } } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 197d217793..c9dc647acd 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -108,7 +108,7 @@ const ThinkModelTypes = [ 'deepseek_hybrid' ] as const -export type ReasoningEffortOption = NonNullable | 'auto' +export type ReasoningEffortOption = NonNullable | 'auto' | 'default' export type ThinkingOption = ReasoningEffortOption export type ThinkingModelType = (typeof ThinkModelTypes)[number] export type ThinkingOptionConfig = Record @@ -120,6 +120,8 @@ export function isThinkModelType(type: string): type is ThinkingModelType { } export const EFFORT_RATIO: EffortRatio = { + // 'default' is not expected to be used. + default: 0, none: 0.01, minimal: 0.05, low: 0.05, @@ -140,12 +142,11 @@ export type AssistantSettings = { streamOutput: boolean defaultModel?: Model customParameters?: AssistantSettingCustomParameters[] - reasoning_effort?: ReasoningEffortOption - /** 保留上一次使用思考模型时的 reasoning effort, 在从非思考模型切换到思考模型时恢复. - * - * TODO: 目前 reasoning_effort === undefined 有两个语义,有的场景是显式关闭思考,有的场景是不传参。 - * 未来应该重构思考控制,将启用/关闭思考和思考选项分离,这样就不用依赖 cache 了。 - * + reasoning_effort: ReasoningEffortOption + /** + * Preserve the effective reasoning effort (not 'default') from the last use of a thinking model which supports thinking control, + * and restore it when switching back from a non-thinking or fixed reasoning model. + * FIXME: It should be managed by external cache service instead of being stored in the assistant */ reasoning_effort_cache?: ReasoningEffortOption qwenThinkMode?: boolean From 0f1b3afa72819cf4ce5bb2711ef3c5dd512cb7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?George=C2=B7Dong?= <98630204+GeorgeDong32@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:30:23 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=81=AB?= =?UTF-8?q?=E5=B1=B1=E5=BC=95=E6=93=8E=20Doubao-Seed-1.8=20=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=94=AF=E6=8C=81=20(#11972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增模型定义: doubao-seed-1-8-251215 - 支持思考模式: reasoning_effort (minimal/low/medium/high) - 支持 Function Call - 支持图像理解 (Vision) - 更新正则表达式支持 seed-1.8 变体 - 添加完整测试覆盖 修改文件: - src/renderer/src/config/models/default.ts - src/renderer/src/config/models/reasoning.ts - src/renderer/src/aiCore/utils/reasoning.ts - src/renderer/src/config/models/vision.ts - src/renderer/src/config/models/tooluse.ts - src/renderer/src/config/models/__tests__/reasoning.test.ts --- src/renderer/src/aiCore/utils/reasoning.ts | 3 ++- .../src/config/models/__tests__/reasoning.test.ts | 6 ++++++ src/renderer/src/config/models/default.ts | 6 ++++++ src/renderer/src/config/models/reasoning.ts | 9 +++++++-- src/renderer/src/config/models/tooluse.ts | 2 +- src/renderer/src/config/models/vision.ts | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index 10afbbaf5f..ca1d03966a 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -10,6 +10,7 @@ import { GEMINI_FLASH_MODEL_REGEX, getModelSupportedReasoningEffortOptions, isDeepSeekHybridInferenceModel, + isDoubaoSeed18Model, isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel, isGemini3ThinkingTokenModel, @@ -389,7 +390,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // Use thinking, doubao, zhipu, etc. if (isSupportedThinkingTokenDoubaoModel(model)) { - if (isDoubaoSeedAfter251015(model)) { + if (isDoubaoSeedAfter251015(model) || isDoubaoSeed18Model(model)) { return { reasoningEffort } } if (reasoningEffort === 'high') { diff --git a/src/renderer/src/config/models/__tests__/reasoning.test.ts b/src/renderer/src/config/models/__tests__/reasoning.test.ts index ded3f9cc77..ba7af37f17 100644 --- a/src/renderer/src/config/models/__tests__/reasoning.test.ts +++ b/src/renderer/src/config/models/__tests__/reasoning.test.ts @@ -733,6 +733,11 @@ describe('getThinkModelType - Comprehensive Coverage', () => { expect(getThinkModelType(createModel({ id: 'doubao-seed-1-6-lite-251015' }))).toBe('doubao_after_251015') }) + it('should return doubao_after_251015 for Doubao-Seed-1.8 models', () => { + expect(getThinkModelType(createModel({ id: 'doubao-seed-1-8-251215' }))).toBe('doubao_after_251015') + expect(getThinkModelType(createModel({ id: 'doubao-seed-1.8' }))).toBe('doubao_after_251015') + }) + it('should return doubao_no_auto for other Doubao thinking models', () => { expect(getThinkModelType(createModel({ id: 'doubao-1.5-thinking-vision-pro' }))).toBe('doubao_no_auto') }) @@ -863,6 +868,7 @@ describe('getThinkModelType - Comprehensive Coverage', () => { // auto > after_251015 > no_auto expect(getThinkModelType(createModel({ id: 'doubao-seed-1.6' }))).toBe('doubao') expect(getThinkModelType(createModel({ id: 'doubao-seed-1-6-251015' }))).toBe('doubao_after_251015') + expect(getThinkModelType(createModel({ id: 'doubao-seed-1-8-251215' }))).toBe('doubao_after_251015') expect(getThinkModelType(createModel({ id: 'doubao-1.5-thinking-vision-pro' }))).toBe('doubao_no_auto') }) diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index d7b0e885d0..66368d35fe 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -746,6 +746,12 @@ export const SYSTEM_MODELS: Record = } ], doubao: [ + { + id: 'doubao-seed-1-8-251215', + provider: 'doubao', + name: 'Doubao-Seed-1.8', + group: 'Doubao-Seed-1.8' + }, { id: 'doubao-1-5-vision-pro-32k-250115', provider: 'doubao', diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 4b0e293f40..14174d1629 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -146,7 +146,7 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { } else if (isSupportedThinkingTokenDoubaoModel(model)) { if (isDoubaoThinkingAutoModel(model)) { thinkingModelType = 'doubao' - } else if (isDoubaoSeedAfter251015(model)) { + } else if (isDoubaoSeedAfter251015(model) || isDoubaoSeed18Model(model)) { thinkingModelType = 'doubao_after_251015' } else { thinkingModelType = 'doubao_no_auto' @@ -457,7 +457,7 @@ export function isQwenAlwaysThinkModel(model?: Model): boolean { // Doubao 支持思考模式的模型正则 export const DOUBAO_THINKING_MODEL_REGEX = - /doubao-(?:1[.-]5-thinking-vision-pro|1[.-]5-thinking-pro-m|seed-1[.-]6(?:-flash)?(?!-(?:thinking)(?:-|$))|seed-code(?:-preview)?(?:-\d+)?)(?:-[\w-]+)*/i + /doubao-(?:1[.-]5-thinking-vision-pro|1[.-]5-thinking-pro-m|seed-1[.-][68](?:-flash)?(?!-(?:thinking)(?:-|$))|seed-code(?:-preview)?(?:-\d+)?)(?:-[\w-]+)*/i // 支持 auto 的 Doubao 模型 doubao-seed-1.6-xxx doubao-seed-1-6-xxx doubao-1-5-thinking-pro-m-xxx // Auto thinking is no longer supported after version 251015, see https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-seed-1-6 @@ -475,6 +475,11 @@ export function isDoubaoSeedAfter251015(model: Model): boolean { return result } +export function isDoubaoSeed18Model(model: Model): boolean { + const pattern = /doubao-seed-1[.-]8(?:-[\w-]+)?/i + return pattern.test(model.id) || pattern.test(model.name) +} + export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean { if (!model) { return false diff --git a/src/renderer/src/config/models/tooluse.ts b/src/renderer/src/config/models/tooluse.ts index 50890aaf8d..66e2dcc209 100644 --- a/src/renderer/src/config/models/tooluse.ts +++ b/src/renderer/src/config/models/tooluse.ts @@ -25,7 +25,7 @@ export const FUNCTION_CALLING_MODELS = [ 'learnlm(?:-[\\w-]+)?', 'gemini(?:-[\\w-]+)?', // 提前排除了gemini的嵌入模型 'grok-3(?:-[\\w-]+)?', - 'doubao-seed-1[.-]6(?:-[\\w-]+)?', + 'doubao-seed-1[.-][68](?:-[\\w-]+)?', 'doubao-seed-code(?:-[\\w-]+)?', 'kimi-k2(?:-[\\w-]+)?', 'ling-\\w+(?:-[\\w-]+)?', diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index 183ec99433..fe4bc9912c 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -45,7 +45,7 @@ const visionAllowedModels = [ 'deepseek-vl(?:[\\w-]+)?', 'kimi-latest', 'gemma-3(?:-[\\w-]+)', - 'doubao-seed-1[.-]6(?:-[\\w-]+)?', + 'doubao-seed-1[.-][68](?:-[\\w-]+)?', 'doubao-seed-code(?:-[\\w-]+)?', 'kimi-thinking-preview', `gemma3(?:[-:\\w]+)?`, From c04529a23c0cd395a214591e6039f091ff7000f1 Mon Sep 17 00:00:00 2001 From: SuYao Date: Thu, 18 Dec 2025 13:30:41 +0800 Subject: [PATCH 3/8] refactor: improve budget calculation logic (#11973) * refactor: improve budget calculation logic * Update src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * [WIP] Address feedback on budget calculation logic refactor (#11974) * Initial plan * fix: revert budget calculation to linear interpolation formula Reverted the budget calculation in getAnthropicThinkingBudget from `tokenLimit.max * effortRatio` back to the original linear interpolation formula `(tokenLimit.max - tokenLimit.min) * effortRatio + tokenLimit.min`. The new formula was causing lower budgets for all effort ratios (e.g., LOW effort changed from 2609 to 1638 tokens, a 37% reduction). The linear interpolation formula ensures budgets range from min (at effortRatio=0) to max (at effortRatio=1), matching the behavior in other parts of the codebase (lines 221, 597). Updated tests to reflect the correct expected values with the linear interpolation formula. Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * fix(test): reasoning * fix: test --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- .../aiCore/utils/__tests__/reasoning.test.ts | 95 ++++++++++++++++++- src/renderer/src/aiCore/utils/reasoning.ts | 18 ++-- 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts b/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts index e5561f6fcf..df7d69d0c2 100644 --- a/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts +++ b/src/renderer/src/aiCore/utils/__tests__/reasoning.test.ts @@ -11,6 +11,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { getAnthropicReasoningParams, + getAnthropicThinkingBudget, getBedrockReasoningParams, getCustomParameters, getGeminiReasoningParams, @@ -89,7 +90,8 @@ vi.mock('@renderer/config/models', async (importOriginal) => { isQwenAlwaysThinkModel: vi.fn(() => false), isSupportedThinkingTokenHunyuanModel: vi.fn(() => false), isSupportedThinkingTokenModel: vi.fn(() => false), - isGPT51SeriesModel: vi.fn(() => false) + isGPT51SeriesModel: vi.fn(() => false), + findTokenLimit: vi.fn(actual.findTokenLimit) } }) @@ -649,7 +651,7 @@ describe('reasoning utils', () => { expect(result).toEqual({ thinking: { type: 'enabled', - budgetTokens: 2048 + budgetTokens: 4096 } }) }) @@ -729,7 +731,7 @@ describe('reasoning utils', () => { const result = getGeminiReasoningParams(assistant, model) expect(result).toEqual({ thinkingConfig: { - thinkingBudget: 16448, + thinkingBudget: expect.any(Number), includeThoughts: true } }) @@ -893,7 +895,7 @@ describe('reasoning utils', () => { expect(result).toEqual({ reasoningConfig: { type: 'enabled', - budgetTokens: 2048 + budgetTokens: 4096 } }) }) @@ -994,4 +996,89 @@ describe('reasoning utils', () => { }) }) }) + + describe('getAnthropicThinkingBudget', () => { + it('should return undefined when reasoningEffort is undefined', async () => { + const result = getAnthropicThinkingBudget(4096, undefined, 'claude-3-7-sonnet') + expect(result).toBeUndefined() + }) + + it('should return undefined when reasoningEffort is none', async () => { + const result = getAnthropicThinkingBudget(4096, 'none', 'claude-3-7-sonnet') + expect(result).toBeUndefined() + }) + + it('should return undefined when tokenLimit is not found', async () => { + const { findTokenLimit } = await import('@renderer/config/models') + vi.mocked(findTokenLimit).mockReturnValue(undefined) + + const result = getAnthropicThinkingBudget(4096, 'medium', 'unknown-model') + expect(result).toBeUndefined() + }) + + it('should calculate budget correctly when maxTokens is provided', async () => { + const { findTokenLimit } = await import('@renderer/config/models') + vi.mocked(findTokenLimit).mockReturnValue({ min: 1024, max: 32768 }) + + const result = getAnthropicThinkingBudget(4096, 'medium', 'claude-3-7-sonnet') + // EFFORT_RATIO['medium'] = 0.5 + // budget = Math.floor((32768 - 1024) * 0.5 + 1024) + // = Math.floor(31744 * 0.5 + 1024) = Math.floor(15872 + 1024) = 16896 + // budgetTokens = Math.min(16896, 4096) = 4096 + // result = Math.max(1024, 4096) = 4096 + expect(result).toBe(4096) + }) + + it('should use tokenLimit.max when maxTokens is undefined', async () => { + const { findTokenLimit } = await import('@renderer/config/models') + vi.mocked(findTokenLimit).mockReturnValue({ min: 1024, max: 32768 }) + + const result = getAnthropicThinkingBudget(undefined, 'medium', 'claude-3-7-sonnet') + // When maxTokens is undefined, budget is not constrained by maxTokens + // EFFORT_RATIO['medium'] = 0.5 + // budget = Math.floor((32768 - 1024) * 0.5 + 1024) + // = Math.floor(31744 * 0.5 + 1024) = Math.floor(15872 + 1024) = 16896 + // result = Math.max(1024, 16896) = 16896 + expect(result).toBe(16896) + }) + + it('should enforce minimum budget of 1024', async () => { + const { findTokenLimit } = await import('@renderer/config/models') + vi.mocked(findTokenLimit).mockReturnValue({ min: 100, max: 1000 }) + + const result = getAnthropicThinkingBudget(500, 'low', 'claude-3-7-sonnet') + // EFFORT_RATIO['low'] = 0.05 + // budget = Math.floor((1000 - 100) * 0.05 + 100) + // = Math.floor(900 * 0.05 + 100) = Math.floor(45 + 100) = 145 + // budgetTokens = Math.min(145, 500) = 145 + // result = Math.max(1024, 145) = 1024 + expect(result).toBe(1024) + }) + + it('should respect effort ratio for high reasoning effort', async () => { + const { findTokenLimit } = await import('@renderer/config/models') + vi.mocked(findTokenLimit).mockReturnValue({ min: 1024, max: 32768 }) + + const result = getAnthropicThinkingBudget(8192, 'high', 'claude-3-7-sonnet') + // EFFORT_RATIO['high'] = 0.8 + // budget = Math.floor((32768 - 1024) * 0.8 + 1024) + // = Math.floor(31744 * 0.8 + 1024) = Math.floor(25395.2 + 1024) = 26419 + // budgetTokens = Math.min(26419, 8192) = 8192 + // result = Math.max(1024, 8192) = 8192 + expect(result).toBe(8192) + }) + + it('should use full token limit when maxTokens is undefined and reasoning effort is high', async () => { + const { findTokenLimit } = await import('@renderer/config/models') + vi.mocked(findTokenLimit).mockReturnValue({ min: 1024, max: 32768 }) + + const result = getAnthropicThinkingBudget(undefined, 'high', 'claude-3-7-sonnet') + // When maxTokens is undefined, budget is not constrained by maxTokens + // EFFORT_RATIO['high'] = 0.8 + // budget = Math.floor((32768 - 1024) * 0.8 + 1024) + // = Math.floor(31744 * 0.8 + 1024) = Math.floor(25395.2 + 1024) = 26419 + // result = Math.max(1024, 26419) = 26419 + expect(result).toBe(26419) + }) + }) }) diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index ca1d03966a..9320fb8e50 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -480,16 +480,14 @@ export function getAnthropicThinkingBudget( return undefined } - const budgetTokens = Math.max( - 1024, - Math.floor( - Math.min( - (tokenLimit.max - tokenLimit.min) * effortRatio + tokenLimit.min, - (maxTokens || DEFAULT_MAX_TOKENS) * effortRatio - ) - ) - ) - return budgetTokens + const budget = Math.floor((tokenLimit.max - tokenLimit.min) * effortRatio + tokenLimit.min) + + let budgetTokens = budget + if (maxTokens !== undefined) { + budgetTokens = Math.min(budget, maxTokens) + } + + return Math.max(1024, budgetTokens) } /** From 6309cc179d465d27330d5523e5eddeab4a3462e4 Mon Sep 17 00:00:00 2001 From: LiuVaayne <10231735+vaayne@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:34:06 +0800 Subject: [PATCH 4/8] feat(mcp): add Nowledge Mem builtin MCP server (#11875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat(mcp): add Nowledge Mem builtin MCP server Add @cherry/nowLedgeMem as a new builtin MCP server that connects to local Nowledge Mem service via HTTP at 127.0.0.1:14242/mcp. - Add nowLedgeMem to BuiltinMCPServerNames type definitions - Add HTTP transport handling in MCPService with APP header - Add server config to builtinMCPServers array - Add i18n translations (en-us, zh-cn, zh-tw) * Fix Nowledge Mem server name typos across codebase * 🌐 i18n: add missing translations for Nowledge Mem and Git Bash settings Translate [to be translated] markers across 8 locale files: - zh-tw, de-de, fr-fr, es-es, pt-pt, ru-ru: nowledgeMem description - fr-fr, es-es, pt-pt, ru-ru, el-gr, ja-jp: xhigh reasoning chain option - el-gr, ja-jp: Git Bash configuration strings * 🐛 fix: address PR review comments for Nowledge Mem MCP - Fix log message typo: use server.name instead of hardcoded "NowLedgeMem" - Rename i18n key from "nowledgeMem" to "nowledge_mem" for consistency - Update descriptions to warn about external dependency requirement --- src/main/services/MCPService.ts | 20 ++++++++++++++++++++ src/renderer/src/i18n/label.ts | 3 ++- src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + src/renderer/src/i18n/translate/de-de.json | 1 + src/renderer/src/i18n/translate/el-gr.json | 1 + src/renderer/src/i18n/translate/es-es.json | 1 + src/renderer/src/i18n/translate/fr-fr.json | 1 + src/renderer/src/i18n/translate/ja-jp.json | 1 + src/renderer/src/i18n/translate/pt-pt.json | 1 + src/renderer/src/i18n/translate/ru-ru.json | 1 + src/renderer/src/store/mcp.ts | 10 ++++++++++ src/renderer/src/types/index.ts | 3 ++- 14 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/main/services/MCPService.ts b/src/main/services/MCPService.ts index cc6bbaa366..ebdc2247fc 100644 --- a/src/main/services/MCPService.ts +++ b/src/main/services/MCPService.ts @@ -249,6 +249,26 @@ class McpService { StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport > => { // Create appropriate transport based on configuration + + // Special case for nowledgeMem - uses HTTP transport instead of in-memory + if (isBuiltinMCPServer(server) && server.name === BuiltinMCPServerNames.nowledgeMem) { + const nowledgeMemUrl = 'http://127.0.0.1:14242/mcp' + const options: StreamableHTTPClientTransportOptions = { + fetch: async (url, init) => { + return net.fetch(typeof url === 'string' ? url : url.toString(), init) + }, + requestInit: { + headers: { + ...defaultAppHeaders(), + APP: 'Cherry Studio' + } + }, + authProvider + } + getServerLogger(server).debug(`Using StreamableHTTPClientTransport for ${server.name}`) + return new StreamableHTTPClientTransport(new URL(nowledgeMemUrl), options) + } + if (isBuiltinMCPServer(server) && server.name !== BuiltinMCPServerNames.mcpAutoInstall) { getServerLogger(server).debug(`Using in-memory transport`) const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair() diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 8e2600a681..ce36282b22 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -330,7 +330,8 @@ const builtInMcpDescriptionKeyMap: Record = { [BuiltinMCPServerNames.difyKnowledge]: 'settings.mcp.builtinServersDescriptions.dify_knowledge', [BuiltinMCPServerNames.python]: 'settings.mcp.builtinServersDescriptions.python', [BuiltinMCPServerNames.didiMCP]: 'settings.mcp.builtinServersDescriptions.didi_mcp', - [BuiltinMCPServerNames.browser]: 'settings.mcp.builtinServersDescriptions.browser' + [BuiltinMCPServerNames.browser]: 'settings.mcp.builtinServersDescriptions.browser', + [BuiltinMCPServerNames.nowledgeMem]: 'settings.mcp.builtinServersDescriptions.nowledge_mem' } as const export const getBuiltInMcpServerDescriptionLabel = (key: string): string => { diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 0085cef491..08c2827200 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "Automatically install MCP service (beta)", "memory": "Persistent memory implementation based on a local knowledge graph. This enables the model to remember user-related information across different conversations. Requires configuring the MEMORY_FILE_PATH environment variable.", "no": "No description", + "nowledge_mem": "Requires Nowledge Mem app running locally. Keeps AI chats, tools, notes, agents, and files in private memory on your computer. Download from https://mem.nowledge.co/", "python": "Execute Python code in a secure sandbox environment. Run Python with Pyodide, supporting most standard libraries and scientific computing packages", "sequentialthinking": "A MCP server implementation that provides tools for dynamic and reflective problem solving through structured thinking processes" }, diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 2ad73a19bf..fa73a46499 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "自动安装 MCP 服务(测试版)", "memory": "基于本地知识图谱的持久性记忆基础实现。这使得模型能够在不同对话间记住用户的相关信息。需要配置 MEMORY_FILE_PATH 环境变量。", "no": "无描述", + "nowledge_mem": "需要本地运行 Nowledge Mem 应用。将 AI 对话、工具、笔记、智能体和文件保存在本地计算机的私有记忆中。请从 https://mem.nowledge.co/ 下载", "python": "在安全的沙盒环境中执行 Python 代码。使用 Pyodide 运行 Python,支持大多数标准库和科学计算包", "sequentialthinking": "一个 MCP 服务器实现,提供了通过结构化思维过程进行动态和反思性问题解决的工具" }, diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 67b56e9873..e8128f33a9 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "自動安裝 MCP 服務(測試版)", "memory": "基於本機知識圖譜的持久性記憶基礎實做。這使得模型能夠在不同對話間記住使用者的相關資訊。需要設定 MEMORY_FILE_PATH 環境變數。", "no": "無描述", + "nowledge_mem": "需要本機執行 Nowledge Mem 應用程式。將 AI 對話、工具、筆記、代理和檔案保存在電腦上的私人記憶體中。請從 https://mem.nowledge.co/ 下載", "python": "在安全的沙盒環境中執行 Python 程式碼。使用 Pyodide 執行 Python,支援大多數標準函式庫和科學計算套件", "sequentialthinking": "一個 MCP 伺服器實做,提供了透過結構化思維過程進行動態和反思性問題解決的工具" }, diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 3fcd849548..3d32436540 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "MCP-Service automatisch installieren (Beta-Version)", "memory": "MCP-Server mit persistenter Erinnerungsbasis auf lokalem Wissensgraphen, der Informationen über verschiedene Dialoge hinweg speichert. MEMORY_FILE_PATH-Umgebungsvariable muss konfiguriert werden", "no": "Keine Beschreibung", + "nowledge_mem": "Erfordert lokal laufende Nowledge Mem App. Speichert KI-Chats, Tools, Notizen, Agenten und Dateien in einem privaten Speicher auf Ihrem Computer. Download unter https://mem.nowledge.co/", "python": "Python-Code in einem sicheren Sandbox-Umgebung ausführen. Verwendung von Pyodide für Python, Unterstützung für die meisten Standardbibliotheken und wissenschaftliche Pakete", "sequentialthinking": "MCP-Server-Implementierung mit strukturiertem Denkprozess, der dynamische und reflektierende Problemlösungen ermöglicht" }, diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index aed40bc2db..2f9ef72b2f 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "Αυτόματη εγκατάσταση υπηρεσίας MCP (προβολή)", "memory": "Βασική υλοποίηση μόνιμης μνήμης με βάση τοπικό γράφημα γνώσης. Αυτό επιτρέπει στο μοντέλο να θυμάται πληροφορίες σχετικές με τον χρήστη ανάμεσα σε διαφορετικές συνομιλίες. Απαιτείται η ρύθμιση της μεταβλητής περιβάλλοντος MEMORY_FILE_PATH.", "no": "Χωρίς περιγραφή", + "nowledge_mem": "[to be translated]:Requires Nowledge Mem app running locally. Keeps AI chats, tools, notes, agents, and files in private memory on your computer. Download from https://mem.nowledge.co/", "python": "Εκτελέστε κώδικα Python σε ένα ασφαλές περιβάλλον sandbox. Χρησιμοποιήστε το Pyodide για να εκτελέσετε Python, υποστηρίζοντας την πλειονότητα των βιβλιοθηκών της τυπικής βιβλιοθήκης και των πακέτων επιστημονικού υπολογισμού", "sequentialthinking": "ένας εξυπηρετητής MCP που υλοποιείται, παρέχοντας εργαλεία για δυναμική και αναστοχαστική επίλυση προβλημάτων μέσω δομημένων διαδικασιών σκέψης" }, diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index b0f584b3b5..adebbfa31b 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "Instalación automática del servicio MCP (versión beta)", "memory": "Implementación básica de memoria persistente basada en un grafo de conocimiento local. Esto permite que el modelo recuerde información relevante del usuario entre diferentes conversaciones. Es necesario configurar la variable de entorno MEMORY_FILE_PATH.", "no": "sin descripción", + "nowledge_mem": "[to be translated]:Requires Nowledge Mem app running locally. Keeps AI chats, tools, notes, agents, and files in private memory on your computer. Download from https://mem.nowledge.co/", "python": "Ejecuta código Python en un entorno sandbox seguro. Usa Pyodide para ejecutar Python, compatible con la mayoría de las bibliotecas estándar y paquetes de cálculo científico.", "sequentialthinking": "Una implementación de servidor MCP que proporciona herramientas para la resolución dinámica y reflexiva de problemas mediante un proceso de pensamiento estructurado" }, diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index eea3ddb1c1..7b24f57672 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "Installation automatique du service MCP (version bêta)", "memory": "Implémentation de base de mémoire persistante basée sur un graphe de connaissances local. Cela permet au modèle de se souvenir des informations relatives à l'utilisateur entre différentes conversations. Nécessite la configuration de la variable d'environnement MEMORY_FILE_PATH.", "no": "sans description", + "nowledge_mem": "[to be translated]:Requires Nowledge Mem app running locally. Keeps AI chats, tools, notes, agents, and files in private memory on your computer. Download from https://mem.nowledge.co/", "python": "Exécutez du code Python dans un environnement bac à sable sécurisé. Utilisez Pyodide pour exécuter Python, prenant en charge la plupart des bibliothèques standard et des packages de calcul scientifique.", "sequentialthinking": "Un serveur MCP qui fournit des outils permettant une résolution dynamique et réflexive des problèmes à travers un processus de pensée structuré" }, diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index ec72e3a3ab..e3b9fc77e5 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "MCPサービスの自動インストール(ベータ版)", "memory": "ローカルのナレッジグラフに基づく永続的なメモリの基本的な実装です。これにより、モデルは異なる会話間でユーザーの関連情報を記憶できるようになります。MEMORY_FILE_PATH 環境変数の設定が必要です。", "no": "説明なし", + "nowledge_mem": "Nowledge Mem アプリをローカルで実行する必要があります。AI チャット、ツール、ノート、エージェント、ファイルをコンピューター上のプライベートメモリに保存します。https://mem.nowledge.co/ からダウンロードしてください", "python": "安全なサンドボックス環境でPythonコードを実行します。Pyodideを使用してPythonを実行し、ほとんどの標準ライブラリと科学計算パッケージをサポートしています。", "sequentialthinking": "構造化された思考プロセスを通じて動的かつ反省的な問題解決を行うためのツールを提供するMCPサーバーの実装" }, diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 5c3d2d1915..10b5a12e16 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "Instalação automática do serviço MCP (beta)", "memory": "Implementação base de memória persistente baseada em grafos de conhecimento locais. Isso permite que o modelo lembre informações relevantes do utilizador entre diferentes conversas. É necessário configurar a variável de ambiente MEMORY_FILE_PATH.", "no": "sem descrição", + "nowledge_mem": "Requer a aplicação Nowledge Mem em execução localmente. Mantém conversas de IA, ferramentas, notas, agentes e ficheiros numa memória privada no seu computador. Transfira de https://mem.nowledge.co/", "python": "Executar código Python num ambiente sandbox seguro. Utilizar Pyodide para executar Python, suportando a maioria das bibliotecas padrão e pacotes de computação científica", "sequentialthinking": "Uma implementação de servidor MCP que fornece ferramentas para resolução dinâmica e reflexiva de problemas através de um processo de pensamento estruturado" }, diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index cba8a1e5eb..645fbaeeb0 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -3939,6 +3939,7 @@ "mcp_auto_install": "Автоматическая установка службы MCP (бета-версия)", "memory": "реализация постоянной памяти на основе локального графа знаний. Это позволяет модели запоминать информацию о пользователе между различными диалогами. Требуется настроить переменную среды MEMORY_FILE_PATH.", "no": "без описания", + "nowledge_mem": "Требуется запущенное локально приложение Nowledge Mem. Хранит чаты ИИ, инструменты, заметки, агентов и файлы в приватной памяти на вашем компьютере. Скачать можно на https://mem.nowledge.co/", "python": "Выполняйте код Python в безопасной песочнице. Запускайте Python с помощью Pyodide, поддерживается большинство стандартных библиотек и пакетов для научных вычислений", "sequentialthinking": "MCP серверная реализация, предоставляющая инструменты для динамического и рефлексивного решения проблем посредством структурированного мыслительного процесса" }, diff --git a/src/renderer/src/store/mcp.ts b/src/renderer/src/store/mcp.ts index ed7076bc1c..5b8d5bcdcf 100644 --- a/src/renderer/src/store/mcp.ts +++ b/src/renderer/src/store/mcp.ts @@ -183,6 +183,16 @@ export const builtinMCPServers: BuiltinMCPServer[] = [ provider: 'CherryAI', installSource: 'builtin', isTrusted: true + }, + { + id: nanoid(), + name: BuiltinMCPServerNames.nowledgeMem, + reference: 'https://mem.nowledge.co/', + type: 'inMemory', + isActive: false, + provider: 'Nowledge', + installSource: 'builtin', + isTrusted: true } ] as const diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index c9dc647acd..9ac3199f35 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -751,7 +751,8 @@ export const BuiltinMCPServerNames = { difyKnowledge: '@cherry/dify-knowledge', python: '@cherry/python', didiMCP: '@cherry/didi-mcp', - browser: '@cherry/browser' + browser: '@cherry/browser', + nowledgeMem: '@cherry/nowledge-mem' } as const export type BuiltinMCPServerName = (typeof BuiltinMCPServerNames)[keyof typeof BuiltinMCPServerNames] From fd6986076ae42f2053ef9a5993549457afb63fde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:34:39 +0800 Subject: [PATCH 5/8] chore(deps): bump jws from 4.0.0 to 4.0.1 (#11977) Bumps [jws](https://github.com/brianloveswords/node-jws) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/brianloveswords/node-jws/releases) - [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianloveswords/node-jws/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: jws dependency-version: 4.0.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6e933257d1..ab781d3700 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11246,7 +11246,7 @@ __metadata: languageName: node linkType: hard -"buffer-equal-constant-time@npm:1.0.1": +"buffer-equal-constant-time@npm:^1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e @@ -17178,24 +17178,24 @@ __metadata: languageName: node linkType: hard -"jwa@npm:^2.0.0": - version: 2.0.0 - resolution: "jwa@npm:2.0.0" +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" dependencies: - buffer-equal-constant-time: "npm:1.0.1" + buffer-equal-constant-time: "npm:^1.0.1" ecdsa-sig-formatter: "npm:1.0.11" safe-buffer: "npm:^5.0.1" - checksum: 10c0/6baab823b93c038ba1d2a9e531984dcadbc04e9eb98d171f4901b7a40d2be15961a359335de1671d78cb6d987f07cbe5d350d8143255977a889160c4d90fcc3c + checksum: 10c0/ab3ebc6598e10dc11419d4ed675c9ca714a387481466b10e8a6f3f65d8d9c9237e2826f2505280a739cf4cbcf511cb288eeec22b5c9c63286fc5a2e4f97e78cf languageName: node linkType: hard "jws@npm:^4.0.0": - version: 4.0.0 - resolution: "jws@npm:4.0.0" + version: 4.0.1 + resolution: "jws@npm:4.0.1" dependencies: - jwa: "npm:^2.0.0" + jwa: "npm:^2.0.1" safe-buffer: "npm:^5.0.1" - checksum: 10c0/f1ca77ea5451e8dc5ee219cb7053b8a4f1254a79cb22417a2e1043c1eb8a569ae118c68f24d72a589e8a3dd1824697f47d6bd4fb4bebb93a3bdf53545e721661 + checksum: 10c0/6be1ed93023aef570ccc5ea8d162b065840f3ef12f0d1bb3114cade844de7a357d5dc558201d9a65101e70885a6fa56b17462f520e6b0d426195510618a154d0 languageName: node linkType: hard From eb7a2cc85ae98daa02b5268c3808297efdf30b93 Mon Sep 17 00:00:00 2001 From: SuYao Date: Thu, 18 Dec 2025 13:49:09 +0800 Subject: [PATCH 6/8] feat: add support for Xiaomi MiMo model (#11961) * feat: add support for Xiaomi MiMo model - Implemented support for the MiMo model in reasoning logic. - Added MiMo model configuration in default models. - Included MiMo logos for both models and providers. - Updated provider configurations to include Xiaomi MiMo. - Enhanced reasoning effort and options to accommodate MiMo. - Added migration logic for state management to include MiMo. - Updated versioning in store to reflect changes. * chore(i18n): add specific provider name * fix(provider): add xiaomi mimo anthropic apihost * chore: url * fix: add tool use capability --- src/renderer/src/aiCore/utils/reasoning.ts | 7 +++++ .../src/assets/images/models/mimo.svg | 17 ++++++++++++ .../src/assets/images/providers/mimo.svg | 17 ++++++++++++ src/renderer/src/config/models/default.ts | 8 ++++++ src/renderer/src/config/models/logo.ts | 4 ++- src/renderer/src/config/models/reasoning.ts | 14 +++++++++- src/renderer/src/config/models/tooluse.ts | 3 ++- src/renderer/src/config/providers.ts | 26 ++++++++++++++++++- src/renderer/src/i18n/label.ts | 3 ++- src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + src/renderer/src/i18n/translate/de-de.json | 1 + src/renderer/src/i18n/translate/el-gr.json | 1 + src/renderer/src/i18n/translate/es-es.json | 1 + src/renderer/src/i18n/translate/fr-fr.json | 1 + src/renderer/src/i18n/translate/ja-jp.json | 1 + src/renderer/src/i18n/translate/pt-pt.json | 1 + src/renderer/src/i18n/translate/ru-ru.json | 1 + .../ProviderSettings/ProviderSetting.tsx | 3 ++- src/renderer/src/store/migrate.ts | 1 + src/renderer/src/types/index.ts | 1 + src/renderer/src/types/provider.ts | 6 +++-- 23 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 src/renderer/src/assets/images/models/mimo.svg create mode 100644 src/renderer/src/assets/images/providers/mimo.svg diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index 9320fb8e50..a2364d97e1 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -29,6 +29,7 @@ import { isSupportedThinkingTokenDoubaoModel, isSupportedThinkingTokenGeminiModel, isSupportedThinkingTokenHunyuanModel, + isSupportedThinkingTokenMiMoModel, isSupportedThinkingTokenModel, isSupportedThinkingTokenQwenModel, isSupportedThinkingTokenZhipuModel @@ -409,6 +410,12 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin return { thinking: { type: 'enabled' } } } + if (isSupportedThinkingTokenMiMoModel(model)) { + return { + thinking: { type: 'enabled' } + } + } + // Default case: no special thinking settings return {} } diff --git a/src/renderer/src/assets/images/models/mimo.svg b/src/renderer/src/assets/images/models/mimo.svg new file mode 100644 index 0000000000..82370fece3 --- /dev/null +++ b/src/renderer/src/assets/images/models/mimo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/assets/images/providers/mimo.svg b/src/renderer/src/assets/images/providers/mimo.svg new file mode 100644 index 0000000000..82370fece3 --- /dev/null +++ b/src/renderer/src/assets/images/providers/mimo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index 66368d35fe..37854c5749 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -1791,5 +1791,13 @@ export const SYSTEM_MODELS: Record = provider: 'cerebras', group: 'qwen' } + ], + mimo: [ + { + id: 'mimo-v2-flash', + name: 'Mimo V2 Flash', + provider: 'mimo', + group: 'Mimo' + } ] } diff --git a/src/renderer/src/config/models/logo.ts b/src/renderer/src/config/models/logo.ts index fe1a919c5e..75ad71f662 100644 --- a/src/renderer/src/config/models/logo.ts +++ b/src/renderer/src/config/models/logo.ts @@ -103,6 +103,7 @@ import MicrosoftModelLogo from '@renderer/assets/images/models/microsoft.png' import MicrosoftModelLogoDark from '@renderer/assets/images/models/microsoft_dark.png' import MidjourneyModelLogo from '@renderer/assets/images/models/midjourney.png' import MidjourneyModelLogoDark from '@renderer/assets/images/models/midjourney_dark.png' +import MiMoModelLogo from '@renderer/assets/images/models/mimo.svg' import { default as MinicpmModelLogo, default as MinicpmModelLogoDark @@ -301,7 +302,8 @@ export function getModelLogoById(modelId: string): string | undefined { bytedance: BytedanceModelLogo, ling: LingModelLogo, ring: LingModelLogo, - '(V_1|V_1_TURBO|V_2|V_2A|V_2_TURBO|DESCRIBE|UPSCALE)': IdeogramModelLogo + '(V_1|V_1_TURBO|V_2|V_2A|V_2_TURBO|DESCRIBE|UPSCALE)': IdeogramModelLogo, + mimo: MiMoModelLogo } as const satisfies Record for (const key in logoMap) { diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 14174d1629..faa04721e6 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -52,6 +52,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT = { doubao_no_auto: ['high'] as const, doubao_after_251015: ['minimal', 'low', 'medium', 'high'] as const, hunyuan: ['auto'] as const, + mimo: ['auto'] as const, zhipu: ['auto'] as const, perplexity: ['low', 'medium', 'high'] as const, deepseek_hybrid: ['auto'] as const @@ -80,6 +81,7 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { doubao: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, doubao_no_auto: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, doubao_after_251015: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015] as const, + mimo: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.mimo] as const, hunyuan: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, zhipu: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, perplexity: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.perplexity] as const, @@ -155,6 +157,7 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { else if (isSupportedReasoningEffortPerplexityModel(model)) thinkingModelType = 'perplexity' else if (isSupportedThinkingTokenZhipuModel(model)) thinkingModelType = 'zhipu' else if (isDeepSeekHybridInferenceModel(model)) thinkingModelType = 'deepseek_hybrid' + else if (isSupportedThinkingTokenMiMoModel(model)) thinkingModelType = 'mimo' return thinkingModelType } @@ -263,7 +266,8 @@ function _isSupportedThinkingTokenModel(model: Model): boolean { isSupportedThinkingTokenClaudeModel(model) || isSupportedThinkingTokenDoubaoModel(model) || isSupportedThinkingTokenHunyuanModel(model) || - isSupportedThinkingTokenZhipuModel(model) + isSupportedThinkingTokenZhipuModel(model) || + isSupportedThinkingTokenMiMoModel(model) ) } @@ -561,6 +565,11 @@ export const isSupportedThinkingTokenZhipuModel = (model: Model): boolean => { return ['glm-4.5', 'glm-4.6'].some((id) => modelId.includes(id)) } +export const isSupportedThinkingTokenMiMoModel = (model: Model): boolean => { + const modelId = getLowerBaseModelName(model.id, '/') + return ['mimo-v2-flash'].some((id) => modelId.includes(id)) +} + export const isDeepSeekHybridInferenceModel = (model: Model) => { const { idResult, nameResult } = withModelIdAndNameAsId(model, (model) => { const modelId = getLowerBaseModelName(model.id) @@ -599,6 +608,8 @@ export const isZhipuReasoningModel = (model?: Model): boolean => { return isSupportedThinkingTokenZhipuModel(model) || modelId.includes('glm-z1') } +export const isMiMoReasoningModel = isSupportedThinkingTokenMiMoModel + export const isStepReasoningModel = (model?: Model): boolean => { if (!model) { return false @@ -649,6 +660,7 @@ export function isReasoningModel(model?: Model): boolean { isDeepSeekHybridInferenceModel(model) || isLingReasoningModel(model) || isMiniMaxReasoningModel(model) || + isMiMoReasoningModel(model) || modelId.includes('magistral') || modelId.includes('pangu-pro-moe') || modelId.includes('seed-oss') || diff --git a/src/renderer/src/config/models/tooluse.ts b/src/renderer/src/config/models/tooluse.ts index 66e2dcc209..54d371dfda 100644 --- a/src/renderer/src/config/models/tooluse.ts +++ b/src/renderer/src/config/models/tooluse.ts @@ -30,7 +30,8 @@ export const FUNCTION_CALLING_MODELS = [ 'kimi-k2(?:-[\\w-]+)?', 'ling-\\w+(?:-[\\w-]+)?', 'ring-\\w+(?:-[\\w-]+)?', - 'minimax-m2' + 'minimax-m2', + 'mimo-v2-flash' ] as const const FUNCTION_CALLING_EXCLUDED_MODELS = [ diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index bc32ef3490..1adeb58ad0 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -31,6 +31,7 @@ import JinaProviderLogo from '@renderer/assets/images/providers/jina.png' import LanyunProviderLogo from '@renderer/assets/images/providers/lanyun.png' import LMStudioProviderLogo from '@renderer/assets/images/providers/lmstudio.png' import LongCatProviderLogo from '@renderer/assets/images/providers/longcat.png' +import MiMoProviderLogo from '@renderer/assets/images/providers/mimo.svg' import MinimaxProviderLogo from '@renderer/assets/images/providers/minimax.png' import MistralProviderLogo from '@renderer/assets/images/providers/mistral.png' import ModelScopeProviderLogo from '@renderer/assets/images/providers/modelscope.png' @@ -695,6 +696,17 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = models: SYSTEM_MODELS.cerebras, isSystem: true, enabled: false + }, + mimo: { + id: 'mimo', + name: 'Xiaomi MiMo', + type: 'openai', + apiKey: '', + apiHost: 'https://api.xiaomimimo.com', + anthropicApiHost: 'https://api.xiaomimimo.com/anthropic', + models: SYSTEM_MODELS.mimo, + isSystem: true, + enabled: false } } as const @@ -763,7 +775,8 @@ export const PROVIDER_LOGO_MAP: AtLeast = { huggingface: HuggingfaceProviderLogo, sophnet: SophnetProviderLogo, gateway: AIGatewayProviderLogo, - cerebras: CerebrasProviderLogo + cerebras: CerebrasProviderLogo, + mimo: MiMoProviderLogo } as const export function getProviderLogo(providerId: string) { @@ -1434,5 +1447,16 @@ export const PROVIDER_URLS: Record = { docs: 'https://inference-docs.cerebras.ai/introduction', models: 'https://inference-docs.cerebras.ai/models/overview' } + }, + mimo: { + api: { + url: 'https://api.xiaomimimo.com' + }, + websites: { + official: 'https://platform.xiaomimimo.com/', + apiKey: 'https://platform.xiaomimimo.com/#/console/usage', + docs: 'https://platform.xiaomimimo.com/#/docs/welcome', + models: 'https://platform.xiaomimimo.com/' + } } } diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index ce36282b22..2e6f84026e 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -88,7 +88,8 @@ const providerKeyMap = { huggingface: 'provider.huggingface', sophnet: 'provider.sophnet', gateway: 'provider.ai-gateway', - cerebras: 'provider.cerebras' + cerebras: 'provider.cerebras', + mimo: 'provider.mimo' } as const /** diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 08c2827200..f4012363e3 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -2643,6 +2643,7 @@ "lanyun": "LANYUN", "lmstudio": "LM Studio", "longcat": "LongCat AI", + "mimo": "Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index fa73a46499..f0d4adf4c8 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2643,6 +2643,7 @@ "lanyun": "蓝耘科技", "lmstudio": "LM Studio", "longcat": "龙猫", + "mimo": "Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope 魔搭", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index e8128f33a9..9625c68386 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -2643,6 +2643,7 @@ "lanyun": "藍耘", "lmstudio": "LM Studio", "longcat": "龍貓", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope 魔搭", diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 3d32436540..b3acb49950 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -2643,6 +2643,7 @@ "lanyun": "Lanyun Technologie", "lmstudio": "LM Studio", "longcat": "Meißner Riesenhamster", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 2f9ef72b2f..ae7b855646 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -2643,6 +2643,7 @@ "lanyun": "Λανιούν Τεχνολογία", "lmstudio": "LM Studio", "longcat": "Τσίρο", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope Magpie", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index adebbfa31b..26b499cba2 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -2643,6 +2643,7 @@ "lanyun": "Tecnología Lanyun", "lmstudio": "Estudio LM", "longcat": "Totoro", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "Minimax", "mistral": "Mistral", "modelscope": "ModelScope Módulo", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 7b24f57672..4dff56d7e9 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -2643,6 +2643,7 @@ "lanyun": "Technologie Lan Yun", "lmstudio": "Studio LM", "longcat": "Mon voisin Totoro", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope MoDa", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index e3b9fc77e5..090a1927cd 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -2643,6 +2643,7 @@ "lanyun": "LANYUN", "lmstudio": "LM Studio", "longcat": "トトロ", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 10b5a12e16..50cc4fae03 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -2643,6 +2643,7 @@ "lanyun": "Lanyun Tecnologia", "lmstudio": "Estúdio LM", "longcat": "Totoro", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "Minimax", "mistral": "Mistral", "modelscope": "ModelScope MôDá", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 645fbaeeb0..8a6a781451 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -2643,6 +2643,7 @@ "lanyun": "LANYUN", "lmstudio": "LM Studio", "longcat": "Тоторо", + "mimo": "[to be translated]:Xiaomi MiMo", "minimax": "MiniMax", "mistral": "Mistral", "modelscope": "ModelScope", diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 85f54fce87..049c14c0d1 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -80,7 +80,8 @@ const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = [ SystemProviderIds.minimax, SystemProviderIds.silicon, SystemProviderIds.qiniu, - SystemProviderIds.dmxapi + SystemProviderIds.dmxapi, + SystemProviderIds.mimo ] as const type AnthropicCompatibleProviderId = (typeof ANTHROPIC_COMPATIBLE_PROVIDER_IDS)[number] diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index f085db2302..5fe1bc0901 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -3046,6 +3046,7 @@ const migrateConfig = { assistant.settings.reasoning_effort = 'default' } }) + addProvider(state, 'mimo') logger.info('migrate 187 success') return state } catch (error) { diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 9ac3199f35..b5707f81fd 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -102,6 +102,7 @@ const ThinkModelTypes = [ 'doubao', 'doubao_no_auto', 'doubao_after_251015', + 'mimo', 'hunyuan', 'zhipu', 'perplexity', diff --git a/src/renderer/src/types/provider.ts b/src/renderer/src/types/provider.ts index 4e3e34760c..edab3a7305 100644 --- a/src/renderer/src/types/provider.ts +++ b/src/renderer/src/types/provider.ts @@ -189,7 +189,8 @@ export const SystemProviderIdSchema = z.enum([ 'huggingface', 'sophnet', 'gateway', - 'cerebras' + 'cerebras', + 'mimo' ]) export type SystemProviderId = z.infer @@ -258,7 +259,8 @@ export const SystemProviderIds = { longcat: 'longcat', huggingface: 'huggingface', gateway: 'gateway', - cerebras: 'cerebras' + cerebras: 'cerebras', + mimo: 'mimo' } as const satisfies Record type SystemProviderIdTypeMap = typeof SystemProviderIds From 7e93e8b9b20502af4e8ce5baa3cac46bbeff74e1 Mon Sep 17 00:00:00 2001 From: Phantom Date: Thu, 18 Dec 2025 14:35:36 +0800 Subject: [PATCH 7/8] feat(gemini): add support for Gemini 3 Flash and Pro model detection (#11984) * feat(gemini): update model types and add support for gemini3 variants add new model type identifiers for gemini3 flash and pro variants implement utility functions to detect gemini3 flash and pro models update reasoning configuration and tests for new gemini variants * docs(i18n): update chinese translation for minimal_description * chore: update @ai-sdk/google and @ai-sdk/google-vertex dependencies - Update @ai-sdk/google to version 2.0.49 with patch for model path fix - Update @ai-sdk/google-vertex to version 3.0.94 with updated dependencies * feat(gemini): add thinking level mapping for Gemini 3 models Implement mapping between reasoning effort options and Gemini's thinking levels. Enable thinking config for Gemini 3 models to support advanced reasoning features. * chore: update yarn.lock with patched @ai-sdk/google dependency * test(reasoning): update tests for Gemini model type classification and reasoning options Update test cases to reflect new Gemini model type classifications (gemini2_flash, gemini3_flash, gemini2_pro, gemini3_pro) and their corresponding reasoning effort options. Add tests for Gemini 3 models and adjust existing ones to match current behavior. * docs(reasoning): remove outdated TODO comment about model support --- ...ai-sdk-google-npm-2.0.49-84720f41bd.patch} | 15 ++- package.json | 7 +- src/renderer/src/aiCore/utils/reasoning.ts | 49 ++++---- .../config/models/__tests__/reasoning.test.ts | 119 ++++++++++++++---- .../src/config/models/__tests__/utils.test.ts | 97 ++++++++++++++ src/renderer/src/config/models/reasoning.ts | 53 ++++---- src/renderer/src/config/models/utils.ts | 40 ++++++ src/renderer/src/i18n/locales/zh-cn.json | 2 +- src/renderer/src/types/index.ts | 7 +- yarn.lock | 111 ++++++++++++---- 10 files changed, 403 insertions(+), 97 deletions(-) rename .yarn/patches/{@ai-sdk-google-npm-2.0.43-689ed559b3.patch => @ai-sdk-google-npm-2.0.49-84720f41bd.patch} (64%) diff --git a/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch b/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch similarity index 64% rename from .yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch rename to .yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch index 3015e702ed..67403a6575 100644 --- a/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch +++ b/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch @@ -1,5 +1,5 @@ diff --git a/dist/index.js b/dist/index.js -index 51ce7e423934fb717cb90245cdfcdb3dae6780e6..0f7f7009e2f41a79a8669d38c8a44867bbff5e1f 100644 +index d004b415c5841a1969705823614f395265ea5a8a..6b1e0dad4610b0424393ecc12e9114723bbe316b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -474,7 +474,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { @@ -12,7 +12,7 @@ index 51ce7e423934fb717cb90245cdfcdb3dae6780e6..0f7f7009e2f41a79a8669d38c8a44867 // src/google-generative-ai-options.ts diff --git a/dist/index.mjs b/dist/index.mjs -index f4b77e35c0cbfece85a3ef0d4f4e67aa6dde6271..8d2fecf8155a226006a0bde72b00b6036d4014b6 100644 +index 1780dd2391b7f42224a0b8048c723d2f81222c44..1f12ed14399d6902107ce9b435d7d8e6cc61e06b 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -480,7 +480,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) { @@ -24,3 +24,14 @@ index f4b77e35c0cbfece85a3ef0d4f4e67aa6dde6271..8d2fecf8155a226006a0bde72b00b603 } // src/google-generative-ai-options.ts +@@ -1909,8 +1909,7 @@ function createGoogleGenerativeAI(options = {}) { + } + var google = createGoogleGenerativeAI(); + export { +- VERSION, + createGoogleGenerativeAI, +- google ++ google, VERSION + }; + //# sourceMappingURL=index.mjs.map +\ No newline at end of file diff --git a/package.json b/package.json index b894030b58..a70663ffc8 100644 --- a/package.json +++ b/package.json @@ -114,8 +114,8 @@ "@ai-sdk/anthropic": "^2.0.49", "@ai-sdk/cerebras": "^1.0.31", "@ai-sdk/gateway": "^2.0.15", - "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch", - "@ai-sdk/google-vertex": "^3.0.79", + "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch", + "@ai-sdk/google-vertex": "^3.0.94", "@ai-sdk/huggingface": "^0.0.10", "@ai-sdk/mistral": "^2.0.24", "@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.85#~/.yarn/patches/@ai-sdk-openai-npm-2.0.85-27483d1d6a.patch", @@ -416,7 +416,8 @@ "@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.42": "patch:@ai-sdk/openai@npm%3A2.0.85#~/.yarn/patches/@ai-sdk-openai-npm-2.0.85-27483d1d6a.patch", "@ai-sdk/google@npm:^2.0.40": "patch:@ai-sdk/google@npm%3A2.0.40#~/.yarn/patches/@ai-sdk-google-npm-2.0.40-47e0eeee83.patch", - "@ai-sdk/openai-compatible@npm:^1.0.27": "patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch" + "@ai-sdk/openai-compatible@npm:^1.0.27": "patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch", + "@ai-sdk/google@npm:2.0.49": "patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch" }, "packageManager": "yarn@4.9.1", "lint-staged": { diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index a2364d97e1..a7d6028857 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -36,7 +36,7 @@ import { } from '@renderer/config/models' import { getStoreSetting } from '@renderer/hooks/useSettings' import { getAssistantSettings, getProviderByModel } from '@renderer/services/AssistantService' -import type { Assistant, Model } from '@renderer/types' +import type { Assistant, Model, ReasoningEffortOption } from '@renderer/types' import { EFFORT_RATIO, isSystemProvider, SystemProviderIds } from '@renderer/types' import type { OpenAIReasoningSummary } from '@renderer/types/aiCoreTypes' import type { ReasoningEffortOptionalParams } from '@renderer/types/sdk' @@ -539,20 +539,25 @@ export function getAnthropicReasoningParams( return {} } -// type GoogleThinkingLevel = NonNullable['thinkingLevel'] +type GoogleThinkingLevel = NonNullable['thinkingLevel'] -// function mapToGeminiThinkingLevel(reasoningEffort: ReasoningEffortOption): GoogelThinkingLevel { -// switch (reasoningEffort) { -// case 'low': -// return 'low' -// case 'medium': -// return 'medium' -// case 'high': -// return 'high' -// default: -// return 'medium' -// } -// } +function mapToGeminiThinkingLevel(reasoningEffort: ReasoningEffortOption): GoogleThinkingLevel { + switch (reasoningEffort) { + case 'default': + return undefined + case 'minimal': + return 'minimal' + case 'low': + return 'low' + case 'medium': + return 'medium' + case 'high': + return 'high' + default: + logger.warn('Unknown thinking level for Gemini. Fallback to medium instead.', { reasoningEffort }) + return 'medium' + } +} /** * 获取 Gemini 推理参数 @@ -585,15 +590,15 @@ export function getGeminiReasoningParams( } } - // TODO: 很多中转还不支持 // https://ai.google.dev/gemini-api/docs/gemini-3?thinking=high#new_api_features_in_gemini_3 - // if (isGemini3ThinkingTokenModel(model)) { - // return { - // thinkingConfig: { - // thinkingLevel: mapToGeminiThinkingLevel(reasoningEffort) - // } - // } - // } + if (isGemini3ThinkingTokenModel(model)) { + return { + thinkingConfig: { + includeThoughts: true, + thinkingLevel: mapToGeminiThinkingLevel(reasoningEffort) + } + } + } const effortRatio = EFFORT_RATIO[reasoningEffort] diff --git a/src/renderer/src/config/models/__tests__/reasoning.test.ts b/src/renderer/src/config/models/__tests__/reasoning.test.ts index ba7af37f17..783cb39993 100644 --- a/src/renderer/src/config/models/__tests__/reasoning.test.ts +++ b/src/renderer/src/config/models/__tests__/reasoning.test.ts @@ -695,15 +695,20 @@ describe('getThinkModelType - Comprehensive Coverage', () => { }) describe('Gemini models', () => { - it('should return gemini for Flash models', () => { - expect(getThinkModelType(createModel({ id: 'gemini-2.5-flash-latest' }))).toBe('gemini') - expect(getThinkModelType(createModel({ id: 'gemini-flash-latest' }))).toBe('gemini') - expect(getThinkModelType(createModel({ id: 'gemini-flash-lite-latest' }))).toBe('gemini') + it('should return gemini2_flash for Flash models', () => { + expect(getThinkModelType(createModel({ id: 'gemini-2.5-flash-latest' }))).toBe('gemini2_flash') + }) + it('should return gemini3_flash for Gemini 3 Flash models', () => { + expect(getThinkModelType(createModel({ id: 'gemini-3-flash-preview' }))).toBe('gemini3_flash') + expect(getThinkModelType(createModel({ id: 'gemini-flash-latest' }))).toBe('gemini3_flash') }) - it('should return gemini_pro for Pro models', () => { - expect(getThinkModelType(createModel({ id: 'gemini-2.5-pro-latest' }))).toBe('gemini_pro') - expect(getThinkModelType(createModel({ id: 'gemini-pro-latest' }))).toBe('gemini_pro') + it('should return gemini2_pro for Gemini 2.5 Pro models', () => { + expect(getThinkModelType(createModel({ id: 'gemini-2.5-pro-latest' }))).toBe('gemini2_pro') + }) + it('should return gemini3_pro for Gemini 3 Pro models', () => { + expect(getThinkModelType(createModel({ id: 'gemini-3-pro-preview' }))).toBe('gemini3_pro') + expect(getThinkModelType(createModel({ id: 'gemini-pro-latest' }))).toBe('gemini3_pro') }) }) @@ -810,7 +815,7 @@ describe('getThinkModelType - Comprehensive Coverage', () => { name: 'gemini-2.5-flash-latest' }) ) - ).toBe('gemini') + ).toBe('gemini2_flash') }) it('should use id result when id matches', () => { @@ -835,7 +840,7 @@ describe('getThinkModelType - Comprehensive Coverage', () => { it('should handle case insensitivity correctly', () => { expect(getThinkModelType(createModel({ id: 'GPT-5.1' }))).toBe('gpt5_1') - expect(getThinkModelType(createModel({ id: 'Gemini-2.5-Flash-Latest' }))).toBe('gemini') + expect(getThinkModelType(createModel({ id: 'Gemini-2.5-Flash-Latest' }))).toBe('gemini2_flash') expect(getThinkModelType(createModel({ id: 'DeepSeek-V3.1' }))).toBe('deepseek_hybrid') }) @@ -855,7 +860,7 @@ describe('getThinkModelType - Comprehensive Coverage', () => { it('should handle models with version suffixes', () => { expect(getThinkModelType(createModel({ id: 'gpt-5-preview-2024' }))).toBe('gpt5') expect(getThinkModelType(createModel({ id: 'o3-mini-2024' }))).toBe('o') - expect(getThinkModelType(createModel({ id: 'gemini-2.5-flash-latest-001' }))).toBe('gemini') + expect(getThinkModelType(createModel({ id: 'gemini-2.5-flash-latest-001' }))).toBe('gemini2_flash') }) it('should prioritize GPT-5.1 over GPT-5 detection', () => { @@ -955,6 +960,14 @@ describe('Gemini Models', () => { group: '' }) ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-flash-preview', + name: '', + provider: '', + group: '' + }) + ).toBe(true) expect( isSupportedThinkingTokenGeminiModel({ id: 'google/gemini-3-pro-preview', @@ -996,6 +1009,31 @@ describe('Gemini Models', () => { group: '' }) ).toBe(true) + // Version with date suffixes + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-flash-preview-09-2025', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-pro-preview-09-2025', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-flash-exp-1234', + name: '', + provider: '', + group: '' + }) + ).toBe(true) // Version with decimals expect( isSupportedThinkingTokenGeminiModel({ @@ -1015,7 +1053,8 @@ describe('Gemini Models', () => { ).toBe(true) }) - it('should return true for gemini-3 image models', () => { + it('should return true for gemini-3-pro-image models only', () => { + // Only gemini-3-pro-image models should return true expect( isSupportedThinkingTokenGeminiModel({ id: 'gemini-3-pro-image-preview', @@ -1024,6 +1063,17 @@ describe('Gemini Models', () => { group: '' }) ).toBe(true) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-pro-image', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return false for other gemini-3 image models', () => { expect( isSupportedThinkingTokenGeminiModel({ id: 'gemini-3.0-flash-image-preview', @@ -1086,6 +1136,22 @@ describe('Gemini Models', () => { group: '' }) ).toBe(false) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-flash-preview-tts', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isSupportedThinkingTokenGeminiModel({ + id: 'gemini-3-pro-tts', + name: '', + provider: '', + group: '' + }) + ).toBe(false) }) it('should return false for older gemini models', () => { @@ -1811,7 +1877,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { describe('Gemini models', () => { it('should return correct options for Gemini Flash models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-flash-latest' }))).toEqual([ + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-flash' }))).toEqual([ 'default', 'none', 'low', @@ -1819,36 +1885,46 @@ describe('getModelSupportedReasoningEffortOptions', () => { 'high', 'auto' ]) - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-flash-latest' }))).toEqual([ + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-flash-preview' }))).toEqual([ 'default', - 'none', + 'minimal', 'low', 'medium', - 'high', - 'auto' + 'high' + ]) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-flash-latest' }))).toEqual([ + 'default', + 'minimal', + 'low', + 'medium', + 'high' ]) }) it('should return correct options for Gemini Pro models', () => { - expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-pro-latest' }))).toEqual([ + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-2.5-pro' }))).toEqual([ 'default', 'low', 'medium', 'high', 'auto' ]) + expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-pro-preview' }))).toEqual([ + 'default', + 'low', + 'high' + ]) expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-pro-latest' }))).toEqual([ 'default', 'low', - 'medium', - 'high', - 'auto' + 'high' ]) }) it('should return correct options for Gemini 3 models', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-flash' }))).toEqual([ 'default', + 'minimal', 'low', 'medium', 'high' @@ -1856,7 +1932,6 @@ describe('getModelSupportedReasoningEffortOptions', () => { expect(getModelSupportedReasoningEffortOptions(createModel({ id: 'gemini-3-pro-preview' }))).toEqual([ 'default', 'low', - 'medium', 'high' ]) }) @@ -2078,7 +2153,7 @@ describe('getModelSupportedReasoningEffortOptions', () => { const geminiModel = createModel({ id: 'gemini-2.5-flash-latest' }) const geminiResult = getModelSupportedReasoningEffortOptions(geminiModel) - expect(geminiResult).toEqual(MODEL_SUPPORTED_OPTIONS.gemini) + expect(geminiResult).toEqual(MODEL_SUPPORTED_OPTIONS.gemini2_flash) }) }) }) diff --git a/src/renderer/src/config/models/__tests__/utils.test.ts b/src/renderer/src/config/models/__tests__/utils.test.ts index 042673b75d..602b0737a8 100644 --- a/src/renderer/src/config/models/__tests__/utils.test.ts +++ b/src/renderer/src/config/models/__tests__/utils.test.ts @@ -20,6 +20,8 @@ import { getModelSupportedVerbosity, groupQwenModels, isAnthropicModel, + isGemini3FlashModel, + isGemini3ProModel, isGeminiModel, isGemmaModel, isGenerateImageModels, @@ -432,6 +434,101 @@ describe('model utils', () => { }) }) + describe('isGemini3FlashModel', () => { + it('detects gemini-3-flash model', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash' }))).toBe(true) + }) + + it('detects gemini-3-flash-preview model', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-preview' }))).toBe(true) + }) + + it('detects gemini-3-flash with version suffixes', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-latest' }))).toBe(true) + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-preview-09-2025' }))).toBe(true) + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-exp-1234' }))).toBe(true) + }) + + it('detects gemini-flash-latest alias', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-flash-latest' }))).toBe(true) + expect(isGemini3FlashModel(createModel({ id: 'Gemini-Flash-Latest' }))).toBe(true) + }) + + it('detects gemini-3-flash with uppercase', () => { + expect(isGemini3FlashModel(createModel({ id: 'Gemini-3-Flash' }))).toBe(true) + expect(isGemini3FlashModel(createModel({ id: 'GEMINI-3-FLASH-PREVIEW' }))).toBe(true) + }) + + it('excludes gemini-3-flash-image models', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-image-preview' }))).toBe(false) + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-flash-image' }))).toBe(false) + }) + + it('returns false for non-flash gemini-3 models', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-pro' }))).toBe(false) + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-pro-preview' }))).toBe(false) + expect(isGemini3FlashModel(createModel({ id: 'gemini-3-pro-image-preview' }))).toBe(false) + }) + + it('returns false for other gemini models', () => { + expect(isGemini3FlashModel(createModel({ id: 'gemini-2-flash' }))).toBe(false) + expect(isGemini3FlashModel(createModel({ id: 'gemini-2-flash-preview' }))).toBe(false) + expect(isGemini3FlashModel(createModel({ id: 'gemini-2.5-flash-preview-09-2025' }))).toBe(false) + }) + + it('returns false for null/undefined models', () => { + expect(isGemini3FlashModel(null)).toBe(false) + expect(isGemini3FlashModel(undefined)).toBe(false) + }) + }) + + describe('isGemini3ProModel', () => { + it('detects gemini-3-pro model', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro' }))).toBe(true) + }) + + it('detects gemini-3-pro-preview model', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-preview' }))).toBe(true) + }) + + it('detects gemini-3-pro with version suffixes', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-latest' }))).toBe(true) + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-preview-09-2025' }))).toBe(true) + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-exp-1234' }))).toBe(true) + }) + + it('detects gemini-pro-latest alias', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-pro-latest' }))).toBe(true) + expect(isGemini3ProModel(createModel({ id: 'Gemini-Pro-Latest' }))).toBe(true) + }) + + it('detects gemini-3-pro with uppercase', () => { + expect(isGemini3ProModel(createModel({ id: 'Gemini-3-Pro' }))).toBe(true) + expect(isGemini3ProModel(createModel({ id: 'GEMINI-3-PRO-PREVIEW' }))).toBe(true) + }) + + it('excludes gemini-3-pro-image models', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-image-preview' }))).toBe(false) + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-image' }))).toBe(false) + expect(isGemini3ProModel(createModel({ id: 'gemini-3-pro-image-latest' }))).toBe(false) + }) + + it('returns false for non-pro gemini-3 models', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-3-flash' }))).toBe(false) + expect(isGemini3ProModel(createModel({ id: 'gemini-3-flash-preview' }))).toBe(false) + }) + + it('returns false for other gemini models', () => { + expect(isGemini3ProModel(createModel({ id: 'gemini-2-pro' }))).toBe(false) + expect(isGemini3ProModel(createModel({ id: 'gemini-2.5-pro-preview-09-2025' }))).toBe(false) + }) + + it('returns false for null/undefined models', () => { + expect(isGemini3ProModel(null)).toBe(false) + expect(isGemini3ProModel(undefined)).toBe(false) + }) + }) + describe('isZhipuModel', () => { it('detects Zhipu models by provider', () => { expect(isZhipuModel(createModel({ provider: 'zhipu' }))).toBe(true) diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index faa04721e6..144afc52a7 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -20,7 +20,7 @@ import { isOpenAIReasoningModel, isSupportedReasoningEffortOpenAIModel } from './openai' -import { GEMINI_FLASH_MODEL_REGEX, isGemini3ThinkingTokenModel } from './utils' +import { GEMINI_FLASH_MODEL_REGEX, isGemini3FlashModel, isGemini3ProModel } from './utils' import { isTextToImageModel } from './vision' // Reasoning models @@ -43,9 +43,10 @@ export const MODEL_SUPPORTED_REASONING_EFFORT = { gpt52pro: ['medium', 'high', 'xhigh'] as const, grok: ['low', 'high'] as const, grok4_fast: ['auto'] as const, - gemini: ['low', 'medium', 'high', 'auto'] as const, - gemini3: ['low', 'medium', 'high'] as const, - gemini_pro: ['low', 'medium', 'high', 'auto'] as const, + gemini2_flash: ['low', 'medium', 'high', 'auto'] as const, + gemini2_pro: ['low', 'medium', 'high', 'auto'] as const, + gemini3_flash: ['minimal', 'low', 'medium', 'high'] as const, + gemini3_pro: ['low', 'high'] as const, qwen: ['low', 'medium', 'high'] as const, qwen_thinking: ['low', 'medium', 'high'] as const, doubao: ['auto', 'high'] as const, @@ -73,9 +74,10 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { gpt52pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gpt52pro] as const, grok: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.grok] as const, grok4_fast: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const, - gemini: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const, - gemini_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini_pro] as const, - gemini3: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3] as const, + gemini2_flash: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini2_flash] as const, + gemini2_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini2_pro] as const, + gemini3_flash: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3_flash] as const, + gemini3_pro: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini3_pro] as const, qwen: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const, qwen_thinking: ['default', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking] as const, doubao: ['default', 'none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, @@ -102,8 +104,7 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { const modelId = getLowerBaseModelName(model.id) if (isOpenAIDeepResearchModel(model)) { return 'openai_deep_research' - } - if (isGPT51SeriesModel(model)) { + } else if (isGPT51SeriesModel(model)) { if (modelId.includes('codex')) { thinkingModelType = 'gpt5_1_codex' if (isGPT51CodexMaxModel(model)) { @@ -131,16 +132,18 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { } else if (isGrok4FastReasoningModel(model)) { thinkingModelType = 'grok4_fast' } else if (isSupportedThinkingTokenGeminiModel(model)) { - if (GEMINI_FLASH_MODEL_REGEX.test(model.id)) { - thinkingModelType = 'gemini' + if (isGemini3FlashModel(model)) { + thinkingModelType = 'gemini3_flash' + } else if (isGemini3ProModel(model)) { + thinkingModelType = 'gemini3_pro' + } else if (GEMINI_FLASH_MODEL_REGEX.test(model.id)) { + thinkingModelType = 'gemini2_flash' } else { - thinkingModelType = 'gemini_pro' + thinkingModelType = 'gemini2_pro' } - if (isGemini3ThinkingTokenModel(model)) { - thinkingModelType = 'gemini3' - } - } else if (isSupportedReasoningEffortGrokModel(model)) thinkingModelType = 'grok' - else if (isSupportedThinkingTokenQwenModel(model)) { + } else if (isSupportedReasoningEffortGrokModel(model)) { + thinkingModelType = 'grok' + } else if (isSupportedThinkingTokenQwenModel(model)) { if (isQwenAlwaysThinkModel(model)) { thinkingModelType = 'qwen_thinking' } @@ -153,11 +156,17 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { } else { thinkingModelType = 'doubao_no_auto' } - } else if (isSupportedThinkingTokenHunyuanModel(model)) thinkingModelType = 'hunyuan' - else if (isSupportedReasoningEffortPerplexityModel(model)) thinkingModelType = 'perplexity' - else if (isSupportedThinkingTokenZhipuModel(model)) thinkingModelType = 'zhipu' - else if (isDeepSeekHybridInferenceModel(model)) thinkingModelType = 'deepseek_hybrid' - else if (isSupportedThinkingTokenMiMoModel(model)) thinkingModelType = 'mimo' + } else if (isSupportedThinkingTokenHunyuanModel(model)) { + thinkingModelType = 'hunyuan' + } else if (isSupportedReasoningEffortPerplexityModel(model)) { + thinkingModelType = 'perplexity' + } else if (isSupportedThinkingTokenZhipuModel(model)) { + thinkingModelType = 'zhipu' + } else if (isDeepSeekHybridInferenceModel(model)) { + thinkingModelType = 'deepseek_hybrid' + } else if (isSupportedThinkingTokenMiMoModel(model)) { + thinkingModelType = 'mimo' + } return thinkingModelType } diff --git a/src/renderer/src/config/models/utils.ts b/src/renderer/src/config/models/utils.ts index 6516550937..12e85326cd 100644 --- a/src/renderer/src/config/models/utils.ts +++ b/src/renderer/src/config/models/utils.ts @@ -267,3 +267,43 @@ export const isGemini3ThinkingTokenModel = (model: Model) => { const modelId = getLowerBaseModelName(model.id) return isGemini3Model(model) && !modelId.includes('image') } + +/** + * Check if the model is a Gemini 3 Flash model + * Matches: gemini-3-flash, gemini-3-flash-preview, gemini-3-flash-preview-09-2025, gemini-flash-latest (alias) + * Excludes: gemini-3-flash-image-preview + * @param model - The model to check + * @returns true if the model is a Gemini 3 Flash model + */ +export const isGemini3FlashModel = (model: Model | undefined | null): boolean => { + if (!model) { + return false + } + const modelId = getLowerBaseModelName(model.id) + // Check for gemini-flash-latest alias (currently points to gemini-3-flash, may change in future) + if (modelId === 'gemini-flash-latest') { + return true + } + // Check for gemini-3-flash with optional suffixes, excluding image variants + return /gemini-3-flash(?!-image)(?:-[\w-]+)*$/i.test(modelId) +} + +/** + * Check if the model is a Gemini 3 Pro model + * Matches: gemini-3-pro, gemini-3-pro-preview, gemini-3-pro-preview-09-2025, gemini-pro-latest (alias) + * Excludes: gemini-3-pro-image-preview + * @param model - The model to check + * @returns true if the model is a Gemini 3 Pro model + */ +export const isGemini3ProModel = (model: Model | undefined | null): boolean => { + if (!model) { + return false + } + const modelId = getLowerBaseModelName(model.id) + // Check for gemini-pro-latest alias (currently points to gemini-3-pro, may change in future) + if (modelId === 'gemini-pro-latest') { + return true + } + // Check for gemini-3-pro with optional suffixes, excluding image variants + return /gemini-3-pro(?!-image)(?:-[\w-]+)*$/i.test(modelId) +} diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index f0d4adf4c8..0e5b2f60e7 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -560,7 +560,7 @@ "medium": "斟酌", "medium_description": "中强度推理", "minimal": "微念", - "minimal_description": "最小程度的思考", + "minimal_description": "最小程度的推理", "off": "关闭", "off_description": "禁用推理", "xhigh": "穷究", diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index b5707f81fd..eefa380a66 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -94,9 +94,10 @@ const ThinkModelTypes = [ 'gpt52pro', 'grok', 'grok4_fast', - 'gemini', - 'gemini_pro', - 'gemini3', + 'gemini2_flash', + 'gemini2_pro', + 'gemini3_flash', + 'gemini3_pro', 'qwen', 'qwen_thinking', 'doubao', diff --git a/yarn.lock b/yarn.lock index ab781d3700..d9d5ec1d6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -102,6 +102,18 @@ __metadata: languageName: node linkType: hard +"@ai-sdk/anthropic@npm:2.0.56": + version: 2.0.56 + resolution: "@ai-sdk/anthropic@npm:2.0.56" + dependencies: + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.19" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/f2b6029c92443f831a2d124420e805d057668003067b1f677a4292d02f27aa3ad533374ea996d77ede7746a42c46fb94a8f2d8c0e7758a4555ea18c8b532052c + languageName: node + linkType: hard + "@ai-sdk/azure@npm:^2.0.87": version: 2.0.87 resolution: "@ai-sdk/azure@npm:2.0.87" @@ -166,42 +178,42 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/google-vertex@npm:^3.0.79": - version: 3.0.79 - resolution: "@ai-sdk/google-vertex@npm:3.0.79" +"@ai-sdk/google-vertex@npm:^3.0.94": + version: 3.0.94 + resolution: "@ai-sdk/google-vertex@npm:3.0.94" dependencies: - "@ai-sdk/anthropic": "npm:2.0.49" - "@ai-sdk/google": "npm:2.0.43" + "@ai-sdk/anthropic": "npm:2.0.56" + "@ai-sdk/google": "npm:2.0.49" "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.17" - google-auth-library: "npm:^9.15.0" + "@ai-sdk/provider-utils": "npm:3.0.19" + google-auth-library: "npm:^10.5.0" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/a86949b8d4a855409acdf7dc8d93ad9ea8ccf2bc3849acbe1ecbe4d6d66f06bcb5242f0df8eea24214e78732618b71ec8a019cbbeab16366f9ad3c860c5d8d30 + checksum: 10c0/68e2ee9e6525a5e43f90304980e64bf2a4227fd3ce74a7bf17e5ace094ea1bca8f8f18a8cc332a492fee4b912568a768f7479a4eed8148b84e7de1adf4104ad0 languageName: node linkType: hard -"@ai-sdk/google@npm:2.0.43": - version: 2.0.43 - resolution: "@ai-sdk/google@npm:2.0.43" +"@ai-sdk/google@npm:2.0.49": + version: 2.0.49 + resolution: "@ai-sdk/google@npm:2.0.49" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.17" + "@ai-sdk/provider-utils": "npm:3.0.19" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/5a421a9746cf8cbdf3bb7fb49426453a4fe0e354ea55a0123e628afb7acf9bb19959d512c0f8e6d7dbefbfa7e1cef4502fc146149007258a8eeb57743ac5e9e5 + checksum: 10c0/f3f8acfcd956edc7d807d22963d5eff0f765418f1f2c7d18615955ccdfcebb4d43cc26ce1f712c6a53572f1d8becc0773311b77b1f1bf1af87d675c5f017d5a4 languageName: node linkType: hard -"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch": - version: 2.0.43 - resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch::version=2.0.43&hash=4dde1e" +"@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch": + version: 2.0.49 + resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch::version=2.0.49&hash=406c25" dependencies: "@ai-sdk/provider": "npm:2.0.0" - "@ai-sdk/provider-utils": "npm:3.0.17" + "@ai-sdk/provider-utils": "npm:3.0.19" peerDependencies: zod: ^3.25.76 || ^4.1.8 - checksum: 10c0/4cfd17e9c47f2b742d8a0b1ca3532b4dc48753088363b74b01a042f63652174fa9a3fbf655a23f823974c673121dffbd2d192bb0c1bf158da4e2bf498fc76527 + checksum: 10c0/8d4d881583c2301dce8a4e3066af2ba7d99b30520b6219811f90271c93bf8a07dc23e752fa25ffd0e72c6ec56e97d40d32e04072a362accf7d01a745a2d2a352 languageName: node linkType: hard @@ -10051,8 +10063,8 @@ __metadata: "@ai-sdk/anthropic": "npm:^2.0.49" "@ai-sdk/cerebras": "npm:^1.0.31" "@ai-sdk/gateway": "npm:^2.0.15" - "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch" - "@ai-sdk/google-vertex": "npm:^3.0.79" + "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch" + "@ai-sdk/google-vertex": "npm:^3.0.94" "@ai-sdk/huggingface": "npm:^0.0.10" "@ai-sdk/mistral": "npm:^2.0.24" "@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.85#~/.yarn/patches/@ai-sdk-openai-npm-2.0.85-27483d1d6a.patch" @@ -15499,6 +15511,18 @@ __metadata: languageName: node linkType: hard +"gaxios@npm:^7.0.0": + version: 7.1.3 + resolution: "gaxios@npm:7.1.3" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + node-fetch: "npm:^3.3.2" + rimraf: "npm:^5.0.1" + checksum: 10c0/a4a1cdf9a392c0c22e9734a40dca5a77a2903f505b939a50f1e68e312458b1289b7993d2f72d011426e89657cae77a3aa9fc62fb140e8ba90a1faa31fdbde4d2 + languageName: node + linkType: hard + "gcp-metadata@npm:^6.1.0": version: 6.1.1 resolution: "gcp-metadata@npm:6.1.1" @@ -15510,6 +15534,17 @@ __metadata: languageName: node linkType: hard +"gcp-metadata@npm:^8.0.0": + version: 8.1.2 + resolution: "gcp-metadata@npm:8.1.2" + dependencies: + gaxios: "npm:^7.0.0" + google-logging-utils: "npm:^1.0.0" + json-bigint: "npm:^1.0.0" + checksum: 10c0/15a61231a9410dc11c2828d2c9fdc8b0a939f1af746195c44edc6f2ffea0acab52cef3a7b9828069a36fd5d68bda730f7328a415fe42a01258f6e249dfba6908 + languageName: node + linkType: hard + "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -15733,7 +15768,22 @@ __metadata: languageName: node linkType: hard -"google-auth-library@npm:^9.14.2, google-auth-library@npm:^9.15.0, google-auth-library@npm:^9.15.1, google-auth-library@npm:^9.4.2": +"google-auth-library@npm:^10.5.0": + version: 10.5.0 + resolution: "google-auth-library@npm:10.5.0" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^7.0.0" + gcp-metadata: "npm:^8.0.0" + google-logging-utils: "npm:^1.0.0" + gtoken: "npm:^8.0.0" + jws: "npm:^4.0.0" + checksum: 10c0/49d3931d20b1f4a4d075216bf5518e2b3396dcf441a8f1952611cf3b6080afb1261c3d32009609047ee4a1cc545269a74b4957e6bba9cce840581df309c4b145 + languageName: node + linkType: hard + +"google-auth-library@npm:^9.14.2, google-auth-library@npm:^9.15.1, google-auth-library@npm:^9.4.2": version: 9.15.1 resolution: "google-auth-library@npm:9.15.1" dependencies: @@ -15754,6 +15804,13 @@ __metadata: languageName: node linkType: hard +"google-logging-utils@npm:^1.0.0": + version: 1.1.3 + resolution: "google-logging-utils@npm:1.1.3" + checksum: 10c0/e65201c7e96543bd1423b9324013736646b9eed60941e0bfa47b9bfd146d2f09cf3df1c99ca60b7d80a726075263ead049ee72de53372cb8458c3bc55c2c1e59 + languageName: node + linkType: hard + "gopd@npm:^1.0.1, gopd@npm:^1.2.0": version: 1.2.0 resolution: "gopd@npm:1.2.0" @@ -15842,6 +15899,16 @@ __metadata: languageName: node linkType: hard +"gtoken@npm:^8.0.0": + version: 8.0.0 + resolution: "gtoken@npm:8.0.0" + dependencies: + gaxios: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10c0/058538e5bbe081d30ada5f1fd34d3a8194357c2e6ecbf7c8a98daeefbf13f7e06c15649c7dace6a1d4cc3bc6dc5483bd484d6d7adc5852021896d7c05c439f37 + languageName: node + linkType: hard + "hachure-fill@npm:^0.5.2": version: 0.5.2 resolution: "hachure-fill@npm:0.5.2" @@ -22778,7 +22845,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^5.0.10": +"rimraf@npm:^5.0.1, rimraf@npm:^5.0.10": version: 5.0.10 resolution: "rimraf@npm:5.0.10" dependencies: From 5e8646c6a59b2206e8d6b7624db0f935fc194755 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Thu, 18 Dec 2025 14:45:21 +0800 Subject: [PATCH 8/8] fix: update API path for image generation requests in OpenAIBaseClient --- .../src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts index 937827db01..9d03552cb3 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts @@ -69,7 +69,7 @@ export abstract class OpenAIBaseClient< const sdk = await this.getSdkInstance() const response = (await sdk.request({ method: 'post', - path: '/images/generations', + path: '/v1/images/generations', signal, body: { model,