惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks
Martin Fowler
Martin Fowler
宝玉的分享
宝玉的分享
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
M
MIT News - Artificial intelligence
U
Unit 42
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
I
InfoQ
WordPress大学
WordPress大学
H
Help Net Security
D
Docker
B
Blog
腾讯CDC
A
About on SuperTechFans
Recent Announcements
Recent Announcements
雷峰网
雷峰网
有赞技术团队
有赞技术团队
C
Check Point Blog
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
refactor(providers): share reasoning payload normalizatio...
vincentkoc · 2026-06-22 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -515,7 +515,7 @@ API key auth, and dynamic model resolution.

515515
516516

- `openclaw/plugin-sdk/provider-model-shared` - `ProviderReplayFamily`, `buildProviderReplayFamilyHooks(...)`, and the raw replay builders (`buildOpenAICompatibleReplayPolicy`, `buildAnthropicReplayPolicyForModel`, `buildGoogleGeminiReplayPolicy`, `buildHybridAnthropicOrOpenAIReplayPolicy`). Also exports Gemini replay helpers (`sanitizeGoogleGeminiReplayHistory`, `resolveTaggedReasoningOutputMode`) and endpoint/model helpers (`resolveProviderEndpoint`, `normalizeProviderId`, `normalizeGooglePreviewModelId`).

517517

- `openclaw/plugin-sdk/provider-stream` - `ProviderStreamFamily`, `buildProviderStreamFamilyHooks(...)`, `composeProviderStreamWrappers(...)`, plus the shared OpenAI/Codex wrappers (`createOpenAIAttributionHeadersWrapper`, `createOpenAIFastModeWrapper`, `createOpenAIServiceTierWrapper`, `createOpenAIResponsesContextManagementWrapper`, `createCodexNativeWebSearchWrapper`), DeepSeek V4 OpenAI-compatible wrapper (`createDeepSeekV4OpenAICompatibleThinkingWrapper`), Anthropic Messages thinking prefill cleanup (`createAnthropicThinkingPrefillPayloadWrapper`), plain-text tool-call compat (`createPlainTextToolCallCompatWrapper`), and shared proxy/provider wrappers (`createOpenRouterWrapper`, `createToolStreamWrapper`, `createMinimaxFastModeWrapper`).

518-

- `openclaw/plugin-sdk/provider-stream-shared` - lightweight payload and event wrappers for hot provider paths, including `createOpenAICompatibleCompletionsThinkingOffWrapper`, `createPayloadPatchStreamWrapper`, `createPlainTextToolCallCompatWrapper`, and `setQwenChatTemplateThinking(...)`.

518+

- `openclaw/plugin-sdk/provider-stream-shared` - lightweight payload and event wrappers for hot provider paths, including `createOpenAICompatibleCompletionsThinkingOffWrapper`, `createPayloadPatchStreamWrapper`, `createPlainTextToolCallCompatWrapper`, `normalizeOpenAICompatibleReasoningPayload(...)`, and `setQwenChatTemplateThinking(...)`.

519519

- `openclaw/plugin-sdk/provider-tools` - `ProviderToolCompatFamily`, `buildProviderToolCompatFamilyHooks("deepseek" | "gemini" | "openai")`, and underlying provider schema helpers.

520520
521521

For Gemini-family providers, keep the reasoning-output mode aligned with

Original file line numberDiff line numberDiff line change

@@ -164,7 +164,7 @@ and pairing-path families.

164164

| `plugin-sdk/provider-tools` | `ProviderToolCompatFamily`, `buildProviderToolCompatFamilyHooks`, and DeepSeek/Gemini/OpenAI schema cleanup + diagnostics |

165165

| `plugin-sdk/provider-usage` | Provider usage snapshot types, shared usage fetch helpers, and provider fetchers such as `fetchClaudeUsage` |

166166

| `plugin-sdk/provider-stream` | `ProviderStreamFamily`, `buildProviderStreamFamilyHooks`, `composeProviderStreamWrappers`, stream wrapper types, plain-text tool-call compat, and shared Anthropic/Bedrock/DeepSeek V4/Google/Kilocode/Moonshot/OpenAI/OpenRouter/Z.A.I/MiniMax/Copilot wrapper helpers |

167-

