From 0c16f8d3212bc039227feaad7f0b7655d988d903 Mon Sep 17 00:00:00 2001 From: xiehui Date: Sun, 7 Jun 2026 23:10:01 +0800 Subject: [PATCH 1/3] docs: add AGENTS.md with reference to CLAUDE.md add new AGENTS.md file that links to project-specific CLAUDE.md instructions --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..f4bdbaa8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +Also read this project's `CLAUDE.md` for project-specific instructions. From 7af3a0283ca5a11b1a1c0f02061f69b37a539847 Mon Sep 17 00:00:00 2001 From: Hui Xie Date: Sun, 7 Jun 2026 23:13:53 +0800 Subject: [PATCH 2/3] Update AGENTS.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index f4bdbaa8..6c196b92 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1 +1 @@ -Also read this project's `CLAUDE.md` for project-specific instructions. +Also read this project's [CLAUDE.md](CLAUDE.md) for project-specific instructions. From 999cc92a12390f233e29c6ba34a24dd6c9dc04b2 Mon Sep 17 00:00:00 2001 From: xiehui Date: Mon, 8 Jun 2026 01:01:40 +0800 Subject: [PATCH 3/3] feat(ai plugin): add support for custom headers and extra request body Add new fields `headers` and `extraBody` to AiOption interface to allow passing custom request headers and additional OpenAI-compatible request body parameters. Implement normalization logic for headers and extra body, filter out reserved keys from extra body, and apply them to both streaming and non-streaming AI API calls. --- src/main/api/plugin/ai.ts | 49 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main/api/plugin/ai.ts b/src/main/api/plugin/ai.ts index 87c5eab5..19d10c44 100644 --- a/src/main/api/plugin/ai.ts +++ b/src/main/api/plugin/ai.ts @@ -12,6 +12,8 @@ export interface AiOption { model?: string // AI 模型,为空默认使用第一个配置的模型 messages: Message[] // 消息列表 tools?: Tool[] // 工具列表 + headers?: Record // 额外请求头 + extraBody?: Record // 额外 OpenAI-compatible 请求体字段 } /** 文本内容块 */ @@ -72,6 +74,7 @@ export interface Tool { } /** 工具调用循环最大轮次 */ const MAX_TOOL_ROUNDS = 25 +const RESERVED_EXTRA_BODY_KEYS = ['model', 'messages', 'tools', 'stream'] as const /** * AI 调用 API(插件专用)- 基于 OpenAI SDK 直接调用 @@ -227,6 +230,36 @@ class PluginAiAPI { baseURL: modelConfig.apiUrl }) } + + private isPlainObject(value: unknown): value is Record { + return Boolean(value && typeof value === 'object' && !Array.isArray(value)) + } + + private normalizeHeaders(headers: unknown): Record | undefined { + if (headers === undefined) return undefined + if (!this.isPlainObject(headers)) { + throw new Error('headers 必须是对象') + } + + const normalized = Object.fromEntries( + Object.entries(headers).map(([key, value]) => [key, String(value)]) + ) + return Object.keys(normalized).length > 0 ? normalized : undefined + } + + private normalizeExtraBody(extraBody: unknown): Record | undefined { + if (extraBody === undefined) return undefined + if (!this.isPlainObject(extraBody)) { + throw new Error('extraBody 必须是对象') + } + + const normalized = { ...extraBody } + for (const key of RESERVED_EXTRA_BODY_KEYS) { + delete normalized[key] + } + return Object.keys(normalized).length > 0 ? normalized : undefined + } + /** * 将 Message[] 转为 OpenAI SDK 格式 * 关键:保留 assistant 消息的 reasoning_content,解决 DeepSeek thinking mode 报错 @@ -325,17 +358,23 @@ class PluginAiAPI { const client = this.createClient(modelConfig) const openaiTools = option.tools?.length ? this.convertTools(option.tools) : undefined const messages = [...option.messages] + const requestHeaders = this.normalizeHeaders(option.headers) + const extraBody = this.normalizeExtraBody(option.extraBody) for (let round = 0; round < MAX_TOOL_ROUNDS; round++) { this.notifyAiStatus(round === 0 ? 'sending' : 'receiving', webContents) const response = await client.chat.completions.create( { + ...extraBody, model: modelConfig.id, messages: this.convertMessages(messages), ...(openaiTools?.length ? { tools: openaiTools } : {}) }, - { signal: abortController.signal } + { + signal: abortController.signal, + ...(requestHeaders ? { headers: requestHeaders } : {}) + } ) const choice = response.choices[0] @@ -423,18 +462,24 @@ class PluginAiAPI { const client = this.createClient(modelConfig) const openaiTools = option.tools?.length ? this.convertTools(option.tools) : undefined const messages = [...option.messages] + const requestHeaders = this.normalizeHeaders(option.headers) + const extraBody = this.normalizeExtraBody(option.extraBody) for (let round = 0; round < MAX_TOOL_ROUNDS; round++) { this.notifyAiStatus(round === 0 ? 'sending' : 'receiving', webContents) const stream = await client.chat.completions.create( { + ...extraBody, model: modelConfig.id, messages: this.convertMessages(messages), stream: true, ...(openaiTools?.length ? { tools: openaiTools } : {}) }, - { signal: abortController.signal } + { + signal: abortController.signal, + ...(requestHeaders ? { headers: requestHeaders } : {}) + } ) let fullContent = ''