@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,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
21
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
__exportStar(require("./message-conversion"), exports);
|
|
25
|
+
__exportStar(require("./error-handling"), exports);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Message } from '@bernierllc/ai-provider-core';
|
|
2
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Convert messages from unified format to Anthropic format
|
|
5
|
+
* Extracts system messages separately (Anthropic uses system parameter, not messages array)
|
|
6
|
+
*/
|
|
7
|
+
export declare function convertMessagesToAnthropicFormat(messages: Message[]): {
|
|
8
|
+
system?: string;
|
|
9
|
+
messages: Anthropic.MessageParam[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Validate message format before conversion
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateMessages(messages: Message[]): {
|
|
15
|
+
isValid: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.convertMessagesToAnthropicFormat = convertMessagesToAnthropicFormat;
|
|
11
|
+
exports.validateMessages = validateMessages;
|
|
12
|
+
/**
|
|
13
|
+
* Convert messages from unified format to Anthropic format
|
|
14
|
+
* Extracts system messages separately (Anthropic uses system parameter, not messages array)
|
|
15
|
+
*/
|
|
16
|
+
function convertMessagesToAnthropicFormat(messages) {
|
|
17
|
+
// Extract system messages
|
|
18
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
19
|
+
const system = systemMessages.length > 0
|
|
20
|
+
? systemMessages.map(m => m.content).join('\n\n')
|
|
21
|
+
: undefined;
|
|
22
|
+
// Convert remaining messages to Anthropic format
|
|
23
|
+
const anthropicMessages = messages
|
|
24
|
+
.filter(m => m.role !== 'system')
|
|
25
|
+
.map(msg => ({
|
|
26
|
+
role: msg.role === 'assistant' ? 'assistant' : 'user',
|
|
27
|
+
content: msg.content
|
|
28
|
+
}));
|
|
29
|
+
return { system, messages: anthropicMessages };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate message format before conversion
|
|
33
|
+
*/
|
|
34
|
+
function validateMessages(messages) {
|
|
35
|
+
if (!messages || messages.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
isValid: false,
|
|
38
|
+
error: 'Messages array cannot be empty'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Check that all messages have required fields
|
|
42
|
+
for (const msg of messages) {
|
|
43
|
+
if (!msg.role || !msg.content) {
|
|
44
|
+
return {
|
|
45
|
+
isValid: false,
|
|
46
|
+
error: 'All messages must have role and content'
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (!['system', 'user', 'assistant'].includes(msg.role)) {
|
|
50
|
+
return {
|
|
51
|
+
isValid: false,
|
|
52
|
+
error: `Invalid message role: ${msg.role}`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Ensure non-system messages exist
|
|
57
|
+
const nonSystemMessages = messages.filter(m => m.role !== 'system');
|
|
58
|
+
if (nonSystemMessages.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
isValid: false,
|
|
61
|
+
error: 'At least one non-system message is required'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { isValid: true };
|
|
65
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
module.exports = {
|
|
10
|
+
preset: 'ts-jest',
|
|
11
|
+
testEnvironment: 'node',
|
|
12
|
+
roots: ['<rootDir>/__tests__'],
|
|
13
|
+
testMatch: ['**/__tests__/**/*.test.ts'],
|
|
14
|
+
collectCoverageFrom: [
|
|
15
|
+
'src/**/*.{ts,tsx}',
|
|
16
|
+
'!src/**/*.d.ts',
|
|
17
|
+
'!src/index.ts'
|
|
18
|
+
],
|
|
19
|
+
coverageThreshold: {
|
|
20
|
+
global: {
|
|
21
|
+
branches: 90,
|
|
22
|
+
functions: 90,
|
|
23
|
+
lines: 90,
|
|
24
|
+
statements: 90
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
coverageDirectory: 'coverage',
|
|
28
|
+
verbose: true,
|
|
29
|
+
testTimeout: 30000
|
|
30
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bernierllc/ai-provider-anthropic",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Anthropic Claude API adapter implementing the unified AI provider interface",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest --watch",
|
|
10
|
+
"test:run": "jest",
|
|
11
|
+
"test:coverage": "jest --coverage",
|
|
12
|
+
"lint": "eslint src --ext .ts",
|
|
13
|
+
"clean": "rm -rf dist"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai",
|
|
17
|
+
"provider",
|
|
18
|
+
"anthropic",
|
|
19
|
+
"claude",
|
|
20
|
+
"adapter",
|
|
21
|
+
"claude-3",
|
|
22
|
+
"vision",
|
|
23
|
+
"streaming"
|
|
24
|
+
],
|
|
25
|
+
"author": "Bernier LLC",
|
|
26
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@anthropic-ai/sdk": "^0.20.0",
|
|
29
|
+
"@bernierllc/ai-provider-core": "^1.0.0",
|
|
30
|
+
"@bernierllc/retry-policy": "^1.0.0",
|
|
31
|
+
"@bernierllc/logger": "^1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@bernierllc/neverhub-adapter": "^1.0.0",
|
|
35
|
+
"@types/jest": "^29.5.0",
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
38
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
39
|
+
"eslint": "^8.50.0",
|
|
40
|
+
"jest": "^29.7.0",
|
|
41
|
+
"ts-jest": "^29.1.0",
|
|
42
|
+
"typescript": "^5.3.0"
|
|
43
|
+
},
|
|
44
|
+
"bernierllc": {
|
|
45
|
+
"category": "core",
|
|
46
|
+
"tags": [
|
|
47
|
+
"ai",
|
|
48
|
+
"provider",
|
|
49
|
+
"anthropic",
|
|
50
|
+
"claude",
|
|
51
|
+
"adapter"
|
|
52
|
+
],
|
|
53
|
+
"integration": {
|
|
54
|
+
"neverhub": "optional",
|
|
55
|
+
"logger": "integrated",
|
|
56
|
+
"docs-suite": "ready"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
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
|
+
import {
|
|
11
|
+
AIProvider,
|
|
12
|
+
CompletionRequest,
|
|
13
|
+
CompletionResponse,
|
|
14
|
+
StreamChunk,
|
|
15
|
+
EmbeddingRequest,
|
|
16
|
+
EmbeddingResponse,
|
|
17
|
+
ModerationResponse,
|
|
18
|
+
ModelInfo,
|
|
19
|
+
HealthStatus,
|
|
20
|
+
CostEstimate
|
|
21
|
+
} from '@bernierllc/ai-provider-core';
|
|
22
|
+
import { AnthropicProviderConfig, AnthropicStopReason } from './types';
|
|
23
|
+
import { ClaudeModelRegistry } from './models/model-registry';
|
|
24
|
+
import { convertMessagesToAnthropicFormat, validateMessages } from './utils/message-conversion';
|
|
25
|
+
import { handleAnthropicError } from './utils/error-handling';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Anthropic Provider Implementation
|
|
29
|
+
* Concrete implementation of the AI provider interface for Anthropic's Claude API
|
|
30
|
+
*/
|
|
31
|
+
export class AnthropicProvider extends AIProvider {
|
|
32
|
+
private client: Anthropic;
|
|
33
|
+
|
|
34
|
+
constructor(config: AnthropicProviderConfig) {
|
|
35
|
+
super(config);
|
|
36
|
+
this.client = new Anthropic({
|
|
37
|
+
apiKey: config.apiKey,
|
|
38
|
+
baseURL: config.baseURL,
|
|
39
|
+
timeout: config.timeout || 60000,
|
|
40
|
+
maxRetries: config.maxRetries || 3
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// CORE OPERATIONS
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate text completion using Anthropic Claude API
|
|
50
|
+
*/
|
|
51
|
+
async complete(request: CompletionRequest): Promise<CompletionResponse> {
|
|
52
|
+
// Validate request
|
|
53
|
+
const validation = this.validateRequest(request);
|
|
54
|
+
if (!validation.isValid) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: validation.errors.join(', ')
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Validate messages
|
|
62
|
+
const messageValidation = validateMessages(request.messages);
|
|
63
|
+
if (!messageValidation.isValid) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: messageValidation.error
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Convert messages to Anthropic format
|
|
72
|
+
const { system, messages } = convertMessagesToAnthropicFormat(request.messages);
|
|
73
|
+
|
|
74
|
+
const completion = await this.client.messages.create({
|
|
75
|
+
model: request.model || this.config.defaultModel || 'claude-3-opus-20240229',
|
|
76
|
+
system,
|
|
77
|
+
messages,
|
|
78
|
+
max_tokens: request.maxTokens || 4096,
|
|
79
|
+
temperature: request.temperature,
|
|
80
|
+
top_p: request.topP,
|
|
81
|
+
stop_sequences: request.stop,
|
|
82
|
+
metadata: request.user ? {
|
|
83
|
+
user_id: request.user
|
|
84
|
+
} : undefined
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Extract content from response
|
|
88
|
+
const content = completion.content
|
|
89
|
+
.filter((block): block is Anthropic.TextBlock => block.type === 'text')
|
|
90
|
+
.map(block => block.text)
|
|
91
|
+
.join('');
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
content,
|
|
96
|
+
finishReason: this.mapStopReason(completion.stop_reason),
|
|
97
|
+
usage: {
|
|
98
|
+
promptTokens: completion.usage.input_tokens,
|
|
99
|
+
completionTokens: completion.usage.output_tokens,
|
|
100
|
+
totalTokens: completion.usage.input_tokens + completion.usage.output_tokens
|
|
101
|
+
},
|
|
102
|
+
model: completion.model,
|
|
103
|
+
metadata: {
|
|
104
|
+
id: completion.id,
|
|
105
|
+
type: completion.type,
|
|
106
|
+
role: completion.role,
|
|
107
|
+
stopSequence: completion.stop_sequence
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const aiError = handleAnthropicError(error);
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: aiError.message
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate streaming text completion
|
|
121
|
+
*/
|
|
122
|
+
async *streamComplete(
|
|
123
|
+
request: CompletionRequest
|
|
124
|
+
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
125
|
+
const validation = this.validateRequest(request);
|
|
126
|
+
if (!validation.isValid) {
|
|
127
|
+
throw new Error(validation.errors.join(', '));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const messageValidation = validateMessages(request.messages);
|
|
131
|
+
if (!messageValidation.isValid) {
|
|
132
|
+
throw new Error(messageValidation.error || 'Invalid messages');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const { system, messages } = convertMessagesToAnthropicFormat(request.messages);
|
|
137
|
+
|
|
138
|
+
const stream = this.client.messages.stream({
|
|
139
|
+
model: request.model || this.config.defaultModel || 'claude-3-opus-20240229',
|
|
140
|
+
system,
|
|
141
|
+
messages,
|
|
142
|
+
max_tokens: request.maxTokens || 4096,
|
|
143
|
+
temperature: request.temperature,
|
|
144
|
+
top_p: request.topP,
|
|
145
|
+
stop_sequences: request.stop
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
let finalUsage: { promptTokens: number; completionTokens: number; totalTokens: number } | undefined;
|
|
149
|
+
let finalStopReason: AnthropicStopReason | undefined;
|
|
150
|
+
|
|
151
|
+
for await (const event of stream) {
|
|
152
|
+
if (event.type === 'content_block_delta') {
|
|
153
|
+
const delta = event.delta;
|
|
154
|
+
if (delta.type === 'text_delta') {
|
|
155
|
+
yield {
|
|
156
|
+
delta: delta.text,
|
|
157
|
+
finishReason: undefined
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (event.type === 'message_delta') {
|
|
163
|
+
// Capture usage information from message_delta event
|
|
164
|
+
const usage = (event as unknown as Record<string, unknown>).usage as { input_tokens: number; output_tokens: number } | undefined;
|
|
165
|
+
if (usage) {
|
|
166
|
+
finalUsage = {
|
|
167
|
+
promptTokens: usage.input_tokens,
|
|
168
|
+
completionTokens: usage.output_tokens,
|
|
169
|
+
totalTokens: usage.input_tokens + usage.output_tokens
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (event.type === 'message_stop') {
|
|
175
|
+
// Capture stop reason
|
|
176
|
+
finalStopReason = null; // message_stop doesn't provide stop_reason directly
|
|
177
|
+
|
|
178
|
+
yield {
|
|
179
|
+
delta: '',
|
|
180
|
+
finishReason: this.mapStopReason(finalStopReason),
|
|
181
|
+
usage: finalUsage
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw handleAnthropicError(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate embeddings (Anthropic doesn't provide embeddings API)
|
|
192
|
+
* Returns error indicating feature not supported
|
|
193
|
+
*/
|
|
194
|
+
generateEmbeddings(
|
|
195
|
+
_request: EmbeddingRequest
|
|
196
|
+
): Promise<EmbeddingResponse> {
|
|
197
|
+
return Promise.resolve({
|
|
198
|
+
success: false,
|
|
199
|
+
error: 'Anthropic does not provide an embeddings API. Use OpenAI provider for embeddings.'
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check content moderation (Anthropic doesn't provide moderation API)
|
|
205
|
+
* Returns success with no flags (Claude has built-in safety)
|
|
206
|
+
*/
|
|
207
|
+
moderate(_content: string): Promise<ModerationResponse> {
|
|
208
|
+
// Claude has built-in safety features
|
|
209
|
+
// We can return success with no flags as Claude refuses unsafe content
|
|
210
|
+
return Promise.resolve({
|
|
211
|
+
success: true,
|
|
212
|
+
flagged: false,
|
|
213
|
+
categories: {},
|
|
214
|
+
categoryScores: {}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get available Anthropic models
|
|
220
|
+
*/
|
|
221
|
+
getAvailableModels(): Promise<ModelInfo[]> {
|
|
222
|
+
// Anthropic doesn't provide a models list endpoint
|
|
223
|
+
// Return cached model information
|
|
224
|
+
return Promise.resolve(ClaudeModelRegistry.getAllModels());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check Anthropic API health
|
|
229
|
+
*/
|
|
230
|
+
async checkHealth(): Promise<HealthStatus> {
|
|
231
|
+
const startTime = Date.now();
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Simple test request with minimal tokens
|
|
235
|
+
await this.client.messages.create({
|
|
236
|
+
model: 'claude-3-haiku-20240307',
|
|
237
|
+
max_tokens: 10,
|
|
238
|
+
messages: [
|
|
239
|
+
{ role: 'user', content: 'Hi' }
|
|
240
|
+
]
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
status: 'healthy',
|
|
245
|
+
latency: Date.now() - startTime,
|
|
246
|
+
lastChecked: new Date()
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
status: 'unavailable',
|
|
251
|
+
latency: Date.now() - startTime,
|
|
252
|
+
lastChecked: new Date(),
|
|
253
|
+
details: {
|
|
254
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ============================================
|
|
261
|
+
// ANTHROPIC-SPECIFIC FEATURES
|
|
262
|
+
// ============================================
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Extended context completion (200K tokens for all Claude 3 models)
|
|
266
|
+
*/
|
|
267
|
+
async extendedContextCompletion(
|
|
268
|
+
request: CompletionRequest & { enableExtendedContext?: boolean }
|
|
269
|
+
): Promise<CompletionResponse> {
|
|
270
|
+
// All Claude 3 models support 200K context window
|
|
271
|
+
const model = request.model || 'claude-3-opus-20240229';
|
|
272
|
+
|
|
273
|
+
return this.complete({
|
|
274
|
+
...request,
|
|
275
|
+
model
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Vision analysis (All Claude 3 models support vision)
|
|
281
|
+
*/
|
|
282
|
+
async analyzeImage(
|
|
283
|
+
imageData: string | Buffer,
|
|
284
|
+
prompt: string,
|
|
285
|
+
model: string = 'claude-3-opus-20240229',
|
|
286
|
+
maxTokens?: number,
|
|
287
|
+
temperature?: number
|
|
288
|
+
): Promise<CompletionResponse> {
|
|
289
|
+
try {
|
|
290
|
+
// Convert image to base64 if needed
|
|
291
|
+
const base64Image = Buffer.isBuffer(imageData)
|
|
292
|
+
? imageData.toString('base64')
|
|
293
|
+
: imageData;
|
|
294
|
+
|
|
295
|
+
const completion = await this.client.messages.create({
|
|
296
|
+
model,
|
|
297
|
+
max_tokens: maxTokens || 4096,
|
|
298
|
+
temperature,
|
|
299
|
+
messages: [
|
|
300
|
+
{
|
|
301
|
+
role: 'user',
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: 'image',
|
|
305
|
+
source: {
|
|
306
|
+
type: 'base64',
|
|
307
|
+
media_type: 'image/jpeg',
|
|
308
|
+
data: base64Image
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: 'text',
|
|
313
|
+
text: prompt
|
|
314
|
+
}
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const content = completion.content
|
|
321
|
+
.filter((block): block is Anthropic.TextBlock => block.type === 'text')
|
|
322
|
+
.map(block => block.text)
|
|
323
|
+
.join('');
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
success: true,
|
|
327
|
+
content,
|
|
328
|
+
finishReason: this.mapStopReason(completion.stop_reason),
|
|
329
|
+
usage: {
|
|
330
|
+
promptTokens: completion.usage.input_tokens,
|
|
331
|
+
completionTokens: completion.usage.output_tokens,
|
|
332
|
+
totalTokens: completion.usage.input_tokens + completion.usage.output_tokens
|
|
333
|
+
},
|
|
334
|
+
model: completion.model
|
|
335
|
+
};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
const aiError = handleAnthropicError(error);
|
|
338
|
+
return {
|
|
339
|
+
success: false,
|
|
340
|
+
error: aiError.message
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ============================================
|
|
346
|
+
// COST ESTIMATION (OVERRIDE)
|
|
347
|
+
// ============================================
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Estimate cost using Anthropic pricing
|
|
351
|
+
*/
|
|
352
|
+
estimateCost(request: CompletionRequest): CostEstimate {
|
|
353
|
+
const model = request.model || this.config.defaultModel || 'claude-3-opus-20240229';
|
|
354
|
+
const pricing = ClaudeModelRegistry.getModelPricing(model);
|
|
355
|
+
|
|
356
|
+
const inputTokens = this.estimateTokens(
|
|
357
|
+
request.messages.map(m => m.content).join(' ')
|
|
358
|
+
);
|
|
359
|
+
const outputTokens = request.maxTokens || 4096;
|
|
360
|
+
|
|
361
|
+
const inputCost = (inputTokens / 1000000) * pricing.inputPrice;
|
|
362
|
+
const outputCost = (outputTokens / 1000000) * pricing.outputPrice;
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
inputTokens,
|
|
366
|
+
outputTokens,
|
|
367
|
+
totalTokens: inputTokens + outputTokens,
|
|
368
|
+
estimatedCostUSD: inputCost + outputCost,
|
|
369
|
+
currency: 'USD'
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ============================================
|
|
374
|
+
// PRIVATE METHODS
|
|
375
|
+
// ============================================
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Map Anthropic stop reason to unified format
|
|
379
|
+
*/
|
|
380
|
+
private mapStopReason(stopReason: AnthropicStopReason): 'stop' | 'length' | 'content_filter' {
|
|
381
|
+
switch (stopReason) {
|
|
382
|
+
case 'end_turn':
|
|
383
|
+
return 'stop';
|
|
384
|
+
case 'max_tokens':
|
|
385
|
+
return 'length';
|
|
386
|
+
case 'stop_sequence':
|
|
387
|
+
return 'stop';
|
|
388
|
+
default:
|
|
389
|
+
return 'stop';
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
// Main provider class
|
|
10
|
+
export { AnthropicProvider } from './AnthropicProvider';
|
|
11
|
+
|
|
12
|
+
// Types
|
|
13
|
+
export * from './types';
|
|
14
|
+
|
|
15
|
+
// Models
|
|
16
|
+
export { ClaudeModelRegistry } from './models/model-registry';
|
|
17
|
+
|
|
18
|
+
// Utilities
|
|
19
|
+
export * from './utils';
|