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