Skip to Content
AgentsAdvanced Patterns

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

  1. Always handle errors - Use ApiClientError for API errors and stream.error events for task failures

  2. Check usage before operations - Use subscription.canExecute() to prevent unexpected failures

  3. Use threads for related tasks - Maintain context by passing threadId between related calls

  4. Track token usage - Monitor stream.completed events for cost management

  5. Implement retries - Add retry logic for transient failures, but don’t retry client errors

  6. Use parallel operations wisely - Run independent tasks concurrently, but respect rate limits

  7. Clean up resources - Delete temporary environments and threads when done

  8. Enable debug mode during development - Use debug: true for verbose logging

  9. Test with mocks - Create mock clients for unit tests, use real API for integration tests

  10. Structure your logs - Use structured logging for better observability

Next Steps

Last updated on