From d2be450906e3c9c9967ed380512ab35f6f415905 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 15 Nov 2025 10:01:33 +0800 Subject: [PATCH 01/10] fix: update gitcode update config url (#11298) * fix: update gitcode update config url * update version --------- Co-authored-by: Payne Fu --- package.json | 2 +- packages/shared/config/constant.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2a0b557c61..351a875057 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CherryStudio", - "version": "1.6.5", + "version": "1.7.0-rc.1", "private": true, "description": "A powerful AI assistant for producer.", "main": "./out/main/index.js", diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index 2eb5942de2..c05fde902c 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -199,7 +199,7 @@ export enum FeedUrl { export enum UpdateConfigUrl { GITHUB = 'https://raw.githubusercontent.com/CherryHQ/cherry-studio/refs/heads/x-files/app-upgrade-config/app-upgrade-config.json', - GITCODE = 'https://raw.gitcode.com/CherryHQ/cherry-studio/raw/x-files/app-upgrade-config/app-upgrade-config.json' + GITCODE = 'https://raw.gitcode.com/CherryHQ/cherry-studio/raw/x-files%2Fapp-upgrade-config/app-upgrade-config.json' } export enum UpgradeChannel { From a29b2bb3d6b762b30cd45dee62f3bd757d8e3331 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 15 Nov 2025 19:07:16 +0800 Subject: [PATCH 02/10] chore: update @opeoginni/github-copilot-openai-compatible to support gpt5.1 (#11299) * chore: update @opeoginni/github-copilot-openai-compatible to version 0.1.21 - Updated package version in package.json and yarn.lock. - Refactored OpenAIBaseClient to enhance getBaseURL method and improve header management for SDK instances. * format --- package.json | 2 +- .../legacy/clients/openai/OpenAIBaseClient.ts | 23 +++++++++++-------- yarn.lock | 10 ++++---- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 351a875057..c6b283a9ac 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", - "@opeoginni/github-copilot-openai-compatible": "0.1.19", + "@opeoginni/github-copilot-openai-compatible": "0.1.21", "@playwright/test": "^1.52.0", "@radix-ui/react-context-menu": "^2.2.16", "@reduxjs/toolkit": "^2.2.5", diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts index abd1793618..9a8d5f8383 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIBaseClient.ts @@ -48,9 +48,8 @@ export abstract class OpenAIBaseClient< } // 仅适用于openai - override getBaseURL(): string { - const host = this.provider.apiHost - return formatApiHost(host) + override getBaseURL(isSupportedAPIVerion: boolean = true): string { + return formatApiHost(this.provider.apiHost, isSupportedAPIVerion) } override async generateImage({ @@ -144,6 +143,11 @@ export abstract class OpenAIBaseClient< } let apiKeyForSdkInstance = this.apiKey + let baseURLForSdkInstance = this.getBaseURL() + let headersForSdkInstance = { + ...this.defaultHeaders(), + ...this.provider.extra_headers + } if (this.provider.id === 'copilot') { const defaultHeaders = store.getState().copilot.defaultHeaders @@ -151,6 +155,11 @@ export abstract class OpenAIBaseClient< // this.provider.apiKey不允许修改 // this.provider.apiKey = token apiKeyForSdkInstance = token + baseURLForSdkInstance = this.getBaseURL(false) + headersForSdkInstance = { + ...headersForSdkInstance, + ...COPILOT_DEFAULT_HEADERS + } } if (this.provider.id === 'azure-openai' || this.provider.type === 'azure-openai') { @@ -164,12 +173,8 @@ export abstract class OpenAIBaseClient< this.sdkInstance = new OpenAI({ dangerouslyAllowBrowser: true, apiKey: apiKeyForSdkInstance, - baseURL: this.getBaseURL(), - defaultHeaders: { - ...this.defaultHeaders(), - ...this.provider.extra_headers, - ...(this.provider.id === 'copilot' ? COPILOT_DEFAULT_HEADERS : {}) - } + baseURL: baseURLForSdkInstance, + defaultHeaders: headersForSdkInstance }) as TSdkInstance } return this.sdkInstance diff --git a/yarn.lock b/yarn.lock index e76a773b90..ee14c1af2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5169,15 +5169,15 @@ __metadata: languageName: node linkType: hard -"@opeoginni/github-copilot-openai-compatible@npm:0.1.19": - version: 0.1.19 - resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.19" +"@opeoginni/github-copilot-openai-compatible@npm:0.1.21": + version: 0.1.21 + resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.21" dependencies: "@ai-sdk/openai": "npm:^2.0.42" "@ai-sdk/openai-compatible": "npm:^1.0.19" "@ai-sdk/provider": "npm:^2.1.0-beta.4" "@ai-sdk/provider-utils": "npm:^3.0.10" - checksum: 10c0/dfb01832d7c704b2eb080fc09d31b07fc26e5ac4e648ce219dc0d80cf044ef3cae504427781ec2ce3c5a2459c9c81d043046a255642108d5b3de0f83f4a9f20a + checksum: 10c0/05b73d935dc7f24123330ade919698b486ac2a25a7d607c1d3789471f782ead4c803ce6ffd3d97b9ca3f1aadaf6b5c1ea52363c9d24b36894fcfc403fda9cef3 languageName: node linkType: hard @@ -9952,7 +9952,7 @@ __metadata: "@opentelemetry/sdk-trace-base": "npm:^2.0.0" "@opentelemetry/sdk-trace-node": "npm:^2.0.0" "@opentelemetry/sdk-trace-web": "npm:^2.0.0" - "@opeoginni/github-copilot-openai-compatible": "npm:0.1.19" + "@opeoginni/github-copilot-openai-compatible": "npm:0.1.21" "@paymoapp/electron-shutdown-handler": "npm:^1.1.2" "@playwright/test": "npm:^1.52.0" "@radix-ui/react-context-menu": "npm:^2.2.16" From 2511113b6244bce20498e2162fd7f9c1409c5140 Mon Sep 17 00:00:00 2001 From: Phantom Date: Sat, 15 Nov 2025 19:09:43 +0800 Subject: [PATCH 03/10] feat: support gpt-5.1 (#11294) * build: update @cherrystudio/openai dependency from v6.5.0 to v6.9.0 * refactor(reasoning): replace 'off' with 'none' for reasoning effort option Update reasoning effort option from 'off' to 'none' across multiple files for consistency Add support for gpt5_1 model with reasoning effort options * fix(openai): handle apply_patch_call and apply_patch_call_output in response conversion Filter and properly handle apply_patch_call and apply_patch_call_output types in OpenAI response conversion. Ensure undefined/null values are handled appropriately and log warnings for missing required fields. * feat(models): add gpt-5.1 model logo and configuration * fix(providers): include cherryin in url context provider check Add SystemProviderIds.cherryin to the list of providers that support URL context to ensure proper functionality * feat(models): add logo images for gpt-5.1 model variants * feat(model): add support for GPT-5.1 series models - Add new model type check for GPT-5.1 series - Update reasoning effort and verbosity checks to include GPT-5.1 - Add logging to provider options builder * feat(models): add gpt5_1_codex model support Add new model type 'gpt5_1_codex' to ThinkModelTypes and configure its reasoning effort levels Update model type detection logic to handle gpt5_1_codex variant --- package.json | 2 +- .../clients/openai/OpenAIResponseAPIClient.ts | 26 +++++++++++++- src/renderer/src/aiCore/utils/options.ts | 4 +++ .../src/assets/images/models/gpt-5.1-chat.png | Bin 0 -> 19329 bytes .../images/models/gpt-5.1-codex-mini.png | Bin 0 -> 21832 bytes .../assets/images/models/gpt-5.1-codex.png | Bin 0 -> 20993 bytes .../src/assets/images/models/gpt-5.1.png | Bin 0 -> 18269 bytes src/renderer/src/config/models/logo.ts | 8 +++++ src/renderer/src/config/models/reasoning.ts | 34 +++++++++++------- src/renderer/src/config/models/utils.ts | 11 ++++-- src/renderer/src/config/providers.ts | 7 ++-- src/renderer/src/hooks/useAssistant.ts | 6 ++-- src/renderer/src/i18n/label.ts | 2 +- .../tools/components/ThinkingButton.tsx | 24 ++++++------- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 19 ++++++++++ src/renderer/src/types/index.ts | 5 ++- yarn.lock | 10 +++--- 18 files changed, 118 insertions(+), 42 deletions(-) create mode 100644 src/renderer/src/assets/images/models/gpt-5.1-chat.png create mode 100644 src/renderer/src/assets/images/models/gpt-5.1-codex-mini.png create mode 100644 src/renderer/src/assets/images/models/gpt-5.1-codex.png create mode 100644 src/renderer/src/assets/images/models/gpt-5.1.png diff --git a/package.json b/package.json index c6b283a9ac..2f8c0c920d 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "@cherrystudio/embedjs-ollama": "^0.1.31", "@cherrystudio/embedjs-openai": "^0.1.31", "@cherrystudio/extension-table-plus": "workspace:^", - "@cherrystudio/openai": "^6.5.0", + "@cherrystudio/openai": "^6.9.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts index 40cace50ea..0f72887196 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts @@ -297,7 +297,31 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient< private convertResponseToMessageContent(response: OpenAI.Responses.Response): ResponseInput { const content: OpenAI.Responses.ResponseInput = [] - content.push(...response.output) + response.output.forEach((item) => { + if (item.type !== 'apply_patch_call' && item.type !== 'apply_patch_call_output') { + content.push(item) + } else if (item.type === 'apply_patch_call') { + if (item.operation !== undefined) { + const applyPatchToolCall: OpenAI.Responses.ResponseInputItem.ApplyPatchCall = { + ...item, + operation: item.operation + } + content.push(applyPatchToolCall) + } else { + logger.warn('Undefined tool call operation for ApplyPatchToolCall.') + } + } else if (item.type === 'apply_patch_call_output') { + if (item.output !== undefined) { + const applyPatchToolCallOutput: OpenAI.Responses.ResponseInputItem.ApplyPatchCallOutput = { + ...item, + output: item.output === null ? undefined : item.output + } + content.push(applyPatchToolCallOutput) + } else { + logger.warn('Undefined tool call operation for ApplyPatchToolCall.') + } + } + }) return content } diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index 88f556438b..2d9f40329d 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -1,4 +1,5 @@ import { baseProviderIdSchema, customProviderIdSchema } from '@cherrystudio/ai-core/provider' +import { loggerService } from '@logger' import { isOpenAIModel, isQwenMTModel, isSupportFlexServiceTierModel } from '@renderer/config/models' import { isSupportServiceTierProvider } from '@renderer/config/providers' import { mapLanguageToQwenMTModel } from '@renderer/config/translate' @@ -26,6 +27,8 @@ import { } from './reasoning' import { getWebSearchParams } from './websearch' +const logger = loggerService.withContext('aiCore.utils.options') + // copy from BaseApiClient.ts const getServiceTier = (model: Model, provider: Provider) => { const serviceTierSetting = provider.serviceTier @@ -70,6 +73,7 @@ export function buildProviderOptions( enableGenerateImage: boolean } ): Record { + logger.debug('buildProviderOptions', { assistant, model, actualProvider, capabilities }) const rawProviderId = getAiSdkProviderId(actualProvider) // 构建 provider 特定的选项 let providerSpecificOptions: Record = {} diff --git a/src/renderer/src/assets/images/models/gpt-5.1-chat.png b/src/renderer/src/assets/images/models/gpt-5.1-chat.png new file mode 100644 index 0000000000000000000000000000000000000000..52ddd61136d78eff68e26519424228eeee8e2e54 GIT binary patch literal 19329 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 A6aWAK literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/images/models/gpt-5.1-codex-mini.png b/src/renderer/src/assets/images/models/gpt-5.1-codex-mini.png new file mode 100644 index 0000000000000000000000000000000000000000..638c3fea9223b3cf7b818aa752a7bec61af12f32 GIT binary patch literal 21832 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 AA^-pY literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/images/models/gpt-5.1.png b/src/renderer/src/assets/images/models/gpt-5.1.png new file mode 100644 index 0000000000000000000000000000000000000000..d7f57f8c58dc561a3d31d501e17bf2ebf777ad09 GIT binary patch literal 18269 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≠Q0Lir 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

la9h2x!z|*!!uVr zPX)QDOMy7~m2fT{y#ut2zCZKT=U#g8PWv(n0K~-yyDw>?+68FLa6LLm$7t|DC%Dx& z+^gTTPFN+>$!6RKvPE;Ram)3p9evv?+2$YiOV3Dl`DQva64$<#|AxUIIX1Cq{^En? zA^2|VxN%Ew@?HV>&QU3;mAqa8`ev>ZX&x8J+zI@M=sWxrNnP*dnpS^~I~P~3eP!{& z7nuQj7ufB-m}KH12SHlL9kit%fqV$AgKk~Lg+7Lza3M%`uXg&)iibuLbM*Co>;RSI ztZuD*V6XOm6e={N64@n~2&Xm5V=fBu^0a61l)TyL(n^OC5854}`4 zsEi3lA>B8;_9O}3PGZr_zlXEFWBkIktzidT$!~BND;@fANoB zkb+I?SHTaOZW2n1!X9iS?3uyuf!o@*qJ0T2i8_;UoZAH_C-hSsan z!R};Lau!{O2}Upcbz=~`Khc2`xB378 z2D?c_K~!tVyc@=_4;kc+9ZR9~PT(D5Nxl-B9bvBezD&3Xydxy}x<|OH=)bfWA44o1 zw41;AJ9qR@^+iu=6DU}j&~i9w1fL*wuPxd^ZCH2jqVw#%;qzsH?Ys`t*X_OZthyQR zAFX$T$4BwvtMRMP;0oV!foJeyqj?rN(+l5YNm)*3Qkikd(0y2okhmE1fza}V;AE02 zaNO9XK(g9<+&6qmdR-3Odk5GZZ2efE4f6rF=QsZJfBKTPwBIv!oy-_(-~tiHrcl5m z)pP<1OM;viJ3Iivq+>kO7u><$lTYzjk>z?vNwUygHtb8_P4ZX0ZF}GE#zz|(J(_)i z-E{=LLgDHpDlvUiXIxfnU)OV^CmfGv%Qio@d*l4rCY|9KJ=c65@k(xN_kxpjXn7;3 zrAPAru-)D7+x>m~fZzCc|N1L>b@2shtVRNQ$e@4`TxcX5zM?Ne>{*Vw20wM)m;4zxK0wIwMSR%w!7h2SlGEr2cr`bw^f zV0Aoxh~DH@*flXQ4S^1rI&zl>)OYY&^!hX2if_piSu?YDzm0^|e>yC#|-u8AG+ zlGNh&y@1CLFPM$4MX!@zdbalSD%rk{ZM}U~Km4gNa39u(WaE0g^hYBmr1A8M<`p5+U|Fi$efB%R2DE1E&T)i#C#R~lB!Bx_XBiSoV@PX@a zGD^ofMs_5oWSTDd@ifsmd|3l6{J|w+rLX+^c+ac#$<>D41MfHH%t5QMbdr32vKy>C z&1Zr5UBh%FybmyF$Uz}=($`156v!!$dVlc3OP~Aw==o@8EPd?u`m0~Qb$WXCH3j*W zCruFLT9QWJ2gyA)77o1c{uz>F^-g@j28*xs=Mh}E)*qoQywHx`47NY$FTG_PMBz;2 zq-Mu5bhH|-&~*fT!v~uXzA4Nqr<(Ku6@L%ehd}{=cWD+zdm7ai+ z;2Ix;jcYoVsvtIa^e85+2VkY!W_TLUO{SO*`BvPjhcPsF)idswKOS>hT1U_YFaRye zxox`$d^5P0eD3UhBS?~S6G-w?03^7_B%Fe%#G8jt_^#a5i>EI>|6jiNrNyI7zgT^JYUyK?vZ;_4rLaOKLEUU=z6U5X>b1@|HJ1mPtR{$oL_!^cXED1+u~-2m+$7%xg!>di_YAV}!|RdyVo=v(^$dOTVDf+t zap{em@ESRf3qb*Fia?iv)6+Y3r!E?a*Ja`KnsCTmRj*ZkdG(model: Model, fn: (model: Model) => T): { idResult: T; nameResult: T } => { @@ -75,7 +79,13 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { if (isOpenAIDeepResearchModel(model)) { return 'openai_deep_research' } - if (isGPT5SeriesModel(model)) { + if (isGPT51SeriesModel(model)) { + if (modelId.includes('codex')) { + thinkingModelType = 'gpt5_1_codex' + } else { + thinkingModelType = 'gpt5_1' + } + } else if (isGPT5SeriesModel(model)) { if (modelId.includes('codex')) { thinkingModelType = 'gpt5_codex' } else { @@ -526,7 +536,7 @@ export function isSupportedReasoningEffortOpenAIModel(model: Model): boolean { modelId.includes('o3') || modelId.includes('o4') || modelId.includes('gpt-oss') || - (isGPT5SeriesModel(model) && !modelId.includes('chat')) + ((isGPT5SeriesModel(model) || isGPT51SeriesModel(model)) && !modelId.includes('chat')) ) } diff --git a/src/renderer/src/config/models/utils.ts b/src/renderer/src/config/models/utils.ts index 743f722528..4197a516b3 100644 --- a/src/renderer/src/config/models/utils.ts +++ b/src/renderer/src/config/models/utils.ts @@ -54,7 +54,7 @@ export function isSupportedFlexServiceTier(model: Model): boolean { export function isSupportVerbosityModel(model: Model): boolean { const modelId = getLowerBaseModelName(model.id) - return isGPT5SeriesModel(model) && !modelId.includes('chat') + return (isGPT5SeriesModel(model) || isGPT51SeriesModel(model)) && !modelId.includes('chat') } export function isOpenAIChatCompletionOnlyModel(model: Model): boolean { @@ -227,12 +227,17 @@ export const isNotSupportSystemMessageModel = (model: Model): boolean => { export const isGPT5SeriesModel = (model: Model) => { const modelId = getLowerBaseModelName(model.id) - return modelId.includes('gpt-5') + return modelId.includes('gpt-5') && !modelId.includes('gpt-5.1') } export const isGPT5SeriesReasoningModel = (model: Model) => { const modelId = getLowerBaseModelName(model.id) - return modelId.includes('gpt-5') && !modelId.includes('chat') + return isGPT5SeriesModel(model) && !modelId.includes('chat') +} + +export const isGPT51SeriesModel = (model: Model) => { + const modelId = getLowerBaseModelName(model.id) + return modelId.includes('gpt-5.1') } export const isGeminiModel = (model: Model) => { diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 965c620ba9..0f2b6cfadd 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -67,7 +67,7 @@ import type { SystemProvider, SystemProviderId } from '@renderer/types' -import { isSystemProvider, OpenAIServiceTiers } from '@renderer/types' +import { isSystemProvider, OpenAIServiceTiers, SystemProviderIds } from '@renderer/types' import { TOKENFLUX_HOST } from './constant' import { glm45FlashModel, qwen38bModel, SYSTEM_MODELS } from './models' @@ -1519,7 +1519,10 @@ const SUPPORT_URL_CONTEXT_PROVIDER_TYPES = [ ] as const satisfies ProviderType[] export const isSupportUrlContextProvider = (provider: Provider) => { - return SUPPORT_URL_CONTEXT_PROVIDER_TYPES.some((type) => type === provider.type) + return ( + SUPPORT_URL_CONTEXT_PROVIDER_TYPES.some((type) => type === provider.type) || + provider.id === SystemProviderIds.cherryin + ) } const SUPPORT_GEMINI_NATIVE_WEB_SEARCH_PROVIDERS = ['gemini', 'vertexai'] as const satisfies SystemProviderId[] diff --git a/src/renderer/src/hooks/useAssistant.ts b/src/renderer/src/hooks/useAssistant.ts index dc0f5c28e3..0571092012 100644 --- a/src/renderer/src/hooks/useAssistant.ts +++ b/src/renderer/src/hooks/useAssistant.ts @@ -123,9 +123,9 @@ export function useAssistant(id: string) { } updateAssistantSettings({ - reasoning_effort: fallbackOption === 'off' ? undefined : fallbackOption, - reasoning_effort_cache: fallbackOption === 'off' ? undefined : fallbackOption, - qwenThinkMode: fallbackOption === 'off' ? undefined : true + reasoning_effort: fallbackOption === 'none' ? undefined : fallbackOption, + reasoning_effort_cache: fallbackOption === 'none' ? undefined : fallbackOption, + qwenThinkMode: fallbackOption === 'none' ? undefined : true }) } else { // 对于支持的选项, 不再更新 cache. diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index f657fd0e08..bd74ecd452 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -311,7 +311,7 @@ export const getHttpMessageLabel = (key: string): string => { } const reasoningEffortOptionsKeyMap: Record = { - off: 'assistants.settings.reasoning_effort.off', + none: 'assistants.settings.reasoning_effort.off', minimal: 'assistants.settings.reasoning_effort.minimal', high: 'assistants.settings.reasoning_effort.high', low: 'assistants.settings.reasoning_effort.low', 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 132c3f2f38..51cbe87c1f 100644 --- a/src/renderer/src/pages/home/Inputbar/tools/components/ThinkingButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/tools/components/ThinkingButton.tsx @@ -36,7 +36,7 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle const { assistant, updateAssistantSettings } = useAssistant(assistantId) const currentReasoningEffort = useMemo(() => { - return assistant.settings?.reasoning_effort || 'off' + return assistant.settings?.reasoning_effort || 'none' }, [assistant.settings?.reasoning_effort]) // 确定当前模型支持的选项类型 @@ -46,21 +46,21 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle const supportedOptions: ThinkingOption[] = useMemo(() => { if (modelType === 'doubao') { if (isDoubaoThinkingAutoModel(model)) { - return ['off', 'auto', 'high'] + return ['none', 'auto', 'high'] } - return ['off', 'high'] + return ['none', 'high'] } return MODEL_SUPPORTED_OPTIONS[modelType] }, [model, modelType]) const onThinkingChange = useCallback( (option?: ThinkingOption) => { - const isEnabled = option !== undefined && option !== 'off' + const isEnabled = option !== undefined && option !== 'none' // 然后更新设置 if (!isEnabled) { updateAssistantSettings({ - reasoning_effort: undefined, - reasoning_effort_cache: undefined, + reasoning_effort: option, + reasoning_effort_cache: option, qwenThinkMode: false }) return @@ -96,10 +96,10 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle })) }, [currentReasoningEffort, supportedOptions, onThinkingChange]) - const isThinkingEnabled = currentReasoningEffort !== undefined && currentReasoningEffort !== 'off' + const isThinkingEnabled = currentReasoningEffort !== undefined && currentReasoningEffort !== 'none' const disableThinking = useCallback(() => { - onThinkingChange('off') + onThinkingChange('none') }, [onThinkingChange]) const openQuickPanel = useCallback(() => { @@ -116,7 +116,7 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle return } - if (isThinkingEnabled && supportedOptions.includes('off')) { + if (isThinkingEnabled && supportedOptions.includes('none')) { disableThinking() return } @@ -146,13 +146,13 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle - + {ThinkingIcon(currentReasoningEffort)} @@ -178,7 +178,7 @@ const ThinkingIcon = (option?: ThinkingOption) => { case 'auto': IconComponent = MdiLightbulbAutoOutline break - case 'off': + case 'none': IconComponent = MdiLightbulbOffOutline break default: diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 4baeeddfde..edc23f7895 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: 174, + version: 175, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 10073de1f3..5a7f3b3a0c 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2819,6 +2819,25 @@ const migrateConfig = { logger.error('migrate 174 error', error as Error) return state } + }, + '175': (state: RootState) => { + try { + state.assistants.assistants.forEach((assistant) => { + // @ts-expect-error removed type 'off' + if (assistant.settings?.reasoning_effort === 'off') { + assistant.settings.reasoning_effort = 'none' + } + // @ts-expect-error removed type 'off' + if (assistant.settings?.reasoning_effort_cache === 'off') { + assistant.settings.reasoning_effort_cache = 'none' + } + }) + logger.info('migrate 175 success') + return state + } catch (error) { + logger.error('migrate 175 error', error as Error) + return state + } } } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index c86e80a157..eb5d2fa1f5 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -83,7 +83,9 @@ const ThinkModelTypes = [ 'o', 'openai_deep_research', 'gpt5', + 'gpt5_1', 'gpt5_codex', + 'gpt5_1_codex', 'grok', 'grok4_fast', 'gemini', @@ -100,7 +102,7 @@ const ThinkModelTypes = [ ] as const export type ReasoningEffortOption = NonNullable | 'auto' -export type ThinkingOption = ReasoningEffortOption | 'off' +export type ThinkingOption = ReasoningEffortOption export type ThinkingModelType = (typeof ThinkModelTypes)[number] export type ThinkingOptionConfig = Record export type ReasoningEffortConfig = Record @@ -111,6 +113,7 @@ export function isThinkModelType(type: string): type is ThinkingModelType { } export const EFFORT_RATIO: EffortRatio = { + none: 0, minimal: 0.05, low: 0.05, medium: 0.5, diff --git a/yarn.lock b/yarn.lock index ee14c1af2a..e7a6944f56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2140,9 +2140,9 @@ __metadata: languageName: unknown linkType: soft -"@cherrystudio/openai@npm:^6.5.0": - version: 6.5.0 - resolution: "@cherrystudio/openai@npm:6.5.0" +"@cherrystudio/openai@npm:^6.9.0": + version: 6.9.0 + resolution: "@cherrystudio/openai@npm:6.9.0" peerDependencies: ws: ^8.18.0 zod: ^3.25 || ^4.0 @@ -2153,7 +2153,7 @@ __metadata: optional: true bin: openai: bin/cli - checksum: 10c0/0f6cafb97aec17037d5ddcccc88e4b4a9c8de77a989a35bab2394b682a1a69e8a9343e8ee5eb8107d5c495970dbf3567642f154c033f7afc3bf078078666a92e + checksum: 10c0/9c51ef33c5b9d08041a115e3d6a8158412a379998a0eae186923d5bdcc808b634c1fef4471a1d499bb8c624b04c075167bc90a1a60a805005c0657ecebbb58d0 languageName: node linkType: hard @@ -9919,7 +9919,7 @@ __metadata: "@cherrystudio/embedjs-ollama": "npm:^0.1.31" "@cherrystudio/embedjs-openai": "npm:^0.1.31" "@cherrystudio/extension-table-plus": "workspace:^" - "@cherrystudio/openai": "npm:^6.5.0" + "@cherrystudio/openai": "npm:^6.9.0" "@dnd-kit/core": "npm:^6.3.1" "@dnd-kit/modifiers": "npm:^9.0.0" "@dnd-kit/sortable": "npm:^10.0.0" From 11fb730b4db1e904366fd135594135fc3222e20c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 16 Nov 2025 10:22:14 +0800 Subject: [PATCH 04/10] fix: add verbosity parameter support for GPT-5 models across legacy and modern AI SDK (#11281) * Initial plan * feat: add verbosity parameter support for GPT-5 models in OpenAIAPIClient Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * fix: ensure gpt-5-pro always uses 'high' verbosity Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * refactor: move verbosity configuration to config/models as suggested Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * refactor: encapsulate verbosity logic in getVerbosity method Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * feat: add support for verbosity and reasoning options for GPT-5 Pro and GPT-5.1 models * fix comment * build: add @ai-sdk/google dependency Add the @ai-sdk/google package to support Google AI SDK integration * build: add @ai-sdk/anthropic dependency * refactor(aiCore): update reasoning params handling for AI providers - Add type imports for provider options - Handle 'none' reasoning effort consistently across providers - Improve type safety by using Pick with provider options - Standardize disabled reasoning config for all providers * fix: adjust none effort ratio from 0 to 0.01 Prevent potential division by zero errors by ensuring none effort ratio has a small positive value * feat(reasoning): add support for GPT-5.1 series models Handle 'none' reasoning effort for GPT-5.1 models and add model type check * Update src/renderer/src/aiCore/utils/reasoning.ts --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> Co-authored-by: suyao Co-authored-by: icarus --- package.json | 2 + .../aiCore/legacy/clients/BaseApiClient.ts | 9 ++- .../legacy/clients/openai/OpenAIApiClient.ts | 8 +++ .../clients/openai/OpenAIResponseAPIClient.ts | 2 +- src/renderer/src/aiCore/utils/options.ts | 25 ++++++- src/renderer/src/aiCore/utils/reasoning.ts | 65 +++++++++++++++---- src/renderer/src/config/models/reasoning.ts | 7 +- src/renderer/src/config/models/utils.ts | 20 ++++++ .../Tabs/components/OpenAISettingsGroup.tsx | 42 ++++++++---- src/renderer/src/types/index.ts | 3 +- src/renderer/src/types/sdk.ts | 4 ++ yarn.lock | 16 ++++- 12 files changed, 171 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 2f8c0c920d..ea14d7d5f5 100644 --- a/package.json +++ b/package.json @@ -108,8 +108,10 @@ "@agentic/searxng": "^7.3.3", "@agentic/tavily": "^7.3.3", "@ai-sdk/amazon-bedrock": "^3.0.53", + "@ai-sdk/anthropic": "^2.0.44", "@ai-sdk/cerebras": "^1.0.31", "@ai-sdk/gateway": "^2.0.9", + "@ai-sdk/google": "^2.0.32", "@ai-sdk/google-vertex": "^3.0.62", "@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch", "@ai-sdk/mistral": "^2.0.23", diff --git a/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts b/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts index 767cad1294..f520162496 100644 --- a/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts @@ -1,5 +1,6 @@ import { loggerService } from '@logger' import { + getModelSupportedVerbosity, isFunctionCallingModel, isNotSupportTemperatureAndTopP, isOpenAIModel, @@ -242,12 +243,18 @@ export abstract class BaseApiClient< return serviceTierSetting } - protected getVerbosity(): OpenAIVerbosity { + protected getVerbosity(model?: Model): OpenAIVerbosity { try { const state = window.store?.getState() const verbosity = state?.settings?.openAI?.verbosity if (verbosity && ['low', 'medium', 'high'].includes(verbosity)) { + // If model is provided, check if the verbosity is supported by the model + if (model) { + const supportedVerbosity = getModelSupportedVerbosity(model) + // Use user's verbosity if supported, otherwise use the first supported option + return supportedVerbosity.includes(verbosity) ? verbosity : supportedVerbosity[0] + } return verbosity } } catch (error) { diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts index 8ff25e356d..ad87331855 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts @@ -35,6 +35,7 @@ import { isSupportedThinkingTokenModel, isSupportedThinkingTokenQwenModel, isSupportedThinkingTokenZhipuModel, + isSupportVerbosityModel, isVisionModel, MODEL_SUPPORTED_REASONING_EFFORT, ZHIPU_RESULT_TOKENS @@ -733,6 +734,13 @@ export class OpenAIAPIClient extends OpenAIBaseClient< ...modalities, // groq 有不同的 service tier 配置,不符合 openai 接口类型 service_tier: this.getServiceTier(model) as OpenAIServiceTier, + ...(isSupportVerbosityModel(model) + ? { + text: { + verbosity: this.getVerbosity(model) + } + } + : {}), ...this.getProviderSpecificParameters(assistant, model), ...reasoningEffort, ...getOpenAIWebSearchParams(model, enableWebSearch), diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts index 0f72887196..cfbfdfd9df 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts @@ -520,7 +520,7 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient< ...(isSupportVerbosityModel(model) ? { text: { - verbosity: this.getVerbosity() + verbosity: this.getVerbosity(model) } } : {}), diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index 2d9f40329d..128a0f5269 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -1,6 +1,12 @@ import { baseProviderIdSchema, customProviderIdSchema } from '@cherrystudio/ai-core/provider' import { loggerService } from '@logger' -import { isOpenAIModel, isQwenMTModel, isSupportFlexServiceTierModel } from '@renderer/config/models' +import { + getModelSupportedVerbosity, + isOpenAIModel, + isQwenMTModel, + isSupportFlexServiceTierModel, + isSupportVerbosityModel +} from '@renderer/config/models' import { isSupportServiceTierProvider } from '@renderer/config/providers' import { mapLanguageToQwenMTModel } from '@renderer/config/translate' import type { Assistant, Model, Provider } from '@renderer/types' @@ -191,6 +197,23 @@ function buildOpenAIProviderOptions( ...reasoningParams } } + + if (isSupportVerbosityModel(model)) { + const state = window.store?.getState() + const userVerbosity = state?.settings?.openAI?.verbosity + + if (userVerbosity && ['low', 'medium', 'high'].includes(userVerbosity)) { + const supportedVerbosity = getModelSupportedVerbosity(model) + // Use user's verbosity if supported, otherwise use the first supported option + const verbosity = supportedVerbosity.includes(userVerbosity) ? userVerbosity : supportedVerbosity[0] + + providerOptions = { + ...providerOptions, + textVerbosity: verbosity + } + } + } + return providerOptions } diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index d0b6f1df25..dfe084179c 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -1,3 +1,7 @@ +import type { BedrockProviderOptions } from '@ai-sdk/amazon-bedrock' +import type { AnthropicProviderOptions } from '@ai-sdk/anthropic' +import type { GoogleGenerativeAIProviderOptions } from '@ai-sdk/google' +import type { XaiProviderOptions } from '@ai-sdk/xai' import { loggerService } from '@logger' import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant' import { @@ -7,6 +11,7 @@ import { isDeepSeekHybridInferenceModel, isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel, + isGPT51SeriesModel, isGrok4FastReasoningModel, isGrokReasoningModel, isOpenAIDeepResearchModel, @@ -56,13 +61,20 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin } const reasoningEffort = assistant?.settings?.reasoning_effort - if (!reasoningEffort) { + // Handle undefined and 'none' reasoningEffort. + // TODO: They should be separated. + if (!reasoningEffort || reasoningEffort === 'none') { // openrouter: use reasoning if (model.provider === SystemProviderIds.openrouter) { // Don't disable reasoning for Gemini models that support thinking tokens if (isSupportedThinkingTokenGeminiModel(model) && !GEMINI_FLASH_MODEL_REGEX.test(model.id)) { return {} } + // 'none' is not an available value for effort for now. + // I think they should resolve this issue soon, so I'll just go ahead and use this value. + if (isGPT51SeriesModel(model) && reasoningEffort === 'none') { + return { reasoning: { effort: 'none' } } + } // Don't disable reasoning for models that require it if ( isGrokReasoningModel(model) || @@ -117,6 +129,13 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin return { thinking: { type: 'disabled' } } } + // Specially for GPT-5.1. Suppose this is a OpenAI Compatible provider + if (isGPT51SeriesModel(model) && reasoningEffort === 'none') { + return { + reasoningEffort: 'none' + } + } + return {} } @@ -371,7 +390,7 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re export function getAnthropicThinkingBudget(assistant: Assistant, model: Model): number { const { maxTokens, reasoning_effort: reasoningEffort } = getAssistantSettings(assistant) - if (reasoningEffort === undefined) { + if (reasoningEffort === undefined || reasoningEffort === 'none') { return 0 } const effortRatio = EFFORT_RATIO[reasoningEffort] @@ -393,14 +412,17 @@ export function getAnthropicThinkingBudget(assistant: Assistant, model: Model): * 获取 Anthropic 推理参数 * 从 AnthropicAPIClient 中提取的逻辑 */ -export function getAnthropicReasoningParams(assistant: Assistant, model: Model): Record { +export function getAnthropicReasoningParams( + assistant: Assistant, + model: Model +): Pick { if (!isReasoningModel(model)) { return {} } const reasoningEffort = assistant?.settings?.reasoning_effort - if (reasoningEffort === undefined) { + if (reasoningEffort === undefined || reasoningEffort === 'none') { return { thinking: { type: 'disabled' @@ -429,7 +451,10 @@ export function getAnthropicReasoningParams(assistant: Assistant, model: Model): * 注意:Gemini/GCP 端点所使用的 thinkingBudget 等参数应该按照驼峰命名法传递 * 而在 Google 官方提供的 OpenAI 兼容端点中则使用蛇形命名法 thinking_budget */ -export function getGeminiReasoningParams(assistant: Assistant, model: Model): Record { +export function getGeminiReasoningParams( + assistant: Assistant, + model: Model +): Pick { if (!isReasoningModel(model)) { return {} } @@ -438,7 +463,7 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re // Gemini 推理参数 if (isSupportedThinkingTokenGeminiModel(model)) { - if (reasoningEffort === undefined) { + if (reasoningEffort === undefined || reasoningEffort === 'none') { return { thinkingConfig: { includeThoughts: false, @@ -478,27 +503,35 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re * @param model - The model being used * @returns XAI-specific reasoning parameters */ -export function getXAIReasoningParams(assistant: Assistant, model: Model): Record { +export function getXAIReasoningParams(assistant: Assistant, model: Model): Pick { if (!isSupportedReasoningEffortGrokModel(model)) { return {} } const { reasoning_effort: reasoningEffort } = getAssistantSettings(assistant) - if (!reasoningEffort) { + if (!reasoningEffort || reasoningEffort === 'none') { return {} } - // For XAI provider Grok models, use reasoningEffort parameter directly - return { - reasoningEffort + switch (reasoningEffort) { + case 'auto': + case 'minimal': + case 'medium': + return { reasoningEffort: 'low' } + case 'low': + case 'high': + return { reasoningEffort } } } /** * Get Bedrock reasoning parameters */ -export function getBedrockReasoningParams(assistant: Assistant, model: Model): Record { +export function getBedrockReasoningParams( + assistant: Assistant, + model: Model +): Pick { if (!isReasoningModel(model)) { return {} } @@ -509,6 +542,14 @@ export function getBedrockReasoningParams(assistant: Assistant, model: Model): R return {} } + if (reasoningEffort === 'none') { + return { + reasoningConfig: { + type: 'disabled' + } + } + } + // Only apply thinking budget for Claude reasoning models if (!isSupportedThinkingTokenClaudeModel(model)) { return {} diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 36b2954875..cc5449f819 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -8,7 +8,7 @@ import type { import { getLowerBaseModelName, isUserSelectedModelType } from '@renderer/utils' import { isEmbeddingModel, isRerankModel } from './embedding' -import { isGPT5SeriesModel, isGPT51SeriesModel } from './utils' +import { isGPT5ProModel, isGPT5SeriesModel, isGPT51SeriesModel } from './utils' import { isTextToImageModel } from './vision' import { GEMINI_FLASH_MODEL_REGEX, isOpenAIDeepResearchModel } from './websearch' @@ -26,6 +26,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = { gpt5_codex: ['low', 'medium', 'high'] as const, gpt5_1: ['none', 'low', 'medium', 'high'] as const, gpt5_1_codex: ['none', 'medium', 'high'] as const, + gpt5pro: ['high'] as const, grok: ['low', 'high'] as const, grok4_fast: ['auto'] as const, gemini: ['low', 'medium', 'high', 'auto'] as const, @@ -47,6 +48,7 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { 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, @@ -90,6 +92,9 @@ const _getThinkModelType = (model: Model): ThinkingModelType => { thinkingModelType = 'gpt5_codex' } else { thinkingModelType = 'gpt5' + if (isGPT5ProModel(model)) { + thinkingModelType = 'gpt5pro' + } } } else if (isSupportedReasoningEffortOpenAIModel(model)) { thinkingModelType = 'o' diff --git a/src/renderer/src/config/models/utils.ts b/src/renderer/src/config/models/utils.ts index 4197a516b3..7fb7c61362 100644 --- a/src/renderer/src/config/models/utils.ts +++ b/src/renderer/src/config/models/utils.ts @@ -240,6 +240,21 @@ export const isGPT51SeriesModel = (model: Model) => { return modelId.includes('gpt-5.1') } +// GPT-5 verbosity configuration +// gpt-5-pro only supports 'high', other GPT-5 models support all levels +export const MODEL_SUPPORTED_VERBOSITY: Record = { + 'gpt-5-pro': ['high'], + default: ['low', 'medium', 'high'] +} + +export const getModelSupportedVerbosity = (model: Model): ('low' | 'medium' | 'high')[] => { + const modelId = getLowerBaseModelName(model.id) + if (modelId.includes('gpt-5-pro')) { + return MODEL_SUPPORTED_VERBOSITY['gpt-5-pro'] + } + return MODEL_SUPPORTED_VERBOSITY.default +} + export const isGeminiModel = (model: Model) => { const modelId = getLowerBaseModelName(model.id) return modelId.includes('gemini') @@ -256,3 +271,8 @@ export const ZHIPU_RESULT_TOKENS = ['<|begin_of_box|>', '<|end_of_box|>'] as con export const agentModelFilter = (model: Model): boolean => { return !isEmbeddingModel(model) && !isRerankModel(model) && !isTextToImageModel(model) } + +export const isGPT5ProModel = (model: Model) => { + const modelId = getLowerBaseModelName(model.id) + return modelId.includes('gpt-5-pro') +} diff --git a/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx b/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx index e752f450b5..2960724183 100644 --- a/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx +++ b/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx @@ -1,5 +1,6 @@ import Selector from '@renderer/components/Selector' import { + getModelSupportedVerbosity, isSupportedReasoningEffortOpenAIModel, isSupportFlexServiceTierModel, isSupportVerbosityModel @@ -80,20 +81,24 @@ const OpenAISettingsGroup: FC = ({ model, providerId, SettingGroup, Setti } ] - const verbosityOptions = [ - { - value: 'low', - label: t('settings.openai.verbosity.low') - }, - { - value: 'medium', - label: t('settings.openai.verbosity.medium') - }, - { - value: 'high', - label: t('settings.openai.verbosity.high') - } - ] + const verbosityOptions = useMemo(() => { + const allOptions = [ + { + value: 'low', + label: t('settings.openai.verbosity.low') + }, + { + value: 'medium', + label: t('settings.openai.verbosity.medium') + }, + { + value: 'high', + label: t('settings.openai.verbosity.high') + } + ] + const supportedVerbosityLevels = getModelSupportedVerbosity(model) + return allOptions.filter((option) => supportedVerbosityLevels.includes(option.value as any)) + }, [model, t]) const serviceTierOptions = useMemo(() => { let baseOptions: { value: ServiceTier; label: string }[] @@ -155,6 +160,15 @@ const OpenAISettingsGroup: FC = ({ model, providerId, SettingGroup, Setti } }, [provider.id, serviceTierMode, serviceTierOptions, setServiceTierMode]) + useEffect(() => { + if (verbosity && !verbosityOptions.some((option) => option.value === verbosity)) { + const supportedVerbosityLevels = getModelSupportedVerbosity(model) + // Default to the highest supported verbosity level + const defaultVerbosity = supportedVerbosityLevels[supportedVerbosityLevels.length - 1] + setVerbosity(defaultVerbosity) + } + }, [model, verbosity, verbosityOptions, setVerbosity]) + if (!isOpenAIReasoning && !isSupportServiceTier && !isSupportVerbosity) { return null } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index eb5d2fa1f5..01d654fdb2 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -86,6 +86,7 @@ const ThinkModelTypes = [ 'gpt5_1', 'gpt5_codex', 'gpt5_1_codex', + 'gpt5pro', 'grok', 'grok4_fast', 'gemini', @@ -113,7 +114,7 @@ export function isThinkModelType(type: string): type is ThinkingModelType { } export const EFFORT_RATIO: EffortRatio = { - none: 0, + none: 0.01, minimal: 0.05, low: 0.05, medium: 0.5, diff --git a/src/renderer/src/types/sdk.ts b/src/renderer/src/types/sdk.ts index 66e6b3627a..8e2af073a7 100644 --- a/src/renderer/src/types/sdk.ts +++ b/src/renderer/src/types/sdk.ts @@ -126,6 +126,10 @@ export type OpenAIExtraBody = { source_lang: 'auto' target_lang: string } + // for gpt-5 series models verbosity control + text?: { + verbosity?: 'low' | 'medium' | 'high' + } } // image is for openrouter. audio is ignored for now export type OpenAIModality = OpenAI.ChatCompletionModality | 'image' diff --git a/yarn.lock b/yarn.lock index e7a6944f56..dc6f25823e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -102,7 +102,7 @@ __metadata: languageName: node linkType: hard -"@ai-sdk/anthropic@npm:2.0.44": +"@ai-sdk/anthropic@npm:2.0.44, @ai-sdk/anthropic@npm:^2.0.44": version: 2.0.44 resolution: "@ai-sdk/anthropic@npm:2.0.44" dependencies: @@ -206,6 +206,18 @@ __metadata: languageName: node linkType: hard +"@ai-sdk/google@npm:^2.0.32": + version: 2.0.32 + resolution: "@ai-sdk/google@npm:2.0.32" + dependencies: + "@ai-sdk/provider": "npm:2.0.0" + "@ai-sdk/provider-utils": "npm:3.0.17" + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + checksum: 10c0/052de16f1f66188e126168c8a9cc903448104528c7e44d6867bbf555c9067b9d6d44a4c4e0e014838156ba39095cb417f1b76363eb65212ca4d005f3651e58d2 + languageName: node + linkType: hard + "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch": version: 2.0.31 resolution: "@ai-sdk/google@patch:@ai-sdk/google@npm%3A2.0.31#~/.yarn/patches/@ai-sdk-google-npm-2.0.31-b0de047210.patch::version=2.0.31&hash=9f3835" @@ -9891,8 +9903,10 @@ __metadata: "@agentic/searxng": "npm:^7.3.3" "@agentic/tavily": "npm:^7.3.3" "@ai-sdk/amazon-bedrock": "npm:^3.0.53" + "@ai-sdk/anthropic": "npm:^2.0.44" "@ai-sdk/cerebras": "npm:^1.0.31" "@ai-sdk/gateway": "npm:^2.0.9" + "@ai-sdk/google": "npm:^2.0.32" "@ai-sdk/google-vertex": "npm:^3.0.62" "@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch" "@ai-sdk/mistral": "npm:^2.0.23" From 556353e9104804eb13fa180acf6e80da097d828b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 10:57:32 +0800 Subject: [PATCH 05/10] docs: Weekly Automated Update: Nov 16, 2025 (#11308) feat(bot): Weekly automated script run Co-authored-by: EurFelux <59059173+EurFelux@users.noreply.github.com> --- src/renderer/src/i18n/locales/zh-cn.json | 2 +- src/renderer/src/i18n/locales/zh-tw.json | 2 +- src/renderer/src/i18n/translate/de-de.json | 2 +- src/renderer/src/i18n/translate/el-gr.json | 2 +- src/renderer/src/i18n/translate/es-es.json | 2 +- src/renderer/src/i18n/translate/fr-fr.json | 2 +- src/renderer/src/i18n/translate/ja-jp.json | 2 +- src/renderer/src/i18n/translate/pt-pt.json | 2 +- src/renderer/src/i18n/translate/ru-ru.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index aaf337b39e..590f805c52 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -4478,7 +4478,7 @@ "confirm": "确认", "forward": "前进", "multiple": "多选", - "noResult": "[to be translated]:No results found", + "noResult": "未找到结果", "page": "翻页", "select": "选择", "title": "快捷菜单" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index fc5516b11f..61c1d86d2e 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -4478,7 +4478,7 @@ "confirm": "確認", "forward": "前進", "multiple": "多選", - "noResult": "[to be translated]:No results found", + "noResult": "未找到結果", "page": "翻頁", "select": "選擇", "title": "快捷選單" diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index fbf7f04956..accc641f0c 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -4478,7 +4478,7 @@ "confirm": "Bestätigen", "forward": "Vorwärts", "multiple": "Mehrfachauswahl", - "noResult": "[to be translated]:No results found", + "noResult": "Keine Ergebnisse gefunden", "page": "Seite umblättern", "select": "Auswählen", "title": "Schnellmenü" diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index ed87590ce0..8a28f1c9ee 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -4478,7 +4478,7 @@ "confirm": "Επιβεβαίωση", "forward": "Μπρος", "multiple": "Πολλαπλή επιλογή", - "noResult": "[to be translated]:No results found", + "noResult": "Δεν βρέθηκαν αποτελέσματα", "page": "Σελίδα", "select": "Επιλογή", "title": "Γρήγορη Πρόσβαση" diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 4cd8f8ad1b..95ab152eeb 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -4478,7 +4478,7 @@ "confirm": "Confirmar", "forward": "Adelante", "multiple": "Selección múltiple", - "noResult": "[to be translated]:No results found", + "noResult": "No se encontraron resultados", "page": "Página", "select": "Seleccionar", "title": "Menú de acceso rápido" diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 0c62faf907..6ffe0b415f 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -4478,7 +4478,7 @@ "confirm": "Подтвердить", "forward": "Вперед", "multiple": "Множественный выбор", - "noResult": "[to be translated]:No results found", + "noResult": "Aucun résultat trouvé", "page": "Перелистнуть страницу", "select": "Выбрать", "title": "Быстрое меню" diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index d67c26c968..00279a90c1 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -4478,7 +4478,7 @@ "confirm": "確認", "forward": "進む", "multiple": "複数選択", - "noResult": "[to be translated]:No results found", + "noResult": "結果が見つかりません", "page": "ページ", "select": "選択", "title": "クイックメニュー" diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 968167906b..33ae8bea25 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -4478,7 +4478,7 @@ "confirm": "Confirmar", "forward": "Avançar", "multiple": "Múltipla Seleção", - "noResult": "[to be translated]:No results found", + "noResult": "Nenhum resultado encontrado", "page": "Página", "select": "Selecionar", "title": "Menu de Atalho" diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 5e60e7247c..5d54eb43e7 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -4478,7 +4478,7 @@ "confirm": "Подтвердить", "forward": "Вперед", "multiple": "Множественный выбор", - "noResult": "[to be translated]:No results found", + "noResult": "Результаты не найдены", "page": "Страница", "select": "Выбрать", "title": "Быстрое меню" From 90b0c8b4a67c33a3d2de1d9ee90ebc77c6cdb103 Mon Sep 17 00:00:00 2001 From: Carlton Date: Sun, 16 Nov 2025 22:10:43 +0800 Subject: [PATCH 06/10] fix: resolve "no such file" error when processing non-English filenames in open-mineru (#11315) --- .../knowledge/preprocess/OpenMineruPreprocessProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts b/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts index 9a3bca65a1..377eafa70c 100644 --- a/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts +++ b/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts @@ -72,8 +72,8 @@ export default class OpenMineruPreprocessProvider extends BasePreprocessProvider // Find the main file after extraction let finalPath = '' let finalName = file.origin_name.replace('.pdf', '.md') - // Find the corresponding folder by file name - outputPath = path.join(outputPath, `${file.origin_name.replace('.pdf', '')}`) + // Find the corresponding folder by file id + outputPath = path.join(outputPath, file.id) try { const files = fs.readdirSync(outputPath) @@ -125,7 +125,7 @@ export default class OpenMineruPreprocessProvider extends BasePreprocessProvider formData.append('return_md', 'true') formData.append('response_format_zip', 'true') formData.append('files', fileBuffer, { - filename: file.origin_name + filename: file.name }) while (retries < maxRetries) { From 7fd4837a47b8f158b119bd6daf2f60e39a4b1fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A7=91=E5=9B=BF=E8=84=91=E8=A2=8B?= <70054568+eeee0717@users.noreply.github.com> Date: Mon, 17 Nov 2025 00:02:15 +0800 Subject: [PATCH 07/10] fix: mineru validate pdf error and 403 error (#11312) * fix: validate pdf error * fix: net fetch error * fix: mineru 403 error * chore: change comment to english * fix: format --- .../preprocess/MineruPreprocessProvider.ts | 109 ++++++++++++------ .../OpenMineruPreprocessProvider.ts | 45 ++++++-- 2 files changed, 109 insertions(+), 45 deletions(-) diff --git a/src/main/knowledge/preprocess/MineruPreprocessProvider.ts b/src/main/knowledge/preprocess/MineruPreprocessProvider.ts index 7a5362a116..80aec40622 100644 --- a/src/main/knowledge/preprocess/MineruPreprocessProvider.ts +++ b/src/main/knowledge/preprocess/MineruPreprocessProvider.ts @@ -21,6 +21,7 @@ type ApiResponse = { type BatchUploadResponse = { batch_id: string file_urls: string[] + headers?: Record[] } type ExtractProgress = { @@ -55,7 +56,7 @@ type QuotaResponse = { export default class MineruPreprocessProvider extends BasePreprocessProvider { constructor(provider: PreprocessProvider, userId?: string) { super(provider, userId) - // todo:免费期结束后删除 + // TODO: remove after free period ends this.provider.apiKey = this.provider.apiKey || import.meta.env.MAIN_VITE_MINERU_API_KEY } @@ -68,21 +69,21 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { logger.info(`MinerU preprocess processing started: ${filePath}`) await this.validateFile(filePath) - // 1. 获取上传URL并上传文件 + // 1. Get upload URL and upload file const batchId = await this.uploadFile(file) logger.info(`MinerU file upload completed: batch_id=${batchId}`) - // 2. 等待处理完成并获取结果 + // 2. Wait for completion and fetch results const extractResult = await this.waitForCompletion(sourceId, batchId, file.origin_name) logger.info(`MinerU processing completed for batch: ${batchId}`) - // 3. 下载并解压文件 + // 3. Download and extract output const { path: outputPath } = await this.downloadAndExtractFile(extractResult.full_zip_url!, file) // 4. check quota const quota = await this.checkQuota() - // 5. 创建处理后的文件信息 + // 5. Create processed file metadata return { processedFile: this.createProcessedFileInfo(file, outputPath), quota @@ -115,23 +116,48 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { } private async validateFile(filePath: string): Promise { + // Phase 1: check file size (without loading into memory) + logger.info(`Validating PDF file: ${filePath}`) + const stats = await fs.promises.stat(filePath) + const fileSizeBytes = stats.size + + // Ensure file size is under 200MB + if (fileSizeBytes >= 200 * 1024 * 1024) { + const fileSizeMB = Math.round(fileSizeBytes / (1024 * 1024)) + throw new Error(`PDF file size (${fileSizeMB}MB) exceeds the limit of 200MB`) + } + + // Phase 2: check page count (requires reading file with error handling) const pdfBuffer = await fs.promises.readFile(filePath) - const doc = await this.readPdf(pdfBuffer) + try { + const doc = await this.readPdf(pdfBuffer) - // 文件页数小于600页 - if (doc.numPages >= 600) { - throw new Error(`PDF page count (${doc.numPages}) exceeds the limit of 600 pages`) - } - // 文件大小小于200MB - if (pdfBuffer.length >= 200 * 1024 * 1024) { - const fileSizeMB = Math.round(pdfBuffer.length / (1024 * 1024)) - throw new Error(`PDF file size (${fileSizeMB}MB) exceeds the limit of 200MB`) + // Ensure page count is under 600 pages + if (doc.numPages >= 600) { + throw new Error(`PDF page count (${doc.numPages}) exceeds the limit of 600 pages`) + } + + logger.info(`PDF validation passed: ${doc.numPages} pages, ${Math.round(fileSizeBytes / (1024 * 1024))}MB`) + } catch (error: any) { + // If the page limit is exceeded, rethrow immediately + if (error.message.includes('exceeds the limit')) { + throw error + } + + // If PDF parsing fails, log a detailed warning but continue processing + logger.warn( + `Failed to parse PDF structure (file may be corrupted or use non-standard format). ` + + `Skipping page count validation. Will attempt to process with MinerU API. ` + + `Error details: ${error.message}. ` + + `Suggestion: If processing fails, try repairing the PDF using tools like Adobe Acrobat or online PDF repair services.` + ) + // Do not throw; continue processing } } private createProcessedFileInfo(file: FileMetadata, outputPath: string): FileMetadata { - // 查找解压后的主要文件 + // Locate the main extracted file let finalPath = '' let finalName = file.origin_name.replace('.pdf', '.md') @@ -143,14 +169,14 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { const originalMdPath = path.join(outputPath, mdFile) const newMdPath = path.join(outputPath, finalName) - // 重命名文件为原始文件名 + // Rename the file to match the original name try { fs.renameSync(originalMdPath, newMdPath) finalPath = newMdPath logger.info(`Renamed markdown file from ${mdFile} to ${finalName}`) } catch (renameError) { logger.warn(`Failed to rename file ${mdFile} to ${finalName}: ${renameError}`) - // 如果重命名失败,使用原文件 + // If renaming fails, fall back to the original file finalPath = originalMdPath finalName = mdFile } @@ -178,7 +204,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { logger.info(`Downloading MinerU result to: ${zipPath}`) try { - // 下载ZIP文件 + // Download the ZIP file const response = await net.fetch(zipUrl, { method: 'GET' }) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) @@ -187,17 +213,17 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { fs.writeFileSync(zipPath, Buffer.from(arrayBuffer)) logger.info(`Downloaded ZIP file: ${zipPath}`) - // 确保提取目录存在 + // Ensure the extraction directory exists if (!fs.existsSync(extractPath)) { fs.mkdirSync(extractPath, { recursive: true }) } - // 解压文件 + // Extract the ZIP contents const zip = new AdmZip(zipPath) zip.extractAllTo(extractPath, true) logger.info(`Extracted files to: ${extractPath}`) - // 删除临时ZIP文件 + // Remove the temporary ZIP file fs.unlinkSync(zipPath) return { path: extractPath } @@ -209,11 +235,11 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { private async uploadFile(file: FileMetadata): Promise { try { - // 步骤1: 获取上传URL - const { batchId, fileUrls } = await this.getBatchUploadUrls(file) - // 步骤2: 上传文件到获取的URL + // Step 1: obtain the upload URL + const { batchId, fileUrls, uploadHeaders } = await this.getBatchUploadUrls(file) + // Step 2: upload the file to the obtained URL const filePath = fileStorage.getFilePathById(file) - await this.putFileToUrl(filePath, fileUrls[0]) + await this.putFileToUrl(filePath, fileUrls[0], file.origin_name, uploadHeaders?.[0]) logger.info(`File uploaded successfully: ${filePath}`, { batchId, fileUrls }) return batchId @@ -223,7 +249,9 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { } } - private async getBatchUploadUrls(file: FileMetadata): Promise<{ batchId: string; fileUrls: string[] }> { + private async getBatchUploadUrls( + file: FileMetadata + ): Promise<{ batchId: string; fileUrls: string[]; uploadHeaders?: Record[] }> { const endpoint = `${this.provider.apiHost}/api/v4/file-urls/batch` const payload = { @@ -254,10 +282,11 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { if (response.ok) { const data: ApiResponse = await response.json() if (data.code === 0 && data.data) { - const { batch_id, file_urls } = data.data + const { batch_id, file_urls, headers: uploadHeaders } = data.data return { batchId: batch_id, - fileUrls: file_urls + fileUrls: file_urls, + uploadHeaders } } else { throw new Error(`API returned error: ${data.msg || JSON.stringify(data)}`) @@ -271,18 +300,28 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { } } - private async putFileToUrl(filePath: string, uploadUrl: string): Promise { + private async putFileToUrl( + filePath: string, + uploadUrl: string, + fileName?: string, + headers?: Record + ): Promise { try { const fileBuffer = await fs.promises.readFile(filePath) + const fileSize = fileBuffer.byteLength + const displayName = fileName ?? path.basename(filePath) + + logger.info(`Uploading file to MinerU OSS: ${displayName} (${fileSize} bytes)`) // https://mineru.net/apiManage/docs const response = await net.fetch(uploadUrl, { method: 'PUT', - body: fileBuffer + headers, + body: new Uint8Array(fileBuffer) }) if (!response.ok) { - // 克隆 response 以避免消费 body stream + // Clone the response to avoid consuming the body stream const responseClone = response.clone() try { @@ -353,20 +392,20 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { try { const result = await this.getExtractResults(batchId) - // 查找对应文件的处理结果 + // Find the corresponding file result const fileResult = result.extract_result.find((item) => item.file_name === fileName) if (!fileResult) { throw new Error(`File ${fileName} not found in batch results`) } - // 检查处理状态 + // Check the processing state if (fileResult.state === 'done' && fileResult.full_zip_url) { logger.info(`Processing completed for file: ${fileName}`) return fileResult } else if (fileResult.state === 'failed') { throw new Error(`Processing failed for file: ${fileName}, error: ${fileResult.err_msg}`) } else if (fileResult.state === 'running') { - // 发送进度更新 + // Send progress updates if (fileResult.extract_progress) { const progress = Math.round( (fileResult.extract_progress.extracted_pages / fileResult.extract_progress.total_pages) * 100 @@ -374,7 +413,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider { await this.sendPreprocessProgress(sourceId, progress) logger.info(`File ${fileName} processing progress: ${progress}%`) } else { - // 如果没有具体进度信息,发送一个通用进度 + // If no detailed progress information is available, send a generic update await this.sendPreprocessProgress(sourceId, 50) logger.info(`File ${fileName} is still processing...`) } diff --git a/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts b/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts index 377eafa70c..f322fbac35 100644 --- a/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts +++ b/src/main/knowledge/preprocess/OpenMineruPreprocessProvider.ts @@ -53,18 +53,43 @@ export default class OpenMineruPreprocessProvider extends BasePreprocessProvider } private async validateFile(filePath: string): Promise { + // 第一阶段:检查文件大小(无需读取文件到内存) + logger.info(`Validating PDF file: ${filePath}`) + const stats = await fs.promises.stat(filePath) + const fileSizeBytes = stats.size + + // File size must be less than 200MB + if (fileSizeBytes >= 200 * 1024 * 1024) { + const fileSizeMB = Math.round(fileSizeBytes / (1024 * 1024)) + throw new Error(`PDF file size (${fileSizeMB}MB) exceeds the limit of 200MB`) + } + + // 第二阶段:检查页数(需要读取文件,带错误处理) const pdfBuffer = await fs.promises.readFile(filePath) - const doc = await this.readPdf(pdfBuffer) + try { + const doc = await this.readPdf(pdfBuffer) - // File page count must be less than 600 pages - if (doc.numPages >= 600) { - throw new Error(`PDF page count (${doc.numPages}) exceeds the limit of 600 pages`) - } - // File size must be less than 200MB - if (pdfBuffer.length >= 200 * 1024 * 1024) { - const fileSizeMB = Math.round(pdfBuffer.length / (1024 * 1024)) - throw new Error(`PDF file size (${fileSizeMB}MB) exceeds the limit of 200MB`) + // File page count must be less than 600 pages + if (doc.numPages >= 600) { + throw new Error(`PDF page count (${doc.numPages}) exceeds the limit of 600 pages`) + } + + logger.info(`PDF validation passed: ${doc.numPages} pages, ${Math.round(fileSizeBytes / (1024 * 1024))}MB`) + } catch (error: any) { + // 如果是页数超限错误,直接抛出 + if (error.message.includes('exceeds the limit')) { + throw error + } + + // PDF 解析失败,记录详细警告但允许继续处理 + logger.warn( + `Failed to parse PDF structure (file may be corrupted or use non-standard format). ` + + `Skipping page count validation. Will attempt to process with MinerU API. ` + + `Error details: ${error.message}. ` + + `Suggestion: If processing fails, try repairing the PDF using tools like Adobe Acrobat or online PDF repair services.` + ) + // 不抛出错误,允许继续处理 } } @@ -139,7 +164,7 @@ export default class OpenMineruPreprocessProvider extends BasePreprocessProvider ...(this.provider.apiKey ? { Authorization: `Bearer ${this.provider.apiKey}` } : {}), ...formData.getHeaders() }, - body: formData.getBuffer() + body: new Uint8Array(formData.getBuffer()) }) if (!response.ok) { From 31eec403f74750b837b8d5e527a92c5a4759ce76 Mon Sep 17 00:00:00 2001 From: SuYao Date: Mon, 17 Nov 2025 10:53:47 +0800 Subject: [PATCH 08/10] fix: url context and web search capability (#11306) * fix: enhance support for interleaved thinking and model compatibility * fix: type --- src/renderer/src/aiCore/prepareParams/header.ts | 13 +++++++++++++ .../src/aiCore/prepareParams/parameterBuilder.ts | 15 ++++++++++++++- src/renderer/src/config/models/websearch.ts | 2 +- src/renderer/src/config/providers.ts | 4 ++++ .../pages/home/Inputbar/tools/urlContextTool.tsx | 5 ++--- 5 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 src/renderer/src/aiCore/prepareParams/header.ts diff --git a/src/renderer/src/aiCore/prepareParams/header.ts b/src/renderer/src/aiCore/prepareParams/header.ts new file mode 100644 index 0000000000..8c53cbce53 --- /dev/null +++ b/src/renderer/src/aiCore/prepareParams/header.ts @@ -0,0 +1,13 @@ +import { isClaude45ReasoningModel } from '@renderer/config/models' +import type { Assistant, Model } from '@renderer/types' +import { isToolUseModeFunction } from '@renderer/utils/assistant' + +const INTERLEAVED_THINKING_HEADER = 'interleaved-thinking-2025-05-14' + +export function addAnthropicHeaders(assistant: Assistant, model: Model): string[] { + const anthropicHeaders: string[] = [] + if (isClaude45ReasoningModel(model) && isToolUseModeFunction(assistant)) { + anthropicHeaders.push(INTERLEAVED_THINKING_HEADER) + } + return anthropicHeaders +} diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index 397c481cf3..e865f9f15f 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -7,10 +7,12 @@ import { anthropic } from '@ai-sdk/anthropic' import { google } from '@ai-sdk/google' import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge' import { vertex } from '@ai-sdk/google-vertex/edge' +import { combineHeaders } from '@ai-sdk/provider-utils' import type { WebSearchPluginConfig } from '@cherrystudio/ai-core/built-in/plugins' import { isBaseProvider } from '@cherrystudio/ai-core/core/providers/schemas' import { loggerService } from '@logger' import { + isAnthropicModel, isGenerateImageModel, isOpenRouterBuiltInWebSearchModel, isReasoningModel, @@ -19,6 +21,8 @@ import { isSupportedThinkingTokenModel, isWebSearchModel } from '@renderer/config/models' +import { isAwsBedrockProvider } from '@renderer/config/providers' +import { isVertexProvider } from '@renderer/hooks/useVertexAI' import { getAssistantSettings, getDefaultModel } from '@renderer/services/AssistantService' import store from '@renderer/store' import type { CherryWebSearchConfig } from '@renderer/store/websearch' @@ -34,6 +38,7 @@ import { setupToolsConfig } from '../utils/mcp' import { buildProviderOptions } from '../utils/options' import { getAnthropicThinkingBudget } from '../utils/reasoning' import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch' +import { addAnthropicHeaders } from './header' import { supportsTopP } from './modelCapabilities' import { getTemperature, getTopP } from './modelParameters' @@ -172,13 +177,21 @@ export async function buildStreamTextParams( } } + let headers: Record = options.requestOptions?.headers ?? {} + + // https://docs.claude.com/en/docs/build-with-claude/extended-thinking#interleaved-thinking + if (!isVertexProvider(provider) && !isAwsBedrockProvider(provider) && isAnthropicModel(model)) { + const newBetaHeaders = { 'anthropic-beta': addAnthropicHeaders(assistant, model).join(',') } + headers = combineHeaders(headers, newBetaHeaders) + } + // 构建基础参数 const params: StreamTextParams = { messages: sdkMessages, maxOutputTokens: maxTokens, temperature: getTemperature(assistant, model), abortSignal: options.requestOptions?.signal, - headers: options.requestOptions?.headers, + headers, providerOptions, stopWhen: stepCountIs(20), maxRetries: 0 diff --git a/src/renderer/src/config/models/websearch.ts b/src/renderer/src/config/models/websearch.ts index 418c81133d..f012be7cfa 100644 --- a/src/renderer/src/config/models/websearch.ts +++ b/src/renderer/src/config/models/websearch.ts @@ -70,7 +70,7 @@ export function isWebSearchModel(model: Model): boolean { // bedrock和vertex不支持 if ( isAnthropicModel(model) && - (provider.id === SystemProviderIds['aws-bedrock'] || provider.id === SystemProviderIds.vertexai) + !(provider.id === SystemProviderIds['aws-bedrock'] || provider.id === SystemProviderIds.vertexai) ) { return CLAUDE_SUPPORTED_WEBSEARCH_REGEX.test(modelId) } diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 0f2b6cfadd..1e25a550f1 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -1573,6 +1573,10 @@ export function isAIGatewayProvider(provider: Provider): boolean { return provider.type === 'ai-gateway' } +export function isAwsBedrockProvider(provider: Provider): boolean { + return provider.type === 'aws-bedrock' +} + const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot', 'perplexity'] as const satisfies SystemProviderId[] export const isSupportAPIVersionProvider = (provider: Provider) => { diff --git a/src/renderer/src/pages/home/Inputbar/tools/urlContextTool.tsx b/src/renderer/src/pages/home/Inputbar/tools/urlContextTool.tsx index 037d43e19f..bb38e67b0e 100644 --- a/src/renderer/src/pages/home/Inputbar/tools/urlContextTool.tsx +++ b/src/renderer/src/pages/home/Inputbar/tools/urlContextTool.tsx @@ -1,4 +1,4 @@ -import { isGeminiModel } from '@renderer/config/models' +import { isAnthropicModel, isGeminiModel } from '@renderer/config/models' import { isSupportUrlContextProvider } from '@renderer/config/providers' import { defineTool, registerTool, TopicType } from '@renderer/pages/home/Inputbar/types' import { getProviderByModel } from '@renderer/services/AssistantService' @@ -10,9 +10,8 @@ const urlContextTool = defineTool({ label: (t) => t('chat.input.url_context'), visibleInScopes: [TopicType.Chat], condition: ({ model }) => { - if (!isGeminiModel(model)) return false const provider = getProviderByModel(model) - return !!provider && isSupportUrlContextProvider(provider) + return !!provider && isSupportUrlContextProvider(provider) && (isGeminiModel(model) || isAnthropicModel(model)) }, render: ({ assistant }) => }) From 139950e1931b00b7b59dd2e3f25724e6ef691344 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 17 Nov 2025 11:51:04 +0800 Subject: [PATCH 09/10] fix(i18n): add input placeholder translations for multiple languages (#11320) feat(i18n): add input placeholder translations for multiple languages - Introduced a new placeholder for the input field in various language files, providing guidance on message entry and command selection. - Updated English, Chinese (Simplified and Traditional), German, Greek, Spanish, French, Japanese, Portuguese, and Russian translations to include the new input placeholder text. - Adjusted the reference in the AgentSessionInputbar component to use the new translation key for consistency. --- src/renderer/src/i18n/locales/en-us.json | 3 +++ src/renderer/src/i18n/locales/zh-cn.json | 3 +++ src/renderer/src/i18n/locales/zh-tw.json | 3 +++ src/renderer/src/i18n/translate/de-de.json | 3 +++ src/renderer/src/i18n/translate/el-gr.json | 3 +++ src/renderer/src/i18n/translate/es-es.json | 3 +++ src/renderer/src/i18n/translate/fr-fr.json | 3 +++ src/renderer/src/i18n/translate/ja-jp.json | 3 +++ src/renderer/src/i18n/translate/pt-pt.json | 3 +++ src/renderer/src/i18n/translate/ru-ru.json | 3 +++ src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx | 2 +- 11 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 47ebef1ffb..c1c0dc2620 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -27,6 +27,9 @@ "null_id": "Agent ID is null." } }, + "input": { + "placeholder": "Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Failed to list agents." diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 590f805c52..2a1ee09688 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -27,6 +27,9 @@ "null_id": "智能体 ID 为空。" } }, + "input": { + "placeholder": "在这里输入消息,按 {{key}} 发送 - @ 选择路径, / 选择命令" + }, "list": { "error": { "failed": "获取智能体列表失败" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 61c1d86d2e..f5a0264875 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -27,6 +27,9 @@ "null_id": "代理程式 ID 為空。" } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "無法列出代理程式。" diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index accc641f0c..597259d919 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -27,6 +27,9 @@ "null_id": "Agent ID ist leer." } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Agent-Liste abrufen fehlgeschlagen" diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 8a28f1c9ee..d7cf3b8cff 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -27,6 +27,9 @@ "null_id": "Το ID του πράκτορα είναι null." } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Αποτυχία καταχώρησης πρακτόρων." diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 95ab152eeb..7c857e9efc 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -27,6 +27,9 @@ "null_id": "El ID del agente es nulo." } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Error al listar agentes." diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 6ffe0b415f..a9025e404e 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -27,6 +27,9 @@ "null_id": "L'ID de l'agent est nul." } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Échec de la liste des agents." diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 00279a90c1..1559b450da 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -27,6 +27,9 @@ "null_id": "エージェント ID が null です。" } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "エージェントの一覧取得に失敗しました。" diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 33ae8bea25..eb55076143 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -27,6 +27,9 @@ "null_id": "O ID do agente é nulo." } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Falha ao listar agentes." diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 5d54eb43e7..c9e4fc6fc4 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -27,6 +27,9 @@ "null_id": "ID агента равен null." } }, + "input": { + "placeholder": "[to be translated]:Enter your message here, send with {{key}} - @ select path, / select command" + }, "list": { "error": { "failed": "Не удалось получить список агентов." diff --git a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx index f2aa12d527..722697be78 100644 --- a/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/AgentSessionInputbar.tsx @@ -470,7 +470,7 @@ const AgentSessionInputbarInner: FC = ({ assistant, agentId, session ) const placeholderText = useMemo( () => - t('chat.input.placeholder', { + t('agent.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) }), [sendMessageShortcut, t] From 096c36caf86974e7bb41cda5da19652d20c7d3aa Mon Sep 17 00:00:00 2001 From: defi-failure <159208748+defi-failure@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:01:27 +0800 Subject: [PATCH 10/10] fix: improve todo tool status icon visibility and colors (#11323) --- .../Tools/MessageAgentTools/TodoWriteTool.tsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/TodoWriteTool.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/TodoWriteTool.tsx index b11f73793b..2796e44fc9 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/TodoWriteTool.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/TodoWriteTool.tsx @@ -1,4 +1,3 @@ -import { cn } from '@renderer/utils' import type { CollapseProps } from 'antd' import { Card } from 'antd' import { CheckCircle, Circle, Clock, ListTodo } from 'lucide-react' @@ -11,23 +10,27 @@ const getStatusConfig = (status: TodoItem['status']) => { switch (status) { case 'completed': return { - color: 'success' as const, - icon: + color: 'var(--color-status-success)', + opacity: 0.6, + icon: } case 'in_progress': return { - color: 'primary' as const, - icon: + color: 'var(--color-primary)', + opacity: 0.9, + icon: } case 'pending': return { - color: 'default' as const, - icon: + color: 'var(--color-border)', + opacity: 0.4, + icon: } default: return { - color: 'default' as const, - icon: + color: 'var(--color-border)', + opacity: 0.4, + icon: } } } @@ -64,10 +67,8 @@ export function TodoWriteTool({

+ className="flex items-center justify-center rounded-full border p-1" + style={{ backgroundColor: statusConfig.color, opacity: statusConfig.opacity }}> {statusConfig.icon}