fix(agent): stream tool input as it arrives during execution#1994
Open
fix(agent): stream tool input as it arrives during execution#1994
Conversation
Surface tool args while a tool is still running instead of waiting for the finalized assistant message. Accumulate `input_json_delta` events per content-block index, parse the partial JSON best-effort, and emit `tool_call_update`s with the parsed-so-far rawInput. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
packages/agent/src/adapters/claude/types.ts:79-85
**`toolName` is stored but never read**
`toolName` is written into every cache entry but is not accessed in `inputJsonDeltaToAcpNotifications` (or anywhere else in the diff). This is a superfluous part — only `toolUseId` and `partialJson` are actually used. Removing it keeps the type honest and avoids confusion about why the name was cached.
### Issue 2 of 3
packages/agent/src/utils/partial-json.test.ts:5-14
**Prefer parameterised tests**
Several `it` blocks group multiple independent input/output pairs as sequential `expect` calls. The team prefers `it.each` for this pattern so each case gets its own label and failure message. For example:
```ts
it.each([
["", null],
[" ", null],
] as const)("returns null for %j", (input, expected) => {
expect(tryParsePartialJson(input)).toBe(expected);
});
```
This pattern also applies to "drops a trailing partial key" and "handles nested objects and arrays mid-stream". The incremental-sequence test is fine as-is since it tests a stateful progression rather than independent cases.
### Issue 3 of 3
packages/agent/src/utils/partial-json.ts:13
**`unknown | null` return type is redundant**
`null` is already assignable to `unknown`, so `unknown | null` simplifies to `unknown`. The explicit `| null` adds noise without adding type safety — callers still have to narrow via `!parsed` check anyway.
Reviews (1): Last reviewed commit: "fix(agent): stream tool input as it arri..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a tool is running, the conversation row shows the tool name but no input args. The args only appear once the tool result lands. For fast tools the gap is invisible; for slow ones (PostHog MCP HTTP calls especially) the user stares at an in-flight row with empty args.
Cause: the agent emits the
tool_callfrom the Anthropiccontent_block_startevent, which always carriesinput: {}. The full input streams in viainput_json_deltaevents, which the chunk handler was dropping. The fullrawInputonly ever reached the renderer when the finalizedSDKAssistantMessagearrived — sometimes after the tool had already returned.Changes
tryParsePartialJsonhelper that walks the streamed JSON tracking quote/escape/bracket state and tries a couple of completions (close any open string, drop trailing comma/colon/partial key, balance brackets). Returnsnullif no completion parses, so callers silently skip un-parseable deltas.streamEventToAcpNotificationsnow:content_block_startfortool_use/mcp_tool_use, registers a per-content-block-index buffer.content_block_deltawithinput_json_delta, appends the partial JSON, parses it best-effort, and emits atool_call_updatecarrying the parsed-so-farrawInput.content_block_stop, clears the per-index entry.clear()ed inprompt()'sfinallyso a cancelled or errored turn can't leak partial entries into the next turn.How did you test this code?
partial-json.test.tscovering empty input, complete JSON, mid-stream state (open string, missing closer), escaped quotes, partial keys, nested objects/arrays, an incremental simulation of a real exec command, and the unparseable-garbage path.pnpm --filter agent typecheckclean; full agent suite (313 tests) passes.mcp__posthog__execcall execute-sql {...}invocation: the args now stream into the row in real time instead of appearing only after the tool returned.Publish to changelog?
no