@x12i/ai-dispatcher 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -31
- package/dist/index.cjs +2118 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +157 -7
- package/dist/index.d.ts +157 -7
- package/dist/index.js +2127 -200
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -23,12 +23,60 @@ __export(index_exports, {
|
|
|
23
23
|
AiDispatcherError: () => AiDispatcherError,
|
|
24
24
|
IMPLEMENTED_AI_PROVIDERS: () => IMPLEMENTED_AI_PROVIDERS,
|
|
25
25
|
createAiDispatcher: () => createAiDispatcher,
|
|
26
|
+
decodeProviderMetadata: () => import_openrouter_runtime4.decodeProviderMetadata,
|
|
26
27
|
isImplementedAiProvider: () => isImplementedAiProvider,
|
|
27
28
|
providerNotImplemented: () => providerNotImplemented,
|
|
28
29
|
resolveProvider: () => resolveProvider,
|
|
29
30
|
stripReasoningHistoryFields: () => stripReasoningHistoryFields
|
|
30
31
|
});
|
|
31
32
|
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
|
|
34
|
+
|
|
35
|
+
// src/cache-usage.ts
|
|
36
|
+
function readCacheTokens(source) {
|
|
37
|
+
const cached = [];
|
|
38
|
+
const written = [];
|
|
39
|
+
for (const usage of usageRecords(source)) {
|
|
40
|
+
const promptDetails = record(usage.prompt_tokens_details);
|
|
41
|
+
const inputDetails = record(usage.input_tokens_details);
|
|
42
|
+
pushNumber(cached, promptDetails?.cached_tokens);
|
|
43
|
+
pushNumber(cached, inputDetails?.cached_tokens);
|
|
44
|
+
pushNumber(cached, usage.cacheReadInputTokens);
|
|
45
|
+
pushNumber(cached, usage.cache_read_input_tokens);
|
|
46
|
+
pushNumber(cached, usage.cached_tokens);
|
|
47
|
+
pushNumber(written, promptDetails?.cache_write_tokens);
|
|
48
|
+
pushNumber(written, inputDetails?.cache_write_tokens);
|
|
49
|
+
pushNumber(written, usage.cacheWriteInputTokens);
|
|
50
|
+
pushNumber(written, usage.cache_write_input_tokens);
|
|
51
|
+
pushNumber(written, usage.cache_write_tokens);
|
|
52
|
+
}
|
|
53
|
+
const cachedTokens = cached[0];
|
|
54
|
+
const cacheWriteTokens = written[0];
|
|
55
|
+
return {
|
|
56
|
+
...cachedTokens !== void 0 ? { cachedTokens } : {},
|
|
57
|
+
...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function usageRecords(source) {
|
|
61
|
+
const root = record(source);
|
|
62
|
+
if (!root) return [];
|
|
63
|
+
const records = [];
|
|
64
|
+
const usage = record(root.usage);
|
|
65
|
+
if (usage) records.push(usage);
|
|
66
|
+
const raw = record(root.raw);
|
|
67
|
+
if (raw) {
|
|
68
|
+
const rawUsage = record(raw.usage);
|
|
69
|
+
records.push(rawUsage ?? raw);
|
|
70
|
+
}
|
|
71
|
+
records.push(root);
|
|
72
|
+
return records;
|
|
73
|
+
}
|
|
74
|
+
function pushNumber(target, value) {
|
|
75
|
+
if (typeof value === "number" && Number.isFinite(value)) target.push(value);
|
|
76
|
+
}
|
|
77
|
+
function record(value) {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
79
|
+
}
|
|
32
80
|
|
|
33
81
|
// src/json-pointer.ts
|
|
34
82
|
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -171,7 +219,7 @@ var ThinkTagSplitter = class {
|
|
|
171
219
|
push(chunk) {
|
|
172
220
|
this.pending += chunk;
|
|
173
221
|
let answerDelta = "";
|
|
174
|
-
let
|
|
222
|
+
let reasoningDelta2 = "";
|
|
175
223
|
while (this.pending.length > 0) {
|
|
176
224
|
if (!this.inThink) {
|
|
177
225
|
const openAt = this.pending.indexOf(OPEN);
|
|
@@ -188,26 +236,26 @@ var ThinkTagSplitter = class {
|
|
|
188
236
|
}
|
|
189
237
|
const closeAt = this.pending.indexOf(CLOSE);
|
|
190
238
|
if (closeAt >= 0) {
|
|
191
|
-
|
|
239
|
+
reasoningDelta2 += this.pending.slice(0, closeAt);
|
|
192
240
|
this.pending = this.pending.slice(closeAt + CLOSE.length);
|
|
193
241
|
this.inThink = false;
|
|
194
242
|
continue;
|
|
195
243
|
}
|
|
196
244
|
const hold = holdSuffix(this.pending, CLOSE);
|
|
197
|
-
|
|
245
|
+
reasoningDelta2 += this.pending.slice(0, this.pending.length - hold);
|
|
198
246
|
this.pending = this.pending.slice(this.pending.length - hold);
|
|
199
247
|
break;
|
|
200
248
|
}
|
|
201
249
|
this.answer += answerDelta;
|
|
202
|
-
this.reasoning +=
|
|
203
|
-
return { answerDelta, reasoningDelta };
|
|
250
|
+
this.reasoning += reasoningDelta2;
|
|
251
|
+
return { answerDelta, reasoningDelta: reasoningDelta2 };
|
|
204
252
|
}
|
|
205
253
|
finish() {
|
|
206
254
|
if (this.inThink) {
|
|
207
|
-
const
|
|
255
|
+
const reasoningDelta2 = this.pending;
|
|
208
256
|
this.pending = "";
|
|
209
|
-
this.reasoning +=
|
|
210
|
-
return { answerDelta: "", reasoningDelta };
|
|
257
|
+
this.reasoning += reasoningDelta2;
|
|
258
|
+
return { answerDelta: "", reasoningDelta: reasoningDelta2 };
|
|
211
259
|
}
|
|
212
260
|
const answerDelta = this.pending;
|
|
213
261
|
this.pending = "";
|
|
@@ -226,6 +274,11 @@ function splitThinkTags(text) {
|
|
|
226
274
|
function applyResolution(response, prepared) {
|
|
227
275
|
const result = response;
|
|
228
276
|
if (prepared.resolution) result.reasoningResolution = prepared.resolution;
|
|
277
|
+
if (prepared.cacheResolution) result.cacheResolution = prepared.cacheResolution;
|
|
278
|
+
if (prepared.cacheWarnings?.length) {
|
|
279
|
+
result.warnings = [...result.warnings, ...prepared.cacheWarnings];
|
|
280
|
+
}
|
|
281
|
+
applyCacheUsage(result);
|
|
229
282
|
const directive = prepared.directive;
|
|
230
283
|
if (!directive) return result;
|
|
231
284
|
const raw = response.raw?.response;
|
|
@@ -241,6 +294,18 @@ function applyResolution(response, prepared) {
|
|
|
241
294
|
applyUsage(result, raw, directive);
|
|
242
295
|
return result;
|
|
243
296
|
}
|
|
297
|
+
function applyCacheUsage(response) {
|
|
298
|
+
const fromResponse = readCacheTokens(response.raw?.response);
|
|
299
|
+
const fromUsage = readCacheTokens(response.usage?.raw);
|
|
300
|
+
const cachedTokens = fromResponse.cachedTokens ?? fromUsage.cachedTokens;
|
|
301
|
+
const cacheWriteTokens = fromResponse.cacheWriteTokens ?? fromUsage.cacheWriteTokens;
|
|
302
|
+
if (cachedTokens === void 0 && cacheWriteTokens === void 0) return;
|
|
303
|
+
response.usage = {
|
|
304
|
+
...response.usage ?? {},
|
|
305
|
+
...cachedTokens !== void 0 ? { cachedTokens } : {},
|
|
306
|
+
...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
244
309
|
function applyUsage(response, raw, directive) {
|
|
245
310
|
const paths = directive.usage?.reasoningTokenPaths;
|
|
246
311
|
if (!paths || paths.length === 0) return;
|
|
@@ -265,6 +330,9 @@ async function* decorateStream(prepared, source) {
|
|
|
265
330
|
for await (const event of source) {
|
|
266
331
|
if (event.type === "stream.start") {
|
|
267
332
|
yield withResolution(event, prepared);
|
|
333
|
+
for (const warning of prepared.cacheWarnings ?? []) {
|
|
334
|
+
yield { type: "stream.warning", requestId: event.requestId, data: { warning } };
|
|
335
|
+
}
|
|
268
336
|
continue;
|
|
269
337
|
}
|
|
270
338
|
if (event.type === "stream.reasoning.delta") {
|
|
@@ -315,6 +383,7 @@ async function* decorateStream(prepared, source) {
|
|
|
315
383
|
if (splitter) data.text = splitter.answer;
|
|
316
384
|
if (reasoningText) data.reasoningText = reasoningText;
|
|
317
385
|
if (prepared.resolution) data.reasoningResolution = prepared.resolution;
|
|
386
|
+
if (prepared.cacheResolution) data.cacheResolution = prepared.cacheResolution;
|
|
318
387
|
yield { ...event, data };
|
|
319
388
|
continue;
|
|
320
389
|
}
|
|
@@ -322,9 +391,10 @@ async function* decorateStream(prepared, source) {
|
|
|
322
391
|
}
|
|
323
392
|
}
|
|
324
393
|
function withResolution(event, prepared) {
|
|
325
|
-
if (!prepared.resolution) return event;
|
|
394
|
+
if (!prepared.resolution && !prepared.cacheResolution) return event;
|
|
326
395
|
const data = isRecord3(event.data) ? { ...event.data } : {};
|
|
327
|
-
data.reasoningResolution = prepared.resolution;
|
|
396
|
+
if (prepared.resolution) data.reasoningResolution = prepared.resolution;
|
|
397
|
+
if (prepared.cacheResolution) data.cacheResolution = prepared.cacheResolution;
|
|
328
398
|
return { ...event, data };
|
|
329
399
|
}
|
|
330
400
|
function textOf(event) {
|
|
@@ -332,20 +402,37 @@ function textOf(event) {
|
|
|
332
402
|
return event.data.text;
|
|
333
403
|
}
|
|
334
404
|
function adjustUsageEvent(event, prepared) {
|
|
405
|
+
if (!isRecord3(event.data) || !isRecord3(event.data.usage)) return event;
|
|
335
406
|
const paths = prepared.directive?.usage?.reasoningTokenPaths;
|
|
336
|
-
if (!paths || paths.length === 0 || !isRecord3(event.data) || !isRecord3(event.data.usage)) return event;
|
|
337
407
|
const usage = { ...event.data.usage };
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
408
|
+
let changed = false;
|
|
409
|
+
if (paths && paths.length > 0) {
|
|
410
|
+
const tokens = readReasoningTokens(usage.raw, paths);
|
|
411
|
+
if (tokens === void 0) {
|
|
412
|
+
if (usage.reasoningTokens !== void 0) {
|
|
413
|
+
delete usage.reasoningTokens;
|
|
414
|
+
changed = true;
|
|
415
|
+
}
|
|
416
|
+
} else if (usage.reasoningTokens !== tokens) {
|
|
417
|
+
usage.reasoningTokens = tokens;
|
|
418
|
+
changed = true;
|
|
419
|
+
}
|
|
343
420
|
}
|
|
421
|
+
const counts = readCacheTokens(usage.raw);
|
|
422
|
+
if (counts.cachedTokens !== void 0) {
|
|
423
|
+
usage.cachedTokens = counts.cachedTokens;
|
|
424
|
+
changed = true;
|
|
425
|
+
}
|
|
426
|
+
if (counts.cacheWriteTokens !== void 0) {
|
|
427
|
+
usage.cacheWriteTokens = counts.cacheWriteTokens;
|
|
428
|
+
changed = true;
|
|
429
|
+
}
|
|
430
|
+
if (!changed) return event;
|
|
344
431
|
return { ...event, data: { ...event.data, usage } };
|
|
345
432
|
}
|
|
346
433
|
|
|
347
434
|
// src/types/providers.ts
|
|
348
|
-
var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai"];
|
|
435
|
+
var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai", "cloudflare"];
|
|
349
436
|
function isImplementedAiProvider(provider) {
|
|
350
437
|
return IMPLEMENTED_AI_PROVIDERS.includes(provider);
|
|
351
438
|
}
|
|
@@ -508,101 +595,1768 @@ function resolveAllowlist(filter, tools) {
|
|
|
508
595
|
if (!known.has(name)) {
|
|
509
596
|
throw new AiDispatcherError("MCP_TOOL_NOT_FOUND", `MCP tool "${name}" is not registered.`);
|
|
510
597
|
}
|
|
511
|
-
}
|
|
512
|
-
return new Set(filter);
|
|
598
|
+
}
|
|
599
|
+
return new Set(filter);
|
|
600
|
+
}
|
|
601
|
+
function assertServer(server, seen) {
|
|
602
|
+
if (!isRecord4(server) || typeof server.id !== "string" || server.id.length === 0 || !Array.isArray(server.tools) || typeof server.callTool !== "function") {
|
|
603
|
+
throw new AiDispatcherError(
|
|
604
|
+
"MCP_SERVER_INVALID",
|
|
605
|
+
"An MCP server needs an id, a tools array, and a callTool function."
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (seen.has(server.id)) {
|
|
609
|
+
throw new AiDispatcherError("MCP_SERVER_INVALID", `MCP server "${server.id}" is listed more than once.`);
|
|
610
|
+
}
|
|
611
|
+
seen.add(server.id);
|
|
612
|
+
if (!TOOL_TOKEN.test(server.id)) {
|
|
613
|
+
throw new AiDispatcherError(
|
|
614
|
+
"MCP_TOOL_NAME_INVALID",
|
|
615
|
+
`MCP server id "${server.id}" must use letters, numbers, underscores, and hyphens.`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function exposedToolName(serverId, tool) {
|
|
620
|
+
if (typeof tool.name !== "string" || !TOOL_TOKEN.test(serverId) || !TOOL_TOKEN.test(tool.name)) {
|
|
621
|
+
throw new AiDispatcherError(
|
|
622
|
+
"MCP_TOOL_NAME_INVALID",
|
|
623
|
+
`MCP tool "${serverId}.${typeof tool.name === "string" ? tool.name : ""}" must use letters, numbers, underscores, and hyphens.`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
const exposedName = `${serverId}__${tool.name}`;
|
|
627
|
+
if (exposedName.length > MAX_EXPOSED_NAME_LENGTH) {
|
|
628
|
+
throw new AiDispatcherError(
|
|
629
|
+
"MCP_TOOL_NAME_INVALID",
|
|
630
|
+
`MCP tool "${exposedName}" is longer than ${MAX_EXPOSED_NAME_LENGTH} characters.`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
return exposedName;
|
|
634
|
+
}
|
|
635
|
+
function toolDescription(tool) {
|
|
636
|
+
if (tool.description === void 0) return "";
|
|
637
|
+
if (typeof tool.description !== "string") {
|
|
638
|
+
throw new AiDispatcherError(
|
|
639
|
+
"MCP_TOOL_NAME_INVALID",
|
|
640
|
+
`MCP tool "${tool.name}" description must be a string.`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
return tool.description;
|
|
644
|
+
}
|
|
645
|
+
function toolParameters(tool) {
|
|
646
|
+
if (tool.inputSchema === void 0) return { ...DEFAULT_PARAMETERS, properties: {} };
|
|
647
|
+
if (!isRecord4(tool.inputSchema)) {
|
|
648
|
+
throw new AiDispatcherError(
|
|
649
|
+
"MCP_TOOL_NAME_INVALID",
|
|
650
|
+
`MCP tool "${tool.name}" inputSchema must be an object.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
return tool.inputSchema;
|
|
654
|
+
}
|
|
655
|
+
function textContent(content) {
|
|
656
|
+
if (!Array.isArray(content)) return void 0;
|
|
657
|
+
const parts = [];
|
|
658
|
+
for (const part of content) {
|
|
659
|
+
if (!isRecord4(part) || part.type !== "text" || typeof part.text !== "string") continue;
|
|
660
|
+
parts.push(part.text);
|
|
661
|
+
}
|
|
662
|
+
if (!parts.length) return void 0;
|
|
663
|
+
return parts.join("\n");
|
|
664
|
+
}
|
|
665
|
+
function toArgs(args) {
|
|
666
|
+
if (isRecord4(args)) return args;
|
|
667
|
+
return {};
|
|
668
|
+
}
|
|
669
|
+
function listFailed(serverId, error) {
|
|
670
|
+
if (error instanceof AiDispatcherError && error.code === "MCP_LIST_FAILED") return error;
|
|
671
|
+
const message = error instanceof Error ? error.message : `MCP server "${serverId}" listTools failed.`;
|
|
672
|
+
return new AiDispatcherError("MCP_LIST_FAILED", message);
|
|
673
|
+
}
|
|
674
|
+
function stringify(result) {
|
|
675
|
+
const serialized = JSON.stringify(result);
|
|
676
|
+
return serialized === void 0 ? "null" : serialized;
|
|
677
|
+
}
|
|
678
|
+
function isRecord4(value) {
|
|
679
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/prepare-request.ts
|
|
683
|
+
var import_ai_profiles = require("@x12i/ai-profiles");
|
|
684
|
+
|
|
685
|
+
// src/cache-apply.ts
|
|
686
|
+
var BREAKPOINT = { mode: "explicit" };
|
|
687
|
+
function applyPromptCache(body, plan) {
|
|
688
|
+
if (!plan.stripMarkers && !plan.placeBreakpoint && !plan.sendOpenAiOptions) {
|
|
689
|
+
return { body, placed: false };
|
|
690
|
+
}
|
|
691
|
+
let next = plan.stripMarkers || plan.placeBreakpoint ? stripCacheMarkers(body) : body;
|
|
692
|
+
if (plan.sendOpenAiOptions) next = applyOpenAiOptions(next, plan);
|
|
693
|
+
if (!plan.placeBreakpoint) return { body: next, placed: false };
|
|
694
|
+
if (plan.dialect === "bedrock-cachepoint") return placeBedrockBreakpoint(next, plan.ttl);
|
|
695
|
+
if (plan.dialect === "anthropic-cache-control") return placeAnthropicBreakpoint(next, plan.ttl);
|
|
696
|
+
if (plan.dialect === "openai-responses") return placeOpenAiBreakpoint(next);
|
|
697
|
+
return { body: next, placed: false };
|
|
698
|
+
}
|
|
699
|
+
function applyOpenAiOptions(body, plan) {
|
|
700
|
+
return {
|
|
701
|
+
...body,
|
|
702
|
+
...plan.key ? { prompt_cache_key: plan.key } : {},
|
|
703
|
+
prompt_cache_options: {
|
|
704
|
+
mode: plan.placeBreakpoint ? "explicit" : "implicit",
|
|
705
|
+
...plan.ttl ? { ttl: plan.ttl } : {}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function placeOpenAiBreakpoint(body) {
|
|
710
|
+
const instructions = typeof body.instructions === "string" ? body.instructions.trim() : "";
|
|
711
|
+
if (instructions) {
|
|
712
|
+
const rest = inputItems(body.input);
|
|
713
|
+
return {
|
|
714
|
+
placed: true,
|
|
715
|
+
body: {
|
|
716
|
+
...omitKey(body, "instructions"),
|
|
717
|
+
input: [developerBlock(instructions, "prompt_cache_breakpoint", BREAKPOINT), ...rest]
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (Array.isArray(body.messages)) {
|
|
722
|
+
return markMessages(body, "messages", "prompt_cache_breakpoint", BREAKPOINT, "text");
|
|
723
|
+
}
|
|
724
|
+
return markInput(body, "prompt_cache_breakpoint", BREAKPOINT, "input_text");
|
|
725
|
+
}
|
|
726
|
+
function placeAnthropicBreakpoint(body, ttl) {
|
|
727
|
+
const marker = { type: "ephemeral", ...ttl ? { ttl } : {} };
|
|
728
|
+
if (Array.isArray(body.messages)) {
|
|
729
|
+
const marked = markMessages(body, "messages", "cache_control", marker, "text");
|
|
730
|
+
if (marked.placed) return marked;
|
|
731
|
+
if (typeof body.system === "string" && body.system.trim()) {
|
|
732
|
+
return {
|
|
733
|
+
placed: true,
|
|
734
|
+
body: {
|
|
735
|
+
...body,
|
|
736
|
+
system: [{ type: "text", text: body.system.trim(), cache_control: marker }]
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
return marked;
|
|
741
|
+
}
|
|
742
|
+
const instructions = typeof body.instructions === "string" ? body.instructions.trim() : "";
|
|
743
|
+
if (instructions) {
|
|
744
|
+
const rest = inputItems(body.input);
|
|
745
|
+
return {
|
|
746
|
+
placed: true,
|
|
747
|
+
body: {
|
|
748
|
+
...omitKey(body, "instructions"),
|
|
749
|
+
input: [developerBlock(instructions, "cache_control", marker), ...rest]
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
return markInput(body, "cache_control", marker, "input_text");
|
|
754
|
+
}
|
|
755
|
+
function placeBedrockBreakpoint(body, ttl) {
|
|
756
|
+
const point = { cachePoint: { type: "default", ...ttl ? { ttl } : {} } };
|
|
757
|
+
const system = Array.isArray(body.system) ? body.system.filter(hasBedrockText) : [];
|
|
758
|
+
if (system.length > 0) {
|
|
759
|
+
return { placed: true, body: { ...body, system: [...system, point] } };
|
|
760
|
+
}
|
|
761
|
+
return { body, placed: false };
|
|
762
|
+
}
|
|
763
|
+
function markMessages(body, field, markerKey, markerValue, textType) {
|
|
764
|
+
const messages = body[field];
|
|
765
|
+
if (!Array.isArray(messages)) return { body, placed: false };
|
|
766
|
+
const index = stableMessageIndex(messages);
|
|
767
|
+
if (index < 0) return { body, placed: false };
|
|
768
|
+
const marked = markMessage(messages[index], markerKey, markerValue, textType);
|
|
769
|
+
if (!marked) return { body, placed: false };
|
|
770
|
+
const next = messages.slice();
|
|
771
|
+
next[index] = marked;
|
|
772
|
+
return { placed: true, body: { ...body, [field]: next } };
|
|
773
|
+
}
|
|
774
|
+
function markInput(body, markerKey, markerValue, textType) {
|
|
775
|
+
if (!Array.isArray(body.input)) return { body, placed: false };
|
|
776
|
+
const index = stableInputIndex(body.input);
|
|
777
|
+
if (index < 0) return { body, placed: false };
|
|
778
|
+
const marked = markMessage(body.input[index], markerKey, markerValue, textType);
|
|
779
|
+
if (!marked) return { body, placed: false };
|
|
780
|
+
const next = body.input.slice();
|
|
781
|
+
next[index] = marked;
|
|
782
|
+
return { placed: true, body: { ...body, input: next } };
|
|
783
|
+
}
|
|
784
|
+
function stableMessageIndex(messages) {
|
|
785
|
+
let lastSystem = -1;
|
|
786
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
787
|
+
const message = messages[index];
|
|
788
|
+
if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index;
|
|
789
|
+
}
|
|
790
|
+
return lastSystem;
|
|
791
|
+
}
|
|
792
|
+
function stableInputIndex(items) {
|
|
793
|
+
let lastStable = -1;
|
|
794
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
795
|
+
const item = items[index];
|
|
796
|
+
if (!isPlainRecord(item) || !hasText(item.content)) continue;
|
|
797
|
+
if (item.role === "system" || item.role === "developer") lastStable = index;
|
|
798
|
+
}
|
|
799
|
+
return lastStable;
|
|
800
|
+
}
|
|
801
|
+
function markMessage(message, markerKey, markerValue, textType) {
|
|
802
|
+
if (!isPlainRecord(message) || !hasText(message.content)) return void 0;
|
|
803
|
+
const content = markContent(message.content, markerKey, markerValue, textType);
|
|
804
|
+
if (!content) return void 0;
|
|
805
|
+
return { ...message, content };
|
|
806
|
+
}
|
|
807
|
+
function markContent(content, markerKey, markerValue, textType) {
|
|
808
|
+
if (typeof content === "string") {
|
|
809
|
+
if (!content.trim()) return void 0;
|
|
810
|
+
return [{ type: textType, text: content, [markerKey]: markerValue }];
|
|
811
|
+
}
|
|
812
|
+
if (!Array.isArray(content)) return void 0;
|
|
813
|
+
const parts = content.map((part) => isPlainRecord(part) ? { ...part } : part);
|
|
814
|
+
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
815
|
+
const part = parts[index];
|
|
816
|
+
if (!isPlainRecord(part) || typeof part.text !== "string" || !part.text.trim()) continue;
|
|
817
|
+
parts[index] = { ...part, [markerKey]: markerValue };
|
|
818
|
+
return parts;
|
|
819
|
+
}
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
function developerBlock(text, markerKey, markerValue) {
|
|
823
|
+
return {
|
|
824
|
+
role: "developer",
|
|
825
|
+
content: [{ type: "input_text", text, [markerKey]: markerValue }]
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function inputItems(input) {
|
|
829
|
+
if (input === void 0) return [];
|
|
830
|
+
if (typeof input === "string") {
|
|
831
|
+
return input.trim() ? [{ role: "user", content: input }] : [];
|
|
832
|
+
}
|
|
833
|
+
return Array.isArray(input) ? input : [];
|
|
834
|
+
}
|
|
835
|
+
function hasBedrockText(block) {
|
|
836
|
+
return isPlainRecord(block) && typeof block.text === "string" && block.text.trim().length > 0;
|
|
837
|
+
}
|
|
838
|
+
function hasText(content) {
|
|
839
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
840
|
+
if (!Array.isArray(content)) return false;
|
|
841
|
+
return content.some((part) => isPlainRecord(part) && typeof part.text === "string" && part.text.trim().length > 0);
|
|
842
|
+
}
|
|
843
|
+
function stripCacheMarkers(value) {
|
|
844
|
+
return stripValue(value);
|
|
845
|
+
}
|
|
846
|
+
function stripValue(value) {
|
|
847
|
+
if (Array.isArray(value)) {
|
|
848
|
+
return value.map((item) => stripValue(item)).filter((item) => !(isPlainRecord(item) && Object.keys(item).length === 0));
|
|
849
|
+
}
|
|
850
|
+
if (!isPlainRecord(value)) return value;
|
|
851
|
+
const next = {};
|
|
852
|
+
for (const [key, child] of Object.entries(value)) {
|
|
853
|
+
if (key === "prompt_cache_breakpoint" || key === "cache_control" || key === "cachePoint") continue;
|
|
854
|
+
if (key === "prompt_cache_key" || key === "prompt_cache_options") continue;
|
|
855
|
+
next[key] = stripValue(child);
|
|
856
|
+
}
|
|
857
|
+
return next;
|
|
858
|
+
}
|
|
859
|
+
function omitKey(body, key) {
|
|
860
|
+
const next = { ...body };
|
|
861
|
+
delete next[key];
|
|
862
|
+
return next;
|
|
863
|
+
}
|
|
864
|
+
function isPlainRecord(value) {
|
|
865
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
866
|
+
const proto = Object.getPrototypeOf(value);
|
|
867
|
+
return proto === Object.prototype || proto === null;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/cache-profiles.ts
|
|
871
|
+
var TTL_MINUTES = { "5m": 5, "30m": 30, "1h": 60 };
|
|
872
|
+
var TTL_SECONDS = { "5m": 300, "30m": 1800, "1h": 3600 };
|
|
873
|
+
var OPENAI_EXPLICIT = {
|
|
874
|
+
dialect: "openai-responses",
|
|
875
|
+
ttlOptions: ["30m"],
|
|
876
|
+
defaultTtl: "30m"
|
|
877
|
+
};
|
|
878
|
+
var OPENAI_IMPLICIT = {
|
|
879
|
+
dialect: "implicit-native",
|
|
880
|
+
ttlOptions: ["30m"],
|
|
881
|
+
defaultTtl: "30m"
|
|
882
|
+
};
|
|
883
|
+
var ANTHROPIC_EXPLICIT = {
|
|
884
|
+
dialect: "anthropic-cache-control",
|
|
885
|
+
ttlOptions: ["5m", "1h"],
|
|
886
|
+
defaultTtl: "5m"
|
|
887
|
+
};
|
|
888
|
+
var QWEN_EXPLICIT = {
|
|
889
|
+
dialect: "anthropic-cache-control",
|
|
890
|
+
ttlOptions: ["5m"],
|
|
891
|
+
defaultTtl: "5m"
|
|
892
|
+
};
|
|
893
|
+
var BEDROCK_EXPLICIT = {
|
|
894
|
+
dialect: "bedrock-cachepoint",
|
|
895
|
+
ttlOptions: ["5m", "1h"],
|
|
896
|
+
defaultTtl: "5m"
|
|
897
|
+
};
|
|
898
|
+
var ROUTER_IMPLICIT = {
|
|
899
|
+
dialect: "implicit-native",
|
|
900
|
+
ttlOptions: ["5m"],
|
|
901
|
+
defaultTtl: "5m"
|
|
902
|
+
};
|
|
903
|
+
function resolvePromptCache(input) {
|
|
904
|
+
const mode = normalizeMode(input.policy.mode);
|
|
905
|
+
const key = input.policy.key?.trim() || void 0;
|
|
906
|
+
const requestedTtl = normalizeTtl(input.policy.ttl);
|
|
907
|
+
const model = input.effectiveModel.trim();
|
|
908
|
+
const resolutionBase = {
|
|
909
|
+
mode,
|
|
910
|
+
target: input.target,
|
|
911
|
+
requestedModel: input.requestedModel,
|
|
912
|
+
effectiveModel: input.effectiveModel,
|
|
913
|
+
keyPresent: Boolean(key)
|
|
914
|
+
};
|
|
915
|
+
if (input.target === "cloudflare") {
|
|
916
|
+
return resolveCloudflarePromptCache({ input, mode, key, requestedTtl, resolutionBase, model });
|
|
917
|
+
}
|
|
918
|
+
if (mode === "disabled") {
|
|
919
|
+
return {
|
|
920
|
+
plan: emptyPlan(mode, "none", key, true),
|
|
921
|
+
resolution: { ...resolutionBase, outcome: "ignored" },
|
|
922
|
+
warnings: []
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
if (input.policy.mode !== void 0 && mode === "explicit" && !isCacheMode(input.policy.mode)) {
|
|
926
|
+
return ignored(input, resolutionBase, key, "CACHE_UNSUPPORTED", "cache.mode is not auto, explicit, or disabled. The call still runs without cache markers.");
|
|
927
|
+
}
|
|
928
|
+
const matched = matchProfile(input.target, model);
|
|
929
|
+
if (!matched.profile) {
|
|
930
|
+
return ignored(
|
|
931
|
+
input,
|
|
932
|
+
resolutionBase,
|
|
933
|
+
key,
|
|
934
|
+
matched.code,
|
|
935
|
+
matched.message
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
return applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile: matched.profile });
|
|
939
|
+
}
|
|
940
|
+
function resolveCloudflarePromptCache(args) {
|
|
941
|
+
const { input, mode, key, requestedTtl, resolutionBase, model } = args;
|
|
942
|
+
if (mode === "disabled") {
|
|
943
|
+
return {
|
|
944
|
+
plan: { ...emptyPlan(mode, "none", key, true), gateway: gatewayDirective(true, void 0, key) },
|
|
945
|
+
resolution: { ...resolutionBase, outcome: "applied" },
|
|
946
|
+
warnings: []
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
if (input.policy.mode !== void 0 && mode === "explicit" && !isCacheMode(input.policy.mode)) {
|
|
950
|
+
return ignored(input, resolutionBase, key, "CACHE_UNSUPPORTED", "cache.mode is not auto, explicit, or disabled. The call still runs without cache markers.");
|
|
951
|
+
}
|
|
952
|
+
const profile = matchCloudflareNative(input.cloudflareEndpoint, model);
|
|
953
|
+
if (!profile) {
|
|
954
|
+
return {
|
|
955
|
+
plan: {
|
|
956
|
+
mode,
|
|
957
|
+
dialect: "none",
|
|
958
|
+
placeBreakpoint: false,
|
|
959
|
+
sendOpenAiOptions: false,
|
|
960
|
+
stripMarkers: false,
|
|
961
|
+
gateway: gatewayDirective(false, requestedTtl, key),
|
|
962
|
+
...requestedTtl ? { ttl: requestedTtl } : {},
|
|
963
|
+
...key ? { key } : {}
|
|
964
|
+
},
|
|
965
|
+
resolution: {
|
|
966
|
+
...resolutionBase,
|
|
967
|
+
outcome: "applied",
|
|
968
|
+
...requestedTtl ? { ttl: requestedTtl } : {}
|
|
969
|
+
},
|
|
970
|
+
warnings: []
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
const resolved = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
|
|
974
|
+
const ttl = resolved.plan.ttl ?? requestedTtl;
|
|
975
|
+
return {
|
|
976
|
+
...resolved,
|
|
977
|
+
plan: { ...resolved.plan, gateway: gatewayDirective(false, ttl, key) }
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function applyProfile(args) {
|
|
981
|
+
const { input, mode, key, requestedTtl, resolutionBase, model, profile } = args;
|
|
982
|
+
const folded = foldTtl(requestedTtl, profile.ttlOptions, profile.defaultTtl);
|
|
983
|
+
const explicitDialect = profile.dialect === "openai-responses" || profile.dialect === "anthropic-cache-control" || profile.dialect === "bedrock-cachepoint";
|
|
984
|
+
if (mode === "explicit" && profile.dialect === "implicit-native") {
|
|
985
|
+
return ignored(
|
|
986
|
+
input,
|
|
987
|
+
resolutionBase,
|
|
988
|
+
key,
|
|
989
|
+
"CACHE_UNSUPPORTED",
|
|
990
|
+
"This model caches automatically and does not accept an explicit breakpoint. The call still runs."
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
if (mode === "auto" && explicitDialect && profile.dialect !== "openai-responses") {
|
|
994
|
+
return ignored(
|
|
995
|
+
input,
|
|
996
|
+
resolutionBase,
|
|
997
|
+
key,
|
|
998
|
+
"CACHE_UNSUPPORTED",
|
|
999
|
+
"This model only caches when an explicit breakpoint is set. Set cache.mode to explicit. The call still runs."
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
const warnings = [];
|
|
1003
|
+
const sendsTtl = profile.dialect === "openai-responses" || mode === "explicit" && explicitDialect;
|
|
1004
|
+
if (requestedTtl && folded.degraded && sendsTtl) {
|
|
1005
|
+
warnings.push({
|
|
1006
|
+
code: "CACHE_TTL_DEGRADED",
|
|
1007
|
+
message: `cache.ttl "${requestedTtl}" is not supported for this model. Using "${folded.ttl}".`,
|
|
1008
|
+
details: { requestedTtl, effectiveTtl: folded.ttl, target: input.target, model }
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
const placeBreakpoint = mode === "explicit" && explicitDialect;
|
|
1012
|
+
const sendOpenAiOptions = profile.dialect === "openai-responses" && (mode === "explicit" || mode === "auto");
|
|
1013
|
+
const outcome = folded.degraded && sendsTtl ? "degraded" : "applied";
|
|
1014
|
+
return {
|
|
1015
|
+
plan: {
|
|
1016
|
+
mode,
|
|
1017
|
+
dialect: profile.dialect,
|
|
1018
|
+
placeBreakpoint,
|
|
1019
|
+
sendOpenAiOptions,
|
|
1020
|
+
stripMarkers: placeBreakpoint,
|
|
1021
|
+
...sendsTtl ? { ttl: folded.ttl } : {},
|
|
1022
|
+
...key ? { key } : {}
|
|
1023
|
+
},
|
|
1024
|
+
resolution: {
|
|
1025
|
+
...resolutionBase,
|
|
1026
|
+
outcome,
|
|
1027
|
+
...sendsTtl ? { ttl: folded.ttl } : {}
|
|
1028
|
+
},
|
|
1029
|
+
warnings
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function gatewayDirective(skip, ttl, key) {
|
|
1033
|
+
return {
|
|
1034
|
+
skip,
|
|
1035
|
+
...ttl ? { ttlSeconds: TTL_SECONDS[ttl] } : {},
|
|
1036
|
+
...key ? { key } : {}
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function matchCloudflareNative(endpoint, model) {
|
|
1040
|
+
const id = model.toLowerCase();
|
|
1041
|
+
if (endpoint === "responses" && isGptExplicit(id)) return OPENAI_EXPLICIT;
|
|
1042
|
+
if (endpoint === "responses" && isGptImplicit(id)) return OPENAI_IMPLICIT;
|
|
1043
|
+
if (endpoint === "messages" && isClaude(id)) return ANTHROPIC_EXPLICIT;
|
|
1044
|
+
return void 0;
|
|
1045
|
+
}
|
|
1046
|
+
function ignored(input, resolutionBase, key, code, message) {
|
|
1047
|
+
return {
|
|
1048
|
+
plan: emptyPlan(resolutionBase.mode, "none", key, false),
|
|
1049
|
+
resolution: { ...resolutionBase, outcome: "ignored" },
|
|
1050
|
+
warnings: [{
|
|
1051
|
+
code,
|
|
1052
|
+
message,
|
|
1053
|
+
details: { target: input.target, model: input.effectiveModel.trim() }
|
|
1054
|
+
}]
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function emptyPlan(mode, dialect, key, stripMarkers) {
|
|
1058
|
+
return {
|
|
1059
|
+
mode,
|
|
1060
|
+
dialect,
|
|
1061
|
+
placeBreakpoint: false,
|
|
1062
|
+
sendOpenAiOptions: false,
|
|
1063
|
+
stripMarkers,
|
|
1064
|
+
...key ? { key } : {}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function matchProfile(target, model) {
|
|
1068
|
+
const id = model.toLowerCase();
|
|
1069
|
+
if (!id) {
|
|
1070
|
+
return {
|
|
1071
|
+
code: "CACHE_UNSUPPORTED",
|
|
1072
|
+
message: "Prompt caching needs a model id. The call still runs without cache markers."
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (target === "bedrock") {
|
|
1076
|
+
if (isOpenAiBedrockId(id)) {
|
|
1077
|
+
return {
|
|
1078
|
+
code: "CACHE_DIALECT_UNSUPPORTED",
|
|
1079
|
+
message: "OpenAI models on Bedrock cache through the Responses API. This runtime sends Converse and does not add those markers. The call still runs."
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
if (isClaude(id) || isNova(id)) return { profile: BEDROCK_EXPLICIT, code: "", message: "" };
|
|
1083
|
+
return {
|
|
1084
|
+
code: "CACHE_UNSUPPORTED",
|
|
1085
|
+
message: "This Bedrock model has no prompt-cache profile. The call still runs without cache markers."
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
if (target === "openai") {
|
|
1089
|
+
if (isGptExplicit(id)) return { profile: OPENAI_EXPLICIT, code: "", message: "" };
|
|
1090
|
+
if (isGptImplicit(id)) return { profile: OPENAI_IMPLICIT, code: "", message: "" };
|
|
1091
|
+
return {
|
|
1092
|
+
code: "CACHE_UNSUPPORTED",
|
|
1093
|
+
message: "This OpenAI model has no prompt-cache profile. The call still runs without cache markers."
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
if (isClaude(id)) return { profile: ANTHROPIC_EXPLICIT, code: "", message: "" };
|
|
1097
|
+
if (isQwen(id)) return { profile: QWEN_EXPLICIT, code: "", message: "" };
|
|
1098
|
+
if (isGptExplicit(id)) return { profile: OPENAI_EXPLICIT, code: "", message: "" };
|
|
1099
|
+
if (isGptImplicit(id) || isRouterImplicit(id)) return { profile: ROUTER_IMPLICIT, code: "", message: "" };
|
|
1100
|
+
return {
|
|
1101
|
+
code: "CACHE_UNSUPPORTED",
|
|
1102
|
+
message: "This model has no prompt-cache profile. The call still runs without cache markers."
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function foldTtl(requested, options, defaultTtl) {
|
|
1106
|
+
if (!requested) return { ttl: defaultTtl, degraded: false };
|
|
1107
|
+
if (options.includes(requested)) return { ttl: requested, degraded: false };
|
|
1108
|
+
let best = defaultTtl;
|
|
1109
|
+
let bestDistance = Math.abs(TTL_MINUTES[defaultTtl] - TTL_MINUTES[requested]);
|
|
1110
|
+
for (const option of options) {
|
|
1111
|
+
const distance = Math.abs(TTL_MINUTES[option] - TTL_MINUTES[requested]);
|
|
1112
|
+
if (distance < bestDistance) {
|
|
1113
|
+
best = option;
|
|
1114
|
+
bestDistance = distance;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return { ttl: best, degraded: true };
|
|
1118
|
+
}
|
|
1119
|
+
function normalizeMode(mode) {
|
|
1120
|
+
if (mode === "auto" || mode === "disabled") return mode;
|
|
1121
|
+
return "explicit";
|
|
1122
|
+
}
|
|
1123
|
+
function isCacheMode(mode) {
|
|
1124
|
+
return mode === "auto" || mode === "explicit" || mode === "disabled";
|
|
1125
|
+
}
|
|
1126
|
+
function normalizeTtl(ttl) {
|
|
1127
|
+
if (ttl === "5m" || ttl === "30m" || ttl === "1h") return ttl;
|
|
1128
|
+
return void 0;
|
|
1129
|
+
}
|
|
1130
|
+
function isGptExplicit(id) {
|
|
1131
|
+
return /(?:^|\/)gpt-(?:5\.6|6)(?:[.\-:]|$)/i.test(id);
|
|
1132
|
+
}
|
|
1133
|
+
function isGptImplicit(id) {
|
|
1134
|
+
if (isGptExplicit(id)) return false;
|
|
1135
|
+
return /(?:^|\/)gpt(?:[.\-:]|$)/i.test(id) || /(?:^|\/)o[134](?:[.\-:]|$)/i.test(id);
|
|
1136
|
+
}
|
|
1137
|
+
function isClaude(id) {
|
|
1138
|
+
return id.includes("claude") || id.startsWith("anthropic.") || id.startsWith("anthropic/");
|
|
1139
|
+
}
|
|
1140
|
+
function isQwen(id) {
|
|
1141
|
+
return id.includes("qwen");
|
|
1142
|
+
}
|
|
1143
|
+
function isNova(id) {
|
|
1144
|
+
return /(?:^|[./])nova(?:[.\-:]|$)/i.test(id) || id.includes("amazon.nova");
|
|
1145
|
+
}
|
|
1146
|
+
function isOpenAiBedrockId(id) {
|
|
1147
|
+
return id.startsWith("openai.") || id.startsWith("openai/") || isGptExplicit(id) || isGptImplicit(id);
|
|
1148
|
+
}
|
|
1149
|
+
function isRouterImplicit(id) {
|
|
1150
|
+
return /(?:^|\/)(gemini|google|deepseek|x-ai|xai|grok|moonshot|kimi)(?:[./:\-]|$)/i.test(id) || id.includes("gemini") || id.includes("deepseek");
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/providers/cloudflare.ts
|
|
1154
|
+
var import_openrouter_runtime = require("@x12i/openrouter-runtime");
|
|
1155
|
+
var DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
1156
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
1157
|
+
var DEFAULT_GATEWAY_ID = "default";
|
|
1158
|
+
function selectCloudflareEndpoint(input) {
|
|
1159
|
+
if (input.endpoint && input.endpoint !== "auto") return input.endpoint;
|
|
1160
|
+
if (input.apiMode === "chat") return "chat";
|
|
1161
|
+
if (input.apiMode === "responses") return "responses";
|
|
1162
|
+
const id = input.model.trim().toLowerCase();
|
|
1163
|
+
if (id.startsWith("anthropic/")) return "messages";
|
|
1164
|
+
if (id.startsWith("openai/") || isBareOpenAiModel(id)) return "responses";
|
|
1165
|
+
return "chat";
|
|
1166
|
+
}
|
|
1167
|
+
function cloudflareCatalogQuery(model) {
|
|
1168
|
+
const id = model.trim();
|
|
1169
|
+
const lower = id.toLowerCase();
|
|
1170
|
+
if (lower.startsWith("openai/")) {
|
|
1171
|
+
const slash = id.indexOf("/");
|
|
1172
|
+
return { target: "openai", model: id.slice(slash + 1), prefix: id.slice(0, slash + 1) };
|
|
1173
|
+
}
|
|
1174
|
+
if (isBareOpenAiModel(id)) return { target: "openai", model: id, prefix: "openai/" };
|
|
1175
|
+
return { target: "openrouter", model: id };
|
|
1176
|
+
}
|
|
1177
|
+
function restoreCloudflareModel(model, prefix) {
|
|
1178
|
+
if (!prefix || model.includes("/")) return model;
|
|
1179
|
+
return `${prefix}${model}`;
|
|
1180
|
+
}
|
|
1181
|
+
function createCloudflareProviderAdapter(options = {}) {
|
|
1182
|
+
const resolved = resolveCloudflareOptions(options);
|
|
1183
|
+
return {
|
|
1184
|
+
compile(request, call) {
|
|
1185
|
+
return compileCloudflareRequest(request, resolved, call, false);
|
|
1186
|
+
},
|
|
1187
|
+
async run(request, call) {
|
|
1188
|
+
const requestId = request.id ?? "cloudflare";
|
|
1189
|
+
try {
|
|
1190
|
+
return await runCloudflare(request, resolved, call, requestId);
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
|
|
1193
|
+
return failedResponse(request, requestId, error, "chat");
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
executeStreamingChat(request, call) {
|
|
1197
|
+
return streamCloudflare(request, resolved, call);
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function compileCloudflareRequest(request, options, call, streaming) {
|
|
1202
|
+
assertSupported(request);
|
|
1203
|
+
assertCredentials(options);
|
|
1204
|
+
const model = (request.model ?? options.defaultModel ?? "").trim();
|
|
1205
|
+
if (!model) {
|
|
1206
|
+
throw new AiDispatcherError(
|
|
1207
|
+
"MODEL_REQUIRED",
|
|
1208
|
+
"A model id is required. Set request.model or cloudflare.defaultModel."
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
const endpoint = call?.cloudflare?.endpoint ?? selectCloudflareEndpoint({
|
|
1212
|
+
model,
|
|
1213
|
+
...options.endpoint ? { endpoint: options.endpoint } : {},
|
|
1214
|
+
...request.apiMode ? { apiMode: request.apiMode } : {}
|
|
1215
|
+
});
|
|
1216
|
+
if (streaming && endpoint === "run") {
|
|
1217
|
+
throw new AiDispatcherError(
|
|
1218
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1219
|
+
"Cloudflare /ai/run does not stream. Use chat, responses, or messages, or call run()."
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
if ((endpoint === "messages" || endpoint === "run") && request.responseFormat !== void 0) {
|
|
1223
|
+
throw new AiDispatcherError(
|
|
1224
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1225
|
+
"responseFormat is supported on Cloudflare chat and responses, not on messages or run."
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const run = endpoint === "run" ? call?.cloudflare?.run : void 0;
|
|
1229
|
+
assertRunOptions(run);
|
|
1230
|
+
let body = endpoint === "responses" ? compileResponsesBody(request, model) : endpoint === "messages" ? compileMessagesBody(request, model) : compileChatBody(request, model);
|
|
1231
|
+
if (call?.finalizeBody) body = call.finalizeBody(body);
|
|
1232
|
+
if (endpoint !== "run") body = { ...body, stream: streaming };
|
|
1233
|
+
const gateway = mergeGateway(options.gateway, call?.cloudflare?.gateway);
|
|
1234
|
+
const fromBody = isRecord5(body.metadata) ? body.metadata : void 0;
|
|
1235
|
+
const record2 = (0, import_openrouter_runtime.toProviderMetadata)(
|
|
1236
|
+
request.metadata,
|
|
1237
|
+
request.agentId,
|
|
1238
|
+
import_openrouter_runtime.OPENROUTER_METADATA_LIMITS,
|
|
1239
|
+
overlayMetadata(gateway?.metadata, fromBody)
|
|
1240
|
+
);
|
|
1241
|
+
body = applyProviderRecord(body, record2);
|
|
1242
|
+
if (endpoint === "run") body = wrapRun(body, run);
|
|
1243
|
+
return {
|
|
1244
|
+
endpoint,
|
|
1245
|
+
url: endpointUrl(options, endpoint),
|
|
1246
|
+
headers: buildHeaders(options, call?.cloudflare, model, record2),
|
|
1247
|
+
body,
|
|
1248
|
+
warnings: []
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function compileResponsesBody(request, model) {
|
|
1252
|
+
const instructions = compileInstructions(request);
|
|
1253
|
+
const input = compileResponsesInput(request);
|
|
1254
|
+
if (input === void 0) {
|
|
1255
|
+
throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
|
|
1256
|
+
}
|
|
1257
|
+
return clean({
|
|
1258
|
+
model,
|
|
1259
|
+
input,
|
|
1260
|
+
instructions,
|
|
1261
|
+
temperature: request.temperature,
|
|
1262
|
+
max_output_tokens: request.maxTokens,
|
|
1263
|
+
reasoning: request.reasoning,
|
|
1264
|
+
text: request.responseFormat,
|
|
1265
|
+
tools: compileResponsesTools(request),
|
|
1266
|
+
tool_choice: compileResponsesToolChoice(request)
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
function compileChatBody(request, model) {
|
|
1270
|
+
const messages = compileChatMessages(request);
|
|
1271
|
+
if (!messages.length) {
|
|
1272
|
+
throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
|
|
1273
|
+
}
|
|
1274
|
+
return clean({
|
|
1275
|
+
model,
|
|
1276
|
+
messages,
|
|
1277
|
+
temperature: request.temperature,
|
|
1278
|
+
max_tokens: request.maxTokens,
|
|
1279
|
+
reasoning: request.reasoning,
|
|
1280
|
+
response_format: request.responseFormat,
|
|
1281
|
+
tools: compileChatTools(request),
|
|
1282
|
+
tool_choice: compileChatToolChoice(request)
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
function compileMessagesBody(request, model) {
|
|
1286
|
+
const system = compileInstructions(request);
|
|
1287
|
+
const messages = compileAnthropicMessages(request);
|
|
1288
|
+
if (!messages.length) {
|
|
1289
|
+
throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
|
|
1290
|
+
}
|
|
1291
|
+
return clean({
|
|
1292
|
+
model,
|
|
1293
|
+
system,
|
|
1294
|
+
messages,
|
|
1295
|
+
temperature: request.temperature,
|
|
1296
|
+
max_tokens: request.maxTokens,
|
|
1297
|
+
tools: compileAnthropicTools(request),
|
|
1298
|
+
tool_choice: compileAnthropicToolChoice(request)
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function wrapRun(body, run) {
|
|
1302
|
+
const model = body.model;
|
|
1303
|
+
const input = { ...body };
|
|
1304
|
+
delete input.model;
|
|
1305
|
+
delete input.stream;
|
|
1306
|
+
return clean({
|
|
1307
|
+
model,
|
|
1308
|
+
input,
|
|
1309
|
+
options: runOptions(run)
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
function runOptions(run) {
|
|
1313
|
+
if (!run) return void 0;
|
|
1314
|
+
return clean({
|
|
1315
|
+
background: run.background,
|
|
1316
|
+
webhookUrl: run.webhookUrl,
|
|
1317
|
+
webhookFormat: run.webhookFormat
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
function assertRunOptions(run) {
|
|
1321
|
+
if (!run) return;
|
|
1322
|
+
if (run.webhookUrl && run.background !== true) {
|
|
1323
|
+
throw new AiDispatcherError(
|
|
1324
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1325
|
+
"Cloudflare webhookUrl requires run.background to be true."
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
if (run.webhookFormat && !run.webhookUrl) {
|
|
1329
|
+
throw new AiDispatcherError(
|
|
1330
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1331
|
+
"Cloudflare webhookFormat requires run.webhookUrl."
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
function assertSupported(request) {
|
|
1336
|
+
if (request.serverTools && serverToolsEnabled(request.serverTools)) {
|
|
1337
|
+
throw new AiDispatcherError(
|
|
1338
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1339
|
+
"Cloudflare does not support OpenRouter server tools."
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
if (request.rawOpenRouterOverrides !== void 0) {
|
|
1343
|
+
throw new AiDispatcherError(
|
|
1344
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1345
|
+
"rawOpenRouterOverrides cannot be sent to Cloudflare."
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
if (request.toolChoice && typeof request.toolChoice === "object" && request.toolChoice.type === "server_tool") {
|
|
1349
|
+
throw new AiDispatcherError(
|
|
1350
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1351
|
+
"Cloudflare does not support OpenRouter server tool choices."
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function assertCredentials(options) {
|
|
1356
|
+
if (!options.apiToken) {
|
|
1357
|
+
throw new AiDispatcherError(
|
|
1358
|
+
"CLOUDFLARE_API_TOKEN_MISSING",
|
|
1359
|
+
"Cloudflare API token is required. Set cloudflare.apiToken or CLOUDFLARE_API_TOKEN."
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
if (!options.accountId) {
|
|
1363
|
+
throw new AiDispatcherError(
|
|
1364
|
+
"CLOUDFLARE_ACCOUNT_ID_MISSING",
|
|
1365
|
+
"Cloudflare account id is required. Set cloudflare.accountId or CLOUDFLARE_ACCOUNT_ID."
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function serverToolsEnabled(policy) {
|
|
1370
|
+
return Object.values(policy).some((value) => {
|
|
1371
|
+
if (Array.isArray(value)) return value.some((item) => isEnabledMode(item));
|
|
1372
|
+
return isEnabledMode(value);
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
function isEnabledMode(value) {
|
|
1376
|
+
return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
|
|
1377
|
+
}
|
|
1378
|
+
function compileInstructions(request) {
|
|
1379
|
+
if (request.instructions) return request.instructions;
|
|
1380
|
+
if (request.system) return request.system;
|
|
1381
|
+
const systemMessages = (request.messages ?? []).filter((message) => message.role === "system");
|
|
1382
|
+
if (!systemMessages.length) return void 0;
|
|
1383
|
+
return systemMessages.map((message) => contentToText(message.content)).filter(Boolean).join("\n");
|
|
1384
|
+
}
|
|
1385
|
+
function compileResponsesInput(request) {
|
|
1386
|
+
if (request.input !== void 0) return request.input;
|
|
1387
|
+
const items = [];
|
|
1388
|
+
for (const message of request.messages ?? []) {
|
|
1389
|
+
if (message.role === "system") continue;
|
|
1390
|
+
items.push(compileResponsesItem(message));
|
|
1391
|
+
}
|
|
1392
|
+
if (typeof request.prompt === "string") items.push({ role: "user", content: request.prompt });
|
|
1393
|
+
return items.length ? items : void 0;
|
|
1394
|
+
}
|
|
1395
|
+
function compileResponsesItem(message) {
|
|
1396
|
+
if (message.role === "tool") {
|
|
1397
|
+
return {
|
|
1398
|
+
type: "function_call_output",
|
|
1399
|
+
call_id: message.toolCallId ?? message.name ?? "tool",
|
|
1400
|
+
output: contentToText(message.content)
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
const role = message.role === "assistant" ? "assistant" : "user";
|
|
1404
|
+
return { role, content: compileResponsesContent(message.content) };
|
|
1405
|
+
}
|
|
1406
|
+
function compileResponsesContent(content) {
|
|
1407
|
+
if (typeof content === "string") return content;
|
|
1408
|
+
return content.map((part) => {
|
|
1409
|
+
if (part.type === "text") return { type: "input_text", text: part.text };
|
|
1410
|
+
if (part.type === "image_url") return { type: "input_image", image_url: part.imageUrl };
|
|
1411
|
+
return compileFilePart(part, "responses");
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
function compileChatMessages(request) {
|
|
1415
|
+
assertExclusiveInput(request);
|
|
1416
|
+
const messages = [];
|
|
1417
|
+
const system = compileInstructions(request);
|
|
1418
|
+
if (system) messages.push({ role: "system", content: system });
|
|
1419
|
+
for (const message of dialogueMessages(request)) messages.push(compileChatMessage(message));
|
|
1420
|
+
return messages;
|
|
1421
|
+
}
|
|
1422
|
+
function compileChatMessage(message) {
|
|
1423
|
+
if (message.role === "tool") {
|
|
1424
|
+
return {
|
|
1425
|
+
role: "tool",
|
|
1426
|
+
tool_call_id: message.toolCallId ?? message.name ?? "tool",
|
|
1427
|
+
content: contentToText(message.content)
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
return {
|
|
1431
|
+
role: message.role === "assistant" ? "assistant" : "user",
|
|
1432
|
+
content: compileChatContent(message.content)
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function compileChatContent(content) {
|
|
1436
|
+
if (typeof content === "string") return content;
|
|
1437
|
+
return content.map((part) => {
|
|
1438
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
1439
|
+
if (part.type === "image_url") return { type: "image_url", image_url: { url: part.imageUrl } };
|
|
1440
|
+
const file = compileFilePart(part, "chat");
|
|
1441
|
+
return file;
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
function compileAnthropicMessages(request) {
|
|
1445
|
+
assertExclusiveInput(request);
|
|
1446
|
+
return dialogueMessages(request).map(compileAnthropicMessage);
|
|
1447
|
+
}
|
|
1448
|
+
function compileAnthropicMessage(message) {
|
|
1449
|
+
if (message.role === "tool") {
|
|
1450
|
+
return {
|
|
1451
|
+
role: "user",
|
|
1452
|
+
content: [{
|
|
1453
|
+
type: "tool_result",
|
|
1454
|
+
tool_use_id: message.toolCallId ?? message.name ?? "tool",
|
|
1455
|
+
content: contentToText(message.content)
|
|
1456
|
+
}]
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
const role = message.role === "assistant" ? "assistant" : "user";
|
|
1460
|
+
return { role, content: compileAnthropicContent(message.content) };
|
|
1461
|
+
}
|
|
1462
|
+
function compileAnthropicContent(content) {
|
|
1463
|
+
if (typeof content === "string") return content;
|
|
1464
|
+
return content.map((part) => {
|
|
1465
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
1466
|
+
if (part.type === "image_url") return anthropicImage(part.imageUrl);
|
|
1467
|
+
return anthropicFile(part);
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function dialogueMessages(request) {
|
|
1471
|
+
const source = request.messages?.length ? request.messages.filter((message) => message.role !== "system") : messagesFromInput(request);
|
|
1472
|
+
if (typeof request.prompt === "string" && request.messages?.length) {
|
|
1473
|
+
return [...source, { role: "user", content: request.prompt }];
|
|
1474
|
+
}
|
|
1475
|
+
if (!source.length && typeof request.prompt === "string") return [{ role: "user", content: request.prompt }];
|
|
1476
|
+
return source;
|
|
1477
|
+
}
|
|
1478
|
+
function messagesFromInput(request) {
|
|
1479
|
+
if (typeof request.input === "string") return [{ role: "user", content: request.input }];
|
|
1480
|
+
if (!Array.isArray(request.input)) return [];
|
|
1481
|
+
const messages = [];
|
|
1482
|
+
for (const item of request.input) {
|
|
1483
|
+
if (typeof item === "string") {
|
|
1484
|
+
messages.push({ role: "user", content: item });
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (!isRecord5(item)) continue;
|
|
1488
|
+
const role = item.role === "assistant" || item.role === "tool" || item.role === "system" ? item.role : "user";
|
|
1489
|
+
if (role === "system") continue;
|
|
1490
|
+
const content = inputContent(item.content);
|
|
1491
|
+
if (content === void 0) continue;
|
|
1492
|
+
const message = { role, content };
|
|
1493
|
+
if (typeof item.toolCallId === "string") message.toolCallId = item.toolCallId;
|
|
1494
|
+
if (typeof item.name === "string") message.name = item.name;
|
|
1495
|
+
messages.push(message);
|
|
1496
|
+
}
|
|
1497
|
+
return messages;
|
|
1498
|
+
}
|
|
1499
|
+
function inputContent(content) {
|
|
1500
|
+
if (typeof content === "string") return content;
|
|
1501
|
+
if (!Array.isArray(content)) return void 0;
|
|
1502
|
+
const parts = [];
|
|
1503
|
+
for (const part of content) {
|
|
1504
|
+
if (!isRecord5(part)) continue;
|
|
1505
|
+
if ((part.type === "text" || part.type === "input_text") && typeof part.text === "string") {
|
|
1506
|
+
parts.push({ type: "text", text: part.text });
|
|
1507
|
+
} else if (part.type === "image_url" && typeof part.imageUrl === "string") {
|
|
1508
|
+
parts.push({ type: "image_url", imageUrl: part.imageUrl });
|
|
1509
|
+
} else if (part.type === "image_url" && isRecord5(part.image_url) && typeof part.image_url.url === "string") {
|
|
1510
|
+
parts.push({ type: "image_url", imageUrl: part.image_url.url });
|
|
1511
|
+
} else if (part.type === "input_image" && typeof part.image_url === "string") {
|
|
1512
|
+
parts.push({ type: "image_url", imageUrl: part.image_url });
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return parts.length ? parts : void 0;
|
|
1516
|
+
}
|
|
1517
|
+
function assertExclusiveInput(request) {
|
|
1518
|
+
const hasMessages2 = Array.isArray(request.messages) && request.messages.length > 0;
|
|
1519
|
+
if (hasMessages2 && request.input !== void 0) {
|
|
1520
|
+
throw new AiDispatcherError(
|
|
1521
|
+
"UNSUPPORTED_REQUEST_FIELD",
|
|
1522
|
+
"Cloudflare chat, messages, and run accept messages or input, not both."
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
function compileFilePart(part, dialect) {
|
|
1527
|
+
if (part.type !== "file") {
|
|
1528
|
+
throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "File content parts need a url or data.");
|
|
1529
|
+
}
|
|
1530
|
+
if (!part.url && !part.data) {
|
|
1531
|
+
throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "File content parts need a url or data.");
|
|
1532
|
+
}
|
|
1533
|
+
if (dialect === "responses") {
|
|
1534
|
+
if (part.url) return clean({ type: "input_file", file_url: part.url, filename: part.fileName });
|
|
1535
|
+
return clean({ type: "input_file", file_data: part.data, filename: part.fileName });
|
|
1536
|
+
}
|
|
1537
|
+
return {
|
|
1538
|
+
type: "file",
|
|
1539
|
+
file: clean({
|
|
1540
|
+
filename: part.fileName,
|
|
1541
|
+
...part.url ? { url: part.url } : {},
|
|
1542
|
+
...part.data ? { file_data: part.data } : {}
|
|
1543
|
+
})
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
function anthropicImage(imageUrl) {
|
|
1547
|
+
const data = dataUrl(imageUrl);
|
|
1548
|
+
if (data) return { type: "image", source: { type: "base64", media_type: data.mediaType, data: data.data } };
|
|
1549
|
+
return { type: "image", source: { type: "url", url: imageUrl } };
|
|
1550
|
+
}
|
|
1551
|
+
function anthropicFile(part) {
|
|
1552
|
+
if (part.type !== "file" || !part.url && !part.data) {
|
|
1553
|
+
throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "File content parts need a url or data.");
|
|
1554
|
+
}
|
|
1555
|
+
if (part.url) {
|
|
1556
|
+
const data = dataUrl(part.url);
|
|
1557
|
+
if (data) return { type: "document", source: { type: "base64", media_type: data.mediaType, data: data.data } };
|
|
1558
|
+
return { type: "document", source: { type: "url", url: part.url } };
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
type: "document",
|
|
1562
|
+
source: {
|
|
1563
|
+
type: "base64",
|
|
1564
|
+
media_type: part.mimeType || "application/octet-stream",
|
|
1565
|
+
data: part.data
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function dataUrl(value) {
|
|
1570
|
+
const match = /^data:([^;,]+);base64,([\s\S]+)$/i.exec(value);
|
|
1571
|
+
if (!match) return void 0;
|
|
1572
|
+
return { mediaType: match[1] ?? "application/octet-stream", data: match[2] ?? "" };
|
|
1573
|
+
}
|
|
1574
|
+
function compileResponsesTools(request) {
|
|
1575
|
+
const tools = request.functionTools ?? [];
|
|
1576
|
+
if (!tools.length || request.toolChoice === "none") return void 0;
|
|
1577
|
+
return tools.map((tool) => ({
|
|
1578
|
+
type: "function",
|
|
1579
|
+
name: tool.name,
|
|
1580
|
+
description: tool.description,
|
|
1581
|
+
parameters: tool.parameters
|
|
1582
|
+
}));
|
|
1583
|
+
}
|
|
1584
|
+
function compileChatTools(request) {
|
|
1585
|
+
const tools = request.functionTools ?? [];
|
|
1586
|
+
if (!tools.length || request.toolChoice === "none") return void 0;
|
|
1587
|
+
return tools.map((tool) => ({
|
|
1588
|
+
type: "function",
|
|
1589
|
+
function: {
|
|
1590
|
+
name: tool.name,
|
|
1591
|
+
description: tool.description,
|
|
1592
|
+
parameters: tool.parameters
|
|
1593
|
+
}
|
|
1594
|
+
}));
|
|
1595
|
+
}
|
|
1596
|
+
function compileAnthropicTools(request) {
|
|
1597
|
+
const tools = request.functionTools ?? [];
|
|
1598
|
+
if (!tools.length || request.toolChoice === "none") return void 0;
|
|
1599
|
+
return tools.map((tool) => ({
|
|
1600
|
+
name: tool.name,
|
|
1601
|
+
description: tool.description,
|
|
1602
|
+
input_schema: tool.parameters
|
|
1603
|
+
}));
|
|
1604
|
+
}
|
|
1605
|
+
function compileResponsesToolChoice(request) {
|
|
1606
|
+
if (!request.functionTools?.length || request.toolChoice === "none") return void 0;
|
|
1607
|
+
const choice = request.toolChoice;
|
|
1608
|
+
if (!choice || choice === "auto") return void 0;
|
|
1609
|
+
if (choice === "required") return "required";
|
|
1610
|
+
if (typeof choice === "object" && choice.type === "function") return { type: "function", name: choice.functionName };
|
|
1611
|
+
return void 0;
|
|
1612
|
+
}
|
|
1613
|
+
function compileChatToolChoice(request) {
|
|
1614
|
+
if (!request.functionTools?.length || request.toolChoice === "none") return void 0;
|
|
1615
|
+
const choice = request.toolChoice;
|
|
1616
|
+
if (!choice || choice === "auto") return void 0;
|
|
1617
|
+
if (choice === "required") return "required";
|
|
1618
|
+
if (typeof choice === "object" && choice.type === "function") {
|
|
1619
|
+
return { type: "function", function: { name: choice.functionName } };
|
|
1620
|
+
}
|
|
1621
|
+
return void 0;
|
|
1622
|
+
}
|
|
1623
|
+
function compileAnthropicToolChoice(request) {
|
|
1624
|
+
if (!request.functionTools?.length || request.toolChoice === "none") return void 0;
|
|
1625
|
+
const choice = request.toolChoice;
|
|
1626
|
+
if (!choice || choice === "auto") return void 0;
|
|
1627
|
+
if (choice === "required") return { type: "any" };
|
|
1628
|
+
if (typeof choice === "object" && choice.type === "function") return { type: "tool", name: choice.functionName };
|
|
1629
|
+
return void 0;
|
|
1630
|
+
}
|
|
1631
|
+
function contentToText(content) {
|
|
1632
|
+
if (typeof content === "string") return content;
|
|
1633
|
+
return content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("");
|
|
1634
|
+
}
|
|
1635
|
+
async function runCloudflare(request, options, call, requestId) {
|
|
1636
|
+
const maxIterations = request.execution?.maxToolIterations ?? 8;
|
|
1637
|
+
const maxFunctionToolCalls = request.execution?.maxFunctionToolCalls ?? 20;
|
|
1638
|
+
const functionUsage = [];
|
|
1639
|
+
let active;
|
|
1640
|
+
try {
|
|
1641
|
+
active = compileCloudflareRequest(request, options, call, false);
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
|
|
1644
|
+
return failedResponse(request, requestId, error, "chat");
|
|
1645
|
+
}
|
|
1646
|
+
let response;
|
|
1647
|
+
try {
|
|
1648
|
+
for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
|
|
1649
|
+
response = await sendWithRetries(active, options, timeoutFor(request, options));
|
|
1650
|
+
if (active.endpoint === "run" && !chatCalls(unwrap(response)).length) break;
|
|
1651
|
+
const calls = extractCalls(active.endpoint, response);
|
|
1652
|
+
if (!calls.length) break;
|
|
1653
|
+
if (!calls.every((item) => resolveExecutor(item.name, request, options))) {
|
|
1654
|
+
return normalizeCloudflareResponse({
|
|
1655
|
+
request,
|
|
1656
|
+
requestId,
|
|
1657
|
+
compiled: active,
|
|
1658
|
+
response,
|
|
1659
|
+
functionTools: [...functionUsage, ...calls],
|
|
1660
|
+
status: "requires_action"
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
if (functionUsage.length + calls.length > maxFunctionToolCalls) {
|
|
1664
|
+
throw new AiDispatcherError("FUNCTION_TOOL_LIMIT", "maxFunctionToolCalls exceeded.");
|
|
1665
|
+
}
|
|
1666
|
+
const results = [];
|
|
1667
|
+
for (const item of calls) results.push(await executeTool(item, request, options));
|
|
1668
|
+
functionUsage.push(...results);
|
|
1669
|
+
active = { ...active, body: continueBody(active, response, results) };
|
|
1670
|
+
}
|
|
1671
|
+
return normalizeCloudflareResponse({
|
|
1672
|
+
request,
|
|
1673
|
+
requestId,
|
|
1674
|
+
compiled: active,
|
|
1675
|
+
response,
|
|
1676
|
+
functionTools: functionUsage,
|
|
1677
|
+
status: "completed"
|
|
1678
|
+
});
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
|
|
1681
|
+
return failedResponse(request, requestId, error, responseApiMode(active.endpoint));
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function continueBody(compiled, response, results) {
|
|
1685
|
+
if (compiled.endpoint === "responses") return continueResponses(compiled.body, response, results);
|
|
1686
|
+
if (compiled.endpoint === "messages") return continueMessages(compiled.body, response, results);
|
|
1687
|
+
if (compiled.endpoint === "run") return continueRun(compiled.body, response, results);
|
|
1688
|
+
return continueChat(compiled.body, response, results);
|
|
1689
|
+
}
|
|
1690
|
+
function continueResponses(body, response, results) {
|
|
1691
|
+
const record2 = unwrap(response);
|
|
1692
|
+
return clean({
|
|
1693
|
+
...body,
|
|
1694
|
+
tool_choice: "auto",
|
|
1695
|
+
previous_response_id: typeof record2.id === "string" ? record2.id : void 0,
|
|
1696
|
+
input: results.map(toFunctionCallOutput)
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
function continueChat(body, response, results) {
|
|
1700
|
+
const messages = Array.isArray(body.messages) ? [...body.messages] : [];
|
|
1701
|
+
const assistant = chatAssistantMessage(unwrap(response));
|
|
1702
|
+
if (assistant) messages.push(assistant);
|
|
1703
|
+
for (const result of results) {
|
|
1704
|
+
messages.push({
|
|
1705
|
+
role: "tool",
|
|
1706
|
+
tool_call_id: result.callId,
|
|
1707
|
+
content: outputText(result)
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
return { ...body, messages, ...body.tool_choice !== void 0 ? { tool_choice: "auto" } : {} };
|
|
1711
|
+
}
|
|
1712
|
+
function continueMessages(body, response, results) {
|
|
1713
|
+
const messages = Array.isArray(body.messages) ? [...body.messages] : [];
|
|
1714
|
+
const record2 = unwrap(response);
|
|
1715
|
+
messages.push({ role: "assistant", content: Array.isArray(record2.content) ? record2.content : [] });
|
|
1716
|
+
messages.push({
|
|
1717
|
+
role: "user",
|
|
1718
|
+
content: results.map((result) => ({
|
|
1719
|
+
type: "tool_result",
|
|
1720
|
+
tool_use_id: result.callId,
|
|
1721
|
+
content: outputText(result),
|
|
1722
|
+
...result.status === "failed" ? { is_error: true } : {}
|
|
1723
|
+
}))
|
|
1724
|
+
});
|
|
1725
|
+
return { ...body, messages };
|
|
1726
|
+
}
|
|
1727
|
+
function continueRun(body, response, results) {
|
|
1728
|
+
const input = isRecord5(body.input) ? body.input : {};
|
|
1729
|
+
const continued = continueChat({ messages: input.messages }, response, results);
|
|
1730
|
+
return { ...body, input: { ...input, messages: continued.messages } };
|
|
1731
|
+
}
|
|
1732
|
+
function toFunctionCallOutput(result) {
|
|
1733
|
+
return {
|
|
1734
|
+
type: "function_call_output",
|
|
1735
|
+
call_id: result.callId,
|
|
1736
|
+
output: outputText(result)
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
function outputText(result) {
|
|
1740
|
+
const output = result.status === "completed" ? result.result : { error: result.error };
|
|
1741
|
+
return typeof output === "string" ? output : JSON.stringify(output);
|
|
1742
|
+
}
|
|
1743
|
+
function resolveExecutor(name, request, options) {
|
|
1744
|
+
return request.functionTools?.find((tool) => tool.name === name)?.executor ?? options.tools?.[name];
|
|
1745
|
+
}
|
|
1746
|
+
async function executeTool(call, request, options) {
|
|
1747
|
+
const executor = resolveExecutor(call.name, request, options);
|
|
1748
|
+
if (!executor) return call;
|
|
1749
|
+
const started = Date.now();
|
|
1750
|
+
try {
|
|
1751
|
+
options.logger?.debug?.("runtime.function_tool.started", { name: call.name, callId: call.callId });
|
|
1752
|
+
const result = await executor(call.args, {
|
|
1753
|
+
requestId: request.id ?? "",
|
|
1754
|
+
toolName: call.name,
|
|
1755
|
+
callId: call.callId,
|
|
1756
|
+
...request.metadata ? { metadata: request.metadata } : {}
|
|
1757
|
+
});
|
|
1758
|
+
const usage = {
|
|
1759
|
+
name: call.name,
|
|
1760
|
+
callId: call.callId,
|
|
1761
|
+
status: "completed",
|
|
1762
|
+
args: call.args,
|
|
1763
|
+
result,
|
|
1764
|
+
durationMs: Date.now() - started
|
|
1765
|
+
};
|
|
1766
|
+
options.logger?.debug?.("runtime.function_tool.completed", { name: call.name, callId: call.callId });
|
|
1767
|
+
return usage;
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
const usage = {
|
|
1770
|
+
name: call.name,
|
|
1771
|
+
callId: call.callId,
|
|
1772
|
+
status: "failed",
|
|
1773
|
+
args: call.args,
|
|
1774
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1775
|
+
durationMs: Date.now() - started
|
|
1776
|
+
};
|
|
1777
|
+
options.logger?.warn?.("runtime.function_tool.failed", usage);
|
|
1778
|
+
return usage;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
async function* streamCloudflare(request, options, call) {
|
|
1782
|
+
const requestId = request.id ?? "";
|
|
1783
|
+
let compiled;
|
|
1784
|
+
try {
|
|
1785
|
+
compiled = compileCloudflareRequest(request, options, call, true);
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
yield errorEvent(requestId, error);
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
yield {
|
|
1791
|
+
type: "stream.start",
|
|
1792
|
+
requestId,
|
|
1793
|
+
data: {
|
|
1794
|
+
model: modelOf(compiled),
|
|
1795
|
+
apiMode: responseApiMode(compiled.endpoint),
|
|
1796
|
+
entrypoint: "executeStreamingChat"
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
const controller = new AbortController();
|
|
1800
|
+
const timeout = setTimeout(() => controller.abort(), timeoutFor(request, options));
|
|
1801
|
+
let text = "";
|
|
1802
|
+
let reported = false;
|
|
1803
|
+
try {
|
|
1804
|
+
const response = await options.fetch(compiled.url, {
|
|
1805
|
+
method: "POST",
|
|
1806
|
+
headers: { ...compiled.headers, Accept: "text/event-stream" },
|
|
1807
|
+
body: JSON.stringify(compiled.body),
|
|
1808
|
+
signal: controller.signal
|
|
1809
|
+
});
|
|
1810
|
+
if (!response.ok) {
|
|
1811
|
+
const failure = await readFailure(response);
|
|
1812
|
+
reported = true;
|
|
1813
|
+
yield errorEvent(requestId, failure);
|
|
1814
|
+
throw failure;
|
|
1815
|
+
}
|
|
1816
|
+
for await (const event of parseSse(response)) {
|
|
1817
|
+
if (!isRecord5(event)) continue;
|
|
1818
|
+
const catalogReasoning = call?.extractStreamReasoning?.(event);
|
|
1819
|
+
const reasoningText = catalogReasoning || reasoningDelta(event);
|
|
1820
|
+
if (reasoningText) yield { type: "stream.reasoning.delta", requestId, data: { text: reasoningText } };
|
|
1821
|
+
const delta = textDelta(event);
|
|
1822
|
+
if (delta) {
|
|
1823
|
+
text += delta;
|
|
1824
|
+
yield { type: "stream.text.delta", requestId, data: { text: delta } };
|
|
1825
|
+
}
|
|
1826
|
+
const toolDelta = toolCallDelta(event);
|
|
1827
|
+
if (toolDelta) yield { type: "stream.tool_call.delta", requestId, data: toolDelta };
|
|
1828
|
+
const usage = streamUsage(event);
|
|
1829
|
+
if (usage) yield { type: "stream.usage", requestId, data: { usage } };
|
|
1830
|
+
if (event.type === "error") {
|
|
1831
|
+
const failure = new AiDispatcherError(
|
|
1832
|
+
"PROVIDER_REQUEST_FAILED",
|
|
1833
|
+
typeof event.message === "string" ? event.message : "Cloudflare stream returned an error event.",
|
|
1834
|
+
event
|
|
1835
|
+
);
|
|
1836
|
+
reported = true;
|
|
1837
|
+
yield errorEvent(requestId, failure);
|
|
1838
|
+
throw failure;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
yield { type: "stream.done", requestId, data: { text, model: modelOf(compiled) } };
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
const failure = error instanceof Error && error.name === "AbortError" ? new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "Cloudflare request timed out.") : error instanceof AiDispatcherError ? error : new AiDispatcherError(
|
|
1844
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1845
|
+
error instanceof Error ? error.message : "Cloudflare request failed before receiving a response."
|
|
1846
|
+
);
|
|
1847
|
+
if (!reported) yield errorEvent(requestId, failure);
|
|
1848
|
+
throw failure;
|
|
1849
|
+
} finally {
|
|
1850
|
+
clearTimeout(timeout);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
async function sendWithRetries(compiled, options, timeoutMs) {
|
|
1854
|
+
let lastError;
|
|
1855
|
+
for (let attempt = 1; attempt <= options.maxAttempts; attempt += 1) {
|
|
1856
|
+
try {
|
|
1857
|
+
return await sendOnce(compiled, options, timeoutMs);
|
|
1858
|
+
} catch (error) {
|
|
1859
|
+
lastError = error;
|
|
1860
|
+
const retryable = error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE";
|
|
1861
|
+
if (!retryable || attempt >= options.maxAttempts) throw asTerminalError(error);
|
|
1862
|
+
await delay(Math.min(500 * 2 ** (attempt - 1), 5e3));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
throw lastError;
|
|
1866
|
+
}
|
|
1867
|
+
async function sendOnce(compiled, options, timeoutMs) {
|
|
1868
|
+
const controller = new AbortController();
|
|
1869
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1870
|
+
try {
|
|
1871
|
+
const response = await options.fetch(compiled.url, {
|
|
1872
|
+
method: "POST",
|
|
1873
|
+
headers: compiled.headers,
|
|
1874
|
+
body: JSON.stringify(compiled.body),
|
|
1875
|
+
signal: controller.signal
|
|
1876
|
+
});
|
|
1877
|
+
if (!response.ok) throw await readFailure(response);
|
|
1878
|
+
const text = await response.text();
|
|
1879
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
1880
|
+
if (isRecord5(parsed) && parsed.success === false) {
|
|
1881
|
+
throw new AiDispatcherError(
|
|
1882
|
+
"PROVIDER_REQUEST_FAILED",
|
|
1883
|
+
messageFrom(parsed) ?? "Cloudflare request failed.",
|
|
1884
|
+
{ body: parsed }
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
return parsed;
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
if (error instanceof AiDispatcherError) throw error;
|
|
1890
|
+
if (error instanceof SyntaxError) {
|
|
1891
|
+
throw new AiDispatcherError("PROVIDER_REQUEST_FAILED", "Cloudflare returned a response that was not JSON.");
|
|
1892
|
+
}
|
|
1893
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1894
|
+
throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "Cloudflare request timed out.");
|
|
1895
|
+
}
|
|
1896
|
+
throw new AiDispatcherError(
|
|
1897
|
+
"PROVIDER_GATEWAY_UNREACHABLE",
|
|
1898
|
+
error instanceof Error ? error.message : "Cloudflare request failed before receiving a response."
|
|
1899
|
+
);
|
|
1900
|
+
} finally {
|
|
1901
|
+
clearTimeout(timeout);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
async function readFailure(response) {
|
|
1905
|
+
const text = await response.text();
|
|
1906
|
+
let body = text;
|
|
1907
|
+
try {
|
|
1908
|
+
body = text ? JSON.parse(text) : void 0;
|
|
1909
|
+
} catch {
|
|
1910
|
+
body = text;
|
|
1911
|
+
}
|
|
1912
|
+
const message = messageFrom(body) ?? `Cloudflare request failed with status ${response.status}.`;
|
|
1913
|
+
const retryable = response.status === 429 || response.status === 408 || response.status >= 500;
|
|
1914
|
+
return new AiDispatcherError(classifyStatus(response.status), message, {
|
|
1915
|
+
status: response.status,
|
|
1916
|
+
body,
|
|
1917
|
+
retryable
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
function classifyStatus(status) {
|
|
1921
|
+
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
1922
|
+
if (status === 404) return "PROVIDER_MODEL_NOT_FOUND";
|
|
1923
|
+
if (status === 429 || status === 408 || status >= 500) return "PROVIDER_RETRYABLE";
|
|
1924
|
+
return "PROVIDER_REQUEST_FAILED";
|
|
1925
|
+
}
|
|
1926
|
+
function isConfigError(code) {
|
|
1927
|
+
return code === "CLOUDFLARE_API_TOKEN_MISSING" || code === "CLOUDFLARE_ACCOUNT_ID_MISSING" || code === "MODEL_REQUIRED" || code === "INPUT_REQUIRED" || code === "UNSUPPORTED_REQUEST_FIELD";
|
|
1928
|
+
}
|
|
1929
|
+
function asTerminalError(error) {
|
|
1930
|
+
if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
|
|
1931
|
+
const status = isRecord5(error.details) ? error.details.status : void 0;
|
|
1932
|
+
if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
|
|
1933
|
+
return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
|
|
1934
|
+
}
|
|
1935
|
+
if (error instanceof AiDispatcherError) return error;
|
|
1936
|
+
return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1937
|
+
}
|
|
1938
|
+
function normalizeCloudflareResponse(params) {
|
|
1939
|
+
const payload = unwrap(params.response);
|
|
1940
|
+
const calls = params.functionTools ?? extractCalls(params.compiled.endpoint, params.response);
|
|
1941
|
+
const usage = normalizeUsage(payload.usage) ?? normalizeUsage(isRecord5(params.response) ? params.response.usage : void 0);
|
|
1942
|
+
const status = params.status ?? (calls.length ? "requires_action" : "completed");
|
|
1943
|
+
return {
|
|
1944
|
+
id: typeof payload.id === "string" ? payload.id : params.requestId,
|
|
1945
|
+
status,
|
|
1946
|
+
apiMode: responseApiMode(params.compiled.endpoint),
|
|
1947
|
+
model: typeof payload.model === "string" ? payload.model : modelOf(params.compiled),
|
|
1948
|
+
text: answerText(payload),
|
|
1949
|
+
citations: [],
|
|
1950
|
+
images: [],
|
|
1951
|
+
patches: [],
|
|
1952
|
+
toolUsage: emptyToolUsage(calls),
|
|
1953
|
+
...usage ? { usage } : {},
|
|
1954
|
+
warnings: params.compiled.warnings,
|
|
1955
|
+
errors: [],
|
|
1956
|
+
raw: { request: params.compiled.body, response: params.response },
|
|
1957
|
+
...params.request.metadata ? { metadata: params.request.metadata } : {}
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function responseApiMode(endpoint) {
|
|
1961
|
+
return endpoint === "responses" ? "responses" : "chat";
|
|
1962
|
+
}
|
|
1963
|
+
function answerText(response) {
|
|
1964
|
+
if (typeof response.output_text === "string") return response.output_text;
|
|
1965
|
+
if (typeof response.text === "string" && !Array.isArray(response.choices) && !Array.isArray(response.content)) {
|
|
1966
|
+
return response.text;
|
|
1967
|
+
}
|
|
1968
|
+
const responses = responsesAnswer(response);
|
|
1969
|
+
if (responses) return responses;
|
|
1970
|
+
const chat = chatAnswer(response);
|
|
1971
|
+
if (chat) return chat;
|
|
1972
|
+
const anthropic = anthropicAnswer(response);
|
|
1973
|
+
if (anthropic) return anthropic;
|
|
1974
|
+
if (isRecord5(response.result)) return answerText(response.result);
|
|
1975
|
+
return "";
|
|
1976
|
+
}
|
|
1977
|
+
function responsesAnswer(response) {
|
|
1978
|
+
if (!Array.isArray(response.output)) return "";
|
|
1979
|
+
const parts = [];
|
|
1980
|
+
for (const item of response.output) {
|
|
1981
|
+
if (!isRecord5(item) || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
1982
|
+
for (const part of item.content) {
|
|
1983
|
+
if (isRecord5(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return parts.join("");
|
|
1987
|
+
}
|
|
1988
|
+
function chatAnswer(response) {
|
|
1989
|
+
const message = chatAssistantMessage(response);
|
|
1990
|
+
if (!message) return "";
|
|
1991
|
+
const content = message.content;
|
|
1992
|
+
if (typeof content === "string") return content;
|
|
1993
|
+
if (!Array.isArray(content)) return "";
|
|
1994
|
+
return content.map((part) => isRecord5(part) && typeof part.text === "string" ? part.text : "").filter(Boolean).join("");
|
|
1995
|
+
}
|
|
1996
|
+
function anthropicAnswer(response) {
|
|
1997
|
+
if (!Array.isArray(response.content)) return "";
|
|
1998
|
+
return response.content.map((part) => isRecord5(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("");
|
|
1999
|
+
}
|
|
2000
|
+
function chatAssistantMessage(response) {
|
|
2001
|
+
if (!Array.isArray(response.choices) || !isRecord5(response.choices[0])) return void 0;
|
|
2002
|
+
const message = response.choices[0].message;
|
|
2003
|
+
return isRecord5(message) ? message : void 0;
|
|
2004
|
+
}
|
|
2005
|
+
function extractCalls(endpoint, response) {
|
|
2006
|
+
const record2 = unwrap(response);
|
|
2007
|
+
if (endpoint === "responses") return responsesCalls(record2);
|
|
2008
|
+
if (endpoint === "messages") return anthropicCalls(record2);
|
|
2009
|
+
return chatCalls(record2);
|
|
2010
|
+
}
|
|
2011
|
+
function responsesCalls(response) {
|
|
2012
|
+
if (!Array.isArray(response.output)) return [];
|
|
2013
|
+
const calls = [];
|
|
2014
|
+
for (const item of response.output) {
|
|
2015
|
+
if (!isRecord5(item) || item.type !== "function_call") continue;
|
|
2016
|
+
calls.push(callUsage(
|
|
2017
|
+
typeof item.name === "string" ? item.name : "function",
|
|
2018
|
+
typeof item.call_id === "string" ? item.call_id : "function",
|
|
2019
|
+
item.arguments
|
|
2020
|
+
));
|
|
2021
|
+
}
|
|
2022
|
+
return calls;
|
|
2023
|
+
}
|
|
2024
|
+
function chatCalls(response) {
|
|
2025
|
+
const message = chatAssistantMessage(response);
|
|
2026
|
+
if (!message || !Array.isArray(message.tool_calls)) return [];
|
|
2027
|
+
const calls = [];
|
|
2028
|
+
for (const item of message.tool_calls) {
|
|
2029
|
+
if (!isRecord5(item) || !isRecord5(item.function)) continue;
|
|
2030
|
+
const name = typeof item.function.name === "string" ? item.function.name : "function";
|
|
2031
|
+
calls.push(callUsage(
|
|
2032
|
+
name,
|
|
2033
|
+
typeof item.id === "string" ? item.id : name,
|
|
2034
|
+
item.function.arguments
|
|
2035
|
+
));
|
|
2036
|
+
}
|
|
2037
|
+
return calls;
|
|
2038
|
+
}
|
|
2039
|
+
function anthropicCalls(response) {
|
|
2040
|
+
if (!Array.isArray(response.content)) return [];
|
|
2041
|
+
const calls = [];
|
|
2042
|
+
for (const item of response.content) {
|
|
2043
|
+
if (!isRecord5(item) || item.type !== "tool_use") continue;
|
|
2044
|
+
const name = typeof item.name === "string" ? item.name : "function";
|
|
2045
|
+
calls.push({
|
|
2046
|
+
name,
|
|
2047
|
+
callId: typeof item.id === "string" ? item.id : name,
|
|
2048
|
+
status: "completed",
|
|
2049
|
+
args: item.input
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
return calls;
|
|
2053
|
+
}
|
|
2054
|
+
function callUsage(name, callId, rawArgs) {
|
|
2055
|
+
let args = rawArgs;
|
|
2056
|
+
if (typeof rawArgs === "string") {
|
|
2057
|
+
try {
|
|
2058
|
+
args = JSON.parse(rawArgs);
|
|
2059
|
+
} catch {
|
|
2060
|
+
args = rawArgs;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return { name, callId, status: "completed", args };
|
|
2064
|
+
}
|
|
2065
|
+
function emptyToolUsage(calls) {
|
|
2066
|
+
const unused = { requested: false, required: false, used: false };
|
|
2067
|
+
return {
|
|
2068
|
+
serverTools: {
|
|
2069
|
+
webSearch: unused,
|
|
2070
|
+
webFetch: unused,
|
|
2071
|
+
datetime: unused,
|
|
2072
|
+
imageGeneration: unused,
|
|
2073
|
+
applyPatch: unused,
|
|
2074
|
+
fusion: unused,
|
|
2075
|
+
advisor: unused,
|
|
2076
|
+
subagent: unused
|
|
2077
|
+
},
|
|
2078
|
+
functionTools: calls
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
function normalizeUsage(usage) {
|
|
2082
|
+
if (!isRecord5(usage)) return void 0;
|
|
2083
|
+
const inputTokens = numberValue(usage.input_tokens) ?? numberValue(usage.prompt_tokens);
|
|
2084
|
+
const outputTokens = numberValue(usage.output_tokens) ?? numberValue(usage.completion_tokens);
|
|
2085
|
+
const totalTokens = numberValue(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
|
|
2086
|
+
if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0) return { raw: usage };
|
|
2087
|
+
return {
|
|
2088
|
+
raw: usage,
|
|
2089
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
2090
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
2091
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
function failedResponse(request, requestId, error, apiMode) {
|
|
2095
|
+
const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
2096
|
+
return {
|
|
2097
|
+
id: requestId,
|
|
2098
|
+
status: "failed",
|
|
2099
|
+
apiMode,
|
|
2100
|
+
model: request.model ?? "",
|
|
2101
|
+
text: "",
|
|
2102
|
+
citations: [],
|
|
2103
|
+
images: [],
|
|
2104
|
+
patches: [],
|
|
2105
|
+
toolUsage: emptyToolUsage([]),
|
|
2106
|
+
warnings: [],
|
|
2107
|
+
errors: [{
|
|
2108
|
+
code: normalized.code,
|
|
2109
|
+
message: normalized.message,
|
|
2110
|
+
source: "runtime",
|
|
2111
|
+
details: normalized.details
|
|
2112
|
+
}],
|
|
2113
|
+
raw: { request, response: void 0 }
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
function errorEvent(requestId, error) {
|
|
2117
|
+
const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
2118
|
+
return {
|
|
2119
|
+
type: "stream.error",
|
|
2120
|
+
requestId,
|
|
2121
|
+
data: {
|
|
2122
|
+
error: {
|
|
2123
|
+
code: normalized.code,
|
|
2124
|
+
message: normalized.message,
|
|
2125
|
+
source: "runtime",
|
|
2126
|
+
details: normalized.details
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
513
2130
|
}
|
|
514
|
-
function
|
|
515
|
-
if (
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
"An MCP server needs an id, a tools array, and a callTool function."
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
if (seen.has(server.id)) {
|
|
522
|
-
throw new AiDispatcherError("MCP_SERVER_INVALID", `MCP server "${server.id}" is listed more than once.`);
|
|
2131
|
+
function textDelta(event) {
|
|
2132
|
+
if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) return event.delta;
|
|
2133
|
+
if (event.type === "content_block_delta" && isRecord5(event.delta) && event.delta.type === "text_delta" && typeof event.delta.text === "string" && event.delta.text) {
|
|
2134
|
+
return event.delta.text;
|
|
523
2135
|
}
|
|
524
|
-
|
|
525
|
-
if (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
2136
|
+
const choice = firstChoiceDelta(event);
|
|
2137
|
+
if (choice && typeof choice.content === "string" && choice.content) return choice.content;
|
|
2138
|
+
return void 0;
|
|
2139
|
+
}
|
|
2140
|
+
function reasoningDelta(event) {
|
|
2141
|
+
if (event.type === "response.reasoning_summary_text.delta" && typeof event.delta === "string" && event.delta) return event.delta;
|
|
2142
|
+
if (event.type === "content_block_delta" && isRecord5(event.delta) && event.delta.type === "thinking_delta" && typeof event.delta.thinking === "string" && event.delta.thinking) {
|
|
2143
|
+
return event.delta.thinking;
|
|
530
2144
|
}
|
|
2145
|
+
const choice = firstChoiceDelta(event);
|
|
2146
|
+
if (!choice) return void 0;
|
|
2147
|
+
if (typeof choice.reasoning === "string" && choice.reasoning) return choice.reasoning;
|
|
2148
|
+
if (typeof choice.reasoning_content === "string" && choice.reasoning_content) return choice.reasoning_content;
|
|
2149
|
+
return void 0;
|
|
531
2150
|
}
|
|
532
|
-
function
|
|
533
|
-
if (
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
2151
|
+
function toolCallDelta(event) {
|
|
2152
|
+
if (event.type === "response.function_call_arguments.delta" && typeof event.delta === "string") {
|
|
2153
|
+
return {
|
|
2154
|
+
index: 0,
|
|
2155
|
+
...typeof event.name === "string" ? { name: event.name } : {},
|
|
2156
|
+
argumentsDelta: event.delta
|
|
2157
|
+
};
|
|
538
2158
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
throw new AiDispatcherError(
|
|
542
|
-
"MCP_TOOL_NAME_INVALID",
|
|
543
|
-
`MCP tool "${exposedName}" is longer than ${MAX_EXPOSED_NAME_LENGTH} characters.`
|
|
544
|
-
);
|
|
2159
|
+
if (event.type === "content_block_delta" && isRecord5(event.delta) && event.delta.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
|
|
2160
|
+
return { index: typeof event.index === "number" ? event.index : 0, argumentsDelta: event.delta.partial_json };
|
|
545
2161
|
}
|
|
546
|
-
|
|
2162
|
+
const choice = firstChoiceDelta(event);
|
|
2163
|
+
if (!choice || !Array.isArray(choice.tool_calls) || !isRecord5(choice.tool_calls[0])) return void 0;
|
|
2164
|
+
const tool = choice.tool_calls[0];
|
|
2165
|
+
const fn = isRecord5(tool.function) ? tool.function : void 0;
|
|
2166
|
+
const args = fn && typeof fn.arguments === "string" ? fn.arguments : void 0;
|
|
2167
|
+
if (!args && !(fn && typeof fn.name === "string")) return void 0;
|
|
2168
|
+
return {
|
|
2169
|
+
index: typeof tool.index === "number" ? tool.index : 0,
|
|
2170
|
+
...fn && typeof fn.name === "string" ? { name: fn.name } : {},
|
|
2171
|
+
...args ? { argumentsDelta: args } : {}
|
|
2172
|
+
};
|
|
547
2173
|
}
|
|
548
|
-
function
|
|
549
|
-
if (
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
2174
|
+
function streamUsage(event) {
|
|
2175
|
+
if (event.type === "response.completed" && isRecord5(event.response)) return normalizeUsage(event.response.usage);
|
|
2176
|
+
if (isRecord5(event.usage)) return normalizeUsage(event.usage);
|
|
2177
|
+
return void 0;
|
|
2178
|
+
}
|
|
2179
|
+
function firstChoiceDelta(event) {
|
|
2180
|
+
if (!Array.isArray(event.choices) || !isRecord5(event.choices[0])) return void 0;
|
|
2181
|
+
const delta = event.choices[0].delta;
|
|
2182
|
+
return isRecord5(delta) ? delta : void 0;
|
|
2183
|
+
}
|
|
2184
|
+
function unwrap(response) {
|
|
2185
|
+
if (!isRecord5(response)) return {};
|
|
2186
|
+
if (response.success === true && isRecord5(response.result)) return response.result;
|
|
2187
|
+
return response;
|
|
2188
|
+
}
|
|
2189
|
+
function modelOf(compiled) {
|
|
2190
|
+
if (typeof compiled.body.model === "string") return compiled.body.model;
|
|
2191
|
+
return "";
|
|
2192
|
+
}
|
|
2193
|
+
function messageFrom(body) {
|
|
2194
|
+
if (!isRecord5(body)) return void 0;
|
|
2195
|
+
if (typeof body.message === "string") return body.message;
|
|
2196
|
+
if (isRecord5(body.error) && typeof body.error.message === "string") return body.error.message;
|
|
2197
|
+
if (Array.isArray(body.errors)) {
|
|
2198
|
+
for (const error of body.errors) {
|
|
2199
|
+
if (isRecord5(error) && typeof error.message === "string") return error.message;
|
|
2200
|
+
}
|
|
555
2201
|
}
|
|
556
|
-
return
|
|
2202
|
+
return void 0;
|
|
557
2203
|
}
|
|
558
|
-
function
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
2204
|
+
function numberValue(value) {
|
|
2205
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2206
|
+
}
|
|
2207
|
+
function applyProviderRecord(body, metadata) {
|
|
2208
|
+
if (metadata) return { ...body, metadata };
|
|
2209
|
+
if (body.metadata === void 0) return body;
|
|
2210
|
+
const rest = { ...body };
|
|
2211
|
+
delete rest.metadata;
|
|
2212
|
+
return rest;
|
|
2213
|
+
}
|
|
2214
|
+
function overlayMetadata(base, extra) {
|
|
2215
|
+
if (!base) return extra;
|
|
2216
|
+
if (!extra) return base;
|
|
2217
|
+
return { ...base, ...extra };
|
|
2218
|
+
}
|
|
2219
|
+
function endpointUrl(options, endpoint) {
|
|
2220
|
+
const account = encodeURIComponent(options.accountId);
|
|
2221
|
+
const root = `${options.baseUrl}/accounts/${account}/ai`;
|
|
2222
|
+
if (endpoint === "chat") return `${root}/v1/chat/completions`;
|
|
2223
|
+
if (endpoint === "responses") return `${root}/v1/responses`;
|
|
2224
|
+
if (endpoint === "messages") return `${root}/v1/messages`;
|
|
2225
|
+
return `${root}/run`;
|
|
2226
|
+
}
|
|
2227
|
+
function buildHeaders(options, call, model, metadata) {
|
|
2228
|
+
const gatewayId = call?.gatewayId?.trim() || options.gatewayId?.trim() || (isWorkersModel(model) ? DEFAULT_GATEWAY_ID : void 0);
|
|
2229
|
+
const gateway = mergeGateway(options.gateway, call?.gateway);
|
|
2230
|
+
const cache = call?.cache;
|
|
2231
|
+
const skip = gateway?.skipCache !== void 0 ? gateway.skipCache : cache?.skip === true ? true : void 0;
|
|
2232
|
+
const ttl = gateway?.cacheTtlSeconds ?? cache?.ttlSeconds;
|
|
2233
|
+
const key = gateway?.cacheKey ?? cache?.key;
|
|
2234
|
+
return clean({
|
|
2235
|
+
"content-type": "application/json",
|
|
2236
|
+
authorization: `Bearer ${options.apiToken}`,
|
|
2237
|
+
...gatewayId ? { "cf-aig-gateway-id": gatewayId } : {},
|
|
2238
|
+
...skip !== void 0 ? { "cf-aig-skip-cache": String(skip) } : {},
|
|
2239
|
+
...ttl !== void 0 ? { "cf-aig-cache-ttl": String(ttl) } : {},
|
|
2240
|
+
...key ? { "cf-aig-cache-key": key } : {},
|
|
2241
|
+
...gateway?.collectLog !== void 0 ? { "cf-aig-collect-log": String(gateway.collectLog) } : {},
|
|
2242
|
+
...gateway?.requestTimeoutMs !== void 0 ? { "cf-aig-request-timeout": String(gateway.requestTimeoutMs) } : {},
|
|
2243
|
+
...gateway?.maxGatewayAttempts !== void 0 ? { "cf-aig-max-attempts": String(gateway.maxGatewayAttempts) } : {},
|
|
2244
|
+
...gateway?.retryDelayMs !== void 0 ? { "cf-aig-retry-delay": String(gateway.retryDelayMs) } : {},
|
|
2245
|
+
...isBackoff(gateway?.backoff) ? { "cf-aig-backoff": gateway.backoff } : {},
|
|
2246
|
+
...metadata ? { "cf-aig-metadata": JSON.stringify(metadata) } : {}
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
function mergeGateway(base, over) {
|
|
2250
|
+
if (!base && !over) return void 0;
|
|
2251
|
+
return { ...base, ...over };
|
|
2252
|
+
}
|
|
2253
|
+
function isBackoff(value) {
|
|
2254
|
+
return value === "constant" || value === "linear" || value === "exponential";
|
|
2255
|
+
}
|
|
2256
|
+
function isWorkersModel(model) {
|
|
2257
|
+
return model.trim().toLowerCase().startsWith("@cf/");
|
|
2258
|
+
}
|
|
2259
|
+
function isBareOpenAiModel(id) {
|
|
2260
|
+
return /^(gpt|o[134])(?:[.\-:]|$)/i.test(id.trim());
|
|
2261
|
+
}
|
|
2262
|
+
function timeoutFor(request, options) {
|
|
2263
|
+
return request.execution?.timeoutMs ?? options.timeoutMs;
|
|
2264
|
+
}
|
|
2265
|
+
function delay(ms) {
|
|
2266
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2267
|
+
}
|
|
2268
|
+
async function* parseSse(response) {
|
|
2269
|
+
const body = response.body;
|
|
2270
|
+
if (!body) throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "Cloudflare stream response had no body.");
|
|
2271
|
+
const reader = body.getReader();
|
|
2272
|
+
const decoder = new TextDecoder();
|
|
2273
|
+
let buffer = "";
|
|
2274
|
+
while (true) {
|
|
2275
|
+
const chunk = await reader.read();
|
|
2276
|
+
if (chunk.done) break;
|
|
2277
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
2278
|
+
const parts = buffer.split("\n\n");
|
|
2279
|
+
buffer = parts.pop() ?? "";
|
|
2280
|
+
for (const part of parts) {
|
|
2281
|
+
const parsed = parseSsePart(part);
|
|
2282
|
+
if (parsed !== void 0) yield parsed;
|
|
2283
|
+
}
|
|
565
2284
|
}
|
|
566
|
-
|
|
2285
|
+
const trailing = parseSsePart(buffer);
|
|
2286
|
+
if (trailing !== void 0) yield trailing;
|
|
567
2287
|
}
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
2288
|
+
function parseSsePart(part) {
|
|
2289
|
+
const data = part.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
|
|
2290
|
+
if (!data || data === "[DONE]") return void 0;
|
|
2291
|
+
try {
|
|
2292
|
+
return JSON.parse(data);
|
|
2293
|
+
} catch {
|
|
2294
|
+
return void 0;
|
|
574
2295
|
}
|
|
575
|
-
if (!parts.length) return void 0;
|
|
576
|
-
return parts.join("\n");
|
|
577
2296
|
}
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
2297
|
+
function resolveCloudflareOptions(options) {
|
|
2298
|
+
const {
|
|
2299
|
+
apiToken,
|
|
2300
|
+
accountId,
|
|
2301
|
+
baseUrl,
|
|
2302
|
+
defaultModel,
|
|
2303
|
+
endpoint,
|
|
2304
|
+
gatewayId,
|
|
2305
|
+
timeoutMs,
|
|
2306
|
+
maxAttempts,
|
|
2307
|
+
fetch,
|
|
2308
|
+
tools,
|
|
2309
|
+
logger,
|
|
2310
|
+
run,
|
|
2311
|
+
...gateway
|
|
2312
|
+
} = options;
|
|
2313
|
+
void run;
|
|
2314
|
+
return {
|
|
2315
|
+
apiToken: apiToken ?? readEnv("CLOUDFLARE_API_TOKEN") ?? "",
|
|
2316
|
+
accountId: accountId ?? readEnv("CLOUDFLARE_ACCOUNT_ID") ?? "",
|
|
2317
|
+
baseUrl: (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
2318
|
+
...defaultModel ? { defaultModel } : {},
|
|
2319
|
+
...endpoint ? { endpoint } : {},
|
|
2320
|
+
...gatewayId ? { gatewayId } : {},
|
|
2321
|
+
...hasGateway(gateway) ? { gateway } : {},
|
|
2322
|
+
timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
2323
|
+
maxAttempts: maxAttempts ?? 2,
|
|
2324
|
+
fetch: fetch ?? globalThis.fetch,
|
|
2325
|
+
...tools ? { tools } : {},
|
|
2326
|
+
...logger ? { logger } : {}
|
|
2327
|
+
};
|
|
581
2328
|
}
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
const message = error instanceof Error ? error.message : `MCP server "${serverId}" listTools failed.`;
|
|
585
|
-
return new AiDispatcherError("MCP_LIST_FAILED", message);
|
|
2329
|
+
function hasGateway(gateway) {
|
|
2330
|
+
return Object.values(gateway).some((value) => value !== void 0);
|
|
586
2331
|
}
|
|
587
|
-
function
|
|
588
|
-
|
|
589
|
-
return serialized === void 0 ? "null" : serialized;
|
|
2332
|
+
function clean(body) {
|
|
2333
|
+
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
|
|
590
2334
|
}
|
|
591
|
-
function
|
|
2335
|
+
function readEnv(name) {
|
|
2336
|
+
if (typeof process === "undefined") return void 0;
|
|
2337
|
+
return process.env[name];
|
|
2338
|
+
}
|
|
2339
|
+
function isRecord5(value) {
|
|
592
2340
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
593
2341
|
}
|
|
594
2342
|
|
|
595
2343
|
// src/prepare-request.ts
|
|
596
|
-
var import_ai_profiles = require("@x12i/ai-profiles");
|
|
597
2344
|
function prepareDispatchRequest(params) {
|
|
598
2345
|
const cloned = cloneRequest(params.request);
|
|
2346
|
+
applyDispatchIdentity(cloned);
|
|
2347
|
+
absorbOverrideMetadata(cloned);
|
|
599
2348
|
const provider = params.provider;
|
|
600
2349
|
const reasoningEffort = cloned.reasoningEffort;
|
|
601
2350
|
const bedrockControls = cloned.bedrockControls;
|
|
2351
|
+
const cachePolicy = cloned.cache;
|
|
2352
|
+
const cloudflareRequest = cloned.cloudflare;
|
|
602
2353
|
delete cloned.provider;
|
|
603
2354
|
delete cloned.reasoningEffort;
|
|
604
2355
|
delete cloned.bedrockControls;
|
|
2356
|
+
delete cloned.cache;
|
|
2357
|
+
delete cloned.cloudflare;
|
|
605
2358
|
if (provider === "openai") assertOpenAiConfigured(params.options);
|
|
2359
|
+
if (provider === "cloudflare") assertCloudflareConfigured(params.options);
|
|
606
2360
|
if (provider === "bedrock" && cloned.reasoning !== void 0) {
|
|
607
2361
|
if (bedrockControls?.additionalModelRequestFields === void 0 && reasoningEffort === void 0) {
|
|
608
2362
|
throw new AiDispatcherError(
|
|
@@ -615,6 +2369,7 @@ function prepareDispatchRequest(params) {
|
|
|
615
2369
|
const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
|
|
616
2370
|
const explicit = hasExplicitControl(provider, cloned, bedrockControls);
|
|
617
2371
|
let directive;
|
|
2372
|
+
let catalogPrefix;
|
|
618
2373
|
if (!explicit && reasoningEffort !== void 0) {
|
|
619
2374
|
if (!requestedModel) {
|
|
620
2375
|
throw new AiDispatcherError(
|
|
@@ -622,26 +2377,69 @@ function prepareDispatchRequest(params) {
|
|
|
622
2377
|
"reasoningEffort requires a model id. Set request.model or the provider defaultModel."
|
|
623
2378
|
);
|
|
624
2379
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
effort: reasoningEffort
|
|
629
|
-
})
|
|
2380
|
+
if (provider === "cloudflare") {
|
|
2381
|
+
const query = cloudflareCatalogQuery(requestedModel);
|
|
2382
|
+
catalogPrefix = query.prefix;
|
|
2383
|
+
directive = resolveCatalog({ target: query.target, model: query.model, effort: reasoningEffort }, true);
|
|
2384
|
+
} else if (isReasoningTarget(provider)) {
|
|
2385
|
+
directive = resolveCatalog({ target: provider, model: requestedModel, effort: reasoningEffort }, false);
|
|
2386
|
+
}
|
|
630
2387
|
}
|
|
631
2388
|
if (provider === "openrouter") {
|
|
632
2389
|
applyNestedReasoning(cloned, reasoningEffort);
|
|
633
2390
|
}
|
|
634
|
-
if (directive?.model)
|
|
2391
|
+
if (directive?.model) {
|
|
2392
|
+
cloned.model = provider === "cloudflare" ? restoreCloudflareModel(directive.model, catalogPrefix) : directive.model;
|
|
2393
|
+
}
|
|
635
2394
|
if (directive?.promptPrefix) applyPromptPrefix(cloned, directive.promptPrefix);
|
|
636
2395
|
const effectiveModel = cloned.model ?? requestedModel;
|
|
2396
|
+
const requestedEndpoint = cloudflareRequest?.endpoint ?? params.options.cloudflare?.endpoint;
|
|
2397
|
+
const cloudflareEndpoint = provider === "cloudflare" ? selectCloudflareEndpoint({
|
|
2398
|
+
model: effectiveModel,
|
|
2399
|
+
...requestedEndpoint ? { endpoint: requestedEndpoint } : {},
|
|
2400
|
+
...cloned.apiMode ? { apiMode: cloned.apiMode } : {}
|
|
2401
|
+
}) : void 0;
|
|
637
2402
|
const call = {};
|
|
638
2403
|
if (reasoningEffort !== void 0 && provider === "openrouter") {
|
|
639
2404
|
call.suppressNestedReasoningFallback = true;
|
|
640
2405
|
}
|
|
641
2406
|
const overlay = directive && hasOwnKeys(directive.body) ? directive.body : explicit && provider === "bedrock" && bedrockControls?.additionalModelRequestFields ? { additionalModelRequestFields: bedrockControls.additionalModelRequestFields } : void 0;
|
|
642
|
-
|
|
2407
|
+
const cache = cachePolicy ? resolvePromptCache({
|
|
2408
|
+
target: provider,
|
|
2409
|
+
requestedModel,
|
|
2410
|
+
effectiveModel,
|
|
2411
|
+
policy: cachePolicy,
|
|
2412
|
+
...cloudflareEndpoint ? { cloudflareEndpoint } : {}
|
|
2413
|
+
}) : void 0;
|
|
2414
|
+
const cachePlan = cache?.plan;
|
|
2415
|
+
const cacheWarnings = cache?.warnings;
|
|
2416
|
+
const cacheResolution = cache?.resolution;
|
|
2417
|
+
const cacheTouchesBody = Boolean(
|
|
2418
|
+
cachePlan && (cachePlan.stripMarkers || cachePlan.placeBreakpoint || cachePlan.sendOpenAiOptions)
|
|
2419
|
+
);
|
|
2420
|
+
if (overlay || cacheTouchesBody) {
|
|
643
2421
|
const bodyOverlay = overlay;
|
|
644
|
-
call.finalizeBody = (body) =>
|
|
2422
|
+
call.finalizeBody = (body) => {
|
|
2423
|
+
const merged = bodyOverlay ? (0, import_ai_profiles.mergeReasoningBody)(body, bodyOverlay) : body;
|
|
2424
|
+
if (!cacheTouchesBody || !cachePlan) return merged;
|
|
2425
|
+
const applied = applyPromptCache(merged, cachePlan);
|
|
2426
|
+
if (cachePlan.placeBreakpoint && !applied.placed) {
|
|
2427
|
+
noteUnplacedCache(cacheResolution, cacheWarnings, provider, effectiveModel);
|
|
2428
|
+
return merged;
|
|
2429
|
+
}
|
|
2430
|
+
return applied.body;
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
if (provider === "cloudflare" && cloudflareEndpoint) {
|
|
2434
|
+
const gateway = mergeCloudflareGateway(params.options.cloudflare, cloudflareRequest);
|
|
2435
|
+
const gatewayId = cloudflareRequest?.gatewayId ?? params.options.cloudflare?.gatewayId;
|
|
2436
|
+
call.cloudflare = {
|
|
2437
|
+
endpoint: cloudflareEndpoint,
|
|
2438
|
+
...gatewayId ? { gatewayId } : {},
|
|
2439
|
+
...cloudflareRequest?.run ? { run: cloudflareRequest.run } : {},
|
|
2440
|
+
...gateway ? { gateway } : {},
|
|
2441
|
+
...cache?.plan.gateway ? { cache: cache.plan.gateway } : {}
|
|
2442
|
+
};
|
|
645
2443
|
}
|
|
646
2444
|
if (directive && hasStreamMetadata(directive)) {
|
|
647
2445
|
const response = directive.response;
|
|
@@ -662,19 +2460,51 @@ function prepareDispatchRequest(params) {
|
|
|
662
2460
|
effectiveModel,
|
|
663
2461
|
...resolution ? { resolution } : {},
|
|
664
2462
|
...directive ? { directive } : {},
|
|
2463
|
+
...cacheResolution ? { cacheResolution } : {},
|
|
2464
|
+
...cacheWarnings ? { cacheWarnings } : {},
|
|
665
2465
|
call
|
|
666
2466
|
};
|
|
667
2467
|
}
|
|
668
|
-
function
|
|
2468
|
+
function noteUnplacedCache(resolution, warnings, target, model) {
|
|
2469
|
+
if (resolution) {
|
|
2470
|
+
resolution.outcome = "ignored";
|
|
2471
|
+
delete resolution.ttl;
|
|
2472
|
+
}
|
|
2473
|
+
if (!warnings) return;
|
|
2474
|
+
const kept = warnings.filter((warning) => warning.code !== "CACHE_TTL_DEGRADED");
|
|
2475
|
+
warnings.length = 0;
|
|
2476
|
+
warnings.push(...kept, {
|
|
2477
|
+
code: "CACHE_UNSUPPORTED",
|
|
2478
|
+
message: "Explicit prompt caching needs a system or instructions prefix. The call still runs without a breakpoint.",
|
|
2479
|
+
details: { target, model }
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
function resolveCatalog(input, ignoreUnknown) {
|
|
669
2483
|
try {
|
|
670
2484
|
return (0, import_ai_profiles.resolveReasoning)(input);
|
|
671
2485
|
} catch (error) {
|
|
672
2486
|
if (error instanceof import_ai_profiles.AIProfilesError) {
|
|
2487
|
+
if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
|
|
2488
|
+
return {
|
|
2489
|
+
outcome: "ignored",
|
|
2490
|
+
body: {},
|
|
2491
|
+
response: {
|
|
2492
|
+
reasoningTextPaths: [],
|
|
2493
|
+
streamReasoningKeys: [],
|
|
2494
|
+
streamReasoningPaths: [],
|
|
2495
|
+
stripFromHistoryPaths: [],
|
|
2496
|
+
stripThinkTags: false
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
673
2500
|
throw new AiDispatcherError(error.code, error.message, error.details);
|
|
674
2501
|
}
|
|
675
2502
|
throw error;
|
|
676
2503
|
}
|
|
677
2504
|
}
|
|
2505
|
+
function isReasoningTarget(provider) {
|
|
2506
|
+
return provider === "openrouter" || provider === "bedrock" || provider === "openai";
|
|
2507
|
+
}
|
|
678
2508
|
function hasExplicitControl(provider, request, bedrockControls) {
|
|
679
2509
|
if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
|
|
680
2510
|
return request.reasoning !== void 0;
|
|
@@ -708,12 +2538,12 @@ function settleNested(policy, kind, parentEffort) {
|
|
|
708
2538
|
if (!effort) return next;
|
|
709
2539
|
const model = kind === "fusion" ? next.judgeModel : next.model;
|
|
710
2540
|
if (!model) return next;
|
|
711
|
-
const directive = resolveCatalog({ target: "openrouter", model, effort });
|
|
2541
|
+
const directive = resolveCatalog({ target: "openrouter", model, effort }, false);
|
|
712
2542
|
if (directive.model) {
|
|
713
2543
|
if (kind === "fusion") next.judgeModel = directive.model;
|
|
714
2544
|
else next.model = directive.model;
|
|
715
2545
|
}
|
|
716
|
-
if (
|
|
2546
|
+
if (isRecord6(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
|
|
717
2547
|
return next;
|
|
718
2548
|
}
|
|
719
2549
|
function applyPromptPrefix(request, prefix) {
|
|
@@ -763,7 +2593,7 @@ function prefixInputItems(items, prefix) {
|
|
|
763
2593
|
}
|
|
764
2594
|
if (!Array.isArray(item.content)) continue;
|
|
765
2595
|
for (const part of item.content) {
|
|
766
|
-
if (!
|
|
2596
|
+
if (!isRecord6(part) || typeof part.text !== "string") continue;
|
|
767
2597
|
if (part.type === "text" || part.type === "input_text") {
|
|
768
2598
|
part.text = prefix + part.text;
|
|
769
2599
|
return true;
|
|
@@ -772,8 +2602,40 @@ function prefixInputItems(items, prefix) {
|
|
|
772
2602
|
}
|
|
773
2603
|
return false;
|
|
774
2604
|
}
|
|
2605
|
+
function mergeCloudflareGateway(base, over) {
|
|
2606
|
+
const gateway = {};
|
|
2607
|
+
for (const source of [base, over]) {
|
|
2608
|
+
if (!source) continue;
|
|
2609
|
+
if (source.skipCache !== void 0) gateway.skipCache = source.skipCache;
|
|
2610
|
+
if (source.cacheTtlSeconds !== void 0) gateway.cacheTtlSeconds = source.cacheTtlSeconds;
|
|
2611
|
+
if (source.cacheKey !== void 0) gateway.cacheKey = source.cacheKey;
|
|
2612
|
+
if (source.collectLog !== void 0) gateway.collectLog = source.collectLog;
|
|
2613
|
+
if (source.requestTimeoutMs !== void 0) gateway.requestTimeoutMs = source.requestTimeoutMs;
|
|
2614
|
+
if (source.maxGatewayAttempts !== void 0) gateway.maxGatewayAttempts = source.maxGatewayAttempts;
|
|
2615
|
+
if (source.retryDelayMs !== void 0) gateway.retryDelayMs = source.retryDelayMs;
|
|
2616
|
+
if (source.backoff !== void 0) gateway.backoff = source.backoff;
|
|
2617
|
+
if (source.metadata !== void 0) gateway.metadata = source.metadata;
|
|
2618
|
+
}
|
|
2619
|
+
return Object.keys(gateway).length ? gateway : void 0;
|
|
2620
|
+
}
|
|
2621
|
+
function assertCloudflareConfigured(options) {
|
|
2622
|
+
const apiToken = options.cloudflare?.apiToken ?? readEnv2("CLOUDFLARE_API_TOKEN");
|
|
2623
|
+
if (!apiToken) {
|
|
2624
|
+
throw new AiDispatcherError(
|
|
2625
|
+
"CLOUDFLARE_API_TOKEN_MISSING",
|
|
2626
|
+
"Cloudflare API token is required. Set cloudflare.apiToken or CLOUDFLARE_API_TOKEN."
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
const accountId = options.cloudflare?.accountId ?? readEnv2("CLOUDFLARE_ACCOUNT_ID");
|
|
2630
|
+
if (!accountId) {
|
|
2631
|
+
throw new AiDispatcherError(
|
|
2632
|
+
"CLOUDFLARE_ACCOUNT_ID_MISSING",
|
|
2633
|
+
"Cloudflare account id is required. Set cloudflare.accountId or CLOUDFLARE_ACCOUNT_ID."
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
775
2637
|
function assertOpenAiConfigured(options) {
|
|
776
|
-
const apiKey = options.openai?.apiKey ??
|
|
2638
|
+
const apiKey = options.openai?.apiKey ?? readEnv2("OPENAI_API_KEY");
|
|
777
2639
|
if (!apiKey) {
|
|
778
2640
|
throw new AiDispatcherError(
|
|
779
2641
|
"OPENAI_API_KEY_MISSING",
|
|
@@ -784,8 +2646,40 @@ function assertOpenAiConfigured(options) {
|
|
|
784
2646
|
function defaultModelFor(provider, options) {
|
|
785
2647
|
if (provider === "openrouter") return options.openrouter?.defaultModel;
|
|
786
2648
|
if (provider === "bedrock") return options.bedrock?.defaultModel;
|
|
2649
|
+
if (provider === "cloudflare") return options.cloudflare?.defaultModel;
|
|
787
2650
|
return options.openai?.defaultModel;
|
|
788
2651
|
}
|
|
2652
|
+
var DISPATCH_IDENTITY_FIELDS = ["orgId", "stepId", "skillId"];
|
|
2653
|
+
function absorbOverrideMetadata(request) {
|
|
2654
|
+
const overrides = request.rawOpenRouterOverrides;
|
|
2655
|
+
if (!isRecord6(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
|
|
2656
|
+
if (isRecord6(overrides.metadata)) {
|
|
2657
|
+
const current = { ...request.metadata ?? {} };
|
|
2658
|
+
const extra = {};
|
|
2659
|
+
for (const [key, value] of Object.entries(overrides.metadata)) {
|
|
2660
|
+
if (!Object.prototype.hasOwnProperty.call(current, key)) extra[key] = value;
|
|
2661
|
+
}
|
|
2662
|
+
request.metadata = { ...current, ...extra };
|
|
2663
|
+
}
|
|
2664
|
+
const remaining = {};
|
|
2665
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
2666
|
+
if (key !== "metadata") remaining[key] = value;
|
|
2667
|
+
}
|
|
2668
|
+
if (Object.keys(remaining).length === 0) delete request.rawOpenRouterOverrides;
|
|
2669
|
+
else request.rawOpenRouterOverrides = remaining;
|
|
2670
|
+
}
|
|
2671
|
+
function applyDispatchIdentity(request) {
|
|
2672
|
+
const identity = {};
|
|
2673
|
+
for (const field of DISPATCH_IDENTITY_FIELDS) {
|
|
2674
|
+
const value = request[field]?.trim();
|
|
2675
|
+
if (value) identity[field] = value;
|
|
2676
|
+
delete request[field];
|
|
2677
|
+
}
|
|
2678
|
+
if (!Object.keys(identity).length) return;
|
|
2679
|
+
const rest = { ...request.metadata ?? {} };
|
|
2680
|
+
for (const key of Object.keys(identity)) delete rest[key];
|
|
2681
|
+
request.metadata = { ...identity, ...rest };
|
|
2682
|
+
}
|
|
789
2683
|
function cloneRequest(request) {
|
|
790
2684
|
const functionTools = request.functionTools?.map((tool) => ({ ...tool }));
|
|
791
2685
|
const cloned = JSON.parse(JSON.stringify(request));
|
|
@@ -798,10 +2692,10 @@ function hasOwnKeys(value) {
|
|
|
798
2692
|
function hasStreamMetadata(directive) {
|
|
799
2693
|
return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
|
|
800
2694
|
}
|
|
801
|
-
function
|
|
2695
|
+
function isRecord6(value) {
|
|
802
2696
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
803
2697
|
}
|
|
804
|
-
function
|
|
2698
|
+
function readEnv2(name) {
|
|
805
2699
|
if (typeof process === "undefined") return void 0;
|
|
806
2700
|
return process.env[name];
|
|
807
2701
|
}
|
|
@@ -864,11 +2758,12 @@ function toBedrockRequest(request) {
|
|
|
864
2758
|
...request.execution.timeoutMs !== void 0 ? { timeoutMs: request.execution.timeoutMs } : {}
|
|
865
2759
|
};
|
|
866
2760
|
}
|
|
2761
|
+
if (request.agentId !== void 0) mapped.agentId = request.agentId;
|
|
867
2762
|
if (request.metadata !== void 0) mapped.metadata = request.metadata;
|
|
868
2763
|
return mapped;
|
|
869
2764
|
}
|
|
870
2765
|
function assertBedrockSupported(request) {
|
|
871
|
-
if (request.serverTools &&
|
|
2766
|
+
if (request.serverTools && serverToolsEnabled2(request.serverTools)) {
|
|
872
2767
|
throw new AiDispatcherError(
|
|
873
2768
|
"UNSUPPORTED_REQUEST_FIELD",
|
|
874
2769
|
"Bedrock does not support OpenRouter server tools."
|
|
@@ -902,13 +2797,13 @@ function assertBedrockSupported(request) {
|
|
|
902
2797
|
function hasMessages(request) {
|
|
903
2798
|
return Array.isArray(request.messages) && request.messages.length > 0;
|
|
904
2799
|
}
|
|
905
|
-
function
|
|
2800
|
+
function serverToolsEnabled2(policy) {
|
|
906
2801
|
return Object.values(policy).some((value) => {
|
|
907
|
-
if (Array.isArray(value)) return value.some((item) =>
|
|
908
|
-
return
|
|
2802
|
+
if (Array.isArray(value)) return value.some((item) => isEnabledMode2(item));
|
|
2803
|
+
return isEnabledMode2(value);
|
|
909
2804
|
});
|
|
910
2805
|
}
|
|
911
|
-
function
|
|
2806
|
+
function isEnabledMode2(value) {
|
|
912
2807
|
return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
|
|
913
2808
|
}
|
|
914
2809
|
function inputItemsToMessages(items) {
|
|
@@ -940,8 +2835,9 @@ function isBedrockToolChoice(toolChoice) {
|
|
|
940
2835
|
}
|
|
941
2836
|
|
|
942
2837
|
// src/providers/openai.ts
|
|
943
|
-
var
|
|
944
|
-
var
|
|
2838
|
+
var import_openrouter_runtime2 = require("@x12i/openrouter-runtime");
|
|
2839
|
+
var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
|
|
2840
|
+
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
945
2841
|
function createOpenAiProviderAdapter(options = {}) {
|
|
946
2842
|
const resolved = resolveOpenAiOptions(options);
|
|
947
2843
|
return {
|
|
@@ -953,8 +2849,8 @@ function createOpenAiProviderAdapter(options = {}) {
|
|
|
953
2849
|
try {
|
|
954
2850
|
return await runOpenAi(request, resolved, call, requestId);
|
|
955
2851
|
} catch (error) {
|
|
956
|
-
if (error instanceof AiDispatcherError &&
|
|
957
|
-
return
|
|
2852
|
+
if (error instanceof AiDispatcherError && isConfigError2(error.code)) throw error;
|
|
2853
|
+
return failedResponse2(request, requestId, error);
|
|
958
2854
|
}
|
|
959
2855
|
},
|
|
960
2856
|
executeStreamingChat(request, call) {
|
|
@@ -963,7 +2859,7 @@ function createOpenAiProviderAdapter(options = {}) {
|
|
|
963
2859
|
};
|
|
964
2860
|
}
|
|
965
2861
|
function compileOpenAiRequest(request, options, call, streaming) {
|
|
966
|
-
|
|
2862
|
+
assertSupported2(request);
|
|
967
2863
|
const model = (request.model ?? options.defaultModel ?? "").trim();
|
|
968
2864
|
if (!model) {
|
|
969
2865
|
throw new AiDispatcherError(
|
|
@@ -977,12 +2873,12 @@ function compileOpenAiRequest(request, options, call, streaming) {
|
|
|
977
2873
|
"OpenAI API key is required. Set openai.apiKey or OPENAI_API_KEY."
|
|
978
2874
|
);
|
|
979
2875
|
}
|
|
980
|
-
const instructions =
|
|
2876
|
+
const instructions = compileInstructions2(request);
|
|
981
2877
|
const input = compileInput(request);
|
|
982
2878
|
if (input === void 0) {
|
|
983
2879
|
throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
|
|
984
2880
|
}
|
|
985
|
-
let body =
|
|
2881
|
+
let body = clean2({
|
|
986
2882
|
model,
|
|
987
2883
|
input,
|
|
988
2884
|
instructions,
|
|
@@ -996,15 +2892,16 @@ function compileOpenAiRequest(request, options, call, streaming) {
|
|
|
996
2892
|
if (call?.finalizeBody) body = call.finalizeBody(body);
|
|
997
2893
|
if (streaming) body = { ...body, stream: true };
|
|
998
2894
|
else body = { ...body, stream: false };
|
|
2895
|
+
body = attachProviderMetadata(body, request);
|
|
999
2896
|
return {
|
|
1000
2897
|
url: `${options.baseUrl}/responses`,
|
|
1001
|
-
headers:
|
|
2898
|
+
headers: buildHeaders2(options),
|
|
1002
2899
|
body,
|
|
1003
2900
|
warnings: []
|
|
1004
2901
|
};
|
|
1005
2902
|
}
|
|
1006
|
-
function
|
|
1007
|
-
if (request.serverTools &&
|
|
2903
|
+
function assertSupported2(request) {
|
|
2904
|
+
if (request.serverTools && serverToolsEnabled3(request.serverTools)) {
|
|
1008
2905
|
throw new AiDispatcherError(
|
|
1009
2906
|
"UNSUPPORTED_REQUEST_FIELD",
|
|
1010
2907
|
"Direct OpenAI does not support OpenRouter server tools."
|
|
@@ -1029,21 +2926,21 @@ function assertSupported(request) {
|
|
|
1029
2926
|
);
|
|
1030
2927
|
}
|
|
1031
2928
|
}
|
|
1032
|
-
function
|
|
2929
|
+
function serverToolsEnabled3(policy) {
|
|
1033
2930
|
return Object.values(policy).some((value) => {
|
|
1034
|
-
if (Array.isArray(value)) return value.some((item) =>
|
|
1035
|
-
return
|
|
2931
|
+
if (Array.isArray(value)) return value.some((item) => isEnabledMode3(item));
|
|
2932
|
+
return isEnabledMode3(value);
|
|
1036
2933
|
});
|
|
1037
2934
|
}
|
|
1038
|
-
function
|
|
2935
|
+
function isEnabledMode3(value) {
|
|
1039
2936
|
return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
|
|
1040
2937
|
}
|
|
1041
|
-
function
|
|
2938
|
+
function compileInstructions2(request) {
|
|
1042
2939
|
if (request.instructions) return request.instructions;
|
|
1043
2940
|
if (request.system) return request.system;
|
|
1044
2941
|
const systemMessages = (request.messages ?? []).filter((message) => message.role === "system");
|
|
1045
2942
|
if (!systemMessages.length) return void 0;
|
|
1046
|
-
return systemMessages.map((message) =>
|
|
2943
|
+
return systemMessages.map((message) => contentToText2(message.content)).filter(Boolean).join("\n");
|
|
1047
2944
|
}
|
|
1048
2945
|
function compileInput(request) {
|
|
1049
2946
|
if (request.input !== void 0) return request.input;
|
|
@@ -1063,7 +2960,7 @@ function compileInputItem(message) {
|
|
|
1063
2960
|
return {
|
|
1064
2961
|
type: "function_call_output",
|
|
1065
2962
|
call_id: message.toolCallId ?? message.name ?? "tool",
|
|
1066
|
-
output:
|
|
2963
|
+
output: contentToText2(message.content)
|
|
1067
2964
|
};
|
|
1068
2965
|
}
|
|
1069
2966
|
const role = message.role === "assistant" ? "assistant" : "user";
|
|
@@ -1075,10 +2972,10 @@ function compileContent(content) {
|
|
|
1075
2972
|
if (part.type === "text") return { type: "input_text", text: part.text };
|
|
1076
2973
|
if (part.type === "image_url") return { type: "input_image", image_url: part.imageUrl };
|
|
1077
2974
|
if (part.url) {
|
|
1078
|
-
return
|
|
2975
|
+
return clean2({ type: "input_file", file_url: part.url, filename: part.fileName });
|
|
1079
2976
|
}
|
|
1080
2977
|
if (part.data) {
|
|
1081
|
-
return
|
|
2978
|
+
return clean2({ type: "input_file", file_data: part.data, filename: part.fileName });
|
|
1082
2979
|
}
|
|
1083
2980
|
throw new AiDispatcherError(
|
|
1084
2981
|
"UNSUPPORTED_REQUEST_FIELD",
|
|
@@ -1112,10 +3009,10 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
1112
3009
|
let active = compileOpenAiRequest(request, options, call, false);
|
|
1113
3010
|
let response;
|
|
1114
3011
|
for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
|
|
1115
|
-
response = await
|
|
1116
|
-
const calls = functionCalls(
|
|
3012
|
+
response = await sendWithRetries2(active, options, timeoutFor2(request, options));
|
|
3013
|
+
const calls = functionCalls(isRecord7(response) ? response : {});
|
|
1117
3014
|
if (!calls.length) break;
|
|
1118
|
-
if (!calls.every((item) =>
|
|
3015
|
+
if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
|
|
1119
3016
|
return normalizeOpenAiResponse({
|
|
1120
3017
|
request,
|
|
1121
3018
|
requestId,
|
|
@@ -1133,14 +3030,14 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
1133
3030
|
results.push(await executeOpenAiTool(item, request, options));
|
|
1134
3031
|
}
|
|
1135
3032
|
functionUsage.push(...results);
|
|
1136
|
-
const
|
|
3033
|
+
const record2 = isRecord7(response) ? response : {};
|
|
1137
3034
|
active = {
|
|
1138
3035
|
...active,
|
|
1139
|
-
body:
|
|
3036
|
+
body: clean2({
|
|
1140
3037
|
...active.body,
|
|
1141
3038
|
tool_choice: "auto",
|
|
1142
|
-
previous_response_id: typeof
|
|
1143
|
-
input: results.map(
|
|
3039
|
+
previous_response_id: typeof record2.id === "string" ? record2.id : void 0,
|
|
3040
|
+
input: results.map(toFunctionCallOutput2)
|
|
1144
3041
|
})
|
|
1145
3042
|
};
|
|
1146
3043
|
}
|
|
@@ -1153,11 +3050,11 @@ async function runOpenAi(request, options, call, requestId) {
|
|
|
1153
3050
|
status: "completed"
|
|
1154
3051
|
});
|
|
1155
3052
|
}
|
|
1156
|
-
function
|
|
3053
|
+
function resolveExecutor2(name, request, options) {
|
|
1157
3054
|
return request.functionTools?.find((tool) => tool.name === name)?.executor ?? options.tools?.[name];
|
|
1158
3055
|
}
|
|
1159
3056
|
async function executeOpenAiTool(call, request, options) {
|
|
1160
|
-
const executor =
|
|
3057
|
+
const executor = resolveExecutor2(call.name, request, options);
|
|
1161
3058
|
if (!executor) {
|
|
1162
3059
|
return call;
|
|
1163
3060
|
}
|
|
@@ -1193,7 +3090,7 @@ async function executeOpenAiTool(call, request, options) {
|
|
|
1193
3090
|
return usage;
|
|
1194
3091
|
}
|
|
1195
3092
|
}
|
|
1196
|
-
function
|
|
3093
|
+
function toFunctionCallOutput2(result) {
|
|
1197
3094
|
const output = result.status === "completed" ? result.result : { error: result.error };
|
|
1198
3095
|
return {
|
|
1199
3096
|
type: "function_call_output",
|
|
@@ -1201,7 +3098,7 @@ function toFunctionCallOutput(result) {
|
|
|
1201
3098
|
output: typeof output === "string" ? output : JSON.stringify(output)
|
|
1202
3099
|
};
|
|
1203
3100
|
}
|
|
1204
|
-
function
|
|
3101
|
+
function contentToText2(content) {
|
|
1205
3102
|
if (typeof content === "string") return content;
|
|
1206
3103
|
return content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("");
|
|
1207
3104
|
}
|
|
@@ -1211,7 +3108,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
1211
3108
|
try {
|
|
1212
3109
|
compiled = compileOpenAiRequest(request, options, call, true);
|
|
1213
3110
|
} catch (error) {
|
|
1214
|
-
yield
|
|
3111
|
+
yield errorEvent2(requestId, error);
|
|
1215
3112
|
throw error;
|
|
1216
3113
|
}
|
|
1217
3114
|
yield {
|
|
@@ -1224,7 +3121,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
1224
3121
|
}
|
|
1225
3122
|
};
|
|
1226
3123
|
const controller = new AbortController();
|
|
1227
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
3124
|
+
const timeout = setTimeout(() => controller.abort(), timeoutFor2(request, options));
|
|
1228
3125
|
let text = "";
|
|
1229
3126
|
let reported = false;
|
|
1230
3127
|
const report = (error) => {
|
|
@@ -1242,13 +3139,13 @@ async function* streamOpenAi(request, options, call) {
|
|
|
1242
3139
|
signal: controller.signal
|
|
1243
3140
|
});
|
|
1244
3141
|
if (!response.ok) {
|
|
1245
|
-
const failure = await
|
|
3142
|
+
const failure = await readFailure2(response);
|
|
1246
3143
|
reported = true;
|
|
1247
|
-
yield
|
|
3144
|
+
yield errorEvent2(requestId, failure);
|
|
1248
3145
|
throw failure;
|
|
1249
3146
|
}
|
|
1250
|
-
for await (const event of
|
|
1251
|
-
if (!
|
|
3147
|
+
for await (const event of parseSse2(response)) {
|
|
3148
|
+
if (!isRecord7(event)) continue;
|
|
1252
3149
|
if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
|
|
1253
3150
|
text += event.delta;
|
|
1254
3151
|
yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
|
|
@@ -1267,8 +3164,8 @@ async function* streamOpenAi(request, options, call) {
|
|
|
1267
3164
|
argumentsDelta: event.delta
|
|
1268
3165
|
}
|
|
1269
3166
|
};
|
|
1270
|
-
} else if (event.type === "response.completed" &&
|
|
1271
|
-
const usage =
|
|
3167
|
+
} else if (event.type === "response.completed" && isRecord7(event.response)) {
|
|
3168
|
+
const usage = normalizeUsage2(event.response.usage);
|
|
1272
3169
|
if (usage) yield { type: "stream.usage", requestId, data: { usage } };
|
|
1273
3170
|
} else if (event.type === "error") {
|
|
1274
3171
|
const failure = new AiDispatcherError(
|
|
@@ -1277,7 +3174,7 @@ async function* streamOpenAi(request, options, call) {
|
|
|
1277
3174
|
event
|
|
1278
3175
|
);
|
|
1279
3176
|
reported = true;
|
|
1280
|
-
yield
|
|
3177
|
+
yield errorEvent2(requestId, failure);
|
|
1281
3178
|
throw failure;
|
|
1282
3179
|
}
|
|
1283
3180
|
}
|
|
@@ -1291,28 +3188,28 @@ async function* streamOpenAi(request, options, call) {
|
|
|
1291
3188
|
};
|
|
1292
3189
|
} catch (error) {
|
|
1293
3190
|
const failure = error instanceof Error && error.name === "AbortError" ? new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI request timed out.") : report(error);
|
|
1294
|
-
if (!reported) yield
|
|
3191
|
+
if (!reported) yield errorEvent2(requestId, failure);
|
|
1295
3192
|
throw failure;
|
|
1296
3193
|
} finally {
|
|
1297
3194
|
clearTimeout(timeout);
|
|
1298
3195
|
}
|
|
1299
3196
|
}
|
|
1300
|
-
async function
|
|
3197
|
+
async function sendWithRetries2(compiled, options, timeoutMs) {
|
|
1301
3198
|
const attempts = options.maxAttempts;
|
|
1302
3199
|
let lastError;
|
|
1303
3200
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1304
3201
|
try {
|
|
1305
|
-
return await
|
|
3202
|
+
return await sendOnce2(compiled, options, timeoutMs);
|
|
1306
3203
|
} catch (error) {
|
|
1307
3204
|
lastError = error;
|
|
1308
3205
|
const retryable = error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE";
|
|
1309
|
-
if (!retryable || attempt >= attempts) throw
|
|
1310
|
-
await
|
|
3206
|
+
if (!retryable || attempt >= attempts) throw asTerminalError2(error);
|
|
3207
|
+
await delay2(Math.min(500 * 2 ** (attempt - 1), 5e3));
|
|
1311
3208
|
}
|
|
1312
3209
|
}
|
|
1313
3210
|
throw lastError;
|
|
1314
3211
|
}
|
|
1315
|
-
async function
|
|
3212
|
+
async function sendOnce2(compiled, options, timeoutMs) {
|
|
1316
3213
|
const controller = new AbortController();
|
|
1317
3214
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1318
3215
|
try {
|
|
@@ -1322,7 +3219,7 @@ async function sendOnce(compiled, options, timeoutMs) {
|
|
|
1322
3219
|
body: JSON.stringify(compiled.body),
|
|
1323
3220
|
signal: controller.signal
|
|
1324
3221
|
});
|
|
1325
|
-
if (!response.ok) throw await
|
|
3222
|
+
if (!response.ok) throw await readFailure2(response);
|
|
1326
3223
|
const text = await response.text();
|
|
1327
3224
|
return text ? JSON.parse(text) : {};
|
|
1328
3225
|
} catch (error) {
|
|
@@ -1338,7 +3235,7 @@ async function sendOnce(compiled, options, timeoutMs) {
|
|
|
1338
3235
|
clearTimeout(timeout);
|
|
1339
3236
|
}
|
|
1340
3237
|
}
|
|
1341
|
-
async function
|
|
3238
|
+
async function readFailure2(response) {
|
|
1342
3239
|
const text = await response.text();
|
|
1343
3240
|
let body = text;
|
|
1344
3241
|
try {
|
|
@@ -1346,21 +3243,21 @@ async function readFailure(response) {
|
|
|
1346
3243
|
} catch {
|
|
1347
3244
|
body = text;
|
|
1348
3245
|
}
|
|
1349
|
-
const message =
|
|
3246
|
+
const message = messageFrom2(body) ?? `OpenAI request failed with status ${response.status}.`;
|
|
1350
3247
|
const retryable = response.status === 429 || response.status >= 500;
|
|
1351
|
-
return new AiDispatcherError(
|
|
3248
|
+
return new AiDispatcherError(classifyStatus2(response.status), message, { status: response.status, body, retryable });
|
|
1352
3249
|
}
|
|
1353
|
-
function
|
|
3250
|
+
function classifyStatus2(status) {
|
|
1354
3251
|
if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
|
|
1355
3252
|
if (status === 404) return "PROVIDER_MODEL_NOT_FOUND";
|
|
1356
3253
|
if (status === 429) return "PROVIDER_RATE_LIMITED";
|
|
1357
3254
|
if (status === 408 || status >= 500) return "PROVIDER_RETRYABLE";
|
|
1358
3255
|
return "PROVIDER_REQUEST_FAILED";
|
|
1359
3256
|
}
|
|
1360
|
-
function
|
|
3257
|
+
function isConfigError2(code) {
|
|
1361
3258
|
return code === "OPENAI_API_KEY_MISSING" || code === "MODEL_REQUIRED" || code === "INPUT_REQUIRED" || code === "UNSUPPORTED_REQUEST_FIELD";
|
|
1362
3259
|
}
|
|
1363
|
-
function
|
|
3260
|
+
function asTerminalError2(error) {
|
|
1364
3261
|
if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
|
|
1365
3262
|
return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
|
|
1366
3263
|
}
|
|
@@ -1368,16 +3265,16 @@ function asTerminalError(error) {
|
|
|
1368
3265
|
return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1369
3266
|
}
|
|
1370
3267
|
function normalizeOpenAiResponse(params) {
|
|
1371
|
-
const
|
|
1372
|
-
const text =
|
|
1373
|
-
const calls = params.functionTools ?? functionCalls(
|
|
1374
|
-
const usage =
|
|
3268
|
+
const record2 = isRecord7(params.response) ? params.response : {};
|
|
3269
|
+
const text = answerText2(record2);
|
|
3270
|
+
const calls = params.functionTools ?? functionCalls(record2);
|
|
3271
|
+
const usage = normalizeUsage2(record2.usage);
|
|
1375
3272
|
const status = params.status ?? (calls.length ? "requires_action" : "completed");
|
|
1376
3273
|
return {
|
|
1377
|
-
id: typeof
|
|
3274
|
+
id: typeof record2.id === "string" ? record2.id : params.requestId,
|
|
1378
3275
|
status,
|
|
1379
3276
|
apiMode: "responses",
|
|
1380
|
-
model: typeof
|
|
3277
|
+
model: typeof record2.model === "string" ? record2.model : modelOf2(params.compiled),
|
|
1381
3278
|
text,
|
|
1382
3279
|
citations: [],
|
|
1383
3280
|
images: [],
|
|
@@ -1390,14 +3287,14 @@ function normalizeOpenAiResponse(params) {
|
|
|
1390
3287
|
...params.request.metadata ? { metadata: params.request.metadata } : {}
|
|
1391
3288
|
};
|
|
1392
3289
|
}
|
|
1393
|
-
function
|
|
3290
|
+
function answerText2(response) {
|
|
1394
3291
|
const output = response.output;
|
|
1395
3292
|
if (Array.isArray(output)) {
|
|
1396
3293
|
const parts = [];
|
|
1397
3294
|
for (const item of output) {
|
|
1398
|
-
if (!
|
|
3295
|
+
if (!isRecord7(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
|
|
1399
3296
|
for (const part of item.content) {
|
|
1400
|
-
if (
|
|
3297
|
+
if (isRecord7(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
|
|
1401
3298
|
}
|
|
1402
3299
|
}
|
|
1403
3300
|
if (parts.length > 0) return parts.join("");
|
|
@@ -1408,7 +3305,7 @@ function functionCalls(response) {
|
|
|
1408
3305
|
if (!Array.isArray(response.output)) return [];
|
|
1409
3306
|
const calls = [];
|
|
1410
3307
|
for (const item of response.output) {
|
|
1411
|
-
if (!
|
|
3308
|
+
if (!isRecord7(item) || item.type !== "function_call") continue;
|
|
1412
3309
|
const name = typeof item.name === "string" ? item.name : "function";
|
|
1413
3310
|
const callId = typeof item.call_id === "string" ? item.call_id : name;
|
|
1414
3311
|
let args = item.arguments;
|
|
@@ -1439,11 +3336,11 @@ function toolUsage(calls) {
|
|
|
1439
3336
|
functionTools: calls
|
|
1440
3337
|
};
|
|
1441
3338
|
}
|
|
1442
|
-
function
|
|
1443
|
-
if (!
|
|
1444
|
-
const inputTokens =
|
|
1445
|
-
const outputTokens =
|
|
1446
|
-
const totalTokens =
|
|
3339
|
+
function normalizeUsage2(usage) {
|
|
3340
|
+
if (!isRecord7(usage)) return void 0;
|
|
3341
|
+
const inputTokens = numberValue2(usage.input_tokens);
|
|
3342
|
+
const outputTokens = numberValue2(usage.output_tokens);
|
|
3343
|
+
const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
|
|
1447
3344
|
return {
|
|
1448
3345
|
raw: usage,
|
|
1449
3346
|
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
@@ -1451,7 +3348,7 @@ function normalizeUsage(usage) {
|
|
|
1451
3348
|
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
1452
3349
|
};
|
|
1453
3350
|
}
|
|
1454
|
-
function
|
|
3351
|
+
function failedResponse2(request, requestId, error) {
|
|
1455
3352
|
const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1456
3353
|
return {
|
|
1457
3354
|
id: requestId,
|
|
@@ -1473,7 +3370,7 @@ function failedResponse(request, requestId, error) {
|
|
|
1473
3370
|
raw: { request, response: void 0 }
|
|
1474
3371
|
};
|
|
1475
3372
|
}
|
|
1476
|
-
function
|
|
3373
|
+
function errorEvent2(requestId, error) {
|
|
1477
3374
|
const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
|
|
1478
3375
|
return {
|
|
1479
3376
|
type: "stream.error",
|
|
@@ -1488,22 +3385,31 @@ function errorEvent(requestId, error) {
|
|
|
1488
3385
|
}
|
|
1489
3386
|
};
|
|
1490
3387
|
}
|
|
1491
|
-
function
|
|
3388
|
+
function modelOf2(compiled) {
|
|
1492
3389
|
return typeof compiled.body.model === "string" ? compiled.body.model : "";
|
|
1493
3390
|
}
|
|
1494
|
-
function
|
|
1495
|
-
if (!
|
|
3391
|
+
function messageFrom2(body) {
|
|
3392
|
+
if (!isRecord7(body)) return void 0;
|
|
1496
3393
|
if (typeof body.message === "string") return body.message;
|
|
1497
|
-
if (
|
|
3394
|
+
if (isRecord7(body.error) && typeof body.error.message === "string") return body.error.message;
|
|
1498
3395
|
return void 0;
|
|
1499
3396
|
}
|
|
1500
|
-
function
|
|
3397
|
+
function numberValue2(value) {
|
|
1501
3398
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1502
3399
|
}
|
|
1503
|
-
function
|
|
3400
|
+
function attachProviderMetadata(body, request) {
|
|
3401
|
+
const fromBody = isRecord7(body.metadata) ? body.metadata : void 0;
|
|
3402
|
+
const metadata = (0, import_openrouter_runtime2.toProviderMetadata)(request.metadata, request.agentId, import_openrouter_runtime2.OPENROUTER_METADATA_LIMITS, fromBody);
|
|
3403
|
+
if (metadata) return { ...body, metadata };
|
|
3404
|
+
if (body.metadata === void 0) return body;
|
|
3405
|
+
const rest = { ...body };
|
|
3406
|
+
delete rest.metadata;
|
|
3407
|
+
return rest;
|
|
3408
|
+
}
|
|
3409
|
+
function clean2(body) {
|
|
1504
3410
|
return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
|
|
1505
3411
|
}
|
|
1506
|
-
function
|
|
3412
|
+
function buildHeaders2(options) {
|
|
1507
3413
|
return {
|
|
1508
3414
|
"content-type": "application/json",
|
|
1509
3415
|
authorization: `Bearer ${options.apiKey}`,
|
|
@@ -1511,13 +3417,13 @@ function buildHeaders(options) {
|
|
|
1511
3417
|
...options.project ? { "OpenAI-Project": options.project } : {}
|
|
1512
3418
|
};
|
|
1513
3419
|
}
|
|
1514
|
-
function
|
|
3420
|
+
function timeoutFor2(request, options) {
|
|
1515
3421
|
return request.execution?.timeoutMs ?? options.timeoutMs;
|
|
1516
3422
|
}
|
|
1517
|
-
function
|
|
3423
|
+
function delay2(ms) {
|
|
1518
3424
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1519
3425
|
}
|
|
1520
|
-
async function*
|
|
3426
|
+
async function* parseSse2(response) {
|
|
1521
3427
|
const body = response.body;
|
|
1522
3428
|
if (!body) throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI stream response had no body.");
|
|
1523
3429
|
const reader = body.getReader();
|
|
@@ -1530,14 +3436,14 @@ async function* parseSse(response) {
|
|
|
1530
3436
|
const parts = buffer.split("\n\n");
|
|
1531
3437
|
buffer = parts.pop() ?? "";
|
|
1532
3438
|
for (const part of parts) {
|
|
1533
|
-
const parsed =
|
|
3439
|
+
const parsed = parseSsePart2(part);
|
|
1534
3440
|
if (parsed !== void 0) yield parsed;
|
|
1535
3441
|
}
|
|
1536
3442
|
}
|
|
1537
|
-
const trailing =
|
|
3443
|
+
const trailing = parseSsePart2(buffer);
|
|
1538
3444
|
if (trailing !== void 0) yield trailing;
|
|
1539
3445
|
}
|
|
1540
|
-
function
|
|
3446
|
+
function parseSsePart2(part) {
|
|
1541
3447
|
const data = part.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
|
|
1542
3448
|
if (!data || data === "[DONE]") return void 0;
|
|
1543
3449
|
try {
|
|
@@ -1547,32 +3453,32 @@ function parseSsePart(part) {
|
|
|
1547
3453
|
}
|
|
1548
3454
|
}
|
|
1549
3455
|
function resolveOpenAiOptions(options) {
|
|
1550
|
-
const baseUrl = (options.baseUrl ??
|
|
3456
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
1551
3457
|
return {
|
|
1552
|
-
apiKey: options.apiKey ??
|
|
3458
|
+
apiKey: options.apiKey ?? readEnv3("OPENAI_API_KEY") ?? "",
|
|
1553
3459
|
baseUrl,
|
|
1554
3460
|
...options.defaultModel ? { defaultModel: options.defaultModel } : {},
|
|
1555
3461
|
...options.organization ? { organization: options.organization } : {},
|
|
1556
3462
|
...options.project ? { project: options.project } : {},
|
|
1557
|
-
timeoutMs: options.timeoutMs ??
|
|
3463
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
|
|
1558
3464
|
maxAttempts: options.maxAttempts ?? 3,
|
|
1559
3465
|
fetch: options.fetch ?? globalThis.fetch,
|
|
1560
3466
|
...options.tools ? { tools: options.tools } : {},
|
|
1561
3467
|
...options.logger ? { logger: options.logger } : {}
|
|
1562
3468
|
};
|
|
1563
3469
|
}
|
|
1564
|
-
function
|
|
3470
|
+
function readEnv3(name) {
|
|
1565
3471
|
if (typeof process === "undefined") return void 0;
|
|
1566
3472
|
return process.env[name];
|
|
1567
3473
|
}
|
|
1568
|
-
function
|
|
3474
|
+
function isRecord7(value) {
|
|
1569
3475
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1570
3476
|
}
|
|
1571
3477
|
|
|
1572
3478
|
// src/providers/openrouter.ts
|
|
1573
|
-
var
|
|
3479
|
+
var import_openrouter_runtime3 = require("@x12i/openrouter-runtime");
|
|
1574
3480
|
function createOpenRouterProviderAdapter(options = {}) {
|
|
1575
|
-
const runtime = (0,
|
|
3481
|
+
const runtime = (0, import_openrouter_runtime3.createOpenRouterRuntime)(options);
|
|
1576
3482
|
return {
|
|
1577
3483
|
run(request, call) {
|
|
1578
3484
|
return runtime.run(request, toCall2(call));
|
|
@@ -1638,6 +3544,13 @@ function createAiDispatcher(options = {}) {
|
|
|
1638
3544
|
};
|
|
1639
3545
|
return createOpenAiProviderAdapter(openaiOptions);
|
|
1640
3546
|
}
|
|
3547
|
+
if (provider === "cloudflare") {
|
|
3548
|
+
const cloudflareOptions = {
|
|
3549
|
+
...options.cloudflare,
|
|
3550
|
+
...options.logger && !options.cloudflare?.logger ? { logger: options.logger } : {}
|
|
3551
|
+
};
|
|
3552
|
+
return createCloudflareProviderAdapter(cloudflareOptions);
|
|
3553
|
+
}
|
|
1641
3554
|
throw new AiDispatcherError(
|
|
1642
3555
|
"PROVIDER_ADAPTER_MISSING",
|
|
1643
3556
|
`No adapter registered for provider "${provider}".`
|
|
@@ -1699,22 +3612,27 @@ function createAiDispatcher(options = {}) {
|
|
|
1699
3612
|
function providerTools(provider, options) {
|
|
1700
3613
|
if (provider === "openrouter") return options.openrouter?.tools;
|
|
1701
3614
|
if (provider === "bedrock") return options.bedrock?.tools;
|
|
3615
|
+
if (provider === "cloudflare") return options.cloudflare?.tools;
|
|
1702
3616
|
return options.openai?.tools;
|
|
1703
3617
|
}
|
|
1704
3618
|
function callOf(call) {
|
|
1705
|
-
if (!call.finalizeBody && !call.extractStreamReasoning && !call.suppressNestedReasoningFallback
|
|
3619
|
+
if (!call.finalizeBody && !call.extractStreamReasoning && !call.suppressNestedReasoningFallback && !call.cloudflare) {
|
|
3620
|
+
return void 0;
|
|
3621
|
+
}
|
|
1706
3622
|
return call;
|
|
1707
3623
|
}
|
|
1708
3624
|
function attachCompileMeta(prepared, compiled) {
|
|
1709
|
-
const
|
|
1710
|
-
const headers =
|
|
3625
|
+
const record2 = compiled;
|
|
3626
|
+
const headers = isRecord8(record2.headers) ? redactHeaders(record2.headers) : void 0;
|
|
1711
3627
|
return {
|
|
1712
|
-
...
|
|
3628
|
+
...record2,
|
|
1713
3629
|
...headers ? { headers } : {},
|
|
1714
3630
|
provider: prepared.provider,
|
|
1715
3631
|
requestedModel: prepared.requestedModel,
|
|
1716
3632
|
effectiveModel: prepared.effectiveModel,
|
|
1717
|
-
...prepared.resolution ? { reasoningResolution: prepared.resolution } : {}
|
|
3633
|
+
...prepared.resolution ? { reasoningResolution: prepared.resolution } : {},
|
|
3634
|
+
...prepared.cacheResolution ? { cacheResolution: prepared.cacheResolution } : {},
|
|
3635
|
+
...prepared.cacheWarnings?.length ? { warnings: [...Array.isArray(record2.warnings) ? record2.warnings : [], ...prepared.cacheWarnings] } : {}
|
|
1718
3636
|
};
|
|
1719
3637
|
}
|
|
1720
3638
|
function redactHeaders(headers) {
|
|
@@ -1735,10 +3653,11 @@ function log(options, event, prepared, entrypoint, streaming) {
|
|
|
1735
3653
|
if (prepared.request.id) payload.requestId = prepared.request.id;
|
|
1736
3654
|
if (prepared.resolution?.outcome) payload.outcome = prepared.resolution.outcome;
|
|
1737
3655
|
if (prepared.resolution) payload.bypassed = prepared.resolution.bypassed;
|
|
3656
|
+
if (prepared.cacheResolution) payload.cacheOutcome = prepared.cacheResolution.outcome;
|
|
1738
3657
|
if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
|
|
1739
3658
|
else options.logger?.info?.(event, payload);
|
|
1740
3659
|
}
|
|
1741
|
-
function
|
|
3660
|
+
function isRecord8(value) {
|
|
1742
3661
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1743
3662
|
}
|
|
1744
3663
|
|
|
@@ -1753,6 +3672,7 @@ function stripReasoningHistoryFields(message, paths) {
|
|
|
1753
3672
|
AiDispatcherError,
|
|
1754
3673
|
IMPLEMENTED_AI_PROVIDERS,
|
|
1755
3674
|
createAiDispatcher,
|
|
3675
|
+
decodeProviderMetadata,
|
|
1756
3676
|
isImplementedAiProvider,
|
|
1757
3677
|
providerNotImplemented,
|
|
1758
3678
|
resolveProvider,
|