diff --git a/package.json b/package.json index 3a302a5452..3dd79c341a 100644 --- a/package.json +++ b/package.json @@ -111,8 +111,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", @@ -138,7 +140,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", "@cherrystudio/ui": "workspace:*", "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", @@ -168,7 +170,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/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index caeb1ae8db..d7ffd76105 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 { diff --git a/src/main/knowledge/preprocess/MineruPreprocessProvider.ts b/src/main/knowledge/preprocess/MineruPreprocessProvider.ts index 146e971713..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 as unknown as BodyInit + 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 9bfb08920c..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.` + ) + // 不抛出错误,允许继续处理 } } @@ -72,8 +97,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 +150,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) { diff --git a/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts b/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts index 0707e9bd40..90539db3b8 100644 --- a/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/BaseApiClient.ts @@ -1,6 +1,7 @@ import { cacheService } from '@data/CacheService' 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/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/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIResponseAPIClient.ts index 40cace50ea..cfbfdfd9df 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 } @@ -496,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/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/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index 88f556438b..128a0f5269 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -1,5 +1,12 @@ import { baseProviderIdSchema, customProviderIdSchema } from '@cherrystudio/ai-core/provider' -import { isOpenAIModel, isQwenMTModel, isSupportFlexServiceTierModel } from '@renderer/config/models' +import { loggerService } from '@logger' +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' @@ -26,6 +33,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 +79,7 @@ export function buildProviderOptions( enableGenerateImage: boolean } ): Record { + logger.debug('buildProviderOptions', { assistant, model, actualProvider, capabilities }) const rawProviderId = getAiSdkProviderId(actualProvider) // 构建 provider 特定的选项 let providerSpecificOptions: Record = {} @@ -187,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/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 0000000000..52ddd61136 Binary files /dev/null and b/src/renderer/src/assets/images/models/gpt-5.1-chat.png differ 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 0000000000..638c3fea92 Binary files /dev/null and b/src/renderer/src/assets/images/models/gpt-5.1-codex-mini.png differ diff --git a/src/renderer/src/assets/images/models/gpt-5.1-codex.png b/src/renderer/src/assets/images/models/gpt-5.1-codex.png new file mode 100644 index 0000000000..ec12a5e901 Binary files /dev/null and b/src/renderer/src/assets/images/models/gpt-5.1-codex.png differ 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 0000000000..d7f57f8c58 Binary files /dev/null and b/src/renderer/src/assets/images/models/gpt-5.1.png differ diff --git a/src/renderer/src/config/models/logo.ts b/src/renderer/src/config/models/logo.ts index ce592ad466..77f4f5fb9d 100644 --- a/src/renderer/src/config/models/logo.ts +++ b/src/renderer/src/config/models/logo.ts @@ -59,6 +59,10 @@ import { } from '@renderer/assets/images/models/gpt_dark.png' import ChatGPTImageModelLogo from '@renderer/assets/images/models/gpt_image_1.png' import ChatGPTo1ModelLogo from '@renderer/assets/images/models/gpt_o1.png' +import GPT51ModelLogo from '@renderer/assets/images/models/gpt-5.1.png' +import GPT51ChatModelLogo from '@renderer/assets/images/models/gpt-5.1-chat.png' +import GPT51CodexModelLogo from '@renderer/assets/images/models/gpt-5.1-codex.png' +import GPT51CodexMiniModelLogo from '@renderer/assets/images/models/gpt-5.1-codex-mini.png' import GPT5ModelLogo from '@renderer/assets/images/models/gpt-5.png' import GPT5ChatModelLogo from '@renderer/assets/images/models/gpt-5-chat.png' import GPT5CodexModelLogo from '@renderer/assets/images/models/gpt-5-codex.png' @@ -182,6 +186,10 @@ export function getModelLogoById(modelId: string): string | undefined { 'gpt-5-nano': GPT5NanoModelLogo, 'gpt-5-chat': GPT5ChatModelLogo, 'gpt-5-codex': GPT5CodexModelLogo, + 'gpt-5.1-codex-mini': GPT51CodexMiniModelLogo, + 'gpt-5.1-codex': GPT51CodexModelLogo, + 'gpt-5.1-chat': GPT51ChatModelLogo, + 'gpt-5.1': GPT51ModelLogo, 'gpt-5': GPT5ModelLogo, gpts: isLight ? ChatGPT4ModelLogo : ChatGPT4ModelLogoDark, 'gpt-oss(?:-[\\w-]+)': isLight ? ChatGptModelLogo : ChatGptModelLogoDark, diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 3a4d97e592..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 } from './utils' +import { isGPT5ProModel, isGPT5SeriesModel, isGPT51SeriesModel } from './utils' import { isTextToImageModel } from './vision' import { GEMINI_FLASH_MODEL_REGEX, isOpenAIDeepResearchModel } from './websearch' @@ -24,6 +24,9 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = { openai_deep_research: ['medium'] as const, gpt5: ['minimal', 'low', 'medium', 'high'] as const, 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, @@ -41,24 +44,27 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = { // 模型类型到支持选项的映射表 export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { - default: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.default] as const, + default: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.default] as const, o: MODEL_SUPPORTED_REASONING_EFFORT.o, openai_deep_research: MODEL_SUPPORTED_REASONING_EFFORT.openai_deep_research, gpt5: [...MODEL_SUPPORTED_REASONING_EFFORT.gpt5] as const, + gpt5pro: MODEL_SUPPORTED_REASONING_EFFORT.gpt5pro, gpt5_codex: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_codex, + gpt5_1: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1, + gpt5_1_codex: MODEL_SUPPORTED_REASONING_EFFORT.gpt5_1_codex, grok: MODEL_SUPPORTED_REASONING_EFFORT.grok, - grok4_fast: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const, - gemini: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const, + grok4_fast: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.grok4_fast] as const, + gemini: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.gemini] as const, gemini_pro: MODEL_SUPPORTED_REASONING_EFFORT.gemini_pro, - qwen: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const, + qwen: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.qwen] as const, qwen_thinking: MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking, - doubao: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, - doubao_no_auto: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, + doubao: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, + doubao_no_auto: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, doubao_after_251015: MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015, - hunyuan: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, - zhipu: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, + hunyuan: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, + zhipu: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, perplexity: MODEL_SUPPORTED_REASONING_EFFORT.perplexity, - deepseek_hybrid: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.deepseek_hybrid] as const + deepseek_hybrid: ['none', ...MODEL_SUPPORTED_REASONING_EFFORT.deepseek_hybrid] as const } as const const withModelIdAndNameAsId = (model: Model, fn: (model: Model) => T): { idResult: T; nameResult: T } => { @@ -75,11 +81,20 @@ 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 { thinkingModelType = 'gpt5' + if (isGPT5ProModel(model)) { + thinkingModelType = 'gpt5pro' + } } } else if (isSupportedReasoningEffortOpenAIModel(model)) { thinkingModelType = 'o' @@ -526,7 +541,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 9cbacfa683..b9fb698320 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,32 @@ 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') +} + +// 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) => { @@ -251,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/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 965c620ba9..1e25a550f1 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[] @@ -1570,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/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/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 aaf337b39e..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": "获取智能体列表失败" @@ -4478,7 +4481,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..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": "無法列出代理程式。" @@ -4478,7 +4481,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..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" @@ -4478,7 +4481,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..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": "Αποτυχία καταχώρησης πρακτόρων." @@ -4478,7 +4481,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..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." @@ -4478,7 +4481,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..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." @@ -4478,7 +4481,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..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": "エージェントの一覧取得に失敗しました。" @@ -4478,7 +4481,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..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." @@ -4478,7 +4481,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..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": "Не удалось получить список агентов." @@ -4478,7 +4481,7 @@ "confirm": "Подтвердить", "forward": "Вперед", "multiple": "Множественный выбор", - "noResult": "[to be translated]:No results found", + "noResult": "Результаты не найдены", "page": "Страница", "select": "Выбрать", "title": "Быстрое меню" 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] 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 bc887d6a31..23c9427cb8 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 } @@ -144,15 +144,16 @@ const ThinkingButton: FC = ({ quickPanel, model, assistantId }): ReactEle return ( @@ -178,7 +179,7 @@ const ThinkingIcon = (option?: ThinkingOption) => { case 'auto': IconComponent = MdiLightbulbAutoOutline break - case 'off': + case 'none': IconComponent = MdiLightbulbOffOutline break default: 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 }) => }) 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}
diff --git a/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx b/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx index aaae0925ab..e1d6eee147 100644 --- a/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx +++ b/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx @@ -1,6 +1,7 @@ import { HelpTooltip } from '@cherrystudio/ui' import Selector from '@renderer/components/Selector' import { + getModelSupportedVerbosity, isSupportedReasoningEffortOpenAIModel, isSupportFlexServiceTierModel, isSupportVerbosityModel @@ -79,20 +80,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 }[] @@ -154,6 +159,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/store/index.ts b/src/renderer/src/store/index.ts index 1bdcc3b31a..382b9dc71e 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -71,7 +71,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 0dc99b3213..240d4e441c 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2824,6 +2824,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 f776fd97ba..453dc205ea 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -83,7 +83,10 @@ const ThinkModelTypes = [ 'o', 'openai_deep_research', 'gpt5', + 'gpt5_1', 'gpt5_codex', + 'gpt5_1_codex', + 'gpt5pro', 'grok', 'grok4_fast', 'gemini', @@ -100,7 +103,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 +114,7 @@ export function isThinkModelType(type: string): type is ThinkingModelType { } export const EFFORT_RATIO: EffortRatio = { + 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 a29a34d9b7..a35fdecfc9 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" @@ -1404,7 +1416,7 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/types@npm:3.910.0, @aws-sdk/types@npm:^3.222.0": +"@aws-sdk/types@npm:3.910.0": version: 3.910.0 resolution: "@aws-sdk/types@npm:3.910.0" dependencies: @@ -1414,6 +1426,16 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/types@npm:^3.222.0": + version: 3.840.0 + resolution: "@aws-sdk/types@npm:3.840.0" + dependencies: + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/292d38f5087c3aa925addd890f8ae2bf650282c2cf4997d971a341dc0249dfca7ce02d69a4af09da2562b78a4232232d2a3b88105f34f66aee608d52aac238d1 + languageName: node + linkType: hard + "@aws-sdk/util-arn-parser@npm:3.893.0": version: 3.893.0 resolution: "@aws-sdk/util-arn-parser@npm:3.893.0" @@ -1683,18 +1705,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.0, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/parser@npm:7.28.4" - dependencies: - "@babel/types": "npm:^7.28.4" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/58b239a5b1477ac7ed7e29d86d675cc81075ca055424eba6485872626db2dc556ce63c45043e5a679cd925e999471dba8a3ed4864e7ab1dbf64306ab72c52707 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.28.5": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4, @babel/parser@npm:^7.28.5": version: 7.28.5 resolution: "@babel/parser@npm:7.28.5" dependencies: @@ -1705,6 +1716,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.5, @babel/parser@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/c2ef81d598990fa949d1d388429df327420357cb5200271d0d0a2784f1e6d54afc8301eb8bdf96d8f6c77781e402da93c7dc07980fcc136ac5b9d5f1fce701b5 + languageName: node + linkType: hard + "@babel/plugin-transform-arrow-functions@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" @@ -1716,13 +1738,20 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.26.7, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.7, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.5, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.4, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.24.8, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.26.7": version: 7.28.3 resolution: "@babel/runtime@npm:7.28.3" checksum: 10c0/b360f82c2c5114f2a062d4d143d7b4ec690094764853937110585a9497977aed66c102166d0e404766c274e02a50ffb8f6d77fef7251ecf3f607f0e03e6397bc languageName: node linkType: hard +"@babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.28.2 + resolution: "@babel/runtime@npm:7.28.2" + checksum: 10c0/c20afe253629d53a405a610b12a62ac74d341a2c1e0fb202bbef0c118f6b5c84f94bf16039f58fd0483dd256901259930a43976845bdeb180cab1f882c21b6e0 + languageName: node + linkType: hard + "@babel/template@npm:^7.27.2": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" @@ -1764,7 +1793,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.4, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.28.4": version: 7.28.4 resolution: "@babel/types@npm:7.28.4" dependencies: @@ -1784,6 +1813,26 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.4, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.0": + version: 7.28.1 + resolution: "@babel/types@npm:7.28.1" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/5e99b346c11ee42ffb0cadc28159fe0b184d865a2cc1593df79b199772a534f6453969b4942aa5e4a55a3081863096e1cc3fc1c724d826926dc787cf229b845d + languageName: node + linkType: hard + +"@babel/types@npm:^7.28.1, @babel/types@npm:^7.28.2": + version: 7.28.2 + resolution: "@babel/types@npm:7.28.2" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/24b11c9368e7e2c291fe3c1bcd1ed66f6593a3975f479cbb9dd7b8c8d8eab8a962b0d2fca616c043396ce82500ac7d23d594fbbbd013828182c01596370a0b10 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^1.0.2": version: 1.0.2 resolution: "@bcoe/v8-coverage@npm:1.0.2" @@ -2176,9 +2225,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 @@ -2189,7 +2238,7 @@ __metadata: optional: true bin: openai: bin/cli - checksum: 10c0/0f6cafb97aec17037d5ddcccc88e4b4a9c8de77a989a35bab2394b682a1a69e8a9343e8ee5eb8107d5c495970dbf3567642f154c033f7afc3bf078078666a92e + checksum: 10c0/9c51ef33c5b9d08041a115e3d6a8158412a379998a0eae186923d5bdcc808b634c1fef4471a1d499bb8c624b04c075167bc90a1a60a805005c0657ecebbb58d0 languageName: node linkType: hard @@ -6788,15 +6837,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 @@ -12195,7 +12244,16 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0": +"@types/node@npm:*, @types/node@npm:>=13.7.0": + version: 22.15.29 + resolution: "@types/node@npm:22.15.29" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/602cc88c6150780cd9b5b44604754e0ce13983ae876a538861d6ecfb1511dff289e5576fffd26c841cde2142418d4bb76e2a72a382b81c04557ccb17cff29e1d + languageName: node + linkType: hard + +"@types/node@npm:>=10.0.0": version: 24.3.1 resolution: "@types/node@npm:24.3.1" dependencies: @@ -12289,7 +12347,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^19.0.12": +"@types/react@npm:*": version: 19.1.12 resolution: "@types/react@npm:19.1.12" dependencies: @@ -12298,6 +12356,15 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^19.0.12": + version: 19.1.2 + resolution: "@types/react@npm:19.1.2" + dependencies: + csstype: "npm:^3.0.2" + checksum: 10c0/76ffe71395c713d4adc3c759465012d3c956db00af35ab7c6d0d91bd07b274b7ce69caa0478c0760311587bd1e38c78ffc9688ebc629f2b266682a19d8750947 + languageName: node + linkType: hard + "@types/resolve@npm:^1.20.2": version: 1.20.6 resolution: "@types/resolve@npm:1.20.6" @@ -13505,8 +13572,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" @@ -13533,7 +13602,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" "@cherrystudio/ui": "workspace:*" "@dnd-kit/core": "npm:^6.3.1" "@dnd-kit/modifiers": "npm:^9.0.0" @@ -13566,7 +13635,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" @@ -16547,13 +16616,20 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.3, decimal.js@npm:^10.5.0": +"decimal.js@npm:^10.4.3": version: 10.6.0 resolution: "decimal.js@npm:10.6.0" checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa languageName: node linkType: hard +"decimal.js@npm:^10.5.0": + version: 10.5.0 + resolution: "decimal.js@npm:10.5.0" + checksum: 10c0/785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3 + languageName: node + linkType: hard + "decode-named-character-reference@npm:^1.0.0": version: 1.1.0 resolution: "decode-named-character-reference@npm:1.1.0" @@ -16807,13 +16883,27 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1, detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4": +"detect-libc@npm:^2.0.0": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 languageName: node linkType: hard +"detect-libc@npm:^2.0.1": + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4": + version: 2.0.4 + resolution: "detect-libc@npm:2.0.4" + checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c + languageName: node + linkType: hard + "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -18216,13 +18306,20 @@ __metadata: languageName: node linkType: hard -"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.1, eventsource-parser@npm:^3.0.5": +"eventsource-parser@npm:^3.0.0, eventsource-parser@npm:^3.0.5": version: 3.0.5 resolution: "eventsource-parser@npm:3.0.5" checksum: 10c0/5cb75e3f84ff1cfa1cee6199d4fd430c4544855ab03e953ddbe5927e7b31bc2af3933ab8aba6440ba160ed2c48972b6c317f27b8a1d0764c7b12e34e249de631 languageName: node linkType: hard +"eventsource-parser@npm:^3.0.1": + version: 3.0.1 + resolution: "eventsource-parser@npm:3.0.1" + checksum: 10c0/146ce5ae8325d07645a49bbc54d7ac3aef42f5138bfbbe83d5cf96293b50eab2219926d6cf41eed0a0f90132578089652ba9286a19297662900133a9da6c2fd0 + languageName: node + linkType: hard + "eventsource-parser@npm:^3.0.6": version: 3.0.6 resolution: "eventsource-parser@npm:3.0.6" @@ -18447,7 +18544,7 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:5.2.5, fast-xml-parser@npm:^5.2.0": +"fast-xml-parser@npm:5.2.5": version: 5.2.5 resolution: "fast-xml-parser@npm:5.2.5" dependencies: @@ -18469,6 +18566,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:^5.2.0": + version: 5.2.0 + resolution: "fast-xml-parser@npm:5.2.0" + dependencies: + strnum: "npm:^2.0.5" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/9d65ea8edeff114200313a7df7b9b1c62ff848099529ab82ed2de75ef789ebf1d7105442a12a1a51899f4dae3961f776adc6ddd85dd461272bc3f1e62211fc37 + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.19.1 resolution: "fastq@npm:1.19.1" @@ -18518,6 +18626,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.4.4": + version: 6.4.4 + resolution: "fdir@npm:6.4.4" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/6ccc33be16945ee7bc841e1b4178c0b4cf18d3804894cb482aa514651c962a162f96da7ffc6ebfaf0df311689fb70091b04dd6caffe28d56b9ebdc0e7ccadfdd + languageName: node + linkType: hard + "fdir@npm:^6.5.0": version: 6.5.0 resolution: "fdir@npm:6.5.0" @@ -19126,7 +19246,16 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.10.1, get-tsconfig@npm:^4.12.0, get-tsconfig@npm:^4.7.0, get-tsconfig@npm:^4.7.5": +"get-tsconfig@npm:^4.10.1, get-tsconfig@npm:^4.7.0, get-tsconfig@npm:^4.7.5": + version: 4.10.1 + resolution: "get-tsconfig@npm:4.10.1" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/7f8e3dabc6a49b747920a800fb88e1952fef871cdf51b79e98db48275a5de6cdaf499c55ee67df5fa6fe7ce65f0063e26de0f2e53049b408c585aa74d39ffa21 + languageName: node + linkType: hard + +"get-tsconfig@npm:^4.12.0": version: 4.12.0 resolution: "get-tsconfig@npm:4.12.0" dependencies: @@ -20484,16 +20613,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^2.4.2": - version: 2.6.1 - resolution: "jiti@npm:2.6.1" - bin: - jiti: lib/jiti-cli.mjs - checksum: 10c0/79b2e96a8e623f66c1b703b98ec1b8be4500e1d217e09b09e343471bbb9c105381b83edbb979d01cef18318cc45ce6e153571b6c83122170eefa531c64b6789b - languageName: node - linkType: hard - -"jiti@npm:^2.5.1": +"jiti@npm:^2.4.2, jiti@npm:^2.5.1": version: 2.5.1 resolution: "jiti@npm:2.5.1" bin: @@ -21591,7 +21711,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.0, magic-string@npm:^0.30.17, magic-string@npm:^0.30.18, magic-string@npm:^0.30.19": +"magic-string@npm:^0.30.0, magic-string@npm:^0.30.19": version: 0.30.19 resolution: "magic-string@npm:0.30.19" dependencies: @@ -21600,6 +21720,24 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.18": + version: 0.30.18 + resolution: "magic-string@npm:0.30.18" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/80fba01e13ce1f5c474a0498a5aa462fa158eb56567310747089a0033e432d83a2021ee2c109ac116010cd9dcf90a5231d89fbe3858165f73c00a50a74dbefcd + languageName: node + linkType: hard + "magicast@npm:^0.3.5": version: 0.3.5 resolution: "magicast@npm:0.3.5" @@ -27749,6 +27887,13 @@ __metadata: languageName: node linkType: hard +"strnum@npm:^2.0.5": + version: 2.0.5 + resolution: "strnum@npm:2.0.5" + checksum: 10c0/856026ef65eaf15359d340a313ece25822b6472377b3029201b00f2657a1a3fa1cd7a7ce349dad35afdd00faf451344153dbb3d8478f082b7af8c17a64799ea6 + languageName: node + linkType: hard + "strnum@npm:^2.1.0": version: 2.1.1 resolution: "strnum@npm:2.1.1" @@ -28257,7 +28402,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15": +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" dependencies: @@ -28267,6 +28412,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.14": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10c0/f789ed6c924287a9b7d3612056ed0cda67306cd2c80c249fd280cf1504742b12583a2089b61f4abbd24605f390809017240e250241f09938054c9b363e51c0a6 + languageName: node + linkType: hard + "tinypool@npm:^1.1.1": version: 1.1.1 resolution: "tinypool@npm:1.1.1" @@ -30208,13 +30363,20 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.25.76 || ^4, zod@npm:^4.1.5": +"zod@npm:^3.25.76 || ^4": version: 4.1.12 resolution: "zod@npm:4.1.12" checksum: 10c0/b64c1feb19e99d77075261eaf613e0b2be4dfcd3551eff65ad8b4f2a079b61e379854d066f7d447491fcf193f45babd8095551a9d47973d30b46b6d8e2c46774 languageName: node linkType: hard +"zod@npm:^4.1.5": + version: 4.1.5 + resolution: "zod@npm:4.1.5" + checksum: 10c0/7826fb931bc71d4d0fff2fbb72f1a1cf30a6672cf9dbe6933a216bbb60242ef1c3bdfbcd3c5b27e806235a35efaad7a4a9897ff4d3621452f9ea278bce6fd42a + languageName: node + linkType: hard + "zustand@npm:^4.4.0": version: 4.5.6 resolution: "zustand@npm:4.5.6"