ai.matey.backend 0.2.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/LICENSE +21 -0
- package/dist/cjs/index.js +60 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/providers/ai21.js +331 -0
- package/dist/cjs/providers/ai21.js.map +1 -0
- package/dist/cjs/providers/anthropic.js +664 -0
- package/dist/cjs/providers/anthropic.js.map +1 -0
- package/dist/cjs/providers/anyscale.js +338 -0
- package/dist/cjs/providers/anyscale.js.map +1 -0
- package/dist/cjs/providers/aws-bedrock.js +374 -0
- package/dist/cjs/providers/aws-bedrock.js.map +1 -0
- package/dist/cjs/providers/azure-openai.js +406 -0
- package/dist/cjs/providers/azure-openai.js.map +1 -0
- package/dist/cjs/providers/cerebras.js +356 -0
- package/dist/cjs/providers/cerebras.js.map +1 -0
- package/dist/cjs/providers/cloudflare.js +359 -0
- package/dist/cjs/providers/cloudflare.js.map +1 -0
- package/dist/cjs/providers/cohere.js +368 -0
- package/dist/cjs/providers/cohere.js.map +1 -0
- package/dist/cjs/providers/deepinfra.js +343 -0
- package/dist/cjs/providers/deepinfra.js.map +1 -0
- package/dist/cjs/providers/deepseek.js +104 -0
- package/dist/cjs/providers/deepseek.js.map +1 -0
- package/dist/cjs/providers/fireworks.js +363 -0
- package/dist/cjs/providers/fireworks.js.map +1 -0
- package/dist/cjs/providers/gemini.js +292 -0
- package/dist/cjs/providers/gemini.js.map +1 -0
- package/dist/cjs/providers/groq.js +143 -0
- package/dist/cjs/providers/groq.js.map +1 -0
- package/dist/cjs/providers/huggingface.js +392 -0
- package/dist/cjs/providers/huggingface.js.map +1 -0
- package/dist/cjs/providers/lmstudio.js +144 -0
- package/dist/cjs/providers/lmstudio.js.map +1 -0
- package/dist/cjs/providers/mistral.js +288 -0
- package/dist/cjs/providers/mistral.js.map +1 -0
- package/dist/cjs/providers/nvidia.js +167 -0
- package/dist/cjs/providers/nvidia.js.map +1 -0
- package/dist/cjs/providers/ollama.js +257 -0
- package/dist/cjs/providers/ollama.js.map +1 -0
- package/dist/cjs/providers/openai.js +640 -0
- package/dist/cjs/providers/openai.js.map +1 -0
- package/dist/cjs/providers/openrouter.js +379 -0
- package/dist/cjs/providers/openrouter.js.map +1 -0
- package/dist/cjs/providers/perplexity.js +372 -0
- package/dist/cjs/providers/perplexity.js.map +1 -0
- package/dist/cjs/providers/replicate.js +340 -0
- package/dist/cjs/providers/replicate.js.map +1 -0
- package/dist/cjs/providers/together-ai.js +341 -0
- package/dist/cjs/providers/together-ai.js.map +1 -0
- package/dist/cjs/providers/xai.js +339 -0
- package/dist/cjs/providers/xai.js.map +1 -0
- package/dist/cjs/shared.js +279 -0
- package/dist/cjs/shared.js.map +1 -0
- package/dist/esm/index.js +44 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/providers/ai21.js +327 -0
- package/dist/esm/providers/ai21.js.map +1 -0
- package/dist/esm/providers/anthropic.js +660 -0
- package/dist/esm/providers/anthropic.js.map +1 -0
- package/dist/esm/providers/anyscale.js +334 -0
- package/dist/esm/providers/anyscale.js.map +1 -0
- package/dist/esm/providers/aws-bedrock.js +370 -0
- package/dist/esm/providers/aws-bedrock.js.map +1 -0
- package/dist/esm/providers/azure-openai.js +402 -0
- package/dist/esm/providers/azure-openai.js.map +1 -0
- package/dist/esm/providers/cerebras.js +352 -0
- package/dist/esm/providers/cerebras.js.map +1 -0
- package/dist/esm/providers/cloudflare.js +355 -0
- package/dist/esm/providers/cloudflare.js.map +1 -0
- package/dist/esm/providers/cohere.js +364 -0
- package/dist/esm/providers/cohere.js.map +1 -0
- package/dist/esm/providers/deepinfra.js +339 -0
- package/dist/esm/providers/deepinfra.js.map +1 -0
- package/dist/esm/providers/deepseek.js +99 -0
- package/dist/esm/providers/deepseek.js.map +1 -0
- package/dist/esm/providers/fireworks.js +359 -0
- package/dist/esm/providers/fireworks.js.map +1 -0
- package/dist/esm/providers/gemini.js +288 -0
- package/dist/esm/providers/gemini.js.map +1 -0
- package/dist/esm/providers/groq.js +138 -0
- package/dist/esm/providers/groq.js.map +1 -0
- package/dist/esm/providers/huggingface.js +387 -0
- package/dist/esm/providers/huggingface.js.map +1 -0
- package/dist/esm/providers/lmstudio.js +139 -0
- package/dist/esm/providers/lmstudio.js.map +1 -0
- package/dist/esm/providers/mistral.js +284 -0
- package/dist/esm/providers/mistral.js.map +1 -0
- package/dist/esm/providers/nvidia.js +162 -0
- package/dist/esm/providers/nvidia.js.map +1 -0
- package/dist/esm/providers/ollama.js +253 -0
- package/dist/esm/providers/ollama.js.map +1 -0
- package/dist/esm/providers/openai.js +636 -0
- package/dist/esm/providers/openai.js.map +1 -0
- package/dist/esm/providers/openrouter.js +375 -0
- package/dist/esm/providers/openrouter.js.map +1 -0
- package/dist/esm/providers/perplexity.js +368 -0
- package/dist/esm/providers/perplexity.js.map +1 -0
- package/dist/esm/providers/replicate.js +336 -0
- package/dist/esm/providers/replicate.js.map +1 -0
- package/dist/esm/providers/together-ai.js +337 -0
- package/dist/esm/providers/together-ai.js.map +1 -0
- package/dist/esm/providers/xai.js +335 -0
- package/dist/esm/providers/xai.js.map +1 -0
- package/dist/esm/shared.js +272 -0
- package/dist/esm/shared.js.map +1 -0
- package/dist/types/index.d.ts +38 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/providers/ai21.d.ts +106 -0
- package/dist/types/providers/ai21.d.ts.map +1 -0
- package/dist/types/providers/anthropic.d.ts +194 -0
- package/dist/types/providers/anthropic.d.ts.map +1 -0
- package/dist/types/providers/anyscale.d.ts +109 -0
- package/dist/types/providers/anyscale.d.ts.map +1 -0
- package/dist/types/providers/aws-bedrock.d.ts +152 -0
- package/dist/types/providers/aws-bedrock.d.ts.map +1 -0
- package/dist/types/providers/azure-openai.d.ts +142 -0
- package/dist/types/providers/azure-openai.d.ts.map +1 -0
- package/dist/types/providers/cerebras.d.ts +130 -0
- package/dist/types/providers/cerebras.d.ts.map +1 -0
- package/dist/types/providers/cloudflare.d.ts +125 -0
- package/dist/types/providers/cloudflare.d.ts.map +1 -0
- package/dist/types/providers/cohere.d.ts +114 -0
- package/dist/types/providers/cohere.d.ts.map +1 -0
- package/dist/types/providers/deepinfra.d.ts +118 -0
- package/dist/types/providers/deepinfra.d.ts.map +1 -0
- package/dist/types/providers/deepseek.d.ts +68 -0
- package/dist/types/providers/deepseek.d.ts.map +1 -0
- package/dist/types/providers/fireworks.d.ts +127 -0
- package/dist/types/providers/fireworks.d.ts.map +1 -0
- package/dist/types/providers/gemini.d.ts +71 -0
- package/dist/types/providers/gemini.d.ts.map +1 -0
- package/dist/types/providers/groq.d.ts +83 -0
- package/dist/types/providers/groq.d.ts.map +1 -0
- package/dist/types/providers/huggingface.d.ts +154 -0
- package/dist/types/providers/huggingface.d.ts.map +1 -0
- package/dist/types/providers/lmstudio.d.ts +88 -0
- package/dist/types/providers/lmstudio.d.ts.map +1 -0
- package/dist/types/providers/mistral.d.ts +65 -0
- package/dist/types/providers/mistral.d.ts.map +1 -0
- package/dist/types/providers/nvidia.d.ts +100 -0
- package/dist/types/providers/nvidia.d.ts.map +1 -0
- package/dist/types/providers/ollama.d.ts +59 -0
- package/dist/types/providers/ollama.d.ts.map +1 -0
- package/dist/types/providers/openai.d.ts +205 -0
- package/dist/types/providers/openai.d.ts.map +1 -0
- package/dist/types/providers/openrouter.d.ts +135 -0
- package/dist/types/providers/openrouter.d.ts.map +1 -0
- package/dist/types/providers/perplexity.d.ts +116 -0
- package/dist/types/providers/perplexity.d.ts.map +1 -0
- package/dist/types/providers/replicate.d.ts +91 -0
- package/dist/types/providers/replicate.d.ts.map +1 -0
- package/dist/types/providers/together-ai.d.ts +118 -0
- package/dist/types/providers/together-ai.d.ts.map +1 -0
- package/dist/types/providers/xai.d.ts +119 -0
- package/dist/types/providers/xai.d.ts.map +1 -0
- package/dist/types/shared.d.ts +116 -0
- package/dist/types/shared.d.ts.map +1 -0
- package/package.json +327 -0
- package/readme.md +86 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic Backend Adapter
|
|
3
|
+
*
|
|
4
|
+
* Adapts Universal IR to Anthropic Messages API.
|
|
5
|
+
* Handles Anthropic's separate system parameter and SSE streaming format.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { AdapterConversionError, NetworkError, ProviderError, StreamError, ErrorCode, createErrorFromHttpResponse, } from 'ai.matey.errors';
|
|
10
|
+
import { normalizeSystemMessages } from 'ai.matey.utils';
|
|
11
|
+
import { getEffectiveStreamMode, mergeStreamingConfig } from 'ai.matey.utils';
|
|
12
|
+
import { estimateTokens, buildStaticResult, applyModelFilter, } from '../shared.js';
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Default Anthropic Models
|
|
15
|
+
// ============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Default list of Anthropic Claude models.
|
|
18
|
+
* Used when no custom model list is provided in config.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_ANTHROPIC_MODELS = [
|
|
21
|
+
{
|
|
22
|
+
id: 'claude-3-5-sonnet-20241022',
|
|
23
|
+
name: 'Claude 3.5 Sonnet (Oct 2024)',
|
|
24
|
+
description: 'Most intelligent model with excellent reasoning and analysis',
|
|
25
|
+
ownedBy: 'anthropic',
|
|
26
|
+
capabilities: {
|
|
27
|
+
maxTokens: 8192,
|
|
28
|
+
contextWindow: 200000,
|
|
29
|
+
supportsStreaming: true,
|
|
30
|
+
supportsVision: true,
|
|
31
|
+
supportsTools: true,
|
|
32
|
+
supportsJSON: false,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'claude-3-5-haiku-20241022',
|
|
37
|
+
name: 'Claude 3.5 Haiku (Oct 2024)',
|
|
38
|
+
description: 'Fastest and most compact model for high-throughput tasks',
|
|
39
|
+
ownedBy: 'anthropic',
|
|
40
|
+
capabilities: {
|
|
41
|
+
maxTokens: 8192,
|
|
42
|
+
contextWindow: 200000,
|
|
43
|
+
supportsStreaming: true,
|
|
44
|
+
supportsVision: false,
|
|
45
|
+
supportsTools: true,
|
|
46
|
+
supportsJSON: false,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'claude-3-opus-20240229',
|
|
51
|
+
name: 'Claude 3 Opus (Feb 2024)',
|
|
52
|
+
description: 'Previous top-tier model with strong performance',
|
|
53
|
+
ownedBy: 'anthropic',
|
|
54
|
+
capabilities: {
|
|
55
|
+
maxTokens: 4096,
|
|
56
|
+
contextWindow: 200000,
|
|
57
|
+
supportsStreaming: true,
|
|
58
|
+
supportsVision: true,
|
|
59
|
+
supportsTools: true,
|
|
60
|
+
supportsJSON: false,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'claude-3-sonnet-20240229',
|
|
65
|
+
name: 'Claude 3 Sonnet (Feb 2024)',
|
|
66
|
+
description: 'Balanced intelligence and speed',
|
|
67
|
+
ownedBy: 'anthropic',
|
|
68
|
+
capabilities: {
|
|
69
|
+
maxTokens: 4096,
|
|
70
|
+
contextWindow: 200000,
|
|
71
|
+
supportsStreaming: true,
|
|
72
|
+
supportsVision: true,
|
|
73
|
+
supportsTools: true,
|
|
74
|
+
supportsJSON: false,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'claude-3-haiku-20240307',
|
|
79
|
+
name: 'Claude 3 Haiku (Mar 2024)',
|
|
80
|
+
description: 'Fast and compact model',
|
|
81
|
+
ownedBy: 'anthropic',
|
|
82
|
+
capabilities: {
|
|
83
|
+
maxTokens: 4096,
|
|
84
|
+
contextWindow: 200000,
|
|
85
|
+
supportsStreaming: true,
|
|
86
|
+
supportsVision: false,
|
|
87
|
+
supportsTools: false,
|
|
88
|
+
supportsJSON: false,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Anthropic Backend Adapter
|
|
94
|
+
// ============================================================================
|
|
95
|
+
/**
|
|
96
|
+
* Backend adapter for Anthropic Messages API.
|
|
97
|
+
*/
|
|
98
|
+
export class AnthropicBackendAdapter {
|
|
99
|
+
metadata;
|
|
100
|
+
config;
|
|
101
|
+
baseURL;
|
|
102
|
+
constructor(config) {
|
|
103
|
+
this.config = config;
|
|
104
|
+
this.baseURL = config.baseURL || 'https://api.anthropic.com/v1';
|
|
105
|
+
this.metadata = {
|
|
106
|
+
name: 'anthropic-backend',
|
|
107
|
+
version: '1.0.0',
|
|
108
|
+
provider: 'Anthropic',
|
|
109
|
+
capabilities: {
|
|
110
|
+
streaming: true,
|
|
111
|
+
multiModal: true,
|
|
112
|
+
tools: true,
|
|
113
|
+
maxContextTokens: 200000,
|
|
114
|
+
systemMessageStrategy: 'separate-parameter',
|
|
115
|
+
supportsMultipleSystemMessages: false, // Anthropic merges multiple system messages
|
|
116
|
+
supportsTemperature: true,
|
|
117
|
+
supportsTopP: true,
|
|
118
|
+
supportsTopK: true,
|
|
119
|
+
supportsSeed: false,
|
|
120
|
+
supportsFrequencyPenalty: false,
|
|
121
|
+
supportsPresencePenalty: false,
|
|
122
|
+
maxStopSequences: 4,
|
|
123
|
+
},
|
|
124
|
+
config: {
|
|
125
|
+
baseURL: this.baseURL,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Execute non-streaming chat completion request.
|
|
131
|
+
*/
|
|
132
|
+
async execute(request, signal) {
|
|
133
|
+
try {
|
|
134
|
+
// Convert IR to Anthropic format
|
|
135
|
+
const anthropicRequest = this.fromIR(request);
|
|
136
|
+
// Make HTTP request
|
|
137
|
+
const startTime = Date.now();
|
|
138
|
+
const response = await this.makeRequest(anthropicRequest, signal);
|
|
139
|
+
// Convert response to IR
|
|
140
|
+
const irResponse = this.toIR(response, request, Date.now() - startTime);
|
|
141
|
+
return irResponse;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// Re-throw adapter errors
|
|
145
|
+
if (error instanceof AdapterConversionError ||
|
|
146
|
+
error instanceof NetworkError ||
|
|
147
|
+
error instanceof ProviderError) {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
// Wrap unknown errors
|
|
151
|
+
throw new ProviderError({
|
|
152
|
+
code: ErrorCode.PROVIDER_ERROR,
|
|
153
|
+
message: `Anthropic request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
154
|
+
isRetryable: true,
|
|
155
|
+
provenance: {
|
|
156
|
+
backend: this.metadata.name,
|
|
157
|
+
},
|
|
158
|
+
cause: error instanceof Error ? error : undefined,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Execute streaming chat completion request.
|
|
164
|
+
*/
|
|
165
|
+
async *executeStream(request, signal) {
|
|
166
|
+
try {
|
|
167
|
+
// Convert IR to Anthropic format
|
|
168
|
+
const anthropicRequest = this.fromIR(request);
|
|
169
|
+
anthropicRequest.stream = true;
|
|
170
|
+
// Get effective streaming configuration
|
|
171
|
+
const streamingConfig = mergeStreamingConfig(this.config.streaming);
|
|
172
|
+
const effectiveMode = getEffectiveStreamMode(request.streamMode, undefined, streamingConfig);
|
|
173
|
+
const includeBoth = streamingConfig.includeBoth || effectiveMode === 'accumulated';
|
|
174
|
+
// Make streaming HTTP request
|
|
175
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: this.getHeaders(),
|
|
178
|
+
body: JSON.stringify(anthropicRequest),
|
|
179
|
+
signal,
|
|
180
|
+
});
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
const errorBody = await response.text();
|
|
183
|
+
throw createErrorFromHttpResponse(response.status, response.statusText, errorBody, {
|
|
184
|
+
backend: this.metadata.name,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (!response.body) {
|
|
188
|
+
throw new StreamError({
|
|
189
|
+
code: ErrorCode.STREAM_INTERRUPTED,
|
|
190
|
+
message: 'Response body is null',
|
|
191
|
+
provenance: {
|
|
192
|
+
backend: this.metadata.name,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Parse SSE stream
|
|
197
|
+
let sequence = 0;
|
|
198
|
+
let contentBuffer = '';
|
|
199
|
+
let messageId = '';
|
|
200
|
+
let model = '';
|
|
201
|
+
let usage;
|
|
202
|
+
// Read stream
|
|
203
|
+
const reader = response.body.getReader();
|
|
204
|
+
const decoder = new TextDecoder();
|
|
205
|
+
let buffer = '';
|
|
206
|
+
try {
|
|
207
|
+
while (true) {
|
|
208
|
+
const { done, value } = await reader.read();
|
|
209
|
+
if (done) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
buffer += decoder.decode(value, { stream: true });
|
|
213
|
+
const lines = buffer.split('\n');
|
|
214
|
+
buffer = lines.pop() || '';
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
// Parse SSE format: "event: <type>" or "data: <json>"
|
|
217
|
+
if (line.startsWith('event:')) {
|
|
218
|
+
// Event type line (Anthropic doesn't always use this)
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (line.startsWith('data:')) {
|
|
222
|
+
const data = line.slice(5).trim();
|
|
223
|
+
if (!data) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const event = JSON.parse(data);
|
|
228
|
+
// Handle different event types
|
|
229
|
+
switch (event.type) {
|
|
230
|
+
case 'message_start':
|
|
231
|
+
// Extract message ID and model
|
|
232
|
+
messageId = event.message.id;
|
|
233
|
+
model = event.message.model;
|
|
234
|
+
usage = {
|
|
235
|
+
promptTokens: event.message.usage.input_tokens,
|
|
236
|
+
completionTokens: 0,
|
|
237
|
+
totalTokens: event.message.usage.input_tokens,
|
|
238
|
+
};
|
|
239
|
+
// Yield start chunk
|
|
240
|
+
yield {
|
|
241
|
+
type: 'start',
|
|
242
|
+
sequence: sequence++,
|
|
243
|
+
metadata: {
|
|
244
|
+
...request.metadata,
|
|
245
|
+
requestId: messageId,
|
|
246
|
+
provenance: {
|
|
247
|
+
...request.metadata.provenance,
|
|
248
|
+
backend: this.metadata.name,
|
|
249
|
+
},
|
|
250
|
+
custom: {
|
|
251
|
+
...request.metadata.custom,
|
|
252
|
+
anthropicMessageId: messageId,
|
|
253
|
+
model,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
break;
|
|
258
|
+
case 'content_block_start':
|
|
259
|
+
// Content block started (we'll handle deltas)
|
|
260
|
+
break;
|
|
261
|
+
case 'content_block_delta':
|
|
262
|
+
// Content delta
|
|
263
|
+
if (event.delta.type === 'text_delta') {
|
|
264
|
+
contentBuffer += event.delta.text;
|
|
265
|
+
// Build content chunk with optional accumulated field
|
|
266
|
+
const contentChunk = {
|
|
267
|
+
type: 'content',
|
|
268
|
+
sequence: sequence++,
|
|
269
|
+
delta: event.delta.text,
|
|
270
|
+
role: 'assistant',
|
|
271
|
+
};
|
|
272
|
+
// Add accumulated field if configured
|
|
273
|
+
if (includeBoth) {
|
|
274
|
+
contentChunk.accumulated = contentBuffer;
|
|
275
|
+
}
|
|
276
|
+
yield contentChunk;
|
|
277
|
+
}
|
|
278
|
+
else if (event.delta.type === 'input_json_delta') {
|
|
279
|
+
// Tool use delta (not implemented yet)
|
|
280
|
+
// TODO: Handle tool use deltas in Phase 5
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
case 'content_block_stop':
|
|
284
|
+
// Content block completed
|
|
285
|
+
break;
|
|
286
|
+
case 'message_delta':
|
|
287
|
+
// Message metadata delta (stop reason, usage)
|
|
288
|
+
if (event.delta.stop_reason && usage) {
|
|
289
|
+
usage.completionTokens = event.usage.output_tokens;
|
|
290
|
+
usage.totalTokens = usage.promptTokens + event.usage.output_tokens;
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
case 'message_stop': {
|
|
294
|
+
// Stream complete
|
|
295
|
+
const finishReason = this.mapStopReason(contentBuffer ? 'end_turn' : 'stop');
|
|
296
|
+
// Build final message
|
|
297
|
+
const message = {
|
|
298
|
+
role: 'assistant',
|
|
299
|
+
content: contentBuffer,
|
|
300
|
+
};
|
|
301
|
+
yield {
|
|
302
|
+
type: 'done',
|
|
303
|
+
sequence: sequence++,
|
|
304
|
+
finishReason,
|
|
305
|
+
usage,
|
|
306
|
+
message,
|
|
307
|
+
};
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case 'ping':
|
|
311
|
+
// Keep-alive ping, ignore
|
|
312
|
+
break;
|
|
313
|
+
case 'error':
|
|
314
|
+
// Error event
|
|
315
|
+
yield {
|
|
316
|
+
type: 'error',
|
|
317
|
+
sequence: sequence++,
|
|
318
|
+
error: {
|
|
319
|
+
code: event.error.type,
|
|
320
|
+
message: event.error.message,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
catch (parseError) {
|
|
327
|
+
// Skip malformed chunks
|
|
328
|
+
console.warn('Failed to parse SSE event:', data, parseError);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
reader.releaseLock();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
// Yield error chunk
|
|
341
|
+
yield {
|
|
342
|
+
type: 'error',
|
|
343
|
+
sequence: 0,
|
|
344
|
+
error: {
|
|
345
|
+
code: error instanceof Error ? error.name : 'UNKNOWN_ERROR',
|
|
346
|
+
message: error instanceof Error ? error.message : String(error),
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Health check to verify Anthropic API is accessible.
|
|
353
|
+
*/
|
|
354
|
+
async healthCheck() {
|
|
355
|
+
try {
|
|
356
|
+
// Anthropic doesn't have a dedicated health endpoint
|
|
357
|
+
// We'll try a minimal request to verify auth
|
|
358
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
359
|
+
method: 'POST',
|
|
360
|
+
headers: this.getHeaders(),
|
|
361
|
+
body: JSON.stringify({
|
|
362
|
+
model: 'claude-3-haiku-20240307',
|
|
363
|
+
max_tokens: 1,
|
|
364
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
365
|
+
}),
|
|
366
|
+
signal: AbortSignal.timeout(5000),
|
|
367
|
+
});
|
|
368
|
+
return response.ok || response.status === 400; // 400 is acceptable (validation error)
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Estimate cost for a request (rough heuristic).
|
|
376
|
+
*/
|
|
377
|
+
estimateCost(request) {
|
|
378
|
+
// Use shared token estimation utility
|
|
379
|
+
const estimatedInputTokens = estimateTokens(request);
|
|
380
|
+
// Rough cost: $0.015 per 1000 tokens for Claude 3.5 Sonnet
|
|
381
|
+
return Promise.resolve((estimatedInputTokens / 1000) * 0.015);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* List available Anthropic models.
|
|
385
|
+
*
|
|
386
|
+
* Since Anthropic doesn't have a public models endpoint, this uses:
|
|
387
|
+
* 1. Static config (config.models) - if provided
|
|
388
|
+
* 2. Default model list - built-in list of Claude models
|
|
389
|
+
*/
|
|
390
|
+
listModels(options) {
|
|
391
|
+
// 1. Check static config first
|
|
392
|
+
if (this.config.models) {
|
|
393
|
+
return Promise.resolve(buildStaticResult(this.config.models, 'anthropic'));
|
|
394
|
+
}
|
|
395
|
+
// 2. Use default Anthropic models
|
|
396
|
+
const result = {
|
|
397
|
+
models: [...DEFAULT_ANTHROPIC_MODELS],
|
|
398
|
+
source: 'static',
|
|
399
|
+
fetchedAt: Date.now(),
|
|
400
|
+
isComplete: true,
|
|
401
|
+
};
|
|
402
|
+
// 3. Apply filter if requested
|
|
403
|
+
return Promise.resolve(applyModelFilter(result, options?.filter));
|
|
404
|
+
}
|
|
405
|
+
// ==========================================================================
|
|
406
|
+
// Private Helper Methods
|
|
407
|
+
// ==========================================================================
|
|
408
|
+
/**
|
|
409
|
+
* Convert IR request to Anthropic format.
|
|
410
|
+
*
|
|
411
|
+
* Public method for testing and debugging - see what will be sent to Anthropic.
|
|
412
|
+
*/
|
|
413
|
+
fromIR(request) {
|
|
414
|
+
try {
|
|
415
|
+
// Normalize system messages (Anthropic uses separate-parameter strategy)
|
|
416
|
+
const { systemParameter, messages } = normalizeSystemMessages(request.messages, this.metadata.capabilities.systemMessageStrategy, this.metadata.capabilities.supportsMultipleSystemMessages);
|
|
417
|
+
// Convert messages
|
|
418
|
+
const anthropicMessages = messages.map((msg) => this.convertMessageToAnthropic(msg));
|
|
419
|
+
// Validate max_tokens is present (required by Anthropic)
|
|
420
|
+
const maxTokens = request.parameters?.maxTokens || 4096;
|
|
421
|
+
// Build Anthropic request
|
|
422
|
+
const anthropicRequest = {
|
|
423
|
+
model: request.parameters?.model || this.config.defaultModel || 'claude-3-5-sonnet-20241022',
|
|
424
|
+
messages: anthropicMessages,
|
|
425
|
+
system: systemParameter || undefined,
|
|
426
|
+
max_tokens: maxTokens,
|
|
427
|
+
temperature: request.parameters?.temperature,
|
|
428
|
+
top_p: request.parameters?.topP,
|
|
429
|
+
top_k: request.parameters?.topK,
|
|
430
|
+
stop_sequences: request.parameters?.stopSequences
|
|
431
|
+
? [...request.parameters.stopSequences].slice(0, 4) // Anthropic max is 4
|
|
432
|
+
: undefined,
|
|
433
|
+
stream: request.stream,
|
|
434
|
+
metadata: request.metadata.custom?.userId !== undefined &&
|
|
435
|
+
(typeof request.metadata.custom.userId === 'string' ||
|
|
436
|
+
typeof request.metadata.custom.userId === 'number')
|
|
437
|
+
? { user_id: String(request.metadata.custom.userId) }
|
|
438
|
+
: undefined,
|
|
439
|
+
};
|
|
440
|
+
return anthropicRequest;
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
throw new AdapterConversionError({
|
|
444
|
+
code: ErrorCode.ADAPTER_CONVERSION_ERROR,
|
|
445
|
+
message: `Failed to convert IR to Anthropic format: ${error instanceof Error ? error.message : String(error)}`,
|
|
446
|
+
provenance: {
|
|
447
|
+
backend: this.metadata.name,
|
|
448
|
+
},
|
|
449
|
+
cause: error instanceof Error ? error : undefined,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Convert Anthropic response to IR format.
|
|
455
|
+
*
|
|
456
|
+
* Public method for testing and debugging - parse Anthropic responses manually.
|
|
457
|
+
*/
|
|
458
|
+
toIR(response, originalRequest, latencyMs) {
|
|
459
|
+
try {
|
|
460
|
+
// Extract text content from content blocks
|
|
461
|
+
let textContent = '';
|
|
462
|
+
const contentBlocks = [];
|
|
463
|
+
for (const block of response.content) {
|
|
464
|
+
if (block.type === 'text') {
|
|
465
|
+
textContent += block.text;
|
|
466
|
+
contentBlocks.push({ type: 'text', text: block.text });
|
|
467
|
+
}
|
|
468
|
+
else if (block.type === 'tool_use') {
|
|
469
|
+
contentBlocks.push({
|
|
470
|
+
type: 'tool_use',
|
|
471
|
+
id: block.id,
|
|
472
|
+
name: block.name,
|
|
473
|
+
input: block.input,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Build message (use simple string if only text, otherwise structured content)
|
|
478
|
+
const firstBlock = contentBlocks[0];
|
|
479
|
+
const message = {
|
|
480
|
+
role: 'assistant',
|
|
481
|
+
content: contentBlocks.length === 1 && firstBlock?.type === 'text' ? textContent : contentBlocks,
|
|
482
|
+
};
|
|
483
|
+
// Map stop reason
|
|
484
|
+
const finishReason = this.mapStopReason(response.stop_reason || 'end_turn');
|
|
485
|
+
// Build IR response
|
|
486
|
+
const irResponse = {
|
|
487
|
+
message,
|
|
488
|
+
finishReason,
|
|
489
|
+
usage: {
|
|
490
|
+
promptTokens: response.usage.input_tokens,
|
|
491
|
+
completionTokens: response.usage.output_tokens,
|
|
492
|
+
totalTokens: response.usage.input_tokens + response.usage.output_tokens,
|
|
493
|
+
},
|
|
494
|
+
metadata: {
|
|
495
|
+
...originalRequest.metadata,
|
|
496
|
+
providerResponseId: response.id, // Anthropic's msg_xxx ID
|
|
497
|
+
provenance: {
|
|
498
|
+
...originalRequest.metadata.provenance,
|
|
499
|
+
backend: this.metadata.name,
|
|
500
|
+
},
|
|
501
|
+
custom: {
|
|
502
|
+
...originalRequest.metadata.custom,
|
|
503
|
+
anthropicMessageId: response.id,
|
|
504
|
+
latencyMs,
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
raw: response,
|
|
508
|
+
};
|
|
509
|
+
return irResponse;
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
throw new AdapterConversionError({
|
|
513
|
+
code: ErrorCode.ADAPTER_CONVERSION_ERROR,
|
|
514
|
+
message: `Failed to convert Anthropic response to IR: ${error instanceof Error ? error.message : String(error)}`,
|
|
515
|
+
provenance: {
|
|
516
|
+
backend: this.metadata.name,
|
|
517
|
+
},
|
|
518
|
+
cause: error instanceof Error ? error : undefined,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Convert IR message to Anthropic message.
|
|
524
|
+
*/
|
|
525
|
+
convertMessageToAnthropic(message) {
|
|
526
|
+
// Anthropic only supports user/assistant roles (system handled separately)
|
|
527
|
+
if (message.role === 'system') {
|
|
528
|
+
throw new AdapterConversionError({
|
|
529
|
+
code: ErrorCode.ADAPTER_CONVERSION_ERROR,
|
|
530
|
+
message: 'System messages should be extracted before conversion to Anthropic format',
|
|
531
|
+
provenance: {
|
|
532
|
+
backend: this.metadata.name,
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
// Convert content
|
|
537
|
+
let content;
|
|
538
|
+
if (typeof message.content === 'string') {
|
|
539
|
+
content = message.content;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
// Convert content blocks
|
|
543
|
+
content = message.content.map((block) => {
|
|
544
|
+
switch (block.type) {
|
|
545
|
+
case 'text':
|
|
546
|
+
return { type: 'text', text: block.text };
|
|
547
|
+
case 'image':
|
|
548
|
+
if (block.source.type === 'url') {
|
|
549
|
+
return {
|
|
550
|
+
type: 'image',
|
|
551
|
+
source: { type: 'url', url: block.source.url },
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
return {
|
|
556
|
+
type: 'image',
|
|
557
|
+
source: {
|
|
558
|
+
type: 'base64',
|
|
559
|
+
media_type: block.source.mediaType,
|
|
560
|
+
data: block.source.data,
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
case 'tool_use':
|
|
565
|
+
return {
|
|
566
|
+
type: 'tool_use',
|
|
567
|
+
id: block.id,
|
|
568
|
+
name: block.name,
|
|
569
|
+
input: block.input,
|
|
570
|
+
};
|
|
571
|
+
case 'tool_result':
|
|
572
|
+
return {
|
|
573
|
+
type: 'tool_result',
|
|
574
|
+
tool_use_id: block.toolUseId,
|
|
575
|
+
content: typeof block.content === 'string'
|
|
576
|
+
? block.content
|
|
577
|
+
: block.content.map((c) => c.type === 'text'
|
|
578
|
+
? { type: 'text', text: c.text }
|
|
579
|
+
: { type: 'text', text: '' }),
|
|
580
|
+
};
|
|
581
|
+
default:
|
|
582
|
+
// Fallback to text for unsupported types
|
|
583
|
+
return { type: 'text', text: JSON.stringify(block) };
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
role: message.role,
|
|
589
|
+
content,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Map Anthropic stop reason to IR finish reason.
|
|
594
|
+
*/
|
|
595
|
+
mapStopReason(stopReason) {
|
|
596
|
+
switch (stopReason) {
|
|
597
|
+
case 'end_turn':
|
|
598
|
+
return 'stop';
|
|
599
|
+
case 'max_tokens':
|
|
600
|
+
return 'length';
|
|
601
|
+
case 'stop_sequence':
|
|
602
|
+
return 'stop';
|
|
603
|
+
case 'tool_use':
|
|
604
|
+
return 'tool_calls';
|
|
605
|
+
default:
|
|
606
|
+
return 'stop';
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Make HTTP request to Anthropic API.
|
|
611
|
+
*/
|
|
612
|
+
async makeRequest(request, signal) {
|
|
613
|
+
try {
|
|
614
|
+
const response = await fetch(`${this.baseURL}/messages`, {
|
|
615
|
+
method: 'POST',
|
|
616
|
+
headers: this.getHeaders(),
|
|
617
|
+
body: JSON.stringify(request),
|
|
618
|
+
signal,
|
|
619
|
+
});
|
|
620
|
+
if (!response.ok) {
|
|
621
|
+
const errorBody = await response.text();
|
|
622
|
+
throw createErrorFromHttpResponse(response.status, response.statusText, errorBody, {
|
|
623
|
+
backend: this.metadata.name,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
const data = (await response.json());
|
|
627
|
+
return data;
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
631
|
+
throw new NetworkError({
|
|
632
|
+
code: ErrorCode.NETWORK_ERROR,
|
|
633
|
+
message: `Network request failed: ${error.message}`,
|
|
634
|
+
provenance: {
|
|
635
|
+
backend: this.metadata.name,
|
|
636
|
+
},
|
|
637
|
+
cause: error,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Get HTTP headers for Anthropic API requests.
|
|
645
|
+
*/
|
|
646
|
+
getHeaders() {
|
|
647
|
+
const headers = {
|
|
648
|
+
'Content-Type': 'application/json',
|
|
649
|
+
'x-api-key': this.config.apiKey,
|
|
650
|
+
'anthropic-version': '2023-06-01',
|
|
651
|
+
};
|
|
652
|
+
// Add dangerous browser access header if browserMode is enabled
|
|
653
|
+
if (this.config.browserMode) {
|
|
654
|
+
headers['anthropic-dangerous-direct-browser-access'] = 'true';
|
|
655
|
+
}
|
|
656
|
+
// Merge with custom headers (custom headers can override)
|
|
657
|
+
return { ...headers, ...this.config.headers };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
//# sourceMappingURL=anthropic.js.map
|