Skip to main content

Streaming

Streaming lets you receive Claude’s response in real-time as it’s generated, providing a better user experience for longer responses.

Basic Streaming

Prompt Streaming

for await (const event of client.promptStream({
  message: 'Write a short story about a robot',
  model: 'claude-sonnet-4-5-20250929',
})) {
  if (event.type === 'text') {
    process.stdout.write(event.text);
  }
}

Session Streaming

const session = await client.createSession({ ... });

for await (const event of session.sendStream('Tell me about space exploration')) {
  if (event.type === 'text') {
    process.stdout.write(event.text);
  }
}

Event Types

type StreamingEvent =
  | { type: 'text'; text: string }
  | { type: 'tool_use'; id: string; name: string; input: Record<string, unknown> }
  | { type: 'tool_result'; id: string; content: unknown; isError?: boolean }
  | { type: 'thinking'; thinking: string }
  | { type: 'error'; error: Error };

Text Events

Text content as it’s generated:
for await (const event of stream) {
  if (event.type === 'text') {
    // Append to display
    displayElement.textContent += event.text;
  }
}

Tool Use Events

When Claude decides to call a tool:
for await (const event of stream) {
  if (event.type === 'tool_use') {
    console.log(`Calling tool: ${event.name}`);
    console.log(`Input:`, event.input);
    // Tool execution happens automatically
  }
}

Tool Result Events

After a tool finishes executing:
for await (const event of stream) {
  if (event.type === 'tool_result') {
    console.log(`Tool ${event.id} returned:`, event.content);
    if (event.isError) {
      console.error('Tool execution failed');
    }
  }
}

Thinking Events

Extended thinking (for supported models):
for await (const event of stream) {
  if (event.type === 'thinking') {
    console.log('[Claude is thinking...]', event.thinking);
  }
}

Error Events

Errors during streaming:
for await (const event of stream) {
  if (event.type === 'error') {
    console.error('Stream error:', event.error);
    break;
  }
}

Complete Example

async function streamResponse(message: string) {
  let fullText = '';
  const toolCalls = [];

  try {
    for await (const event of client.promptStream({ message })) {
      switch (event.type) {
        case 'text':
          fullText += event.text;
          updateUI(fullText);
          break;

        case 'tool_use':
          showToolIndicator(event.name);
          toolCalls.push(event);
          break;

        case 'tool_result':
          hideToolIndicator();
          break;

        case 'thinking':
          showThinkingIndicator(event.thinking);
          break;

        case 'error':
          showError(event.error.message);
          return;
      }
    }

    console.log('Stream complete. Final text length:', fullText.length);
    console.log('Tool calls made:', toolCalls.length);
  } catch (err) {
    console.error('Stream failed:', err);
  }
}

Python Streaming

async for event in client.stream('Write a poem about coding'):
    if event.type == 'message':
        print(event.data, end='', flush=True)
    elif event.type == 'tool_use':
        print(f'\n[Calling tool: {event.name}]')
    elif event.type == 'error':
        print(f'\nError: {event.error}')
        break

Browser Usage

Display streaming text in a browser:
async function streamToElement(element: HTMLElement, message: string) {
  element.textContent = '';

  for await (const event of client.promptStream({ message })) {
    if (event.type === 'text') {
      element.textContent += event.text;
      // Auto-scroll to bottom
      element.scrollTop = element.scrollHeight;
    }
  }
}

With React

function StreamingResponse({ message }: { message: string }) {
  const [text, setText] = useState('');
  const [isStreaming, setIsStreaming] = useState(true);

  useEffect(() => {
    let cancelled = false;

    async function stream() {
      setIsStreaming(true);
      setText('');

      try {
        for await (const event of client.promptStream({ message })) {
          if (cancelled) break;
          if (event.type === 'text') {
            setText(prev => prev + event.text);
          }
        }
      } finally {
        setIsStreaming(false);
      }
    }

    stream();

    return () => { cancelled = true; };
  }, [message]);

  return (
    <div>
      <p>{text}</p>
      {isStreaming && <span>Streaming...</span>}
    </div>
  );
}

Buffering Strategies

Character-by-character

for await (const event of stream) {
  if (event.type === 'text') {
    for (const char of event.text) {
      appendChar(char);
      await delay(10); // Typewriter effect
    }
  }
}

Word-by-word

let buffer = '';
for await (const event of stream) {
  if (event.type === 'text') {
    buffer += event.text;
    const words = buffer.split(/(\s+)/);
    // Keep incomplete word in buffer
    buffer = words.pop() || '';
    for (const word of words) {
      displayWord(word);
    }
  }
}
// Display remaining buffer
if (buffer) displayWord(buffer);

Sentence-by-sentence

let buffer = '';
for await (const event of stream) {
  if (event.type === 'text') {
    buffer += event.text;
    const sentences = buffer.split(/(?<=[.!?])\s+/);
    // Keep incomplete sentence in buffer
    buffer = sentences.pop() || '';
    for (const sentence of sentences) {
      displaySentence(sentence);
    }
  }
}
if (buffer) displaySentence(buffer);

Performance Tips

Instead of updating the DOM on every text event, batch updates:
let pending = '';
let rafId: number;

for await (const event of stream) {
  if (event.type === 'text') {
    pending += event.text;
    if (!rafId) {
      rafId = requestAnimationFrame(() => {
        element.textContent += pending;
        pending = '';
        rafId = 0;
      });
    }
  }
}
For better performance with large amounts of text:
const fragment = document.createDocumentFragment();

for await (const event of stream) {
  if (event.type === 'text') {
    const span = document.createElement('span');
    span.textContent = event.text;
    fragment.appendChild(span);
  }
}

container.appendChild(fragment);
If the user navigates away, cancel the stream:
const controller = new AbortController();

try {
  for await (const event of stream) {
    if (controller.signal.aborted) break;
    // Handle event
  }
} catch (err) {
  if (err.name !== 'AbortError') throw err;
}

// Cancel when needed
controller.abort();