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
Copy
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
Copy
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
Copy
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:Copy
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:Copy
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:Copy
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):Copy
for await (const event of stream) {
if (event.type === 'thinking') {
console.log('[Claude is thinking...]', event.thinking);
}
}
Error Events
Errors during streaming:Copy
for await (const event of stream) {
if (event.type === 'error') {
console.error('Stream error:', event.error);
break;
}
}
Complete Example
Copy
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
Copy
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:Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
Batch DOM updates
Batch DOM updates
Instead of updating the DOM on every text event, batch updates:
Copy
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;
});
}
}
}
Use text fragments
Use text fragments
For better performance with large amounts of text:
Copy
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);
Cancel streams
Cancel streams
If the user navigates away, cancel the stream:
Copy
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();