@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/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 reasoningDelta = "";
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
- reasoningDelta += this.pending.slice(0, closeAt);
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
- reasoningDelta += this.pending.slice(0, this.pending.length - hold);
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 += reasoningDelta;
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 reasoningDelta = this.pending;
255
+ const reasoningDelta2 = this.pending;
208
256
  this.pending = "";
209
- this.reasoning += reasoningDelta;
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
- const tokens = readReasoningTokens(usage.raw, paths);
339
- if (tokens === void 0) {
340
- delete usage.reasoningTokens;
341
- } else {
342
- usage.reasoningTokens = tokens;
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 assertServer(server, seen) {
515
- if (!isRecord4(server) || typeof server.id !== "string" || server.id.length === 0 || !Array.isArray(server.tools) || typeof server.callTool !== "function") {
516
- throw new AiDispatcherError(
517
- "MCP_SERVER_INVALID",
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
- seen.add(server.id);
525
- if (!TOOL_TOKEN.test(server.id)) {
526
- throw new AiDispatcherError(
527
- "MCP_TOOL_NAME_INVALID",
528
- `MCP server id "${server.id}" must use letters, numbers, underscores, and hyphens.`
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 exposedToolName(serverId, tool) {
533
- if (typeof tool.name !== "string" || !TOOL_TOKEN.test(serverId) || !TOOL_TOKEN.test(tool.name)) {
534
- throw new AiDispatcherError(
535
- "MCP_TOOL_NAME_INVALID",
536
- `MCP tool "${serverId}.${typeof tool.name === "string" ? tool.name : ""}" must use letters, numbers, underscores, and hyphens.`
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
- const exposedName = `${serverId}__${tool.name}`;
540
- if (exposedName.length > MAX_EXPOSED_NAME_LENGTH) {
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
- return exposedName;
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 toolDescription(tool) {
549
- if (tool.description === void 0) return "";
550
- if (typeof tool.description !== "string") {
551
- throw new AiDispatcherError(
552
- "MCP_TOOL_NAME_INVALID",
553
- `MCP tool "${tool.name}" description must be a string.`
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 tool.description;
2202
+ return void 0;
557
2203
  }
558
- function toolParameters(tool) {
559
- if (tool.inputSchema === void 0) return { ...DEFAULT_PARAMETERS, properties: {} };
560
- if (!isRecord4(tool.inputSchema)) {
561
- throw new AiDispatcherError(
562
- "MCP_TOOL_NAME_INVALID",
563
- `MCP tool "${tool.name}" inputSchema must be an object.`
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
- return tool.inputSchema;
2285
+ const trailing = parseSsePart(buffer);
2286
+ if (trailing !== void 0) yield trailing;
567
2287
  }
568
- function textContent(content) {
569
- if (!Array.isArray(content)) return void 0;
570
- const parts = [];
571
- for (const part of content) {
572
- if (!isRecord4(part) || part.type !== "text" || typeof part.text !== "string") continue;
573
- parts.push(part.text);
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 toArgs(args) {
579
- if (isRecord4(args)) return args;
580
- return {};
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 listFailed(serverId, error) {
583
- if (error instanceof AiDispatcherError && error.code === "MCP_LIST_FAILED") return error;
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 stringify(result) {
588
- const serialized = JSON.stringify(result);
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 isRecord4(value) {
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
- directive = resolveCatalog({
626
- target: provider,
627
- model: requestedModel,
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) cloned.model = 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
- if (overlay) {
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) => (0, import_ai_profiles.mergeReasoningBody)(body, bodyOverlay);
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 resolveCatalog(input) {
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 (isRecord5(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
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 (!isRecord5(part) || typeof part.text !== "string") continue;
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 ?? readEnv("OPENAI_API_KEY");
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 isRecord5(value) {
2695
+ function isRecord6(value) {
802
2696
  return typeof value === "object" && value !== null && !Array.isArray(value);
803
2697
  }
804
- function readEnv(name) {
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 && serverToolsEnabled(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 serverToolsEnabled(policy) {
2800
+ function serverToolsEnabled2(policy) {
906
2801
  return Object.values(policy).some((value) => {
907
- if (Array.isArray(value)) return value.some((item) => isEnabledMode(item));
908
- return isEnabledMode(value);
2802
+ if (Array.isArray(value)) return value.some((item) => isEnabledMode2(item));
2803
+ return isEnabledMode2(value);
909
2804
  });
910
2805
  }
911
- function isEnabledMode(value) {
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 DEFAULT_BASE_URL = "https://api.openai.com/v1";
944
- var DEFAULT_TIMEOUT_MS = 12e4;
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 && isConfigError(error.code)) throw error;
957
- return failedResponse(request, requestId, error);
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
- assertSupported(request);
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 = compileInstructions(request);
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 = clean({
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: buildHeaders(options),
2898
+ headers: buildHeaders2(options),
1002
2899
  body,
1003
2900
  warnings: []
1004
2901
  };
1005
2902
  }
1006
- function assertSupported(request) {
1007
- if (request.serverTools && serverToolsEnabled2(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 serverToolsEnabled2(policy) {
2929
+ function serverToolsEnabled3(policy) {
1033
2930
  return Object.values(policy).some((value) => {
1034
- if (Array.isArray(value)) return value.some((item) => isEnabledMode2(item));
1035
- return isEnabledMode2(value);
2931
+ if (Array.isArray(value)) return value.some((item) => isEnabledMode3(item));
2932
+ return isEnabledMode3(value);
1036
2933
  });
1037
2934
  }
1038
- function isEnabledMode2(value) {
2935
+ function isEnabledMode3(value) {
1039
2936
  return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
1040
2937
  }
1041
- function compileInstructions(request) {
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) => contentToText(message.content)).filter(Boolean).join("\n");
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: contentToText(message.content)
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 clean({ type: "input_file", file_url: part.url, filename: part.fileName });
2975
+ return clean2({ type: "input_file", file_url: part.url, filename: part.fileName });
1079
2976
  }
1080
2977
  if (part.data) {
1081
- return clean({ type: "input_file", file_data: part.data, filename: part.fileName });
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 sendWithRetries(active, options, timeoutFor(request, options));
1116
- const calls = functionCalls(isRecord6(response) ? response : {});
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) => resolveExecutor(item.name, request, options))) {
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 record = isRecord6(response) ? response : {};
3033
+ const record2 = isRecord7(response) ? response : {};
1137
3034
  active = {
1138
3035
  ...active,
1139
- body: clean({
3036
+ body: clean2({
1140
3037
  ...active.body,
1141
3038
  tool_choice: "auto",
1142
- previous_response_id: typeof record.id === "string" ? record.id : void 0,
1143
- input: results.map(toFunctionCallOutput)
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 resolveExecutor(name, request, options) {
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 = resolveExecutor(call.name, request, options);
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 toFunctionCallOutput(result) {
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 contentToText(content) {
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 errorEvent(requestId, error);
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(), timeoutFor(request, options));
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 readFailure(response);
3142
+ const failure = await readFailure2(response);
1246
3143
  reported = true;
1247
- yield errorEvent(requestId, failure);
3144
+ yield errorEvent2(requestId, failure);
1248
3145
  throw failure;
1249
3146
  }
1250
- for await (const event of parseSse(response)) {
1251
- if (!isRecord6(event)) continue;
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" && isRecord6(event.response)) {
1271
- const usage = normalizeUsage(event.response.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 errorEvent(requestId, failure);
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 errorEvent(requestId, failure);
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 sendWithRetries(compiled, options, timeoutMs) {
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 sendOnce(compiled, options, timeoutMs);
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 asTerminalError(error);
1310
- await delay(Math.min(500 * 2 ** (attempt - 1), 5e3));
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 sendOnce(compiled, options, timeoutMs) {
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 readFailure(response);
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 readFailure(response) {
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 = messageFrom(body) ?? `OpenAI request failed with status ${response.status}.`;
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(classifyStatus(response.status), message, { status: response.status, body, retryable });
3248
+ return new AiDispatcherError(classifyStatus2(response.status), message, { status: response.status, body, retryable });
1352
3249
  }
1353
- function classifyStatus(status) {
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 isConfigError(code) {
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 asTerminalError(error) {
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 record = isRecord6(params.response) ? params.response : {};
1372
- const text = answerText(record);
1373
- const calls = params.functionTools ?? functionCalls(record);
1374
- const usage = normalizeUsage(record.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 record.id === "string" ? record.id : params.requestId,
3274
+ id: typeof record2.id === "string" ? record2.id : params.requestId,
1378
3275
  status,
1379
3276
  apiMode: "responses",
1380
- model: typeof record.model === "string" ? record.model : modelOf(params.compiled),
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 answerText(response) {
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 (!isRecord6(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
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 (isRecord6(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
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 (!isRecord6(item) || item.type !== "function_call") continue;
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 normalizeUsage(usage) {
1443
- if (!isRecord6(usage)) return void 0;
1444
- const inputTokens = numberValue(usage.input_tokens);
1445
- const outputTokens = numberValue(usage.output_tokens);
1446
- const totalTokens = numberValue(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
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 failedResponse(request, requestId, error) {
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 errorEvent(requestId, error) {
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 modelOf(compiled) {
3388
+ function modelOf2(compiled) {
1492
3389
  return typeof compiled.body.model === "string" ? compiled.body.model : "";
1493
3390
  }
1494
- function messageFrom(body) {
1495
- if (!isRecord6(body)) return void 0;
3391
+ function messageFrom2(body) {
3392
+ if (!isRecord7(body)) return void 0;
1496
3393
  if (typeof body.message === "string") return body.message;
1497
- if (isRecord6(body.error) && typeof body.error.message === "string") return body.error.message;
3394
+ if (isRecord7(body.error) && typeof body.error.message === "string") return body.error.message;
1498
3395
  return void 0;
1499
3396
  }
1500
- function numberValue(value) {
3397
+ function numberValue2(value) {
1501
3398
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1502
3399
  }
1503
- function clean(body) {
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 buildHeaders(options) {
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 timeoutFor(request, options) {
3420
+ function timeoutFor2(request, options) {
1515
3421
  return request.execution?.timeoutMs ?? options.timeoutMs;
1516
3422
  }
1517
- function delay(ms) {
3423
+ function delay2(ms) {
1518
3424
  return new Promise((resolve) => setTimeout(resolve, ms));
1519
3425
  }
1520
- async function* parseSse(response) {
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 = parseSsePart(part);
3439
+ const parsed = parseSsePart2(part);
1534
3440
  if (parsed !== void 0) yield parsed;
1535
3441
  }
1536
3442
  }
1537
- const trailing = parseSsePart(buffer);
3443
+ const trailing = parseSsePart2(buffer);
1538
3444
  if (trailing !== void 0) yield trailing;
1539
3445
  }
1540
- function parseSsePart(part) {
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 ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
3456
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
1551
3457
  return {
1552
- apiKey: options.apiKey ?? readEnv2("OPENAI_API_KEY") ?? "",
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 ?? DEFAULT_TIMEOUT_MS,
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 readEnv2(name) {
3470
+ function readEnv3(name) {
1565
3471
  if (typeof process === "undefined") return void 0;
1566
3472
  return process.env[name];
1567
3473
  }
1568
- function isRecord6(value) {
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 import_openrouter_runtime = require("@x12i/openrouter-runtime");
3479
+ var import_openrouter_runtime3 = require("@x12i/openrouter-runtime");
1574
3480
  function createOpenRouterProviderAdapter(options = {}) {
1575
- const runtime = (0, import_openrouter_runtime.createOpenRouterRuntime)(options);
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) return void 0;
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 record = compiled;
1710
- const headers = isRecord7(record.headers) ? redactHeaders(record.headers) : void 0;
3625
+ const record2 = compiled;
3626
+ const headers = isRecord8(record2.headers) ? redactHeaders(record2.headers) : void 0;
1711
3627
  return {
1712
- ...record,
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 isRecord7(value) {
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,