feat: add initial implementation of Simple AI Provider package

This commit is contained in:
2025-05-28 11:54:24 +02:00
commit 42902445fb
15 changed files with 1616 additions and 0 deletions

138
tests/claude.test.ts Normal file
View File

@ -0,0 +1,138 @@
/**
* Tests for Claude Provider
*/
import { describe, it, expect, beforeEach } from 'bun:test';
import { ClaudeProvider, AIProviderError, AIErrorType } from '../src/index.js';
describe('ClaudeProvider', () => {
let provider: ClaudeProvider;
beforeEach(() => {
provider = new ClaudeProvider({
apiKey: 'test-api-key',
defaultModel: 'claude-3-5-haiku-20241022'
});
});
describe('constructor', () => {
it('should create provider with valid config', () => {
expect(provider).toBeInstanceOf(ClaudeProvider);
expect(provider.isInitialized()).toBe(false);
});
it('should throw error for missing API key', () => {
expect(() => {
new ClaudeProvider({ apiKey: '' });
}).toThrow(AIProviderError);
});
});
describe('getInfo', () => {
it('should return provider information', () => {
const info = provider.getInfo();
expect(info.name).toBe('Claude');
expect(info.version).toBe('1.0.0');
expect(info.supportsStreaming).toBe(true);
expect(info.models).toContain('claude-3-5-sonnet-20241022');
expect(info.maxContextLength).toBe(200000);
});
});
describe('validation', () => {
it('should validate temperature range', async () => {
// Mock initialization to avoid API call
(provider as any).initialized = true;
(provider as any).client = {};
await expect(
provider.complete({
messages: [{ role: 'user', content: 'test' }],
temperature: 1.5
})
).rejects.toThrow('Temperature must be between 0.0 and 1.0');
});
it('should validate message format', async () => {
(provider as any).initialized = true;
(provider as any).client = {};
await expect(
provider.complete({
messages: [{ role: 'invalid' as any, content: 'test' }]
})
).rejects.toThrow('Each message must have a valid role');
});
it('should require initialization before use', async () => {
await expect(
provider.complete({
messages: [{ role: 'user', content: 'test' }]
})
).rejects.toThrow('Provider must be initialized before use');
});
});
describe('error handling', () => {
it('should handle authentication errors', () => {
const error = new Error('Unauthorized');
(error as any).status = 401;
const providerError = (provider as any).handleAnthropicError(error);
expect(providerError).toBeInstanceOf(AIProviderError);
expect(providerError.type).toBe(AIErrorType.AUTHENTICATION);
});
it('should handle rate limit errors', () => {
const error = new Error('Rate limited');
(error as any).status = 429;
const providerError = (provider as any).handleAnthropicError(error);
expect(providerError).toBeInstanceOf(AIProviderError);
expect(providerError.type).toBe(AIErrorType.RATE_LIMIT);
});
it('should handle model not found errors', () => {
const error = new Error('Model not found');
(error as any).status = 404;
const providerError = (provider as any).handleAnthropicError(error);
expect(providerError).toBeInstanceOf(AIProviderError);
expect(providerError.type).toBe(AIErrorType.MODEL_NOT_FOUND);
});
});
describe('message conversion', () => {
it('should separate system messages from conversation', () => {
const messages = [
{ role: 'system' as const, content: 'You are helpful' },
{ role: 'user' as const, content: 'Hello' },
{ role: 'assistant' as const, content: 'Hi there' },
{ role: 'system' as const, content: 'Be concise' }
];
const result = (provider as any).convertMessages(messages);
expect(result.system).toBe('You are helpful\n\nBe concise');
expect(result.messages).toHaveLength(2);
expect(result.messages[0].role).toBe('user');
expect(result.messages[1].role).toBe('assistant');
});
it('should handle messages without system prompts', () => {
const messages = [
{ role: 'user' as const, content: 'Hello' },
{ role: 'assistant' as const, content: 'Hi there' }
];
const result = (provider as any).convertMessages(messages);
expect(result.system).toBeNull();
expect(result.messages).toHaveLength(2);
});
});
});