From d2d4e39983d46f016d3a403c5268168210810bf8 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Sun, 22 Mar 2026 15:28:51 +0800 Subject: [PATCH 1/3] Fix refactor line gate for stream tool sieve helper --- internal/js/helpers/stream-tool-sieve/sieve.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/js/helpers/stream-tool-sieve/sieve.js b/internal/js/helpers/stream-tool-sieve/sieve.js index fe90901..c567545 100644 --- a/internal/js/helpers/stream-tool-sieve/sieve.js +++ b/internal/js/helpers/stream-tool-sieve/sieve.js @@ -6,7 +6,6 @@ const { } = require('./state'); const { parseStandaloneToolCallsDetailed } = require('./parse'); const { extractJSONObjectFrom } = require('./jsonscan'); - function processToolSieveChunk(state, chunk, toolNames) { if (!state) { return []; @@ -285,7 +284,6 @@ function trimWrappingJSONFence(prefix, suffix) { if (header && header !== 'json') { return { prefix, suffix }; } - const leftTrimmedSuffix = (suffix || '').replace(/^[ \t\r\n]+/g, ''); if (!leftTrimmedSuffix.startsWith('```')) { return { prefix, suffix }; @@ -296,7 +294,6 @@ function trimWrappingJSONFence(prefix, suffix) { suffix: (suffix || '').slice(consumed + 3), }; } - module.exports = { processToolSieveChunk, flushToolSieve, From d5a23191f2ba054e009547c470f0e121c91bb5c8 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Sun, 22 Mar 2026 15:55:38 +0800 Subject: [PATCH 2/3] Refactor stream sieve keyword scanning into shared helper --- .../js/helpers/stream-tool-sieve/sieve.js | 23 ++------------- .../stream-tool-sieve/tool-keywords.js | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 internal/js/helpers/stream-tool-sieve/tool-keywords.js diff --git a/internal/js/helpers/stream-tool-sieve/sieve.js b/internal/js/helpers/stream-tool-sieve/sieve.js index c567545..b930b25 100644 --- a/internal/js/helpers/stream-tool-sieve/sieve.js +++ b/internal/js/helpers/stream-tool-sieve/sieve.js @@ -6,6 +6,7 @@ const { } = require('./state'); const { parseStandaloneToolCallsDetailed } = require('./parse'); const { extractJSONObjectFrom } = require('./jsonscan'); +const { TOOL_SEGMENT_KEYWORDS, earliestKeywordIndex } = require('./tool-keywords'); function processToolSieveChunk(state, chunk, toolNames) { if (!state) { return []; @@ -151,20 +152,9 @@ function findToolSegmentStart(state, s) { return -1; } const lower = s.toLowerCase(); - const keywords = ['tool_calls', 'function.name:', '[tool_call_history]', '[tool_result_history]']; let offset = 0; while (true) { - let bestKeyIdx = -1; - let matchedKeyword = ''; - for (const kw of keywords) { - const idx = lower.indexOf(kw, offset); - if (idx >= 0) { - if (bestKeyIdx < 0 || idx < bestKeyIdx) { - bestKeyIdx = idx; - matchedKeyword = kw; - } - } - } + const { index: bestKeyIdx, keyword: matchedKeyword } = earliestKeywordIndex(lower, TOOL_SEGMENT_KEYWORDS, offset); if (bestKeyIdx < 0) { return -1; } @@ -184,14 +174,7 @@ function consumeToolCapture(state, toolNames) { return { ready: false, prefix: '', calls: [], suffix: '' }; } const lower = captured.toLowerCase(); - let keyIdx = -1; - const keywords = ['tool_calls', 'function.name:', '[tool_call_history]', '[tool_result_history]']; - for (const kw of keywords) { - const idx = lower.indexOf(kw); - if (idx >= 0 && (keyIdx < 0 || idx < keyIdx)) { - keyIdx = idx; - } - } + const { index: keyIdx } = earliestKeywordIndex(lower, TOOL_SEGMENT_KEYWORDS); if (keyIdx < 0) { return { ready: false, prefix: '', calls: [], suffix: '' }; } diff --git a/internal/js/helpers/stream-tool-sieve/tool-keywords.js b/internal/js/helpers/stream-tool-sieve/tool-keywords.js new file mode 100644 index 0000000..34cd226 --- /dev/null +++ b/internal/js/helpers/stream-tool-sieve/tool-keywords.js @@ -0,0 +1,29 @@ +'use strict'; + +const TOOL_SEGMENT_KEYWORDS = [ + 'tool_calls', + 'function.name:', + '[tool_call_history]', + '[tool_result_history]', +]; + +function earliestKeywordIndex(text, keywords = TOOL_SEGMENT_KEYWORDS, offset = 0) { + if (!text) { + return { index: -1, keyword: '' }; + } + let index = -1; + let keyword = ''; + for (const kw of keywords) { + const candidate = text.indexOf(kw, offset); + if (candidate >= 0 && (index < 0 || candidate < index)) { + index = candidate; + keyword = kw; + } + } + return { index, keyword }; +} + +module.exports = { + TOOL_SEGMENT_KEYWORDS, + earliestKeywordIndex, +}; From add13366d2d549d7d8183c5597bc359dfce361e4 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Sun, 22 Mar 2026 15:55:47 +0800 Subject: [PATCH 3/3] Split parse syntax markers to shared keyword module --- .../js/helpers/stream-tool-sieve/parse.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/internal/js/helpers/stream-tool-sieve/parse.js b/internal/js/helpers/stream-tool-sieve/parse.js index 21378eb..132baff 100644 --- a/internal/js/helpers/stream-tool-sieve/parse.js +++ b/internal/js/helpers/stream-tool-sieve/parse.js @@ -10,8 +10,10 @@ const { parseTextKVToolCalls, stripFencedCodeBlocks, } = require('./parse_payload'); +const { TOOL_SEGMENT_KEYWORDS } = require('./tool-keywords'); const TOOL_NAME_LOOSE_PATTERN = /[^a-z0-9]+/g; +const TOOL_MARKUP_PREFIXES = [' lower.includes(kw)) + || TOOL_MARKUP_PREFIXES.some((prefix) => lower.includes(prefix)); } function shouldSkipToolCallParsingForCodeFenceExample(text) { - if (!looksLikeToolCallSyntax(text) || looksLikeMarkupToolSyntax(text)) { - return false; - } - const stripped = stripFencedCodeBlocks(text); - return !looksLikeToolCallSyntax(stripped); -} - -function looksLikeMarkupToolSyntax(text) { - const raw = toStringSafe(text); - if (!raw) { return false; } return /<(?:(?:[a-z0-9_:-]+:)?(?:tool_call|function_call|invoke)\b)/i.test(raw)