Skip to content

Subprocess tool-use / thinking / usage / compact-boundary parity with SDK (agent-sidepanel-v2 Increment 2 follow-up) #383

@Luis85

Description

@Luis85

Context

PR #378 (StreamDelta union extension) adds thinking, tool-use-start, tool-use-input-delta, tool-use-stop, compact-boundary, and usage variants — fully wired on the SDK path (ClaudeCliAdapter._dispatchMessage).

The subprocess path (ClaudeSubprocessAdapter._handleNdjsonLine) was deferred:

Deferred to follow-up PRs:

Subprocess parityClaudeSubprocessAdapter._handleNdjsonLine uses a different wire format (system/init / assistant/message / result, not stream_event content blocks). Extending it for tool-use parity needs the CLI's actual --include-partial-messages event surface verified against a real binary.

— from PR #378 description.

This issue tracks closing that gap so the subscription transport gets the same tool-use / thinking / usage / compact-boundary coverage as the SDK transport.

Why it matters

Today, a user on the subscription transport (transportKind === 'subscription'ClaudeSubprocessAdapter) sends a turn that produces tool calls or extended thinking, and the chat UI:

  • Sees only text deltas (existing behaviour) — falls back to the pre-extension experience.
  • Misses tool-call cards, thinking-block panels, usage telemetry, compaction notices.

That's a transport-dependent UX regression — the SDK transport user gets one experience, the subscription user gets another.

Wire format gap

The SDK adapter already maps Anthropic stream_event content blocks:

  • system + subtype === 'init'session-id
  • system + subtype === 'compact_boundary'compact-boundary
  • stream_event + content_block_start (type === 'tool_use') → tool-use-start
  • stream_event + content_block_delta:
    • text_deltatext
    • thinking_deltathinking
    • input_json_deltatool-use-input-delta
  • stream_event + content_block_stop (tool_use only) → tool-use-stop
  • stream_event + message_start / message_delta + .usageusage

The subprocess emits a different envelope when --output-format stream-json --include-partial-messages is set. Need to verify the actual event names against a current claude binary, then implement the same dispatcher in _handleNdjsonLine.

Plan

Step 1 — capture real wire output

Run locally:

echo "Use the Bash tool to list /tmp" | claude -p \
  --output-format stream-json \
  --verbose \
  --include-partial-messages \
  --allowedTools Bash \
  > /tmp/wire.ndjson 2>&1

Note: that command will trigger the real Anthropic Terms of Service flow. Run it against a permissive sandbox vault.

Document the event names + payload shapes in inputs/subproc-tool-wire-2026-XX/ so future contributors don't have to re-capture.

Step 2 — extend _handleNdjsonLine

Mirror the SDK dispatcher. Use the same blockId = ${turnId}-${index} pattern from PR #378 for tool blocks. Surface the same StreamSink (or createPushChannel) helper.

Watch out for:

  • The subprocess buffers chunks across \n boundaries — already handled by the existing stdoutBuffer.
  • --include-partial-messages may produce duplicate emits (content_block_delta followed by content_block_stop with the same final fragment); Claudian PR #510 fixed similar dedup. Add a per-blockId "already-stopped" guard.

Step 3 — tests

Step 4 — MessageList.vue scroll watcher

PR #379 added a tool-call input-growth observer to the scroll watcher. Verify it works against the subprocess transport too (same streamingToolCalls map → same observable).

Acceptance

  • Wire capture documented under inputs/.
  • ClaudeSubprocessAdapter._handleNdjsonLine emits all new StreamDelta variants.
  • Block dedup guard for content_block_stop-after-_delta race.
  • Tests for every new variant on the subprocess adapter.
  • All 1500+ existing tests still green.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions