Skip to content

fix(agent): stream tool input as it arrives during execution#1994

Open
skoob13 wants to merge 2 commits intomainfrom
fix/streaming-tool-input-display
Open

fix(agent): stream tool input as it arrives during execution#1994
skoob13 wants to merge 2 commits intomainfrom
fix/streaming-tool-input-display

Conversation

@skoob13
Copy link
Copy Markdown
Contributor

@skoob13 skoob13 commented May 4, 2026

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_call from the Anthropic content_block_start event, which always carries input: {}. The full input streams in via input_json_delta events, which the chunk handler was dropping. The full rawInput only ever reached the renderer when the finalized SDKAssistantMessage arrived — sometimes after the tool had already returned.

2026-05-04 13 46 33

Changes

  • New tryParsePartialJson helper 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). Returns null if no completion parses, so callers silently skip un-parseable deltas.
  • streamEventToAcpNotifications now:
    • On content_block_start for tool_use / mcp_tool_use, registers a per-content-block-index buffer.
    • On content_block_delta with input_json_delta, appends the partial JSON, parses it best-effort, and emits a tool_call_update carrying the parsed-so-far rawInput.
    • On content_block_stop, clears the per-index entry.
  • The cache is also clear()ed in prompt()'s finally so a cancelled or errored turn can't leak partial entries into the next turn.

How did you test this code?

  • 11 unit tests in partial-json.test.ts covering 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 typecheck clean; full agent suite (313 tests) passes.
  • Manually exercised in PostHog Code against a slow mcp__posthog__exec call 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

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>
@skoob13 skoob13 requested review from a team May 4, 2026 11:53
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Prompt To Fix All With AI
Fix 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

Comment thread packages/agent/src/adapters/claude/types.ts
Comment thread packages/agent/src/utils/partial-json.test.ts
Comment thread packages/agent/src/utils/partial-json.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant