diff --git a/internal/js/helpers/stream-tool-sieve/sieve.js b/internal/js/helpers/stream-tool-sieve/sieve.js index 23e477d..3250c86 100644 --- a/internal/js/helpers/stream-tool-sieve/sieve.js +++ b/internal/js/helpers/stream-tool-sieve/sieve.js @@ -197,11 +197,7 @@ function findToolSegmentStart(state, s) { } const keyIdx = bestKeyIdx; const start = s.slice(0, keyIdx).lastIndexOf('{'); - if (start < 0) { - offset = keyIdx + matchedKeyword.length; - continue; - } - let candidateStart = start; + let candidateStart = start >= 0 ? start : keyIdx; // If the keyword matched inside an XML tag (e.g. "tool_calls" in ""), // back up past the '<' to capture the full tag. if (candidateStart > 0 && s[candidateStart - 1] === '<') { diff --git a/internal/js/helpers/stream-tool-sieve/state.js b/internal/js/helpers/stream-tool-sieve/state.js index 74d1904..df82404 100644 --- a/internal/js/helpers/stream-tool-sieve/state.js +++ b/internal/js/helpers/stream-tool-sieve/state.js @@ -1,6 +1,6 @@ 'use strict'; -const TOOL_SIEVE_CONTEXT_TAIL_LIMIT = 4096; +const TOOL_SIEVE_CONTEXT_TAIL_LIMIT = 256; function createToolSieveState() { return { diff --git a/tests/node/stream-tool-sieve.test.js b/tests/node/stream-tool-sieve.test.js index e072373..8f8a2bd 100644 --- a/tests/node/stream-tool-sieve.test.js +++ b/tests/node/stream-tool-sieve.test.js @@ -252,7 +252,7 @@ test('sieve keeps plain text intact in tool mode when no tool call appears', () assert.equal(leakedText, '你好,这是普通文本回复。请继续。'); }); -test('sieve does not start capture on plain "tool_calls" prose without opening json brace', () => { +test('sieve keeps plain "tool_calls" prose as text when no valid payload follows', () => { const events = runSieve( ['前置。', '这里提到 tool_calls 只是解释,不是调用。', '后置。'], ['read_file'],