feat(models): add streaming support to AnthropicClient

This commit is contained in:
William Valentin
2026-02-05 10:47:42 -08:00
parent 1f0cf28d1f
commit 896a0da10e
2 changed files with 75 additions and 2 deletions
+38 -2
View File
@@ -1,8 +1,9 @@
import Anthropic from '@anthropic-ai/sdk';
import type { ChatRequest, ChatResponse, ModelClient } from './types.js';
import type { ChatRequest, ChatResponse, ChatStreamEvent, ModelClient } from './types.js';
export interface AnthropicClientConfig {
apiKey?: string; // Falls back to ANTHROPIC_API_KEY env var
apiKey?: string; // Falls back to ANTHROPIC_API_KEY env var
authToken?: string; // Alternative: use auth token instead of API key
model: string;
maxTokens?: number;
}
@@ -15,6 +16,7 @@ export class AnthropicClient implements ModelClient {
constructor(config: AnthropicClientConfig) {
this.client = new Anthropic({
apiKey: config.apiKey,
authToken: config.authToken,
});
this.model = config.model;
this.defaultMaxTokens = config.maxTokens ?? 4096;
@@ -43,4 +45,38 @@ export class AnthropicClient implements ModelClient {
},
};
}
async *chatStream(request: ChatRequest): AsyncIterable<ChatStreamEvent> {
const stream = this.client.messages.stream({
model: this.model,
max_tokens: request.maxTokens ?? this.defaultMaxTokens,
system: request.system,
messages: request.messages.map((m) => ({
role: m.role,
content: m.content,
})),
});
try {
for await (const event of stream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
yield { type: 'content', content: event.delta.text };
}
}
const finalMessage = await stream.finalMessage();
yield {
type: 'done',
usage: {
inputTokens: finalMessage.usage.input_tokens,
outputTokens: finalMessage.usage.output_tokens,
},
};
} catch (error) {
yield {
type: 'error',
error: error instanceof Error ? error : new Error(String(error)),
};
}
}
}