From 5157b097436495ecd278921d30715452f7021066 Mon Sep 17 00:00:00 2001 From: Cepvor <154242055+Evsdrg@users.noreply.github.com> Date: Sun, 17 May 2026 07:28:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Grok=20=E9=80=82=E9=85=8D=E5=AE=8C?= =?UTF-8?q?=E5=96=84=20=E2=80=94=20=E9=98=B2=E5=BE=A1=E6=80=A7=20usage=20?= =?UTF-8?q?=E5=90=88=E5=B9=B6=20+=20thinking=20=E8=87=AA=E5=8A=A8=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=20(#1234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Grok 适配完善 — 防御性 usage 合并 + thinking 自动检测 1. 提取 updateOpenAIUsage 到共享模块 openaiShared.ts,供 OpenAI 和 Grok 两条路径复用,消除 Grok 中重复的 spread 漏洞。 2. 在 requestBody.ts 的 isOpenAIThinkingEnabled() 中增加 Grok 模型 自动检测(模型名含 "grok"),与 DeepSeek/MiMo 并列。 3. messaging 层的 reasoning_content 回传(openaiConvertMessages.ts) 和流解析(openaiStreamAdapter.ts)无需修改,Grok 与 DeepSeek/MiMo 共用相同的 reasoning_content 字段协议。 Co-Authored-By: deepseek-v4-pro[1m] * fix: 回退 Grok 从 isOpenAIThinkingEnabled 的自动检测 Grok 推理模型(如 grok-4.20-reasoning)自动进行推理,不需要 thinking/enable_thinking 请求参数。发送这些参数虽大概率被忽略 (OpenAI SDK 透传 unknown keys),但属于不正确行为。 Co-Authored-By: deepseek-v4-pro[1m] --------- Co-authored-by: deepseek-v4-pro[1m] --- src/services/api/grok/index.ts | 5 +-- src/services/api/openai/index.ts | 3 +- src/services/api/openai/openaiShared.ts | 46 +++++++++++++++++++++++++ src/services/api/openai/requestBody.ts | 4 ++- 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/services/api/openai/openaiShared.ts diff --git a/src/services/api/grok/index.ts b/src/services/api/grok/index.ts index bb0ead96c..55c91dad4 100644 --- a/src/services/api/grok/index.ts +++ b/src/services/api/grok/index.ts @@ -12,6 +12,7 @@ import type { ChatCompletionCreateParamsStreaming, } from 'openai/resources/chat/completions/completions.mjs' import { getGrokClient } from './client.js' +import { updateOpenAIUsage } from '../openai/openaiShared.js' import { anthropicMessagesToOpenAI, anthropicToolsToOpenAI, @@ -136,7 +137,7 @@ export async function* queryModelGrok( partialMessage = (event as any).message ttftMs = Date.now() - start if ((event as any).message?.usage) { - usage = { ...usage, ...(event as any).message.usage } + usage = updateOpenAIUsage(usage, (event as any).message.usage) } break } @@ -192,7 +193,7 @@ export async function* queryModelGrok( case 'message_delta': { const deltaUsage = (event as any).usage if (deltaUsage) { - usage = { ...usage, ...deltaUsage } + usage = updateOpenAIUsage(usage, deltaUsage) } break } diff --git a/src/services/api/openai/index.ts b/src/services/api/openai/index.ts index 520290b18..b76be9b9b 100644 --- a/src/services/api/openai/index.ts +++ b/src/services/api/openai/index.ts @@ -10,6 +10,7 @@ import type { import type { AgentId } from '../../../types/ids.js' import type { Tools } from '../../../Tool.js' import { getOpenAIClient } from './client.js' +import { updateOpenAIUsage } from './openaiShared.js' import { anthropicMessagesToOpenAI, resolveOpenAIModel, @@ -449,7 +450,7 @@ export async function* queryModelOpenAI( case 'message_delta': { const deltaUsage = (event as any).usage if (deltaUsage) { - usage = { ...usage, ...deltaUsage } + usage = updateOpenAIUsage(usage, deltaUsage) } if ((event as any).delta?.stop_reason != null) { stopReason = (event as any).delta.stop_reason diff --git a/src/services/api/openai/openaiShared.ts b/src/services/api/openai/openaiShared.ts new file mode 100644 index 000000000..6012080a6 --- /dev/null +++ b/src/services/api/openai/openaiShared.ts @@ -0,0 +1,46 @@ +/** + * Shared utilities for OpenAI-compatible API paths. + * + * Both the OpenAI path (queryModelOpenAI) and Grok path (queryModelGrok) use + * the same adapters (openaiStreamAdapter, openaiConvertMessages), so the event + * processing logic should be shared rather than duplicated. + */ + +/** + * Merge a delta usage into the accumulated usage, preserving cache-related + * fields from previous values when the delta carries explicit zeroes or + * undefined values. + * + * Mirrors updateUsage() in claude.ts: a future adapter change that omits + * cache fields from certain streaming events should not silently zero the + * accumulated counters. + */ +export function updateOpenAIUsage( + current: { + input_tokens: number + output_tokens: number + cache_creation_input_tokens: number + cache_read_input_tokens: number + }, + delta: { + input_tokens?: number + output_tokens?: number + cache_creation_input_tokens?: number + cache_read_input_tokens?: number + }, +): typeof current { + return { + input_tokens: delta.input_tokens ?? current.input_tokens, + output_tokens: delta.output_tokens ?? current.output_tokens, + cache_creation_input_tokens: + delta.cache_creation_input_tokens !== undefined && + delta.cache_creation_input_tokens > 0 + ? delta.cache_creation_input_tokens + : current.cache_creation_input_tokens, + cache_read_input_tokens: + delta.cache_read_input_tokens !== undefined && + delta.cache_read_input_tokens > 0 + ? delta.cache_read_input_tokens + : current.cache_read_input_tokens, + } +} diff --git a/src/services/api/openai/requestBody.ts b/src/services/api/openai/requestBody.ts index d47ccdfc8..095332cf5 100644 --- a/src/services/api/openai/requestBody.ts +++ b/src/services/api/openai/requestBody.ts @@ -23,7 +23,9 @@ export function isOpenAIThinkingEnabled(model: string): boolean { if (isEnvDefinedFalsy(process.env.OPENAI_ENABLE_THINKING)) return false // Explicit enable if (isEnvTruthy(process.env.OPENAI_ENABLE_THINKING)) return true - // Auto-detect from model name (DeepSeek and MiMo models support thinking mode) + // Auto-detect from model name (DeepSeek and MiMo models support thinking mode). + // Grok is intentionally excluded — Grok reasoning models reason automatically + // and do NOT require thinking/enable_thinking request body parameters. const modelLower = model.toLowerCase() return modelLower.includes('deepseek') || modelLower.includes('mimo') }