Advanced Patterns
This guide covers production patterns and best practices for building robust applications with the Computer Agents SDK.
Error Handling
API Errors
Handle API errors with the ApiClientError class:
import { ComputerAgentsClient, ApiClientError } from 'computer-agents';
const client = new ComputerAgentsClient();
try {
await client.run('Create a REST API', { environmentId: 'env_xxx' });
} catch (error) {
if (error instanceof ApiClientError) {
console.error('API Error:', error.message);
console.error('Status:', error.status); // HTTP status code
console.error('Code:', error.code); // Error code from API
// Handle specific errors
switch (error.status) {
case 401:
console.error('Invalid API key');
break;
case 402:
console.error('Monthly usage allowance exhausted');
break;
case 404:
console.error('Resource not found');
break;
case 429:
console.error('Rate limited');
break;
}
} else {
throw error;
}
}Stream Errors
Handle errors during streaming:
const result = await client.run('Complex task', {
environmentId: 'env_xxx',
onEvent: (event) => {
if (event.type === 'stream.error') {
// Task execution failed
console.error('Task error:', event.error);
console.error('Details:', event.message);
}
}
});Retry Logic
Implement retry for transient failures:
async function runWithRetry(
client: ComputerAgentsClient,
task: string,
options: { environmentId: string; maxRetries?: number }
) {
const maxRetries = options.maxRetries ?? 3;
let lastError: Error | undefined;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await client.run(task, {
environmentId: options.environmentId
});
} catch (error) {
lastError = error as Error;
if (error instanceof ApiClientError) {
// Don't retry client errors (4xx)
if (error.status && error.status < 500) {
throw error;
}
}
console.warn(`Attempt ${attempt} failed, retrying...`);
await new Promise(r => setTimeout(r, 1000 * attempt)); // Exponential backoff
}
}
throw lastError;
}Multi-Step Workflows
Sequential Tasks
Chain tasks in a single thread:
async function buildProject(client: ComputerAgentsClient, envId: string) {
// Step 1: Setup
const r1 = await client.run('Initialize a Node.js project with TypeScript', {
environmentId: envId
});
console.log('Setup complete');
// Step 2: Build features (continues thread)
const r2 = await client.run('Add Express server with /health endpoint', {
environmentId: envId,
threadId: r1.threadId
});
console.log('Server created');
// Step 3: Add tests (continues thread)
const r3 = await client.run('Add Jest tests for all endpoints', {
environmentId: envId,
threadId: r1.threadId
});
console.log('Tests added');
return { threadId: r1.threadId, finalResult: r3 };
}Parallel Tasks
Run independent tasks concurrently:
async function setupMultipleEnvironments(client: ComputerAgentsClient) {
const envs = await client.environments.list();
// Run setup in parallel across environments
const results = await Promise.all(
envs.map(env =>
client.run('Initialize project structure', {
environmentId: env.id
})
)
);
return results;
}Conditional Workflows
Branch based on results:
async function smartFix(
client: ComputerAgentsClient,
envId: string,
threadId: string
) {
// Analyze the issue
const analysis = await client.run('Analyze the test failures and categorize them', {
environmentId: envId,
threadId
});
// Branch based on analysis
if (analysis.content.includes('type error')) {
return client.run('Fix all TypeScript type errors', {
environmentId: envId,
threadId
});
} else if (analysis.content.includes('missing import')) {
return client.run('Fix all missing imports', {
environmentId: envId,
threadId
});
} else {
return client.run('Fix the failing tests', {
environmentId: envId,
threadId
});
}
}Subscription Management
Pre-Execution Checks
Check usage before operations:
async function runIfAllowanceAvailable(
client: ComputerAgentsClient,
task: string,
envId: string
) {
const { canExecute, reason } = await client.subscription.canExecute();
if (!canExecute) {
throw new Error(`Cannot execute: ${reason}`);
}
return client.run(task, { environmentId: envId });
}Usage Tracking
Track token usage across operations:
class UsageTracker {
private totalTokens = { input: 0, output: 0 };
async runTracked(
client: ComputerAgentsClient,
task: string,
options: { environmentId: string; threadId?: string }
) {
const result = await client.run(task, {
...options,
onEvent: (event) => {
if (event.type === 'stream.completed' && event.run.tokens) {
this.totalTokens.input += event.run.tokens.input;
this.totalTokens.output += event.run.tokens.output;
}
}
});
return result;
}
getUsage() {
return this.totalTokens;
}
}
// Usage
const tracker = new UsageTracker();
await tracker.runTracked(client, 'Build feature', { environmentId: envId });
console.log('Total tokens:', tracker.getUsage());Usage Alerts
Monitor subscription usage:
async function checkUsageStatus(client: ComputerAgentsClient) {
const status = await client.subscription.getStatus();
const percentUsed = (status.used / status.allowance) * 100;
if (percentUsed > 90) {
console.warn('CRITICAL: Monthly allowance nearly exhausted');
// Suggest upgrade
} else if (percentUsed > 75) {
console.warn('WARNING: Monthly allowance running low');
}
return status;
}Environment Management
Environment Pooling
Maintain a pool of ready environments:
class EnvironmentPool {
private pool: string[] = [];
constructor(
private client: ComputerAgentsClient,
private size: number = 3
) {}
async initialize() {
const envs = await this.client.environments.list();
this.pool = envs.slice(0, this.size).map(e => e.id);
// Create more if needed
while (this.pool.length < this.size) {
const env = await this.client.environments.create({
name: `pool-env-${this.pool.length}`,
internetAccess: true
});
this.pool.push(env.id);
}
}
async getEnvironment(): Promise<string> {
if (this.pool.length === 0) {
throw new Error('No environments available');
}
return this.pool.shift()!;
}
returnEnvironment(envId: string) {
this.pool.push(envId);
}
}Environment Templates
Create environments with preset configurations:
async function createTypedEnvironment(
client: ComputerAgentsClient,
type: 'node' | 'python' | 'go'
) {
const configs = {
node: {
name: 'Node.js Environment',
environmentVariables: [{ key: 'NODE_ENV', value: 'development' }]
},
python: {
name: 'Python Environment',
environmentVariables: [{ key: 'PYTHONPATH', value: '/workspace' }]
},
go: {
name: 'Go Environment',
environmentVariables: [{ key: 'GOPATH', value: '/workspace/go' }]
}
};
return client.environments.create({
...configs[type],
internetAccess: true
});
}File Management Patterns
Bulk Operations
Upload multiple files efficiently:
async function uploadProjectFiles(
client: ComputerAgentsClient,
envId: string,
files: { path: string; content: string }[]
) {
// Upload in parallel batches
const batchSize = 5;
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
await Promise.all(
batch.map(file =>
client.files.uploadFile({
environmentId: envId,
filename: file.path.split('/').pop()!,
path: file.path.split('/').slice(0, -1).join('/'),
content: file.content
})
)
);
}
}File Watching Pattern
Poll for file changes:
async function watchFiles(
client: ComputerAgentsClient,
envId: string,
callback: (files: any[]) => void,
interval = 5000
) {
let lastFiles = new Map<string, number>();
const check = async () => {
const files = await client.files.listFiles(envId);
const changes: any[] = [];
for (const file of files) {
const lastMod = lastFiles.get(file.path);
if (!lastMod || file.modifiedAt !== lastMod) {
changes.push(file);
}
lastFiles.set(file.path, file.modifiedAt);
}
if (changes.length > 0) {
callback(changes);
}
};
const timer = setInterval(check, interval);
await check(); // Initial check
return () => clearInterval(timer);
}Testing Strategies
Mock Client
Create a mock client for testing:
// test/mocks/client.ts
export function createMockClient() {
return {
run: jest.fn().mockResolvedValue({
content: 'Mock response',
threadId: 'thread_mock'
}),
environments: {
list: jest.fn().mockResolvedValue([
{ id: 'env_mock', name: 'Mock Environment' }
]),
create: jest.fn().mockResolvedValue({
id: 'env_new',
name: 'New Environment'
})
},
threads: {
create: jest.fn().mockResolvedValue({ id: 'thread_mock' }),
sendMessage: jest.fn().mockResolvedValue({
content: 'Mock response'
})
},
files: {
listFiles: jest.fn().mockResolvedValue([]),
uploadFile: jest.fn().mockResolvedValue({ path: '/test.txt' })
},
subscription: {
canExecute: jest.fn().mockResolvedValue({ canExecute: true })
}
};
}Integration Testing
Test against the real API:
describe('Computer Agents Integration', () => {
const client = new ComputerAgentsClient({
apiKey: process.env.TEST_API_KEY
});
let testEnvId: string;
beforeAll(async () => {
const env = await client.environments.create({
name: `test-${Date.now()}`
});
testEnvId = env.id;
});
afterAll(async () => {
await client.environments.delete(testEnvId);
});
it('should execute a simple task', async () => {
const result = await client.run('Create a hello.txt file', {
environmentId: testEnvId
});
expect(result.content).toBeDefined();
expect(result.threadId).toMatch(/^thread_/);
});
});Logging and Observability
Structured Logging
import { ComputerAgentsClient } from 'computer-agents';
function createLoggedClient(apiKey: string) {
const client = new ComputerAgentsClient({ apiKey, debug: true });
// Wrap run method with logging
const originalRun = client.run.bind(client);
client.run = async (task, options) => {
const startTime = Date.now();
const requestId = crypto.randomUUID();
console.log(JSON.stringify({
event: 'task_started',
requestId,
task: task.slice(0, 100),
environmentId: options.environmentId,
timestamp: new Date().toISOString()
}));
try {
const result = await originalRun(task, {
...options,
onEvent: (event) => {
console.log(JSON.stringify({
event: 'stream_event',
requestId,
eventType: event.type,
timestamp: new Date().toISOString()
}));
options.onEvent?.(event);
}
});
console.log(JSON.stringify({
event: 'task_completed',
requestId,
threadId: result.threadId,
durationMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}));
return result;
} catch (error) {
console.log(JSON.stringify({
event: 'task_failed',
requestId,
error: (error as Error).message,
durationMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}));
throw error;
}
};
return client;
}Configuration Patterns
Environment-Based Config
interface ClientConfig {
apiKey: string;
baseUrl?: string;
timeout?: number;
debug?: boolean;
}
function getClientConfig(): ClientConfig {
const env = process.env.NODE_ENV || 'development';
const configs: Record<string, ClientConfig> = {
development: {
apiKey: process.env.COMPUTER_AGENTS_API_KEY!,
debug: true,
timeout: 120000
},
production: {
apiKey: process.env.COMPUTER_AGENTS_API_KEY!,
debug: false,
timeout: 60000
},
test: {
apiKey: process.env.TEST_API_KEY || 'test-key',
debug: true,
timeout: 30000
}
};
return configs[env] || configs.development;
}
const client = new ComputerAgentsClient(getClientConfig());Best Practices Summary
-
Always handle errors - Use
ApiClientErrorfor API errors andstream.errorevents for task failures -
Check usage before operations - Use
subscription.canExecute()to prevent unexpected failures -
Use threads for related tasks - Maintain context by passing
threadIdbetween related calls -
Track token usage - Monitor
stream.completedevents for cost management -
Implement retries - Add retry logic for transient failures, but don’t retry client errors
-
Use parallel operations wisely - Run independent tasks concurrently, but respect rate limits
-
Clean up resources - Delete temporary environments and threads when done
-
Enable debug mode during development - Use
debug: truefor verbose logging -
Test with mocks - Create mock clients for unit tests, use real API for integration tests
-
Structure your logs - Use structured logging for better observability
Next Steps
- API Reference - Complete API documentation
- Streaming & Events - Real-time event handling