Repository navigation
fix(core): preserve no-argument tool calls that stream an empty arguments string #6250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
2a02b5d
0d2b91d
a5db830
f1efd7f
096eea5
852e763
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,7 +265,7 @@ export class StreamingToolCallParser { | |
|
|
||
| for (const [index, buffer] of this.buffers.entries()) { | ||
| const meta = this.toolCallMeta.get(index); | ||
| if (meta?.name && buffer.trim()) { | ||
| if (meta?.name) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] The guard relaxation here (
None of these paths are covered by the new tests (they require multi-tool-call interleaving). The fix for all three is the same — replace — qwen3.7-max via Qwen Code /review |
||
| if (meta.id) { | ||
| if (emittedIds.has(meta.id)) { | ||
| continue; | ||
|
|
@@ -275,21 +275,27 @@ export class StreamingToolCallParser { | |
|
|
||
| let args: Record<string, unknown> = {}; | ||
|
|
||
| // Try to parse the final buffer | ||
| try { | ||
| args = JSON.parse(buffer); | ||
| } catch { | ||
| // Try with repair (auto-close strings) | ||
| const inString = this.inStrings.get(index); | ||
| if (inString) { | ||
| try { | ||
| args = JSON.parse(buffer + '"'); | ||
| } catch { | ||
| // If all parsing fails, use safeJsonParse as fallback | ||
| // An empty buffer is a legal end state: for no-argument tools some | ||
| // providers stream `arguments: ""` (or omit the field entirely) and | ||
| // never send an argument fragment. Keep args as {} to match the | ||
| // non-streaming path in converter.ts. | ||
| if (buffer.trim()) { | ||
| // Try to parse the final buffer | ||
| try { | ||
| args = JSON.parse(buffer); | ||
| } catch { | ||
| // Try with repair (auto-close strings) | ||
| const inString = this.inStrings.get(index); | ||
| if (inString) { | ||
| try { | ||
| args = JSON.parse(buffer + '"'); | ||
| } catch { | ||
| // If all parsing fails, use safeJsonParse as fallback | ||
| args = safeJsonParse(buffer, {}); | ||
| } | ||
| } else { | ||
| args = safeJsonParse(buffer, {}); | ||
| } | ||
| } else { | ||
| args = safeJsonParse(buffer, {}); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] This test uses weaker assertions than the empty-buffer test above (line 398). The empty-buffer test uses
expect(completed).toEqual([{ id: 'call_1', name: 'function1', args: {}, index: 0 }])(full shape), while this one only checkstoHaveLength(1)andcompleted[0].args. It would not catch metadata corruption or wrong-index routing.— qwen3.7-max via Qwen Code /review