Streaming & Events
The Computer Agents SDK uses Server-Sent Events (SSE) for real-time streaming of execution progress. This allows you to show progress, capture intermediate results, and respond to events as they happen.
Basic Streaming
Use the onEvent callback to receive events:
const result = await client.run('Build a REST API', {
environmentId: 'env_xxx',
onEvent: (event) => {
console.log(`Event: ${event.type}`);
}
});Or with threads.sendMessage:
const result = await client.threads.sendMessage(threadId, {
content: 'Create a user model',
onEvent: (event) => {
console.log(`Event: ${event.type}`);
}
});Event Types
The SDK emits these event types during execution:
response.started
Fired when the agent begins processing.
onEvent: (event) => {
if (event.type === 'response.started') {
console.log('Agent started processing...');
}
}response.item.completed
Fired when the agent completes a response item (text, tool call, reasoning, or file change).
onEvent: (event) => {
if (event.type === 'response.item.completed') {
const item = event.item;
switch (item.type) {
case 'text':
console.log('Agent said:', item.content);
break;
case 'tool_call':
console.log('Tool called:', item.name);
break;
case 'reasoning':
console.log('Agent reasoning:', item.content);
break;
case 'file_change':
console.log('File changed:', item.path);
break;
}
}
}response.completed
Fired when the agent finishes its response.
onEvent: (event) => {
if (event.type === 'response.completed') {
console.log('Final response:', event.response.content);
}
}stream.completed
Fired when the entire stream ends, including run metadata.
onEvent: (event) => {
if (event.type === 'stream.completed') {
const run = event.run;
console.log('Run completed:', run.id);
console.log('Status:', run.status);
console.log('Tokens:', run.tokens?.input, '+', run.tokens?.output);
}
}stream.error
Fired if an error occurs during streaming.
onEvent: (event) => {
if (event.type === 'stream.error') {
console.error('Stream error:', event.error);
console.error('Message:', event.message);
}
}TypeScript Event Types
Import the event type for full type safety:
import type { MessageStreamEvent } from 'computer-agents';
const handleEvent = (event: MessageStreamEvent) => {
switch (event.type) {
case 'response.started':
// event is ResponseStartedEvent
break;
case 'response.item.completed':
// event is ResponseItemCompletedEvent
// event.item is typed
break;
case 'response.completed':
// event is ResponseCompletedEvent
// event.response is typed
break;
case 'stream.completed':
// event is StreamCompletedEvent
// event.run is typed
break;
case 'stream.error':
// event is StreamErrorEvent
// event.error and event.message are typed
break;
}
};Progress Indicators
Build a progress indicator by tracking events:
let totalItems = 0;
let currentPhase = 'starting';
const result = await client.run('Build a complex app', {
environmentId: 'env_xxx',
onEvent: (event) => {
switch (event.type) {
case 'response.started':
currentPhase = 'processing';
console.log('⏳ Processing...');
break;
case 'response.item.completed':
totalItems++;
const item = event.item;
if (item.type === 'tool_call') {
console.log(`🔧 [${totalItems}] Tool: ${item.name}`);
} else if (item.type === 'text') {
console.log(`💬 [${totalItems}] Response chunk`);
}
break;
case 'response.completed':
currentPhase = 'complete';
console.log('✅ Response complete');
break;
case 'stream.completed':
console.log(`\n📊 Stats: ${event.run.tokens?.input} in, ${event.run.tokens?.output} out`);
break;
case 'stream.error':
currentPhase = 'error';
console.log('❌ Error:', event.error);
break;
}
}
});Real-Time UI Updates
For React or other UI frameworks, use state to drive updates:
// React example
const [status, setStatus] = useState('idle');
const [items, setItems] = useState([]);
const [result, setResult] = useState(null);
const executeTask = async (task: string) => {
setStatus('running');
setItems([]);
try {
const response = await client.run(task, {
environmentId: envId,
onEvent: (event) => {
if (event.type === 'response.item.completed') {
setItems(prev => [...prev, event.item]);
}
if (event.type === 'stream.error') {
setStatus('error');
}
}
});
setResult(response);
setStatus('complete');
} catch (error) {
setStatus('error');
}
};Collecting All Events
Store events for logging or replay:
const events: MessageStreamEvent[] = [];
const result = await client.run('Task', {
environmentId: 'env_xxx',
onEvent: (event) => {
events.push(event);
}
});
// Log all events
console.log('Total events:', events.length);
console.log('Event types:', events.map(e => e.type));
// Find specific events
const toolCalls = events
.filter(e => e.type === 'response.item.completed')
.filter(e => e.item.type === 'tool_call');
console.log('Tool calls made:', toolCalls.length);Timeout Configuration
Configure streaming timeout:
const result = await client.run('Long running task', {
environmentId: 'env_xxx',
timeout: 300000, // 5 minutes (default: 10 minutes)
onEvent: (event) => {
console.log(event.type);
}
});For sendMessage:
const result = await client.threads.sendMessage(threadId, {
content: 'Complex task',
timeout: 600000 // 10 minutes
});Error Handling in Streams
Handle both stream errors and exceptions:
try {
const result = await client.run('Task', {
environmentId: 'env_xxx',
onEvent: (event) => {
if (event.type === 'stream.error') {
// Handle stream-level error (task failed)
console.error('Task failed:', event.error);
}
}
});
} catch (error) {
// Handle connection/API errors
if (error instanceof ApiClientError) {
console.error('API error:', error.message);
}
}Best Practices
1. Always Handle Errors
onEvent: (event) => {
if (event.type === 'stream.error') {
// Log, notify, or recover
logger.error('Stream error', { error: event.error });
}
}2. Use Event Types for UI States
const getStatusFromEvent = (event: MessageStreamEvent) => {
switch (event.type) {
case 'response.started': return 'processing';
case 'response.completed': return 'complete';
case 'stream.error': return 'error';
default: return 'running';
}
};3. Debounce UI Updates
// For high-frequency updates
import { debounce } from 'lodash';
const debouncedUpdate = debounce((item) => {
setItems(prev => [...prev, item]);
}, 100);
onEvent: (event) => {
if (event.type === 'response.item.completed') {
debouncedUpdate(event.item);
}
}4. Track Token Usage
let totalTokens = { input: 0, output: 0 };
onEvent: (event) => {
if (event.type === 'stream.completed' && event.run.tokens) {
totalTokens.input += event.run.tokens.input;
totalTokens.output += event.run.tokens.output;
}
}Next Steps
- Advanced Patterns - Production patterns and best practices
- API Reference - Complete API documentation