5.2 KiB
Richer Stdout Display
Deliverable: Improve the NDJSON stream filter so the operator can follow Claude's full reasoning. Currently assistant text is truncated to 3 lines / 120 chars and many event types are silently dropped. No new crates — pure std only.
Context
The stream filter in src/stream.rs reads Claude's NDJSON output and formats it as ANSI-colored terminal output. Current problems:
-
Assistant text is hard-capped at 3 lines and 120 chars per line. This cuts off Claude's thinking and explanations — the operator has no idea what it's doing.
-
The
stream_eventwrapper type (the majority of NDJSON lines) is silently dropped. These carrycontent_block_deltaevents with partial text and input JSON, which are useful for streaming progress. -
Tool results are invisible — the
userevent type (which carries tool_result content) is never matched.
Goal: Show Claude's full text output, stream partial text deltas as they arrive, and show a brief summary of tool results.
Implementation Stages
Stage A: Remove text truncation, show full assistant output
Objective: Stop cutting off assistant text so the operator sees everything.
src/stream.rs — in the Some("assistant") branch, the text display path:
Current code truncates: for text_line in trimmed.lines().take(3) { let truncated = if text_line.len() > 120 { &text_line[..120] } else { text_line }; println!("{} {}{}", DIM, truncated, RESET); }
Change to print all lines with no length limit: for text_line in trimmed.lines() { println!("{} {}{}", DIM, text_line, RESET); }
Verify: cargo check -p yoke passes.
Stage B: Display streaming text deltas
Objective: Show content_block_delta events so text streams to the terminal
as Claude thinks, rather than appearing only in the final assistant message.
src/stream.rs — add a Some("stream_event") match arm. These lines wrap inner
events in a {"type":"stream_event","event":{...}} envelope.
The inner event types to handle:
content_block_delta with "text_delta":
The delta has {"type":"text_delta","text":"..."}. Extract the text field
from the inner delta and print it inline (no newline — use print! not
println!) so streaming text accumulates naturally:
print!("{}{}{}", DIM, text, RESET);
Flush stdout after each delta.
content_block_delta with "input_json_delta":
These are partial tool input being streamed. Skip these silently — the
complete tool call will be shown when the full assistant event arrives.
content_block_start:
If it contains "tool_use", print nothing (the full assistant event will
show the tool call). If it contains "text", print a newline to start
a fresh line for streaming text.
content_block_stop:
Print a newline to terminate any streaming text on the current line.
message_start, message_delta, message_stop:
Skip silently — these are bookkeeping.
To detect the inner event type, use extract_str on the line for the "type"
field inside "event". Since the line has a top-level "type":"stream_event"
and an inner "type":"content_block_delta" (etc.), and extract_str now retries
past value matches, you can search for the inner type by looking for specific
strings:
- line.contains(""content_block_delta"") → delta handling
- line.contains(""content_block_start"") → start handling
- line.contains(""content_block_stop"") → stop handling
- Otherwise → skip
For text_delta extraction: search for "text_delta" in the line, then extract
the "text" field. Since the line may have multiple "text" keys (the delta
type and the actual text content), extract_str's retry logic will handle this.
Important: flush stdout after each print! call so streaming text appears immediately: use std::io::stdout; stdout().flush().ok();
Verify: cargo check -p yoke passes.
Stage C: Show tool result summaries
Objective: When Claude reads a file or runs a command, show a brief summary of the tool result so the operator knows what happened.
src/stream.rs — add a Some("user") match arm. User events carry tool results
in the format:
{"type":"user","message":{"role":"user","content":[{"tool_use_id":"...","type":"tool_result","content":"..."}]}}
For tool results:
- Extract the tool_use_id (not critical but nice)
- Check if the line contains
"tool_result" - Show a brief dim summary: the first 120 chars of the content, or just "[tool result]" if content can't be extracted
- Format:
println!("{} ← result ({}b){}", DIM, content_len, RESET)where content_len is the approximate length of the result content
Keep it simple — just indicate a result came back and roughly how big it was. The exact content is less important than knowing it happened.
Verify: cargo check -p yoke passes.
Files to modify
| File | Change |
|---|---|
| src/stream.rs | Remove truncation, add stream_event + user event handling |
Constraints
No external crates. Edition 2024. Pure std only. Only modify src/stream.rs.
Success Criteria
- cargo check -p yoke passes
- Assistant text displays in full — no line count or character limit
- Streaming text deltas appear as Claude thinks
- Tool results show a brief acknowledgment line