| `plugin-sdk/provider-stream-shared` | Public shared provider stream wrapper helpers including `composeProviderStreamWrappers`, `createOpenAICompatibleCompletionsThinkingOffWrapper`, `createPlainTextToolCallCompatWrapper`, `createPayloadPatchStreamWrapper`, `createToolStreamWrapper`, `setQwenChatTemplateThinking`, and Anthropic/DeepSeek/OpenAI-compatible stream utilities |

167+

| `plugin-sdk/provider-stream-shared` | Public shared provider stream wrapper helpers including `composeProviderStreamWrappers`, `createOpenAICompatibleCompletionsThinkingOffWrapper`, `createPlainTextToolCallCompatWrapper`, `createPayloadPatchStreamWrapper`, `createToolStreamWrapper`, `normalizeOpenAICompatibleReasoningPayload`, `setQwenChatTemplateThinking`, and Anthropic/DeepSeek/OpenAI-compatible stream utilities |

168168

| `plugin-sdk/provider-transport-runtime` | Native provider transport helpers such as guarded fetch, transport message transforms, and writable transport event streams |

169169

| `plugin-sdk/provider-onboard` | Onboarding config patch helpers |

170170

| `plugin-sdk/global-singleton` | Process-local singleton/map/cache helpers |

Original file line numberDiff line numberDiff line change

@@ -1,6 +1,7 @@

11

// Kilocode plugin module implements stream behavior.

22

import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";

33

import { resolveProviderRequestHeaders } from "openclaw/plugin-sdk/provider-http";

4+

import { normalizeOpenAICompatibleReasoningPayload } from "openclaw/plugin-sdk/provider-stream-shared";

45

import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";

56
67

const KILOCODE_FEATURE_HEADER = "X-KILOCODE-FEATURE";

@@ -9,52 +10,12 @@ const KILOCODE_FEATURE_ENV_VAR = "KILOCODE_FEATURE";

910
1011

type ThinkLevel = NonNullable<ProviderWrapStreamFnContext["thinkingLevel"]>;

1112

type ProviderStreamFn = NonNullable<ProviderWrapStreamFnContext["streamFn"]>;

12-

type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh";

1313
1414

function resolveKilocodeAppHeaders(): Record<string, string> {

1515

const feature = process.env[KILOCODE_FEATURE_ENV_VAR]?.trim() || KILOCODE_FEATURE_DEFAULT;

1616

return { [KILOCODE_FEATURE_HEADER]: feature };

1717

}

1818
19-

function mapThinkingLevelToReasoningEffort(thinkingLevel: ThinkLevel): ReasoningEffort {

20-

if (thinkingLevel === "off") {

21-

return "none";

22-

}

23-

if (thinkingLevel === "adaptive") {

24-

return "medium";

25-

}

26-

if (thinkingLevel === "max") {

27-

return "xhigh";

28-

}

29-

return thinkingLevel;

30-

}

31-
32-

function normalizeKilocodeReasoningPayload(

33-

payloadObj: Record<string, unknown>,

34-

thinkingLevel?: ThinkLevel,

35-

): void {

36-

delete payloadObj.reasoning_effort;

37-

if (!thinkingLevel || thinkingLevel === "off") {

38-

return;

39-

}

40-
41-

const existingReasoning = payloadObj.reasoning;

42-

if (

43-

existingReasoning &&

44-

typeof existingReasoning === "object" &&

45-

!Array.isArray(existingReasoning)

46-

) {

47-

const reasoningObj = existingReasoning as Record<string, unknown>;

48-

if (!("max_tokens" in reasoningObj) && !("effort" in reasoningObj)) {

49-

reasoningObj.effort = mapThinkingLevelToReasoningEffort(thinkingLevel);

50-

}

51-

} else if (!existingReasoning) {

52-

payloadObj.reasoning = {

53-

effort: mapThinkingLevelToReasoningEffort(thinkingLevel),

54-

};

55-

}

56-

}

57-
5819

