Skip to main content

Streaming Events

Streaming provides real-time visibility into AI execution, allowing you to display progress, handle tool calls, and respond to events as they occur.

Basic Streaming

const { events, result } = await thread.runStreamed('Create a web server');

for await (const event of events) {
console.log(event.type, event);
}

const finalResult = await result;

Event Types

System Event

Emitted at the start with session metadata:

{
type: 'system',
cwd: '/path/to/project',
session_id: 'session_abc123',
tools: ['Read', 'Write', 'Bash', ...],
model: 'claude-sonnet-4-5-20250929'
}

Message Event

User or assistant text messages:

{
type: 'message',
role: 'assistant',
id: 'msg_123',
text: 'I will create the server...',
timestamp: 1704067200000
}

Tool Call Event

When the AI invokes a tool:

{
type: 'tool_call',
id: 'tc_456',
messageId: 'msg_123',
toolId: 'tool_789',
toolName: 'Write',
parameters: {
path: 'server.ts',
content: '...'
},
timestamp: 1704067201000
}

Tool Result Event

Tool execution results:

{
type: 'tool_result',
id: 'tr_012',
messageId: 'msg_123',
toolId: 'tool_789',
toolName: 'Write',
isError: false,
value: 'File written successfully',
timestamp: 1704067202000
}

Completion Event

Execution completed successfully:

{
type: 'completion',
finalText: 'Server created at server.ts',
durationMs: 5432,
numTurns: 1
}

Turn Failed Event

Execution failed:

{
type: 'turn.failed',
error: {
message: 'Command timed out',
code: 'TIMEOUT'
}
}

Type Guards

Use type guards for type-safe event handling:

import {
isMessageEvent,
isToolCallEvent,
isToolResultEvent,
isTurnCompletedEvent,
isTurnFailedEvent
} from '@activade/droid-sdk';

for await (const event of events) {
if (isMessageEvent(event)) {
// TypeScript knows: event.role, event.text
console.log(`[${event.role}] ${event.text}`);
}

if (isToolCallEvent(event)) {
// TypeScript knows: event.toolName, event.parameters
console.log(`Calling ${event.toolName}`);
}

if (isToolResultEvent(event)) {
// TypeScript knows: event.isError, event.value
if (event.isError) {
console.error(`Tool failed: ${event.value}`);
}
}
}

Building a Progress UI

const { events, result } = await thread.runStreamed(prompt);

for await (const event of events) {
switch (event.type) {
case 'message':
if (event.role === 'assistant') {
updateUI({ message: event.text });
}
break;

case 'tool_call':
updateUI({
status: 'working',
tool: event.toolName,
action: `Running ${event.toolName}...`
});
break;

case 'tool_result':
updateUI({
status: event.isError ? 'error' : 'success',
result: event.value
});
break;

case 'completion':
updateUI({
status: 'complete',
duration: `${event.durationMs}ms`
});
break;

case 'turn.failed':
updateUI({
status: 'failed',
error: event.error.message
});
break;
}
}

StreamedTurn Structure

interface StreamedTurn {
events: AsyncIterable<StreamEvent>;
result: Promise<TurnResult>;
}
  • events - Async iterator of events as they occur
  • result - Promise that resolves to the final TurnResult

Best Practices

  1. Always await result - Even if you only need events
  2. Handle all event types - Especially turn.failed
  3. Use type guards - For type-safe event handling
  4. Stream for long tasks - Better UX than waiting