@assemble-dev/providers 0.1.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 +93 -0
- package/dist/index.cjs +1989 -0
- package/dist/index.d.cts +562 -0
- package/dist/index.d.ts +562 -0
- package/dist/index.js +1919 -0
- package/package.json +47 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1989 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AnthropicMessagesResponseSchema: () => AnthropicMessagesResponseSchema,
|
|
24
|
+
AnthropicStreamEventSchema: () => AnthropicStreamEventSchema,
|
|
25
|
+
ChatMessageRoleSchema: () => ChatMessageRoleSchema,
|
|
26
|
+
ChatMessageSchema: () => ChatMessageSchema,
|
|
27
|
+
ChatStopReasonSchema: () => ChatStopReasonSchema,
|
|
28
|
+
MessageContentBlockSchema: () => MessageContentBlockSchema,
|
|
29
|
+
ModelToolCallSchema: () => ModelToolCallSchema,
|
|
30
|
+
anthropic: () => anthropic,
|
|
31
|
+
buildAnthropicRequestBody: () => buildAnthropicRequestBody,
|
|
32
|
+
buildOpenAiRequestBody: () => buildOpenAiRequestBody,
|
|
33
|
+
buildProviderCanceledError: () => buildProviderCanceledError,
|
|
34
|
+
calculateProviderRetryDelayMs: () => calculateProviderRetryDelayMs,
|
|
35
|
+
createAnthropicBatchClient: () => createAnthropicBatchClient,
|
|
36
|
+
defaultAnthropicBaseUrl: () => defaultAnthropicBaseUrl,
|
|
37
|
+
defaultAnthropicBatchPollIntervalMs: () => defaultAnthropicBatchPollIntervalMs,
|
|
38
|
+
defaultAnthropicBatchTimeoutMs: () => defaultAnthropicBatchTimeoutMs,
|
|
39
|
+
defaultAnthropicMaxOutputTokens: () => defaultAnthropicMaxOutputTokens,
|
|
40
|
+
defaultGeminiBaseUrl: () => defaultGeminiBaseUrl,
|
|
41
|
+
defaultOpenAiBaseUrl: () => defaultOpenAiBaseUrl,
|
|
42
|
+
defaultProviderRetryInitialDelayMs: () => defaultProviderRetryInitialDelayMs,
|
|
43
|
+
defaultProviderRetryMaxAttempts: () => defaultProviderRetryMaxAttempts,
|
|
44
|
+
defaultProviderRetryMaxDelayMs: () => defaultProviderRetryMaxDelayMs,
|
|
45
|
+
estimateModelCallCostUsd: () => estimateModelCallCostUsd,
|
|
46
|
+
executeAbortableProviderFetch: () => executeAbortableProviderFetch,
|
|
47
|
+
executeProviderCallWithModelFallback: () => executeProviderCallWithModelFallback,
|
|
48
|
+
executeProviderCallWithRetry: () => executeProviderCallWithRetry,
|
|
49
|
+
extractAnthropicTextContent: () => extractAnthropicTextContent,
|
|
50
|
+
extractAnthropicToolCalls: () => extractAnthropicToolCalls,
|
|
51
|
+
extractMessageText: () => extractMessageText,
|
|
52
|
+
gemini: () => gemini,
|
|
53
|
+
isAbortError: () => isAbortError,
|
|
54
|
+
isRetryableProviderAppError: () => isRetryableProviderAppError,
|
|
55
|
+
joinSystemMessageText: () => joinSystemMessageText,
|
|
56
|
+
mapAnthropicResponseToChatCompletionResult: () => mapAnthropicResponseToChatCompletionResult,
|
|
57
|
+
mapAnthropicUsageToTokenUsage: () => mapAnthropicUsageToTokenUsage,
|
|
58
|
+
mapChatMessageToAnthropicRequestMessage: () => mapChatMessageToAnthropicRequestMessage,
|
|
59
|
+
mapChatMessageToOpenAiRequestMessage: () => mapChatMessageToOpenAiRequestMessage,
|
|
60
|
+
mapModelToolChoiceToAnthropicToolChoice: () => mapModelToolChoiceToAnthropicToolChoice,
|
|
61
|
+
mapModelToolChoiceToOpenAiToolChoice: () => mapModelToolChoiceToOpenAiToolChoice,
|
|
62
|
+
mapModelToolDefinitionToOpenAiTool: () => mapModelToolDefinitionToOpenAiTool,
|
|
63
|
+
mapModelToolDefinitionsToAnthropicTools: () => mapModelToolDefinitionsToAnthropicTools,
|
|
64
|
+
mockModel: () => mockModel,
|
|
65
|
+
normalizeAnthropicStopReason: () => normalizeAnthropicStopReason,
|
|
66
|
+
openai: () => openai,
|
|
67
|
+
parseAnthropicStreamEvents: () => parseAnthropicStreamEvents,
|
|
68
|
+
parseAnthropicToolInputJson: () => parseAnthropicToolInputJson,
|
|
69
|
+
resolveProviderApiKey: () => resolveProviderApiKey,
|
|
70
|
+
streamAnthropicChatCompletion: () => streamAnthropicChatCompletion
|
|
71
|
+
});
|
|
72
|
+
module.exports = __toCommonJS(index_exports);
|
|
73
|
+
|
|
74
|
+
// src/model-adapter.ts
|
|
75
|
+
var import_zod = require("zod");
|
|
76
|
+
var import_json = require("@assemble-dev/shared-types/json");
|
|
77
|
+
var ChatMessageRoleSchema = import_zod.z.enum([
|
|
78
|
+
"system",
|
|
79
|
+
"user",
|
|
80
|
+
"assistant",
|
|
81
|
+
"tool"
|
|
82
|
+
]);
|
|
83
|
+
var MessageContentBlockSchema = import_zod.z.discriminatedUnion("type", [
|
|
84
|
+
import_zod.z.object({
|
|
85
|
+
type: import_zod.z.literal("text"),
|
|
86
|
+
text: import_zod.z.string()
|
|
87
|
+
}),
|
|
88
|
+
import_zod.z.object({
|
|
89
|
+
type: import_zod.z.literal("image"),
|
|
90
|
+
mediaType: import_zod.z.string(),
|
|
91
|
+
dataBase64: import_zod.z.string()
|
|
92
|
+
}),
|
|
93
|
+
import_zod.z.object({
|
|
94
|
+
type: import_zod.z.literal("document"),
|
|
95
|
+
mediaType: import_zod.z.string(),
|
|
96
|
+
dataBase64: import_zod.z.string()
|
|
97
|
+
})
|
|
98
|
+
]);
|
|
99
|
+
var ModelToolCallSchema = import_zod.z.object({
|
|
100
|
+
id: import_zod.z.string(),
|
|
101
|
+
toolName: import_zod.z.string(),
|
|
102
|
+
input: import_json.JsonValueSchema
|
|
103
|
+
});
|
|
104
|
+
var ChatMessageSchema = import_zod.z.object({
|
|
105
|
+
role: ChatMessageRoleSchema,
|
|
106
|
+
content: import_zod.z.union([import_zod.z.string(), import_zod.z.array(MessageContentBlockSchema)]),
|
|
107
|
+
toolName: import_zod.z.string().optional(),
|
|
108
|
+
toolCallId: import_zod.z.string().optional(),
|
|
109
|
+
toolCalls: import_zod.z.array(ModelToolCallSchema).optional()
|
|
110
|
+
});
|
|
111
|
+
var ChatStopReasonSchema = import_zod.z.enum([
|
|
112
|
+
"end_turn",
|
|
113
|
+
"tool_use",
|
|
114
|
+
"max_tokens",
|
|
115
|
+
"stop_sequence"
|
|
116
|
+
]);
|
|
117
|
+
function extractMessageText(message) {
|
|
118
|
+
if (typeof message.content === "string") {
|
|
119
|
+
return message.content;
|
|
120
|
+
}
|
|
121
|
+
return message.content.filter((contentBlock) => contentBlock.type === "text").map((contentBlock) => contentBlock.text).join("");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/openai-adapter.ts
|
|
125
|
+
var import_zod3 = require("zod");
|
|
126
|
+
var import_errors14 = require("@assemble-dev/shared-types/errors");
|
|
127
|
+
var import_json3 = require("@assemble-dev/shared-types/json");
|
|
128
|
+
var import_errors15 = require("@assemble-dev/shared-utils/errors");
|
|
129
|
+
|
|
130
|
+
// src/openai-request-mapping.ts
|
|
131
|
+
var import_errors = require("@assemble-dev/shared-types/errors");
|
|
132
|
+
var import_errors2 = require("@assemble-dev/shared-utils/errors");
|
|
133
|
+
function buildOpenAiRequestBody(model, input) {
|
|
134
|
+
const requestBody = {
|
|
135
|
+
model,
|
|
136
|
+
messages: input.messages.map(mapChatMessageToOpenAiRequestMessage)
|
|
137
|
+
};
|
|
138
|
+
if (input.temperature !== void 0) {
|
|
139
|
+
requestBody.temperature = input.temperature;
|
|
140
|
+
}
|
|
141
|
+
if (input.maxOutputTokens !== void 0) {
|
|
142
|
+
requestBody.max_tokens = input.maxOutputTokens;
|
|
143
|
+
}
|
|
144
|
+
if (input.requireJsonObjectResponse) {
|
|
145
|
+
requestBody.response_format = { type: "json_object" };
|
|
146
|
+
}
|
|
147
|
+
if (input.tools !== void 0 && input.tools.length > 0) {
|
|
148
|
+
requestBody.tools = input.tools.map(mapModelToolDefinitionToOpenAiTool);
|
|
149
|
+
}
|
|
150
|
+
if (input.toolChoice !== void 0) {
|
|
151
|
+
requestBody.tool_choice = mapModelToolChoiceToOpenAiToolChoice(
|
|
152
|
+
input.toolChoice
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return requestBody;
|
|
156
|
+
}
|
|
157
|
+
function mapModelToolDefinitionToOpenAiTool(tool) {
|
|
158
|
+
return {
|
|
159
|
+
type: "function",
|
|
160
|
+
function: {
|
|
161
|
+
name: tool.name,
|
|
162
|
+
description: tool.description,
|
|
163
|
+
parameters: tool.inputJsonSchema
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function mapModelToolChoiceToOpenAiToolChoice(toolChoice) {
|
|
168
|
+
if (toolChoice === "auto" || toolChoice === "required" || toolChoice === "none") {
|
|
169
|
+
return toolChoice;
|
|
170
|
+
}
|
|
171
|
+
return { type: "function", function: { name: toolChoice.toolName } };
|
|
172
|
+
}
|
|
173
|
+
function mapChatMessageToOpenAiRequestMessage(message) {
|
|
174
|
+
if (message.role === "tool" && message.toolCallId !== void 0) {
|
|
175
|
+
return {
|
|
176
|
+
role: "tool",
|
|
177
|
+
tool_call_id: message.toolCallId,
|
|
178
|
+
content: extractMessageText(message)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (message.role === "assistant" && message.toolCalls !== void 0 && message.toolCalls.length > 0) {
|
|
182
|
+
return mapAssistantToolCallMessageToOpenAiRequestMessage(message);
|
|
183
|
+
}
|
|
184
|
+
const requestMessage = {
|
|
185
|
+
role: message.role,
|
|
186
|
+
content: mapMessageContentToOpenAi(message)
|
|
187
|
+
};
|
|
188
|
+
if (message.toolName !== void 0) {
|
|
189
|
+
requestMessage.name = message.toolName;
|
|
190
|
+
}
|
|
191
|
+
return requestMessage;
|
|
192
|
+
}
|
|
193
|
+
function mapAssistantToolCallMessageToOpenAiRequestMessage(message) {
|
|
194
|
+
const assistantText = extractMessageText(message);
|
|
195
|
+
const toolCalls = message.toolCalls ?? [];
|
|
196
|
+
return {
|
|
197
|
+
role: "assistant",
|
|
198
|
+
content: assistantText.length > 0 ? assistantText : null,
|
|
199
|
+
tool_calls: toolCalls.map(mapModelToolCallToOpenAiRequestToolCall)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function mapModelToolCallToOpenAiRequestToolCall(toolCall) {
|
|
203
|
+
return {
|
|
204
|
+
id: toolCall.id,
|
|
205
|
+
type: "function",
|
|
206
|
+
function: {
|
|
207
|
+
name: toolCall.toolName,
|
|
208
|
+
arguments: JSON.stringify(toolCall.input)
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function mapMessageContentToOpenAi(message) {
|
|
213
|
+
if (typeof message.content === "string") {
|
|
214
|
+
return message.content;
|
|
215
|
+
}
|
|
216
|
+
return message.content.map(mapMessageContentBlockToOpenAiContentPart);
|
|
217
|
+
}
|
|
218
|
+
function mapMessageContentBlockToOpenAiContentPart(contentBlock) {
|
|
219
|
+
if (contentBlock.type === "text") {
|
|
220
|
+
return { type: "text", text: contentBlock.text };
|
|
221
|
+
}
|
|
222
|
+
if (contentBlock.type === "image") {
|
|
223
|
+
return {
|
|
224
|
+
type: "image_url",
|
|
225
|
+
image_url: {
|
|
226
|
+
url: `data:${contentBlock.mediaType};base64,${contentBlock.dataBase64}`
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
throw new import_errors2.AppError({
|
|
231
|
+
code: import_errors.AppErrorCode.ValidationFailed,
|
|
232
|
+
message: "OpenAI adapter does not support document content blocks in chat messages",
|
|
233
|
+
statusCode: 400
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/parse-structured-json.ts
|
|
238
|
+
var import_errors3 = require("@assemble-dev/shared-types/errors");
|
|
239
|
+
var import_json2 = require("@assemble-dev/shared-types/json");
|
|
240
|
+
var import_errors4 = require("@assemble-dev/shared-utils/errors");
|
|
241
|
+
var markdownCodeFencePattern = /^```[A-Za-z]*\s*\n?([\s\S]*?)\n?```$/;
|
|
242
|
+
function parseStructuredJsonText(text, schema) {
|
|
243
|
+
return schema.parse(parseJsonText(stripMarkdownCodeFences(text)));
|
|
244
|
+
}
|
|
245
|
+
function stripMarkdownCodeFences(text) {
|
|
246
|
+
const trimmedText = text.trim();
|
|
247
|
+
const fenceMatch = markdownCodeFencePattern.exec(trimmedText);
|
|
248
|
+
if (fenceMatch?.[1] === void 0) {
|
|
249
|
+
return trimmedText;
|
|
250
|
+
}
|
|
251
|
+
return fenceMatch[1].trim();
|
|
252
|
+
}
|
|
253
|
+
function parseJsonText(text) {
|
|
254
|
+
try {
|
|
255
|
+
return import_json2.JsonValueSchema.parse(JSON.parse(text));
|
|
256
|
+
} catch (error) {
|
|
257
|
+
throw new import_errors4.AppError({
|
|
258
|
+
code: import_errors3.AppErrorCode.ValidationFailed,
|
|
259
|
+
message: "Structured output was not valid JSON",
|
|
260
|
+
statusCode: 422,
|
|
261
|
+
cause: error instanceof Error ? error : void 0
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/provider-api-key.ts
|
|
267
|
+
var import_zod2 = require("zod");
|
|
268
|
+
var import_errors5 = require("@assemble-dev/shared-types/errors");
|
|
269
|
+
var import_errors6 = require("@assemble-dev/shared-utils/errors");
|
|
270
|
+
var import_provider_keys = require("@assemble-dev/shared-utils/provider-keys");
|
|
271
|
+
function resolveProviderApiKey(input) {
|
|
272
|
+
if (input.apiKey !== void 0 && input.apiKey.length > 0) {
|
|
273
|
+
return input.apiKey;
|
|
274
|
+
}
|
|
275
|
+
if (input.apiKeyName !== void 0) {
|
|
276
|
+
return resolveNamedProviderApiKeyFromEnv(input, input.apiKeyName);
|
|
277
|
+
}
|
|
278
|
+
return resolveBaseProviderApiKeyFromEnv(input);
|
|
279
|
+
}
|
|
280
|
+
function resolveNamedProviderApiKeyFromEnv(input, apiKeyName) {
|
|
281
|
+
if (!(0, import_provider_keys.validateProviderApiKeyName)(apiKeyName)) {
|
|
282
|
+
throw new import_errors6.AppError({
|
|
283
|
+
code: import_errors5.AppErrorCode.BadRequest,
|
|
284
|
+
message: `Invalid ${input.providerLabel} apiKeyName "${apiKeyName}". Expected ${import_provider_keys.expectedProviderApiKeyNameFormat}.`,
|
|
285
|
+
statusCode: 400
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const envVarName = (0, import_provider_keys.buildNamedApiKeyEnvVarName)(input.baseEnvVarName, apiKeyName);
|
|
289
|
+
const envApiKey = readEnvironmentVariable(envVarName);
|
|
290
|
+
if (envApiKey === void 0 || envApiKey.length === 0) {
|
|
291
|
+
throw new import_errors6.AppError({
|
|
292
|
+
code: import_errors5.AppErrorCode.Unauthorized,
|
|
293
|
+
message: `${input.providerLabel} API key named "${apiKeyName}" is missing. Set the ${envVarName} environment variable or pass apiKey to ${input.adapterFunctionName}.`,
|
|
294
|
+
statusCode: 401
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return envApiKey;
|
|
298
|
+
}
|
|
299
|
+
function resolveBaseProviderApiKeyFromEnv(input) {
|
|
300
|
+
const envApiKey = readEnvironmentVariable(input.baseEnvVarName);
|
|
301
|
+
if (envApiKey === void 0 || envApiKey.length === 0) {
|
|
302
|
+
throw new import_errors6.AppError({
|
|
303
|
+
code: import_errors5.AppErrorCode.Unauthorized,
|
|
304
|
+
message: `${input.providerLabel} API key is missing. Pass apiKey to ${input.adapterFunctionName} or set the ${input.baseEnvVarName} environment variable.`,
|
|
305
|
+
statusCode: 401
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return envApiKey;
|
|
309
|
+
}
|
|
310
|
+
function readEnvironmentVariable(envVarName) {
|
|
311
|
+
const parsedEnv = import_zod2.z.object({ [envVarName]: import_zod2.z.string().optional() }).parse(process.env);
|
|
312
|
+
return parsedEnv[envVarName];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/provider-cancellation.ts
|
|
316
|
+
var import_errors7 = require("@assemble-dev/shared-types/errors");
|
|
317
|
+
var import_errors8 = require("@assemble-dev/shared-utils/errors");
|
|
318
|
+
function isAbortError(error) {
|
|
319
|
+
return error.name === "AbortError";
|
|
320
|
+
}
|
|
321
|
+
function buildProviderCanceledError(providerLabel) {
|
|
322
|
+
return new import_errors8.AppError({
|
|
323
|
+
code: import_errors7.AppErrorCode.Canceled,
|
|
324
|
+
message: `${providerLabel} request was canceled`,
|
|
325
|
+
statusCode: 499
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async function executeAbortableProviderFetch(providerLabel, performFetch) {
|
|
329
|
+
try {
|
|
330
|
+
return await performFetch();
|
|
331
|
+
} catch (error) {
|
|
332
|
+
if (error instanceof Error && isAbortError(error)) {
|
|
333
|
+
throw buildProviderCanceledError(providerLabel);
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/provider-fallback.ts
|
|
340
|
+
var import_errors11 = require("@assemble-dev/shared-utils/errors");
|
|
341
|
+
|
|
342
|
+
// src/provider-retry.ts
|
|
343
|
+
var import_errors9 = require("@assemble-dev/shared-types/errors");
|
|
344
|
+
var import_errors10 = require("@assemble-dev/shared-utils/errors");
|
|
345
|
+
var defaultProviderRetryMaxAttempts = 3;
|
|
346
|
+
var defaultProviderRetryInitialDelayMs = 500;
|
|
347
|
+
var defaultProviderRetryMaxDelayMs = 8e3;
|
|
348
|
+
var retryableAppErrorCodes = /* @__PURE__ */ new Set([
|
|
349
|
+
import_errors9.AppErrorCode.RateLimited,
|
|
350
|
+
import_errors9.AppErrorCode.ServiceUnavailable,
|
|
351
|
+
import_errors9.AppErrorCode.InternalServerError
|
|
352
|
+
]);
|
|
353
|
+
function isRetryableProviderAppError(appError) {
|
|
354
|
+
return retryableAppErrorCodes.has(appError.code);
|
|
355
|
+
}
|
|
356
|
+
async function executeProviderCallWithRetry(retryOptions, executeCall, waitFor) {
|
|
357
|
+
if (retryOptions === void 0) {
|
|
358
|
+
return await executeCall();
|
|
359
|
+
}
|
|
360
|
+
const maxAttempts = retryOptions.maxAttempts ?? defaultProviderRetryMaxAttempts;
|
|
361
|
+
const waitForRetryDelay = waitFor ?? waitForMilliseconds;
|
|
362
|
+
for (let attemptNumber = 1; ; attemptNumber += 1) {
|
|
363
|
+
try {
|
|
364
|
+
return await executeCall();
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error instanceof import_errors10.AppError && !isRetryableProviderAppError(error)) {
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
if (error instanceof Error && isAbortError(error)) {
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
if (attemptNumber >= maxAttempts) {
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
await waitForRetryDelay(
|
|
376
|
+
calculateProviderRetryDelayMs(retryOptions, attemptNumber)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function calculateProviderRetryDelayMs(retryOptions, attemptNumber) {
|
|
382
|
+
const initialDelayMs = retryOptions.initialDelayMs ?? defaultProviderRetryInitialDelayMs;
|
|
383
|
+
const maxDelayMs = retryOptions.maxDelayMs ?? defaultProviderRetryMaxDelayMs;
|
|
384
|
+
return Math.min(initialDelayMs * 2 ** (attemptNumber - 1), maxDelayMs);
|
|
385
|
+
}
|
|
386
|
+
async function waitForMilliseconds(ms) {
|
|
387
|
+
await new Promise((resolve) => {
|
|
388
|
+
setTimeout(resolve, ms);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/provider-fallback.ts
|
|
393
|
+
async function executeProviderCallWithModelFallback(fallbackModel, executePrimaryCall, executeFallbackCall) {
|
|
394
|
+
try {
|
|
395
|
+
return await executePrimaryCall();
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (fallbackModel === void 0) {
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
if (error instanceof import_errors11.AppError && !isRetryableProviderAppError(error)) {
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
if (error instanceof Error && isAbortError(error)) {
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
return await executeFallbackCall(fallbackModel);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/provider-status-error.ts
|
|
411
|
+
var import_errors12 = require("@assemble-dev/shared-types/errors");
|
|
412
|
+
var import_errors13 = require("@assemble-dev/shared-utils/errors");
|
|
413
|
+
function buildProviderStatusError(providerLabel, providerStatus) {
|
|
414
|
+
const code = mapProviderStatusToAppErrorCode(providerStatus);
|
|
415
|
+
return new import_errors13.AppError({
|
|
416
|
+
code,
|
|
417
|
+
message: `${providerLabel} request failed with status ${String(providerStatus)}`,
|
|
418
|
+
statusCode: mapAppErrorCodeToStatusCode(code),
|
|
419
|
+
details: { providerStatus }
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function buildProviderInvalidResponseError(providerLabel, expectedShapeDescription) {
|
|
423
|
+
return new import_errors13.AppError({
|
|
424
|
+
code: import_errors12.AppErrorCode.ValidationFailed,
|
|
425
|
+
message: `${providerLabel} response did not match the expected ${expectedShapeDescription} shape`,
|
|
426
|
+
statusCode: 502
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function mapProviderStatusToAppErrorCode(providerStatus) {
|
|
430
|
+
if (providerStatus === 400) {
|
|
431
|
+
return import_errors12.AppErrorCode.BadRequest;
|
|
432
|
+
}
|
|
433
|
+
if (providerStatus === 401) {
|
|
434
|
+
return import_errors12.AppErrorCode.Unauthorized;
|
|
435
|
+
}
|
|
436
|
+
if (providerStatus === 403) {
|
|
437
|
+
return import_errors12.AppErrorCode.Forbidden;
|
|
438
|
+
}
|
|
439
|
+
if (providerStatus === 404) {
|
|
440
|
+
return import_errors12.AppErrorCode.NotFound;
|
|
441
|
+
}
|
|
442
|
+
if (providerStatus === 429) {
|
|
443
|
+
return import_errors12.AppErrorCode.RateLimited;
|
|
444
|
+
}
|
|
445
|
+
return import_errors12.AppErrorCode.InternalServerError;
|
|
446
|
+
}
|
|
447
|
+
function mapAppErrorCodeToStatusCode(code) {
|
|
448
|
+
if (code === import_errors12.AppErrorCode.BadRequest) {
|
|
449
|
+
return 400;
|
|
450
|
+
}
|
|
451
|
+
if (code === import_errors12.AppErrorCode.Unauthorized) {
|
|
452
|
+
return 401;
|
|
453
|
+
}
|
|
454
|
+
if (code === import_errors12.AppErrorCode.Forbidden) {
|
|
455
|
+
return 403;
|
|
456
|
+
}
|
|
457
|
+
if (code === import_errors12.AppErrorCode.NotFound) {
|
|
458
|
+
return 404;
|
|
459
|
+
}
|
|
460
|
+
if (code === import_errors12.AppErrorCode.RateLimited) {
|
|
461
|
+
return 429;
|
|
462
|
+
}
|
|
463
|
+
return 500;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/openai-adapter.ts
|
|
467
|
+
var defaultOpenAiBaseUrl = "https://api.openai.com/v1";
|
|
468
|
+
var OpenAiResponseToolCallSchema = import_zod3.z.object({
|
|
469
|
+
id: import_zod3.z.string(),
|
|
470
|
+
function: import_zod3.z.object({
|
|
471
|
+
name: import_zod3.z.string(),
|
|
472
|
+
arguments: import_zod3.z.string()
|
|
473
|
+
})
|
|
474
|
+
});
|
|
475
|
+
var OpenAiChatCompletionResponseSchema = import_zod3.z.object({
|
|
476
|
+
choices: import_zod3.z.array(
|
|
477
|
+
import_zod3.z.object({
|
|
478
|
+
message: import_zod3.z.object({
|
|
479
|
+
content: import_zod3.z.string().nullable(),
|
|
480
|
+
tool_calls: import_zod3.z.array(OpenAiResponseToolCallSchema).optional()
|
|
481
|
+
}),
|
|
482
|
+
finish_reason: import_zod3.z.string().nullable().optional()
|
|
483
|
+
})
|
|
484
|
+
).min(1),
|
|
485
|
+
usage: import_zod3.z.object({
|
|
486
|
+
prompt_tokens: import_zod3.z.number().int().nonnegative(),
|
|
487
|
+
completion_tokens: import_zod3.z.number().int().nonnegative(),
|
|
488
|
+
total_tokens: import_zod3.z.number().int().nonnegative()
|
|
489
|
+
})
|
|
490
|
+
});
|
|
491
|
+
var structuredOutputSystemMessage = {
|
|
492
|
+
role: "system",
|
|
493
|
+
content: "Respond with a single JSON object that satisfies the caller's requested structure. Output only valid JSON with no surrounding text."
|
|
494
|
+
};
|
|
495
|
+
function openai(options) {
|
|
496
|
+
return {
|
|
497
|
+
name: `openai:${options.model}`,
|
|
498
|
+
async generateChatCompletion(request) {
|
|
499
|
+
return await executeOpenAiAdapterCall(options, {
|
|
500
|
+
messages: request.messages,
|
|
501
|
+
temperature: request.temperature ?? options.temperature,
|
|
502
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
503
|
+
requireJsonObjectResponse: false,
|
|
504
|
+
tools: request.tools,
|
|
505
|
+
toolChoice: request.toolChoice,
|
|
506
|
+
abortSignal: request.abortSignal
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
async generateStructuredOutput(request) {
|
|
510
|
+
const completion = await executeOpenAiAdapterCall(options, {
|
|
511
|
+
messages: [structuredOutputSystemMessage, ...request.messages],
|
|
512
|
+
temperature: request.temperature ?? options.temperature,
|
|
513
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
514
|
+
requireJsonObjectResponse: true,
|
|
515
|
+
abortSignal: request.abortSignal
|
|
516
|
+
});
|
|
517
|
+
return {
|
|
518
|
+
value: parseStructuredJsonText(completion.content, request.schema),
|
|
519
|
+
tokenUsage: completion.tokenUsage,
|
|
520
|
+
latencyMs: completion.latencyMs
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
async function executeOpenAiAdapterCall(options, input) {
|
|
526
|
+
return await executeProviderCallWithModelFallback(
|
|
527
|
+
options.fallbackModel,
|
|
528
|
+
async () => await executeProviderCallWithRetry(
|
|
529
|
+
options.retry,
|
|
530
|
+
async () => await requestOpenAiChatCompletion(options, input)
|
|
531
|
+
),
|
|
532
|
+
async (fallbackModelId) => await requestOpenAiChatCompletion(
|
|
533
|
+
{ ...options, model: fallbackModelId },
|
|
534
|
+
input
|
|
535
|
+
)
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
async function requestOpenAiChatCompletion(options, input) {
|
|
539
|
+
const apiKey = resolveProviderApiKey({
|
|
540
|
+
providerLabel: "OpenAI",
|
|
541
|
+
adapterFunctionName: "openai()",
|
|
542
|
+
baseEnvVarName: "OPENAI_API_KEY",
|
|
543
|
+
apiKey: options.apiKey,
|
|
544
|
+
apiKeyName: options.apiKeyName
|
|
545
|
+
});
|
|
546
|
+
const fetchImplementation = options.fetchImplementation ?? fetch;
|
|
547
|
+
const baseUrl = options.baseURL ?? defaultOpenAiBaseUrl;
|
|
548
|
+
const requestBody = buildOpenAiRequestBody(options.model, input);
|
|
549
|
+
const startedAt = Date.now();
|
|
550
|
+
const response = await executeAbortableProviderFetch(
|
|
551
|
+
"OpenAI",
|
|
552
|
+
async () => await fetchImplementation(`${baseUrl}/chat/completions`, {
|
|
553
|
+
method: "POST",
|
|
554
|
+
headers: {
|
|
555
|
+
"Content-Type": "application/json",
|
|
556
|
+
Authorization: `Bearer ${apiKey}`
|
|
557
|
+
},
|
|
558
|
+
body: JSON.stringify(requestBody),
|
|
559
|
+
signal: input.abortSignal
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
const latencyMs = Date.now() - startedAt;
|
|
563
|
+
if (!response.ok) {
|
|
564
|
+
throw buildProviderStatusError("OpenAI", response.status);
|
|
565
|
+
}
|
|
566
|
+
const parseResult = OpenAiChatCompletionResponseSchema.safeParse(
|
|
567
|
+
await response.json()
|
|
568
|
+
);
|
|
569
|
+
if (!parseResult.success) {
|
|
570
|
+
throw buildProviderInvalidResponseError("OpenAI", "chat completion");
|
|
571
|
+
}
|
|
572
|
+
return mapOpenAiResponseToChatCompletionResult(parseResult.data, latencyMs);
|
|
573
|
+
}
|
|
574
|
+
function mapOpenAiResponseToChatCompletionResult(response, latencyMs) {
|
|
575
|
+
const toolCalls = extractOpenAiToolCalls(response);
|
|
576
|
+
const result = {
|
|
577
|
+
content: extractOpenAiMessageContent(response, toolCalls.length > 0),
|
|
578
|
+
tokenUsage: mapOpenAiUsageToTokenUsage(response),
|
|
579
|
+
latencyMs
|
|
580
|
+
};
|
|
581
|
+
const finishReason = response.choices[0]?.finish_reason;
|
|
582
|
+
if (toolCalls.length > 0) {
|
|
583
|
+
result.toolCalls = toolCalls;
|
|
584
|
+
}
|
|
585
|
+
if (finishReason !== null && finishReason !== void 0) {
|
|
586
|
+
result.stopReason = normalizeOpenAiFinishReason(finishReason);
|
|
587
|
+
}
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
function extractOpenAiToolCalls(response) {
|
|
591
|
+
const responseToolCalls = response.choices[0]?.message.tool_calls ?? [];
|
|
592
|
+
return responseToolCalls.map((responseToolCall) => ({
|
|
593
|
+
id: responseToolCall.id,
|
|
594
|
+
toolName: responseToolCall.function.name,
|
|
595
|
+
input: parseOpenAiToolArgumentsJson(responseToolCall.function.arguments)
|
|
596
|
+
}));
|
|
597
|
+
}
|
|
598
|
+
function parseOpenAiToolArgumentsJson(argumentsJsonText) {
|
|
599
|
+
if (argumentsJsonText.trim().length === 0) {
|
|
600
|
+
return {};
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
return import_json3.JsonValueSchema.parse(JSON.parse(argumentsJsonText));
|
|
604
|
+
} catch (error) {
|
|
605
|
+
throw new import_errors15.AppError({
|
|
606
|
+
code: import_errors14.AppErrorCode.ValidationFailed,
|
|
607
|
+
message: "OpenAI tool call arguments were not valid JSON",
|
|
608
|
+
statusCode: 502,
|
|
609
|
+
cause: error instanceof Error ? error : void 0
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function normalizeOpenAiFinishReason(finishReason) {
|
|
614
|
+
if (finishReason === "tool_calls") {
|
|
615
|
+
return "tool_use";
|
|
616
|
+
}
|
|
617
|
+
if (finishReason === "length") {
|
|
618
|
+
return "max_tokens";
|
|
619
|
+
}
|
|
620
|
+
return "end_turn";
|
|
621
|
+
}
|
|
622
|
+
function extractOpenAiMessageContent(response, allowEmptyContent) {
|
|
623
|
+
const content = response.choices[0]?.message.content;
|
|
624
|
+
if (content === void 0 || content === null) {
|
|
625
|
+
if (allowEmptyContent) {
|
|
626
|
+
return "";
|
|
627
|
+
}
|
|
628
|
+
throw new import_errors15.AppError({
|
|
629
|
+
code: import_errors14.AppErrorCode.ValidationFailed,
|
|
630
|
+
message: "OpenAI response contained no message content",
|
|
631
|
+
statusCode: 502
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
return content;
|
|
635
|
+
}
|
|
636
|
+
function mapOpenAiUsageToTokenUsage(response) {
|
|
637
|
+
return {
|
|
638
|
+
inputTokens: response.usage.prompt_tokens,
|
|
639
|
+
outputTokens: response.usage.completion_tokens,
|
|
640
|
+
totalTokens: response.usage.total_tokens
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/anthropic-request-mapping.ts
|
|
645
|
+
function buildAnthropicRequestBody(settings, input) {
|
|
646
|
+
const requestBody = {
|
|
647
|
+
model: settings.model,
|
|
648
|
+
max_tokens: input.maxOutputTokens ?? settings.defaultMaxOutputTokens,
|
|
649
|
+
messages: input.messages.filter((message) => message.role !== "system").map(mapChatMessageToAnthropicRequestMessage)
|
|
650
|
+
};
|
|
651
|
+
applyAnthropicSystemField(requestBody, settings, input);
|
|
652
|
+
applyAnthropicToolFields(requestBody, settings, input);
|
|
653
|
+
if (input.temperature !== void 0) {
|
|
654
|
+
requestBody.temperature = input.temperature;
|
|
655
|
+
}
|
|
656
|
+
if (settings.thinking !== void 0) {
|
|
657
|
+
requestBody.thinking = {
|
|
658
|
+
type: "enabled",
|
|
659
|
+
budget_tokens: settings.thinking.budgetTokens
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
return requestBody;
|
|
663
|
+
}
|
|
664
|
+
function applyAnthropicSystemField(requestBody, settings, input) {
|
|
665
|
+
const systemText = joinSystemMessageText(input.messages);
|
|
666
|
+
if (systemText.length === 0) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (settings.promptCaching !== "auto") {
|
|
670
|
+
requestBody.system = systemText;
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
requestBody.system = [
|
|
674
|
+
{ type: "text", text: systemText, cache_control: { type: "ephemeral" } }
|
|
675
|
+
];
|
|
676
|
+
}
|
|
677
|
+
function applyAnthropicToolFields(requestBody, settings, input) {
|
|
678
|
+
if (input.tools !== void 0 && input.tools.length > 0) {
|
|
679
|
+
requestBody.tools = mapModelToolDefinitionsToAnthropicTools(
|
|
680
|
+
input.tools,
|
|
681
|
+
settings.promptCaching
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
if (input.toolChoice !== void 0) {
|
|
685
|
+
requestBody.tool_choice = mapModelToolChoiceToAnthropicToolChoice(
|
|
686
|
+
input.toolChoice
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function mapModelToolDefinitionsToAnthropicTools(tools, promptCaching) {
|
|
691
|
+
return tools.map((tool, toolIndex) => {
|
|
692
|
+
const anthropicTool = {
|
|
693
|
+
name: tool.name,
|
|
694
|
+
description: tool.description,
|
|
695
|
+
input_schema: tool.inputJsonSchema
|
|
696
|
+
};
|
|
697
|
+
if (promptCaching === "auto" && toolIndex === tools.length - 1) {
|
|
698
|
+
anthropicTool.cache_control = { type: "ephemeral" };
|
|
699
|
+
}
|
|
700
|
+
return anthropicTool;
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
function mapModelToolChoiceToAnthropicToolChoice(toolChoice) {
|
|
704
|
+
if (toolChoice === "auto") {
|
|
705
|
+
return { type: "auto" };
|
|
706
|
+
}
|
|
707
|
+
if (toolChoice === "required") {
|
|
708
|
+
return { type: "any" };
|
|
709
|
+
}
|
|
710
|
+
if (toolChoice === "none") {
|
|
711
|
+
return { type: "none" };
|
|
712
|
+
}
|
|
713
|
+
return { type: "tool", name: toolChoice.toolName };
|
|
714
|
+
}
|
|
715
|
+
function joinSystemMessageText(messages) {
|
|
716
|
+
return messages.filter((message) => message.role === "system").map(extractMessageText).join("\n\n");
|
|
717
|
+
}
|
|
718
|
+
function mapChatMessageToAnthropicRequestMessage(message) {
|
|
719
|
+
if (message.role === "assistant") {
|
|
720
|
+
return mapAssistantMessageToAnthropicRequestMessage(message);
|
|
721
|
+
}
|
|
722
|
+
if (message.role === "tool") {
|
|
723
|
+
return mapToolMessageToAnthropicRequestMessage(message);
|
|
724
|
+
}
|
|
725
|
+
return { role: "user", content: mapMessageContentToAnthropic(message) };
|
|
726
|
+
}
|
|
727
|
+
function mapAssistantMessageToAnthropicRequestMessage(message) {
|
|
728
|
+
if (message.toolCalls === void 0 || message.toolCalls.length === 0) {
|
|
729
|
+
return { role: "assistant", content: mapMessageContentToAnthropic(message) };
|
|
730
|
+
}
|
|
731
|
+
const contentBlocks = [];
|
|
732
|
+
const assistantText = extractMessageText(message);
|
|
733
|
+
if (assistantText.length > 0) {
|
|
734
|
+
contentBlocks.push({ type: "text", text: assistantText });
|
|
735
|
+
}
|
|
736
|
+
for (const toolCall of message.toolCalls) {
|
|
737
|
+
contentBlocks.push({
|
|
738
|
+
type: "tool_use",
|
|
739
|
+
id: toolCall.id,
|
|
740
|
+
name: toolCall.toolName,
|
|
741
|
+
input: toolCall.input
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
return { role: "assistant", content: contentBlocks };
|
|
745
|
+
}
|
|
746
|
+
function mapToolMessageToAnthropicRequestMessage(message) {
|
|
747
|
+
if (message.toolCallId !== void 0) {
|
|
748
|
+
return {
|
|
749
|
+
role: "user",
|
|
750
|
+
content: [
|
|
751
|
+
{
|
|
752
|
+
type: "tool_result",
|
|
753
|
+
tool_use_id: message.toolCallId,
|
|
754
|
+
content: extractMessageText(message)
|
|
755
|
+
}
|
|
756
|
+
]
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
return { role: "user", content: buildAnthropicToolResultText(message) };
|
|
760
|
+
}
|
|
761
|
+
function buildAnthropicToolResultText(message) {
|
|
762
|
+
const messageText = extractMessageText(message);
|
|
763
|
+
if (message.toolName === void 0) {
|
|
764
|
+
return `Tool result: ${messageText}`;
|
|
765
|
+
}
|
|
766
|
+
return `Tool result (${message.toolName}): ${messageText}`;
|
|
767
|
+
}
|
|
768
|
+
function mapMessageContentToAnthropic(message) {
|
|
769
|
+
if (typeof message.content === "string") {
|
|
770
|
+
return message.content;
|
|
771
|
+
}
|
|
772
|
+
return message.content.map(mapMessageContentBlockToAnthropic);
|
|
773
|
+
}
|
|
774
|
+
function mapMessageContentBlockToAnthropic(contentBlock) {
|
|
775
|
+
if (contentBlock.type === "text") {
|
|
776
|
+
return { type: "text", text: contentBlock.text };
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
type: contentBlock.type,
|
|
780
|
+
source: {
|
|
781
|
+
type: "base64",
|
|
782
|
+
media_type: contentBlock.mediaType,
|
|
783
|
+
data: contentBlock.dataBase64
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/anthropic-response-parsing.ts
|
|
789
|
+
var import_zod4 = require("zod");
|
|
790
|
+
var import_errors16 = require("@assemble-dev/shared-types/errors");
|
|
791
|
+
var import_json4 = require("@assemble-dev/shared-types/json");
|
|
792
|
+
var import_errors17 = require("@assemble-dev/shared-utils/errors");
|
|
793
|
+
var AnthropicUsageSchema = import_zod4.z.object({
|
|
794
|
+
input_tokens: import_zod4.z.number().int().nonnegative(),
|
|
795
|
+
output_tokens: import_zod4.z.number().int().nonnegative(),
|
|
796
|
+
cache_creation_input_tokens: import_zod4.z.number().int().nonnegative().optional(),
|
|
797
|
+
cache_read_input_tokens: import_zod4.z.number().int().nonnegative().optional()
|
|
798
|
+
});
|
|
799
|
+
var AnthropicContentBlockSchema = import_zod4.z.discriminatedUnion("type", [
|
|
800
|
+
import_zod4.z.object({
|
|
801
|
+
type: import_zod4.z.literal("text"),
|
|
802
|
+
text: import_zod4.z.string()
|
|
803
|
+
}),
|
|
804
|
+
import_zod4.z.object({
|
|
805
|
+
type: import_zod4.z.literal("tool_use"),
|
|
806
|
+
id: import_zod4.z.string(),
|
|
807
|
+
name: import_zod4.z.string(),
|
|
808
|
+
input: import_json4.JsonValueSchema
|
|
809
|
+
}),
|
|
810
|
+
import_zod4.z.object({
|
|
811
|
+
type: import_zod4.z.literal("thinking"),
|
|
812
|
+
thinking: import_zod4.z.string().optional(),
|
|
813
|
+
signature: import_zod4.z.string().optional()
|
|
814
|
+
}),
|
|
815
|
+
import_zod4.z.object({
|
|
816
|
+
type: import_zod4.z.literal("redacted_thinking"),
|
|
817
|
+
data: import_zod4.z.string().optional()
|
|
818
|
+
})
|
|
819
|
+
]);
|
|
820
|
+
var AnthropicMessagesResponseSchema = import_zod4.z.object({
|
|
821
|
+
content: import_zod4.z.array(AnthropicContentBlockSchema),
|
|
822
|
+
stop_reason: import_zod4.z.string().nullable().optional(),
|
|
823
|
+
usage: AnthropicUsageSchema
|
|
824
|
+
});
|
|
825
|
+
function mapAnthropicResponseToChatCompletionResult(response, latencyMs) {
|
|
826
|
+
const toolCalls = extractAnthropicToolCalls(response);
|
|
827
|
+
const result = {
|
|
828
|
+
content: extractAnthropicTextContent(response, toolCalls.length > 0),
|
|
829
|
+
tokenUsage: mapAnthropicUsageToTokenUsage(response.usage),
|
|
830
|
+
latencyMs
|
|
831
|
+
};
|
|
832
|
+
if (toolCalls.length > 0) {
|
|
833
|
+
result.toolCalls = toolCalls;
|
|
834
|
+
}
|
|
835
|
+
if (response.stop_reason !== null && response.stop_reason !== void 0) {
|
|
836
|
+
result.stopReason = normalizeAnthropicStopReason(response.stop_reason);
|
|
837
|
+
}
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
function extractAnthropicToolCalls(response) {
|
|
841
|
+
return response.content.filter((contentBlock) => contentBlock.type === "tool_use").map((contentBlock) => ({
|
|
842
|
+
id: contentBlock.id,
|
|
843
|
+
toolName: contentBlock.name,
|
|
844
|
+
input: contentBlock.input
|
|
845
|
+
}));
|
|
846
|
+
}
|
|
847
|
+
function extractAnthropicTextContent(response, allowEmptyText) {
|
|
848
|
+
const textBlocks = response.content.filter(
|
|
849
|
+
(contentBlock) => contentBlock.type === "text"
|
|
850
|
+
);
|
|
851
|
+
if (textBlocks.length === 0 && !allowEmptyText) {
|
|
852
|
+
throw new import_errors17.AppError({
|
|
853
|
+
code: import_errors16.AppErrorCode.ValidationFailed,
|
|
854
|
+
message: "Anthropic response contained no text content",
|
|
855
|
+
statusCode: 502
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
return textBlocks.map((textBlock) => textBlock.text).join("");
|
|
859
|
+
}
|
|
860
|
+
function normalizeAnthropicStopReason(providerStopReason) {
|
|
861
|
+
if (providerStopReason === "tool_use") {
|
|
862
|
+
return "tool_use";
|
|
863
|
+
}
|
|
864
|
+
if (providerStopReason === "max_tokens") {
|
|
865
|
+
return "max_tokens";
|
|
866
|
+
}
|
|
867
|
+
if (providerStopReason === "stop_sequence") {
|
|
868
|
+
return "stop_sequence";
|
|
869
|
+
}
|
|
870
|
+
return "end_turn";
|
|
871
|
+
}
|
|
872
|
+
function mapAnthropicUsageToTokenUsage(usage) {
|
|
873
|
+
const cacheCreationInputTokens = usage.cache_creation_input_tokens;
|
|
874
|
+
const cacheReadInputTokens = usage.cache_read_input_tokens;
|
|
875
|
+
const tokenUsage = {
|
|
876
|
+
inputTokens: usage.input_tokens,
|
|
877
|
+
outputTokens: usage.output_tokens,
|
|
878
|
+
totalTokens: usage.input_tokens + usage.output_tokens + (cacheCreationInputTokens ?? 0) + (cacheReadInputTokens ?? 0)
|
|
879
|
+
};
|
|
880
|
+
if (cacheCreationInputTokens !== void 0) {
|
|
881
|
+
tokenUsage.cacheCreationInputTokens = cacheCreationInputTokens;
|
|
882
|
+
}
|
|
883
|
+
if (cacheReadInputTokens !== void 0) {
|
|
884
|
+
tokenUsage.cacheReadInputTokens = cacheReadInputTokens;
|
|
885
|
+
}
|
|
886
|
+
return tokenUsage;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/anthropic-stream.ts
|
|
890
|
+
var import_errors18 = require("@assemble-dev/shared-types/errors");
|
|
891
|
+
var import_json5 = require("@assemble-dev/shared-types/json");
|
|
892
|
+
var import_errors19 = require("@assemble-dev/shared-utils/errors");
|
|
893
|
+
|
|
894
|
+
// src/anthropic-stream-events.ts
|
|
895
|
+
var import_zod5 = require("zod");
|
|
896
|
+
var AnthropicStreamEventSchema = import_zod5.z.discriminatedUnion("type", [
|
|
897
|
+
import_zod5.z.object({
|
|
898
|
+
type: import_zod5.z.literal("message_start"),
|
|
899
|
+
message: import_zod5.z.object({
|
|
900
|
+
usage: import_zod5.z.object({
|
|
901
|
+
input_tokens: import_zod5.z.number().int().nonnegative(),
|
|
902
|
+
output_tokens: import_zod5.z.number().int().nonnegative().optional(),
|
|
903
|
+
cache_creation_input_tokens: import_zod5.z.number().int().nonnegative().optional(),
|
|
904
|
+
cache_read_input_tokens: import_zod5.z.number().int().nonnegative().optional()
|
|
905
|
+
})
|
|
906
|
+
})
|
|
907
|
+
}),
|
|
908
|
+
import_zod5.z.object({
|
|
909
|
+
type: import_zod5.z.literal("content_block_start"),
|
|
910
|
+
index: import_zod5.z.number().int().nonnegative(),
|
|
911
|
+
content_block: import_zod5.z.discriminatedUnion("type", [
|
|
912
|
+
import_zod5.z.object({ type: import_zod5.z.literal("text"), text: import_zod5.z.string().optional() }),
|
|
913
|
+
import_zod5.z.object({
|
|
914
|
+
type: import_zod5.z.literal("tool_use"),
|
|
915
|
+
id: import_zod5.z.string(),
|
|
916
|
+
name: import_zod5.z.string()
|
|
917
|
+
})
|
|
918
|
+
])
|
|
919
|
+
}),
|
|
920
|
+
import_zod5.z.object({
|
|
921
|
+
type: import_zod5.z.literal("content_block_delta"),
|
|
922
|
+
index: import_zod5.z.number().int().nonnegative(),
|
|
923
|
+
delta: import_zod5.z.discriminatedUnion("type", [
|
|
924
|
+
import_zod5.z.object({ type: import_zod5.z.literal("text_delta"), text: import_zod5.z.string() }),
|
|
925
|
+
import_zod5.z.object({
|
|
926
|
+
type: import_zod5.z.literal("input_json_delta"),
|
|
927
|
+
partial_json: import_zod5.z.string()
|
|
928
|
+
})
|
|
929
|
+
])
|
|
930
|
+
}),
|
|
931
|
+
import_zod5.z.object({ type: import_zod5.z.literal("content_block_stop") }),
|
|
932
|
+
import_zod5.z.object({
|
|
933
|
+
type: import_zod5.z.literal("message_delta"),
|
|
934
|
+
delta: import_zod5.z.object({ stop_reason: import_zod5.z.string().nullable().optional() }),
|
|
935
|
+
usage: import_zod5.z.object({ output_tokens: import_zod5.z.number().int().nonnegative() }).optional()
|
|
936
|
+
}),
|
|
937
|
+
import_zod5.z.object({ type: import_zod5.z.literal("message_stop") }),
|
|
938
|
+
import_zod5.z.object({ type: import_zod5.z.literal("ping") }),
|
|
939
|
+
import_zod5.z.object({
|
|
940
|
+
type: import_zod5.z.literal("error"),
|
|
941
|
+
error: import_zod5.z.object({ message: import_zod5.z.string() })
|
|
942
|
+
})
|
|
943
|
+
]);
|
|
944
|
+
|
|
945
|
+
// src/anthropic-stream.ts
|
|
946
|
+
async function* streamAnthropicChatCompletion(input) {
|
|
947
|
+
const startedAt = Date.now();
|
|
948
|
+
const response = await establishAnthropicStreamResponse(input);
|
|
949
|
+
if (response.body === null) {
|
|
950
|
+
throw buildProviderInvalidResponseError("Anthropic", "stream body");
|
|
951
|
+
}
|
|
952
|
+
yield* parseAnthropicStreamEvents(response.body, startedAt, input.abortSignal);
|
|
953
|
+
}
|
|
954
|
+
async function establishAnthropicStreamResponse(input) {
|
|
955
|
+
return await executeProviderCallWithRetry(input.retry, async () => {
|
|
956
|
+
const response = await executeAbortableProviderFetch(
|
|
957
|
+
"Anthropic",
|
|
958
|
+
async () => await input.fetchImplementation(input.url, {
|
|
959
|
+
method: "POST",
|
|
960
|
+
headers: input.headers,
|
|
961
|
+
body: JSON.stringify(input.body),
|
|
962
|
+
signal: input.abortSignal
|
|
963
|
+
})
|
|
964
|
+
);
|
|
965
|
+
if (!response.ok) {
|
|
966
|
+
throw buildProviderStatusError("Anthropic", response.status);
|
|
967
|
+
}
|
|
968
|
+
return response;
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
async function* parseAnthropicStreamEvents(body, startedAt, abortSignal) {
|
|
972
|
+
const accumulator = createAnthropicStreamAccumulator();
|
|
973
|
+
for await (const dataLine of readServerSentEventDataLines(body, abortSignal)) {
|
|
974
|
+
const parsedEvent = AnthropicStreamEventSchema.safeParse(
|
|
975
|
+
JSON.parse(dataLine)
|
|
976
|
+
);
|
|
977
|
+
if (!parsedEvent.success) {
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
yield* applyAnthropicStreamEvent(parsedEvent.data, accumulator);
|
|
981
|
+
if (parsedEvent.data.type === "message_stop") {
|
|
982
|
+
yield {
|
|
983
|
+
type: "completed",
|
|
984
|
+
result: buildAnthropicStreamResult(accumulator, Date.now() - startedAt)
|
|
985
|
+
};
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
throw buildProviderInvalidResponseError("Anthropic", "stream termination");
|
|
990
|
+
}
|
|
991
|
+
async function* readServerSentEventDataLines(body, abortSignal) {
|
|
992
|
+
const reader = body.getReader();
|
|
993
|
+
const decoder = new TextDecoder();
|
|
994
|
+
let bufferedText = "";
|
|
995
|
+
try {
|
|
996
|
+
for (; ; ) {
|
|
997
|
+
if (abortSignal?.aborted === true) {
|
|
998
|
+
await reader.cancel();
|
|
999
|
+
throw buildProviderCanceledError("Anthropic");
|
|
1000
|
+
}
|
|
1001
|
+
const readResult = await reader.read();
|
|
1002
|
+
if (readResult.done) {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
bufferedText += decoder.decode(readResult.value, { stream: true });
|
|
1006
|
+
const lines = bufferedText.split("\n");
|
|
1007
|
+
bufferedText = lines.pop() ?? "";
|
|
1008
|
+
for (const line of lines) {
|
|
1009
|
+
if (line.startsWith("data:")) {
|
|
1010
|
+
const dataText = line.slice("data:".length).trim();
|
|
1011
|
+
if (dataText.length > 0) {
|
|
1012
|
+
yield dataText;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
if (error instanceof Error && isAbortError(error)) {
|
|
1019
|
+
throw buildProviderCanceledError("Anthropic");
|
|
1020
|
+
}
|
|
1021
|
+
throw error;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function createAnthropicStreamAccumulator() {
|
|
1025
|
+
return {
|
|
1026
|
+
contentText: "",
|
|
1027
|
+
toolCallsByBlockIndex: /* @__PURE__ */ new Map(),
|
|
1028
|
+
inputTokens: 0,
|
|
1029
|
+
outputTokens: 0
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function* applyAnthropicStreamEvent(event, accumulator) {
|
|
1033
|
+
if (event.type === "error") {
|
|
1034
|
+
throw new import_errors19.AppError({
|
|
1035
|
+
code: import_errors18.AppErrorCode.InternalServerError,
|
|
1036
|
+
message: `Anthropic stream reported an error: ${event.error.message}`,
|
|
1037
|
+
statusCode: 502
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
if (event.type === "message_start") {
|
|
1041
|
+
applyAnthropicMessageStartUsage(event.message.usage, accumulator);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
if (event.type === "content_block_start") {
|
|
1045
|
+
yield* applyAnthropicContentBlockStart(event, accumulator);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (event.type === "content_block_delta") {
|
|
1049
|
+
yield* applyAnthropicContentBlockDelta(event, accumulator);
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
if (event.type === "message_delta") {
|
|
1053
|
+
applyAnthropicMessageDelta(event, accumulator);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function applyAnthropicMessageStartUsage(usage, accumulator) {
|
|
1057
|
+
accumulator.inputTokens = usage.input_tokens;
|
|
1058
|
+
accumulator.outputTokens = usage.output_tokens ?? 0;
|
|
1059
|
+
accumulator.cacheCreationInputTokens = usage.cache_creation_input_tokens;
|
|
1060
|
+
accumulator.cacheReadInputTokens = usage.cache_read_input_tokens;
|
|
1061
|
+
}
|
|
1062
|
+
function* applyAnthropicContentBlockStart(event, accumulator) {
|
|
1063
|
+
if (event.content_block.type === "text") {
|
|
1064
|
+
const initialText = event.content_block.text ?? "";
|
|
1065
|
+
if (initialText.length > 0) {
|
|
1066
|
+
accumulator.contentText += initialText;
|
|
1067
|
+
yield { type: "text_delta", text: initialText };
|
|
1068
|
+
}
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
accumulator.toolCallsByBlockIndex.set(event.index, {
|
|
1072
|
+
id: event.content_block.id,
|
|
1073
|
+
toolName: event.content_block.name,
|
|
1074
|
+
inputJsonText: ""
|
|
1075
|
+
});
|
|
1076
|
+
yield {
|
|
1077
|
+
type: "tool_call_started",
|
|
1078
|
+
id: event.content_block.id,
|
|
1079
|
+
toolName: event.content_block.name
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function* applyAnthropicContentBlockDelta(event, accumulator) {
|
|
1083
|
+
if (event.delta.type === "text_delta") {
|
|
1084
|
+
accumulator.contentText += event.delta.text;
|
|
1085
|
+
yield { type: "text_delta", text: event.delta.text };
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const toolCallAccumulation = accumulator.toolCallsByBlockIndex.get(
|
|
1089
|
+
event.index
|
|
1090
|
+
);
|
|
1091
|
+
if (toolCallAccumulation === void 0) {
|
|
1092
|
+
throw buildProviderInvalidResponseError(
|
|
1093
|
+
"Anthropic",
|
|
1094
|
+
"stream tool input delta"
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
toolCallAccumulation.inputJsonText += event.delta.partial_json;
|
|
1098
|
+
yield {
|
|
1099
|
+
type: "tool_call_input_delta",
|
|
1100
|
+
id: toolCallAccumulation.id,
|
|
1101
|
+
inputJsonText: event.delta.partial_json
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function applyAnthropicMessageDelta(event, accumulator) {
|
|
1105
|
+
if (event.usage !== void 0) {
|
|
1106
|
+
accumulator.outputTokens = event.usage.output_tokens;
|
|
1107
|
+
}
|
|
1108
|
+
if (event.delta.stop_reason !== null && event.delta.stop_reason !== void 0) {
|
|
1109
|
+
accumulator.stopReason = normalizeAnthropicStopReason(
|
|
1110
|
+
event.delta.stop_reason
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
function buildAnthropicStreamResult(accumulator, latencyMs) {
|
|
1115
|
+
const result = {
|
|
1116
|
+
content: accumulator.contentText,
|
|
1117
|
+
tokenUsage: mapAnthropicUsageToTokenUsage({
|
|
1118
|
+
input_tokens: accumulator.inputTokens,
|
|
1119
|
+
output_tokens: accumulator.outputTokens,
|
|
1120
|
+
cache_creation_input_tokens: accumulator.cacheCreationInputTokens,
|
|
1121
|
+
cache_read_input_tokens: accumulator.cacheReadInputTokens
|
|
1122
|
+
}),
|
|
1123
|
+
latencyMs
|
|
1124
|
+
};
|
|
1125
|
+
const toolCalls = [...accumulator.toolCallsByBlockIndex.values()].map(
|
|
1126
|
+
mapAccumulatedToolCallToModelToolCall
|
|
1127
|
+
);
|
|
1128
|
+
if (toolCalls.length > 0) {
|
|
1129
|
+
result.toolCalls = toolCalls;
|
|
1130
|
+
}
|
|
1131
|
+
if (accumulator.stopReason !== void 0) {
|
|
1132
|
+
result.stopReason = accumulator.stopReason;
|
|
1133
|
+
}
|
|
1134
|
+
return result;
|
|
1135
|
+
}
|
|
1136
|
+
function mapAccumulatedToolCallToModelToolCall(toolCallAccumulation) {
|
|
1137
|
+
return {
|
|
1138
|
+
id: toolCallAccumulation.id,
|
|
1139
|
+
toolName: toolCallAccumulation.toolName,
|
|
1140
|
+
input: parseAnthropicToolInputJson(toolCallAccumulation.inputJsonText)
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function parseAnthropicToolInputJson(inputJsonText) {
|
|
1144
|
+
if (inputJsonText.trim().length === 0) {
|
|
1145
|
+
return {};
|
|
1146
|
+
}
|
|
1147
|
+
try {
|
|
1148
|
+
return import_json5.JsonValueSchema.parse(JSON.parse(inputJsonText));
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
throw new import_errors19.AppError({
|
|
1151
|
+
code: import_errors18.AppErrorCode.ValidationFailed,
|
|
1152
|
+
message: "Anthropic tool input was not valid JSON",
|
|
1153
|
+
statusCode: 502,
|
|
1154
|
+
cause: error instanceof Error ? error : void 0
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/anthropic-adapter.ts
|
|
1160
|
+
var defaultAnthropicBaseUrl = "https://api.anthropic.com";
|
|
1161
|
+
var defaultAnthropicMaxOutputTokens = 1024;
|
|
1162
|
+
var anthropicApiVersion = "2023-06-01";
|
|
1163
|
+
var structuredOutputSystemMessage2 = {
|
|
1164
|
+
role: "system",
|
|
1165
|
+
content: "Respond with a single JSON object that satisfies the caller's requested structure. Output only valid JSON with no surrounding text."
|
|
1166
|
+
};
|
|
1167
|
+
function anthropic(options) {
|
|
1168
|
+
return {
|
|
1169
|
+
name: `anthropic:${options.model}`,
|
|
1170
|
+
async generateChatCompletion(request) {
|
|
1171
|
+
return await executeAnthropicAdapterCall(
|
|
1172
|
+
options,
|
|
1173
|
+
buildAnthropicCallInput(options, request)
|
|
1174
|
+
);
|
|
1175
|
+
},
|
|
1176
|
+
async generateStructuredOutput(request) {
|
|
1177
|
+
const completion = await executeAnthropicAdapterCall(options, {
|
|
1178
|
+
messages: [structuredOutputSystemMessage2, ...request.messages],
|
|
1179
|
+
temperature: request.temperature ?? options.temperature,
|
|
1180
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
1181
|
+
abortSignal: request.abortSignal
|
|
1182
|
+
});
|
|
1183
|
+
return {
|
|
1184
|
+
value: parseStructuredJsonText(completion.content, request.schema),
|
|
1185
|
+
tokenUsage: completion.tokenUsage,
|
|
1186
|
+
latencyMs: completion.latencyMs
|
|
1187
|
+
};
|
|
1188
|
+
},
|
|
1189
|
+
streamChatCompletion(request) {
|
|
1190
|
+
return streamAnthropicChatCompletion(
|
|
1191
|
+
buildAnthropicStreamInput(options, request)
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function buildAnthropicCallInput(options, request) {
|
|
1197
|
+
return {
|
|
1198
|
+
messages: request.messages,
|
|
1199
|
+
temperature: request.temperature ?? options.temperature,
|
|
1200
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
1201
|
+
tools: request.tools,
|
|
1202
|
+
toolChoice: request.toolChoice,
|
|
1203
|
+
abortSignal: request.abortSignal
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
async function executeAnthropicAdapterCall(options, input) {
|
|
1207
|
+
return await executeProviderCallWithModelFallback(
|
|
1208
|
+
options.fallbackModel,
|
|
1209
|
+
async () => await executeProviderCallWithRetry(
|
|
1210
|
+
options.retry,
|
|
1211
|
+
async () => await requestAnthropicMessages(options, input)
|
|
1212
|
+
),
|
|
1213
|
+
async (fallbackModelId) => await requestAnthropicMessages(
|
|
1214
|
+
{ ...options, model: fallbackModelId },
|
|
1215
|
+
input
|
|
1216
|
+
)
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
async function requestAnthropicMessages(options, input) {
|
|
1220
|
+
const requestBody = buildAnthropicRequestBody(
|
|
1221
|
+
buildAnthropicRequestSettings(options),
|
|
1222
|
+
input
|
|
1223
|
+
);
|
|
1224
|
+
const startedAt = Date.now();
|
|
1225
|
+
const response = await executeAbortableProviderFetch(
|
|
1226
|
+
"Anthropic",
|
|
1227
|
+
async () => await resolveAnthropicFetch(options)(
|
|
1228
|
+
`${resolveAnthropicBaseUrl(options)}/v1/messages`,
|
|
1229
|
+
{
|
|
1230
|
+
method: "POST",
|
|
1231
|
+
headers: buildAnthropicRequestHeaders(options),
|
|
1232
|
+
body: JSON.stringify(requestBody),
|
|
1233
|
+
signal: input.abortSignal
|
|
1234
|
+
}
|
|
1235
|
+
)
|
|
1236
|
+
);
|
|
1237
|
+
const latencyMs = Date.now() - startedAt;
|
|
1238
|
+
if (!response.ok) {
|
|
1239
|
+
throw buildProviderStatusError("Anthropic", response.status);
|
|
1240
|
+
}
|
|
1241
|
+
const parseResult = AnthropicMessagesResponseSchema.safeParse(
|
|
1242
|
+
await response.json()
|
|
1243
|
+
);
|
|
1244
|
+
if (!parseResult.success) {
|
|
1245
|
+
throw buildProviderInvalidResponseError("Anthropic", "messages");
|
|
1246
|
+
}
|
|
1247
|
+
return mapAnthropicResponseToChatCompletionResult(parseResult.data, latencyMs);
|
|
1248
|
+
}
|
|
1249
|
+
function buildAnthropicStreamInput(options, request) {
|
|
1250
|
+
const input = buildAnthropicCallInput(options, request);
|
|
1251
|
+
const requestBody = buildAnthropicRequestBody(
|
|
1252
|
+
buildAnthropicRequestSettings(options),
|
|
1253
|
+
input
|
|
1254
|
+
);
|
|
1255
|
+
return {
|
|
1256
|
+
url: `${resolveAnthropicBaseUrl(options)}/v1/messages`,
|
|
1257
|
+
headers: buildAnthropicRequestHeaders(options),
|
|
1258
|
+
body: { ...requestBody, stream: true },
|
|
1259
|
+
fetchImplementation: resolveAnthropicFetch(options),
|
|
1260
|
+
retry: options.retry,
|
|
1261
|
+
abortSignal: request.abortSignal
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function buildAnthropicRequestSettings(options) {
|
|
1265
|
+
return {
|
|
1266
|
+
model: options.model,
|
|
1267
|
+
defaultMaxOutputTokens: defaultAnthropicMaxOutputTokens,
|
|
1268
|
+
promptCaching: options.promptCaching,
|
|
1269
|
+
thinking: options.thinking
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function buildAnthropicRequestHeaders(options) {
|
|
1273
|
+
return {
|
|
1274
|
+
"content-type": "application/json",
|
|
1275
|
+
"x-api-key": resolveProviderApiKey({
|
|
1276
|
+
providerLabel: "Anthropic",
|
|
1277
|
+
adapterFunctionName: "anthropic()",
|
|
1278
|
+
baseEnvVarName: "ANTHROPIC_API_KEY",
|
|
1279
|
+
apiKey: options.apiKey,
|
|
1280
|
+
apiKeyName: options.apiKeyName
|
|
1281
|
+
}),
|
|
1282
|
+
"anthropic-version": anthropicApiVersion
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
function resolveAnthropicBaseUrl(options) {
|
|
1286
|
+
return options.baseURL ?? defaultAnthropicBaseUrl;
|
|
1287
|
+
}
|
|
1288
|
+
function resolveAnthropicFetch(options) {
|
|
1289
|
+
return options.fetchImplementation ?? fetch;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/gemini-adapter.ts
|
|
1293
|
+
var import_zod6 = require("zod");
|
|
1294
|
+
var import_errors20 = require("@assemble-dev/shared-types/errors");
|
|
1295
|
+
var import_errors21 = require("@assemble-dev/shared-utils/errors");
|
|
1296
|
+
var defaultGeminiBaseUrl = "https://generativelanguage.googleapis.com";
|
|
1297
|
+
var GeminiGenerateContentResponseSchema = import_zod6.z.object({
|
|
1298
|
+
candidates: import_zod6.z.array(
|
|
1299
|
+
import_zod6.z.object({
|
|
1300
|
+
content: import_zod6.z.object({
|
|
1301
|
+
parts: import_zod6.z.array(import_zod6.z.object({ text: import_zod6.z.string() }))
|
|
1302
|
+
})
|
|
1303
|
+
})
|
|
1304
|
+
).optional(),
|
|
1305
|
+
usageMetadata: import_zod6.z.object({
|
|
1306
|
+
promptTokenCount: import_zod6.z.number().int().nonnegative().optional(),
|
|
1307
|
+
candidatesTokenCount: import_zod6.z.number().int().nonnegative().optional(),
|
|
1308
|
+
totalTokenCount: import_zod6.z.number().int().nonnegative().optional()
|
|
1309
|
+
}).optional()
|
|
1310
|
+
});
|
|
1311
|
+
var structuredOutputSystemMessage3 = {
|
|
1312
|
+
role: "system",
|
|
1313
|
+
content: "Respond with a single JSON object that satisfies the caller's requested structure. Output only valid JSON with no surrounding text."
|
|
1314
|
+
};
|
|
1315
|
+
function gemini(options) {
|
|
1316
|
+
return {
|
|
1317
|
+
name: `gemini:${options.model}`,
|
|
1318
|
+
async generateChatCompletion(request) {
|
|
1319
|
+
ensureGeminiRequestHasNoTools(request);
|
|
1320
|
+
return await executeGeminiAdapterCall(options, {
|
|
1321
|
+
messages: request.messages,
|
|
1322
|
+
temperature: request.temperature ?? options.temperature,
|
|
1323
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
1324
|
+
requireJsonResponse: false,
|
|
1325
|
+
abortSignal: request.abortSignal
|
|
1326
|
+
});
|
|
1327
|
+
},
|
|
1328
|
+
async generateStructuredOutput(request) {
|
|
1329
|
+
const completion = await executeGeminiAdapterCall(options, {
|
|
1330
|
+
messages: [structuredOutputSystemMessage3, ...request.messages],
|
|
1331
|
+
temperature: request.temperature ?? options.temperature,
|
|
1332
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
1333
|
+
requireJsonResponse: true,
|
|
1334
|
+
abortSignal: request.abortSignal
|
|
1335
|
+
});
|
|
1336
|
+
return {
|
|
1337
|
+
value: parseStructuredJsonText(completion.content, request.schema),
|
|
1338
|
+
tokenUsage: completion.tokenUsage,
|
|
1339
|
+
latencyMs: completion.latencyMs
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
async function executeGeminiAdapterCall(options, input) {
|
|
1345
|
+
return await executeProviderCallWithModelFallback(
|
|
1346
|
+
options.fallbackModel,
|
|
1347
|
+
async () => await executeProviderCallWithRetry(
|
|
1348
|
+
options.retry,
|
|
1349
|
+
async () => await requestGeminiGenerateContent(options, input)
|
|
1350
|
+
),
|
|
1351
|
+
async (fallbackModelId) => await requestGeminiGenerateContent(
|
|
1352
|
+
{ ...options, model: fallbackModelId },
|
|
1353
|
+
input
|
|
1354
|
+
)
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
async function requestGeminiGenerateContent(options, input) {
|
|
1358
|
+
const apiKey = resolveProviderApiKey({
|
|
1359
|
+
providerLabel: "Gemini",
|
|
1360
|
+
adapterFunctionName: "gemini()",
|
|
1361
|
+
baseEnvVarName: "GOOGLE_API_KEY",
|
|
1362
|
+
apiKey: options.apiKey,
|
|
1363
|
+
apiKeyName: options.apiKeyName
|
|
1364
|
+
});
|
|
1365
|
+
const fetchImplementation = options.fetchImplementation ?? fetch;
|
|
1366
|
+
const baseUrl = options.baseURL ?? defaultGeminiBaseUrl;
|
|
1367
|
+
const requestBody = buildGeminiRequestBody(input);
|
|
1368
|
+
const requestUrl = `${baseUrl}/v1beta/models/${options.model}:generateContent`;
|
|
1369
|
+
const startedAt = Date.now();
|
|
1370
|
+
const response = await executeAbortableProviderFetch(
|
|
1371
|
+
"Gemini",
|
|
1372
|
+
async () => await fetchImplementation(requestUrl, {
|
|
1373
|
+
method: "POST",
|
|
1374
|
+
headers: {
|
|
1375
|
+
"content-type": "application/json",
|
|
1376
|
+
"x-goog-api-key": apiKey
|
|
1377
|
+
},
|
|
1378
|
+
body: JSON.stringify(requestBody),
|
|
1379
|
+
signal: input.abortSignal
|
|
1380
|
+
})
|
|
1381
|
+
);
|
|
1382
|
+
const latencyMs = Date.now() - startedAt;
|
|
1383
|
+
if (!response.ok) {
|
|
1384
|
+
throw buildProviderStatusError("Gemini", response.status);
|
|
1385
|
+
}
|
|
1386
|
+
const parseResult = GeminiGenerateContentResponseSchema.safeParse(
|
|
1387
|
+
await response.json()
|
|
1388
|
+
);
|
|
1389
|
+
if (!parseResult.success) {
|
|
1390
|
+
throw buildProviderInvalidResponseError("Gemini", "generateContent");
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
content: extractGeminiCandidateText(parseResult.data),
|
|
1394
|
+
tokenUsage: mapGeminiUsageToTokenUsage(parseResult.data),
|
|
1395
|
+
latencyMs
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
function buildGeminiRequestBody(input) {
|
|
1399
|
+
const systemText = joinSystemMessageText2(input.messages);
|
|
1400
|
+
const generationConfig = buildGeminiGenerationConfig(input);
|
|
1401
|
+
const requestBody = {
|
|
1402
|
+
contents: input.messages.filter((message) => message.role !== "system").map(mapChatMessageToGeminiContent)
|
|
1403
|
+
};
|
|
1404
|
+
if (systemText.length > 0) {
|
|
1405
|
+
requestBody.systemInstruction = { parts: [{ text: systemText }] };
|
|
1406
|
+
}
|
|
1407
|
+
if (generationConfig !== void 0) {
|
|
1408
|
+
requestBody.generationConfig = generationConfig;
|
|
1409
|
+
}
|
|
1410
|
+
return requestBody;
|
|
1411
|
+
}
|
|
1412
|
+
function ensureGeminiRequestHasNoTools(request) {
|
|
1413
|
+
if (request.tools === void 0 || request.tools.length === 0) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
throw new import_errors21.AppError({
|
|
1417
|
+
code: import_errors20.AppErrorCode.ValidationFailed,
|
|
1418
|
+
message: "gemini adapter does not support model-driven tool use; remove tools from the request or use another adapter",
|
|
1419
|
+
statusCode: 400
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
function joinSystemMessageText2(messages) {
|
|
1423
|
+
return messages.filter((message) => message.role === "system").map(extractMessageText).join("\n\n");
|
|
1424
|
+
}
|
|
1425
|
+
function mapChatMessageToGeminiContent(message) {
|
|
1426
|
+
const role = message.role === "assistant" ? "model" : "user";
|
|
1427
|
+
return { role, parts: mapMessageContentToGeminiParts(message) };
|
|
1428
|
+
}
|
|
1429
|
+
function mapMessageContentToGeminiParts(message) {
|
|
1430
|
+
if (typeof message.content === "string") {
|
|
1431
|
+
return [{ text: message.content }];
|
|
1432
|
+
}
|
|
1433
|
+
return message.content.map(mapMessageContentBlockToGeminiPart);
|
|
1434
|
+
}
|
|
1435
|
+
function mapMessageContentBlockToGeminiPart(contentBlock) {
|
|
1436
|
+
if (contentBlock.type === "text") {
|
|
1437
|
+
return { text: contentBlock.text };
|
|
1438
|
+
}
|
|
1439
|
+
return {
|
|
1440
|
+
inline_data: {
|
|
1441
|
+
mime_type: contentBlock.mediaType,
|
|
1442
|
+
data: contentBlock.dataBase64
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
function buildGeminiGenerationConfig(input) {
|
|
1447
|
+
const generationConfig = {};
|
|
1448
|
+
if (input.temperature !== void 0) {
|
|
1449
|
+
generationConfig.temperature = input.temperature;
|
|
1450
|
+
}
|
|
1451
|
+
if (input.maxOutputTokens !== void 0) {
|
|
1452
|
+
generationConfig.maxOutputTokens = input.maxOutputTokens;
|
|
1453
|
+
}
|
|
1454
|
+
if (input.requireJsonResponse) {
|
|
1455
|
+
generationConfig.responseMimeType = "application/json";
|
|
1456
|
+
}
|
|
1457
|
+
if (Object.keys(generationConfig).length === 0) {
|
|
1458
|
+
return void 0;
|
|
1459
|
+
}
|
|
1460
|
+
return generationConfig;
|
|
1461
|
+
}
|
|
1462
|
+
function extractGeminiCandidateText(response) {
|
|
1463
|
+
const firstCandidate = response.candidates?.[0];
|
|
1464
|
+
if (firstCandidate === void 0) {
|
|
1465
|
+
throw new import_errors21.AppError({
|
|
1466
|
+
code: import_errors20.AppErrorCode.ValidationFailed,
|
|
1467
|
+
message: "Gemini response contained no candidates",
|
|
1468
|
+
statusCode: 502
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
return firstCandidate.content.parts.map((part) => part.text).join("");
|
|
1472
|
+
}
|
|
1473
|
+
function mapGeminiUsageToTokenUsage(response) {
|
|
1474
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
1475
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
1476
|
+
const totalTokens = response.usageMetadata?.totalTokenCount ?? inputTokens + outputTokens;
|
|
1477
|
+
return {
|
|
1478
|
+
inputTokens,
|
|
1479
|
+
outputTokens,
|
|
1480
|
+
totalTokens
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/mock-adapter.ts
|
|
1485
|
+
var import_errors22 = require("@assemble-dev/shared-types/errors");
|
|
1486
|
+
var import_errors23 = require("@assemble-dev/shared-utils/errors");
|
|
1487
|
+
var mockStreamChunkCount = 3;
|
|
1488
|
+
function mockModel(options = {}) {
|
|
1489
|
+
const remainingScriptedResponses = [...options.scriptedResponses ?? []];
|
|
1490
|
+
const adapterName = options.name ?? "mock";
|
|
1491
|
+
return {
|
|
1492
|
+
name: adapterName,
|
|
1493
|
+
async generateChatCompletion(request) {
|
|
1494
|
+
return await resolveMockChatCompletion(
|
|
1495
|
+
request,
|
|
1496
|
+
remainingScriptedResponses,
|
|
1497
|
+
options.respond
|
|
1498
|
+
);
|
|
1499
|
+
},
|
|
1500
|
+
async generateStructuredOutput(request) {
|
|
1501
|
+
const completion = await resolveMockChatCompletion(
|
|
1502
|
+
{
|
|
1503
|
+
messages: request.messages,
|
|
1504
|
+
temperature: request.temperature,
|
|
1505
|
+
maxOutputTokens: request.maxOutputTokens
|
|
1506
|
+
},
|
|
1507
|
+
remainingScriptedResponses,
|
|
1508
|
+
options.respond
|
|
1509
|
+
);
|
|
1510
|
+
return {
|
|
1511
|
+
value: parseStructuredJsonText(completion.content, request.schema),
|
|
1512
|
+
tokenUsage: completion.tokenUsage,
|
|
1513
|
+
latencyMs: completion.latencyMs
|
|
1514
|
+
};
|
|
1515
|
+
},
|
|
1516
|
+
streamChatCompletion(request) {
|
|
1517
|
+
return streamMockChatCompletion(
|
|
1518
|
+
request,
|
|
1519
|
+
remainingScriptedResponses,
|
|
1520
|
+
options.respond
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
async function* streamMockChatCompletion(request, remainingScriptedResponses, respond) {
|
|
1526
|
+
const completion = await resolveMockChatCompletion(
|
|
1527
|
+
request,
|
|
1528
|
+
remainingScriptedResponses,
|
|
1529
|
+
respond
|
|
1530
|
+
);
|
|
1531
|
+
for (const chunkText of splitTextIntoStreamChunks(completion.content)) {
|
|
1532
|
+
yield { type: "text_delta", text: chunkText };
|
|
1533
|
+
}
|
|
1534
|
+
yield { type: "completed", result: completion };
|
|
1535
|
+
}
|
|
1536
|
+
function splitTextIntoStreamChunks(text) {
|
|
1537
|
+
if (text.length === 0) {
|
|
1538
|
+
return [];
|
|
1539
|
+
}
|
|
1540
|
+
const chunkSize = Math.max(1, Math.ceil(text.length / mockStreamChunkCount));
|
|
1541
|
+
const chunks = [];
|
|
1542
|
+
for (let startIndex = 0; startIndex < text.length; startIndex += chunkSize) {
|
|
1543
|
+
chunks.push(text.slice(startIndex, startIndex + chunkSize));
|
|
1544
|
+
}
|
|
1545
|
+
return chunks;
|
|
1546
|
+
}
|
|
1547
|
+
async function resolveMockChatCompletion(request, remainingScriptedResponses, respond) {
|
|
1548
|
+
const startedAt = Date.now();
|
|
1549
|
+
const content = await Promise.resolve(
|
|
1550
|
+
resolveNextMockResponseText(request, remainingScriptedResponses, respond)
|
|
1551
|
+
);
|
|
1552
|
+
return {
|
|
1553
|
+
content,
|
|
1554
|
+
tokenUsage: calculateMockTokenUsage(request.messages, content),
|
|
1555
|
+
latencyMs: Date.now() - startedAt
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
function resolveNextMockResponseText(request, remainingScriptedResponses, respond) {
|
|
1559
|
+
const nextScriptedResponse = remainingScriptedResponses.shift();
|
|
1560
|
+
if (nextScriptedResponse !== void 0) {
|
|
1561
|
+
return nextScriptedResponse;
|
|
1562
|
+
}
|
|
1563
|
+
if (respond !== void 0) {
|
|
1564
|
+
return respond(request);
|
|
1565
|
+
}
|
|
1566
|
+
throw new import_errors23.AppError({
|
|
1567
|
+
code: import_errors22.AppErrorCode.BadRequest,
|
|
1568
|
+
message: "Mock model has no scripted responses left and no respond function was provided",
|
|
1569
|
+
statusCode: 400
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
function calculateMockTokenUsage(messages, outputText) {
|
|
1573
|
+
const inputCharacterCount = messages.reduce(
|
|
1574
|
+
(characterCount, message) => characterCount + extractMessageText(message).length,
|
|
1575
|
+
0
|
|
1576
|
+
);
|
|
1577
|
+
const inputTokens = Math.max(1, Math.ceil(inputCharacterCount / 4));
|
|
1578
|
+
const outputTokens = Math.max(1, Math.ceil(outputText.length / 4));
|
|
1579
|
+
return {
|
|
1580
|
+
inputTokens,
|
|
1581
|
+
outputTokens,
|
|
1582
|
+
totalTokens: inputTokens + outputTokens
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// src/provider-pricing.ts
|
|
1587
|
+
var modelPricingByModelIdPrefix = {
|
|
1588
|
+
"claude-sonnet-4": {
|
|
1589
|
+
inputUsdPerMillionTokens: 3,
|
|
1590
|
+
outputUsdPerMillionTokens: 15,
|
|
1591
|
+
cacheWriteUsdPerMillionTokens: 3.75,
|
|
1592
|
+
cacheReadUsdPerMillionTokens: 0.3
|
|
1593
|
+
},
|
|
1594
|
+
"claude-haiku-4": {
|
|
1595
|
+
inputUsdPerMillionTokens: 1,
|
|
1596
|
+
outputUsdPerMillionTokens: 5,
|
|
1597
|
+
cacheWriteUsdPerMillionTokens: 1.25,
|
|
1598
|
+
cacheReadUsdPerMillionTokens: 0.1
|
|
1599
|
+
},
|
|
1600
|
+
"claude-opus-4": {
|
|
1601
|
+
inputUsdPerMillionTokens: 15,
|
|
1602
|
+
outputUsdPerMillionTokens: 75,
|
|
1603
|
+
cacheWriteUsdPerMillionTokens: 18.75,
|
|
1604
|
+
cacheReadUsdPerMillionTokens: 1.5
|
|
1605
|
+
},
|
|
1606
|
+
"gpt-4o-mini": {
|
|
1607
|
+
inputUsdPerMillionTokens: 0.15,
|
|
1608
|
+
outputUsdPerMillionTokens: 0.6
|
|
1609
|
+
},
|
|
1610
|
+
"gpt-4o": {
|
|
1611
|
+
inputUsdPerMillionTokens: 2.5,
|
|
1612
|
+
outputUsdPerMillionTokens: 10
|
|
1613
|
+
},
|
|
1614
|
+
"gemini-2.0-flash": {
|
|
1615
|
+
inputUsdPerMillionTokens: 0.1,
|
|
1616
|
+
outputUsdPerMillionTokens: 0.4
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
function estimateModelCallCostUsd(modelName, tokenUsage) {
|
|
1620
|
+
const modelId = stripAdapterPrefixFromModelName(modelName);
|
|
1621
|
+
const pricing = findModelPricingByLongestPrefix(modelId);
|
|
1622
|
+
if (pricing === null) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
return calculateModelCallCostUsd(pricing, tokenUsage);
|
|
1626
|
+
}
|
|
1627
|
+
function stripAdapterPrefixFromModelName(modelName) {
|
|
1628
|
+
const separatorIndex = modelName.indexOf(":");
|
|
1629
|
+
if (separatorIndex === -1) {
|
|
1630
|
+
return modelName;
|
|
1631
|
+
}
|
|
1632
|
+
return modelName.slice(separatorIndex + 1);
|
|
1633
|
+
}
|
|
1634
|
+
function findModelPricingByLongestPrefix(modelId) {
|
|
1635
|
+
const matchingPrefixes = Object.keys(modelPricingByModelIdPrefix).filter((modelIdPrefix) => modelId.startsWith(modelIdPrefix)).sort(
|
|
1636
|
+
(firstPrefix, secondPrefix) => secondPrefix.length - firstPrefix.length
|
|
1637
|
+
);
|
|
1638
|
+
const longestMatchingPrefix = matchingPrefixes[0];
|
|
1639
|
+
if (longestMatchingPrefix === void 0) {
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
return modelPricingByModelIdPrefix[longestMatchingPrefix] ?? null;
|
|
1643
|
+
}
|
|
1644
|
+
function calculateModelCallCostUsd(pricing, tokenUsage) {
|
|
1645
|
+
const cacheWriteRate = pricing.cacheWriteUsdPerMillionTokens ?? pricing.inputUsdPerMillionTokens;
|
|
1646
|
+
const cacheReadRate = pricing.cacheReadUsdPerMillionTokens ?? pricing.inputUsdPerMillionTokens;
|
|
1647
|
+
const inputCostUsd = tokenUsage.inputTokens * pricing.inputUsdPerMillionTokens;
|
|
1648
|
+
const outputCostUsd = tokenUsage.outputTokens * pricing.outputUsdPerMillionTokens;
|
|
1649
|
+
const cacheWriteCostUsd = (tokenUsage.cacheCreationInputTokens ?? 0) * cacheWriteRate;
|
|
1650
|
+
const cacheReadCostUsd = (tokenUsage.cacheReadInputTokens ?? 0) * cacheReadRate;
|
|
1651
|
+
return (inputCostUsd + outputCostUsd + cacheWriteCostUsd + cacheReadCostUsd) / 1e6;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/anthropic-batch.ts
|
|
1655
|
+
var import_errors24 = require("@assemble-dev/shared-types/errors");
|
|
1656
|
+
var import_json6 = require("@assemble-dev/shared-types/json");
|
|
1657
|
+
var import_errors25 = require("@assemble-dev/shared-utils/errors");
|
|
1658
|
+
|
|
1659
|
+
// src/anthropic-batch-types.ts
|
|
1660
|
+
var import_zod7 = require("zod");
|
|
1661
|
+
var AnthropicMessageBatchResponseSchema = import_zod7.z.object({
|
|
1662
|
+
id: import_zod7.z.string(),
|
|
1663
|
+
processing_status: import_zod7.z.enum(["in_progress", "canceling", "ended"]),
|
|
1664
|
+
request_counts: import_zod7.z.object({
|
|
1665
|
+
processing: import_zod7.z.number().int().nonnegative(),
|
|
1666
|
+
succeeded: import_zod7.z.number().int().nonnegative(),
|
|
1667
|
+
errored: import_zod7.z.number().int().nonnegative(),
|
|
1668
|
+
canceled: import_zod7.z.number().int().nonnegative(),
|
|
1669
|
+
expired: import_zod7.z.number().int().nonnegative()
|
|
1670
|
+
}),
|
|
1671
|
+
results_url: import_zod7.z.string().nullable(),
|
|
1672
|
+
created_at: import_zod7.z.string(),
|
|
1673
|
+
ended_at: import_zod7.z.string().nullable()
|
|
1674
|
+
});
|
|
1675
|
+
var AnthropicBatchContentBlockSchema = import_zod7.z.discriminatedUnion("type", [
|
|
1676
|
+
import_zod7.z.object({
|
|
1677
|
+
type: import_zod7.z.literal("text"),
|
|
1678
|
+
text: import_zod7.z.string()
|
|
1679
|
+
}),
|
|
1680
|
+
import_zod7.z.object({
|
|
1681
|
+
type: import_zod7.z.literal("thinking"),
|
|
1682
|
+
thinking: import_zod7.z.string().optional(),
|
|
1683
|
+
signature: import_zod7.z.string().optional()
|
|
1684
|
+
}),
|
|
1685
|
+
import_zod7.z.object({
|
|
1686
|
+
type: import_zod7.z.literal("redacted_thinking"),
|
|
1687
|
+
data: import_zod7.z.string().optional()
|
|
1688
|
+
})
|
|
1689
|
+
]);
|
|
1690
|
+
var AnthropicBatchResultLineSchema = import_zod7.z.object({
|
|
1691
|
+
custom_id: import_zod7.z.string(),
|
|
1692
|
+
result: import_zod7.z.discriminatedUnion("type", [
|
|
1693
|
+
import_zod7.z.object({
|
|
1694
|
+
type: import_zod7.z.literal("succeeded"),
|
|
1695
|
+
message: import_zod7.z.object({
|
|
1696
|
+
content: import_zod7.z.array(AnthropicBatchContentBlockSchema),
|
|
1697
|
+
usage: AnthropicUsageSchema
|
|
1698
|
+
})
|
|
1699
|
+
}),
|
|
1700
|
+
import_zod7.z.object({
|
|
1701
|
+
type: import_zod7.z.literal("errored"),
|
|
1702
|
+
error: import_zod7.z.object({
|
|
1703
|
+
type: import_zod7.z.string(),
|
|
1704
|
+
message: import_zod7.z.string()
|
|
1705
|
+
})
|
|
1706
|
+
}),
|
|
1707
|
+
import_zod7.z.object({ type: import_zod7.z.literal("canceled") }),
|
|
1708
|
+
import_zod7.z.object({ type: import_zod7.z.literal("expired") })
|
|
1709
|
+
])
|
|
1710
|
+
});
|
|
1711
|
+
function mapAnthropicMessageBatchResponseToMessageBatch(response) {
|
|
1712
|
+
return {
|
|
1713
|
+
id: response.id,
|
|
1714
|
+
processingStatus: response.processing_status,
|
|
1715
|
+
requestCounts: {
|
|
1716
|
+
processing: response.request_counts.processing,
|
|
1717
|
+
succeeded: response.request_counts.succeeded,
|
|
1718
|
+
errored: response.request_counts.errored,
|
|
1719
|
+
canceled: response.request_counts.canceled,
|
|
1720
|
+
expired: response.request_counts.expired
|
|
1721
|
+
},
|
|
1722
|
+
resultsUrl: response.results_url,
|
|
1723
|
+
createdAt: response.created_at,
|
|
1724
|
+
endedAt: response.ended_at
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function mapAnthropicBatchResultLineToBatchResult(resultLine) {
|
|
1728
|
+
return {
|
|
1729
|
+
customId: resultLine.custom_id,
|
|
1730
|
+
outcome: mapAnthropicBatchResultToOutcome(resultLine.result)
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function mapAnthropicBatchResultToOutcome(result) {
|
|
1734
|
+
if (result.type === "succeeded") {
|
|
1735
|
+
return {
|
|
1736
|
+
type: "succeeded",
|
|
1737
|
+
content: joinAnthropicBatchTextBlocks(result.message.content),
|
|
1738
|
+
tokenUsage: mapAnthropicUsageToTokenUsage(result.message.usage)
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
if (result.type === "errored") {
|
|
1742
|
+
return {
|
|
1743
|
+
type: "errored",
|
|
1744
|
+
errorType: result.error.type,
|
|
1745
|
+
errorMessage: result.error.message
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
if (result.type === "canceled") {
|
|
1749
|
+
return { type: "canceled" };
|
|
1750
|
+
}
|
|
1751
|
+
return { type: "expired" };
|
|
1752
|
+
}
|
|
1753
|
+
function joinAnthropicBatchTextBlocks(contentBlocks) {
|
|
1754
|
+
return contentBlocks.filter((contentBlock) => contentBlock.type === "text").map((contentBlock) => contentBlock.text).join("");
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// src/anthropic-batch.ts
|
|
1758
|
+
var defaultAnthropicBatchPollIntervalMs = 5e3;
|
|
1759
|
+
var defaultAnthropicBatchTimeoutMs = 36e5;
|
|
1760
|
+
var anthropicBatchApiVersion = "2023-06-01";
|
|
1761
|
+
function createAnthropicBatchClient(options) {
|
|
1762
|
+
return {
|
|
1763
|
+
async submitMessageBatch(requests) {
|
|
1764
|
+
return await submitAnthropicMessageBatch(options, requests);
|
|
1765
|
+
},
|
|
1766
|
+
async fetchMessageBatch(batchId) {
|
|
1767
|
+
return await fetchAnthropicMessageBatch(options, batchId);
|
|
1768
|
+
},
|
|
1769
|
+
async listMessageBatchResults(batchId) {
|
|
1770
|
+
return await listAnthropicMessageBatchResults(options, batchId);
|
|
1771
|
+
},
|
|
1772
|
+
async waitForMessageBatchCompletion(batchId, waitOptions) {
|
|
1773
|
+
return await waitForAnthropicMessageBatchCompletion(
|
|
1774
|
+
options,
|
|
1775
|
+
batchId,
|
|
1776
|
+
waitOptions
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
function resolveAnthropicBatchRequestContext(options) {
|
|
1782
|
+
return {
|
|
1783
|
+
apiKey: resolveProviderApiKey({
|
|
1784
|
+
providerLabel: "Anthropic",
|
|
1785
|
+
adapterFunctionName: "createAnthropicBatchClient()",
|
|
1786
|
+
baseEnvVarName: "ANTHROPIC_API_KEY",
|
|
1787
|
+
apiKey: options.apiKey,
|
|
1788
|
+
apiKeyName: options.apiKeyName
|
|
1789
|
+
}),
|
|
1790
|
+
baseUrl: options.baseURL ?? defaultAnthropicBaseUrl,
|
|
1791
|
+
fetchImplementation: options.fetchImplementation ?? fetch
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
function buildAnthropicBatchRequestHeaders(apiKey) {
|
|
1795
|
+
return {
|
|
1796
|
+
"content-type": "application/json",
|
|
1797
|
+
"x-api-key": apiKey,
|
|
1798
|
+
"anthropic-version": anthropicBatchApiVersion
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
async function submitAnthropicMessageBatch(options, requests) {
|
|
1802
|
+
const context = resolveAnthropicBatchRequestContext(options);
|
|
1803
|
+
const response = await context.fetchImplementation(
|
|
1804
|
+
`${context.baseUrl}/v1/messages/batches`,
|
|
1805
|
+
{
|
|
1806
|
+
method: "POST",
|
|
1807
|
+
headers: buildAnthropicBatchRequestHeaders(context.apiKey),
|
|
1808
|
+
body: JSON.stringify({
|
|
1809
|
+
requests: requests.map(mapBatchMessageRequestToApiBatchRequest)
|
|
1810
|
+
})
|
|
1811
|
+
}
|
|
1812
|
+
);
|
|
1813
|
+
return await parseAnthropicMessageBatchHttpResponse(response);
|
|
1814
|
+
}
|
|
1815
|
+
async function fetchAnthropicMessageBatch(options, batchId) {
|
|
1816
|
+
const context = resolveAnthropicBatchRequestContext(options);
|
|
1817
|
+
const response = await context.fetchImplementation(
|
|
1818
|
+
`${context.baseUrl}/v1/messages/batches/${batchId}`,
|
|
1819
|
+
{
|
|
1820
|
+
method: "GET",
|
|
1821
|
+
headers: buildAnthropicBatchRequestHeaders(context.apiKey)
|
|
1822
|
+
}
|
|
1823
|
+
);
|
|
1824
|
+
return await parseAnthropicMessageBatchHttpResponse(response);
|
|
1825
|
+
}
|
|
1826
|
+
async function parseAnthropicMessageBatchHttpResponse(response) {
|
|
1827
|
+
if (!response.ok) {
|
|
1828
|
+
throw buildProviderStatusError("Anthropic", response.status);
|
|
1829
|
+
}
|
|
1830
|
+
const parseResult = AnthropicMessageBatchResponseSchema.safeParse(
|
|
1831
|
+
await response.json()
|
|
1832
|
+
);
|
|
1833
|
+
if (!parseResult.success) {
|
|
1834
|
+
throw buildProviderInvalidResponseError("Anthropic", "message batch");
|
|
1835
|
+
}
|
|
1836
|
+
return mapAnthropicMessageBatchResponseToMessageBatch(parseResult.data);
|
|
1837
|
+
}
|
|
1838
|
+
async function listAnthropicMessageBatchResults(options, batchId) {
|
|
1839
|
+
const batch = await fetchAnthropicMessageBatch(options, batchId);
|
|
1840
|
+
if (batch.resultsUrl === null) {
|
|
1841
|
+
throw new import_errors25.AppError({
|
|
1842
|
+
code: import_errors24.AppErrorCode.Conflict,
|
|
1843
|
+
message: `Anthropic message batch ${batchId} has no results because processing has not ended`,
|
|
1844
|
+
statusCode: 409,
|
|
1845
|
+
details: { batchId, processingStatus: batch.processingStatus }
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
const context = resolveAnthropicBatchRequestContext(options);
|
|
1849
|
+
const response = await context.fetchImplementation(batch.resultsUrl, {
|
|
1850
|
+
method: "GET",
|
|
1851
|
+
headers: buildAnthropicBatchRequestHeaders(context.apiKey)
|
|
1852
|
+
});
|
|
1853
|
+
if (!response.ok) {
|
|
1854
|
+
throw buildProviderStatusError("Anthropic", response.status);
|
|
1855
|
+
}
|
|
1856
|
+
return parseAnthropicBatchResultsJsonl(await response.text());
|
|
1857
|
+
}
|
|
1858
|
+
function parseAnthropicBatchResultsJsonl(jsonlText) {
|
|
1859
|
+
return jsonlText.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map(parseAnthropicBatchResultLine);
|
|
1860
|
+
}
|
|
1861
|
+
function parseAnthropicBatchResultLine(line) {
|
|
1862
|
+
const parseResult = AnthropicBatchResultLineSchema.safeParse(
|
|
1863
|
+
parseAnthropicBatchResultLineJson(line)
|
|
1864
|
+
);
|
|
1865
|
+
if (!parseResult.success) {
|
|
1866
|
+
throw buildProviderInvalidResponseError(
|
|
1867
|
+
"Anthropic",
|
|
1868
|
+
"message batch results"
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
return mapAnthropicBatchResultLineToBatchResult(parseResult.data);
|
|
1872
|
+
}
|
|
1873
|
+
function parseAnthropicBatchResultLineJson(line) {
|
|
1874
|
+
try {
|
|
1875
|
+
return import_json6.JsonValueSchema.parse(JSON.parse(line));
|
|
1876
|
+
} catch {
|
|
1877
|
+
throw buildProviderInvalidResponseError(
|
|
1878
|
+
"Anthropic",
|
|
1879
|
+
"message batch results"
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
async function waitForAnthropicMessageBatchCompletion(options, batchId, waitOptions) {
|
|
1884
|
+
const pollIntervalMs = waitOptions?.pollIntervalMs ?? defaultAnthropicBatchPollIntervalMs;
|
|
1885
|
+
const timeoutMs = waitOptions?.timeoutMs ?? defaultAnthropicBatchTimeoutMs;
|
|
1886
|
+
const waitFor = waitOptions?.waitFor ?? waitForMilliseconds;
|
|
1887
|
+
const startedAt = Date.now();
|
|
1888
|
+
let batch = await fetchAnthropicMessageBatch(options, batchId);
|
|
1889
|
+
while (batch.processingStatus !== "ended") {
|
|
1890
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
1891
|
+
throw buildAnthropicBatchTimeoutError(batchId, timeoutMs);
|
|
1892
|
+
}
|
|
1893
|
+
await waitFor(pollIntervalMs);
|
|
1894
|
+
batch = await fetchAnthropicMessageBatch(options, batchId);
|
|
1895
|
+
}
|
|
1896
|
+
return batch;
|
|
1897
|
+
}
|
|
1898
|
+
function buildAnthropicBatchTimeoutError(batchId, timeoutMs) {
|
|
1899
|
+
return new import_errors25.AppError({
|
|
1900
|
+
code: import_errors24.AppErrorCode.ServiceUnavailable,
|
|
1901
|
+
message: `Anthropic message batch ${batchId} did not complete within ${String(timeoutMs)}ms`,
|
|
1902
|
+
statusCode: 504,
|
|
1903
|
+
details: { batchId, timeoutMs }
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
function mapBatchMessageRequestToApiBatchRequest(request) {
|
|
1907
|
+
const params = {
|
|
1908
|
+
model: request.model,
|
|
1909
|
+
max_tokens: request.maxOutputTokens ?? defaultAnthropicMaxOutputTokens,
|
|
1910
|
+
messages: request.messages.filter((message) => message.role !== "system").map(mapChatMessageToBatchApiRequestMessage)
|
|
1911
|
+
};
|
|
1912
|
+
const systemText = joinBatchSystemMessageText(request.messages);
|
|
1913
|
+
if (systemText.length > 0) {
|
|
1914
|
+
params.system = systemText;
|
|
1915
|
+
}
|
|
1916
|
+
if (request.temperature !== void 0) {
|
|
1917
|
+
params.temperature = request.temperature;
|
|
1918
|
+
}
|
|
1919
|
+
return { custom_id: request.customId, params };
|
|
1920
|
+
}
|
|
1921
|
+
function joinBatchSystemMessageText(messages) {
|
|
1922
|
+
return messages.filter((message) => message.role === "system").map(extractMessageText).join("\n\n");
|
|
1923
|
+
}
|
|
1924
|
+
function mapChatMessageToBatchApiRequestMessage(message) {
|
|
1925
|
+
if (message.role === "assistant") {
|
|
1926
|
+
return { role: "assistant", content: extractMessageText(message) };
|
|
1927
|
+
}
|
|
1928
|
+
if (message.role === "tool") {
|
|
1929
|
+
return { role: "user", content: buildBatchToolResultText(message) };
|
|
1930
|
+
}
|
|
1931
|
+
return { role: "user", content: extractMessageText(message) };
|
|
1932
|
+
}
|
|
1933
|
+
function buildBatchToolResultText(message) {
|
|
1934
|
+
if (message.toolName === void 0) {
|
|
1935
|
+
return `Tool result: ${extractMessageText(message)}`;
|
|
1936
|
+
}
|
|
1937
|
+
return `Tool result (${message.toolName}): ${extractMessageText(message)}`;
|
|
1938
|
+
}
|
|
1939
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1940
|
+
0 && (module.exports = {
|
|
1941
|
+
AnthropicMessagesResponseSchema,
|
|
1942
|
+
AnthropicStreamEventSchema,
|
|
1943
|
+
ChatMessageRoleSchema,
|
|
1944
|
+
ChatMessageSchema,
|
|
1945
|
+
ChatStopReasonSchema,
|
|
1946
|
+
MessageContentBlockSchema,
|
|
1947
|
+
ModelToolCallSchema,
|
|
1948
|
+
anthropic,
|
|
1949
|
+
buildAnthropicRequestBody,
|
|
1950
|
+
buildOpenAiRequestBody,
|
|
1951
|
+
buildProviderCanceledError,
|
|
1952
|
+
calculateProviderRetryDelayMs,
|
|
1953
|
+
createAnthropicBatchClient,
|
|
1954
|
+
defaultAnthropicBaseUrl,
|
|
1955
|
+
defaultAnthropicBatchPollIntervalMs,
|
|
1956
|
+
defaultAnthropicBatchTimeoutMs,
|
|
1957
|
+
defaultAnthropicMaxOutputTokens,
|
|
1958
|
+
defaultGeminiBaseUrl,
|
|
1959
|
+
defaultOpenAiBaseUrl,
|
|
1960
|
+
defaultProviderRetryInitialDelayMs,
|
|
1961
|
+
defaultProviderRetryMaxAttempts,
|
|
1962
|
+
defaultProviderRetryMaxDelayMs,
|
|
1963
|
+
estimateModelCallCostUsd,
|
|
1964
|
+
executeAbortableProviderFetch,
|
|
1965
|
+
executeProviderCallWithModelFallback,
|
|
1966
|
+
executeProviderCallWithRetry,
|
|
1967
|
+
extractAnthropicTextContent,
|
|
1968
|
+
extractAnthropicToolCalls,
|
|
1969
|
+
extractMessageText,
|
|
1970
|
+
gemini,
|
|
1971
|
+
isAbortError,
|
|
1972
|
+
isRetryableProviderAppError,
|
|
1973
|
+
joinSystemMessageText,
|
|
1974
|
+
mapAnthropicResponseToChatCompletionResult,
|
|
1975
|
+
mapAnthropicUsageToTokenUsage,
|
|
1976
|
+
mapChatMessageToAnthropicRequestMessage,
|
|
1977
|
+
mapChatMessageToOpenAiRequestMessage,
|
|
1978
|
+
mapModelToolChoiceToAnthropicToolChoice,
|
|
1979
|
+
mapModelToolChoiceToOpenAiToolChoice,
|
|
1980
|
+
mapModelToolDefinitionToOpenAiTool,
|
|
1981
|
+
mapModelToolDefinitionsToAnthropicTools,
|
|
1982
|
+
mockModel,
|
|
1983
|
+
normalizeAnthropicStopReason,
|
|
1984
|
+
openai,
|
|
1985
|
+
parseAnthropicStreamEvents,
|
|
1986
|
+
parseAnthropicToolInputJson,
|
|
1987
|
+
resolveProviderApiKey,
|
|
1988
|
+
streamAnthropicChatCompletion
|
|
1989
|
+
});
|