From 313dac0f64c051b463f53c60700b6294c6111f07 Mon Sep 17 00:00:00 2001 From: yudong Date: Tue, 6 Jan 2026 15:17:22 +0800 Subject: [PATCH 1/7] fix: Changed the ID of the doubao-seed-1-8 from '251215' to '251228' (#12307) Co-authored-by: wangyudong --- src/renderer/src/config/models/__tests__/reasoning.test.ts | 4 ++-- src/renderer/src/config/models/default.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/config/models/__tests__/reasoning.test.ts b/src/renderer/src/config/models/__tests__/reasoning.test.ts index 56f9cd0b60..0f58be4ef0 100644 --- a/src/renderer/src/config/models/__tests__/reasoning.test.ts +++ b/src/renderer/src/config/models/__tests__/reasoning.test.ts @@ -745,7 +745,7 @@ describe('getThinkModelType - Comprehensive Coverage', () => { }) 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-251228' }))).toBe('doubao_after_251015') expect(getThinkModelType(createModel({ id: 'doubao-seed-1.8' }))).toBe('doubao_after_251015') }) @@ -879,7 +879,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-seed-1-8-251228' }))).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 1223d0c92c..408c047639 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -771,7 +771,7 @@ export const SYSTEM_MODELS: Record = ], doubao: [ { - id: 'doubao-seed-1-8-251215', + id: 'doubao-seed-1-8-251228', provider: 'doubao', name: 'Doubao-Seed-1.8', group: 'Doubao-Seed-1.8' From 9e45f801a801930ccfe62e3d779dfc223bd1ef83 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Tue, 6 Jan 2026 15:30:22 +0800 Subject: [PATCH 2/7] chore: optimize build excludes to reduce package size (#12311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Exclude config, patches directories - Exclude app-upgrade-config.json - Exclude unnecessary node_modules files (*.cpp, node-addon-api, prebuild-install) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.5 --- electron-builder.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/electron-builder.yml b/electron-builder.yml index bf7b7b4e91..af1774c941 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -28,6 +28,12 @@ files: - "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}" - "!**/{.editorconfig,.jekyll-metadata}" - "!src" + - "!config" + - "!patches" + - "!app-upgrade-config.json" + - "!**/node_modules/**/*.cpp" + - "!**/node_modules/node-addon-api/**" + - "!**/node_modules/prebuild-install/**" - "!scripts" - "!local" - "!docs" From a5038ac84488ba023957eea1e7289ce498ab14e8 Mon Sep 17 00:00:00 2001 From: Phantom Date: Tue, 6 Jan 2026 17:28:34 +0800 Subject: [PATCH 3/7] fix: Add reasoning control for Deepseek hybrid inference models when reasoning effort is 'none' (#12314) fix: Add reasoning control for Deepseek hybrid inference models when reasoning effort is 'none' It prevents warning --- src/renderer/src/aiCore/utils/reasoning.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index ab8a0b7983..c57dc31d17 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -118,6 +118,11 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin return { thinking: { type: 'disabled' } } } + // Deepseek, default behavior is non-thinking + if (isDeepSeekHybridInferenceModel(model)) { + return {} + } + // GPT 5.1, GPT 5.2, or newer if (isSupportNoneReasoningEffortModel(model)) { return { From bb9b73557bd98d8a3a1f7ab63451c86b804fa6ec Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Tue, 6 Jan 2026 17:33:19 +0800 Subject: [PATCH 4/7] fix: use ipinfo lite API with token for IP country detection (#12312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: use ipinfo lite API with token for IP country detection Switch from ipinfo.io/json to api.ipinfo.io/lite/me endpoint with authentication token to improve reliability and avoid rate limiting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 * fix: use country_code field from ipinfo lite API response The lite API returns country_code instead of country field. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --------- Co-authored-by: Claude Sonnet 4.5 --- src/main/utils/ipService.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/utils/ipService.ts b/src/main/utils/ipService.ts index 3180f9457c..708af4c40e 100644 --- a/src/main/utils/ipService.ts +++ b/src/main/utils/ipService.ts @@ -13,18 +13,13 @@ export async function getIpCountry(): Promise { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) - const ipinfo = await net.fetch('https://ipinfo.io/json', { - signal: controller.signal, - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', - 'Accept-Language': 'en-US,en;q=0.9' - } + const ipinfo = await net.fetch(`https://api.ipinfo.io/lite/me?token=2a42580355dae4`, { + signal: controller.signal }) clearTimeout(timeoutId) const data = await ipinfo.json() - const country = data.country || 'CN' + const country = data.country_code || 'CN' logger.info(`Detected user IP address country: ${country}`) return country } catch (error) { From af7896b90048225c416f4933a54b80aedb35f943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?George=C2=B7Dong?= <98630204+GeorgeDong32@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:45:27 +0800 Subject: [PATCH 5/7] fix(prompts): standardize tool use example format to use 'A:' label consistently (#12313) - Changed all 'Assistant:' labels to 'A:' in tool use examples for consistency - Added missing blank line before final response in both files - Affects promptToolUsePlugin.ts and prompt.ts - Resolves #12310 --- .../toolUsePlugin/promptToolUsePlugin.ts | 3 ++- src/renderer/src/utils/prompt.ts | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts b/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts index 224cee05ae..67dd33e9e0 100644 --- a/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts +++ b/packages/aiCore/src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts @@ -154,7 +154,8 @@ User: search 26 million (2019) -Assistant: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population.` + +A: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population.` /** * 构建可用工具部分(提取自 Cherry Studio) diff --git a/src/renderer/src/utils/prompt.ts b/src/renderer/src/utils/prompt.ts index 4e799800a7..326392947a 100644 --- a/src/renderer/src/utils/prompt.ts +++ b/src/renderer/src/utils/prompt.ts @@ -14,7 +14,7 @@ Here are a few examples using notional tools: --- User: Generate an image of the oldest person in this document. -Assistant: I can use the document_qa tool to find out who the oldest person is in the document. +A: I can use the document_qa tool to find out who the oldest person is in the document. document_qa {"document": "document.pdf", "question": "Who is the oldest person mentioned?"} @@ -25,7 +25,7 @@ User: John Doe, a 55 year old lumberjack living in Newfoundland. -Assistant: I can use the image_generator tool to create a portrait of John Doe. +A: I can use the image_generator tool to create a portrait of John Doe. image_generator {"prompt": "A portrait of John Doe, a 55-year-old man living in Canada."} @@ -36,12 +36,12 @@ User: image.png -Assistant: the image is generated as image.png +A: the image is generated as image.png --- User: "What is the result of the following operation: 5 + 3 + 1294.678?" -Assistant: I can use the python_interpreter tool to calculate the result of the operation. +A: I can use the python_interpreter tool to calculate the result of the operation. python_interpreter {"code": "5 + 3 + 1294.678"} @@ -52,12 +52,12 @@ User: 1302.678 -Assistant: The result of the operation is 1302.678. +A: The result of the operation is 1302.678. --- User: "Which city has the highest population , Guangzhou or Shanghai?" -Assistant: I can use the search tool to find the population of Guangzhou. +A: I can use the search tool to find the population of Guangzhou. search {"query": "Population Guangzhou"} @@ -68,7 +68,7 @@ User: Guangzhou has a population of 15 million inhabitants as of 2021. -Assistant: I can use the search tool to find the population of Shanghai. +A: I can use the search tool to find the population of Shanghai. search {"query": "Population Shanghai"} @@ -78,7 +78,8 @@ User: search 26 million (2019) -Assistant: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population. + +A: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population. ` export const AvailableTools = (tools: MCPTool[]) => { From 116ee6f94bb099f951b7c96ad63f2b90d8b65102 Mon Sep 17 00:00:00 2001 From: Shemol Date: Tue, 6 Jan 2026 22:19:03 +0800 Subject: [PATCH 6/7] fix: TokenFlux models list empty in drawing panel (#12326) Use fixed base URL for TokenFlux image API instead of provider.apiHost. After migration 191, apiHost was changed to include /openai/v1 suffix for chat API compatibility, but image API needs the base URL without this suffix, causing /openai/v1/v1/images/models (wrong path). Fixes #12284 Signed-off-by: SherlockShemol --- .../src/pages/paintings/utils/TokenFluxService.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/paintings/utils/TokenFluxService.ts b/src/renderer/src/pages/paintings/utils/TokenFluxService.ts index 4b1e224a8a..ddd362045b 100644 --- a/src/renderer/src/pages/paintings/utils/TokenFluxService.ts +++ b/src/renderer/src/pages/paintings/utils/TokenFluxService.ts @@ -6,6 +6,9 @@ import type { TokenFluxModel } from '../config/tokenFluxConfig' const logger = loggerService.withContext('TokenFluxService') +// 图片 API 使用固定的基础地址,独立于 provider.apiHost(后者是 OpenAI 兼容的聊天 API 地址) +const TOKENFLUX_IMAGE_API_HOST = 'https://api.tokenflux.ai' + export interface TokenFluxGenerationRequest { model: string input: { @@ -66,7 +69,7 @@ export class TokenFluxService { return cachedModels } - const response = await fetch(`${this.apiHost}/v1/images/models`, { + const response = await fetch(`${TOKENFLUX_IMAGE_API_HOST}/v1/images/models`, { headers: { Authorization: `Bearer ${this.apiKey}` } @@ -88,7 +91,7 @@ export class TokenFluxService { * Create a new image generation request */ async createGeneration(request: TokenFluxGenerationRequest, signal?: AbortSignal): Promise { - const response = await fetch(`${this.apiHost}/v1/images/generations`, { + const response = await fetch(`${TOKENFLUX_IMAGE_API_HOST}/v1/images/generations`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(request), @@ -108,7 +111,7 @@ export class TokenFluxService { * Get the status and result of a generation */ async getGenerationResult(generationId: string): Promise { - const response = await fetch(`${this.apiHost}/v1/images/generations/${generationId}`, { + const response = await fetch(`${TOKENFLUX_IMAGE_API_HOST}/v1/images/generations/${generationId}`, { headers: { Authorization: `Bearer ${this.apiKey}` } From 6b0bb64795beb99b5e7a63261132695462d69da4 Mon Sep 17 00:00:00 2001 From: SuYao Date: Wed, 7 Jan 2026 01:03:37 +0800 Subject: [PATCH 7/7] fix: convert 'developer' role to 'system' for unsupported providers (#12325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AI SDK v5 uses 'developer' role for reasoning models, but some providers like Azure DeepSeek R1 only support 'system', 'user', 'assistant', 'tool' roles, causing HTTP 422 errors. This fix adds a custom fetch wrapper that converts 'developer' role back to 'system' for providers that don't support it. Fixes #12321 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- .../src/aiCore/provider/providerConfig.ts | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/aiCore/provider/providerConfig.ts b/src/renderer/src/aiCore/provider/providerConfig.ts index 0ad15ea895..54915795a8 100644 --- a/src/renderer/src/aiCore/provider/providerConfig.ts +++ b/src/renderer/src/aiCore/provider/providerConfig.ts @@ -1,5 +1,5 @@ import { formatPrivateKey, hasProviderConfig, ProviderConfigFactory } from '@cherrystudio/ai-core/provider' -import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models' +import { isOpenAIChatCompletionOnlyModel, isOpenAIReasoningModel } from '@renderer/config/models' import { getAwsBedrockAccessKeyId, getAwsBedrockApiKey, @@ -29,6 +29,7 @@ import { isNewApiProvider, isOllamaProvider, isPerplexityProvider, + isSupportDeveloperRoleProvider, isSupportStreamOptionsProvider, isVertexProvider } from '@renderer/utils/provider' @@ -264,6 +265,14 @@ export function providerToAiSdkConfig(actualProvider: Provider, model: Model): A } } + // Apply developer-to-system role conversion for providers that don't support developer role + // bug: https://github.com/vercel/ai/issues/10982 + // fixPR: https://github.com/vercel/ai/pull/11127 + // TODO: but the PR don't backport to v5, the code will be removed when upgrading to v6 + if (!isSupportDeveloperRoleProvider(actualProvider) || !isOpenAIReasoningModel(model)) { + extraOptions.fetch = createDeveloperToSystemFetch(extraOptions.fetch) + } + if (hasProviderConfig(aiSdkProviderId) && aiSdkProviderId !== 'openai-compatible') { const options = ProviderConfigFactory.fromProvider(aiSdkProviderId, baseConfig, extraOptions) return { @@ -302,6 +311,44 @@ export function isModernSdkSupported(provider: Provider): boolean { return hasProviderConfig(aiSdkProviderId) } +/** + * Creates a custom fetch wrapper that converts 'developer' role to 'system' role in request body. + * This is needed for providers that don't support the 'developer' role (e.g., Azure DeepSeek R1). + * + * @param originalFetch - Optional original fetch function to wrap + * @returns A fetch function that transforms the request body + */ +function createDeveloperToSystemFetch(originalFetch?: typeof fetch): typeof fetch { + const baseFetch = originalFetch ?? fetch + return async (input: RequestInfo | URL, init?: RequestInit) => { + let options = init + if (options?.body && typeof options.body === 'string') { + try { + const body = JSON.parse(options.body) + if (body.messages && Array.isArray(body.messages)) { + let hasChanges = false + body.messages = body.messages.map((msg: { role: string }) => { + if (msg.role === 'developer') { + hasChanges = true + return { ...msg, role: 'system' } + } + return msg + }) + if (hasChanges) { + options = { + ...options, + body: JSON.stringify(body) + } + } + } + } catch { + // If parsing fails, just use original body + } + } + return baseFetch(input, options) + } +} + /** * 准备特殊provider的配置,主要用于异步处理的配置 */ @@ -360,5 +407,6 @@ export async function prepareSpecialProviderConfig( } } } + return config }