From 2d5103997b849d31bcf0de11b42aa57d3e738e3d Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Fri, 20 Mar 2026 01:15:15 +0800 Subject: [PATCH] fix(tool-sieve): keep mixed prose tool json in strict text mode --- .../js/helpers/stream-tool-sieve/sieve.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/internal/js/helpers/stream-tool-sieve/sieve.js b/internal/js/helpers/stream-tool-sieve/sieve.js index a3b7fd8..ef12e8b 100644 --- a/internal/js/helpers/stream-tool-sieve/sieve.js +++ b/internal/js/helpers/stream-tool-sieve/sieve.js @@ -235,6 +235,17 @@ function consumeToolCapture(state, toolNames) { }; } + // Strict standalone mode: if the stream already produced meaningful prose, + // treat later tool-looking JSON as plain text instead of intercepting. + if ((state.recentTextTail || '').trim() !== '' && prefixPart.trim() === '') { + return { + ready: true, + prefix: captured, + calls: [], + suffix: '', + }; + } + const parsed = parseStandaloneToolCallsDetailed(captured.slice(actualStart, obj.end), toolNames); if (!Array.isArray(parsed.calls) || parsed.calls.length === 0) { if (parsed.sawToolCallSyntax && parsed.rejectedByPolicy) { @@ -253,6 +264,18 @@ function consumeToolCapture(state, toolNames) { }; } + // Strict standalone mode: only intercept when the tool payload stands alone + // (allowing only surrounding whitespace). If there is non-whitespace prose + // before/after the JSON object, keep everything as normal text. + if (prefixPart.trim() !== '' || suffixPart.trim() !== '') { + return { + ready: true, + prefix: captured, + calls: [], + suffix: '', + }; + } + return { ready: true, prefix: prefixPart,