Streaming & Events

Lesson Content

Why Streaming Matters

Batch vs Stream
query()
Wait 30 seconds
Blank screen
All at once
→ User waits
stream()
Word by word
Tool by tool
Event by event
→ User watches
Same result, completely different experience.

When you call query(), you wait for the entire response before seeing anything. For short tasks, that is fine. But when an agent is reading files, running commands, and thinking for 30 seconds, your user stares at a blank screen. Streaming fixes that by sending you each piece of the response as it happens — word by word, tool by tool, event by event.

Real-world analogy: Non-streaming is like ordering food at a restaurant and sitting in silence until the entire meal arrives at once. Streaming is like watching the chef cook through the kitchen window — you see the prep, the sizzle, the plating. Same food, completely different experience.

The Event Stream

When you use streaming mode, the SDK emits a sequence of events that tell you exactly what is happening inside your agent. Each event has a type and a payload:

1
content_block_start
Claude is about to start generating text or requesting a tool. This is your signal to prepare the UI for incoming content.
2
content_block_delta
A chunk of text or tool input has arrived. This is what you append to your display in real time — the "typing" effect users expect from AI interfaces.
3
content_block_stop
A content block is complete. If it was a tool call, the SDK will now execute the tool and continue the loop.
4
tool_use / tool_result
A tool was called and returned a result. You can display this to show users what the agent is doing behind the scenes.

Streaming in Practice

The SDK provides a streaming interface that lets you handle events as they arrive. Here is a complete example:

TypeScript — streaming agent output
import { Claude } from "@anthropic-ai/claude-agent";

const agent = new Claude({
  model: "claude-sonnet-4-6",
  tools: "defaults",
});

// Use the streaming query method
const stream = agent.streamQuery(
  "Read package.json and summarize the project."
);

// Listen for events as they arrive
for await (const event of stream) {
  switch (event.type) {
    case "text_delta":
      // A chunk of text — print it immediately
      process.stdout.write(event.text);
      break;

    case "tool_use":
      // Claude is calling a tool
      console.log(`\n[Tool: ${event.name}]`);
      break;

    case "tool_result":
      // Tool finished — show a brief status
      console.log(`[Tool complete]\n`);
      break;

    case "result":
      // Final result with metadata
      console.log(`\nCost: $${event.cost}`);
      break;
  }
}

Building a Real-Time Terminal UI

Here is a more polished example that shows tool activity and text output in a terminal interface — the kind of experience you see in Claude Code itself:

TypeScript — terminal UI with streaming
async function runWithUI(prompt: string) {
  const agent = new Claude({
    model: "claude-sonnet-4-6",
    tools: "defaults",
  });

  console.log(`\x1b[36m> ${prompt}\x1b[0m\n`);

  const stream = agent.streamQuery(prompt);
  let toolDepth = 0;

  for await (const event of stream) {
    if (event.type === "tool_use") {
      toolDepth++;
      // Show tool activity in dim text
      console.log(
        `\x1b[2m  ${"  ".repeat(toolDepth)}[${event.name}]\x1b[0m`
      );
    } else if (event.type === "tool_result") {
      toolDepth = Math.max(0, toolDepth - 1);
    } else if (event.type === "text_delta") {
      // Stream text in real time
      process.stdout.write(event.text);
    } else if (event.type === "result") {
      console.log(`\n\x1b[2mCost: $${event.cost.toFixed(4)}\x1b[0m`);
    }
  }
}

runWithUI("Find all TypeScript files in this project and count the total lines of code.");

Event Lifecycle

Understanding the full event lifecycle helps you build robust UIs. Here is the sequence for a typical agent interaction:

User prompt: "How many .ts files are in src/?"

text_delta     "Let me check"
text_delta     " the src directory..."
tool_use       Glob { pattern: "src/**/*.ts" }
tool_result    ["src/index.ts", "src/utils.ts", ...]
text_delta     "I found "
text_delta     "12 TypeScript files"
text_delta     " in the src/ directory."
result         { cost: 0.003, toolCalls: 1 }

Notice how text deltas arrive in small chunks — sometimes just a word or part of a word. Tool events arrive as complete units. The result event always comes last and signals that the agent is done.

Common Pitfalls

Forgetting to handle all event types

If you only handle text_delta, you will miss tool activity and the final result. Always have a default case or handle at least text, tool_use, tool_result, and result events.

Using console.log for text deltas

console.log() adds a newline after each call. Use process.stdout.write() for text deltas so words flow together naturally instead of appearing on separate lines.

Not handling stream errors

Network issues, rate limits, or API errors can interrupt the stream. Wrap your for await loop in a try/catch to handle errors gracefully instead of crashing.

Streaming & Events

What is streaming in the Agent SDK?
Instead of waiting for the complete response, streaming sends events in real time as the agent thinks, calls tools, and generates text. This enables live UI updates and responsive user experiences.
text_delta event
A chunk of text from Claude arriving in real time. These are small pieces (words or partial words) that you append to your display to create the typing effect. Use process.stdout.write(), not console.log().
tool_use event
Fired when Claude decides to call a tool. Contains the tool name and parameters. Display this to show users what the agent is doing behind the scenes.
tool_result event
Fired when a tool finishes executing and returns its result to Claude. The agent then continues reasoning with this new information.
result event
The final event, fired when the agent is completely done. Contains metadata: total cost, number of tool calls, session ID. This signals that the stream is finished.
streamQuery() vs query()
query() waits for the complete response and returns the final result. streamQuery() returns an async iterable of events that you process in real time. Same underlying agent, different consumption pattern.
Why use process.stdout.write() for text deltas?
console.log() adds a newline after each call, making streamed text appear on separate lines. process.stdout.write() outputs text without newlines, so words flow together naturally.

Streaming & Events Check

1What is the main benefit of streaming over a regular query() call?

2Which event type represents a chunk of Claude text arriving in real time?

3A stream emits events in this order: text_delta, tool_use, tool_result, text_delta, result. What happened?

4Why should you NOT use console.log() for streaming text deltas?

5What should you do if the stream throws an error mid-execution?