claude-web/node_modules/@instantlyeasy/claude-code-sdk-ts/examples/fluent-api/new-features/error-handling.js

327 lines
10 KiB
JavaScript

#!/usr/bin/env node
/**
* Advanced Error Handling Example
*
* This example demonstrates the SDK's typed error handling system,
* showing how to catch and handle specific error types gracefully.
*
* Use cases:
* - Implementing retry logic for rate limits
* - Handling permission errors with user prompts
* - Building robust error recovery systems
*/
import {
claude,
detectErrorType,
createTypedError,
isRateLimitError,
isToolPermissionError,
isAuthenticationError,
isNetworkError,
isTimeoutError,
isValidationError
} from '@instantlyeasy/claude-code-sdk-ts';
async function errorHandlingExample() {
console.log('🛡️ Advanced Error Handling Example\n');
// Example 1: Handling specific error types
console.log('1. Handling Specific Error Types');
console.log('--------------------------------\n');
// Test different error scenarios
const errorScenarios = [
{
name: 'Tool Permission Error',
setup: () => claude().denyTools('Bash'),
prompt: 'Run the command: ls -la'
},
{
name: 'Model Validation Error',
setup: () => claude().withModel('invalid-model'),
prompt: 'Hello'
}
];
for (const scenario of errorScenarios) {
console.log(`Testing ${scenario.name}:`);
try {
await scenario.setup()
.query(scenario.prompt)
.asText();
console.log('✅ No error occurred');
} catch (error) {
const errorType = detectErrorType(error.message);
console.log(`❌ Caught error type: ${errorType}`);
console.log(` Message: ${error.message}`);
// Type-specific handling
if (error.message.includes('denied') || error.message.includes('permission')) {
console.log(' → This looks like a permission issue');
} else if (error.message.includes('model')) {
console.log(' → This looks like a model configuration issue');
}
}
console.log();
}
// Example 2: Simulating error types
console.log('\n2. Simulating Different Error Types');
console.log('-----------------------------------\n');
// Create typed errors for demonstration
const simulatedErrors = [
createTypedError('rate_limit_error', 'Too many requests', { retryAfter: 30 }),
createTypedError('tool_permission_error', 'Bash tool denied', { tool: 'Bash' }),
createTypedError('authentication_error', 'Authentication failed - run: claude login'),
createTypedError('network_error', 'Connection timeout'),
createTypedError('timeout_error', 'Query timeout', { timeout: 5000 }),
createTypedError('validation_error', 'Invalid model name', { field: 'model' })
];
for (const error of simulatedErrors) {
console.log(`${error.constructor.name}:`);
console.log(` Message: ${error.message}`);
console.log(` Type detection: ${detectErrorType(error.message)}`);
// Show error-specific properties
if (isRateLimitError(error)) {
console.log(` Retry after: ${error.retryAfter}s`);
} else if (isToolPermissionError(error)) {
console.log(` Tool: ${error.tool}`);
} else if (isTimeoutError(error)) {
console.log(` Timeout: ${error.timeout}ms`);
} else if (isValidationError(error)) {
console.log(` Field: ${error.field}`);
}
console.log();
}
// Example 3: Retry logic demonstration
console.log('\n3. Retry Logic for Transient Errors');
console.log('-----------------------------------\n');
async function queryWithRetry(prompt, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempt ${attempt}/${maxRetries}...`);
// Simulate occasional failures
if (attempt < 2 && Math.random() < 0.7) {
throw createTypedError('network_error', 'Simulated network error');
}
return await claude()
.withModel('sonnet')
.withTimeout(10000)
.query(prompt)
.asText();
} catch (error) {
lastError = error;
console.log(`❌ Attempt ${attempt} failed: ${error.message}`);
if (attempt < maxRetries) {
if (isRateLimitError(error)) {
const waitTime = error.retryAfter || Math.pow(2, attempt);
console.log(`⏳ Rate limited. Waiting ${waitTime}s...`);
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
} else if (isNetworkError(error)) {
const waitTime = Math.pow(2, attempt - 1);
console.log(`⏳ Network error. Waiting ${waitTime}s...`);
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
} else {
// Non-retryable error
throw error;
}
}
}
}
throw lastError;
}
try {
const result = await queryWithRetry('Say "Hello from retry logic!"');
console.log('✅ Success:', result);
} catch (error) {
console.error('❌ Failed after all retries:', error.message);
}
// Example 4: Graceful degradation
console.log('\n\n4. Graceful Degradation');
console.log('-----------------------\n');
async function queryWithFallback(prompt) {
const strategies = [
{ name: 'Primary', model: 'opus', timeout: 5000 },
{ name: 'Secondary', model: 'sonnet', timeout: 10000 },
{ name: 'Fallback', model: 'sonnet', timeout: 15000 }
];
for (const strategy of strategies) {
try {
console.log(`Trying ${strategy.name} strategy (${strategy.model})...`);
return await claude()
.withModel(strategy.model)
.withTimeout(strategy.timeout)
.query(prompt)
.asText();
} catch (error) {
console.warn(`⚠️ ${strategy.name} failed: ${error.message}`);
if (strategy === strategies[strategies.length - 1]) {
throw error; // Last strategy failed
}
}
}
}
try {
const result = await queryWithFallback('What is 2+2?');
console.log('✅ Success with fallback:', result);
} catch (error) {
console.error('❌ All strategies failed:', error.message);
}
// Example 5: Error recovery patterns
console.log('\n\n5. Error Recovery Patterns');
console.log('--------------------------\n');
class ErrorRecovery {
static async withCircuitBreaker(fn, options = {}) {
const { threshold = 3, resetTime = 30000 } = options;
const state = { failures: 0, lastFailure: null, isOpen: false };
return async (...args) => {
// Check if circuit is open
if (state.isOpen) {
const timeSinceFailure = Date.now() - state.lastFailure;
if (timeSinceFailure < resetTime) {
throw new Error('Circuit breaker is OPEN');
}
// Try to close circuit
state.isOpen = false;
console.log('🔄 Circuit breaker: Attempting to close...');
}
try {
const result = await fn(...args);
// Success - reset failure count
if (state.failures > 0) {
console.log('✅ Circuit breaker: Reset after success');
state.failures = 0;
}
return result;
} catch (error) {
state.failures++;
state.lastFailure = Date.now();
if (state.failures >= threshold) {
state.isOpen = true;
console.log(`🚫 Circuit breaker: OPEN after ${state.failures} failures`);
}
throw error;
}
};
}
static async withTimeout(fn, timeout) {
return Promise.race([
fn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout)
)
]);
}
static async withFallbackValue(fn, fallbackValue) {
try {
return await fn();
} catch (error) {
console.log(`⚠️ Using fallback value due to: ${error.message}`);
return fallbackValue;
}
}
}
// Test circuit breaker
const protectedQuery = ErrorRecovery.withCircuitBreaker(
async (prompt) => {
// Simulate some failures
if (Math.random() < 0.4) {
throw new Error('Simulated service error');
}
return `Response: ${prompt}`;
},
{ threshold: 2, resetTime: 5000 }
);
console.log('Testing circuit breaker:');
for (let i = 0; i < 5; i++) {
try {
const result = await protectedQuery('Test query');
console.log(` Attempt ${i + 1}: ${result}`);
} catch (error) {
console.log(` Attempt ${i + 1}: ❌ ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Example 6: Error context and logging
console.log('\n\n6. Error Context and Logging');
console.log('----------------------------\n');
class ErrorLogger {
static log(error, context = {}) {
const errorInfo = {
timestamp: new Date().toISOString(),
type: detectErrorType(error.message),
message: error.message,
context,
stack: error.stack?.split('\n').slice(0, 3).join('\n')
};
console.log('📋 Error Log Entry:');
console.log(JSON.stringify(errorInfo, null, 2));
// Return user-friendly message
const userMessages = {
authentication_error: 'Authentication required. Please run: claude login',
rate_limit_error: 'Too many requests. Please try again later.',
network_error: 'Connection issue. Please check your internet.',
tool_permission_error: 'This operation requires additional permissions.',
timeout_error: 'The operation took too long. Please try again.',
validation_error: 'Invalid input. Please check your parameters.',
api_error: 'Something went wrong. Please try again.'
};
return userMessages[errorInfo.type] || userMessages.api_error;
}
}
// Test error logging
const testError = createTypedError('authentication_error', 'Authentication failed');
const userMessage = ErrorLogger.log(testError, {
userId: 'user123',
action: 'query',
model: 'opus'
});
console.log('\n👤 User-friendly message:', userMessage);
console.log('\n✨ Error handling examples completed!');
}
// Error handling wrapper with final fallback
errorHandlingExample().catch(error => {
console.error('\n💥 Unhandled error in example:', error);
console.error('This is the final error boundary.');
process.exit(1);
});