diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json
index 752ed7c37..f9741421e 100644
--- a/messages/en/dashboard.json
+++ b/messages/en/dashboard.json
@@ -551,6 +551,11 @@
"overridden": "Overridden by provider",
"tooltip": "Thinking effort in the Codex request (reasoning.effort), shown verbatim."
},
+ "reasoningEffortOpenai": {
+ "label": "Reasoning effort",
+ "overridden": "Overridden by provider",
+ "tooltip": "Thinking effort in the OpenAI chat/completions request (reasoning_effort / reasoning.effort), shown verbatim."
+ },
"logicTrace": {
"title": "Decision Chain",
"singleRouteSelectionTitle": "Provider selection under single-route protection",
diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json
index 0f49373bb..79b101a14 100644
--- a/messages/ja/dashboard.json
+++ b/messages/ja/dashboard.json
@@ -551,6 +551,11 @@
"overridden": "プロバイダーにより上書き",
"tooltip": "Codex リクエストの推論強度 (reasoning.effort) をそのまま表示します。"
},
+ "reasoningEffortOpenai": {
+ "label": "推論強度",
+ "overridden": "プロバイダーにより上書き",
+ "tooltip": "OpenAI chat/completions リクエストの推論強度 (reasoning_effort / reasoning.effort) をそのまま表示します。"
+ },
"logicTrace": {
"title": "決定チェーン",
"singleRouteSelectionTitle": "単一経路保護での Provider 選択",
diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json
index 5b630c199..22de6672d 100644
--- a/messages/ru/dashboard.json
+++ b/messages/ru/dashboard.json
@@ -551,6 +551,11 @@
"overridden": "Переопределено провайдером",
"tooltip": "Интенсивность рассуждений в запросе Codex (reasoning.effort), показанная без изменений."
},
+ "reasoningEffortOpenai": {
+ "label": "Интенсивность рассуждений",
+ "overridden": "Переопределено провайдером",
+ "tooltip": "Интенсивность рассуждений в запросе OpenAI chat/completions (reasoning_effort / reasoning.effort), показанная без изменений."
+ },
"logicTrace": {
"title": "Цепочка решений",
"singleRouteSelectionTitle": "Выбор провайдера в режиме защиты одним маршрутом",
diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json
index 5b07a9346..5fa41c03d 100644
--- a/messages/zh-CN/dashboard.json
+++ b/messages/zh-CN/dashboard.json
@@ -551,6 +551,11 @@
"overridden": "已被供应商覆写",
"tooltip": "Codex 请求中的思考强度(reasoning.effort),按原值显示。"
},
+ "reasoningEffortOpenai": {
+ "label": "思考强度",
+ "overridden": "已被供应商覆写",
+ "tooltip": "OpenAI chat/completions 请求中的思考强度(reasoning_effort / reasoning.effort),按原值显示。"
+ },
"logicTrace": {
"title": "决策链",
"singleRouteSelectionTitle": "单路保护下的供应商选择",
diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json
index b69873d4b..8caa52816 100644
--- a/messages/zh-TW/dashboard.json
+++ b/messages/zh-TW/dashboard.json
@@ -551,6 +551,11 @@
"overridden": "已被供應商覆寫",
"tooltip": "Codex 請求中的思考強度(reasoning.effort),按原值顯示。"
},
+ "reasoningEffortOpenai": {
+ "label": "思考強度",
+ "overridden": "已被供應商覆寫",
+ "tooltip": "OpenAI chat/completions 請求中的思考強度(reasoning_effort / reasoning.effort),按原值顯示。"
+ },
"logicTrace": {
"title": "決策鏈",
"singleRouteSelectionTitle": "單路保護下的供應商選擇",
diff --git a/package.json b/package.json
index 24465dab9..b64a31e0a 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
"@langfuse/otel": "^5.10.0",
"@langfuse/tracing": "^5.10.0",
"@lobehub/icons": "^5.15.0",
+ "@lobehub/ui": "^5.0.0",
"@opentelemetry/sdk-node": "^0.221.0",
"@radix-ui/react-alert-dialog": "^1.1.23",
"@radix-ui/react-avatar": "^1.2.6",
diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx
index ad967e6e1..61d381c87 100644
--- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx
+++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx
@@ -142,7 +142,12 @@ export function SummaryTab({
const showNoSignatureBadge =
thinkingSignatureDetection?.source === "fallback_no_signature_with_thinking";
const thinkingEffortInfo = extractThinkingEffortInfo(specialSettings);
- const effortMessageKey = thinkingEffortInfo?.source === "codex" ? "reasoningEffort" : "effort";
+ const effortMessageKey =
+ thinkingEffortInfo?.source === "codex"
+ ? "reasoningEffort"
+ : thinkingEffortInfo?.source === "openai"
+ ? "reasoningEffortOpenai"
+ : "effort";
const effortDisplay = thinkingEffortInfo
? {
requestedEffort: thinkingEffortInfo.requestedEffort,
diff --git a/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.test.tsx b/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.test.tsx
index 5a1367fc6..b71f64215 100644
--- a/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.test.tsx
+++ b/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.test.tsx
@@ -180,4 +180,51 @@ describe("ThinkingEffortDisplay", () => {
expect(html).toContain("reasoningEffort.tooltip");
expect(html).not.toContain(">medium<");
});
+
+ test("显示 OpenAI chat/completions 请求中的思考强度", () => {
+ const html = renderToStaticMarkup(
+
+ );
+
+ expect(html).toContain('data-slot="thinking-effort"');
+ expect(html).toContain("max");
+ expect(html).toContain("reasoningEffortOpenai.tooltip");
+ expect(html).not.toContain("overridden");
+ });
+
+ test("OpenAI 与 Codex 审计并存时优先展示 Codex 强度", () => {
+ const html = renderToStaticMarkup(
+
+ );
+
+ expect(html).toContain("high");
+ expect(html).toContain("reasoningEffort.tooltip");
+ expect(html).not.toContain(">max<");
+ });
});
diff --git a/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.tsx b/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.tsx
index 624b8cae2..4dce53430 100644
--- a/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.tsx
+++ b/src/app/[locale]/dashboard/logs/_components/thinking-effort-display.tsx
@@ -14,7 +14,8 @@ interface ThinkingEffortDisplayProps {
}
/**
- * 在使用记录中展示任意模型的思考强度(Codex reasoning.effort 或 Anthropic effort)。
+ * 在使用记录中展示任意模型的思考强度(Codex / OpenAI chat/completions 的
+ * reasoning.effort,或 Anthropic 的 output_config.effort)。
*
* 供应商改变强度时同时展示请求值和实际转发值,避免只看到客户端参数而误判上游行为。
*/
@@ -26,7 +27,12 @@ export function ThinkingEffortDisplay({ specialSettings }: ThinkingEffortDisplay
return -;
}
- const messageNamespace = effortInfo.source === "codex" ? "reasoningEffort" : "effort";
+ const messageNamespace =
+ effortInfo.source === "anthropic"
+ ? "effort"
+ : effortInfo.source === "openai"
+ ? "reasoningEffortOpenai"
+ : "reasoningEffort";
const showEffectiveBadge = effortInfo.isOverridden && effortInfo.effectiveEffort != null;
return (
diff --git a/src/app/v1/_lib/proxy/message-service.test.ts b/src/app/v1/_lib/proxy/message-service.test.ts
index d46e26cb0..fc1f49412 100644
--- a/src/app/v1/_lib/proxy/message-service.test.ts
+++ b/src/app/v1/_lib/proxy/message-service.test.ts
@@ -9,7 +9,11 @@ vi.mock("@/repository/message", () => ({
import { ProxyMessageService } from "./message-service";
-function createSession(providerType: string, message: Record) {
+function createSession(
+ providerType: string,
+ message: Record,
+ endpoint: string = "/v1/responses"
+) {
const specialSettings: NonNullable> = [];
const setMessageContext = vi.fn();
const session = {
@@ -28,8 +32,8 @@ function createSession(providerType: string, message: Record) {
sessionId: "session-1",
userAgent: "codex_cli_rs/1.0.0",
clientIp: "127.0.0.1",
- getEndpoint: () => "/v1/responses",
- getManagedEndpoint: () => "/v1/responses",
+ getEndpoint: () => endpoint,
+ getManagedEndpoint: () => endpoint,
getOriginalModel: () => "gpt-5",
setOriginalModel: vi.fn(),
getSpecialSettings: () => (specialSettings.length > 0 ? specialSettings : null),
@@ -139,4 +143,123 @@ describe("ProxyMessageService Codex reasoning effort audit", () => {
expect.objectContaining({ endpoint: "/v1/responses/compact" })
);
});
+
+ test("openai-compatible chat/completions 顶层 reasoning_effort 保存审计", async () => {
+ const { session, specialSettings } = createSession(
+ "openai-compatible",
+ { model: "gpt-5.5", messages: [], reasoning_effort: "high" },
+ "/v1/chat/completions"
+ );
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toContainEqual({
+ type: "openai_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: "high",
+ source: "reasoning_effort",
+ });
+ expect(createMessageRequestMock).toHaveBeenCalledWith(
+ expect.objectContaining({ special_settings: specialSettings })
+ );
+ });
+
+ test("openai-compatible chat/completions 嵌套 reasoning.effort 保存审计", async () => {
+ const { session, specialSettings } = createSession(
+ "openai-compatible",
+ { model: "gpt-5.5", messages: [], reasoning: { effort: "low" } },
+ "/v1/chat/completions"
+ );
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toContainEqual({
+ type: "openai_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: "low",
+ source: "reasoning.effort",
+ });
+ });
+
+ test("openai-compatible 顶层与嵌套不一致时以顶层为准", async () => {
+ const { session, specialSettings } = createSession(
+ "openai-compatible",
+ {
+ model: "gpt-5.5",
+ messages: [],
+ reasoning_effort: "high",
+ reasoning: { effort: "low" },
+ },
+ "/v1/chat/completions"
+ );
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toContainEqual(
+ expect.objectContaining({ effort: "high", source: "reasoning_effort" })
+ );
+ });
+
+ test("openai-compatible 非 chat/completions 端点不写入思考强度审计", async () => {
+ const { session, specialSettings } = createSession("openai-compatible", {
+ model: "gpt-5.5",
+ messages: [],
+ reasoning_effort: "high",
+ });
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toEqual([]);
+ });
+
+ test("openai-compatible chat/completions 尾斜杠变体仍保存审计", async () => {
+ const { session, specialSettings } = createSession(
+ "openai-compatible",
+ { model: "gpt-5.5", messages: [], reasoning_effort: "high" },
+ "/v1/chat/completions/"
+ );
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toContainEqual({
+ type: "openai_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: "high",
+ source: "reasoning_effort",
+ });
+ });
+
+ test("openai-compatible 请求缺少 effort 时不写入空审计", async () => {
+ const { session, specialSettings } = createSession(
+ "openai-compatible",
+ { model: "gpt-5.5", messages: [] },
+ "/v1/chat/completions"
+ );
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toEqual([]);
+ });
+
+ test("复用已有 openai 思考强度审计,避免重复记录", async () => {
+ const { session, specialSettings } = createSession(
+ "openai-compatible",
+ { model: "gpt-5.5", messages: [], reasoning_effort: "high" },
+ "/v1/chat/completions"
+ );
+ specialSettings.push({
+ type: "openai_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: "high",
+ source: "reasoning_effort",
+ });
+
+ await ProxyMessageService.ensureContext(session);
+
+ expect(specialSettings).toHaveLength(1);
+ });
});
diff --git a/src/app/v1/_lib/proxy/message-service.ts b/src/app/v1/_lib/proxy/message-service.ts
index b891f902a..fb6907e38 100644
--- a/src/app/v1/_lib/proxy/message-service.ts
+++ b/src/app/v1/_lib/proxy/message-service.ts
@@ -1,5 +1,7 @@
+import { normalizeEndpointPath, V1_ENDPOINT_PATHS } from "@/app/v1/_lib/proxy/endpoint-paths";
import { extractAnthropicEffortFromRequestBody } from "@/lib/utils/anthropic-effort";
import { extractCodexReasoningEffortFromRequestBody } from "@/lib/utils/codex-reasoning-effort";
+import { extractOpenAIReasoningEffortFromRequestBody } from "@/lib/utils/openai-reasoning-effort";
import { createMessageRequest } from "@/repository/message";
import type { ProxySession } from "./session";
@@ -75,6 +77,30 @@ export class ProxyMessageService {
}
}
+ // openai-compatible 供应商的 chat/completions 请求:解析并记录思考强度审计。
+ // 兼容顶层 reasoning_effort(OpenAI 官方及多数 openai-compatible 供应商)与嵌套
+ // reasoning.effort(OpenRouter / Ollama / Vercel AI Gateway 等),见 openai-reasoning-effort。
+ const hasOpenAIReasoningEffortAudit = session
+ .getSpecialSettings()
+ ?.some((setting) => setting.type === "openai_reasoning_effort");
+
+ if (
+ provider.providerType === "openai-compatible" &&
+ normalizeEndpointPath(endpoint ?? "") === V1_ENDPOINT_PATHS.CHAT_COMPLETIONS &&
+ !hasOpenAIReasoningEffortAudit
+ ) {
+ const extraction = extractOpenAIReasoningEffortFromRequestBody(session.request.message);
+ if (extraction) {
+ session.addSpecialSetting({
+ type: "openai_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: extraction.effort,
+ source: extraction.source,
+ });
+ }
+ }
+
const messageRequest = await createMessageRequest({
provider_id: provider.id,
user_id: authState.user.id,
diff --git a/src/lib/utils/openai-reasoning-effort.ts b/src/lib/utils/openai-reasoning-effort.ts
new file mode 100644
index 000000000..82e83cbdd
--- /dev/null
+++ b/src/lib/utils/openai-reasoning-effort.ts
@@ -0,0 +1,84 @@
+import type { OpenAIReasoningEffortFieldSource, SpecialSetting } from "@/types/special-settings";
+
+/** 从 OpenAI Chat Completions 请求体解析出的思考强度信息。 */
+export interface OpenAIReasoningEffortExtraction {
+ effort: string;
+ source: OpenAIReasoningEffortFieldSource;
+}
+
+/**
+ * 过滤非字符串及空白值,避免把无效参数写入审计记录。
+ * 校验用裁剪后值,但返回原始字符串,保证审计记录如实反映客户端发送的字段值。
+ */
+function normalizeOpenAIReasoningEffort(value: unknown): string | null {
+ if (typeof value !== "string") {
+ return null;
+ }
+
+ return value.trim().length > 0 ? value : null;
+}
+
+/**
+ * 从 OpenAI Chat Completions 请求体解析思考强度。
+ *
+ * 兼容两种载体(外部兼容性调研结论):
+ * - 顶层标量 `reasoning_effort`:OpenAI 官方 Chat Completions 参数,且是
+ * DeepSeek / xAI / Mistral / Groq / Gemini(OAI-compat) / DeepInfra / OpenRouter /
+ * LiteLLM 等大多数 openai-compatible 供应商接受的 de-facto 标准 → 优先读取。
+ * - 嵌套对象 `reasoning.effort`:OpenRouter / Ollama / Vercel AI Gateway 等
+ * 在 chat/completions 端点也接受该 Responses 风格载体 → 兜底读取。
+ *
+ * 两者同时存在且不一致时以顶层为准:与 OpenRouter 官方声明「reasoning_effort 是
+ * reasoning.effort 的简写、二者不可冲突」的语义一致,避免嵌套值覆盖顶层声明。
+ * 审计层仅做非空校验、原样记录,不做取值白名单(各供应商取值域不同,白名单会丢真实值)。
+ */
+export function extractOpenAIReasoningEffortFromRequestBody(
+ requestBody: unknown
+): OpenAIReasoningEffortExtraction | null {
+ if (!requestBody || typeof requestBody !== "object" || Array.isArray(requestBody)) {
+ return null;
+ }
+
+ const record = requestBody as Record;
+
+ const topLevel = normalizeOpenAIReasoningEffort(record.reasoning_effort);
+ if (topLevel) {
+ return { effort: topLevel, source: "reasoning_effort" };
+ }
+
+ const reasoning = record.reasoning;
+ if (!reasoning || typeof reasoning !== "object" || Array.isArray(reasoning)) {
+ return null;
+ }
+
+ const nested = normalizeOpenAIReasoningEffort("effort" in reasoning ? reasoning.effort : null);
+ if (!nested) {
+ return null;
+ }
+
+ return { effort: nested, source: "reasoning.effort" };
+}
+
+/** 从使用记录审计中读取 OpenAI Chat Completions 的思考强度。 */
+export function extractOpenAIReasoningEffortFromSpecialSettings(
+ specialSettings: SpecialSetting[] | null | undefined
+): OpenAIReasoningEffortExtraction | null {
+ if (!Array.isArray(specialSettings)) {
+ return null;
+ }
+
+ for (const setting of specialSettings) {
+ if (setting.type !== "openai_reasoning_effort") {
+ continue;
+ }
+ if (typeof setting.effort !== "string" || setting.effort.trim().length === 0) {
+ continue;
+ }
+ return {
+ effort: setting.effort,
+ source: setting.source,
+ };
+ }
+
+ return null;
+}
diff --git a/src/lib/utils/special-settings.ts b/src/lib/utils/special-settings.ts
index 61a5e659f..8aef096c9 100644
--- a/src/lib/utils/special-settings.ts
+++ b/src/lib/utils/special-settings.ts
@@ -54,6 +54,8 @@ function buildSettingKey(setting: SpecialSetting): string {
return JSON.stringify([setting.type, setting.hit, setting.effort]);
case "codex_reasoning_effort":
return JSON.stringify([setting.type, setting.hit, setting.effort]);
+ case "openai_reasoning_effort":
+ return JSON.stringify([setting.type, setting.hit, setting.effort, setting.source]);
case "anthropic_cache_ttl_header_override":
return JSON.stringify([setting.type, setting.ttl]);
case "anthropic_context_1m_header_override":
diff --git a/src/lib/utils/thinking-effort.ts b/src/lib/utils/thinking-effort.ts
index aad4d3279..68e2b998e 100644
--- a/src/lib/utils/thinking-effort.ts
+++ b/src/lib/utils/thinking-effort.ts
@@ -1,9 +1,10 @@
import { extractAnthropicEffortInfo } from "@/lib/utils/anthropic-effort";
import { extractCodexReasoningEffortInfo } from "@/lib/utils/codex-reasoning-effort";
+import { extractOpenAIReasoningEffortFromSpecialSettings } from "@/lib/utils/openai-reasoning-effort";
import type { SpecialSetting } from "@/types/special-settings";
-/** 思考强度审计来源:Codex 的 reasoning.effort 或 Anthropic 的 output_config.effort。 */
-export type ThinkingEffortSource = "codex" | "anthropic";
+/** 思考强度审计来源:Codex 的 reasoning.effort、OpenAI chat/completions 或 Anthropic 的 output_config.effort。 */
+export type ThinkingEffortSource = "codex" | "openai" | "anthropic";
/** 任意模型统一后的思考强度展示信息,供列表列与请求详情共用。 */
export interface ThinkingEffortInfo {
@@ -18,8 +19,9 @@ export interface ThinkingEffortInfo {
/**
* 从 specialSettings 中提取任意模型的思考强度。
*
- * 复用 Codex 与 Anthropic 两个提取器并统一返回结构:Codex 审计优先,
- * 其次回退到 Anthropic effort,两者都无则返回 null。
+ * 复用 Codex、OpenAI chat/completions 与 Anthropic 三个提取器并统一返回结构:
+ * Codex 审计优先,其次 OpenAI,最后回退到 Anthropic effort,三者都无则返回 null。
+ * OpenAI chat/completions 目前无供应商级覆写机制,isOverridden 恒为 false。
*/
export function extractThinkingEffortInfo(
specialSettings: SpecialSetting[] | null | undefined
@@ -34,6 +36,16 @@ export function extractThinkingEffortInfo(
};
}
+ const openaiInfo = extractOpenAIReasoningEffortFromSpecialSettings(specialSettings);
+ if (openaiInfo) {
+ return {
+ source: "openai",
+ requestedEffort: openaiInfo.effort,
+ effectiveEffort: openaiInfo.effort,
+ isOverridden: false,
+ };
+ }
+
const anthropicInfo = extractAnthropicEffortInfo(specialSettings);
if (anthropicInfo) {
return {
diff --git a/src/types/special-settings.ts b/src/types/special-settings.ts
index 1b14f417f..0ad2b5940 100644
--- a/src/types/special-settings.ts
+++ b/src/types/special-settings.ts
@@ -17,6 +17,7 @@ export type SpecialSetting =
| ClaudeMetadataUserIdInjectionSpecialSetting
| AnthropicEffortSpecialSetting
| CodexReasoningEffortSpecialSetting
+ | OpenAIReasoningEffortSpecialSetting
| AnthropicCacheTtlHeaderOverrideSpecialSetting
| AnthropicContext1mHeaderOverrideSpecialSetting
| LongContextPricingSpecialSetting
@@ -103,6 +104,25 @@ export type CodexReasoningEffortSpecialSetting = {
effort: string;
};
+/** OpenAI Chat Completions 请求体中思考强度(effort)的载体字段。 */
+export type OpenAIReasoningEffortFieldSource = "reasoning_effort" | "reasoning.effort";
+
+/**
+ * OpenAI Chat Completions reasoning effort 请求参数审计
+ *
+ * 记录 openai-compatible 供应商的 /v1/chat/completions 请求中客户端声明的思考强度;
+ * 兼容顶层 reasoning_effort 与嵌套 reasoning.effort 两种载体(source 标注来源字段),
+ * 便于排查客户端实际用哪种字段表达思考等级。
+ */
+export type OpenAIReasoningEffortSpecialSetting = {
+ type: "openai_reasoning_effort";
+ scope: "request";
+ hit: boolean;
+ effort: string;
+ /** 请求体中的载体字段:顶层 reasoning_effort 或嵌套 reasoning.effort。 */
+ source: OpenAIReasoningEffortFieldSource;
+};
+
/**
* Anthropic 缓存 TTL 相关标头覆写审计
*
diff --git a/tests/unit/lib/utils/openai-reasoning-effort.test.ts b/tests/unit/lib/utils/openai-reasoning-effort.test.ts
new file mode 100644
index 000000000..8a1d46d04
--- /dev/null
+++ b/tests/unit/lib/utils/openai-reasoning-effort.test.ts
@@ -0,0 +1,132 @@
+import { describe, expect, test } from "vitest";
+import {
+ extractOpenAIReasoningEffortFromRequestBody,
+ extractOpenAIReasoningEffortFromSpecialSettings,
+} from "@/lib/utils/openai-reasoning-effort";
+import type { SpecialSetting } from "@/types/special-settings";
+
+describe("extractOpenAIReasoningEffortFromRequestBody", () => {
+ test("优先读取顶层 reasoning_effort", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ model: "gpt-5.5",
+ messages: [],
+ reasoning_effort: "high",
+ });
+
+ expect(result).toEqual({ effort: "high", source: "reasoning_effort" });
+ });
+
+ test("顶层缺失时兜底读取嵌套 reasoning.effort", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ model: "gpt-5.5",
+ messages: [],
+ reasoning: { effort: "low" },
+ });
+
+ expect(result).toEqual({ effort: "low", source: "reasoning.effort" });
+ });
+
+ test("顶层与嵌套不一致时以顶层为准", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ reasoning_effort: "max",
+ reasoning: { effort: "minimal" },
+ });
+
+ expect(result).toEqual({ effort: "max", source: "reasoning_effort" });
+ });
+
+ test("顶层与嵌套一致时读取顶层", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ reasoning_effort: "medium",
+ reasoning: { effort: "medium" },
+ });
+
+ expect(result).toEqual({ effort: "medium", source: "reasoning_effort" });
+ });
+
+ test("顶层为空白时回退嵌套值", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ reasoning_effort: " ",
+ reasoning: { effort: "xhigh" },
+ });
+
+ expect(result).toEqual({ effort: "xhigh", source: "reasoning.effort" });
+ });
+
+ test("保留非空值的前后空白(原样记录审计值)", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ reasoning_effort: " high ",
+ });
+
+ expect(result).toEqual({ effort: " high ", source: "reasoning_effort" });
+ });
+
+ test("顶层为非法类型时回退嵌套值", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({
+ reasoning_effort: 42,
+ reasoning: { effort: "high" },
+ });
+
+ expect(result).toEqual({ effort: "high", source: "reasoning.effort" });
+ });
+
+ test("两者均缺失时返回 null", () => {
+ expect(
+ extractOpenAIReasoningEffortFromRequestBody({ model: "gpt-5.5", messages: [] })
+ ).toBeNull();
+ });
+
+ test("reasoning 非对象时返回 null", () => {
+ expect(extractOpenAIReasoningEffortFromRequestBody({ reasoning: "not-an-object" })).toBeNull();
+ });
+
+ test("非对象请求体返回 null", () => {
+ expect(extractOpenAIReasoningEffortFromRequestBody(null)).toBeNull();
+ expect(extractOpenAIReasoningEffortFromRequestBody("string")).toBeNull();
+ expect(extractOpenAIReasoningEffortFromRequestBody([1, 2])).toBeNull();
+ });
+
+ test("保留实际取值域(不做白名单过滤)", () => {
+ const result = extractOpenAIReasoningEffortFromRequestBody({ reasoning_effort: "max" });
+
+ expect(result?.effort).toBe("max");
+ });
+});
+
+describe("extractOpenAIReasoningEffortFromSpecialSettings", () => {
+ test("读取首个有效的 openai 思考强度审计", () => {
+ const settings: SpecialSetting[] = [
+ {
+ type: "openai_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: "high",
+ source: "reasoning_effort",
+ },
+ ];
+
+ expect(extractOpenAIReasoningEffortFromSpecialSettings(settings)).toEqual({
+ effort: "high",
+ source: "reasoning_effort",
+ });
+ });
+
+ test("忽略其它类型审计", () => {
+ const settings: SpecialSetting[] = [
+ {
+ type: "codex_reasoning_effort",
+ scope: "request",
+ hit: true,
+ effort: "high",
+ },
+ ];
+
+ expect(extractOpenAIReasoningEffortFromSpecialSettings(settings)).toBeNull();
+ });
+
+ test("缺少审计数组时返回 null", () => {
+ expect(extractOpenAIReasoningEffortFromSpecialSettings(undefined)).toBeNull();
+ expect(extractOpenAIReasoningEffortFromSpecialSettings(null)).toBeNull();
+ expect(extractOpenAIReasoningEffortFromSpecialSettings([])).toBeNull();
+ });
+});