@bernierllc/ai-provider-anthropic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +34 -0
- package/README.md +310 -0
- package/__tests__/AnthropicProvider.test.ts +655 -0
- package/__tests__/error-handling.test.ts +208 -0
- package/__tests__/message-conversion.test.ts +161 -0
- package/__tests__/model-registry.test.ts +150 -0
- package/dist/AnthropicProvider.d.ts +54 -0
- package/dist/AnthropicProvider.js +337 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +34 -0
- package/dist/models/model-registry.d.ts +31 -0
- package/dist/models/model-registry.js +113 -0
- package/dist/types/anthropic-types.d.ts +46 -0
- package/dist/types/anthropic-types.js +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +24 -0
- package/dist/utils/error-handling.d.ts +12 -0
- package/dist/utils/error-handling.js +67 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +25 -0
- package/dist/utils/message-conversion.d.ts +17 -0
- package/dist/utils/message-conversion.js +65 -0
- package/jest.config.js +30 -0
- package/package.json +59 -0
- package/src/AnthropicProvider.ts +392 -0
- package/src/index.ts +19 -0
- package/src/models/model-registry.ts +120 -0
- package/src/types/anthropic-types.ts +60 -0
- package/src/types/index.ts +9 -0
- package/src/utils/error-handling.ts +71 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/message-conversion.ts +78 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ModelInfo } from '@bernierllc/ai-provider-core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Claude 3 Model Registry
|
|
13
|
+
* Contains information about all available Claude models
|
|
14
|
+
*/
|
|
15
|
+
export class ClaudeModelRegistry {
|
|
16
|
+
private static models: Map<string, ModelInfo> = new Map();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize the model registry with Claude 3 models
|
|
20
|
+
*/
|
|
21
|
+
static initialize(): void {
|
|
22
|
+
const models: ModelInfo[] = [
|
|
23
|
+
{
|
|
24
|
+
id: 'claude-3-opus-20240229',
|
|
25
|
+
name: 'Claude 3 Opus',
|
|
26
|
+
contextWindow: 200000,
|
|
27
|
+
maxOutputTokens: 4096,
|
|
28
|
+
pricing: {
|
|
29
|
+
inputPricePerToken: 15 / 1000000,
|
|
30
|
+
outputPricePerToken: 75 / 1000000,
|
|
31
|
+
currency: 'USD'
|
|
32
|
+
},
|
|
33
|
+
capabilities: ['chat', 'completion', 'vision', 'extended-context', 'analysis'],
|
|
34
|
+
description: 'Most capable Claude model for complex tasks with superior reasoning'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'claude-3-sonnet-20240229',
|
|
38
|
+
name: 'Claude 3 Sonnet',
|
|
39
|
+
contextWindow: 200000,
|
|
40
|
+
maxOutputTokens: 4096,
|
|
41
|
+
pricing: {
|
|
42
|
+
inputPricePerToken: 3 / 1000000,
|
|
43
|
+
outputPricePerToken: 15 / 1000000,
|
|
44
|
+
currency: 'USD'
|
|
45
|
+
},
|
|
46
|
+
capabilities: ['chat', 'completion', 'vision', 'extended-context'],
|
|
47
|
+
description: 'Balanced performance and speed for most tasks'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'claude-3-haiku-20240307',
|
|
51
|
+
name: 'Claude 3 Haiku',
|
|
52
|
+
contextWindow: 200000,
|
|
53
|
+
maxOutputTokens: 4096,
|
|
54
|
+
pricing: {
|
|
55
|
+
inputPricePerToken: 0.25 / 1000000,
|
|
56
|
+
outputPricePerToken: 1.25 / 1000000,
|
|
57
|
+
currency: 'USD'
|
|
58
|
+
},
|
|
59
|
+
capabilities: ['chat', 'completion', 'vision', 'extended-context'],
|
|
60
|
+
description: 'Fastest and most compact model for quick responses'
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
models.forEach(model => {
|
|
65
|
+
this.models.set(model.id, model);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get a specific model by ID
|
|
71
|
+
*/
|
|
72
|
+
static getModel(modelId: string): ModelInfo | undefined {
|
|
73
|
+
if (this.models.size === 0) {
|
|
74
|
+
this.initialize();
|
|
75
|
+
}
|
|
76
|
+
return this.models.get(modelId);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get all available models
|
|
81
|
+
*/
|
|
82
|
+
static getAllModels(): ModelInfo[] {
|
|
83
|
+
if (this.models.size === 0) {
|
|
84
|
+
this.initialize();
|
|
85
|
+
}
|
|
86
|
+
return Array.from(this.models.values());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if a model ID is valid
|
|
91
|
+
*/
|
|
92
|
+
static isValidModel(modelId: string): boolean {
|
|
93
|
+
if (this.models.size === 0) {
|
|
94
|
+
this.initialize();
|
|
95
|
+
}
|
|
96
|
+
return this.models.has(modelId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get pricing for a specific model
|
|
101
|
+
*/
|
|
102
|
+
static getModelPricing(modelId: string): { inputPrice: number; outputPrice: number } {
|
|
103
|
+
const model = this.getModel(modelId);
|
|
104
|
+
if (model?.pricing) {
|
|
105
|
+
return {
|
|
106
|
+
inputPrice: model.pricing.inputPricePerToken * 1000000,
|
|
107
|
+
outputPrice: model.pricing.outputPricePerToken * 1000000
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Default to Sonnet pricing
|
|
112
|
+
return {
|
|
113
|
+
inputPrice: 3,
|
|
114
|
+
outputPrice: 15
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Initialize on module load
|
|
120
|
+
ClaudeModelRegistry.initialize();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AIProviderConfig, CompletionRequest } from '@bernierllc/ai-provider-core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Anthropic Provider Configuration
|
|
13
|
+
*/
|
|
14
|
+
export interface AnthropicProviderConfig extends AIProviderConfig {
|
|
15
|
+
providerName: 'anthropic';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Anthropic Extended Context Request
|
|
20
|
+
*/
|
|
21
|
+
export interface AnthropicExtendedContextRequest extends CompletionRequest {
|
|
22
|
+
enableExtendedContext?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Anthropic Vision Request
|
|
27
|
+
*/
|
|
28
|
+
export interface AnthropicVisionRequest {
|
|
29
|
+
imageData: string | Buffer;
|
|
30
|
+
prompt: string;
|
|
31
|
+
model?: string;
|
|
32
|
+
maxTokens?: number;
|
|
33
|
+
temperature?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Anthropic Message Content Block
|
|
38
|
+
*/
|
|
39
|
+
export interface AnthropicMessageContent {
|
|
40
|
+
type: 'text' | 'image';
|
|
41
|
+
text?: string;
|
|
42
|
+
source?: {
|
|
43
|
+
type: 'base64';
|
|
44
|
+
media_type: string;
|
|
45
|
+
data: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Anthropic Message Format (Internal)
|
|
51
|
+
*/
|
|
52
|
+
export interface AnthropicMessage {
|
|
53
|
+
role: 'user' | 'assistant';
|
|
54
|
+
content: string | AnthropicMessageContent[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Anthropic Stop Reason
|
|
59
|
+
*/
|
|
60
|
+
export type AnthropicStopReason = 'end_turn' | 'max_tokens' | 'stop_sequence' | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './anthropic-types';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handle Anthropic API errors and convert to provider error format
|
|
13
|
+
*/
|
|
14
|
+
export function handleAnthropicError(error: unknown): Error {
|
|
15
|
+
if (error instanceof Anthropic.APIError) {
|
|
16
|
+
const status = error.status || 'unknown';
|
|
17
|
+
const message = `Anthropic API Error (${status}): ${error.message}`;
|
|
18
|
+
|
|
19
|
+
const providerError = new Error(message);
|
|
20
|
+
providerError.name = 'AnthropicAPIError';
|
|
21
|
+
return providerError;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (error instanceof Anthropic.AuthenticationError) {
|
|
25
|
+
const providerError = new Error('Anthropic authentication failed. Check your API key.');
|
|
26
|
+
providerError.name = 'AnthropicAuthError';
|
|
27
|
+
return providerError;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (error instanceof Anthropic.RateLimitError) {
|
|
31
|
+
const providerError = new Error('Anthropic rate limit exceeded. Please retry after a delay.');
|
|
32
|
+
providerError.name = 'AnthropicRateLimitError';
|
|
33
|
+
return providerError;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (error instanceof Error) {
|
|
37
|
+
return error;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Error(`Unknown Anthropic error: ${String(error)}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if error is retryable
|
|
45
|
+
*/
|
|
46
|
+
export function isRetryableError(error: unknown): boolean {
|
|
47
|
+
if (error instanceof Anthropic.APIError) {
|
|
48
|
+
// Retry on 5xx errors and rate limits
|
|
49
|
+
const status = error.status || 0;
|
|
50
|
+
return status >= 500 || status === 429;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (error instanceof Anthropic.RateLimitError) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get retry delay from error (if available)
|
|
62
|
+
*/
|
|
63
|
+
export function getRetryDelay(error: unknown): number | undefined {
|
|
64
|
+
if (error instanceof Anthropic.RateLimitError) {
|
|
65
|
+
// Check for Retry-After header if available
|
|
66
|
+
// Default to 1 second if not specified
|
|
67
|
+
return 1000;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './message-conversion';
|
|
10
|
+
export * from './error-handling';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Message } from '@bernierllc/ai-provider-core';
|
|
10
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert messages from unified format to Anthropic format
|
|
14
|
+
* Extracts system messages separately (Anthropic uses system parameter, not messages array)
|
|
15
|
+
*/
|
|
16
|
+
export function convertMessagesToAnthropicFormat(messages: Message[]): {
|
|
17
|
+
system?: string;
|
|
18
|
+
messages: Anthropic.MessageParam[];
|
|
19
|
+
} {
|
|
20
|
+
// Extract system messages
|
|
21
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
22
|
+
const system = systemMessages.length > 0
|
|
23
|
+
? systemMessages.map(m => m.content).join('\n\n')
|
|
24
|
+
: undefined;
|
|
25
|
+
|
|
26
|
+
// Convert remaining messages to Anthropic format
|
|
27
|
+
const anthropicMessages: Anthropic.MessageParam[] = messages
|
|
28
|
+
.filter(m => m.role !== 'system')
|
|
29
|
+
.map(msg => ({
|
|
30
|
+
role: msg.role === 'assistant' ? ('assistant' as const) : ('user' as const),
|
|
31
|
+
content: msg.content
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
return { system, messages: anthropicMessages };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate message format before conversion
|
|
39
|
+
*/
|
|
40
|
+
export function validateMessages(messages: Message[]): {
|
|
41
|
+
isValid: boolean;
|
|
42
|
+
error?: string;
|
|
43
|
+
} {
|
|
44
|
+
if (!messages || messages.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
isValid: false,
|
|
47
|
+
error: 'Messages array cannot be empty'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check that all messages have required fields
|
|
52
|
+
for (const msg of messages) {
|
|
53
|
+
if (!msg.role || !msg.content) {
|
|
54
|
+
return {
|
|
55
|
+
isValid: false,
|
|
56
|
+
error: 'All messages must have role and content'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!['system', 'user', 'assistant'].includes(msg.role)) {
|
|
61
|
+
return {
|
|
62
|
+
isValid: false,
|
|
63
|
+
error: `Invalid message role: ${msg.role}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Ensure non-system messages exist
|
|
69
|
+
const nonSystemMessages = messages.filter(m => m.role !== 'system');
|
|
70
|
+
if (nonSystemMessages.length === 0) {
|
|
71
|
+
return {
|
|
72
|
+
isValid: false,
|
|
73
|
+
error: 'At least one non-system message is required'
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { isValid: true };
|
|
78
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"strictFunctionTypes": true,
|
|
18
|
+
"strictBindCallApply": true,
|
|
19
|
+
"strictPropertyInitialization": true,
|
|
20
|
+
"noImplicitThis": true,
|
|
21
|
+
"alwaysStrict": true,
|
|
22
|
+
"noUnusedLocals": true,
|
|
23
|
+
"noUnusedParameters": true,
|
|
24
|
+
"noImplicitReturns": true,
|
|
25
|
+
"noFallthroughCasesInSwitch": true
|
|
26
|
+
},
|
|
27
|
+
"include": ["src/**/*"],
|
|
28
|
+
"exclude": ["node_modules", "dist", "__tests__"]
|
|
29
|
+
}
|