From c3c644ff8c07e8a210e22c50ce5283bf53b1ac94 Mon Sep 17 00:00:00 2001 From: CJACK Date: Sun, 29 Mar 2026 19:49:52 +0800 Subject: [PATCH] 111 --- internal/js/helpers/stream-tool-sieve/sieve.js | 7 +++++-- tests/node/stream-tool-sieve.test.js | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/internal/js/helpers/stream-tool-sieve/sieve.js b/internal/js/helpers/stream-tool-sieve/sieve.js index fca7683..9fa5b4c 100644 --- a/internal/js/helpers/stream-tool-sieve/sieve.js +++ b/internal/js/helpers/stream-tool-sieve/sieve.js @@ -116,8 +116,11 @@ function flushToolSieve(state, toolNames) { events.push({ type: 'text', text: consumed.suffix }); } } else if (state.capture) { - noteText(state, state.capture); - events.push({ type: 'text', text: state.capture }); + const content = state.capture; + if (!hasOpenXMLToolTag(content) && !looksLikeXMLToolTagFragment(content)) { + noteText(state, content); + events.push({ type: 'text', text: content }); + } } state.capture = ''; state.capturing = false; diff --git a/tests/node/stream-tool-sieve.test.js b/tests/node/stream-tool-sieve.test.js index 36ee798..936a815 100644 --- a/tests/node/stream-tool-sieve.test.js +++ b/tests/node/stream-tool-sieve.test.js @@ -213,6 +213,22 @@ test('sieve flushes incomplete captured tool json as text on stream finalize', ( assert.equal(leakedText.includes('{'), true); }); +test('sieve flushes incomplete captured XML tool blocks without leaking raw tags', () => { + const events = runSieve( + [ + '前置正文G。', + '\n', + ' \n', + ' read_file\n', + ], + ['read_file'], + ); + const leakedText = collectText(events); + assert.equal(leakedText.includes('前置正文G。'), true); + assert.equal(leakedText.toLowerCase().includes('tool_calls'), false); + assert.equal(leakedText.includes(' { const large = 'a'.repeat(9000); const payload = `{"tool_calls":[{"name":"read_file","input":{"path":"${large}"}}]}`;