@assemble-dev/providers 0.3.0 → 0.4.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.cjs +566 -32
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +554 -31
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -204,6 +204,9 @@ var openAiUnsupportedSchemaKeywords = [
|
|
|
204
204
|
"uniqueItems",
|
|
205
205
|
"default"
|
|
206
206
|
];
|
|
207
|
+
function isZodV4Schema(schema) {
|
|
208
|
+
return "_zod" in schema;
|
|
209
|
+
}
|
|
207
210
|
function convertZodSchemaToJsonSchema(schema) {
|
|
208
211
|
try {
|
|
209
212
|
return JsonObjectSchema.parse(z2.toJSONSchema(schema, { io: "input" }));
|
|
@@ -268,18 +271,72 @@ function listSchemaPropertyNames(node) {
|
|
|
268
271
|
return Object.keys(properties);
|
|
269
272
|
}
|
|
270
273
|
|
|
274
|
+
// src/structured-output-fallback.ts
|
|
275
|
+
var structuredOutputJsonOnlySystemInstruction = "Respond with a single JSON object that satisfies the caller's requested structure. Output only valid JSON with no surrounding text.";
|
|
276
|
+
var structuredOutputJsonOnlySystemMessage = {
|
|
277
|
+
role: "system",
|
|
278
|
+
content: structuredOutputJsonOnlySystemInstruction
|
|
279
|
+
};
|
|
280
|
+
function prependStructuredOutputJsonOnlySystemMessage(messages) {
|
|
281
|
+
return [structuredOutputJsonOnlySystemMessage, ...messages];
|
|
282
|
+
}
|
|
283
|
+
|
|
271
284
|
// src/openai-request-mapping.ts
|
|
285
|
+
var openAiReasoningModelNamePrefixes = [
|
|
286
|
+
"gpt-5",
|
|
287
|
+
"o1",
|
|
288
|
+
"o3",
|
|
289
|
+
"o4"
|
|
290
|
+
];
|
|
291
|
+
function isOpenAiReasoningModel(model) {
|
|
292
|
+
return openAiReasoningModelNamePrefixes.some(
|
|
293
|
+
(prefix) => matchesOpenAiModelNamePrefix(model, prefix)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
function matchesOpenAiModelNamePrefix(model, prefix) {
|
|
297
|
+
if (!model.startsWith(prefix)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const boundaryCharacter = model.charAt(prefix.length);
|
|
301
|
+
return boundaryCharacter === "" || boundaryCharacter === "-" || boundaryCharacter === ".";
|
|
302
|
+
}
|
|
303
|
+
function resolveOpenAiStructuredOutputMode(messages, schema) {
|
|
304
|
+
if (isZodV4Schema(schema)) {
|
|
305
|
+
return {
|
|
306
|
+
messages,
|
|
307
|
+
structuredOutputJsonSchema: buildOpenAiStructuredOutputJsonSchema(schema)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
messages: prependStructuredOutputJsonOnlySystemMessage(messages),
|
|
312
|
+
requireJsonObjectResponse: true
|
|
313
|
+
};
|
|
314
|
+
}
|
|
272
315
|
function buildOpenAiRequestBody(model, input) {
|
|
273
316
|
const requestBody = {
|
|
274
317
|
model,
|
|
275
318
|
messages: input.messages.map(mapChatMessageToOpenAiRequestMessage)
|
|
276
319
|
};
|
|
277
|
-
|
|
320
|
+
applyOpenAiGenerationSettings(requestBody, model, input);
|
|
321
|
+
applyOpenAiResponseFormat(requestBody, input);
|
|
322
|
+
applyOpenAiToolFields(requestBody, input);
|
|
323
|
+
return requestBody;
|
|
324
|
+
}
|
|
325
|
+
function applyOpenAiGenerationSettings(requestBody, model, input) {
|
|
326
|
+
const reasoningModel = input.reasoningModel ?? isOpenAiReasoningModel(model);
|
|
327
|
+
if (input.temperature !== void 0 && !reasoningModel) {
|
|
278
328
|
requestBody.temperature = input.temperature;
|
|
279
329
|
}
|
|
280
|
-
if (input.maxOutputTokens
|
|
281
|
-
|
|
330
|
+
if (input.maxOutputTokens === void 0) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (reasoningModel) {
|
|
334
|
+
requestBody.max_completion_tokens = input.maxOutputTokens;
|
|
335
|
+
return;
|
|
282
336
|
}
|
|
337
|
+
requestBody.max_tokens = input.maxOutputTokens;
|
|
338
|
+
}
|
|
339
|
+
function applyOpenAiResponseFormat(requestBody, input) {
|
|
283
340
|
if (input.structuredOutputJsonSchema !== void 0) {
|
|
284
341
|
requestBody.response_format = {
|
|
285
342
|
type: "json_schema",
|
|
@@ -289,9 +346,13 @@ function buildOpenAiRequestBody(model, input) {
|
|
|
289
346
|
schema: input.structuredOutputJsonSchema
|
|
290
347
|
}
|
|
291
348
|
};
|
|
292
|
-
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (input.requireJsonObjectResponse === true) {
|
|
293
352
|
requestBody.response_format = { type: "json_object" };
|
|
294
353
|
}
|
|
354
|
+
}
|
|
355
|
+
function applyOpenAiToolFields(requestBody, input) {
|
|
295
356
|
if (input.tools !== void 0 && input.tools.length > 0) {
|
|
296
357
|
requestBody.tools = input.tools.map(mapModelToolDefinitionToOpenAiTool);
|
|
297
358
|
}
|
|
@@ -300,7 +361,6 @@ function buildOpenAiRequestBody(model, input) {
|
|
|
300
361
|
input.toolChoice
|
|
301
362
|
);
|
|
302
363
|
}
|
|
303
|
-
return requestBody;
|
|
304
364
|
}
|
|
305
365
|
function mapModelToolDefinitionToOpenAiTool(tool) {
|
|
306
366
|
return {
|
|
@@ -665,12 +725,9 @@ function openai(options) {
|
|
|
665
725
|
},
|
|
666
726
|
async generateStructuredOutput(request) {
|
|
667
727
|
const completion = await executeOpenAiAdapterCall(options, {
|
|
668
|
-
messages
|
|
728
|
+
...resolveOpenAiStructuredOutputMode(request.messages, request.schema),
|
|
669
729
|
temperature: request.temperature ?? options.temperature,
|
|
670
730
|
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
671
|
-
structuredOutputJsonSchema: buildOpenAiStructuredOutputJsonSchema(
|
|
672
|
-
request.schema
|
|
673
|
-
),
|
|
674
731
|
abortSignal: request.abortSignal
|
|
675
732
|
});
|
|
676
733
|
return {
|
|
@@ -1438,15 +1495,10 @@ function anthropic(options) {
|
|
|
1438
1495
|
);
|
|
1439
1496
|
},
|
|
1440
1497
|
async generateStructuredOutput(request) {
|
|
1441
|
-
const completion = await executeAnthropicAdapterCall(
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
structuredOutputJsonSchema: buildAnthropicStructuredOutputJsonSchema(
|
|
1446
|
-
request.schema
|
|
1447
|
-
),
|
|
1448
|
-
abortSignal: request.abortSignal
|
|
1449
|
-
});
|
|
1498
|
+
const completion = await executeAnthropicAdapterCall(
|
|
1499
|
+
options,
|
|
1500
|
+
buildAnthropicStructuredOutputInput(options, request)
|
|
1501
|
+
);
|
|
1450
1502
|
return {
|
|
1451
1503
|
value: parseStructuredJsonText(completion.content, request.schema),
|
|
1452
1504
|
tokenUsage: completion.tokenUsage,
|
|
@@ -1460,6 +1512,26 @@ function anthropic(options) {
|
|
|
1460
1512
|
}
|
|
1461
1513
|
};
|
|
1462
1514
|
}
|
|
1515
|
+
function buildAnthropicStructuredOutputInput(options, request) {
|
|
1516
|
+
const baseInput = {
|
|
1517
|
+
messages: request.messages,
|
|
1518
|
+
temperature: request.temperature ?? options.temperature,
|
|
1519
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
1520
|
+
abortSignal: request.abortSignal
|
|
1521
|
+
};
|
|
1522
|
+
if (isZodV4Schema(request.schema)) {
|
|
1523
|
+
return {
|
|
1524
|
+
...baseInput,
|
|
1525
|
+
structuredOutputJsonSchema: buildAnthropicStructuredOutputJsonSchema(
|
|
1526
|
+
request.schema
|
|
1527
|
+
)
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
...baseInput,
|
|
1532
|
+
messages: prependStructuredOutputJsonOnlySystemMessage(request.messages)
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1463
1535
|
function buildAnthropicCallInput(options, request) {
|
|
1464
1536
|
return {
|
|
1465
1537
|
messages: request.messages,
|
|
@@ -1684,13 +1756,10 @@ function gemini(options) {
|
|
|
1684
1756
|
});
|
|
1685
1757
|
},
|
|
1686
1758
|
async generateStructuredOutput(request) {
|
|
1687
|
-
const completion = await executeGeminiAdapterCall(
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
responseSchema: buildGeminiResponseSchema(request.schema),
|
|
1692
|
-
abortSignal: request.abortSignal
|
|
1693
|
-
});
|
|
1759
|
+
const completion = await executeGeminiAdapterCall(
|
|
1760
|
+
options,
|
|
1761
|
+
buildGeminiStructuredOutputInput(options, request)
|
|
1762
|
+
);
|
|
1694
1763
|
return {
|
|
1695
1764
|
value: parseStructuredJsonText(completion.content, request.schema),
|
|
1696
1765
|
tokenUsage: completion.tokenUsage,
|
|
@@ -1699,6 +1768,25 @@ function gemini(options) {
|
|
|
1699
1768
|
}
|
|
1700
1769
|
};
|
|
1701
1770
|
}
|
|
1771
|
+
function buildGeminiStructuredOutputInput(options, request) {
|
|
1772
|
+
const baseInput = {
|
|
1773
|
+
messages: request.messages,
|
|
1774
|
+
temperature: request.temperature ?? options.temperature,
|
|
1775
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
1776
|
+
abortSignal: request.abortSignal
|
|
1777
|
+
};
|
|
1778
|
+
if (isZodV4Schema(request.schema)) {
|
|
1779
|
+
return {
|
|
1780
|
+
...baseInput,
|
|
1781
|
+
responseSchema: buildGeminiResponseSchema(request.schema)
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
return {
|
|
1785
|
+
...baseInput,
|
|
1786
|
+
messages: prependStructuredOutputJsonOnlySystemMessage(request.messages),
|
|
1787
|
+
requireJsonResponse: true
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1702
1790
|
async function executeGeminiAdapterCall(options, input) {
|
|
1703
1791
|
return await executeProviderCallWithModelFallback(
|
|
1704
1792
|
options.fallbackModel,
|
|
@@ -1816,6 +1904,8 @@ function buildGeminiGenerationConfig(input) {
|
|
|
1816
1904
|
if (input.responseSchema !== void 0) {
|
|
1817
1905
|
generationConfig.responseMimeType = "application/json";
|
|
1818
1906
|
generationConfig.responseSchema = input.responseSchema;
|
|
1907
|
+
} else if (input.requireJsonResponse === true) {
|
|
1908
|
+
generationConfig.responseMimeType = "application/json";
|
|
1819
1909
|
}
|
|
1820
1910
|
if (Object.keys(generationConfig).length === 0) {
|
|
1821
1911
|
return void 0;
|
|
@@ -2023,7 +2113,7 @@ import { AppError as AppError15 } from "@assemble-dev/shared-utils/errors";
|
|
|
2023
2113
|
import { AppErrorCode as AppErrorCode13 } from "@assemble-dev/shared-types/errors";
|
|
2024
2114
|
import { JsonValueSchema as JsonValueSchema6 } from "@assemble-dev/shared-types/json";
|
|
2025
2115
|
import { AppError as AppError14 } from "@assemble-dev/shared-utils/errors";
|
|
2026
|
-
var anthropicBatchJsonOnlySystemInstruction =
|
|
2116
|
+
var anthropicBatchJsonOnlySystemInstruction = structuredOutputJsonOnlySystemInstruction;
|
|
2027
2117
|
function parseAnthropicBatchStructuredContent(content, schema) {
|
|
2028
2118
|
const jsonValue = parseStructuredJsonText(
|
|
2029
2119
|
content,
|
|
@@ -2833,6 +2923,7 @@ function azureOpenai(options) {
|
|
|
2833
2923
|
messages: request.messages,
|
|
2834
2924
|
temperature: request.temperature ?? options.temperature,
|
|
2835
2925
|
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
2926
|
+
reasoningModel: options.reasoningModel,
|
|
2836
2927
|
tools: request.tools,
|
|
2837
2928
|
toolChoice: request.toolChoice,
|
|
2838
2929
|
abortSignal: request.abortSignal,
|
|
@@ -2841,12 +2932,10 @@ function azureOpenai(options) {
|
|
|
2841
2932
|
},
|
|
2842
2933
|
async generateStructuredOutput(request) {
|
|
2843
2934
|
const completion = await executeAzureOpenAiAdapterCall(options, {
|
|
2844
|
-
messages
|
|
2935
|
+
...resolveOpenAiStructuredOutputMode(request.messages, request.schema),
|
|
2845
2936
|
temperature: request.temperature ?? options.temperature,
|
|
2846
2937
|
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
2847
|
-
|
|
2848
|
-
request.schema
|
|
2849
|
-
),
|
|
2938
|
+
reasoningModel: options.reasoningModel,
|
|
2850
2939
|
abortSignal: request.abortSignal
|
|
2851
2940
|
});
|
|
2852
2941
|
return {
|
|
@@ -2945,6 +3034,7 @@ function buildAzureOpenAiStreamInput(options, request) {
|
|
|
2945
3034
|
messages: request.messages,
|
|
2946
3035
|
temperature: request.temperature ?? options.temperature,
|
|
2947
3036
|
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
3037
|
+
reasoningModel: options.reasoningModel,
|
|
2948
3038
|
tools: request.tools,
|
|
2949
3039
|
toolChoice: request.toolChoice,
|
|
2950
3040
|
abortSignal: request.abortSignal,
|
|
@@ -3239,6 +3329,9 @@ function buildBedrockInvokeModelRequestBody(input) {
|
|
|
3239
3329
|
};
|
|
3240
3330
|
}
|
|
3241
3331
|
function buildBedrockStructuredOutputSystemMessage(schema) {
|
|
3332
|
+
if (!isZodV4Schema(schema)) {
|
|
3333
|
+
return structuredOutputJsonOnlySystemMessage;
|
|
3334
|
+
}
|
|
3242
3335
|
return {
|
|
3243
3336
|
role: "system",
|
|
3244
3337
|
content: `Respond with a single JSON object that satisfies the following JSON Schema. Output only valid JSON with no surrounding text.
|
|
@@ -3247,6 +3340,425 @@ JSON Schema:
|
|
|
3247
3340
|
${JSON.stringify(convertZodSchemaToJsonSchema(schema))}`
|
|
3248
3341
|
};
|
|
3249
3342
|
}
|
|
3343
|
+
|
|
3344
|
+
// src/openrouter-adapter.ts
|
|
3345
|
+
import { z as z12 } from "zod";
|
|
3346
|
+
import { AppErrorCode as AppErrorCode18 } from "@assemble-dev/shared-types/errors";
|
|
3347
|
+
import { JsonValueSchema as JsonValueSchema9 } from "@assemble-dev/shared-types/json";
|
|
3348
|
+
import { AppError as AppError19 } from "@assemble-dev/shared-utils/errors";
|
|
3349
|
+
var defaultOpenRouterBaseUrl = "https://openrouter.ai/api/v1";
|
|
3350
|
+
var OpenRouterResponseToolCallSchema = z12.object({
|
|
3351
|
+
id: z12.string(),
|
|
3352
|
+
function: z12.object({
|
|
3353
|
+
name: z12.string(),
|
|
3354
|
+
arguments: z12.string()
|
|
3355
|
+
})
|
|
3356
|
+
});
|
|
3357
|
+
var OpenRouterChatCompletionResponseSchema = z12.object({
|
|
3358
|
+
choices: z12.array(
|
|
3359
|
+
z12.object({
|
|
3360
|
+
message: z12.object({
|
|
3361
|
+
content: z12.string().nullable(),
|
|
3362
|
+
tool_calls: z12.array(OpenRouterResponseToolCallSchema).optional()
|
|
3363
|
+
}),
|
|
3364
|
+
finish_reason: z12.string().nullable().optional()
|
|
3365
|
+
})
|
|
3366
|
+
).min(1),
|
|
3367
|
+
usage: z12.object({
|
|
3368
|
+
prompt_tokens: z12.number().int().nonnegative(),
|
|
3369
|
+
completion_tokens: z12.number().int().nonnegative(),
|
|
3370
|
+
total_tokens: z12.number().int().nonnegative()
|
|
3371
|
+
})
|
|
3372
|
+
});
|
|
3373
|
+
function openrouter(options) {
|
|
3374
|
+
return {
|
|
3375
|
+
name: `openrouter:${options.model}`,
|
|
3376
|
+
async generateChatCompletion(request) {
|
|
3377
|
+
return await executeOpenRouterAdapterCall(options, {
|
|
3378
|
+
messages: request.messages,
|
|
3379
|
+
temperature: request.temperature ?? options.temperature,
|
|
3380
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
3381
|
+
reasoningModel: options.reasoningModel,
|
|
3382
|
+
tools: request.tools,
|
|
3383
|
+
toolChoice: request.toolChoice,
|
|
3384
|
+
abortSignal: request.abortSignal,
|
|
3385
|
+
extraBody: request.extraBody
|
|
3386
|
+
});
|
|
3387
|
+
},
|
|
3388
|
+
async generateStructuredOutput(request) {
|
|
3389
|
+
const completion = await executeOpenRouterAdapterCall(options, {
|
|
3390
|
+
...resolveOpenAiStructuredOutputMode(request.messages, request.schema),
|
|
3391
|
+
temperature: request.temperature ?? options.temperature,
|
|
3392
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
3393
|
+
reasoningModel: options.reasoningModel,
|
|
3394
|
+
abortSignal: request.abortSignal
|
|
3395
|
+
});
|
|
3396
|
+
return {
|
|
3397
|
+
value: parseStructuredJsonText(completion.content, request.schema),
|
|
3398
|
+
tokenUsage: completion.tokenUsage,
|
|
3399
|
+
latencyMs: completion.latencyMs
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
async function executeOpenRouterAdapterCall(options, input) {
|
|
3405
|
+
return await executeProviderCallWithModelFallback(
|
|
3406
|
+
options.fallbackModel,
|
|
3407
|
+
async () => await executeProviderCallWithRetry(
|
|
3408
|
+
options.retry,
|
|
3409
|
+
async () => await requestOpenRouterChatCompletion(options, input)
|
|
3410
|
+
),
|
|
3411
|
+
async (fallbackModelId) => await requestOpenRouterChatCompletion(
|
|
3412
|
+
{ ...options, model: fallbackModelId },
|
|
3413
|
+
input
|
|
3414
|
+
)
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
function buildOpenRouterRequestHeaders(options, apiKey) {
|
|
3418
|
+
const headers = {
|
|
3419
|
+
"Content-Type": "application/json",
|
|
3420
|
+
Authorization: `Bearer ${apiKey}`
|
|
3421
|
+
};
|
|
3422
|
+
if (options.appUrl !== void 0) {
|
|
3423
|
+
headers["HTTP-Referer"] = options.appUrl;
|
|
3424
|
+
}
|
|
3425
|
+
if (options.appName !== void 0) {
|
|
3426
|
+
headers["X-Title"] = options.appName;
|
|
3427
|
+
}
|
|
3428
|
+
return headers;
|
|
3429
|
+
}
|
|
3430
|
+
async function requestOpenRouterChatCompletion(options, input) {
|
|
3431
|
+
const apiKey = resolveProviderApiKey({
|
|
3432
|
+
providerLabel: "OpenRouter",
|
|
3433
|
+
adapterFunctionName: "openrouter()",
|
|
3434
|
+
baseEnvVarName: "OPENROUTER_API_KEY",
|
|
3435
|
+
apiKey: options.apiKey,
|
|
3436
|
+
apiKeyName: options.apiKeyName
|
|
3437
|
+
});
|
|
3438
|
+
const fetchImplementation = options.fetchImplementation ?? fetch;
|
|
3439
|
+
const baseUrl = options.baseURL ?? defaultOpenRouterBaseUrl;
|
|
3440
|
+
const requestBody = mergeProviderRequestExtraBody(
|
|
3441
|
+
buildOpenAiRequestBody(options.model, input),
|
|
3442
|
+
options.extraBody,
|
|
3443
|
+
input.extraBody
|
|
3444
|
+
);
|
|
3445
|
+
const startedAt = Date.now();
|
|
3446
|
+
const response = await executeAbortableProviderFetch(
|
|
3447
|
+
"OpenRouter",
|
|
3448
|
+
async () => await fetchImplementation(`${baseUrl}/chat/completions`, {
|
|
3449
|
+
method: "POST",
|
|
3450
|
+
headers: buildOpenRouterRequestHeaders(options, apiKey),
|
|
3451
|
+
body: JSON.stringify(requestBody),
|
|
3452
|
+
signal: input.abortSignal
|
|
3453
|
+
})
|
|
3454
|
+
);
|
|
3455
|
+
const latencyMs = Date.now() - startedAt;
|
|
3456
|
+
if (!response.ok) {
|
|
3457
|
+
throw buildProviderStatusError("OpenRouter", response.status);
|
|
3458
|
+
}
|
|
3459
|
+
const parseResult = OpenRouterChatCompletionResponseSchema.safeParse(
|
|
3460
|
+
await response.json()
|
|
3461
|
+
);
|
|
3462
|
+
if (!parseResult.success) {
|
|
3463
|
+
throw buildProviderInvalidResponseError("OpenRouter", "chat completion");
|
|
3464
|
+
}
|
|
3465
|
+
return mapOpenRouterResponseToChatCompletionResult(
|
|
3466
|
+
parseResult.data,
|
|
3467
|
+
latencyMs
|
|
3468
|
+
);
|
|
3469
|
+
}
|
|
3470
|
+
function mapOpenRouterResponseToChatCompletionResult(response, latencyMs) {
|
|
3471
|
+
const toolCalls = extractOpenRouterToolCalls(response);
|
|
3472
|
+
const result = {
|
|
3473
|
+
content: extractOpenRouterMessageContent(response, toolCalls.length > 0),
|
|
3474
|
+
tokenUsage: mapOpenRouterUsageToTokenUsage(response),
|
|
3475
|
+
latencyMs
|
|
3476
|
+
};
|
|
3477
|
+
const finishReason = response.choices[0]?.finish_reason;
|
|
3478
|
+
if (toolCalls.length > 0) {
|
|
3479
|
+
result.toolCalls = toolCalls;
|
|
3480
|
+
}
|
|
3481
|
+
if (finishReason !== null && finishReason !== void 0) {
|
|
3482
|
+
result.stopReason = normalizeOpenRouterFinishReason(finishReason);
|
|
3483
|
+
}
|
|
3484
|
+
return result;
|
|
3485
|
+
}
|
|
3486
|
+
function extractOpenRouterToolCalls(response) {
|
|
3487
|
+
const responseToolCalls = response.choices[0]?.message.tool_calls ?? [];
|
|
3488
|
+
return responseToolCalls.map((responseToolCall) => ({
|
|
3489
|
+
id: responseToolCall.id,
|
|
3490
|
+
toolName: responseToolCall.function.name,
|
|
3491
|
+
input: parseOpenRouterToolArgumentsJson(
|
|
3492
|
+
responseToolCall.function.arguments
|
|
3493
|
+
)
|
|
3494
|
+
}));
|
|
3495
|
+
}
|
|
3496
|
+
function parseOpenRouterToolArgumentsJson(argumentsJsonText) {
|
|
3497
|
+
if (argumentsJsonText.trim().length === 0) {
|
|
3498
|
+
return {};
|
|
3499
|
+
}
|
|
3500
|
+
try {
|
|
3501
|
+
return JsonValueSchema9.parse(JSON.parse(argumentsJsonText));
|
|
3502
|
+
} catch (error) {
|
|
3503
|
+
throw new AppError19({
|
|
3504
|
+
code: AppErrorCode18.ValidationFailed,
|
|
3505
|
+
message: "OpenRouter tool call arguments were not valid JSON",
|
|
3506
|
+
statusCode: 502,
|
|
3507
|
+
cause: error instanceof Error ? error : void 0
|
|
3508
|
+
});
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
function normalizeOpenRouterFinishReason(finishReason) {
|
|
3512
|
+
if (finishReason === "tool_calls") {
|
|
3513
|
+
return "tool_use";
|
|
3514
|
+
}
|
|
3515
|
+
if (finishReason === "length") {
|
|
3516
|
+
return "max_tokens";
|
|
3517
|
+
}
|
|
3518
|
+
return "end_turn";
|
|
3519
|
+
}
|
|
3520
|
+
function extractOpenRouterMessageContent(response, allowEmptyContent) {
|
|
3521
|
+
const content = response.choices[0]?.message.content;
|
|
3522
|
+
if (content === void 0 || content === null) {
|
|
3523
|
+
if (allowEmptyContent) {
|
|
3524
|
+
return "";
|
|
3525
|
+
}
|
|
3526
|
+
throw new AppError19({
|
|
3527
|
+
code: AppErrorCode18.ValidationFailed,
|
|
3528
|
+
message: "OpenRouter response contained no message content",
|
|
3529
|
+
statusCode: 502
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
return content;
|
|
3533
|
+
}
|
|
3534
|
+
function mapOpenRouterUsageToTokenUsage(response) {
|
|
3535
|
+
return {
|
|
3536
|
+
inputTokens: response.usage.prompt_tokens,
|
|
3537
|
+
outputTokens: response.usage.completion_tokens,
|
|
3538
|
+
totalTokens: response.usage.total_tokens
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
// src/cloudflare-workers-ai-adapter.ts
|
|
3543
|
+
import { z as z13 } from "zod";
|
|
3544
|
+
import { AppErrorCode as AppErrorCode19 } from "@assemble-dev/shared-types/errors";
|
|
3545
|
+
import { JsonValueSchema as JsonValueSchema10 } from "@assemble-dev/shared-types/json";
|
|
3546
|
+
import { AppError as AppError20 } from "@assemble-dev/shared-utils/errors";
|
|
3547
|
+
var defaultCloudflareApiBaseUrl = "https://api.cloudflare.com/client/v4";
|
|
3548
|
+
var cloudflareAccountIdEnvVarName = "CLOUDFLARE_ACCOUNT_ID";
|
|
3549
|
+
var CloudflareWorkersAiResponseToolCallSchema = z13.object({
|
|
3550
|
+
id: z13.string(),
|
|
3551
|
+
function: z13.object({
|
|
3552
|
+
name: z13.string(),
|
|
3553
|
+
arguments: z13.string()
|
|
3554
|
+
})
|
|
3555
|
+
});
|
|
3556
|
+
var CloudflareWorkersAiChatCompletionResponseSchema = z13.object({
|
|
3557
|
+
choices: z13.array(
|
|
3558
|
+
z13.object({
|
|
3559
|
+
message: z13.object({
|
|
3560
|
+
content: z13.string().nullable(),
|
|
3561
|
+
tool_calls: z13.array(CloudflareWorkersAiResponseToolCallSchema).optional()
|
|
3562
|
+
}),
|
|
3563
|
+
finish_reason: z13.string().nullable().optional()
|
|
3564
|
+
})
|
|
3565
|
+
).min(1),
|
|
3566
|
+
usage: z13.object({
|
|
3567
|
+
prompt_tokens: z13.number().int().nonnegative(),
|
|
3568
|
+
completion_tokens: z13.number().int().nonnegative(),
|
|
3569
|
+
total_tokens: z13.number().int().nonnegative()
|
|
3570
|
+
}).optional()
|
|
3571
|
+
});
|
|
3572
|
+
function cloudflareWorkersAi(options) {
|
|
3573
|
+
return {
|
|
3574
|
+
name: `cloudflare-workers-ai:${options.model}`,
|
|
3575
|
+
async generateChatCompletion(request) {
|
|
3576
|
+
return await executeCloudflareWorkersAiAdapterCall(options, {
|
|
3577
|
+
messages: request.messages,
|
|
3578
|
+
temperature: request.temperature ?? options.temperature,
|
|
3579
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
3580
|
+
tools: request.tools,
|
|
3581
|
+
toolChoice: request.toolChoice,
|
|
3582
|
+
abortSignal: request.abortSignal,
|
|
3583
|
+
extraBody: request.extraBody
|
|
3584
|
+
});
|
|
3585
|
+
},
|
|
3586
|
+
async generateStructuredOutput(request) {
|
|
3587
|
+
const completion = await executeCloudflareWorkersAiAdapterCall(options, {
|
|
3588
|
+
...resolveOpenAiStructuredOutputMode(request.messages, request.schema),
|
|
3589
|
+
temperature: request.temperature ?? options.temperature,
|
|
3590
|
+
maxOutputTokens: request.maxOutputTokens ?? options.maxOutputTokens,
|
|
3591
|
+
abortSignal: request.abortSignal
|
|
3592
|
+
});
|
|
3593
|
+
return {
|
|
3594
|
+
value: parseStructuredJsonText(completion.content, request.schema),
|
|
3595
|
+
tokenUsage: completion.tokenUsage,
|
|
3596
|
+
latencyMs: completion.latencyMs
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
function buildCloudflareWorkersAiChatCompletionsUrl(options) {
|
|
3602
|
+
if (options.baseURL !== void 0 && options.baseURL.length > 0) {
|
|
3603
|
+
return `${options.baseURL.replace(/\/+$/, "")}/chat/completions`;
|
|
3604
|
+
}
|
|
3605
|
+
const accountId = resolveCloudflareWorkersAiAccountId(options);
|
|
3606
|
+
return `${defaultCloudflareApiBaseUrl}/accounts/${encodeURIComponent(accountId)}/ai/v1/chat/completions`;
|
|
3607
|
+
}
|
|
3608
|
+
function resolveCloudflareWorkersAiAccountId(options) {
|
|
3609
|
+
if (options.accountId !== void 0 && options.accountId.length > 0) {
|
|
3610
|
+
return options.accountId;
|
|
3611
|
+
}
|
|
3612
|
+
const envAccountId = readEnvironmentVariable2(cloudflareAccountIdEnvVarName);
|
|
3613
|
+
if (envAccountId !== void 0 && envAccountId.length > 0) {
|
|
3614
|
+
return envAccountId;
|
|
3615
|
+
}
|
|
3616
|
+
throw new AppError20({
|
|
3617
|
+
code: AppErrorCode19.BadRequest,
|
|
3618
|
+
message: "Cloudflare Workers AI account is missing. Pass accountId or baseURL to cloudflareWorkersAi() or set the CLOUDFLARE_ACCOUNT_ID environment variable.",
|
|
3619
|
+
statusCode: 400
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
function readEnvironmentVariable2(envVarName) {
|
|
3623
|
+
const parsedEnv = z13.object({ [envVarName]: z13.string().optional() }).parse(process.env);
|
|
3624
|
+
return parsedEnv[envVarName];
|
|
3625
|
+
}
|
|
3626
|
+
async function executeCloudflareWorkersAiAdapterCall(options, input) {
|
|
3627
|
+
return await executeProviderCallWithModelFallback(
|
|
3628
|
+
options.fallbackModel,
|
|
3629
|
+
async () => await executeProviderCallWithRetry(
|
|
3630
|
+
options.retry,
|
|
3631
|
+
async () => await requestCloudflareWorkersAiChatCompletion(options, input)
|
|
3632
|
+
),
|
|
3633
|
+
async (fallbackModelId) => await requestCloudflareWorkersAiChatCompletion(
|
|
3634
|
+
{ ...options, model: fallbackModelId },
|
|
3635
|
+
input
|
|
3636
|
+
)
|
|
3637
|
+
);
|
|
3638
|
+
}
|
|
3639
|
+
async function requestCloudflareWorkersAiChatCompletion(options, input) {
|
|
3640
|
+
const apiToken = resolveProviderApiKey({
|
|
3641
|
+
providerLabel: "Cloudflare Workers AI",
|
|
3642
|
+
adapterFunctionName: "cloudflareWorkersAi()",
|
|
3643
|
+
baseEnvVarName: "CLOUDFLARE_API_TOKEN",
|
|
3644
|
+
apiKey: options.apiKey,
|
|
3645
|
+
apiKeyName: options.apiKeyName
|
|
3646
|
+
});
|
|
3647
|
+
const fetchImplementation = options.fetchImplementation ?? fetch;
|
|
3648
|
+
const requestUrl = buildCloudflareWorkersAiChatCompletionsUrl(options);
|
|
3649
|
+
const requestBody = mergeProviderRequestExtraBody(
|
|
3650
|
+
buildOpenAiRequestBody(options.model, input),
|
|
3651
|
+
options.extraBody,
|
|
3652
|
+
input.extraBody
|
|
3653
|
+
);
|
|
3654
|
+
const startedAt = Date.now();
|
|
3655
|
+
const response = await executeAbortableProviderFetch(
|
|
3656
|
+
"Cloudflare Workers AI",
|
|
3657
|
+
async () => await fetchImplementation(requestUrl, {
|
|
3658
|
+
method: "POST",
|
|
3659
|
+
headers: {
|
|
3660
|
+
"Content-Type": "application/json",
|
|
3661
|
+
Authorization: `Bearer ${apiToken}`
|
|
3662
|
+
},
|
|
3663
|
+
body: JSON.stringify(requestBody),
|
|
3664
|
+
signal: input.abortSignal
|
|
3665
|
+
})
|
|
3666
|
+
);
|
|
3667
|
+
const latencyMs = Date.now() - startedAt;
|
|
3668
|
+
if (!response.ok) {
|
|
3669
|
+
throw buildProviderStatusError("Cloudflare Workers AI", response.status);
|
|
3670
|
+
}
|
|
3671
|
+
const parseResult = CloudflareWorkersAiChatCompletionResponseSchema.safeParse(
|
|
3672
|
+
await response.json()
|
|
3673
|
+
);
|
|
3674
|
+
if (!parseResult.success) {
|
|
3675
|
+
throw buildProviderInvalidResponseError(
|
|
3676
|
+
"Cloudflare Workers AI",
|
|
3677
|
+
"chat completion"
|
|
3678
|
+
);
|
|
3679
|
+
}
|
|
3680
|
+
return mapCloudflareWorkersAiResponseToChatCompletionResult(
|
|
3681
|
+
parseResult.data,
|
|
3682
|
+
latencyMs
|
|
3683
|
+
);
|
|
3684
|
+
}
|
|
3685
|
+
function mapCloudflareWorkersAiResponseToChatCompletionResult(response, latencyMs) {
|
|
3686
|
+
const toolCalls = extractCloudflareWorkersAiToolCalls(response);
|
|
3687
|
+
const result = {
|
|
3688
|
+
content: extractCloudflareWorkersAiMessageContent(
|
|
3689
|
+
response,
|
|
3690
|
+
toolCalls.length > 0
|
|
3691
|
+
),
|
|
3692
|
+
tokenUsage: mapCloudflareWorkersAiUsageToTokenUsage(response),
|
|
3693
|
+
latencyMs
|
|
3694
|
+
};
|
|
3695
|
+
const finishReason = response.choices[0]?.finish_reason;
|
|
3696
|
+
if (toolCalls.length > 0) {
|
|
3697
|
+
result.toolCalls = toolCalls;
|
|
3698
|
+
}
|
|
3699
|
+
if (finishReason !== null && finishReason !== void 0) {
|
|
3700
|
+
result.stopReason = normalizeCloudflareWorkersAiFinishReason(finishReason);
|
|
3701
|
+
}
|
|
3702
|
+
return result;
|
|
3703
|
+
}
|
|
3704
|
+
function extractCloudflareWorkersAiToolCalls(response) {
|
|
3705
|
+
const responseToolCalls = response.choices[0]?.message.tool_calls ?? [];
|
|
3706
|
+
return responseToolCalls.map((responseToolCall) => ({
|
|
3707
|
+
id: responseToolCall.id,
|
|
3708
|
+
toolName: responseToolCall.function.name,
|
|
3709
|
+
input: parseCloudflareWorkersAiToolArgumentsJson(
|
|
3710
|
+
responseToolCall.function.arguments
|
|
3711
|
+
)
|
|
3712
|
+
}));
|
|
3713
|
+
}
|
|
3714
|
+
function parseCloudflareWorkersAiToolArgumentsJson(argumentsJsonText) {
|
|
3715
|
+
if (argumentsJsonText.trim().length === 0) {
|
|
3716
|
+
return {};
|
|
3717
|
+
}
|
|
3718
|
+
try {
|
|
3719
|
+
return JsonValueSchema10.parse(JSON.parse(argumentsJsonText));
|
|
3720
|
+
} catch (error) {
|
|
3721
|
+
throw new AppError20({
|
|
3722
|
+
code: AppErrorCode19.ValidationFailed,
|
|
3723
|
+
message: "Cloudflare Workers AI tool call arguments were not valid JSON",
|
|
3724
|
+
statusCode: 502,
|
|
3725
|
+
cause: error instanceof Error ? error : void 0
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
function normalizeCloudflareWorkersAiFinishReason(finishReason) {
|
|
3730
|
+
if (finishReason === "tool_calls") {
|
|
3731
|
+
return "tool_use";
|
|
3732
|
+
}
|
|
3733
|
+
if (finishReason === "length") {
|
|
3734
|
+
return "max_tokens";
|
|
3735
|
+
}
|
|
3736
|
+
return "end_turn";
|
|
3737
|
+
}
|
|
3738
|
+
function extractCloudflareWorkersAiMessageContent(response, allowEmptyContent) {
|
|
3739
|
+
const content = response.choices[0]?.message.content;
|
|
3740
|
+
if (content === void 0 || content === null) {
|
|
3741
|
+
if (allowEmptyContent) {
|
|
3742
|
+
return "";
|
|
3743
|
+
}
|
|
3744
|
+
throw new AppError20({
|
|
3745
|
+
code: AppErrorCode19.ValidationFailed,
|
|
3746
|
+
message: "Cloudflare Workers AI response contained no message content",
|
|
3747
|
+
statusCode: 502
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
return content;
|
|
3751
|
+
}
|
|
3752
|
+
function mapCloudflareWorkersAiUsageToTokenUsage(response) {
|
|
3753
|
+
if (response.usage === void 0) {
|
|
3754
|
+
return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
3755
|
+
}
|
|
3756
|
+
return {
|
|
3757
|
+
inputTokens: response.usage.prompt_tokens,
|
|
3758
|
+
outputTokens: response.usage.completion_tokens,
|
|
3759
|
+
totalTokens: response.usage.total_tokens
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3250
3762
|
export {
|
|
3251
3763
|
AnthropicMessagesResponseSchema,
|
|
3252
3764
|
AnthropicStreamEventSchema,
|
|
@@ -3272,11 +3784,13 @@ export {
|
|
|
3272
3784
|
buildAzureOpenAiChatRequestBody,
|
|
3273
3785
|
buildBedrockInvokeModelRequestBody,
|
|
3274
3786
|
buildBedrockInvokeModelUrl,
|
|
3787
|
+
buildCloudflareWorkersAiChatCompletionsUrl,
|
|
3275
3788
|
buildGeminiResponseSchema,
|
|
3276
3789
|
buildOpenAiRequestBody,
|
|
3277
3790
|
buildOpenAiStructuredOutputJsonSchema,
|
|
3278
3791
|
buildProviderCanceledError,
|
|
3279
3792
|
calculateProviderRetryDelayMs,
|
|
3793
|
+
cloudflareWorkersAi,
|
|
3280
3794
|
convertZodSchemaToJsonSchema,
|
|
3281
3795
|
createAnthropicBatchClient,
|
|
3282
3796
|
defaultAnthropicBaseUrl,
|
|
@@ -3285,8 +3799,10 @@ export {
|
|
|
3285
3799
|
defaultAnthropicMaxOutputTokens,
|
|
3286
3800
|
defaultAzureOpenAiApiVersion,
|
|
3287
3801
|
defaultBedrockMaxOutputTokens,
|
|
3802
|
+
defaultCloudflareApiBaseUrl,
|
|
3288
3803
|
defaultGeminiBaseUrl,
|
|
3289
3804
|
defaultOpenAiBaseUrl,
|
|
3805
|
+
defaultOpenRouterBaseUrl,
|
|
3290
3806
|
defaultProviderRetryInitialDelayMs,
|
|
3291
3807
|
defaultProviderRetryMaxAttempts,
|
|
3292
3808
|
defaultProviderRetryMaxDelayMs,
|
|
@@ -3300,7 +3816,9 @@ export {
|
|
|
3300
3816
|
extractMessageText,
|
|
3301
3817
|
gemini,
|
|
3302
3818
|
isAbortError,
|
|
3819
|
+
isOpenAiReasoningModel,
|
|
3303
3820
|
isRetryableProviderAppError,
|
|
3821
|
+
isZodV4Schema,
|
|
3304
3822
|
joinSystemMessageText,
|
|
3305
3823
|
mapAnthropicResponseToChatCompletionResult,
|
|
3306
3824
|
mapAnthropicUsageToTokenUsage,
|
|
@@ -3318,18 +3836,23 @@ export {
|
|
|
3318
3836
|
normalizeAzureOpenAiFinishReason,
|
|
3319
3837
|
openAiStructuredOutputSchemaName,
|
|
3320
3838
|
openai,
|
|
3839
|
+
openrouter,
|
|
3321
3840
|
parseAnthropicBatchStructuredContent,
|
|
3322
3841
|
parseAnthropicStreamEvents,
|
|
3323
3842
|
parseAnthropicToolInputJson,
|
|
3324
3843
|
parseAzureOpenAiStreamEvents,
|
|
3325
3844
|
parseAzureOpenAiToolArgumentsJson,
|
|
3845
|
+
prependStructuredOutputJsonOnlySystemMessage,
|
|
3326
3846
|
resolveAnthropicPromptCachingSettings,
|
|
3327
3847
|
resolveBedrockCredentials,
|
|
3848
|
+
resolveOpenAiStructuredOutputMode,
|
|
3328
3849
|
resolveProviderApiKey,
|
|
3329
3850
|
sanitizeJsonSchemaForAnthropicStructuredOutput,
|
|
3330
3851
|
sanitizeJsonSchemaForGeminiResponseSchema,
|
|
3331
3852
|
sanitizeJsonSchemaForOpenAiStructuredOutput,
|
|
3332
3853
|
signAwsRequestWithSigV4,
|
|
3333
3854
|
streamAnthropicChatCompletion,
|
|
3334
|
-
streamAzureOpenAiChatCompletion
|
|
3855
|
+
streamAzureOpenAiChatCompletion,
|
|
3856
|
+
structuredOutputJsonOnlySystemInstruction,
|
|
3857
|
+
structuredOutputJsonOnlySystemMessage
|
|
3335
3858
|
};
|