194 lines
5.6 KiB
JavaScript
194 lines
5.6 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Enhanced Features Demo
|
|||
|
|
*
|
|||
|
|
* This example demonstrates the new enhanced features added to Claude Code SDK:
|
|||
|
|
* 1. Typed error handling
|
|||
|
|
* 2. Token-level streaming
|
|||
|
|
* 3. Per-call tool permissions
|
|||
|
|
* 4. OpenTelemetry integration
|
|||
|
|
* 5. Exponential backoff
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import {
|
|||
|
|
query,
|
|||
|
|
createTokenStream,
|
|||
|
|
createPermissionManager,
|
|||
|
|
createTelemetryProvider,
|
|||
|
|
createRetryExecutor,
|
|||
|
|
isRateLimitError,
|
|||
|
|
isToolPermissionError
|
|||
|
|
} from '../dist/index.mjs';
|
|||
|
|
|
|||
|
|
async function runDemo() {
|
|||
|
|
console.log('🚀 Claude Code SDK Enhanced Features Demo\n');
|
|||
|
|
|
|||
|
|
// 1. Typed Error Handling Demo
|
|||
|
|
console.log('1️⃣ Typed Error Handling');
|
|||
|
|
console.log('------------------------');
|
|||
|
|
try {
|
|||
|
|
// Simulate an error scenario
|
|||
|
|
const messages = [];
|
|||
|
|
for await (const message of query('Simulate a rate limit error')) {
|
|||
|
|
messages.push(message);
|
|||
|
|
if (message.type === 'error') {
|
|||
|
|
throw new Error('Rate limit exceeded: too many requests');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
if (isRateLimitError(error)) {
|
|||
|
|
console.log(`❌ Rate limited! Retry after ${error.retryAfter} seconds`);
|
|||
|
|
} else if (isToolPermissionError(error)) {
|
|||
|
|
console.log(`❌ Tool permission denied: ${error.tool}`);
|
|||
|
|
} else {
|
|||
|
|
console.log(`❌ Error: ${error.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
console.log();
|
|||
|
|
|
|||
|
|
// 2. Token Streaming Demo
|
|||
|
|
console.log('2️⃣ Token-Level Streaming');
|
|||
|
|
console.log('------------------------');
|
|||
|
|
const messageGenerator = query('Write a haiku about programming');
|
|||
|
|
const tokenStream = createTokenStream(messageGenerator);
|
|||
|
|
|
|||
|
|
console.log('Streaming tokens in real-time:');
|
|||
|
|
let tokenCount = 0;
|
|||
|
|
for await (const chunk of tokenStream.tokens()) {
|
|||
|
|
process.stdout.write(chunk.token);
|
|||
|
|
tokenCount++;
|
|||
|
|
|
|||
|
|
// Demonstrate pause/resume
|
|||
|
|
if (tokenCount === 10) {
|
|||
|
|
console.log('\n⏸️ Pausing stream...');
|
|||
|
|
tokenStream.getController().pause();
|
|||
|
|
setTimeout(() => {
|
|||
|
|
console.log('▶️ Resuming stream...');
|
|||
|
|
tokenStream.getController().resume();
|
|||
|
|
}, 1000);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`\n\n📊 Stream metrics:`, tokenStream.getMetrics());
|
|||
|
|
console.log();
|
|||
|
|
|
|||
|
|
// 3. Per-Call Permissions Demo
|
|||
|
|
console.log('3️⃣ Per-Call Tool Permissions');
|
|||
|
|
console.log('-----------------------------');
|
|||
|
|
const permissionManager = createPermissionManager({
|
|||
|
|
allowedTools: ['Read', 'Write'],
|
|||
|
|
deniedTools: ['Bash']
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Check permissions with different contexts
|
|||
|
|
const contexts = [
|
|||
|
|
{ userId: 'admin', role: 'admin' },
|
|||
|
|
{ userId: 'user123', role: 'user' }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const context of contexts) {
|
|||
|
|
console.log(`\nChecking permissions for ${context.role}:`);
|
|||
|
|
const isReadAllowed = await permissionManager.isToolAllowed('Read', context);
|
|||
|
|
const isBashAllowed = await permissionManager.isToolAllowed('Bash', context);
|
|||
|
|
console.log(`- Read: ${isReadAllowed ? '✅ Allowed' : '❌ Denied'}`);
|
|||
|
|
console.log(`- Bash: ${isBashAllowed ? '✅ Allowed' : '❌ Denied'}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Dynamic permission based on time
|
|||
|
|
const timeBasedOverride = {
|
|||
|
|
dynamicPermissions: {
|
|||
|
|
Write: async (ctx) => {
|
|||
|
|
const hour = new Date().getHours();
|
|||
|
|
return (hour >= 9 && hour < 17) ? 'allow' : 'deny';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const isWriteAllowed = await permissionManager.isToolAllowed(
|
|||
|
|
'Write',
|
|||
|
|
{ userId: 'user123' },
|
|||
|
|
timeBasedOverride
|
|||
|
|
);
|
|||
|
|
console.log(`\n⏰ Write permission (business hours only): ${isWriteAllowed ? '✅ Allowed' : '❌ Denied'}`);
|
|||
|
|
console.log();
|
|||
|
|
|
|||
|
|
// 4. OpenTelemetry Integration Demo
|
|||
|
|
console.log('4️⃣ OpenTelemetry Integration');
|
|||
|
|
console.log('-----------------------------');
|
|||
|
|
const telemetryProvider = createTelemetryProvider();
|
|||
|
|
await telemetryProvider.initialize({
|
|||
|
|
serviceName: 'claude-sdk-demo',
|
|||
|
|
serviceVersion: '1.0.0',
|
|||
|
|
environment: 'demo'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const logger = telemetryProvider.getLogger('demo');
|
|||
|
|
const span = logger.startSpan('demo-query', {
|
|||
|
|
attributes: {
|
|||
|
|
'demo.feature': 'telemetry',
|
|||
|
|
'demo.user': 'test-user'
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
span.addEvent('query-start');
|
|||
|
|
|
|||
|
|
// Simulate some work
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|||
|
|
|
|||
|
|
span.setAttribute('demo.result', 'success');
|
|||
|
|
span.setStatus('ok');
|
|||
|
|
} catch (error) {
|
|||
|
|
span.recordException(error);
|
|||
|
|
} finally {
|
|||
|
|
span.end();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logger.recordMetric('demo_queries_total', 1, { feature: 'telemetry' });
|
|||
|
|
console.log('📈 Telemetry span created and metrics recorded');
|
|||
|
|
console.log(`📊 Query metrics:`, telemetryProvider.getQueryMetrics());
|
|||
|
|
await telemetryProvider.shutdown();
|
|||
|
|
console.log();
|
|||
|
|
|
|||
|
|
// 5. Exponential Backoff Demo
|
|||
|
|
console.log('5️⃣ Exponential Backoff & Retry');
|
|||
|
|
console.log('--------------------------------');
|
|||
|
|
const retryExecutor = createRetryExecutor({
|
|||
|
|
maxAttempts: 3,
|
|||
|
|
initialDelay: 1000,
|
|||
|
|
multiplier: 2,
|
|||
|
|
jitter: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
let attemptCount = 0;
|
|||
|
|
try {
|
|||
|
|
const result = await retryExecutor.execute(async () => {
|
|||
|
|
attemptCount++;
|
|||
|
|
console.log(`🔄 Attempt ${attemptCount}...`);
|
|||
|
|
|
|||
|
|
if (attemptCount < 3) {
|
|||
|
|
throw new Error('Temporary network error');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 'Success!';
|
|||
|
|
}, {
|
|||
|
|
onRetry: (attempt, error, nextDelay) => {
|
|||
|
|
console.log(`⚠️ Retry ${attempt} after error: ${error.message}`);
|
|||
|
|
console.log(`⏱️ Waiting ${nextDelay}ms before next attempt...`);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(`✅ Result: ${result}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.log(`❌ Failed after retries: ${error.message}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const stats = retryExecutor.getStats();
|
|||
|
|
console.log(`\n📊 Retry statistics:`, stats);
|
|||
|
|
|
|||
|
|
console.log('\n✨ Demo completed!');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Run the demo
|
|||
|
|
runDemo().catch(console.error);
|