feat: Grok 适配完善 — 防御性 usage 合并 + thinking 自动检测 (#1234)

* 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] <deepseek-ai@claude-code-best.win>

* fix: 回退 Grok 从 isOpenAIThinkingEnabled 的自动检测

Grok 推理模型(如 grok-4.20-reasoning)自动进行推理,不需要
thinking/enable_thinking 请求参数。发送这些参数虽大概率被忽略
(OpenAI SDK 透传 unknown keys),但属于不正确行为。

Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>

---------

Co-authored-by: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
This commit is contained in:
Cepvor
2026-05-17 07:28:33 +08:00
committed by GitHub
parent ecd3f9d791
commit 5157b09743
4 changed files with 54 additions and 4 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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,
}
}

View File

@@ -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')
}