284 lines
7.8 KiB
TypeScript
284 lines
7.8 KiB
TypeScript
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
|
/**
|
|
* Error Handling Template
|
|
*
|
|
* Demonstrates:
|
|
* - SDK error handling
|
|
* - Message-level error handling
|
|
* - Retry strategies
|
|
* - Graceful degradation
|
|
*/
|
|
|
|
// Example 1: Basic Error Handling
|
|
async function basicErrorHandling() {
|
|
try {
|
|
const response = query({
|
|
prompt: "Analyze and refactor code",
|
|
options: {
|
|
model: "claude-sonnet-4-5",
|
|
workingDirectory: "/path/to/project"
|
|
}
|
|
});
|
|
|
|
for await (const message of response) {
|
|
switch (message.type) {
|
|
case 'assistant':
|
|
console.log('Assistant:', message.content);
|
|
break;
|
|
|
|
case 'error':
|
|
console.error('Agent error:', message.error.message);
|
|
if (message.error.type === 'permission_denied') {
|
|
console.log('Permission denied for:', message.error.tool);
|
|
// Handle permission errors gracefully
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Fatal error:', error);
|
|
|
|
// Handle specific error codes
|
|
if (error.code === 'CLI_NOT_FOUND') {
|
|
console.error('Claude Code CLI not installed');
|
|
console.error('Install: npm install -g @anthropic-ai/claude-code');
|
|
} else if (error.code === 'AUTHENTICATION_FAILED') {
|
|
console.error('Invalid API key. Check ANTHROPIC_API_KEY');
|
|
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
|
|
console.error('Rate limit exceeded. Retry after delay.');
|
|
} else if (error.code === 'CONTEXT_LENGTH_EXCEEDED') {
|
|
console.error('Context too large. Use session compaction.');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Example 2: Retry with Exponential Backoff
|
|
async function retryWithBackoff(
|
|
prompt: string,
|
|
maxRetries = 3,
|
|
baseDelay = 1000
|
|
): Promise<void> {
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
try {
|
|
const response = query({
|
|
prompt,
|
|
options: {
|
|
model: "claude-sonnet-4-5"
|
|
}
|
|
});
|
|
|
|
for await (const message of response) {
|
|
if (message.type === 'assistant') {
|
|
console.log(message.content);
|
|
}
|
|
}
|
|
|
|
return; // Success, exit
|
|
} catch (error) {
|
|
if (error.code === 'RATE_LIMIT_EXCEEDED' && attempt < maxRetries - 1) {
|
|
const delay = baseDelay * Math.pow(2, attempt);
|
|
console.log(`Rate limited. Retrying in ${delay}ms...`);
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
} else {
|
|
throw error; // Re-throw if not rate limit or final attempt
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Example 3: Graceful Degradation
|
|
async function gracefulDegradation(prompt: string) {
|
|
// Try with full capabilities first
|
|
try {
|
|
console.log('Attempting with Sonnet model...');
|
|
const response = query({
|
|
prompt,
|
|
options: {
|
|
model: "claude-sonnet-4-5",
|
|
allowedTools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
|
|
}
|
|
});
|
|
|
|
for await (const message of response) {
|
|
if (message.type === 'assistant') {
|
|
console.log(message.content);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn('Sonnet failed, falling back to Haiku...');
|
|
|
|
// Fallback to faster/cheaper model with limited tools
|
|
try {
|
|
const response = query({
|
|
prompt,
|
|
options: {
|
|
model: "haiku",
|
|
allowedTools: ["Read", "Grep", "Glob"] // Read-only
|
|
}
|
|
});
|
|
|
|
for await (const message of response) {
|
|
if (message.type === 'assistant') {
|
|
console.log(message.content);
|
|
}
|
|
}
|
|
} catch (fallbackError) {
|
|
console.error('All attempts failed:', fallbackError);
|
|
throw fallbackError;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Example 4: Comprehensive Error Handler
|
|
async function comprehensiveErrorHandling() {
|
|
const errors: Array<{ type: string; message: string; timestamp: Date }> = [];
|
|
|
|
try {
|
|
const response = query({
|
|
prompt: "Complex multi-step task",
|
|
options: {
|
|
model: "claude-sonnet-4-5",
|
|
permissionMode: "default"
|
|
}
|
|
});
|
|
|
|
for await (const message of response) {
|
|
switch (message.type) {
|
|
case 'assistant':
|
|
console.log('✅ Assistant:', message.content);
|
|
break;
|
|
|
|
case 'tool_call':
|
|
console.log(`🔧 Executing: ${message.tool_name}`);
|
|
break;
|
|
|
|
case 'tool_result':
|
|
console.log(`✅ ${message.tool_name} completed`);
|
|
break;
|
|
|
|
case 'error':
|
|
console.error('❌ Error:', message.error.message);
|
|
errors.push({
|
|
type: message.error.type,
|
|
message: message.error.message,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
// Handle different error types
|
|
if (message.error.type === 'permission_denied') {
|
|
console.log('→ Permission was denied, continuing with limited access');
|
|
} else if (message.error.type === 'tool_execution_failed') {
|
|
console.log('→ Tool failed, attempting alternative approach');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('💥 Fatal error:', error);
|
|
|
|
// Log error details
|
|
errors.push({
|
|
type: error.code || 'UNKNOWN',
|
|
message: error.message,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
// Specific handlers
|
|
if (error.code === 'AUTHENTICATION_FAILED') {
|
|
console.error('→ Check your ANTHROPIC_API_KEY environment variable');
|
|
console.error('→ Visit https://console.anthropic.com/ for API keys');
|
|
} else if (error.code === 'RATE_LIMIT_EXCEEDED') {
|
|
console.error('→ Rate limit exceeded');
|
|
console.error('→ Implement exponential backoff or reduce request frequency');
|
|
} else if (error.code === 'CONTEXT_LENGTH_EXCEEDED') {
|
|
console.error('→ Context too large');
|
|
console.error('→ Consider using session management or reducing prompt size');
|
|
} else if (error.code === 'CLI_NOT_FOUND') {
|
|
console.error('→ Claude Code CLI not found');
|
|
console.error('→ Install: npm install -g @anthropic-ai/claude-code');
|
|
}
|
|
|
|
throw error;
|
|
} finally {
|
|
// Always log error summary
|
|
if (errors.length > 0) {
|
|
console.log('\n\n=== Error Summary ===');
|
|
errors.forEach(err => {
|
|
console.log(`${err.timestamp.toISOString()} - ${err.type}: ${err.message}`);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Example 5: Circuit Breaker Pattern
|
|
class CircuitBreaker {
|
|
private failures = 0;
|
|
private lastFailureTime?: Date;
|
|
private readonly threshold = 3;
|
|
private readonly resetTimeout = 60000; // 1 minute
|
|
|
|
async execute(fn: () => Promise<void>): Promise<void> {
|
|
// Check if circuit is open
|
|
if (this.isOpen()) {
|
|
throw new Error('Circuit breaker is OPEN. Too many failures.');
|
|
}
|
|
|
|
try {
|
|
await fn();
|
|
this.onSuccess();
|
|
} catch (error) {
|
|
this.onFailure();
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private isOpen(): boolean {
|
|
if (this.failures >= this.threshold) {
|
|
const now = new Date();
|
|
if (this.lastFailureTime &&
|
|
now.getTime() - this.lastFailureTime.getTime() < this.resetTimeout) {
|
|
return true;
|
|
}
|
|
// Reset after timeout
|
|
this.failures = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private onSuccess() {
|
|
this.failures = 0;
|
|
this.lastFailureTime = undefined;
|
|
}
|
|
|
|
private onFailure() {
|
|
this.failures++;
|
|
this.lastFailureTime = new Date();
|
|
console.warn(`Circuit breaker: ${this.failures}/${this.threshold} failures`);
|
|
}
|
|
}
|
|
|
|
async function useCircuitBreaker() {
|
|
const breaker = new CircuitBreaker();
|
|
|
|
try {
|
|
await breaker.execute(async () => {
|
|
const response = query({
|
|
prompt: "Perform task",
|
|
options: { model: "sonnet" }
|
|
});
|
|
|
|
for await (const message of response) {
|
|
if (message.type === 'assistant') {
|
|
console.log(message.content);
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Circuit breaker prevented execution or task failed:', error);
|
|
}
|
|
}
|
|
|
|
// Run
|
|
comprehensiveErrorHandling().catch(console.error);
|