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
- Always await result - Even if you only need events
- Handle all event types - Especially
turn.failed - Use type guards - For type-safe event handling
- Stream for long tasks - Better UX than waiting