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

推荐订阅源

L
LangChain Blog
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Help Net Security
MyScale Blog
MyScale Blog
WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
Martin Fowler
Martin Fowler
Vercel News
Vercel News
S
SegmentFault 最新的问题
M
MIT News - Artificial intelligence
Microsoft Security Blog
Microsoft Security Blog
G
Google Developers Blog
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
云风的 BLOG
云风的 BLOG

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
fix: scrub serialized tool-call text from replies (#86924...
fuller-stack · 2026-05-27 · via Recent Commits to openclaw:main

@@ -60,6 +60,7 @@ const END_TOOL_REQUEST = "[END_TOOL_REQUEST]";

6060

const HARMONY_CHANNEL_MARKER = "<|channel|>";

6161

const HARMONY_MESSAGE_MARKER = "<|message|>";

6262

const HARMONY_CALL_MARKER = "<|call|>";

63+

const XMLISH_PARAMETER_CLOSE = "</parameter>";

63646465

type PlainTextToolCallOpening = {

6566

end: number;

@@ -168,6 +169,14 @@ function parseHarmonyOpening(text: string, start: number): PlainTextToolCallOpen

168169

return { end: cursor, name, requiresClosing: false };

169170

}

170171172+

function parseXmlishFunctionOpening(text: string, start: number): PlainTextToolCallOpening | null {

173+

const match = /^<function=([A-Za-z0-9_.:-]{1,120})>\s*/i.exec(text.slice(start));

174+

if (!match?.[1]) {

175+

return null;

176+

}

177+

return { end: start + match[0].length, name: match[1], requiresClosing: false };

178+

}

179+171180

function parseOpening(text: string, start: number): PlainTextToolCallOpening | null {

172181

return parseBracketOpening(text, start) ?? parseHarmonyOpening(text, start);

173182

}

@@ -282,6 +291,78 @@ function parsePlainTextToolCallBlockAt(

282291

};

283292

}

284293294+

function consumeXmlishParameterBlock(

295+

text: string,

296+

start: number,

297+

maxPayloadBytes: number,

298+

): number | null {

299+

const cursor = skipWhitespace(text, start);

300+

const openMatch = /^<parameter=[A-Za-z0-9_.:-]{1,120}>\s*/i.exec(text.slice(cursor));

301+

if (!openMatch) {

302+

return null;

303+

}

304+

const payloadStart = cursor + openMatch[0].length;

305+

const closeStart = text.toLowerCase().indexOf(XMLISH_PARAMETER_CLOSE, payloadStart);

306+

if (closeStart === -1 || closeStart + XMLISH_PARAMETER_CLOSE.length - cursor > maxPayloadBytes) {

307+

return null;

308+

}

309+

return closeStart + XMLISH_PARAMETER_CLOSE.length;

310+

}

311+312+

function consumeXmlishParameterBlocks(

313+

text: string,

314+

start: number,

315+

maxPayloadBytes: number,

316+

): number | null {

317+

let cursor = start;

318+

let consumed = false;

319+

while (true) {

320+

const next = consumeXmlishParameterBlock(text, cursor, maxPayloadBytes);

321+

if (next === null) {

322+

break;

323+

}

324+

if (next - start > maxPayloadBytes) {

325+

return null;

326+

}

327+

cursor = next;

328+

consumed = true;

329+

}

330+

return consumed ? cursor : null;

331+

}

332+333+

function consumeOptionalXmlishFunctionClose(text: string, start: number): number {

334+

const cursor = skipWhitespace(text, start);

335+

return text.slice(cursor).toLowerCase().startsWith("</function>")

336+

? cursor + "</function>".length

337+

: start;

338+

}

339+340+

function parseXmlishPlainTextToolCallBlockEndAt(

341+

text: string,

342+

start: number,

343+

options?: PlainTextToolCallParseOptions,

344+

): number | null {

345+

const opening = parseBracketOpening(text, start) ?? parseXmlishFunctionOpening(text, start);

346+

if (!opening) {

347+

return null;

348+

}

349+

const allowedToolNames = options?.allowedToolNames

350+

? new Set(options.allowedToolNames)

351+

: undefined;

352+

if (allowedToolNames && !allowedToolNames.has(opening.name)) {

353+

return null;

354+

}

355+

const payloadEnd = consumeXmlishParameterBlocks(

356+

text,

357+

opening.end,

358+

options?.maxPayloadBytes ?? DEFAULT_MAX_PLAIN_TEXT_TOOL_PAYLOAD_BYTES,

359+

);

360+

if (payloadEnd === null) {

361+

return null;

362+

}

363+

return consumeOptionalXmlishFunctionClose(text, payloadEnd);

364+

}

365+285366

export function parseStandalonePlainTextToolCallBlocks(

286367

text: string,

287368

options?: PlainTextToolCallParseOptions,

@@ -303,7 +384,8 @@ export function stripPlainTextToolCallBlocks(text: string): string {

303384

if (

304385

!text ||

305386

(!/\[(?:tool:)?[A-Za-z0-9_-]+\]/.test(text) &&

306-

!/(?:^|\n)\s*(?:<\|channel\|>)?(?:commentary|analysis|final)\s+to=/.test(text))

387+

!/(?:^|\n)\s*(?:<\|channel\|>)?(?:commentary|analysis|final)\s+to=/.test(text) &&

388+

!/(?:^|\n)\s*<function=[A-Za-z0-9_.:-]{1,120}>/i.test(text))

307389

) {

308390

return text;

309391

}

@@ -318,12 +400,13 @@ export function stripPlainTextToolCallBlocks(text: string): string {

318400

}

319401

const blockStart = skipHorizontalWhitespace(text, index);

320402

const block = parsePlainTextToolCallBlockAt(text, blockStart);

321-

if (!block) {

403+

const blockEnd = block?.end ?? parseXmlishPlainTextToolCallBlockEndAt(text, blockStart);

404+

if (blockEnd === null) {

322405

index += 1;

323406

continue;

324407

}

325408

result += text.slice(cursor, index);

326-

cursor = block.end;

409+

cursor = blockEnd;

327410

const afterBlockLineBreak = consumeLineBreak(text, cursor);

328411

if (afterBlockLineBreak !== null) {

329412

cursor = afterBlockLineBreak;