

























@@ -60,6 +60,7 @@ const END_TOOL_REQUEST = "[END_TOOL_REQUEST]";
6060const HARMONY_CHANNEL_MARKER = "<|channel|>";
6161const HARMONY_MESSAGE_MARKER = "<|message|>";
6262const HARMONY_CALL_MARKER = "<|call|>";
63+const XMLISH_PARAMETER_CLOSE = "</parameter>";
63646465type PlainTextToolCallOpening = {
6566end: number;
@@ -168,6 +169,14 @@ function parseHarmonyOpening(text: string, start: number): PlainTextToolCallOpen
168169return { 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+171180function parseOpening(text: string, start: number): PlainTextToolCallOpening | null {
172181return 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+285366export function parseStandalonePlainTextToolCallBlocks(
286367text: string,
287368options?: PlainTextToolCallParseOptions,
@@ -303,7 +384,8 @@ export function stripPlainTextToolCallBlocks(text: string): string {
303384if (
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) {
308390return text;
309391}
@@ -318,12 +400,13 @@ export function stripPlainTextToolCallBlocks(text: string): string {
318400}
319401const blockStart = skipHorizontalWhitespace(text, index);
320402const block = parsePlainTextToolCallBlockAt(text, blockStart);
321-if (!block) {
403+const blockEnd = block?.end ?? parseXmlishPlainTextToolCallBlockEndAt(text, blockStart);
404+if (blockEnd === null) {
322405index += 1;
323406continue;
324407}
325408result += text.slice(cursor, index);
326-cursor = block.end;
409+cursor = blockEnd;
327410const afterBlockLineBreak = consumeLineBreak(text, cursor);
328411if (afterBlockLineBreak !== null) {
329412cursor = afterBlockLineBreak;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。