= ({ provider, resolve }) => {
{
key: 'upload',
label: (
-
- {}}
- accept="image/png, image/jpeg, image/gif"
- itemRender={() => null}
- maxCount={1}
- onChange={async ({ file }) => {
- try {
- const _file = file.originFileObj as File
- let logoData: string | Blob
+ {}}
+ accept="image/png, image/jpeg, image/gif"
+ itemRender={() => null}
+ maxCount={1}
+ onChange={async ({ file }) => {
+ try {
+ const _file = file.originFileObj as File
+ let logoData: string | Blob
- if (_file.type === 'image/gif') {
- logoData = _file
- } else {
- logoData = await compressImage(_file)
- }
-
- if (provider?.id) {
- if (logoData instanceof Blob && !(logoData instanceof File)) {
- const fileFromBlob = new File([logoData], 'logo.png', { type: logoData.type })
- await ImageStorage.set(`provider-${provider.id}`, fileFromBlob)
- } else {
- await ImageStorage.set(`provider-${provider.id}`, logoData)
- }
- const savedLogo = await ImageStorage.get(`provider-${provider.id}`)
- setLogo(savedLogo)
- } else {
- // 临时保存在内存中,等创建 provider 后会在调用方保存
- const tempUrl = await new Promise((resolve) => {
- const reader = new FileReader()
- reader.onload = () => resolve(reader.result as string)
- reader.readAsDataURL(logoData)
- })
- setLogo(tempUrl)
- }
-
- setDropdownOpen(false)
- } catch (error: any) {
- window.message.error(error.message)
+ if (_file.type === 'image/gif') {
+ logoData = _file
+ } else {
+ logoData = await compressImage(_file)
}
- }}>
- {t('settings.general.image_upload')}
-
-
- )
+
+ if (provider?.id) {
+ if (logoData instanceof Blob && !(logoData instanceof File)) {
+ const fileFromBlob = new File([logoData], 'logo.png', { type: logoData.type })
+ await ImageStorage.set(`provider-${provider.id}`, fileFromBlob)
+ } else {
+ await ImageStorage.set(`provider-${provider.id}`, logoData)
+ }
+ const savedLogo = await ImageStorage.get(`provider-${provider.id}`)
+ setLogo(savedLogo)
+ } else {
+ // 临时保存在内存中,等创建 provider 后会在调用方保存
+ const tempUrl = await new Promise((resolve) => {
+ const reader = new FileReader()
+ reader.onload = () => resolve(reader.result as string)
+ reader.readAsDataURL(logoData)
+ })
+ setLogo(tempUrl)
+ }
+
+ setDropdownOpen(false)
+ } catch (error: any) {
+ window.message.error(error.message)
+ }
+ }}>
+
+
+ ),
+ onClick: () => {
+ uploadRef.current?.click()
+ }
},
{
key: 'builtin',
- label: (
- {
- e.stopPropagation()
- setDropdownOpen(false)
- setLogoPickerOpen(true)
- }}>
- {t('settings.general.avatar.builtin')}
-
- )
+ label: ,
+ onClick: () => {
+ setDropdownOpen(false)
+ setLogoPickerOpen(true)
+ }
},
{
key: 'reset',
- label: (
- {
- e.stopPropagation()
- handleReset()
- }}>
- {t('settings.general.avatar.reset')}
-
- )
+ label: ,
+ onClick: handleReset
}
- ]
+ ] satisfies ItemType[]
// for logo
const backgroundColor = generateColorFromChar(name)
@@ -302,6 +291,11 @@ const ProviderInitialsLogo = styled.div`
}
`
+const MenuItem = styled.div`
+ width: 100%;
+ text-align: center;
+`
+
export default class AddProviderPopup {
static topviewId = 0
static hide() {
diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts
index 4a19ffc403..8604b3e520 100644
--- a/src/renderer/src/services/ApiService.ts
+++ b/src/renderer/src/services/ApiService.ts
@@ -7,7 +7,6 @@ import {
isOpenRouterBuiltInWebSearchModel,
isQwenMTModel,
isReasoningModel,
- isSupportedDisableGenerationModel,
isSupportedReasoningEffortModel,
isSupportedThinkingTokenModel,
isWebSearchModel
@@ -85,7 +84,7 @@ async function fetchExternalTool(
// 可能会有重复?
const knowledgeBaseIds = assistant.knowledge_bases?.map((base) => base.id)
const hasKnowledgeBase = !isEmpty(knowledgeBaseIds)
- const knowledgeRecognition = assistant.knowledgeRecognition || 'on'
+ const knowledgeRecognition = assistant.knowledgeRecognition || 'off'
const webSearchProvider = WebSearchService.getWebSearchProvider(assistant.webSearchProviderId)
// 使用外部搜索工具
@@ -463,10 +462,15 @@ export async function fetchChatCompletion({
const filteredMessages4 = filterAdjacentUserMessaegs(filteredMessages3)
- const _messages = filterUserRoleStartMessages(
+ let _messages = filterUserRoleStartMessages(
filterEmptyMessages(filterAfterContextClearMessages(takeRight(filteredMessages4, contextCount + 2))) // 取原来几个provider的最大值
)
+ // Fallback: ensure at least the last user message is present to avoid empty payloads
+ if ((!_messages || _messages.length === 0) && lastUserMessage) {
+ _messages = [lastUserMessage]
+ }
+
// FIXME: qwen3即使关闭思考仍然会导致enableReasoning的结果为true
const enableReasoning =
((isSupportedThinkingTokenModel(model) || isSupportedReasoningEffortModel(model)) &&
@@ -483,8 +487,7 @@ export async function fetchChatCompletion({
const enableUrlContext = assistant.enableUrlContext || false
- const enableGenerateImage =
- isGenerateImageModel(model) && (isSupportedDisableGenerationModel(model) ? assistant.enableGenerateImage : true)
+ const enableGenerateImage = isGenerateImageModel(model) && assistant.enableGenerateImage
// --- Call AI Completions ---
onChunkReceived({ type: ChunkType.LLM_RESPONSE_CREATED })
diff --git a/src/renderer/src/services/__tests__/ApiService.test.ts b/src/renderer/src/services/__tests__/ApiService.test.ts
index f0fc782a52..12ac0f95e5 100644
--- a/src/renderer/src/services/__tests__/ApiService.test.ts
+++ b/src/renderer/src/services/__tests__/ApiService.test.ts
@@ -103,8 +103,8 @@ vi.mock('@renderer/config/prompts', () => ({
}))
vi.mock('@renderer/config/systemModels', () => ({
- GENERATE_IMAGE_MODELS: [],
- SUPPORTED_DISABLE_GENERATION_MODELS: []
+ OPENAI_IMAGE_GENERATION_MODELS: [],
+ GENERATE_IMAGE_MODELS: []
}))
vi.mock('@renderer/config/tools', () => ({
diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts
index faad488cf7..8a66373558 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: 142,
+ version: 144,
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
migrate
},
diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts
index 44a8a6a248..f04ad009e8 100644
--- a/src/renderer/src/store/migrate.ts
+++ b/src/renderer/src/store/migrate.ts
@@ -1605,7 +1605,6 @@ const migrateConfig = {
if (state.paintings && !state.paintings.tokenflux_paintings) {
state.paintings.tokenflux_paintings = []
}
- state.settings.showTokens = true
state.settings.testPlan = false
return state
} catch (error) {
@@ -2315,6 +2314,26 @@ const migrateConfig = {
logger.error('migrate 142 error', error as Error)
return state
}
+ },
+ '143': (state: RootState) => {
+ try {
+ addMiniApp(state, 'longcat')
+ return state
+ } catch (error) {
+ return state
+ }
+ },
+ '144': (state: RootState) => {
+ try {
+ if (state.settings) {
+ state.settings.confirmDeleteMessage = settingsInitialState.confirmDeleteMessage
+ state.settings.confirmRegenerateMessage = settingsInitialState.confirmRegenerateMessage
+ }
+ return state
+ } catch (error) {
+ logger.error('migrate 144 error', error as Error)
+ return state
+ }
}
}
diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts
index 85c2a6c2c8..1d6a5dac7c 100644
--- a/src/renderer/src/store/settings.ts
+++ b/src/renderer/src/store/settings.ts
@@ -47,7 +47,6 @@ export interface SettingsState {
userName: string
userId: string
showPrompt: boolean
- showTokens: boolean
showMessageDivider: boolean
messageFont: 'system' | 'serif'
showInputEstimatedTokens: boolean
@@ -124,6 +123,9 @@ export interface SettingsState {
enableTopicNaming: boolean
customCss: string
topicNamingPrompt: string
+ // 消息操作确认设置
+ confirmDeleteMessage: boolean
+ confirmRegenerateMessage: boolean
// Sidebar icons
sidebarIcons: {
visible: SidebarIcon[]
@@ -233,7 +235,6 @@ export const initialState: SettingsState = {
userName: '',
userId: uuid(),
showPrompt: true,
- showTokens: true,
showMessageDivider: true,
messageFont: 'system',
showInputEstimatedTokens: false,
@@ -349,6 +350,9 @@ export const initialState: SettingsState = {
enableSpellCheck: false,
spellCheckLanguages: [],
enableQuickPanelTriggers: false,
+ // 消息操作确认设置
+ confirmDeleteMessage: true,
+ confirmRegenerateMessage: true,
// 硬件加速设置
disableHardwareAcceleration: false,
exportMenuOptions: {
@@ -454,9 +458,6 @@ const settingsSlice = createSlice({
setShowPrompt: (state, action: PayloadAction) => {
state.showPrompt = action.payload
},
- setShowTokens: (state, action: PayloadAction) => {
- state.showTokens = action.payload
- },
setShowMessageDivider: (state, action: PayloadAction) => {
state.showMessageDivider = action.payload
},
@@ -776,6 +777,12 @@ const settingsSlice = createSlice({
setEnableQuickPanelTriggers: (state, action: PayloadAction) => {
state.enableQuickPanelTriggers = action.payload
},
+ setConfirmDeleteMessage: (state, action: PayloadAction) => {
+ state.confirmDeleteMessage = action.payload
+ },
+ setConfirmRegenerateMessage: (state, action: PayloadAction) => {
+ state.confirmRegenerateMessage = action.payload
+ },
// setDisableHardwareAcceleration: (state, action: PayloadAction) => {
// state.disableHardwareAcceleration = action.payload
// },
@@ -866,7 +873,6 @@ export const {
setProxyBypassRules,
setUserName,
setShowPrompt,
- setShowTokens,
setShowMessageDivider,
setMessageFont,
setShowInputEstimatedTokens,
@@ -956,6 +962,8 @@ export const {
setSpellCheckLanguages,
setExportMenuOptions,
setEnableQuickPanelTriggers,
+ setConfirmDeleteMessage,
+ setConfirmRegenerateMessage,
// setDisableHardwareAcceleration,
setOpenAISummaryText,
setOpenAIVerbosity,
diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts
index 039fdfd648..f4d009e0e5 100644
--- a/src/renderer/src/store/thunk/messageThunk.ts
+++ b/src/renderer/src/store/thunk/messageThunk.ts
@@ -338,6 +338,15 @@ const fetchAndProcessAssistantResponseImpl = async (
messagesForContext = contextSlice.filter((m) => m && !m.status?.includes('ing'))
}
+ // Ensure at least the triggering user message is present to avoid empty payloads
+ if ((!messagesForContext || messagesForContext.length === 0) && userMessageId) {
+ const stateAfter = getState()
+ const maybeUserMessage = stateAfter.messages.entities[userMessageId]
+ if (maybeUserMessage) {
+ messagesForContext = [maybeUserMessage]
+ }
+ }
+
callbacks = createCallbacks({
blockManager,
dispatch,
diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts
index 09536f979c..012d21a7c4 100644
--- a/src/renderer/src/types/index.ts
+++ b/src/renderer/src/types/index.ts
@@ -8,8 +8,10 @@ export * from './file'
export * from './note'
import type { FileMetadata } from './file'
+import { MCPConfigSample, McpServerType } from './mcp'
import type { Message } from './newMessage'
+export * from './mcp'
export * from './ocr'
export type Assistant = {
@@ -825,6 +827,7 @@ export type KnowledgeReference = {
file?: FileMetadata
}
+// TODO: 把 mcp 相关类型定义迁移到独立文件中
export type MCPArgType = 'string' | 'list' | 'number'
export type MCPEnvType = 'string' | 'number'
export type MCPArgParameter = { [key: string]: MCPArgType }
@@ -836,29 +839,17 @@ export interface MCPServerParameter {
description: string
}
-export interface MCPConfigSample {
- command: string
- args: string[]
- env?: Record | undefined
-}
-
export interface MCPServer {
- id: string
- name: string
- type?: 'stdio' | 'sse' | 'inMemory' | 'streamableHttp'
+ id: string // internal id
+ name: string // mcp name, generally as unique key
+ type?: McpServerType | 'inMemory'
description?: string
baseUrl?: string
command?: string
registryUrl?: string
args?: string[]
env?: Record
- shouldConfig?: boolean
- isActive: boolean
- disabledTools?: string[] // List of tool names that are disabled for this server
- disabledAutoApproveTools?: string[] // Whether to auto-approve tools for this server
- configSample?: MCPConfigSample
headers?: Record // Custom headers to be sent with requests to this server
- searchKey?: string
provider?: string // Provider name for this server like ModelScope, Higress, etc.
providerUrl?: string // URL of the MCP server in provider's website or documentation
logoUrl?: string // URL of the MCP server's logo
@@ -868,6 +859,17 @@ export interface MCPServer {
dxtVersion?: string // Version of the DXT package
dxtPath?: string // Path where the DXT package was extracted
reference?: string // Reference link for the server, e.g., documentation or homepage
+ searchKey?: string
+ configSample?: MCPConfigSample
+ /** List of tool names that are disabled for this server */
+ disabledTools?: string[]
+ /** Whether to auto-approve tools for this server */
+ disabledAutoApproveTools?: string[]
+
+ /** 用于标记内置 MCP 是否需要配置 */
+ shouldConfig?: boolean
+ /** 用于标记服务器是否运行中 */
+ isActive: boolean
}
export type BuiltinMCPServer = MCPServer & {
@@ -1242,6 +1244,28 @@ export type AtLeast = {
[key: string]: U
}
+/**
+ * 从对象中移除指定的属性键,返回新对象
+ * @template T - 源对象类型
+ * @template K - 要移除的属性键类型,必须是T的键
+ * @param obj - 源对象
+ * @param keys - 要移除的属性键列表
+ * @returns 移除指定属性后的新对象
+ * @example
+ * ```ts
+ * const obj = { a: 1, b: 2, c: 3 };
+ * const result = strip(obj, ['a', 'b']);
+ * // result = { c: 3 }
+ * ```
+ */
+export function strip(obj: T, keys: K[]): Omit {
+ const result = { ...obj }
+ for (const key of keys) {
+ delete (result as any)[key] // 类型上 Omit 已保证安全
+ }
+ return result
+}
+
export type HexColor = string
/**
diff --git a/src/renderer/src/types/mcp.ts b/src/renderer/src/types/mcp.ts
new file mode 100644
index 0000000000..b2e44180d8
--- /dev/null
+++ b/src/renderer/src/types/mcp.ts
@@ -0,0 +1,227 @@
+import z from 'zod'
+
+import { isBuiltinMCPServerName } from '.'
+
+export const MCPConfigSampleSchema = z.object({
+ command: z.string(),
+ args: z.array(z.string()),
+ env: z.record(z.string(), z.string()).optional()
+})
+export type MCPConfigSample = z.infer
+/**
+ * 定义 MCP 服务器的通信类型。
+ * stdio: 通过标准输入/输出与子进程通信 (最常见)。
+ * sse: 通过HTTP Server-Sent Events 通信。
+ *
+ * 允许 inMemory 作为合法字段,需要额外校验 name 是否 builtin
+ */
+export const McpServerTypeSchema = z
+ .union([z.literal('stdio'), z.literal('sse'), z.literal('streamableHttp'), z.literal('inMemory')])
+ .default('stdio') // 大多数情况下默认使用 stdio
+/**
+ * 定义单个 MCP 服务器的配置。
+ * FIXME: 为了兼容性,暂时允许用户编辑任意字段,这可能会导致问题。
+ * 除了类型匹配以外,目前唯一显式禁止的行为是将 type 设置为 inMemory
+ */
+export const McpServerConfigSchema = z
+ .object({
+ /**
+ * 服务器内部ID
+ * 可选。用于内部标识服务器的唯一标识符。
+ */
+ id: z.string().optional().describe('Server internal id.'),
+ /**
+ * 服务器名称
+ * 可选。用于标识和显示服务器。
+ */
+ name: z.string().optional().describe('Server name for identification and display'),
+ /**
+ * 服务器的通信类型。
+ * 可选。如果未指定,默认为 "stdio"。
+ */
+ type: McpServerTypeSchema.optional(),
+ /**
+ * 服务器描述
+ * 可选。用于描述服务器的功能和用途。
+ */
+ description: z.string().optional().describe('Server description'),
+ /**
+ * 服务器的URL地址
+ * 可选。用于指定服务器的访问地址。
+ */
+ url: z.string().optional().describe('Server URL address'),
+ /**
+ * url 的内部别名,优先使用 baseUrl 字段。
+ * 可选。用于指定服务器的访问地址。
+ */
+ baseUrl: z.string().optional().describe('Server URL address'),
+ /**
+ * 启动服务器的命令 (如 "uvx", "npx")。
+ * 可选。
+ */
+ command: z.string().optional().describe("The command to execute (e.g., 'uvx', 'npx')"),
+ /**
+ * registry URL
+ * 可选。用于指定服务器的 registry 地址。
+ */
+ registryUrl: z.string().optional().describe('Registry URL for the server'),
+ /**
+ * 传递给命令的参数数组。
+ * 通常第一个参数是脚本路径或包名。
+ * 可选。
+ */
+ args: z.array(z.string()).optional().describe('The arguments to pass to the command'),
+ /**
+ * 启动时注入的环境变量对象。
+ * 键为变量名,值为字符串。
+ * 可选。
+ */
+ env: z.record(z.string(), z.string()).optional().describe('Environment variables for the server process'),
+ /**
+ * 请求头配置
+ * 可选。用于设置请求时的自定义headers。
+ */
+ headers: z.record(z.string(), z.string()).optional().describe('Custom headers configuration'),
+ /**
+ * provider 名称
+ * 可选。用于指定服务器的提供商。
+ */
+ provider: z.string().optional().describe('Provider name for the server'),
+ /**
+ * provider URL
+ * 可选。用于指定服务器提供商的网站或文档地址。
+ */
+ providerUrl: z.string().optional().describe('URL of the provider website or documentation'),
+ /**
+ * logo URL
+ * 可选。用于指定服务器的logo图片地址。
+ */
+ logoUrl: z.string().optional().describe('URL of the server logo'),
+ /**
+ * 服务器标签
+ * 可选。用于对服务器进行分类和标记。
+ */
+ tags: z.array(z.string()).optional().describe('Server tags for categorization'),
+ /**
+ * 是否为长期运行的服务器
+ * 可选。用于标识服务器是否需要持续运行。
+ */
+ longRunning: z.boolean().optional().describe('Whether the server is long running'),
+ /**
+ * 请求超时时间
+ * 可选。单位为秒,默认为60秒。
+ */
+ timeout: z.number().optional().describe('Timeout in seconds for requests to this server'),
+ /**
+ * DXT包版本号
+ * 可选。用于标识DXT包的版本。
+ */
+ dxtVersion: z.string().optional().describe('Version of the DXT package'),
+ /**
+ * DXT包解压路径
+ * 可选。指定DXT包解压后的存放路径。
+ */
+ dxtPath: z.string().optional().describe('Path where the DXT package was extracted'),
+ /**
+ * 参考链接
+ * 可选。服务器的文档或主页链接。
+ */
+ reference: z.string().optional().describe('Reference link for the server'),
+ /**
+ * 搜索关键字
+ * 可选。用于服务器搜索的关键字。
+ */
+ searchKey: z.string().optional().describe('Search key for the server'),
+ /**
+ * 配置示例
+ * 可选。服务器配置的示例。
+ */
+ configSample: MCPConfigSampleSchema.optional().describe('Configuration sample for the server'),
+ /**
+ * 禁用的工具列表
+ * 可选。用于指定该服务器上禁用的工具。
+ */
+ disabledTools: z.array(z.string()).optional().describe('List of disabled tools for this server'),
+ /**
+ * 禁用自动批准的工具列表
+ * 可选。用于指定该服务器上禁用自动批准的工具。
+ */
+ disabledAutoApproveTools: z
+ .array(z.string())
+ .optional()
+ .describe('List of tools that are disabled for auto-approval on this server'),
+ /**
+ * 是否应该配置
+ * 可选。用于标识服务器是否需要配置。
+ */
+ shouldConfig: z.boolean().optional().describe('Whether the server should be configured'),
+ /**
+ * 是否激活
+ * 可选。用于标识服务器是否处于激活状态。
+ */
+ isActive: z.boolean().optional().describe('Whether the server is active')
+ })
+ .strict()
+ // 在这里定义额外的校验逻辑
+ .refine(
+ (schema) => {
+ if (schema.type === 'inMemory' && schema.name && !isBuiltinMCPServerName(schema.name)) {
+ return false
+ }
+ return true
+ },
+ {
+ message: 'Server type is inMemory but this is not a builtin MCP server, which is not allowed'
+ }
+ )
+/**
+ * 将服务器别名(字符串ID)映射到其配置的对象。
+ * 例如: { "my-tools": { command: "...", args: [...] }, "github": { ... } }
+ */
+export const McpServersMapSchema = z.record(z.string(), McpServerConfigSchema)
+/**
+ * 顶层配置对象Schema。
+ * 表示整个MCP配置文件的结构。
+ */
+export const McpConfigSchema = z.object({
+ /**
+ * 包含一个或多个MCP服务器定义的映射。
+ * 名称(键)是用户定义的别名。
+ * 此字段为必需。
+ */
+ // 不在这里 refine 服务器数量,因为在类型定义文件中不能用 i18n 处理错误信息
+ mcpServers: McpServersMapSchema.describe('Mapping of server aliases to their configurations')
+})
+// 数据校验用类型,McpServerType 复用于 MCPServer
+
+export type McpServerType = z.infer
+export type McpServerConfig = z.infer
+export type McpServersMap = z.infer
+export type McpConfig = z.infer
+/**
+ * 验证一个未知对象是否为合法的MCP配置。
+ * @param config - 要验证的配置对象
+ * @returns 如果有效则为解析后的 `McpConfig` 对象,否则抛出 ZodError。
+ */
+
+export function validateMcpConfig(config: unknown): McpConfig {
+ return McpConfigSchema.parse(config)
+}
+/**
+ * 安全地验证一个未知对象,返回结果和可能的错误。
+ * @param config - 要验证的配置对象
+ * @returns 包含成功/失败状态和数据的 `SafeParseResult`。
+ */
+
+export function safeValidateMcpConfig(config: unknown) {
+ return McpConfigSchema.safeParse(config)
+}
+
+/**
+ * 安全地验证一个未知对象是否为合法的MCP服务器配置。
+ * @param config - 要验证的配置对象
+ * @returns 包含成功/失败状态和数据的 `SafeParseResult`。
+ */
+export function safeValidateMcpServerConfig(config: unknown) {
+ return McpServerConfigSchema.safeParse(config)
+}
diff --git a/src/renderer/src/types/sdk.ts b/src/renderer/src/types/sdk.ts
index 70387045a8..8ce94e6f4c 100644
--- a/src/renderer/src/types/sdk.ts
+++ b/src/renderer/src/types/sdk.ts
@@ -72,7 +72,7 @@ export type RequestOptions = Anthropic.RequestOptions | OpenAI.RequestOptions |
* OpenAI
*/
-type OpenAIParamsWithoutReasoningEffort = Omit
+type OpenAIParamsPurified = Omit
export type ReasoningEffortOptionalParams = {
thinking?: { type: 'disabled' | 'enabled' | 'auto'; budget_tokens?: number }
@@ -97,7 +97,7 @@ export type ReasoningEffortOptionalParams = {
// Add any other potential reasoning-related keys here if they exist
}
-export type OpenAISdkParams = OpenAIParamsWithoutReasoningEffort & ReasoningEffortOptionalParams
+export type OpenAISdkParams = OpenAIParamsPurified & ReasoningEffortOptionalParams & OpenAIModalities & OpenAIExtraBody
// OpenRouter may include additional fields like cost
export type OpenAISdkRawChunk =
@@ -116,7 +116,18 @@ export type OpenAISdkRawContentSource =
})
export type OpenAISdkMessageParam = OpenAI.Chat.Completions.ChatCompletionMessageParam
-
+export type OpenAIExtraBody = {
+ // for qwen mt
+ translation_options?: {
+ source_lang: 'auto'
+ target_lang: string
+ }
+}
+// image is for openrouter. audio is ignored for now
+export type OpenAIModality = OpenAI.ChatCompletionModality | 'image'
+export type OpenAIModalities = {
+ modalities?: OpenAIModality[]
+}
/**
* OpenAI Response
*/
diff --git a/src/renderer/src/utils/__tests__/error.test.ts b/src/renderer/src/utils/__tests__/error.test.ts
index fa130d82bb..db09f99952 100644
--- a/src/renderer/src/utils/__tests__/error.test.ts
+++ b/src/renderer/src/utils/__tests__/error.test.ts
@@ -56,7 +56,6 @@ describe('error', () => {
const error = new Error('Test error')
const result = formatErrorMessage(error)
- expect(console.error).toHaveBeenCalled()
expect(result).toContain('Error Details:')
expect(result).toContain(' {')
expect(result).toContain(' "message": "Test error"')
diff --git a/src/renderer/src/utils/__tests__/markdownConverter.test.ts b/src/renderer/src/utils/__tests__/markdownConverter.test.ts
index 9f42f61830..2224f7f0e7 100644
--- a/src/renderer/src/utils/__tests__/markdownConverter.test.ts
+++ b/src/renderer/src/utils/__tests__/markdownConverter.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
-import { htmlToMarkdown, markdownToHtml, markdownToSafeHtml, sanitizeHtml } from '../markdownConverter'
+import { htmlToMarkdown, markdownToHtml } from '../markdownConverter'
describe('markdownConverter', () => {
describe('htmlToMarkdown', () => {
@@ -294,33 +294,6 @@ describe('markdownConverter', () => {
})
})
- describe('sanitizeHtml', () => {
- it('should sanitize HTML content and remove scripts', () => {
- const html = 'Hello
'
- const result = sanitizeHtml(html)
- expect(result).toContain('Hello
')
- expect(result).not.toContain('