214 lines
6.6 KiB
JavaScript
214 lines
6.6 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Token Streaming Example
|
|||
|
|
*
|
|||
|
|
* This example demonstrates how to use the SDK's token streaming feature
|
|||
|
|
* to receive Claude's responses in real-time, token by token.
|
|||
|
|
*
|
|||
|
|
* Use cases:
|
|||
|
|
* - Building responsive chat interfaces
|
|||
|
|
* - Displaying progress for long-running generations
|
|||
|
|
* - Implementing typewriter effects in UIs
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { claude, createTokenStream } from '@instantlyeasy/claude-code-sdk-ts';
|
|||
|
|
|
|||
|
|
async function tokenStreamingExample() {
|
|||
|
|
console.log('📝 Token Streaming Example\n');
|
|||
|
|
|
|||
|
|
// Example 1: Basic token streaming
|
|||
|
|
console.log('1. Basic Token Streaming');
|
|||
|
|
console.log('------------------------');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// Create a raw query generator for token streaming
|
|||
|
|
const messageGenerator = claude()
|
|||
|
|
.withModel('sonnet') // Using faster model for demo
|
|||
|
|
.queryRaw('Write a short story about a robot learning to paint, in exactly 3 sentences.');
|
|||
|
|
|
|||
|
|
// Create a token stream from the message generator
|
|||
|
|
const tokenStream = createTokenStream(messageGenerator);
|
|||
|
|
|
|||
|
|
console.log('Streaming response:\n');
|
|||
|
|
|
|||
|
|
// Collect tokens for display
|
|||
|
|
const tokens = [];
|
|||
|
|
for await (const chunk of tokenStream.tokens()) {
|
|||
|
|
// Display each token as it arrives
|
|||
|
|
process.stdout.write(chunk.token);
|
|||
|
|
tokens.push(chunk.token);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Get streaming metrics
|
|||
|
|
const metrics = tokenStream.getMetrics();
|
|||
|
|
console.log('\n\n📊 Streaming Metrics:');
|
|||
|
|
console.log(`- Tokens received: ${metrics.tokensEmitted}`);
|
|||
|
|
console.log(`- Duration: ${metrics.duration}ms`);
|
|||
|
|
console.log(`- State: ${metrics.state}`);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Streaming error:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Example 2: Controlled streaming with pause/resume
|
|||
|
|
console.log('\n\n2. Controlled Streaming (Pause/Resume)');
|
|||
|
|
console.log('--------------------------------------');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const messageGenerator = claude()
|
|||
|
|
.withModel('sonnet')
|
|||
|
|
.queryRaw('Count from 1 to 10 slowly, with each number on a new line.');
|
|||
|
|
|
|||
|
|
const tokenStream = createTokenStream(messageGenerator);
|
|||
|
|
const controller = tokenStream.getController();
|
|||
|
|
|
|||
|
|
console.log('Streaming with pause control:\n');
|
|||
|
|
|
|||
|
|
let tokenCount = 0;
|
|||
|
|
for await (const chunk of tokenStream.tokens()) {
|
|||
|
|
process.stdout.write(chunk.token);
|
|||
|
|
tokenCount++;
|
|||
|
|
|
|||
|
|
// Pause after receiving 5 tokens
|
|||
|
|
if (tokenCount === 5 && controller.getState() === 'streaming') {
|
|||
|
|
console.log('\n\n⏸️ Pausing stream for 2 seconds...');
|
|||
|
|
controller.pause();
|
|||
|
|
|
|||
|
|
// Resume after 2 seconds
|
|||
|
|
setTimeout(() => {
|
|||
|
|
console.log('▶️ Resuming stream...\n');
|
|||
|
|
controller.resume();
|
|||
|
|
}, 2000);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\n\n✅ Streaming completed');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Controlled streaming error:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Example 3: Building a progress indicator
|
|||
|
|
console.log('\n\n3. Progress Indicator Example');
|
|||
|
|
console.log('-----------------------------');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const messageGenerator = claude()
|
|||
|
|
.withModel('sonnet')
|
|||
|
|
.queryRaw('List 5 interesting facts about space exploration.');
|
|||
|
|
|
|||
|
|
const tokenStream = createTokenStream(messageGenerator);
|
|||
|
|
|
|||
|
|
console.log('Generating response with progress:\n');
|
|||
|
|
|
|||
|
|
// Collect tokens while showing progress
|
|||
|
|
const allTokens = [];
|
|||
|
|
const progressWidth = 30;
|
|||
|
|
let receivedTokens = 0;
|
|||
|
|
|
|||
|
|
for await (const chunk of tokenStream.tokens()) {
|
|||
|
|
allTokens.push(chunk.token);
|
|||
|
|
receivedTokens++;
|
|||
|
|
|
|||
|
|
// Update progress bar every 5 tokens
|
|||
|
|
if (receivedTokens % 5 === 0) {
|
|||
|
|
const progress = Math.min(receivedTokens / 100, 1); // Assume ~100 tokens
|
|||
|
|
const filled = Math.floor(progress * progressWidth);
|
|||
|
|
const empty = progressWidth - filled;
|
|||
|
|
process.stdout.write(`\r[${'█'.repeat(filled)}${' '.repeat(empty)}] ${Math.floor(progress * 100)}%`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Clear progress bar and show completion
|
|||
|
|
process.stdout.write('\r' + ' '.repeat(progressWidth + 10) + '\r');
|
|||
|
|
console.log('✅ Response generated successfully!\n');
|
|||
|
|
|
|||
|
|
// Show the complete response
|
|||
|
|
console.log('Complete response:');
|
|||
|
|
console.log(allTokens.join(''));
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Progress indicator error:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Example 4: Token streaming with event handlers
|
|||
|
|
console.log('\n\n4. Token Streaming with Event Handlers');
|
|||
|
|
console.log('--------------------------------------');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// Track different types of content
|
|||
|
|
let textTokens = 0;
|
|||
|
|
let toolCalls = 0;
|
|||
|
|
|
|||
|
|
const messageGenerator = claude()
|
|||
|
|
.withModel('sonnet')
|
|||
|
|
.onMessage(msg => {
|
|||
|
|
if (msg.type === 'assistant') {
|
|||
|
|
for (const block of msg.content) {
|
|||
|
|
if (block.type === 'tool_use') {
|
|||
|
|
toolCalls++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.queryRaw('What is the weather like today? (Just make something up)');
|
|||
|
|
|
|||
|
|
const tokenStream = createTokenStream(messageGenerator);
|
|||
|
|
|
|||
|
|
console.log('Streaming with event tracking:\n');
|
|||
|
|
|
|||
|
|
for await (const chunk of tokenStream.tokens()) {
|
|||
|
|
process.stdout.write(chunk.token);
|
|||
|
|
textTokens++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\n\n📊 Event Statistics:');
|
|||
|
|
console.log(`- Text tokens: ${textTokens}`);
|
|||
|
|
console.log(`- Tool calls: ${toolCalls}`);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Event handler error:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Example 5: Error handling in token streams
|
|||
|
|
console.log('\n\n5. Token Stream Error Handling');
|
|||
|
|
console.log('------------------------------');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const messageGenerator = claude()
|
|||
|
|
.withModel('sonnet')
|
|||
|
|
.withTimeout(3000) // Short timeout for demo
|
|||
|
|
.queryRaw('Write a very long essay about the history of computing');
|
|||
|
|
|
|||
|
|
const tokenStream = createTokenStream(messageGenerator);
|
|||
|
|
|
|||
|
|
console.log('Attempting to stream with timeout:\n');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
for await (const chunk of tokenStream.tokens()) {
|
|||
|
|
process.stdout.write(chunk.token);
|
|||
|
|
}
|
|||
|
|
console.log('\n✅ Completed successfully');
|
|||
|
|
} catch (streamError) {
|
|||
|
|
console.error('\n❌ Stream error:', streamError.message);
|
|||
|
|
const metrics = tokenStream.getMetrics();
|
|||
|
|
console.log('📊 Partial metrics:', {
|
|||
|
|
tokensReceived: metrics.tokensEmitted,
|
|||
|
|
duration: metrics.duration,
|
|||
|
|
state: metrics.state
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ Setup error:', error.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('\n✨ Token streaming examples completed!');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Error handling wrapper
|
|||
|
|
tokenStreamingExample().catch(error => {
|
|||
|
|
console.error('Fatal error:', error);
|
|||
|
|
process.exit(1);
|
|||
|
|
});
|