function normalizeKilocodeStopPayload(payloadObj: Record<string, unknown>): void {

5920

if (typeof payloadObj.stop === "string") {

6021

payloadObj.stop = [payloadObj.stop];

@@ -122,7 +83,7 @@ export function createKilocodeStreamWrapper(

12283

const payloadObj = asRecord(payload);

12384

if (payloadObj) {

12485

// Keep Kilo thinking defaults overrideable by later caller/config payload hooks.

125-

normalizeKilocodeReasoningPayload(payloadObj, thinkingLevel);

86+

normalizeOpenAICompatibleReasoningPayload(payloadObj, thinkingLevel);

12687

}

12788
12889

const result = originalOnPayload?.(payload, payloadModel);

Original file line numberDiff line numberDiff line change

@@ -163,8 +163,8 @@ let publicDeprecatedExportsByEntrypointBudget;

163163

try {

164164

budgets = {

165165

publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 321),

166-

publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10328),

167-

publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5185),

166+

publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10329),

167+

publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5186),

168168

publicDeprecatedExports: readBudgetEnv(

169169

"OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS",

170170

3247,

Original file line numberDiff line numberDiff line change

@@ -8,13 +8,13 @@ import { resolveProviderRequestPolicyConfig } from "../../../agents/provider-req

88

import type { StreamFn } from "../../../agents/runtime/index.js";

99

import type { ThinkLevel } from "../../../auto-reply/thinking.js";

1010

import { parseStrictFiniteNumber } from "../../../infra/parse-finite-number.js";

11+

import { normalizeOpenAICompatibleReasoningPayload } from "../../../plugin-sdk/provider-stream-shared.js";

1112

import { streamSimple } from "../../stream.js";

1213

import {

1314

applyAnthropicEphemeralCacheControlMarkers,

1415

resolveAnthropicEphemeralCacheControl,

1516

} from "./anthropic-cache-control-payload.js";

1617

import { isAnthropicModelRef } from "./anthropic-family-cache-semantics.js";

17-

import { mapThinkingLevelToReasoningEffort } from "./reasoning-effort-utils.js";

1818

import { streamWithPayloadPatch } from "./stream-payload-utils.js";

1919

const KILOCODE_FEATURE_HEADER = "X-KILOCODE-FEATURE";

2020

const KILOCODE_FEATURE_DEFAULT = "openclaw";

@@ -130,34 +130,6 @@ function resolveOpenRouterResponseCacheHeaders(

130130

return headers;

131131

}

132132
133-

function normalizeProxyReasoningPayload(payload: unknown, thinkingLevel?: ThinkLevel): void {

134-

if (!payload || typeof payload !== "object") {

135-

return;

136-

}

137-
138-

const payloadObj = payload as Record<string, unknown>;

139-

delete payloadObj.reasoning_effort;

140-

if (!thinkingLevel || thinkingLevel === "off") {

141-

return;

142-

}

143-
144-

const existingReasoning = payloadObj.reasoning;

145-

if (

146-

existingReasoning &&

147-

typeof existingReasoning === "object" &&

148-

!Array.isArray(existingReasoning)

149-

) {

150-

const reasoningObj = existingReasoning as Record<string, unknown>;

151-

if (!("max_tokens" in reasoningObj) && !("effort" in reasoningObj)) {

152-

reasoningObj.effort = mapThinkingLevelToReasoningEffort(thinkingLevel);

153-

}

154-

} else if (!existingReasoning) {

155-

payloadObj.reasoning = {

156-

effort: mapThinkingLevelToReasoningEffort(thinkingLevel),

157-

};

158-

}

159-

}

160-
161133

/** @deprecated OpenRouter provider-owned stream helper; do not use from third-party plugins. */

162134

export function createOpenRouterSystemCacheWrapper(

163135

baseStreamFn: StreamFn | undefined,

@@ -246,7 +218,7 @@ export function createOpenRouterWrapper(

246218

headers,

247219

},

248220

(payload) => {

249-

normalizeProxyReasoningPayload(payload, thinkingLevel);

221+

normalizeOpenAICompatibleReasoningPayload(payload, thinkingLevel);

250222

},

251223

);

252224

};

@@ -285,7 +257,7 @@ export function createKilocodeWrapper(

285257

headers,

286258

},

287259

(payload) => {

288-

normalizeProxyReasoningPayload(payload, thinkingLevel);

260+

normalizeOpenAICompatibleReasoningPayload(payload, thinkingLevel);

289261

},

290262

);

291263

};

Original file line numberDiff line numberDiff line change

@@ -13,6 +13,7 @@ import {

1313

createPlainTextToolCallCompatWrapper,

1414

defaultToolStreamExtraParams,

1515

isOpenAICompatibleThinkingEnabled,

16+

normalizeOpenAICompatibleReasoningPayload,

1617

setQwenChatTemplateThinking,

1718

stripTrailingAnthropicAssistantPrefillWhenThinking,

1819

} from "./provider-stream-shared.js";

