feat: add Anthropic client wrapper
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { AnthropicClient } from './anthropic.js';
|
||||||
|
|
||||||
|
// Mock the SDK
|
||||||
|
vi.mock('@anthropic-ai/sdk', () => ({
|
||||||
|
default: vi.fn().mockImplementation(() => ({
|
||||||
|
messages: {
|
||||||
|
create: vi.fn().mockResolvedValue({
|
||||||
|
content: [{ type: 'text', text: 'Hello from Claude!' }],
|
||||||
|
stop_reason: 'end_turn',
|
||||||
|
usage: { input_tokens: 10, output_tokens: 5 },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AnthropicClient', () => {
|
||||||
|
it('sends messages and returns response', async () => {
|
||||||
|
const client = new AnthropicClient({
|
||||||
|
apiKey: 'test-key',
|
||||||
|
model: 'claude-sonnet-4-20250514',
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await client.chat({
|
||||||
|
messages: [{ role: 'user', content: 'Hello' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.content).toBe('Hello from Claude!');
|
||||||
|
expect(response.stopReason).toBe('end_turn');
|
||||||
|
expect(response.usage.inputTokens).toBe(10);
|
||||||
|
expect(response.usage.outputTokens).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import Anthropic from '@anthropic-ai/sdk';
|
||||||
|
import type { ChatRequest, ChatResponse, ModelClient } from './types.js';
|
||||||
|
|
||||||
|
export interface AnthropicClientConfig {
|
||||||
|
apiKey?: string; // Falls back to ANTHROPIC_API_KEY env var
|
||||||
|
model: string;
|
||||||
|
maxTokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AnthropicClient implements ModelClient {
|
||||||
|
private client: Anthropic;
|
||||||
|
private model: string;
|
||||||
|
private defaultMaxTokens: number;
|
||||||
|
|
||||||
|
constructor(config: AnthropicClientConfig) {
|
||||||
|
this.client = new Anthropic({
|
||||||
|
apiKey: config.apiKey,
|
||||||
|
});
|
||||||
|
this.model = config.model;
|
||||||
|
this.defaultMaxTokens = config.maxTokens ?? 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
async chat(request: ChatRequest): Promise<ChatResponse> {
|
||||||
|
const response = await this.client.messages.create({
|
||||||
|
model: this.model,
|
||||||
|
max_tokens: request.maxTokens ?? this.defaultMaxTokens,
|
||||||
|
system: request.system,
|
||||||
|
messages: request.messages.map((m) => ({
|
||||||
|
role: m.role,
|
||||||
|
content: m.content,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
const textContent = response.content.find((c) => c.type === 'text');
|
||||||
|
const content = textContent?.type === 'text' ? textContent.text : '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
stopReason: response.stop_reason ?? 'end_turn',
|
||||||
|
usage: {
|
||||||
|
inputTokens: response.usage.input_tokens,
|
||||||
|
outputTokens: response.usage.output_tokens,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { AnthropicClient, type AnthropicClientConfig } from './anthropic.js';
|
||||||
|
export type { Message, ChatRequest, ChatResponse, ModelClient } from './types.js';
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export interface Message {
|
||||||
|
role: 'user' | 'assistant';
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatRequest {
|
||||||
|
messages: Message[];
|
||||||
|
system?: string;
|
||||||
|
maxTokens?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatResponse {
|
||||||
|
content: string;
|
||||||
|
stopReason: 'end_turn' | 'max_tokens' | 'stop_sequence' | string;
|
||||||
|
usage: {
|
||||||
|
inputTokens: number;
|
||||||
|
outputTokens: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelClient {
|
||||||
|
chat(request: ChatRequest): Promise<ChatResponse>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user