@@ -193,6 +194,45 @@ describe("setQwenChatTemplateThinking", () => {

193194

});

194195

});

195196
197+

describe("normalizeOpenAICompatibleReasoningPayload", () => {

198+

it("removes the legacy field and adds the selected reasoning effort", () => {

199+

const payload: Record<string, unknown> = {

200+

reasoning_effort: "high",

201+

};

202+
203+

normalizeOpenAICompatibleReasoningPayload(payload, "adaptive");

204+
205+

expect(payload).toEqual({ reasoning: { effort: "medium" } });

206+

});

207+
208+

it("preserves explicit reasoning controls", () => {

209+

const withMaxTokens: Record<string, unknown> = {

210+

reasoning_effort: "high",

211+

reasoning: { max_tokens: 256 },

212+

};

213+

const withEffort: Record<string, unknown> = {

214+

reasoning_effort: "high",

215+

reasoning: { effort: "low", summary: "auto" },

216+

};

217+
218+

normalizeOpenAICompatibleReasoningPayload(withMaxTokens, "high");

219+

normalizeOpenAICompatibleReasoningPayload(withEffort, "high");

220+
221+

expect(withMaxTokens).toEqual({ reasoning: { max_tokens: 256 } });

222+

expect(withEffort).toEqual({ reasoning: { effort: "low", summary: "auto" } });

223+

});

224+
225+

it("removes only the legacy field when thinking is disabled", () => {

226+

const payload: Record<string, unknown> = {

227+

reasoning_effort: "high",

228+

};

229+
230+

normalizeOpenAICompatibleReasoningPayload(payload, "off");

231+
232+

expect(payload).toEqual({});

233+

});

234+

});

235+
196236

describe("createDeepSeekV4OpenAICompatibleThinkingWrapper", () => {

197237

it("backfills reasoning_content on every replayed assistant message when thinking is enabled", () => {

198238

const payload = {

Original file line numberDiff line numberDiff line change

@@ -13,6 +13,7 @@ import { resolveOpenAIReasoningEffortMap } from "../agents/openai-reasoning-comp

1313

import { resolveOpenAIReasoningEffortForModel } from "../agents/openai-reasoning-effort.js";

1414

import type { StreamFn } from "../agents/runtime/index.js";

1515

import type { ThinkLevel } from "../auto-reply/thinking.js";

16+

import { mapThinkingLevelToReasoningEffort } from "../llm/providers/stream-wrappers/reasoning-effort-utils.js";

1617

import { streamWithPayloadPatch } from "../llm/providers/stream-wrappers/stream-payload-utils.js";

1718

import { streamSimple } from "../llm/stream.js";

1819

import { createAssistantMessageEventStream } from "../llm/utils/event-stream.js";

@@ -411,6 +412,33 @@ export function isOpenAICompatibleThinkingEnabled(params: {

411412

return normalized !== "off" && normalized !== "none";

412413

}

413414
415+

/** Applies the shared reasoning payload policy used by OpenAI-compatible proxy providers. */

416+

export function normalizeOpenAICompatibleReasoningPayload(

417+

payload: Record<string, unknown>,

418+

thinkingLevel?: ThinkLevel,

419+

): void {

420+

delete payload.reasoning_effort;

421+

if (!thinkingLevel || thinkingLevel === "off") {

422+

return;

423+

}

424+
425+

const existingReasoning = payload.reasoning;

426+

if (

427+

existingReasoning &&

428+

typeof existingReasoning === "object" &&

429+

!Array.isArray(existingReasoning)

430+

) {

431+

const reasoning = existingReasoning as Record<string, unknown>;

432+

if (!("max_tokens" in reasoning) && !("effort" in reasoning)) {

433+

reasoning.effort = mapThinkingLevelToReasoningEffort(thinkingLevel);

434+

}

435+

} else if (!existingReasoning) {

436+

payload.reasoning = {

437+

effort: mapThinkingLevelToReasoningEffort(thinkingLevel),

438+

};

439+

}

440+

}

441+
414442

/** Applies Qwen chat-template thinking flags without discarding provider-specific kwargs. */

415443

export function setQwenChatTemplateThinking(

416444

payload: Record<string, unknown>,