@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.js CHANGED
@@ -1,3 +1,52 @@
1
+ // src/index.ts
2
+ import { decodeProviderMetadata } from "@x12i/openrouter-runtime";
3
+
4
+ // src/cache-usage.ts
5
+ function readCacheTokens(source) {
6
+ const cached = [];
7
+ const written = [];
8
+ for (const usage of usageRecords(source)) {
9
+ const promptDetails = record(usage.prompt_tokens_details);
10
+ const inputDetails = record(usage.input_tokens_details);
11
+ pushNumber(cached, promptDetails?.cached_tokens);
12
+ pushNumber(cached, inputDetails?.cached_tokens);
13
+ pushNumber(cached, usage.cacheReadInputTokens);
14
+ pushNumber(cached, usage.cache_read_input_tokens);
15
+ pushNumber(cached, usage.cached_tokens);
16
+ pushNumber(written, promptDetails?.cache_write_tokens);
17
+ pushNumber(written, inputDetails?.cache_write_tokens);
18
+ pushNumber(written, usage.cacheWriteInputTokens);
19
+ pushNumber(written, usage.cache_write_input_tokens);
20
+ pushNumber(written, usage.cache_write_tokens);
21
+ }
22
+ const cachedTokens = cached[0];
23
+ const cacheWriteTokens = written[0];
24
+ return {
25
+ ...cachedTokens !== void 0 ? { cachedTokens } : {},
26
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
27
+ };
28
+ }
29
+ function usageRecords(source) {
30
+ const root = record(source);
31
+ if (!root) return [];
32
+ const records = [];
33
+ const usage = record(root.usage);
34
+ if (usage) records.push(usage);
35
+ const raw = record(root.raw);
36
+ if (raw) {
37
+ const rawUsage = record(raw.usage);
38
+ records.push(rawUsage ?? raw);
39
+ }
40
+ records.push(root);
41
+ return records;
42
+ }
43
+ function pushNumber(target, value) {
44
+ if (typeof value === "number" && Number.isFinite(value)) target.push(value);
45
+ }
46
+ function record(value) {
47
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
48
+ }
49
+
1
50
  // src/json-pointer.ts
2
51
  var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3
52
  function isRecord(value) {
@@ -139,7 +188,7 @@ var ThinkTagSplitter = class {
139
188
  push(chunk) {
140
189
  this.pending += chunk;
141
190
  let answerDelta = "";
142
- let reasoningDelta = "";
191
+ let reasoningDelta2 = "";
143
192
  while (this.pending.length > 0) {
144
193
  if (!this.inThink) {
145
194
  const openAt = this.pending.indexOf(OPEN);
@@ -156,26 +205,26 @@ var ThinkTagSplitter = class {
156
205
  }
157
206
  const closeAt = this.pending.indexOf(CLOSE);
158
207
  if (closeAt >= 0) {
159
- reasoningDelta += this.pending.slice(0, closeAt);
208
+ reasoningDelta2 += this.pending.slice(0, closeAt);
160
209
  this.pending = this.pending.slice(closeAt + CLOSE.length);
161
210
  this.inThink = false;
162
211
  continue;
163
212
  }
164
213
  const hold = holdSuffix(this.pending, CLOSE);
165
- reasoningDelta += this.pending.slice(0, this.pending.length - hold);
214
+ reasoningDelta2 += this.pending.slice(0, this.pending.length - hold);
166
215
  this.pending = this.pending.slice(this.pending.length - hold);
167
216
  break;
168
217
  }
169
218
  this.answer += answerDelta;
170
- this.reasoning += reasoningDelta;
171
- return { answerDelta, reasoningDelta };
219
+ this.reasoning += reasoningDelta2;
220
+ return { answerDelta, reasoningDelta: reasoningDelta2 };
172
221
  }
173
222
  finish() {
174
223
  if (this.inThink) {
175
- const reasoningDelta = this.pending;
224
+ const reasoningDelta2 = this.pending;
176
225
  this.pending = "";
177
- this.reasoning += reasoningDelta;
178
- return { answerDelta: "", reasoningDelta };
226
+ this.reasoning += reasoningDelta2;
227
+ return { answerDelta: "", reasoningDelta: reasoningDelta2 };
179
228
  }
180
229
  const answerDelta = this.pending;
181
230
  this.pending = "";
@@ -194,6 +243,11 @@ function splitThinkTags(text) {
194
243
  function applyResolution(response, prepared) {
195
244
  const result = response;
196
245
  if (prepared.resolution) result.reasoningResolution = prepared.resolution;
246
+ if (prepared.cacheResolution) result.cacheResolution = prepared.cacheResolution;
247
+ if (prepared.cacheWarnings?.length) {
248
+ result.warnings = [...result.warnings, ...prepared.cacheWarnings];
249
+ }
250
+ applyCacheUsage(result);
197
251
  const directive = prepared.directive;
198
252
  if (!directive) return result;
199
253
  const raw = response.raw?.response;
@@ -209,6 +263,18 @@ function applyResolution(response, prepared) {
209
263
  applyUsage(result, raw, directive);
210
264
  return result;
211
265
  }
266
+ function applyCacheUsage(response) {
267
+ const fromResponse = readCacheTokens(response.raw?.response);
268
+ const fromUsage = readCacheTokens(response.usage?.raw);
269
+ const cachedTokens = fromResponse.cachedTokens ?? fromUsage.cachedTokens;
270
+ const cacheWriteTokens = fromResponse.cacheWriteTokens ?? fromUsage.cacheWriteTokens;
271
+ if (cachedTokens === void 0 && cacheWriteTokens === void 0) return;
272
+ response.usage = {
273
+ ...response.usage ?? {},
274
+ ...cachedTokens !== void 0 ? { cachedTokens } : {},
275
+ ...cacheWriteTokens !== void 0 ? { cacheWriteTokens } : {}
276
+ };
277
+ }
212
278
  function applyUsage(response, raw, directive) {
213
279
  const paths = directive.usage?.reasoningTokenPaths;
214
280
  if (!paths || paths.length === 0) return;
@@ -233,6 +299,9 @@ async function* decorateStream(prepared, source) {
233
299
  for await (const event of source) {
234
300
  if (event.type === "stream.start") {
235
301
  yield withResolution(event, prepared);
302
+ for (const warning of prepared.cacheWarnings ?? []) {
303
+ yield { type: "stream.warning", requestId: event.requestId, data: { warning } };
304
+ }
236
305
  continue;
237
306
  }
238
307
  if (event.type === "stream.reasoning.delta") {
@@ -283,6 +352,7 @@ async function* decorateStream(prepared, source) {
283
352
  if (splitter) data.text = splitter.answer;
284
353
  if (reasoningText) data.reasoningText = reasoningText;
285
354
  if (prepared.resolution) data.reasoningResolution = prepared.resolution;
355
+ if (prepared.cacheResolution) data.cacheResolution = prepared.cacheResolution;
286
356
  yield { ...event, data };
287
357
  continue;
288
358
  }
@@ -290,9 +360,10 @@ async function* decorateStream(prepared, source) {
290
360
  }
291
361
  }
292
362
  function withResolution(event, prepared) {
293
- if (!prepared.resolution) return event;
363
+ if (!prepared.resolution && !prepared.cacheResolution) return event;
294
364
  const data = isRecord3(event.data) ? { ...event.data } : {};
295
- data.reasoningResolution = prepared.resolution;
365
+ if (prepared.resolution) data.reasoningResolution = prepared.resolution;
366
+ if (prepared.cacheResolution) data.cacheResolution = prepared.cacheResolution;
296
367
  return { ...event, data };
297
368
  }
298
369
  function textOf(event) {
@@ -300,20 +371,37 @@ function textOf(event) {
300
371
  return event.data.text;
301
372
  }
302
373
  function adjustUsageEvent(event, prepared) {
374
+ if (!isRecord3(event.data) || !isRecord3(event.data.usage)) return event;
303
375
  const paths = prepared.directive?.usage?.reasoningTokenPaths;
304
- if (!paths || paths.length === 0 || !isRecord3(event.data) || !isRecord3(event.data.usage)) return event;
305
376
  const usage = { ...event.data.usage };
306
- const tokens = readReasoningTokens(usage.raw, paths);
307
- if (tokens === void 0) {
308
- delete usage.reasoningTokens;
309
- } else {
310
- usage.reasoningTokens = tokens;
377
+ let changed = false;
378
+ if (paths && paths.length > 0) {
379
+ const tokens = readReasoningTokens(usage.raw, paths);
380
+ if (tokens === void 0) {
381
+ if (usage.reasoningTokens !== void 0) {
382
+ delete usage.reasoningTokens;
383
+ changed = true;
384
+ }
385
+ } else if (usage.reasoningTokens !== tokens) {
386
+ usage.reasoningTokens = tokens;
387
+ changed = true;
388
+ }
389
+ }
390
+ const counts = readCacheTokens(usage.raw);
391
+ if (counts.cachedTokens !== void 0) {
392
+ usage.cachedTokens = counts.cachedTokens;
393
+ changed = true;
311
394
  }
395
+ if (counts.cacheWriteTokens !== void 0) {
396
+ usage.cacheWriteTokens = counts.cacheWriteTokens;
397
+ changed = true;
398
+ }
399
+ if (!changed) return event;
312
400
  return { ...event, data: { ...event.data, usage } };
313
401
  }
314
402
 
315
403
  // src/types/providers.ts
316
- var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai"];
404
+ var IMPLEMENTED_AI_PROVIDERS = ["openrouter", "bedrock", "openai", "cloudflare"];
317
405
  function isImplementedAiProvider(provider) {
318
406
  return IMPLEMENTED_AI_PROVIDERS.includes(provider);
319
407
  }
@@ -476,105 +564,1775 @@ function resolveAllowlist(filter, tools) {
476
564
  if (!known.has(name)) {
477
565
  throw new AiDispatcherError("MCP_TOOL_NOT_FOUND", `MCP tool "${name}" is not registered.`);
478
566
  }
479
- }
480
- return new Set(filter);
567
+ }
568
+ return new Set(filter);
569
+ }
570
+ function assertServer(server, seen) {
571
+ if (!isRecord4(server) || typeof server.id !== "string" || server.id.length === 0 || !Array.isArray(server.tools) || typeof server.callTool !== "function") {
572
+ throw new AiDispatcherError(
573
+ "MCP_SERVER_INVALID",
574
+ "An MCP server needs an id, a tools array, and a callTool function."
575
+ );
576
+ }
577
+ if (seen.has(server.id)) {
578
+ throw new AiDispatcherError("MCP_SERVER_INVALID", `MCP server "${server.id}" is listed more than once.`);
579
+ }
580
+ seen.add(server.id);
581
+ if (!TOOL_TOKEN.test(server.id)) {
582
+ throw new AiDispatcherError(
583
+ "MCP_TOOL_NAME_INVALID",
584
+ `MCP server id "${server.id}" must use letters, numbers, underscores, and hyphens.`
585
+ );
586
+ }
587
+ }
588
+ function exposedToolName(serverId, tool) {
589
+ if (typeof tool.name !== "string" || !TOOL_TOKEN.test(serverId) || !TOOL_TOKEN.test(tool.name)) {
590
+ throw new AiDispatcherError(
591
+ "MCP_TOOL_NAME_INVALID",
592
+ `MCP tool "${serverId}.${typeof tool.name === "string" ? tool.name : ""}" must use letters, numbers, underscores, and hyphens.`
593
+ );
594
+ }
595
+ const exposedName = `${serverId}__${tool.name}`;
596
+ if (exposedName.length > MAX_EXPOSED_NAME_LENGTH) {
597
+ throw new AiDispatcherError(
598
+ "MCP_TOOL_NAME_INVALID",
599
+ `MCP tool "${exposedName}" is longer than ${MAX_EXPOSED_NAME_LENGTH} characters.`
600
+ );
601
+ }
602
+ return exposedName;
603
+ }
604
+ function toolDescription(tool) {
605
+ if (tool.description === void 0) return "";
606
+ if (typeof tool.description !== "string") {
607
+ throw new AiDispatcherError(
608
+ "MCP_TOOL_NAME_INVALID",
609
+ `MCP tool "${tool.name}" description must be a string.`
610
+ );
611
+ }
612
+ return tool.description;
613
+ }
614
+ function toolParameters(tool) {
615
+ if (tool.inputSchema === void 0) return { ...DEFAULT_PARAMETERS, properties: {} };
616
+ if (!isRecord4(tool.inputSchema)) {
617
+ throw new AiDispatcherError(
618
+ "MCP_TOOL_NAME_INVALID",
619
+ `MCP tool "${tool.name}" inputSchema must be an object.`
620
+ );
621
+ }
622
+ return tool.inputSchema;
623
+ }
624
+ function textContent(content) {
625
+ if (!Array.isArray(content)) return void 0;
626
+ const parts = [];
627
+ for (const part of content) {
628
+ if (!isRecord4(part) || part.type !== "text" || typeof part.text !== "string") continue;
629
+ parts.push(part.text);
630
+ }
631
+ if (!parts.length) return void 0;
632
+ return parts.join("\n");
633
+ }
634
+ function toArgs(args) {
635
+ if (isRecord4(args)) return args;
636
+ return {};
637
+ }
638
+ function listFailed(serverId, error) {
639
+ if (error instanceof AiDispatcherError && error.code === "MCP_LIST_FAILED") return error;
640
+ const message = error instanceof Error ? error.message : `MCP server "${serverId}" listTools failed.`;
641
+ return new AiDispatcherError("MCP_LIST_FAILED", message);
642
+ }
643
+ function stringify(result) {
644
+ const serialized = JSON.stringify(result);
645
+ return serialized === void 0 ? "null" : serialized;
646
+ }
647
+ function isRecord4(value) {
648
+ return typeof value === "object" && value !== null && !Array.isArray(value);
649
+ }
650
+
651
+ // src/prepare-request.ts
652
+ import {
653
+ AIProfilesError,
654
+ mergeReasoningBody,
655
+ resolveReasoning
656
+ } from "@x12i/ai-profiles";
657
+
658
+ // src/cache-apply.ts
659
+ var BREAKPOINT = { mode: "explicit" };
660
+ function applyPromptCache(body, plan) {
661
+ if (!plan.stripMarkers && !plan.placeBreakpoint && !plan.sendOpenAiOptions) {
662
+ return { body, placed: false };
663
+ }
664
+ let next = plan.stripMarkers || plan.placeBreakpoint ? stripCacheMarkers(body) : body;
665
+ if (plan.sendOpenAiOptions) next = applyOpenAiOptions(next, plan);
666
+ if (!plan.placeBreakpoint) return { body: next, placed: false };
667
+ if (plan.dialect === "bedrock-cachepoint") return placeBedrockBreakpoint(next, plan.ttl);
668
+ if (plan.dialect === "anthropic-cache-control") return placeAnthropicBreakpoint(next, plan.ttl);
669
+ if (plan.dialect === "openai-responses") return placeOpenAiBreakpoint(next);
670
+ return { body: next, placed: false };
671
+ }
672
+ function applyOpenAiOptions(body, plan) {
673
+ return {
674
+ ...body,
675
+ ...plan.key ? { prompt_cache_key: plan.key } : {},
676
+ prompt_cache_options: {
677
+ mode: plan.placeBreakpoint ? "explicit" : "implicit",
678
+ ...plan.ttl ? { ttl: plan.ttl } : {}
679
+ }
680
+ };
681
+ }
682
+ function placeOpenAiBreakpoint(body) {
683
+ const instructions = typeof body.instructions === "string" ? body.instructions.trim() : "";
684
+ if (instructions) {
685
+ const rest = inputItems(body.input);
686
+ return {
687
+ placed: true,
688
+ body: {
689
+ ...omitKey(body, "instructions"),
690
+ input: [developerBlock(instructions, "prompt_cache_breakpoint", BREAKPOINT), ...rest]
691
+ }
692
+ };
693
+ }
694
+ if (Array.isArray(body.messages)) {
695
+ return markMessages(body, "messages", "prompt_cache_breakpoint", BREAKPOINT, "text");
696
+ }
697
+ return markInput(body, "prompt_cache_breakpoint", BREAKPOINT, "input_text");
698
+ }
699
+ function placeAnthropicBreakpoint(body, ttl) {
700
+ const marker = { type: "ephemeral", ...ttl ? { ttl } : {} };
701
+ if (Array.isArray(body.messages)) {
702
+ const marked = markMessages(body, "messages", "cache_control", marker, "text");
703
+ if (marked.placed) return marked;
704
+ if (typeof body.system === "string" && body.system.trim()) {
705
+ return {
706
+ placed: true,
707
+ body: {
708
+ ...body,
709
+ system: [{ type: "text", text: body.system.trim(), cache_control: marker }]
710
+ }
711
+ };
712
+ }
713
+ return marked;
714
+ }
715
+ const instructions = typeof body.instructions === "string" ? body.instructions.trim() : "";
716
+ if (instructions) {
717
+ const rest = inputItems(body.input);
718
+ return {
719
+ placed: true,
720
+ body: {
721
+ ...omitKey(body, "instructions"),
722
+ input: [developerBlock(instructions, "cache_control", marker), ...rest]
723
+ }
724
+ };
725
+ }
726
+ return markInput(body, "cache_control", marker, "input_text");
727
+ }
728
+ function placeBedrockBreakpoint(body, ttl) {
729
+ const point = { cachePoint: { type: "default", ...ttl ? { ttl } : {} } };
730
+ const system = Array.isArray(body.system) ? body.system.filter(hasBedrockText) : [];
731
+ if (system.length > 0) {
732
+ return { placed: true, body: { ...body, system: [...system, point] } };
733
+ }
734
+ return { body, placed: false };
735
+ }
736
+ function markMessages(body, field, markerKey, markerValue, textType) {
737
+ const messages = body[field];
738
+ if (!Array.isArray(messages)) return { body, placed: false };
739
+ const index = stableMessageIndex(messages);
740
+ if (index < 0) return { body, placed: false };
741
+ const marked = markMessage(messages[index], markerKey, markerValue, textType);
742
+ if (!marked) return { body, placed: false };
743
+ const next = messages.slice();
744
+ next[index] = marked;
745
+ return { placed: true, body: { ...body, [field]: next } };
746
+ }
747
+ function markInput(body, markerKey, markerValue, textType) {
748
+ if (!Array.isArray(body.input)) return { body, placed: false };
749
+ const index = stableInputIndex(body.input);
750
+ if (index < 0) return { body, placed: false };
751
+ const marked = markMessage(body.input[index], markerKey, markerValue, textType);
752
+ if (!marked) return { body, placed: false };
753
+ const next = body.input.slice();
754
+ next[index] = marked;
755
+ return { placed: true, body: { ...body, input: next } };
756
+ }
757
+ function stableMessageIndex(messages) {
758
+ let lastSystem = -1;
759
+ for (let index = 0; index < messages.length; index += 1) {
760
+ const message = messages[index];
761
+ if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index;
762
+ }
763
+ return lastSystem;
764
+ }
765
+ function stableInputIndex(items) {
766
+ let lastStable = -1;
767
+ for (let index = 0; index < items.length; index += 1) {
768
+ const item = items[index];
769
+ if (!isPlainRecord(item) || !hasText(item.content)) continue;
770
+ if (item.role === "system" || item.role === "developer") lastStable = index;
771
+ }
772
+ return lastStable;
773
+ }
774
+ function markMessage(message, markerKey, markerValue, textType) {
775
+ if (!isPlainRecord(message) || !hasText(message.content)) return void 0;
776
+ const content = markContent(message.content, markerKey, markerValue, textType);
777
+ if (!content) return void 0;
778
+ return { ...message, content };
779
+ }
780
+ function markContent(content, markerKey, markerValue, textType) {
781
+ if (typeof content === "string") {
782
+ if (!content.trim()) return void 0;
783
+ return [{ type: textType, text: content, [markerKey]: markerValue }];
784
+ }
785
+ if (!Array.isArray(content)) return void 0;
786
+ const parts = content.map((part) => isPlainRecord(part) ? { ...part } : part);
787
+ for (let index = parts.length - 1; index >= 0; index -= 1) {
788
+ const part = parts[index];
789
+ if (!isPlainRecord(part) || typeof part.text !== "string" || !part.text.trim()) continue;
790
+ parts[index] = { ...part, [markerKey]: markerValue };
791
+ return parts;
792
+ }
793
+ return void 0;
794
+ }
795
+ function developerBlock(text, markerKey, markerValue) {
796
+ return {
797
+ role: "developer",
798
+ content: [{ type: "input_text", text, [markerKey]: markerValue }]
799
+ };
800
+ }
801
+ function inputItems(input) {
802
+ if (input === void 0) return [];
803
+ if (typeof input === "string") {
804
+ return input.trim() ? [{ role: "user", content: input }] : [];
805
+ }
806
+ return Array.isArray(input) ? input : [];
807
+ }
808
+ function hasBedrockText(block) {
809
+ return isPlainRecord(block) && typeof block.text === "string" && block.text.trim().length > 0;
810
+ }
811
+ function hasText(content) {
812
+ if (typeof content === "string") return content.trim().length > 0;
813
+ if (!Array.isArray(content)) return false;
814
+ return content.some((part) => isPlainRecord(part) && typeof part.text === "string" && part.text.trim().length > 0);
815
+ }
816
+ function stripCacheMarkers(value) {
817
+ return stripValue(value);
818
+ }
819
+ function stripValue(value) {
820
+ if (Array.isArray(value)) {
821
+ return value.map((item) => stripValue(item)).filter((item) => !(isPlainRecord(item) && Object.keys(item).length === 0));
822
+ }
823
+ if (!isPlainRecord(value)) return value;
824
+ const next = {};
825
+ for (const [key, child] of Object.entries(value)) {
826
+ if (key === "prompt_cache_breakpoint" || key === "cache_control" || key === "cachePoint") continue;
827
+ if (key === "prompt_cache_key" || key === "prompt_cache_options") continue;
828
+ next[key] = stripValue(child);
829
+ }
830
+ return next;
831
+ }
832
+ function omitKey(body, key) {
833
+ const next = { ...body };
834
+ delete next[key];
835
+ return next;
836
+ }
837
+ function isPlainRecord(value) {
838
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
839
+ const proto = Object.getPrototypeOf(value);
840
+ return proto === Object.prototype || proto === null;
841
+ }
842
+
843
+ // src/cache-profiles.ts
844
+ var TTL_MINUTES = { "5m": 5, "30m": 30, "1h": 60 };
845
+ var TTL_SECONDS = { "5m": 300, "30m": 1800, "1h": 3600 };
846
+ var OPENAI_EXPLICIT = {
847
+ dialect: "openai-responses",
848
+ ttlOptions: ["30m"],
849
+ defaultTtl: "30m"
850
+ };
851
+ var OPENAI_IMPLICIT = {
852
+ dialect: "implicit-native",
853
+ ttlOptions: ["30m"],
854
+ defaultTtl: "30m"
855
+ };
856
+ var ANTHROPIC_EXPLICIT = {
857
+ dialect: "anthropic-cache-control",
858
+ ttlOptions: ["5m", "1h"],
859
+ defaultTtl: "5m"
860
+ };
861
+ var QWEN_EXPLICIT = {
862
+ dialect: "anthropic-cache-control",
863
+ ttlOptions: ["5m"],
864
+ defaultTtl: "5m"
865
+ };
866
+ var BEDROCK_EXPLICIT = {
867
+ dialect: "bedrock-cachepoint",
868
+ ttlOptions: ["5m", "1h"],
869
+ defaultTtl: "5m"
870
+ };
871
+ var ROUTER_IMPLICIT = {
872
+ dialect: "implicit-native",
873
+ ttlOptions: ["5m"],
874
+ defaultTtl: "5m"
875
+ };
876
+ function resolvePromptCache(input) {
877
+ const mode = normalizeMode(input.policy.mode);
878
+ const key = input.policy.key?.trim() || void 0;
879
+ const requestedTtl = normalizeTtl(input.policy.ttl);
880
+ const model = input.effectiveModel.trim();
881
+ const resolutionBase = {
882
+ mode,
883
+ target: input.target,
884
+ requestedModel: input.requestedModel,
885
+ effectiveModel: input.effectiveModel,
886
+ keyPresent: Boolean(key)
887
+ };
888
+ if (input.target === "cloudflare") {
889
+ return resolveCloudflarePromptCache({ input, mode, key, requestedTtl, resolutionBase, model });
890
+ }
891
+ if (mode === "disabled") {
892
+ return {
893
+ plan: emptyPlan(mode, "none", key, true),
894
+ resolution: { ...resolutionBase, outcome: "ignored" },
895
+ warnings: []
896
+ };
897
+ }
898
+ if (input.policy.mode !== void 0 && mode === "explicit" && !isCacheMode(input.policy.mode)) {
899
+ return ignored(input, resolutionBase, key, "CACHE_UNSUPPORTED", "cache.mode is not auto, explicit, or disabled. The call still runs without cache markers.");
900
+ }
901
+ const matched = matchProfile(input.target, model);
902
+ if (!matched.profile) {
903
+ return ignored(
904
+ input,
905
+ resolutionBase,
906
+ key,
907
+ matched.code,
908
+ matched.message
909
+ );
910
+ }
911
+ return applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile: matched.profile });
912
+ }
913
+ function resolveCloudflarePromptCache(args) {
914
+ const { input, mode, key, requestedTtl, resolutionBase, model } = args;
915
+ if (mode === "disabled") {
916
+ return {
917
+ plan: { ...emptyPlan(mode, "none", key, true), gateway: gatewayDirective(true, void 0, key) },
918
+ resolution: { ...resolutionBase, outcome: "applied" },
919
+ warnings: []
920
+ };
921
+ }
922
+ if (input.policy.mode !== void 0 && mode === "explicit" && !isCacheMode(input.policy.mode)) {
923
+ return ignored(input, resolutionBase, key, "CACHE_UNSUPPORTED", "cache.mode is not auto, explicit, or disabled. The call still runs without cache markers.");
924
+ }
925
+ const profile = matchCloudflareNative(input.cloudflareEndpoint, model);
926
+ if (!profile) {
927
+ return {
928
+ plan: {
929
+ mode,
930
+ dialect: "none",
931
+ placeBreakpoint: false,
932
+ sendOpenAiOptions: false,
933
+ stripMarkers: false,
934
+ gateway: gatewayDirective(false, requestedTtl, key),
935
+ ...requestedTtl ? { ttl: requestedTtl } : {},
936
+ ...key ? { key } : {}
937
+ },
938
+ resolution: {
939
+ ...resolutionBase,
940
+ outcome: "applied",
941
+ ...requestedTtl ? { ttl: requestedTtl } : {}
942
+ },
943
+ warnings: []
944
+ };
945
+ }
946
+ const resolved = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
947
+ const ttl = resolved.plan.ttl ?? requestedTtl;
948
+ return {
949
+ ...resolved,
950
+ plan: { ...resolved.plan, gateway: gatewayDirective(false, ttl, key) }
951
+ };
952
+ }
953
+ function applyProfile(args) {
954
+ const { input, mode, key, requestedTtl, resolutionBase, model, profile } = args;
955
+ const folded = foldTtl(requestedTtl, profile.ttlOptions, profile.defaultTtl);
956
+ const explicitDialect = profile.dialect === "openai-responses" || profile.dialect === "anthropic-cache-control" || profile.dialect === "bedrock-cachepoint";
957
+ if (mode === "explicit" && profile.dialect === "implicit-native") {
958
+ return ignored(
959
+ input,
960
+ resolutionBase,
961
+ key,
962
+ "CACHE_UNSUPPORTED",
963
+ "This model caches automatically and does not accept an explicit breakpoint. The call still runs."
964
+ );
965
+ }
966
+ if (mode === "auto" && explicitDialect && profile.dialect !== "openai-responses") {
967
+ return ignored(
968
+ input,
969
+ resolutionBase,
970
+ key,
971
+ "CACHE_UNSUPPORTED",
972
+ "This model only caches when an explicit breakpoint is set. Set cache.mode to explicit. The call still runs."
973
+ );
974
+ }
975
+ const warnings = [];
976
+ const sendsTtl = profile.dialect === "openai-responses" || mode === "explicit" && explicitDialect;
977
+ if (requestedTtl && folded.degraded && sendsTtl) {
978
+ warnings.push({
979
+ code: "CACHE_TTL_DEGRADED",
980
+ message: `cache.ttl "${requestedTtl}" is not supported for this model. Using "${folded.ttl}".`,
981
+ details: { requestedTtl, effectiveTtl: folded.ttl, target: input.target, model }
982
+ });
983
+ }
984
+ const placeBreakpoint = mode === "explicit" && explicitDialect;
985
+ const sendOpenAiOptions = profile.dialect === "openai-responses" && (mode === "explicit" || mode === "auto");
986
+ const outcome = folded.degraded && sendsTtl ? "degraded" : "applied";
987
+ return {
988
+ plan: {
989
+ mode,
990
+ dialect: profile.dialect,
991
+ placeBreakpoint,
992
+ sendOpenAiOptions,
993
+ stripMarkers: placeBreakpoint,
994
+ ...sendsTtl ? { ttl: folded.ttl } : {},
995
+ ...key ? { key } : {}
996
+ },
997
+ resolution: {
998
+ ...resolutionBase,
999
+ outcome,
1000
+ ...sendsTtl ? { ttl: folded.ttl } : {}
1001
+ },
1002
+ warnings
1003
+ };
1004
+ }
1005
+ function gatewayDirective(skip, ttl, key) {
1006
+ return {
1007
+ skip,
1008
+ ...ttl ? { ttlSeconds: TTL_SECONDS[ttl] } : {},
1009
+ ...key ? { key } : {}
1010
+ };
1011
+ }
1012
+ function matchCloudflareNative(endpoint, model) {
1013
+ const id = model.toLowerCase();
1014
+ if (endpoint === "responses" && isGptExplicit(id)) return OPENAI_EXPLICIT;
1015
+ if (endpoint === "responses" && isGptImplicit(id)) return OPENAI_IMPLICIT;
1016
+ if (endpoint === "messages" && isClaude(id)) return ANTHROPIC_EXPLICIT;
1017
+ return void 0;
1018
+ }
1019
+ function ignored(input, resolutionBase, key, code, message) {
1020
+ return {
1021
+ plan: emptyPlan(resolutionBase.mode, "none", key, false),
1022
+ resolution: { ...resolutionBase, outcome: "ignored" },
1023
+ warnings: [{
1024
+ code,
1025
+ message,
1026
+ details: { target: input.target, model: input.effectiveModel.trim() }
1027
+ }]
1028
+ };
1029
+ }
1030
+ function emptyPlan(mode, dialect, key, stripMarkers) {
1031
+ return {
1032
+ mode,
1033
+ dialect,
1034
+ placeBreakpoint: false,
1035
+ sendOpenAiOptions: false,
1036
+ stripMarkers,
1037
+ ...key ? { key } : {}
1038
+ };
1039
+ }
1040
+ function matchProfile(target, model) {
1041
+ const id = model.toLowerCase();
1042
+ if (!id) {
1043
+ return {
1044
+ code: "CACHE_UNSUPPORTED",
1045
+ message: "Prompt caching needs a model id. The call still runs without cache markers."
1046
+ };
1047
+ }
1048
+ if (target === "bedrock") {
1049
+ if (isOpenAiBedrockId(id)) {
1050
+ return {
1051
+ code: "CACHE_DIALECT_UNSUPPORTED",
1052
+ message: "OpenAI models on Bedrock cache through the Responses API. This runtime sends Converse and does not add those markers. The call still runs."
1053
+ };
1054
+ }
1055
+ if (isClaude(id) || isNova(id)) return { profile: BEDROCK_EXPLICIT, code: "", message: "" };
1056
+ return {
1057
+ code: "CACHE_UNSUPPORTED",
1058
+ message: "This Bedrock model has no prompt-cache profile. The call still runs without cache markers."
1059
+ };
1060
+ }
1061
+ if (target === "openai") {
1062
+ if (isGptExplicit(id)) return { profile: OPENAI_EXPLICIT, code: "", message: "" };
1063
+ if (isGptImplicit(id)) return { profile: OPENAI_IMPLICIT, code: "", message: "" };
1064
+ return {
1065
+ code: "CACHE_UNSUPPORTED",
1066
+ message: "This OpenAI model has no prompt-cache profile. The call still runs without cache markers."
1067
+ };
1068
+ }
1069
+ if (isClaude(id)) return { profile: ANTHROPIC_EXPLICIT, code: "", message: "" };
1070
+ if (isQwen(id)) return { profile: QWEN_EXPLICIT, code: "", message: "" };
1071
+ if (isGptExplicit(id)) return { profile: OPENAI_EXPLICIT, code: "", message: "" };
1072
+ if (isGptImplicit(id) || isRouterImplicit(id)) return { profile: ROUTER_IMPLICIT, code: "", message: "" };
1073
+ return {
1074
+ code: "CACHE_UNSUPPORTED",
1075
+ message: "This model has no prompt-cache profile. The call still runs without cache markers."
1076
+ };
1077
+ }
1078
+ function foldTtl(requested, options, defaultTtl) {
1079
+ if (!requested) return { ttl: defaultTtl, degraded: false };
1080
+ if (options.includes(requested)) return { ttl: requested, degraded: false };
1081
+ let best = defaultTtl;
1082
+ let bestDistance = Math.abs(TTL_MINUTES[defaultTtl] - TTL_MINUTES[requested]);
1083
+ for (const option of options) {
1084
+ const distance = Math.abs(TTL_MINUTES[option] - TTL_MINUTES[requested]);
1085
+ if (distance < bestDistance) {
1086
+ best = option;
1087
+ bestDistance = distance;
1088
+ }
1089
+ }
1090
+ return { ttl: best, degraded: true };
1091
+ }
1092
+ function normalizeMode(mode) {
1093
+ if (mode === "auto" || mode === "disabled") return mode;
1094
+ return "explicit";
1095
+ }
1096
+ function isCacheMode(mode) {
1097
+ return mode === "auto" || mode === "explicit" || mode === "disabled";
1098
+ }
1099
+ function normalizeTtl(ttl) {
1100
+ if (ttl === "5m" || ttl === "30m" || ttl === "1h") return ttl;
1101
+ return void 0;
1102
+ }
1103
+ function isGptExplicit(id) {
1104
+ return /(?:^|\/)gpt-(?:5\.6|6)(?:[.\-:]|$)/i.test(id);
1105
+ }
1106
+ function isGptImplicit(id) {
1107
+ if (isGptExplicit(id)) return false;
1108
+ return /(?:^|\/)gpt(?:[.\-:]|$)/i.test(id) || /(?:^|\/)o[134](?:[.\-:]|$)/i.test(id);
1109
+ }
1110
+ function isClaude(id) {
1111
+ return id.includes("claude") || id.startsWith("anthropic.") || id.startsWith("anthropic/");
1112
+ }
1113
+ function isQwen(id) {
1114
+ return id.includes("qwen");
1115
+ }
1116
+ function isNova(id) {
1117
+ return /(?:^|[./])nova(?:[.\-:]|$)/i.test(id) || id.includes("amazon.nova");
1118
+ }
1119
+ function isOpenAiBedrockId(id) {
1120
+ return id.startsWith("openai.") || id.startsWith("openai/") || isGptExplicit(id) || isGptImplicit(id);
1121
+ }
1122
+ function isRouterImplicit(id) {
1123
+ return /(?:^|\/)(gemini|google|deepseek|x-ai|xai|grok|moonshot|kimi)(?:[./:\-]|$)/i.test(id) || id.includes("gemini") || id.includes("deepseek");
1124
+ }
1125
+
1126
+ // src/providers/cloudflare.ts
1127
+ import {
1128
+ OPENROUTER_METADATA_LIMITS,
1129
+ toProviderMetadata
1130
+ } from "@x12i/openrouter-runtime";
1131
+ var DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
1132
+ var DEFAULT_TIMEOUT_MS = 12e4;
1133
+ var DEFAULT_GATEWAY_ID = "default";
1134
+ function selectCloudflareEndpoint(input) {
1135
+ if (input.endpoint && input.endpoint !== "auto") return input.endpoint;
1136
+ if (input.apiMode === "chat") return "chat";
1137
+ if (input.apiMode === "responses") return "responses";
1138
+ const id = input.model.trim().toLowerCase();
1139
+ if (id.startsWith("anthropic/")) return "messages";
1140
+ if (id.startsWith("openai/") || isBareOpenAiModel(id)) return "responses";
1141
+ return "chat";
1142
+ }
1143
+ function cloudflareCatalogQuery(model) {
1144
+ const id = model.trim();
1145
+ const lower = id.toLowerCase();
1146
+ if (lower.startsWith("openai/")) {
1147
+ const slash = id.indexOf("/");
1148
+ return { target: "openai", model: id.slice(slash + 1), prefix: id.slice(0, slash + 1) };
1149
+ }
1150
+ if (isBareOpenAiModel(id)) return { target: "openai", model: id, prefix: "openai/" };
1151
+ return { target: "openrouter", model: id };
1152
+ }
1153
+ function restoreCloudflareModel(model, prefix) {
1154
+ if (!prefix || model.includes("/")) return model;
1155
+ return `${prefix}${model}`;
1156
+ }
1157
+ function createCloudflareProviderAdapter(options = {}) {
1158
+ const resolved = resolveCloudflareOptions(options);
1159
+ return {
1160
+ compile(request, call) {
1161
+ return compileCloudflareRequest(request, resolved, call, false);
1162
+ },
1163
+ async run(request, call) {
1164
+ const requestId = request.id ?? "cloudflare";
1165
+ try {
1166
+ return await runCloudflare(request, resolved, call, requestId);
1167
+ } catch (error) {
1168
+ if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1169
+ return failedResponse(request, requestId, error, "chat");
1170
+ }
1171
+ },
1172
+ executeStreamingChat(request, call) {
1173
+ return streamCloudflare(request, resolved, call);
1174
+ }
1175
+ };
1176
+ }
1177
+ function compileCloudflareRequest(request, options, call, streaming) {
1178
+ assertSupported(request);
1179
+ assertCredentials(options);
1180
+ const model = (request.model ?? options.defaultModel ?? "").trim();
1181
+ if (!model) {
1182
+ throw new AiDispatcherError(
1183
+ "MODEL_REQUIRED",
1184
+ "A model id is required. Set request.model or cloudflare.defaultModel."
1185
+ );
1186
+ }
1187
+ const endpoint = call?.cloudflare?.endpoint ?? selectCloudflareEndpoint({
1188
+ model,
1189
+ ...options.endpoint ? { endpoint: options.endpoint } : {},
1190
+ ...request.apiMode ? { apiMode: request.apiMode } : {}
1191
+ });
1192
+ if (streaming && endpoint === "run") {
1193
+ throw new AiDispatcherError(
1194
+ "UNSUPPORTED_REQUEST_FIELD",
1195
+ "Cloudflare /ai/run does not stream. Use chat, responses, or messages, or call run()."
1196
+ );
1197
+ }
1198
+ if ((endpoint === "messages" || endpoint === "run") && request.responseFormat !== void 0) {
1199
+ throw new AiDispatcherError(
1200
+ "UNSUPPORTED_REQUEST_FIELD",
1201
+ "responseFormat is supported on Cloudflare chat and responses, not on messages or run."
1202
+ );
1203
+ }
1204
+ const run = endpoint === "run" ? call?.cloudflare?.run : void 0;
1205
+ assertRunOptions(run);
1206
+ let body = endpoint === "responses" ? compileResponsesBody(request, model) : endpoint === "messages" ? compileMessagesBody(request, model) : compileChatBody(request, model);
1207
+ if (call?.finalizeBody) body = call.finalizeBody(body);
1208
+ if (endpoint !== "run") body = { ...body, stream: streaming };
1209
+ const gateway = mergeGateway(options.gateway, call?.cloudflare?.gateway);
1210
+ const fromBody = isRecord5(body.metadata) ? body.metadata : void 0;
1211
+ const record2 = toProviderMetadata(
1212
+ request.metadata,
1213
+ request.agentId,
1214
+ OPENROUTER_METADATA_LIMITS,
1215
+ overlayMetadata(gateway?.metadata, fromBody)
1216
+ );
1217
+ body = applyProviderRecord(body, record2);
1218
+ if (endpoint === "run") body = wrapRun(body, run);
1219
+ return {
1220
+ endpoint,
1221
+ url: endpointUrl(options, endpoint),
1222
+ headers: buildHeaders(options, call?.cloudflare, model, record2),
1223
+ body,
1224
+ warnings: []
1225
+ };
1226
+ }
1227
+ function compileResponsesBody(request, model) {
1228
+ const instructions = compileInstructions(request);
1229
+ const input = compileResponsesInput(request);
1230
+ if (input === void 0) {
1231
+ throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
1232
+ }
1233
+ return clean({
1234
+ model,
1235
+ input,
1236
+ instructions,
1237
+ temperature: request.temperature,
1238
+ max_output_tokens: request.maxTokens,
1239
+ reasoning: request.reasoning,
1240
+ text: request.responseFormat,
1241
+ tools: compileResponsesTools(request),
1242
+ tool_choice: compileResponsesToolChoice(request)
1243
+ });
1244
+ }
1245
+ function compileChatBody(request, model) {
1246
+ const messages = compileChatMessages(request);
1247
+ if (!messages.length) {
1248
+ throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
1249
+ }
1250
+ return clean({
1251
+ model,
1252
+ messages,
1253
+ temperature: request.temperature,
1254
+ max_tokens: request.maxTokens,
1255
+ reasoning: request.reasoning,
1256
+ response_format: request.responseFormat,
1257
+ tools: compileChatTools(request),
1258
+ tool_choice: compileChatToolChoice(request)
1259
+ });
1260
+ }
1261
+ function compileMessagesBody(request, model) {
1262
+ const system = compileInstructions(request);
1263
+ const messages = compileAnthropicMessages(request);
1264
+ if (!messages.length) {
1265
+ throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
1266
+ }
1267
+ return clean({
1268
+ model,
1269
+ system,
1270
+ messages,
1271
+ temperature: request.temperature,
1272
+ max_tokens: request.maxTokens,
1273
+ tools: compileAnthropicTools(request),
1274
+ tool_choice: compileAnthropicToolChoice(request)
1275
+ });
1276
+ }
1277
+ function wrapRun(body, run) {
1278
+ const model = body.model;
1279
+ const input = { ...body };
1280
+ delete input.model;
1281
+ delete input.stream;
1282
+ return clean({
1283
+ model,
1284
+ input,
1285
+ options: runOptions(run)
1286
+ });
1287
+ }
1288
+ function runOptions(run) {
1289
+ if (!run) return void 0;
1290
+ return clean({
1291
+ background: run.background,
1292
+ webhookUrl: run.webhookUrl,
1293
+ webhookFormat: run.webhookFormat
1294
+ });
1295
+ }
1296
+ function assertRunOptions(run) {
1297
+ if (!run) return;
1298
+ if (run.webhookUrl && run.background !== true) {
1299
+ throw new AiDispatcherError(
1300
+ "UNSUPPORTED_REQUEST_FIELD",
1301
+ "Cloudflare webhookUrl requires run.background to be true."
1302
+ );
1303
+ }
1304
+ if (run.webhookFormat && !run.webhookUrl) {
1305
+ throw new AiDispatcherError(
1306
+ "UNSUPPORTED_REQUEST_FIELD",
1307
+ "Cloudflare webhookFormat requires run.webhookUrl."
1308
+ );
1309
+ }
1310
+ }
1311
+ function assertSupported(request) {
1312
+ if (request.serverTools && serverToolsEnabled(request.serverTools)) {
1313
+ throw new AiDispatcherError(
1314
+ "UNSUPPORTED_REQUEST_FIELD",
1315
+ "Cloudflare does not support OpenRouter server tools."
1316
+ );
1317
+ }
1318
+ if (request.rawOpenRouterOverrides !== void 0) {
1319
+ throw new AiDispatcherError(
1320
+ "UNSUPPORTED_REQUEST_FIELD",
1321
+ "rawOpenRouterOverrides cannot be sent to Cloudflare."
1322
+ );
1323
+ }
1324
+ if (request.toolChoice && typeof request.toolChoice === "object" && request.toolChoice.type === "server_tool") {
1325
+ throw new AiDispatcherError(
1326
+ "UNSUPPORTED_REQUEST_FIELD",
1327
+ "Cloudflare does not support OpenRouter server tool choices."
1328
+ );
1329
+ }
1330
+ }
1331
+ function assertCredentials(options) {
1332
+ if (!options.apiToken) {
1333
+ throw new AiDispatcherError(
1334
+ "CLOUDFLARE_API_TOKEN_MISSING",
1335
+ "Cloudflare API token is required. Set cloudflare.apiToken or CLOUDFLARE_API_TOKEN."
1336
+ );
1337
+ }
1338
+ if (!options.accountId) {
1339
+ throw new AiDispatcherError(
1340
+ "CLOUDFLARE_ACCOUNT_ID_MISSING",
1341
+ "Cloudflare account id is required. Set cloudflare.accountId or CLOUDFLARE_ACCOUNT_ID."
1342
+ );
1343
+ }
1344
+ }
1345
+ function serverToolsEnabled(policy) {
1346
+ return Object.values(policy).some((value) => {
1347
+ if (Array.isArray(value)) return value.some((item) => isEnabledMode(item));
1348
+ return isEnabledMode(value);
1349
+ });
1350
+ }
1351
+ function isEnabledMode(value) {
1352
+ return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
1353
+ }
1354
+ function compileInstructions(request) {
1355
+ if (request.instructions) return request.instructions;
1356
+ if (request.system) return request.system;
1357
+ const systemMessages = (request.messages ?? []).filter((message) => message.role === "system");
1358
+ if (!systemMessages.length) return void 0;
1359
+ return systemMessages.map((message) => contentToText(message.content)).filter(Boolean).join("\n");
1360
+ }
1361
+ function compileResponsesInput(request) {
1362
+ if (request.input !== void 0) return request.input;
1363
+ const items = [];
1364
+ for (const message of request.messages ?? []) {
1365
+ if (message.role === "system") continue;
1366
+ items.push(compileResponsesItem(message));
1367
+ }
1368
+ if (typeof request.prompt === "string") items.push({ role: "user", content: request.prompt });
1369
+ return items.length ? items : void 0;
1370
+ }
1371
+ function compileResponsesItem(message) {
1372
+ if (message.role === "tool") {
1373
+ return {
1374
+ type: "function_call_output",
1375
+ call_id: message.toolCallId ?? message.name ?? "tool",
1376
+ output: contentToText(message.content)
1377
+ };
1378
+ }
1379
+ const role = message.role === "assistant" ? "assistant" : "user";
1380
+ return { role, content: compileResponsesContent(message.content) };
1381
+ }
1382
+ function compileResponsesContent(content) {
1383
+ if (typeof content === "string") return content;
1384
+ return content.map((part) => {
1385
+ if (part.type === "text") return { type: "input_text", text: part.text };
1386
+ if (part.type === "image_url") return { type: "input_image", image_url: part.imageUrl };
1387
+ return compileFilePart(part, "responses");
1388
+ });
1389
+ }
1390
+ function compileChatMessages(request) {
1391
+ assertExclusiveInput(request);
1392
+ const messages = [];
1393
+ const system = compileInstructions(request);
1394
+ if (system) messages.push({ role: "system", content: system });
1395
+ for (const message of dialogueMessages(request)) messages.push(compileChatMessage(message));
1396
+ return messages;
1397
+ }
1398
+ function compileChatMessage(message) {
1399
+ if (message.role === "tool") {
1400
+ return {
1401
+ role: "tool",
1402
+ tool_call_id: message.toolCallId ?? message.name ?? "tool",
1403
+ content: contentToText(message.content)
1404
+ };
1405
+ }
1406
+ return {
1407
+ role: message.role === "assistant" ? "assistant" : "user",
1408
+ content: compileChatContent(message.content)
1409
+ };
1410
+ }
1411
+ function compileChatContent(content) {
1412
+ if (typeof content === "string") return content;
1413
+ return content.map((part) => {
1414
+ if (part.type === "text") return { type: "text", text: part.text };
1415
+ if (part.type === "image_url") return { type: "image_url", image_url: { url: part.imageUrl } };
1416
+ const file = compileFilePart(part, "chat");
1417
+ return file;
1418
+ });
1419
+ }
1420
+ function compileAnthropicMessages(request) {
1421
+ assertExclusiveInput(request);
1422
+ return dialogueMessages(request).map(compileAnthropicMessage);
1423
+ }
1424
+ function compileAnthropicMessage(message) {
1425
+ if (message.role === "tool") {
1426
+ return {
1427
+ role: "user",
1428
+ content: [{
1429
+ type: "tool_result",
1430
+ tool_use_id: message.toolCallId ?? message.name ?? "tool",
1431
+ content: contentToText(message.content)
1432
+ }]
1433
+ };
1434
+ }
1435
+ const role = message.role === "assistant" ? "assistant" : "user";
1436
+ return { role, content: compileAnthropicContent(message.content) };
1437
+ }
1438
+ function compileAnthropicContent(content) {
1439
+ if (typeof content === "string") return content;
1440
+ return content.map((part) => {
1441
+ if (part.type === "text") return { type: "text", text: part.text };
1442
+ if (part.type === "image_url") return anthropicImage(part.imageUrl);
1443
+ return anthropicFile(part);
1444
+ });
1445
+ }
1446
+ function dialogueMessages(request) {
1447
+ const source = request.messages?.length ? request.messages.filter((message) => message.role !== "system") : messagesFromInput(request);
1448
+ if (typeof request.prompt === "string" && request.messages?.length) {
1449
+ return [...source, { role: "user", content: request.prompt }];
1450
+ }
1451
+ if (!source.length && typeof request.prompt === "string") return [{ role: "user", content: request.prompt }];
1452
+ return source;
1453
+ }
1454
+ function messagesFromInput(request) {
1455
+ if (typeof request.input === "string") return [{ role: "user", content: request.input }];
1456
+ if (!Array.isArray(request.input)) return [];
1457
+ const messages = [];
1458
+ for (const item of request.input) {
1459
+ if (typeof item === "string") {
1460
+ messages.push({ role: "user", content: item });
1461
+ continue;
1462
+ }
1463
+ if (!isRecord5(item)) continue;
1464
+ const role = item.role === "assistant" || item.role === "tool" || item.role === "system" ? item.role : "user";
1465
+ if (role === "system") continue;
1466
+ const content = inputContent(item.content);
1467
+ if (content === void 0) continue;
1468
+ const message = { role, content };
1469
+ if (typeof item.toolCallId === "string") message.toolCallId = item.toolCallId;
1470
+ if (typeof item.name === "string") message.name = item.name;
1471
+ messages.push(message);
1472
+ }
1473
+ return messages;
1474
+ }
1475
+ function inputContent(content) {
1476
+ if (typeof content === "string") return content;
1477
+ if (!Array.isArray(content)) return void 0;
1478
+ const parts = [];
1479
+ for (const part of content) {
1480
+ if (!isRecord5(part)) continue;
1481
+ if ((part.type === "text" || part.type === "input_text") && typeof part.text === "string") {
1482
+ parts.push({ type: "text", text: part.text });
1483
+ } else if (part.type === "image_url" && typeof part.imageUrl === "string") {
1484
+ parts.push({ type: "image_url", imageUrl: part.imageUrl });
1485
+ } else if (part.type === "image_url" && isRecord5(part.image_url) && typeof part.image_url.url === "string") {
1486
+ parts.push({ type: "image_url", imageUrl: part.image_url.url });
1487
+ } else if (part.type === "input_image" && typeof part.image_url === "string") {
1488
+ parts.push({ type: "image_url", imageUrl: part.image_url });
1489
+ }
1490
+ }
1491
+ return parts.length ? parts : void 0;
1492
+ }
1493
+ function assertExclusiveInput(request) {
1494
+ const hasMessages2 = Array.isArray(request.messages) && request.messages.length > 0;
1495
+ if (hasMessages2 && request.input !== void 0) {
1496
+ throw new AiDispatcherError(
1497
+ "UNSUPPORTED_REQUEST_FIELD",
1498
+ "Cloudflare chat, messages, and run accept messages or input, not both."
1499
+ );
1500
+ }
1501
+ }
1502
+ function compileFilePart(part, dialect) {
1503
+ if (part.type !== "file") {
1504
+ throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "File content parts need a url or data.");
1505
+ }
1506
+ if (!part.url && !part.data) {
1507
+ throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "File content parts need a url or data.");
1508
+ }
1509
+ if (dialect === "responses") {
1510
+ if (part.url) return clean({ type: "input_file", file_url: part.url, filename: part.fileName });
1511
+ return clean({ type: "input_file", file_data: part.data, filename: part.fileName });
1512
+ }
1513
+ return {
1514
+ type: "file",
1515
+ file: clean({
1516
+ filename: part.fileName,
1517
+ ...part.url ? { url: part.url } : {},
1518
+ ...part.data ? { file_data: part.data } : {}
1519
+ })
1520
+ };
1521
+ }
1522
+ function anthropicImage(imageUrl) {
1523
+ const data = dataUrl(imageUrl);
1524
+ if (data) return { type: "image", source: { type: "base64", media_type: data.mediaType, data: data.data } };
1525
+ return { type: "image", source: { type: "url", url: imageUrl } };
1526
+ }
1527
+ function anthropicFile(part) {
1528
+ if (part.type !== "file" || !part.url && !part.data) {
1529
+ throw new AiDispatcherError("UNSUPPORTED_REQUEST_FIELD", "File content parts need a url or data.");
1530
+ }
1531
+ if (part.url) {
1532
+ const data = dataUrl(part.url);
1533
+ if (data) return { type: "document", source: { type: "base64", media_type: data.mediaType, data: data.data } };
1534
+ return { type: "document", source: { type: "url", url: part.url } };
1535
+ }
1536
+ return {
1537
+ type: "document",
1538
+ source: {
1539
+ type: "base64",
1540
+ media_type: part.mimeType || "application/octet-stream",
1541
+ data: part.data
1542
+ }
1543
+ };
1544
+ }
1545
+ function dataUrl(value) {
1546
+ const match = /^data:([^;,]+);base64,([\s\S]+)$/i.exec(value);
1547
+ if (!match) return void 0;
1548
+ return { mediaType: match[1] ?? "application/octet-stream", data: match[2] ?? "" };
1549
+ }
1550
+ function compileResponsesTools(request) {
1551
+ const tools = request.functionTools ?? [];
1552
+ if (!tools.length || request.toolChoice === "none") return void 0;
1553
+ return tools.map((tool) => ({
1554
+ type: "function",
1555
+ name: tool.name,
1556
+ description: tool.description,
1557
+ parameters: tool.parameters
1558
+ }));
1559
+ }
1560
+ function compileChatTools(request) {
1561
+ const tools = request.functionTools ?? [];
1562
+ if (!tools.length || request.toolChoice === "none") return void 0;
1563
+ return tools.map((tool) => ({
1564
+ type: "function",
1565
+ function: {
1566
+ name: tool.name,
1567
+ description: tool.description,
1568
+ parameters: tool.parameters
1569
+ }
1570
+ }));
1571
+ }
1572
+ function compileAnthropicTools(request) {
1573
+ const tools = request.functionTools ?? [];
1574
+ if (!tools.length || request.toolChoice === "none") return void 0;
1575
+ return tools.map((tool) => ({
1576
+ name: tool.name,
1577
+ description: tool.description,
1578
+ input_schema: tool.parameters
1579
+ }));
1580
+ }
1581
+ function compileResponsesToolChoice(request) {
1582
+ if (!request.functionTools?.length || request.toolChoice === "none") return void 0;
1583
+ const choice = request.toolChoice;
1584
+ if (!choice || choice === "auto") return void 0;
1585
+ if (choice === "required") return "required";
1586
+ if (typeof choice === "object" && choice.type === "function") return { type: "function", name: choice.functionName };
1587
+ return void 0;
1588
+ }
1589
+ function compileChatToolChoice(request) {
1590
+ if (!request.functionTools?.length || request.toolChoice === "none") return void 0;
1591
+ const choice = request.toolChoice;
1592
+ if (!choice || choice === "auto") return void 0;
1593
+ if (choice === "required") return "required";
1594
+ if (typeof choice === "object" && choice.type === "function") {
1595
+ return { type: "function", function: { name: choice.functionName } };
1596
+ }
1597
+ return void 0;
1598
+ }
1599
+ function compileAnthropicToolChoice(request) {
1600
+ if (!request.functionTools?.length || request.toolChoice === "none") return void 0;
1601
+ const choice = request.toolChoice;
1602
+ if (!choice || choice === "auto") return void 0;
1603
+ if (choice === "required") return { type: "any" };
1604
+ if (typeof choice === "object" && choice.type === "function") return { type: "tool", name: choice.functionName };
1605
+ return void 0;
1606
+ }
1607
+ function contentToText(content) {
1608
+ if (typeof content === "string") return content;
1609
+ return content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("");
1610
+ }
1611
+ async function runCloudflare(request, options, call, requestId) {
1612
+ const maxIterations = request.execution?.maxToolIterations ?? 8;
1613
+ const maxFunctionToolCalls = request.execution?.maxFunctionToolCalls ?? 20;
1614
+ const functionUsage = [];
1615
+ let active;
1616
+ try {
1617
+ active = compileCloudflareRequest(request, options, call, false);
1618
+ } catch (error) {
1619
+ if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1620
+ return failedResponse(request, requestId, error, "chat");
1621
+ }
1622
+ let response;
1623
+ try {
1624
+ for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
1625
+ response = await sendWithRetries(active, options, timeoutFor(request, options));
1626
+ if (active.endpoint === "run" && !chatCalls(unwrap(response)).length) break;
1627
+ const calls = extractCalls(active.endpoint, response);
1628
+ if (!calls.length) break;
1629
+ if (!calls.every((item) => resolveExecutor(item.name, request, options))) {
1630
+ return normalizeCloudflareResponse({
1631
+ request,
1632
+ requestId,
1633
+ compiled: active,
1634
+ response,
1635
+ functionTools: [...functionUsage, ...calls],
1636
+ status: "requires_action"
1637
+ });
1638
+ }
1639
+ if (functionUsage.length + calls.length > maxFunctionToolCalls) {
1640
+ throw new AiDispatcherError("FUNCTION_TOOL_LIMIT", "maxFunctionToolCalls exceeded.");
1641
+ }
1642
+ const results = [];
1643
+ for (const item of calls) results.push(await executeTool(item, request, options));
1644
+ functionUsage.push(...results);
1645
+ active = { ...active, body: continueBody(active, response, results) };
1646
+ }
1647
+ return normalizeCloudflareResponse({
1648
+ request,
1649
+ requestId,
1650
+ compiled: active,
1651
+ response,
1652
+ functionTools: functionUsage,
1653
+ status: "completed"
1654
+ });
1655
+ } catch (error) {
1656
+ if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1657
+ return failedResponse(request, requestId, error, responseApiMode(active.endpoint));
1658
+ }
1659
+ }
1660
+ function continueBody(compiled, response, results) {
1661
+ if (compiled.endpoint === "responses") return continueResponses(compiled.body, response, results);
1662
+ if (compiled.endpoint === "messages") return continueMessages(compiled.body, response, results);
1663
+ if (compiled.endpoint === "run") return continueRun(compiled.body, response, results);
1664
+ return continueChat(compiled.body, response, results);
1665
+ }
1666
+ function continueResponses(body, response, results) {
1667
+ const record2 = unwrap(response);
1668
+ return clean({
1669
+ ...body,
1670
+ tool_choice: "auto",
1671
+ previous_response_id: typeof record2.id === "string" ? record2.id : void 0,
1672
+ input: results.map(toFunctionCallOutput)
1673
+ });
1674
+ }
1675
+ function continueChat(body, response, results) {
1676
+ const messages = Array.isArray(body.messages) ? [...body.messages] : [];
1677
+ const assistant = chatAssistantMessage(unwrap(response));
1678
+ if (assistant) messages.push(assistant);
1679
+ for (const result of results) {
1680
+ messages.push({
1681
+ role: "tool",
1682
+ tool_call_id: result.callId,
1683
+ content: outputText(result)
1684
+ });
1685
+ }
1686
+ return { ...body, messages, ...body.tool_choice !== void 0 ? { tool_choice: "auto" } : {} };
1687
+ }
1688
+ function continueMessages(body, response, results) {
1689
+ const messages = Array.isArray(body.messages) ? [...body.messages] : [];
1690
+ const record2 = unwrap(response);
1691
+ messages.push({ role: "assistant", content: Array.isArray(record2.content) ? record2.content : [] });
1692
+ messages.push({
1693
+ role: "user",
1694
+ content: results.map((result) => ({
1695
+ type: "tool_result",
1696
+ tool_use_id: result.callId,
1697
+ content: outputText(result),
1698
+ ...result.status === "failed" ? { is_error: true } : {}
1699
+ }))
1700
+ });
1701
+ return { ...body, messages };
1702
+ }
1703
+ function continueRun(body, response, results) {
1704
+ const input = isRecord5(body.input) ? body.input : {};
1705
+ const continued = continueChat({ messages: input.messages }, response, results);
1706
+ return { ...body, input: { ...input, messages: continued.messages } };
1707
+ }
1708
+ function toFunctionCallOutput(result) {
1709
+ return {
1710
+ type: "function_call_output",
1711
+ call_id: result.callId,
1712
+ output: outputText(result)
1713
+ };
1714
+ }
1715
+ function outputText(result) {
1716
+ const output = result.status === "completed" ? result.result : { error: result.error };
1717
+ return typeof output === "string" ? output : JSON.stringify(output);
1718
+ }
1719
+ function resolveExecutor(name, request, options) {
1720
+ return request.functionTools?.find((tool) => tool.name === name)?.executor ?? options.tools?.[name];
1721
+ }
1722
+ async function executeTool(call, request, options) {
1723
+ const executor = resolveExecutor(call.name, request, options);
1724
+ if (!executor) return call;
1725
+ const started = Date.now();
1726
+ try {
1727
+ options.logger?.debug?.("runtime.function_tool.started", { name: call.name, callId: call.callId });
1728
+ const result = await executor(call.args, {
1729
+ requestId: request.id ?? "",
1730
+ toolName: call.name,
1731
+ callId: call.callId,
1732
+ ...request.metadata ? { metadata: request.metadata } : {}
1733
+ });
1734
+ const usage = {
1735
+ name: call.name,
1736
+ callId: call.callId,
1737
+ status: "completed",
1738
+ args: call.args,
1739
+ result,
1740
+ durationMs: Date.now() - started
1741
+ };
1742
+ options.logger?.debug?.("runtime.function_tool.completed", { name: call.name, callId: call.callId });
1743
+ return usage;
1744
+ } catch (error) {
1745
+ const usage = {
1746
+ name: call.name,
1747
+ callId: call.callId,
1748
+ status: "failed",
1749
+ args: call.args,
1750
+ error: error instanceof Error ? error.message : String(error),
1751
+ durationMs: Date.now() - started
1752
+ };
1753
+ options.logger?.warn?.("runtime.function_tool.failed", usage);
1754
+ return usage;
1755
+ }
1756
+ }
1757
+ async function* streamCloudflare(request, options, call) {
1758
+ const requestId = request.id ?? "";
1759
+ let compiled;
1760
+ try {
1761
+ compiled = compileCloudflareRequest(request, options, call, true);
1762
+ } catch (error) {
1763
+ yield errorEvent(requestId, error);
1764
+ throw error;
1765
+ }
1766
+ yield {
1767
+ type: "stream.start",
1768
+ requestId,
1769
+ data: {
1770
+ model: modelOf(compiled),
1771
+ apiMode: responseApiMode(compiled.endpoint),
1772
+ entrypoint: "executeStreamingChat"
1773
+ }
1774
+ };
1775
+ const controller = new AbortController();
1776
+ const timeout = setTimeout(() => controller.abort(), timeoutFor(request, options));
1777
+ let text = "";
1778
+ let reported = false;
1779
+ try {
1780
+ const response = await options.fetch(compiled.url, {
1781
+ method: "POST",
1782
+ headers: { ...compiled.headers, Accept: "text/event-stream" },
1783
+ body: JSON.stringify(compiled.body),
1784
+ signal: controller.signal
1785
+ });
1786
+ if (!response.ok) {
1787
+ const failure = await readFailure(response);
1788
+ reported = true;
1789
+ yield errorEvent(requestId, failure);
1790
+ throw failure;
1791
+ }
1792
+ for await (const event of parseSse(response)) {
1793
+ if (!isRecord5(event)) continue;
1794
+ const catalogReasoning = call?.extractStreamReasoning?.(event);
1795
+ const reasoningText = catalogReasoning || reasoningDelta(event);
1796
+ if (reasoningText) yield { type: "stream.reasoning.delta", requestId, data: { text: reasoningText } };
1797
+ const delta = textDelta(event);
1798
+ if (delta) {
1799
+ text += delta;
1800
+ yield { type: "stream.text.delta", requestId, data: { text: delta } };
1801
+ }
1802
+ const toolDelta = toolCallDelta(event);
1803
+ if (toolDelta) yield { type: "stream.tool_call.delta", requestId, data: toolDelta };
1804
+ const usage = streamUsage(event);
1805
+ if (usage) yield { type: "stream.usage", requestId, data: { usage } };
1806
+ if (event.type === "error") {
1807
+ const failure = new AiDispatcherError(
1808
+ "PROVIDER_REQUEST_FAILED",
1809
+ typeof event.message === "string" ? event.message : "Cloudflare stream returned an error event.",
1810
+ event
1811
+ );
1812
+ reported = true;
1813
+ yield errorEvent(requestId, failure);
1814
+ throw failure;
1815
+ }
1816
+ }
1817
+ yield { type: "stream.done", requestId, data: { text, model: modelOf(compiled) } };
1818
+ } catch (error) {
1819
+ const failure = error instanceof Error && error.name === "AbortError" ? new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "Cloudflare request timed out.") : error instanceof AiDispatcherError ? error : new AiDispatcherError(
1820
+ "PROVIDER_GATEWAY_UNREACHABLE",
1821
+ error instanceof Error ? error.message : "Cloudflare request failed before receiving a response."
1822
+ );
1823
+ if (!reported) yield errorEvent(requestId, failure);
1824
+ throw failure;
1825
+ } finally {
1826
+ clearTimeout(timeout);
1827
+ }
1828
+ }
1829
+ async function sendWithRetries(compiled, options, timeoutMs) {
1830
+ let lastError;
1831
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt += 1) {
1832
+ try {
1833
+ return await sendOnce(compiled, options, timeoutMs);
1834
+ } catch (error) {
1835
+ lastError = error;
1836
+ const retryable = error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE";
1837
+ if (!retryable || attempt >= options.maxAttempts) throw asTerminalError(error);
1838
+ await delay(Math.min(500 * 2 ** (attempt - 1), 5e3));
1839
+ }
1840
+ }
1841
+ throw lastError;
1842
+ }
1843
+ async function sendOnce(compiled, options, timeoutMs) {
1844
+ const controller = new AbortController();
1845
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1846
+ try {
1847
+ const response = await options.fetch(compiled.url, {
1848
+ method: "POST",
1849
+ headers: compiled.headers,
1850
+ body: JSON.stringify(compiled.body),
1851
+ signal: controller.signal
1852
+ });
1853
+ if (!response.ok) throw await readFailure(response);
1854
+ const text = await response.text();
1855
+ const parsed = text ? JSON.parse(text) : {};
1856
+ if (isRecord5(parsed) && parsed.success === false) {
1857
+ throw new AiDispatcherError(
1858
+ "PROVIDER_REQUEST_FAILED",
1859
+ messageFrom(parsed) ?? "Cloudflare request failed.",
1860
+ { body: parsed }
1861
+ );
1862
+ }
1863
+ return parsed;
1864
+ } catch (error) {
1865
+ if (error instanceof AiDispatcherError) throw error;
1866
+ if (error instanceof SyntaxError) {
1867
+ throw new AiDispatcherError("PROVIDER_REQUEST_FAILED", "Cloudflare returned a response that was not JSON.");
1868
+ }
1869
+ if (error instanceof Error && error.name === "AbortError") {
1870
+ throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "Cloudflare request timed out.");
1871
+ }
1872
+ throw new AiDispatcherError(
1873
+ "PROVIDER_GATEWAY_UNREACHABLE",
1874
+ error instanceof Error ? error.message : "Cloudflare request failed before receiving a response."
1875
+ );
1876
+ } finally {
1877
+ clearTimeout(timeout);
1878
+ }
1879
+ }
1880
+ async function readFailure(response) {
1881
+ const text = await response.text();
1882
+ let body = text;
1883
+ try {
1884
+ body = text ? JSON.parse(text) : void 0;
1885
+ } catch {
1886
+ body = text;
1887
+ }
1888
+ const message = messageFrom(body) ?? `Cloudflare request failed with status ${response.status}.`;
1889
+ const retryable = response.status === 429 || response.status === 408 || response.status >= 500;
1890
+ return new AiDispatcherError(classifyStatus(response.status), message, {
1891
+ status: response.status,
1892
+ body,
1893
+ retryable
1894
+ });
1895
+ }
1896
+ function classifyStatus(status) {
1897
+ if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
1898
+ if (status === 404) return "PROVIDER_MODEL_NOT_FOUND";
1899
+ if (status === 429 || status === 408 || status >= 500) return "PROVIDER_RETRYABLE";
1900
+ return "PROVIDER_REQUEST_FAILED";
1901
+ }
1902
+ function isConfigError(code) {
1903
+ return code === "CLOUDFLARE_API_TOKEN_MISSING" || code === "CLOUDFLARE_ACCOUNT_ID_MISSING" || code === "MODEL_REQUIRED" || code === "INPUT_REQUIRED" || code === "UNSUPPORTED_REQUEST_FIELD";
1904
+ }
1905
+ function asTerminalError(error) {
1906
+ if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
1907
+ const status = isRecord5(error.details) ? error.details.status : void 0;
1908
+ if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
1909
+ return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
1910
+ }
1911
+ if (error instanceof AiDispatcherError) return error;
1912
+ return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
1913
+ }
1914
+ function normalizeCloudflareResponse(params) {
1915
+ const payload = unwrap(params.response);
1916
+ const calls = params.functionTools ?? extractCalls(params.compiled.endpoint, params.response);
1917
+ const usage = normalizeUsage(payload.usage) ?? normalizeUsage(isRecord5(params.response) ? params.response.usage : void 0);
1918
+ const status = params.status ?? (calls.length ? "requires_action" : "completed");
1919
+ return {
1920
+ id: typeof payload.id === "string" ? payload.id : params.requestId,
1921
+ status,
1922
+ apiMode: responseApiMode(params.compiled.endpoint),
1923
+ model: typeof payload.model === "string" ? payload.model : modelOf(params.compiled),
1924
+ text: answerText(payload),
1925
+ citations: [],
1926
+ images: [],
1927
+ patches: [],
1928
+ toolUsage: emptyToolUsage(calls),
1929
+ ...usage ? { usage } : {},
1930
+ warnings: params.compiled.warnings,
1931
+ errors: [],
1932
+ raw: { request: params.compiled.body, response: params.response },
1933
+ ...params.request.metadata ? { metadata: params.request.metadata } : {}
1934
+ };
1935
+ }
1936
+ function responseApiMode(endpoint) {
1937
+ return endpoint === "responses" ? "responses" : "chat";
1938
+ }
1939
+ function answerText(response) {
1940
+ if (typeof response.output_text === "string") return response.output_text;
1941
+ if (typeof response.text === "string" && !Array.isArray(response.choices) && !Array.isArray(response.content)) {
1942
+ return response.text;
1943
+ }
1944
+ const responses = responsesAnswer(response);
1945
+ if (responses) return responses;
1946
+ const chat = chatAnswer(response);
1947
+ if (chat) return chat;
1948
+ const anthropic = anthropicAnswer(response);
1949
+ if (anthropic) return anthropic;
1950
+ if (isRecord5(response.result)) return answerText(response.result);
1951
+ return "";
1952
+ }
1953
+ function responsesAnswer(response) {
1954
+ if (!Array.isArray(response.output)) return "";
1955
+ const parts = [];
1956
+ for (const item of response.output) {
1957
+ if (!isRecord5(item) || item.type !== "message" || !Array.isArray(item.content)) continue;
1958
+ for (const part of item.content) {
1959
+ if (isRecord5(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
1960
+ }
1961
+ }
1962
+ return parts.join("");
1963
+ }
1964
+ function chatAnswer(response) {
1965
+ const message = chatAssistantMessage(response);
1966
+ if (!message) return "";
1967
+ const content = message.content;
1968
+ if (typeof content === "string") return content;
1969
+ if (!Array.isArray(content)) return "";
1970
+ return content.map((part) => isRecord5(part) && typeof part.text === "string" ? part.text : "").filter(Boolean).join("");
1971
+ }
1972
+ function anthropicAnswer(response) {
1973
+ if (!Array.isArray(response.content)) return "";
1974
+ return response.content.map((part) => isRecord5(part) && part.type === "text" && typeof part.text === "string" ? part.text : "").filter(Boolean).join("");
1975
+ }
1976
+ function chatAssistantMessage(response) {
1977
+ if (!Array.isArray(response.choices) || !isRecord5(response.choices[0])) return void 0;
1978
+ const message = response.choices[0].message;
1979
+ return isRecord5(message) ? message : void 0;
1980
+ }
1981
+ function extractCalls(endpoint, response) {
1982
+ const record2 = unwrap(response);
1983
+ if (endpoint === "responses") return responsesCalls(record2);
1984
+ if (endpoint === "messages") return anthropicCalls(record2);
1985
+ return chatCalls(record2);
1986
+ }
1987
+ function responsesCalls(response) {
1988
+ if (!Array.isArray(response.output)) return [];
1989
+ const calls = [];
1990
+ for (const item of response.output) {
1991
+ if (!isRecord5(item) || item.type !== "function_call") continue;
1992
+ calls.push(callUsage(
1993
+ typeof item.name === "string" ? item.name : "function",
1994
+ typeof item.call_id === "string" ? item.call_id : "function",
1995
+ item.arguments
1996
+ ));
1997
+ }
1998
+ return calls;
1999
+ }
2000
+ function chatCalls(response) {
2001
+ const message = chatAssistantMessage(response);
2002
+ if (!message || !Array.isArray(message.tool_calls)) return [];
2003
+ const calls = [];
2004
+ for (const item of message.tool_calls) {
2005
+ if (!isRecord5(item) || !isRecord5(item.function)) continue;
2006
+ const name = typeof item.function.name === "string" ? item.function.name : "function";
2007
+ calls.push(callUsage(
2008
+ name,
2009
+ typeof item.id === "string" ? item.id : name,
2010
+ item.function.arguments
2011
+ ));
2012
+ }
2013
+ return calls;
2014
+ }
2015
+ function anthropicCalls(response) {
2016
+ if (!Array.isArray(response.content)) return [];
2017
+ const calls = [];
2018
+ for (const item of response.content) {
2019
+ if (!isRecord5(item) || item.type !== "tool_use") continue;
2020
+ const name = typeof item.name === "string" ? item.name : "function";
2021
+ calls.push({
2022
+ name,
2023
+ callId: typeof item.id === "string" ? item.id : name,
2024
+ status: "completed",
2025
+ args: item.input
2026
+ });
2027
+ }
2028
+ return calls;
2029
+ }
2030
+ function callUsage(name, callId, rawArgs) {
2031
+ let args = rawArgs;
2032
+ if (typeof rawArgs === "string") {
2033
+ try {
2034
+ args = JSON.parse(rawArgs);
2035
+ } catch {
2036
+ args = rawArgs;
2037
+ }
2038
+ }
2039
+ return { name, callId, status: "completed", args };
2040
+ }
2041
+ function emptyToolUsage(calls) {
2042
+ const unused = { requested: false, required: false, used: false };
2043
+ return {
2044
+ serverTools: {
2045
+ webSearch: unused,
2046
+ webFetch: unused,
2047
+ datetime: unused,
2048
+ imageGeneration: unused,
2049
+ applyPatch: unused,
2050
+ fusion: unused,
2051
+ advisor: unused,
2052
+ subagent: unused
2053
+ },
2054
+ functionTools: calls
2055
+ };
2056
+ }
2057
+ function normalizeUsage(usage) {
2058
+ if (!isRecord5(usage)) return void 0;
2059
+ const inputTokens = numberValue(usage.input_tokens) ?? numberValue(usage.prompt_tokens);
2060
+ const outputTokens = numberValue(usage.output_tokens) ?? numberValue(usage.completion_tokens);
2061
+ const totalTokens = numberValue(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
2062
+ if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0) return { raw: usage };
2063
+ return {
2064
+ raw: usage,
2065
+ ...inputTokens !== void 0 ? { inputTokens } : {},
2066
+ ...outputTokens !== void 0 ? { outputTokens } : {},
2067
+ ...totalTokens !== void 0 ? { totalTokens } : {}
2068
+ };
2069
+ }
2070
+ function failedResponse(request, requestId, error, apiMode) {
2071
+ const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
2072
+ return {
2073
+ id: requestId,
2074
+ status: "failed",
2075
+ apiMode,
2076
+ model: request.model ?? "",
2077
+ text: "",
2078
+ citations: [],
2079
+ images: [],
2080
+ patches: [],
2081
+ toolUsage: emptyToolUsage([]),
2082
+ warnings: [],
2083
+ errors: [{
2084
+ code: normalized.code,
2085
+ message: normalized.message,
2086
+ source: "runtime",
2087
+ details: normalized.details
2088
+ }],
2089
+ raw: { request, response: void 0 }
2090
+ };
2091
+ }
2092
+ function errorEvent(requestId, error) {
2093
+ const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
2094
+ return {
2095
+ type: "stream.error",
2096
+ requestId,
2097
+ data: {
2098
+ error: {
2099
+ code: normalized.code,
2100
+ message: normalized.message,
2101
+ source: "runtime",
2102
+ details: normalized.details
2103
+ }
2104
+ }
2105
+ };
481
2106
  }
482
- function assertServer(server, seen) {
483
- if (!isRecord4(server) || typeof server.id !== "string" || server.id.length === 0 || !Array.isArray(server.tools) || typeof server.callTool !== "function") {
484
- throw new AiDispatcherError(
485
- "MCP_SERVER_INVALID",
486
- "An MCP server needs an id, a tools array, and a callTool function."
487
- );
488
- }
489
- if (seen.has(server.id)) {
490
- throw new AiDispatcherError("MCP_SERVER_INVALID", `MCP server "${server.id}" is listed more than once.`);
2107
+ function textDelta(event) {
2108
+ if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) return event.delta;
2109
+ if (event.type === "content_block_delta" && isRecord5(event.delta) && event.delta.type === "text_delta" && typeof event.delta.text === "string" && event.delta.text) {
2110
+ return event.delta.text;
491
2111
  }
492
- seen.add(server.id);
493
- if (!TOOL_TOKEN.test(server.id)) {
494
- throw new AiDispatcherError(
495
- "MCP_TOOL_NAME_INVALID",
496
- `MCP server id "${server.id}" must use letters, numbers, underscores, and hyphens.`
497
- );
2112
+ const choice = firstChoiceDelta(event);
2113
+ if (choice && typeof choice.content === "string" && choice.content) return choice.content;
2114
+ return void 0;
2115
+ }
2116
+ function reasoningDelta(event) {
2117
+ if (event.type === "response.reasoning_summary_text.delta" && typeof event.delta === "string" && event.delta) return event.delta;
2118
+ if (event.type === "content_block_delta" && isRecord5(event.delta) && event.delta.type === "thinking_delta" && typeof event.delta.thinking === "string" && event.delta.thinking) {
2119
+ return event.delta.thinking;
498
2120
  }
2121
+ const choice = firstChoiceDelta(event);
2122
+ if (!choice) return void 0;
2123
+ if (typeof choice.reasoning === "string" && choice.reasoning) return choice.reasoning;
2124
+ if (typeof choice.reasoning_content === "string" && choice.reasoning_content) return choice.reasoning_content;
2125
+ return void 0;
499
2126
  }
500
- function exposedToolName(serverId, tool) {
501
- if (typeof tool.name !== "string" || !TOOL_TOKEN.test(serverId) || !TOOL_TOKEN.test(tool.name)) {
502
- throw new AiDispatcherError(
503
- "MCP_TOOL_NAME_INVALID",
504
- `MCP tool "${serverId}.${typeof tool.name === "string" ? tool.name : ""}" must use letters, numbers, underscores, and hyphens.`
505
- );
2127
+ function toolCallDelta(event) {
2128
+ if (event.type === "response.function_call_arguments.delta" && typeof event.delta === "string") {
2129
+ return {
2130
+ index: 0,
2131
+ ...typeof event.name === "string" ? { name: event.name } : {},
2132
+ argumentsDelta: event.delta
2133
+ };
506
2134
  }
507
- const exposedName = `${serverId}__${tool.name}`;
508
- if (exposedName.length > MAX_EXPOSED_NAME_LENGTH) {
509
- throw new AiDispatcherError(
510
- "MCP_TOOL_NAME_INVALID",
511
- `MCP tool "${exposedName}" is longer than ${MAX_EXPOSED_NAME_LENGTH} characters.`
512
- );
2135
+ if (event.type === "content_block_delta" && isRecord5(event.delta) && event.delta.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
2136
+ return { index: typeof event.index === "number" ? event.index : 0, argumentsDelta: event.delta.partial_json };
513
2137
  }
514
- return exposedName;
2138
+ const choice = firstChoiceDelta(event);
2139
+ if (!choice || !Array.isArray(choice.tool_calls) || !isRecord5(choice.tool_calls[0])) return void 0;
2140
+ const tool = choice.tool_calls[0];
2141
+ const fn = isRecord5(tool.function) ? tool.function : void 0;
2142
+ const args = fn && typeof fn.arguments === "string" ? fn.arguments : void 0;
2143
+ if (!args && !(fn && typeof fn.name === "string")) return void 0;
2144
+ return {
2145
+ index: typeof tool.index === "number" ? tool.index : 0,
2146
+ ...fn && typeof fn.name === "string" ? { name: fn.name } : {},
2147
+ ...args ? { argumentsDelta: args } : {}
2148
+ };
515
2149
  }
516
- function toolDescription(tool) {
517
- if (tool.description === void 0) return "";
518
- if (typeof tool.description !== "string") {
519
- throw new AiDispatcherError(
520
- "MCP_TOOL_NAME_INVALID",
521
- `MCP tool "${tool.name}" description must be a string.`
522
- );
2150
+ function streamUsage(event) {
2151
+ if (event.type === "response.completed" && isRecord5(event.response)) return normalizeUsage(event.response.usage);
2152
+ if (isRecord5(event.usage)) return normalizeUsage(event.usage);
2153
+ return void 0;
2154
+ }
2155
+ function firstChoiceDelta(event) {
2156
+ if (!Array.isArray(event.choices) || !isRecord5(event.choices[0])) return void 0;
2157
+ const delta = event.choices[0].delta;
2158
+ return isRecord5(delta) ? delta : void 0;
2159
+ }
2160
+ function unwrap(response) {
2161
+ if (!isRecord5(response)) return {};
2162
+ if (response.success === true && isRecord5(response.result)) return response.result;
2163
+ return response;
2164
+ }
2165
+ function modelOf(compiled) {
2166
+ if (typeof compiled.body.model === "string") return compiled.body.model;
2167
+ return "";
2168
+ }
2169
+ function messageFrom(body) {
2170
+ if (!isRecord5(body)) return void 0;
2171
+ if (typeof body.message === "string") return body.message;
2172
+ if (isRecord5(body.error) && typeof body.error.message === "string") return body.error.message;
2173
+ if (Array.isArray(body.errors)) {
2174
+ for (const error of body.errors) {
2175
+ if (isRecord5(error) && typeof error.message === "string") return error.message;
2176
+ }
523
2177
  }
524
- return tool.description;
2178
+ return void 0;
525
2179
  }
526
- function toolParameters(tool) {
527
- if (tool.inputSchema === void 0) return { ...DEFAULT_PARAMETERS, properties: {} };
528
- if (!isRecord4(tool.inputSchema)) {
529
- throw new AiDispatcherError(
530
- "MCP_TOOL_NAME_INVALID",
531
- `MCP tool "${tool.name}" inputSchema must be an object.`
532
- );
2180
+ function numberValue(value) {
2181
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2182
+ }
2183
+ function applyProviderRecord(body, metadata) {
2184
+ if (metadata) return { ...body, metadata };
2185
+ if (body.metadata === void 0) return body;
2186
+ const rest = { ...body };
2187
+ delete rest.metadata;
2188
+ return rest;
2189
+ }
2190
+ function overlayMetadata(base, extra) {
2191
+ if (!base) return extra;
2192
+ if (!extra) return base;
2193
+ return { ...base, ...extra };
2194
+ }
2195
+ function endpointUrl(options, endpoint) {
2196
+ const account = encodeURIComponent(options.accountId);
2197
+ const root = `${options.baseUrl}/accounts/${account}/ai`;
2198
+ if (endpoint === "chat") return `${root}/v1/chat/completions`;
2199
+ if (endpoint === "responses") return `${root}/v1/responses`;
2200
+ if (endpoint === "messages") return `${root}/v1/messages`;
2201
+ return `${root}/run`;
2202
+ }
2203
+ function buildHeaders(options, call, model, metadata) {
2204
+ const gatewayId = call?.gatewayId?.trim() || options.gatewayId?.trim() || (isWorkersModel(model) ? DEFAULT_GATEWAY_ID : void 0);
2205
+ const gateway = mergeGateway(options.gateway, call?.gateway);
2206
+ const cache = call?.cache;
2207
+ const skip = gateway?.skipCache !== void 0 ? gateway.skipCache : cache?.skip === true ? true : void 0;
2208
+ const ttl = gateway?.cacheTtlSeconds ?? cache?.ttlSeconds;
2209
+ const key = gateway?.cacheKey ?? cache?.key;
2210
+ return clean({
2211
+ "content-type": "application/json",
2212
+ authorization: `Bearer ${options.apiToken}`,
2213
+ ...gatewayId ? { "cf-aig-gateway-id": gatewayId } : {},
2214
+ ...skip !== void 0 ? { "cf-aig-skip-cache": String(skip) } : {},
2215
+ ...ttl !== void 0 ? { "cf-aig-cache-ttl": String(ttl) } : {},
2216
+ ...key ? { "cf-aig-cache-key": key } : {},
2217
+ ...gateway?.collectLog !== void 0 ? { "cf-aig-collect-log": String(gateway.collectLog) } : {},
2218
+ ...gateway?.requestTimeoutMs !== void 0 ? { "cf-aig-request-timeout": String(gateway.requestTimeoutMs) } : {},
2219
+ ...gateway?.maxGatewayAttempts !== void 0 ? { "cf-aig-max-attempts": String(gateway.maxGatewayAttempts) } : {},
2220
+ ...gateway?.retryDelayMs !== void 0 ? { "cf-aig-retry-delay": String(gateway.retryDelayMs) } : {},
2221
+ ...isBackoff(gateway?.backoff) ? { "cf-aig-backoff": gateway.backoff } : {},
2222
+ ...metadata ? { "cf-aig-metadata": JSON.stringify(metadata) } : {}
2223
+ });
2224
+ }
2225
+ function mergeGateway(base, over) {
2226
+ if (!base && !over) return void 0;
2227
+ return { ...base, ...over };
2228
+ }
2229
+ function isBackoff(value) {
2230
+ return value === "constant" || value === "linear" || value === "exponential";
2231
+ }
2232
+ function isWorkersModel(model) {
2233
+ return model.trim().toLowerCase().startsWith("@cf/");
2234
+ }
2235
+ function isBareOpenAiModel(id) {
2236
+ return /^(gpt|o[134])(?:[.\-:]|$)/i.test(id.trim());
2237
+ }
2238
+ function timeoutFor(request, options) {
2239
+ return request.execution?.timeoutMs ?? options.timeoutMs;
2240
+ }
2241
+ function delay(ms) {
2242
+ return new Promise((resolve) => setTimeout(resolve, ms));
2243
+ }
2244
+ async function* parseSse(response) {
2245
+ const body = response.body;
2246
+ if (!body) throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "Cloudflare stream response had no body.");
2247
+ const reader = body.getReader();
2248
+ const decoder = new TextDecoder();
2249
+ let buffer = "";
2250
+ while (true) {
2251
+ const chunk = await reader.read();
2252
+ if (chunk.done) break;
2253
+ buffer += decoder.decode(chunk.value, { stream: true });
2254
+ const parts = buffer.split("\n\n");
2255
+ buffer = parts.pop() ?? "";
2256
+ for (const part of parts) {
2257
+ const parsed = parseSsePart(part);
2258
+ if (parsed !== void 0) yield parsed;
2259
+ }
533
2260
  }
534
- return tool.inputSchema;
2261
+ const trailing = parseSsePart(buffer);
2262
+ if (trailing !== void 0) yield trailing;
535
2263
  }
536
- function textContent(content) {
537
- if (!Array.isArray(content)) return void 0;
538
- const parts = [];
539
- for (const part of content) {
540
- if (!isRecord4(part) || part.type !== "text" || typeof part.text !== "string") continue;
541
- parts.push(part.text);
2264
+ function parseSsePart(part) {
2265
+ const data = part.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
2266
+ if (!data || data === "[DONE]") return void 0;
2267
+ try {
2268
+ return JSON.parse(data);
2269
+ } catch {
2270
+ return void 0;
542
2271
  }
543
- if (!parts.length) return void 0;
544
- return parts.join("\n");
545
2272
  }
546
- function toArgs(args) {
547
- if (isRecord4(args)) return args;
548
- return {};
2273
+ function resolveCloudflareOptions(options) {
2274
+ const {
2275
+ apiToken,
2276
+ accountId,
2277
+ baseUrl,
2278
+ defaultModel,
2279
+ endpoint,
2280
+ gatewayId,
2281
+ timeoutMs,
2282
+ maxAttempts,
2283
+ fetch,
2284
+ tools,
2285
+ logger,
2286
+ run,
2287
+ ...gateway
2288
+ } = options;
2289
+ void run;
2290
+ return {
2291
+ apiToken: apiToken ?? readEnv("CLOUDFLARE_API_TOKEN") ?? "",
2292
+ accountId: accountId ?? readEnv("CLOUDFLARE_ACCOUNT_ID") ?? "",
2293
+ baseUrl: (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
2294
+ ...defaultModel ? { defaultModel } : {},
2295
+ ...endpoint ? { endpoint } : {},
2296
+ ...gatewayId ? { gatewayId } : {},
2297
+ ...hasGateway(gateway) ? { gateway } : {},
2298
+ timeoutMs: timeoutMs ?? DEFAULT_TIMEOUT_MS,
2299
+ maxAttempts: maxAttempts ?? 2,
2300
+ fetch: fetch ?? globalThis.fetch,
2301
+ ...tools ? { tools } : {},
2302
+ ...logger ? { logger } : {}
2303
+ };
549
2304
  }
550
- function listFailed(serverId, error) {
551
- if (error instanceof AiDispatcherError && error.code === "MCP_LIST_FAILED") return error;
552
- const message = error instanceof Error ? error.message : `MCP server "${serverId}" listTools failed.`;
553
- return new AiDispatcherError("MCP_LIST_FAILED", message);
2305
+ function hasGateway(gateway) {
2306
+ return Object.values(gateway).some((value) => value !== void 0);
554
2307
  }
555
- function stringify(result) {
556
- const serialized = JSON.stringify(result);
557
- return serialized === void 0 ? "null" : serialized;
2308
+ function clean(body) {
2309
+ return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
558
2310
  }
559
- function isRecord4(value) {
2311
+ function readEnv(name) {
2312
+ if (typeof process === "undefined") return void 0;
2313
+ return process.env[name];
2314
+ }
2315
+ function isRecord5(value) {
560
2316
  return typeof value === "object" && value !== null && !Array.isArray(value);
561
2317
  }
562
2318
 
563
2319
  // src/prepare-request.ts
564
- import {
565
- AIProfilesError,
566
- mergeReasoningBody,
567
- resolveReasoning
568
- } from "@x12i/ai-profiles";
569
2320
  function prepareDispatchRequest(params) {
570
2321
  const cloned = cloneRequest(params.request);
2322
+ applyDispatchIdentity(cloned);
2323
+ absorbOverrideMetadata(cloned);
571
2324
  const provider = params.provider;
572
2325
  const reasoningEffort = cloned.reasoningEffort;
573
2326
  const bedrockControls = cloned.bedrockControls;
2327
+ const cachePolicy = cloned.cache;
2328
+ const cloudflareRequest = cloned.cloudflare;
574
2329
  delete cloned.provider;
575
2330
  delete cloned.reasoningEffort;
576
2331
  delete cloned.bedrockControls;
2332
+ delete cloned.cache;
2333
+ delete cloned.cloudflare;
577
2334
  if (provider === "openai") assertOpenAiConfigured(params.options);
2335
+ if (provider === "cloudflare") assertCloudflareConfigured(params.options);
578
2336
  if (provider === "bedrock" && cloned.reasoning !== void 0) {
579
2337
  if (bedrockControls?.additionalModelRequestFields === void 0 && reasoningEffort === void 0) {
580
2338
  throw new AiDispatcherError(
@@ -587,6 +2345,7 @@ function prepareDispatchRequest(params) {
587
2345
  const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
588
2346
  const explicit = hasExplicitControl(provider, cloned, bedrockControls);
589
2347
  let directive;
2348
+ let catalogPrefix;
590
2349
  if (!explicit && reasoningEffort !== void 0) {
591
2350
  if (!requestedModel) {
592
2351
  throw new AiDispatcherError(
@@ -594,26 +2353,69 @@ function prepareDispatchRequest(params) {
594
2353
  "reasoningEffort requires a model id. Set request.model or the provider defaultModel."
595
2354
  );
596
2355
  }
597
- directive = resolveCatalog({
598
- target: provider,
599
- model: requestedModel,
600
- effort: reasoningEffort
601
- });
2356
+ if (provider === "cloudflare") {
2357
+ const query = cloudflareCatalogQuery(requestedModel);
2358
+ catalogPrefix = query.prefix;
2359
+ directive = resolveCatalog({ target: query.target, model: query.model, effort: reasoningEffort }, true);
2360
+ } else if (isReasoningTarget(provider)) {
2361
+ directive = resolveCatalog({ target: provider, model: requestedModel, effort: reasoningEffort }, false);
2362
+ }
602
2363
  }
603
2364
  if (provider === "openrouter") {
604
2365
  applyNestedReasoning(cloned, reasoningEffort);
605
2366
  }
606
- if (directive?.model) cloned.model = directive.model;
2367
+ if (directive?.model) {
2368
+ cloned.model = provider === "cloudflare" ? restoreCloudflareModel(directive.model, catalogPrefix) : directive.model;
2369
+ }
607
2370
  if (directive?.promptPrefix) applyPromptPrefix(cloned, directive.promptPrefix);
608
2371
  const effectiveModel = cloned.model ?? requestedModel;
2372
+ const requestedEndpoint = cloudflareRequest?.endpoint ?? params.options.cloudflare?.endpoint;
2373
+ const cloudflareEndpoint = provider === "cloudflare" ? selectCloudflareEndpoint({
2374
+ model: effectiveModel,
2375
+ ...requestedEndpoint ? { endpoint: requestedEndpoint } : {},
2376
+ ...cloned.apiMode ? { apiMode: cloned.apiMode } : {}
2377
+ }) : void 0;
609
2378
  const call = {};
610
2379
  if (reasoningEffort !== void 0 && provider === "openrouter") {
611
2380
  call.suppressNestedReasoningFallback = true;
612
2381
  }
613
2382
  const overlay = directive && hasOwnKeys(directive.body) ? directive.body : explicit && provider === "bedrock" && bedrockControls?.additionalModelRequestFields ? { additionalModelRequestFields: bedrockControls.additionalModelRequestFields } : void 0;
614
- if (overlay) {
2383
+ const cache = cachePolicy ? resolvePromptCache({
2384
+ target: provider,
2385
+ requestedModel,
2386
+ effectiveModel,
2387
+ policy: cachePolicy,
2388
+ ...cloudflareEndpoint ? { cloudflareEndpoint } : {}
2389
+ }) : void 0;
2390
+ const cachePlan = cache?.plan;
2391
+ const cacheWarnings = cache?.warnings;
2392
+ const cacheResolution = cache?.resolution;
2393
+ const cacheTouchesBody = Boolean(
2394
+ cachePlan && (cachePlan.stripMarkers || cachePlan.placeBreakpoint || cachePlan.sendOpenAiOptions)
2395
+ );
2396
+ if (overlay || cacheTouchesBody) {
615
2397
  const bodyOverlay = overlay;
616
- call.finalizeBody = (body) => mergeReasoningBody(body, bodyOverlay);
2398
+ call.finalizeBody = (body) => {
2399
+ const merged = bodyOverlay ? mergeReasoningBody(body, bodyOverlay) : body;
2400
+ if (!cacheTouchesBody || !cachePlan) return merged;
2401
+ const applied = applyPromptCache(merged, cachePlan);
2402
+ if (cachePlan.placeBreakpoint && !applied.placed) {
2403
+ noteUnplacedCache(cacheResolution, cacheWarnings, provider, effectiveModel);
2404
+ return merged;
2405
+ }
2406
+ return applied.body;
2407
+ };
2408
+ }
2409
+ if (provider === "cloudflare" && cloudflareEndpoint) {
2410
+ const gateway = mergeCloudflareGateway(params.options.cloudflare, cloudflareRequest);
2411
+ const gatewayId = cloudflareRequest?.gatewayId ?? params.options.cloudflare?.gatewayId;
2412
+ call.cloudflare = {
2413
+ endpoint: cloudflareEndpoint,
2414
+ ...gatewayId ? { gatewayId } : {},
2415
+ ...cloudflareRequest?.run ? { run: cloudflareRequest.run } : {},
2416
+ ...gateway ? { gateway } : {},
2417
+ ...cache?.plan.gateway ? { cache: cache.plan.gateway } : {}
2418
+ };
617
2419
  }
618
2420
  if (directive && hasStreamMetadata(directive)) {
619
2421
  const response = directive.response;
@@ -634,19 +2436,51 @@ function prepareDispatchRequest(params) {
634
2436
  effectiveModel,
635
2437
  ...resolution ? { resolution } : {},
636
2438
  ...directive ? { directive } : {},
2439
+ ...cacheResolution ? { cacheResolution } : {},
2440
+ ...cacheWarnings ? { cacheWarnings } : {},
637
2441
  call
638
2442
  };
639
2443
  }
640
- function resolveCatalog(input) {
2444
+ function noteUnplacedCache(resolution, warnings, target, model) {
2445
+ if (resolution) {
2446
+ resolution.outcome = "ignored";
2447
+ delete resolution.ttl;
2448
+ }
2449
+ if (!warnings) return;
2450
+ const kept = warnings.filter((warning) => warning.code !== "CACHE_TTL_DEGRADED");
2451
+ warnings.length = 0;
2452
+ warnings.push(...kept, {
2453
+ code: "CACHE_UNSUPPORTED",
2454
+ message: "Explicit prompt caching needs a system or instructions prefix. The call still runs without a breakpoint.",
2455
+ details: { target, model }
2456
+ });
2457
+ }
2458
+ function resolveCatalog(input, ignoreUnknown) {
641
2459
  try {
642
2460
  return resolveReasoning(input);
643
2461
  } catch (error) {
644
2462
  if (error instanceof AIProfilesError) {
2463
+ if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2464
+ return {
2465
+ outcome: "ignored",
2466
+ body: {},
2467
+ response: {
2468
+ reasoningTextPaths: [],
2469
+ streamReasoningKeys: [],
2470
+ streamReasoningPaths: [],
2471
+ stripFromHistoryPaths: [],
2472
+ stripThinkTags: false
2473
+ }
2474
+ };
2475
+ }
645
2476
  throw new AiDispatcherError(error.code, error.message, error.details);
646
2477
  }
647
2478
  throw error;
648
2479
  }
649
2480
  }
2481
+ function isReasoningTarget(provider) {
2482
+ return provider === "openrouter" || provider === "bedrock" || provider === "openai";
2483
+ }
650
2484
  function hasExplicitControl(provider, request, bedrockControls) {
651
2485
  if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
652
2486
  return request.reasoning !== void 0;
@@ -680,12 +2514,12 @@ function settleNested(policy, kind, parentEffort) {
680
2514
  if (!effort) return next;
681
2515
  const model = kind === "fusion" ? next.judgeModel : next.model;
682
2516
  if (!model) return next;
683
- const directive = resolveCatalog({ target: "openrouter", model, effort });
2517
+ const directive = resolveCatalog({ target: "openrouter", model, effort }, false);
684
2518
  if (directive.model) {
685
2519
  if (kind === "fusion") next.judgeModel = directive.model;
686
2520
  else next.model = directive.model;
687
2521
  }
688
- if (isRecord5(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
2522
+ if (isRecord6(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
689
2523
  return next;
690
2524
  }
691
2525
  function applyPromptPrefix(request, prefix) {
@@ -735,7 +2569,7 @@ function prefixInputItems(items, prefix) {
735
2569
  }
736
2570
  if (!Array.isArray(item.content)) continue;
737
2571
  for (const part of item.content) {
738
- if (!isRecord5(part) || typeof part.text !== "string") continue;
2572
+ if (!isRecord6(part) || typeof part.text !== "string") continue;
739
2573
  if (part.type === "text" || part.type === "input_text") {
740
2574
  part.text = prefix + part.text;
741
2575
  return true;
@@ -744,8 +2578,40 @@ function prefixInputItems(items, prefix) {
744
2578
  }
745
2579
  return false;
746
2580
  }
2581
+ function mergeCloudflareGateway(base, over) {
2582
+ const gateway = {};
2583
+ for (const source of [base, over]) {
2584
+ if (!source) continue;
2585
+ if (source.skipCache !== void 0) gateway.skipCache = source.skipCache;
2586
+ if (source.cacheTtlSeconds !== void 0) gateway.cacheTtlSeconds = source.cacheTtlSeconds;
2587
+ if (source.cacheKey !== void 0) gateway.cacheKey = source.cacheKey;
2588
+ if (source.collectLog !== void 0) gateway.collectLog = source.collectLog;
2589
+ if (source.requestTimeoutMs !== void 0) gateway.requestTimeoutMs = source.requestTimeoutMs;
2590
+ if (source.maxGatewayAttempts !== void 0) gateway.maxGatewayAttempts = source.maxGatewayAttempts;
2591
+ if (source.retryDelayMs !== void 0) gateway.retryDelayMs = source.retryDelayMs;
2592
+ if (source.backoff !== void 0) gateway.backoff = source.backoff;
2593
+ if (source.metadata !== void 0) gateway.metadata = source.metadata;
2594
+ }
2595
+ return Object.keys(gateway).length ? gateway : void 0;
2596
+ }
2597
+ function assertCloudflareConfigured(options) {
2598
+ const apiToken = options.cloudflare?.apiToken ?? readEnv2("CLOUDFLARE_API_TOKEN");
2599
+ if (!apiToken) {
2600
+ throw new AiDispatcherError(
2601
+ "CLOUDFLARE_API_TOKEN_MISSING",
2602
+ "Cloudflare API token is required. Set cloudflare.apiToken or CLOUDFLARE_API_TOKEN."
2603
+ );
2604
+ }
2605
+ const accountId = options.cloudflare?.accountId ?? readEnv2("CLOUDFLARE_ACCOUNT_ID");
2606
+ if (!accountId) {
2607
+ throw new AiDispatcherError(
2608
+ "CLOUDFLARE_ACCOUNT_ID_MISSING",
2609
+ "Cloudflare account id is required. Set cloudflare.accountId or CLOUDFLARE_ACCOUNT_ID."
2610
+ );
2611
+ }
2612
+ }
747
2613
  function assertOpenAiConfigured(options) {
748
- const apiKey = options.openai?.apiKey ?? readEnv("OPENAI_API_KEY");
2614
+ const apiKey = options.openai?.apiKey ?? readEnv2("OPENAI_API_KEY");
749
2615
  if (!apiKey) {
750
2616
  throw new AiDispatcherError(
751
2617
  "OPENAI_API_KEY_MISSING",
@@ -756,8 +2622,40 @@ function assertOpenAiConfigured(options) {
756
2622
  function defaultModelFor(provider, options) {
757
2623
  if (provider === "openrouter") return options.openrouter?.defaultModel;
758
2624
  if (provider === "bedrock") return options.bedrock?.defaultModel;
2625
+ if (provider === "cloudflare") return options.cloudflare?.defaultModel;
759
2626
  return options.openai?.defaultModel;
760
2627
  }
2628
+ var DISPATCH_IDENTITY_FIELDS = ["orgId", "stepId", "skillId"];
2629
+ function absorbOverrideMetadata(request) {
2630
+ const overrides = request.rawOpenRouterOverrides;
2631
+ if (!isRecord6(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
2632
+ if (isRecord6(overrides.metadata)) {
2633
+ const current = { ...request.metadata ?? {} };
2634
+ const extra = {};
2635
+ for (const [key, value] of Object.entries(overrides.metadata)) {
2636
+ if (!Object.prototype.hasOwnProperty.call(current, key)) extra[key] = value;
2637
+ }
2638
+ request.metadata = { ...current, ...extra };
2639
+ }
2640
+ const remaining = {};
2641
+ for (const [key, value] of Object.entries(overrides)) {
2642
+ if (key !== "metadata") remaining[key] = value;
2643
+ }
2644
+ if (Object.keys(remaining).length === 0) delete request.rawOpenRouterOverrides;
2645
+ else request.rawOpenRouterOverrides = remaining;
2646
+ }
2647
+ function applyDispatchIdentity(request) {
2648
+ const identity = {};
2649
+ for (const field of DISPATCH_IDENTITY_FIELDS) {
2650
+ const value = request[field]?.trim();
2651
+ if (value) identity[field] = value;
2652
+ delete request[field];
2653
+ }
2654
+ if (!Object.keys(identity).length) return;
2655
+ const rest = { ...request.metadata ?? {} };
2656
+ for (const key of Object.keys(identity)) delete rest[key];
2657
+ request.metadata = { ...identity, ...rest };
2658
+ }
761
2659
  function cloneRequest(request) {
762
2660
  const functionTools = request.functionTools?.map((tool) => ({ ...tool }));
763
2661
  const cloned = JSON.parse(JSON.stringify(request));
@@ -770,10 +2668,10 @@ function hasOwnKeys(value) {
770
2668
  function hasStreamMetadata(directive) {
771
2669
  return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
772
2670
  }
773
- function isRecord5(value) {
2671
+ function isRecord6(value) {
774
2672
  return typeof value === "object" && value !== null && !Array.isArray(value);
775
2673
  }
776
- function readEnv(name) {
2674
+ function readEnv2(name) {
777
2675
  if (typeof process === "undefined") return void 0;
778
2676
  return process.env[name];
779
2677
  }
@@ -838,11 +2736,12 @@ function toBedrockRequest(request) {
838
2736
  ...request.execution.timeoutMs !== void 0 ? { timeoutMs: request.execution.timeoutMs } : {}
839
2737
  };
840
2738
  }
2739
+ if (request.agentId !== void 0) mapped.agentId = request.agentId;
841
2740
  if (request.metadata !== void 0) mapped.metadata = request.metadata;
842
2741
  return mapped;
843
2742
  }
844
2743
  function assertBedrockSupported(request) {
845
- if (request.serverTools && serverToolsEnabled(request.serverTools)) {
2744
+ if (request.serverTools && serverToolsEnabled2(request.serverTools)) {
846
2745
  throw new AiDispatcherError(
847
2746
  "UNSUPPORTED_REQUEST_FIELD",
848
2747
  "Bedrock does not support OpenRouter server tools."
@@ -876,13 +2775,13 @@ function assertBedrockSupported(request) {
876
2775
  function hasMessages(request) {
877
2776
  return Array.isArray(request.messages) && request.messages.length > 0;
878
2777
  }
879
- function serverToolsEnabled(policy) {
2778
+ function serverToolsEnabled2(policy) {
880
2779
  return Object.values(policy).some((value) => {
881
- if (Array.isArray(value)) return value.some((item) => isEnabledMode(item));
882
- return isEnabledMode(value);
2780
+ if (Array.isArray(value)) return value.some((item) => isEnabledMode2(item));
2781
+ return isEnabledMode2(value);
883
2782
  });
884
2783
  }
885
- function isEnabledMode(value) {
2784
+ function isEnabledMode2(value) {
886
2785
  return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
887
2786
  }
888
2787
  function inputItemsToMessages(items) {
@@ -914,8 +2813,12 @@ function isBedrockToolChoice(toolChoice) {
914
2813
  }
915
2814
 
916
2815
  // src/providers/openai.ts
917
- var DEFAULT_BASE_URL = "https://api.openai.com/v1";
918
- var DEFAULT_TIMEOUT_MS = 12e4;
2816
+ import {
2817
+ OPENROUTER_METADATA_LIMITS as OPENROUTER_METADATA_LIMITS2,
2818
+ toProviderMetadata as toProviderMetadata2
2819
+ } from "@x12i/openrouter-runtime";
2820
+ var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
2821
+ var DEFAULT_TIMEOUT_MS2 = 12e4;
919
2822
  function createOpenAiProviderAdapter(options = {}) {
920
2823
  const resolved = resolveOpenAiOptions(options);
921
2824
  return {
@@ -927,8 +2830,8 @@ function createOpenAiProviderAdapter(options = {}) {
927
2830
  try {
928
2831
  return await runOpenAi(request, resolved, call, requestId);
929
2832
  } catch (error) {
930
- if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
931
- return failedResponse(request, requestId, error);
2833
+ if (error instanceof AiDispatcherError && isConfigError2(error.code)) throw error;
2834
+ return failedResponse2(request, requestId, error);
932
2835
  }
933
2836
  },
934
2837
  executeStreamingChat(request, call) {
@@ -937,7 +2840,7 @@ function createOpenAiProviderAdapter(options = {}) {
937
2840
  };
938
2841
  }
939
2842
  function compileOpenAiRequest(request, options, call, streaming) {
940
- assertSupported(request);
2843
+ assertSupported2(request);
941
2844
  const model = (request.model ?? options.defaultModel ?? "").trim();
942
2845
  if (!model) {
943
2846
  throw new AiDispatcherError(
@@ -951,12 +2854,12 @@ function compileOpenAiRequest(request, options, call, streaming) {
951
2854
  "OpenAI API key is required. Set openai.apiKey or OPENAI_API_KEY."
952
2855
  );
953
2856
  }
954
- const instructions = compileInstructions(request);
2857
+ const instructions = compileInstructions2(request);
955
2858
  const input = compileInput(request);
956
2859
  if (input === void 0) {
957
2860
  throw new AiDispatcherError("INPUT_REQUIRED", "Provide request.messages, request.input, or request.prompt.");
958
2861
  }
959
- let body = clean({
2862
+ let body = clean2({
960
2863
  model,
961
2864
  input,
962
2865
  instructions,
@@ -970,15 +2873,16 @@ function compileOpenAiRequest(request, options, call, streaming) {
970
2873
  if (call?.finalizeBody) body = call.finalizeBody(body);
971
2874
  if (streaming) body = { ...body, stream: true };
972
2875
  else body = { ...body, stream: false };
2876
+ body = attachProviderMetadata(body, request);
973
2877
  return {
974
2878
  url: `${options.baseUrl}/responses`,
975
- headers: buildHeaders(options),
2879
+ headers: buildHeaders2(options),
976
2880
  body,
977
2881
  warnings: []
978
2882
  };
979
2883
  }
980
- function assertSupported(request) {
981
- if (request.serverTools && serverToolsEnabled2(request.serverTools)) {
2884
+ function assertSupported2(request) {
2885
+ if (request.serverTools && serverToolsEnabled3(request.serverTools)) {
982
2886
  throw new AiDispatcherError(
983
2887
  "UNSUPPORTED_REQUEST_FIELD",
984
2888
  "Direct OpenAI does not support OpenRouter server tools."
@@ -1003,21 +2907,21 @@ function assertSupported(request) {
1003
2907
  );
1004
2908
  }
1005
2909
  }
1006
- function serverToolsEnabled2(policy) {
2910
+ function serverToolsEnabled3(policy) {
1007
2911
  return Object.values(policy).some((value) => {
1008
- if (Array.isArray(value)) return value.some((item) => isEnabledMode2(item));
1009
- return isEnabledMode2(value);
2912
+ if (Array.isArray(value)) return value.some((item) => isEnabledMode3(item));
2913
+ return isEnabledMode3(value);
1010
2914
  });
1011
2915
  }
1012
- function isEnabledMode2(value) {
2916
+ function isEnabledMode3(value) {
1013
2917
  return typeof value === "object" && value !== null && "mode" in value && value.mode !== "disabled";
1014
2918
  }
1015
- function compileInstructions(request) {
2919
+ function compileInstructions2(request) {
1016
2920
  if (request.instructions) return request.instructions;
1017
2921
  if (request.system) return request.system;
1018
2922
  const systemMessages = (request.messages ?? []).filter((message) => message.role === "system");
1019
2923
  if (!systemMessages.length) return void 0;
1020
- return systemMessages.map((message) => contentToText(message.content)).filter(Boolean).join("\n");
2924
+ return systemMessages.map((message) => contentToText2(message.content)).filter(Boolean).join("\n");
1021
2925
  }
1022
2926
  function compileInput(request) {
1023
2927
  if (request.input !== void 0) return request.input;
@@ -1037,7 +2941,7 @@ function compileInputItem(message) {
1037
2941
  return {
1038
2942
  type: "function_call_output",
1039
2943
  call_id: message.toolCallId ?? message.name ?? "tool",
1040
- output: contentToText(message.content)
2944
+ output: contentToText2(message.content)
1041
2945
  };
1042
2946
  }
1043
2947
  const role = message.role === "assistant" ? "assistant" : "user";
@@ -1049,10 +2953,10 @@ function compileContent(content) {
1049
2953
  if (part.type === "text") return { type: "input_text", text: part.text };
1050
2954
  if (part.type === "image_url") return { type: "input_image", image_url: part.imageUrl };
1051
2955
  if (part.url) {
1052
- return clean({ type: "input_file", file_url: part.url, filename: part.fileName });
2956
+ return clean2({ type: "input_file", file_url: part.url, filename: part.fileName });
1053
2957
  }
1054
2958
  if (part.data) {
1055
- return clean({ type: "input_file", file_data: part.data, filename: part.fileName });
2959
+ return clean2({ type: "input_file", file_data: part.data, filename: part.fileName });
1056
2960
  }
1057
2961
  throw new AiDispatcherError(
1058
2962
  "UNSUPPORTED_REQUEST_FIELD",
@@ -1086,10 +2990,10 @@ async function runOpenAi(request, options, call, requestId) {
1086
2990
  let active = compileOpenAiRequest(request, options, call, false);
1087
2991
  let response;
1088
2992
  for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
1089
- response = await sendWithRetries(active, options, timeoutFor(request, options));
1090
- const calls = functionCalls(isRecord6(response) ? response : {});
2993
+ response = await sendWithRetries2(active, options, timeoutFor2(request, options));
2994
+ const calls = functionCalls(isRecord7(response) ? response : {});
1091
2995
  if (!calls.length) break;
1092
- if (!calls.every((item) => resolveExecutor(item.name, request, options))) {
2996
+ if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
1093
2997
  return normalizeOpenAiResponse({
1094
2998
  request,
1095
2999
  requestId,
@@ -1107,14 +3011,14 @@ async function runOpenAi(request, options, call, requestId) {
1107
3011
  results.push(await executeOpenAiTool(item, request, options));
1108
3012
  }
1109
3013
  functionUsage.push(...results);
1110
- const record = isRecord6(response) ? response : {};
3014
+ const record2 = isRecord7(response) ? response : {};
1111
3015
  active = {
1112
3016
  ...active,
1113
- body: clean({
3017
+ body: clean2({
1114
3018
  ...active.body,
1115
3019
  tool_choice: "auto",
1116
- previous_response_id: typeof record.id === "string" ? record.id : void 0,
1117
- input: results.map(toFunctionCallOutput)
3020
+ previous_response_id: typeof record2.id === "string" ? record2.id : void 0,
3021
+ input: results.map(toFunctionCallOutput2)
1118
3022
  })
1119
3023
  };
1120
3024
  }
@@ -1127,11 +3031,11 @@ async function runOpenAi(request, options, call, requestId) {
1127
3031
  status: "completed"
1128
3032
  });
1129
3033
  }
1130
- function resolveExecutor(name, request, options) {
3034
+ function resolveExecutor2(name, request, options) {
1131
3035
  return request.functionTools?.find((tool) => tool.name === name)?.executor ?? options.tools?.[name];
1132
3036
  }
1133
3037
  async function executeOpenAiTool(call, request, options) {
1134
- const executor = resolveExecutor(call.name, request, options);
3038
+ const executor = resolveExecutor2(call.name, request, options);
1135
3039
  if (!executor) {
1136
3040
  return call;
1137
3041
  }
@@ -1167,7 +3071,7 @@ async function executeOpenAiTool(call, request, options) {
1167
3071
  return usage;
1168
3072
  }
1169
3073
  }
1170
- function toFunctionCallOutput(result) {
3074
+ function toFunctionCallOutput2(result) {
1171
3075
  const output = result.status === "completed" ? result.result : { error: result.error };
1172
3076
  return {
1173
3077
  type: "function_call_output",
@@ -1175,7 +3079,7 @@ function toFunctionCallOutput(result) {
1175
3079
  output: typeof output === "string" ? output : JSON.stringify(output)
1176
3080
  };
1177
3081
  }
1178
- function contentToText(content) {
3082
+ function contentToText2(content) {
1179
3083
  if (typeof content === "string") return content;
1180
3084
  return content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("");
1181
3085
  }
@@ -1185,7 +3089,7 @@ async function* streamOpenAi(request, options, call) {
1185
3089
  try {
1186
3090
  compiled = compileOpenAiRequest(request, options, call, true);
1187
3091
  } catch (error) {
1188
- yield errorEvent(requestId, error);
3092
+ yield errorEvent2(requestId, error);
1189
3093
  throw error;
1190
3094
  }
1191
3095
  yield {
@@ -1198,7 +3102,7 @@ async function* streamOpenAi(request, options, call) {
1198
3102
  }
1199
3103
  };
1200
3104
  const controller = new AbortController();
1201
- const timeout = setTimeout(() => controller.abort(), timeoutFor(request, options));
3105
+ const timeout = setTimeout(() => controller.abort(), timeoutFor2(request, options));
1202
3106
  let text = "";
1203
3107
  let reported = false;
1204
3108
  const report = (error) => {
@@ -1216,13 +3120,13 @@ async function* streamOpenAi(request, options, call) {
1216
3120
  signal: controller.signal
1217
3121
  });
1218
3122
  if (!response.ok) {
1219
- const failure = await readFailure(response);
3123
+ const failure = await readFailure2(response);
1220
3124
  reported = true;
1221
- yield errorEvent(requestId, failure);
3125
+ yield errorEvent2(requestId, failure);
1222
3126
  throw failure;
1223
3127
  }
1224
- for await (const event of parseSse(response)) {
1225
- if (!isRecord6(event)) continue;
3128
+ for await (const event of parseSse2(response)) {
3129
+ if (!isRecord7(event)) continue;
1226
3130
  if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
1227
3131
  text += event.delta;
1228
3132
  yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
@@ -1241,8 +3145,8 @@ async function* streamOpenAi(request, options, call) {
1241
3145
  argumentsDelta: event.delta
1242
3146
  }
1243
3147
  };
1244
- } else if (event.type === "response.completed" && isRecord6(event.response)) {
1245
- const usage = normalizeUsage(event.response.usage);
3148
+ } else if (event.type === "response.completed" && isRecord7(event.response)) {
3149
+ const usage = normalizeUsage2(event.response.usage);
1246
3150
  if (usage) yield { type: "stream.usage", requestId, data: { usage } };
1247
3151
  } else if (event.type === "error") {
1248
3152
  const failure = new AiDispatcherError(
@@ -1251,7 +3155,7 @@ async function* streamOpenAi(request, options, call) {
1251
3155
  event
1252
3156
  );
1253
3157
  reported = true;
1254
- yield errorEvent(requestId, failure);
3158
+ yield errorEvent2(requestId, failure);
1255
3159
  throw failure;
1256
3160
  }
1257
3161
  }
@@ -1265,28 +3169,28 @@ async function* streamOpenAi(request, options, call) {
1265
3169
  };
1266
3170
  } catch (error) {
1267
3171
  const failure = error instanceof Error && error.name === "AbortError" ? new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI request timed out.") : report(error);
1268
- if (!reported) yield errorEvent(requestId, failure);
3172
+ if (!reported) yield errorEvent2(requestId, failure);
1269
3173
  throw failure;
1270
3174
  } finally {
1271
3175
  clearTimeout(timeout);
1272
3176
  }
1273
3177
  }
1274
- async function sendWithRetries(compiled, options, timeoutMs) {
3178
+ async function sendWithRetries2(compiled, options, timeoutMs) {
1275
3179
  const attempts = options.maxAttempts;
1276
3180
  let lastError;
1277
3181
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
1278
3182
  try {
1279
- return await sendOnce(compiled, options, timeoutMs);
3183
+ return await sendOnce2(compiled, options, timeoutMs);
1280
3184
  } catch (error) {
1281
3185
  lastError = error;
1282
3186
  const retryable = error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE";
1283
- if (!retryable || attempt >= attempts) throw asTerminalError(error);
1284
- await delay(Math.min(500 * 2 ** (attempt - 1), 5e3));
3187
+ if (!retryable || attempt >= attempts) throw asTerminalError2(error);
3188
+ await delay2(Math.min(500 * 2 ** (attempt - 1), 5e3));
1285
3189
  }
1286
3190
  }
1287
3191
  throw lastError;
1288
3192
  }
1289
- async function sendOnce(compiled, options, timeoutMs) {
3193
+ async function sendOnce2(compiled, options, timeoutMs) {
1290
3194
  const controller = new AbortController();
1291
3195
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
1292
3196
  try {
@@ -1296,7 +3200,7 @@ async function sendOnce(compiled, options, timeoutMs) {
1296
3200
  body: JSON.stringify(compiled.body),
1297
3201
  signal: controller.signal
1298
3202
  });
1299
- if (!response.ok) throw await readFailure(response);
3203
+ if (!response.ok) throw await readFailure2(response);
1300
3204
  const text = await response.text();
1301
3205
  return text ? JSON.parse(text) : {};
1302
3206
  } catch (error) {
@@ -1312,7 +3216,7 @@ async function sendOnce(compiled, options, timeoutMs) {
1312
3216
  clearTimeout(timeout);
1313
3217
  }
1314
3218
  }
1315
- async function readFailure(response) {
3219
+ async function readFailure2(response) {
1316
3220
  const text = await response.text();
1317
3221
  let body = text;
1318
3222
  try {
@@ -1320,21 +3224,21 @@ async function readFailure(response) {
1320
3224
  } catch {
1321
3225
  body = text;
1322
3226
  }
1323
- const message = messageFrom(body) ?? `OpenAI request failed with status ${response.status}.`;
3227
+ const message = messageFrom2(body) ?? `OpenAI request failed with status ${response.status}.`;
1324
3228
  const retryable = response.status === 429 || response.status >= 500;
1325
- return new AiDispatcherError(classifyStatus(response.status), message, { status: response.status, body, retryable });
3229
+ return new AiDispatcherError(classifyStatus2(response.status), message, { status: response.status, body, retryable });
1326
3230
  }
1327
- function classifyStatus(status) {
3231
+ function classifyStatus2(status) {
1328
3232
  if (status === 401 || status === 403) return "PROVIDER_AUTH_FAILED";
1329
3233
  if (status === 404) return "PROVIDER_MODEL_NOT_FOUND";
1330
3234
  if (status === 429) return "PROVIDER_RATE_LIMITED";
1331
3235
  if (status === 408 || status >= 500) return "PROVIDER_RETRYABLE";
1332
3236
  return "PROVIDER_REQUEST_FAILED";
1333
3237
  }
1334
- function isConfigError(code) {
3238
+ function isConfigError2(code) {
1335
3239
  return code === "OPENAI_API_KEY_MISSING" || code === "MODEL_REQUIRED" || code === "INPUT_REQUIRED" || code === "UNSUPPORTED_REQUEST_FIELD";
1336
3240
  }
1337
- function asTerminalError(error) {
3241
+ function asTerminalError2(error) {
1338
3242
  if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
1339
3243
  return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
1340
3244
  }
@@ -1342,16 +3246,16 @@ function asTerminalError(error) {
1342
3246
  return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
1343
3247
  }
1344
3248
  function normalizeOpenAiResponse(params) {
1345
- const record = isRecord6(params.response) ? params.response : {};
1346
- const text = answerText(record);
1347
- const calls = params.functionTools ?? functionCalls(record);
1348
- const usage = normalizeUsage(record.usage);
3249
+ const record2 = isRecord7(params.response) ? params.response : {};
3250
+ const text = answerText2(record2);
3251
+ const calls = params.functionTools ?? functionCalls(record2);
3252
+ const usage = normalizeUsage2(record2.usage);
1349
3253
  const status = params.status ?? (calls.length ? "requires_action" : "completed");
1350
3254
  return {
1351
- id: typeof record.id === "string" ? record.id : params.requestId,
3255
+ id: typeof record2.id === "string" ? record2.id : params.requestId,
1352
3256
  status,
1353
3257
  apiMode: "responses",
1354
- model: typeof record.model === "string" ? record.model : modelOf(params.compiled),
3258
+ model: typeof record2.model === "string" ? record2.model : modelOf2(params.compiled),
1355
3259
  text,
1356
3260
  citations: [],
1357
3261
  images: [],
@@ -1364,14 +3268,14 @@ function normalizeOpenAiResponse(params) {
1364
3268
  ...params.request.metadata ? { metadata: params.request.metadata } : {}
1365
3269
  };
1366
3270
  }
1367
- function answerText(response) {
3271
+ function answerText2(response) {
1368
3272
  const output = response.output;
1369
3273
  if (Array.isArray(output)) {
1370
3274
  const parts = [];
1371
3275
  for (const item of output) {
1372
- if (!isRecord6(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3276
+ if (!isRecord7(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
1373
3277
  for (const part of item.content) {
1374
- if (isRecord6(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3278
+ if (isRecord7(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
1375
3279
  }
1376
3280
  }
1377
3281
  if (parts.length > 0) return parts.join("");
@@ -1382,7 +3286,7 @@ function functionCalls(response) {
1382
3286
  if (!Array.isArray(response.output)) return [];
1383
3287
  const calls = [];
1384
3288
  for (const item of response.output) {
1385
- if (!isRecord6(item) || item.type !== "function_call") continue;
3289
+ if (!isRecord7(item) || item.type !== "function_call") continue;
1386
3290
  const name = typeof item.name === "string" ? item.name : "function";
1387
3291
  const callId = typeof item.call_id === "string" ? item.call_id : name;
1388
3292
  let args = item.arguments;
@@ -1413,11 +3317,11 @@ function toolUsage(calls) {
1413
3317
  functionTools: calls
1414
3318
  };
1415
3319
  }
1416
- function normalizeUsage(usage) {
1417
- if (!isRecord6(usage)) return void 0;
1418
- const inputTokens = numberValue(usage.input_tokens);
1419
- const outputTokens = numberValue(usage.output_tokens);
1420
- const totalTokens = numberValue(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
3320
+ function normalizeUsage2(usage) {
3321
+ if (!isRecord7(usage)) return void 0;
3322
+ const inputTokens = numberValue2(usage.input_tokens);
3323
+ const outputTokens = numberValue2(usage.output_tokens);
3324
+ const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
1421
3325
  return {
1422
3326
  raw: usage,
1423
3327
  ...inputTokens !== void 0 ? { inputTokens } : {},
@@ -1425,7 +3329,7 @@ function normalizeUsage(usage) {
1425
3329
  ...totalTokens !== void 0 ? { totalTokens } : {}
1426
3330
  };
1427
3331
  }
1428
- function failedResponse(request, requestId, error) {
3332
+ function failedResponse2(request, requestId, error) {
1429
3333
  const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
1430
3334
  return {
1431
3335
  id: requestId,
@@ -1447,7 +3351,7 @@ function failedResponse(request, requestId, error) {
1447
3351
  raw: { request, response: void 0 }
1448
3352
  };
1449
3353
  }
1450
- function errorEvent(requestId, error) {
3354
+ function errorEvent2(requestId, error) {
1451
3355
  const normalized = error instanceof AiDispatcherError ? error : new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
1452
3356
  return {
1453
3357
  type: "stream.error",
@@ -1462,22 +3366,31 @@ function errorEvent(requestId, error) {
1462
3366
  }
1463
3367
  };
1464
3368
  }
1465
- function modelOf(compiled) {
3369
+ function modelOf2(compiled) {
1466
3370
  return typeof compiled.body.model === "string" ? compiled.body.model : "";
1467
3371
  }
1468
- function messageFrom(body) {
1469
- if (!isRecord6(body)) return void 0;
3372
+ function messageFrom2(body) {
3373
+ if (!isRecord7(body)) return void 0;
1470
3374
  if (typeof body.message === "string") return body.message;
1471
- if (isRecord6(body.error) && typeof body.error.message === "string") return body.error.message;
3375
+ if (isRecord7(body.error) && typeof body.error.message === "string") return body.error.message;
1472
3376
  return void 0;
1473
3377
  }
1474
- function numberValue(value) {
3378
+ function numberValue2(value) {
1475
3379
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1476
3380
  }
1477
- function clean(body) {
3381
+ function attachProviderMetadata(body, request) {
3382
+ const fromBody = isRecord7(body.metadata) ? body.metadata : void 0;
3383
+ const metadata = toProviderMetadata2(request.metadata, request.agentId, OPENROUTER_METADATA_LIMITS2, fromBody);
3384
+ if (metadata) return { ...body, metadata };
3385
+ if (body.metadata === void 0) return body;
3386
+ const rest = { ...body };
3387
+ delete rest.metadata;
3388
+ return rest;
3389
+ }
3390
+ function clean2(body) {
1478
3391
  return Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
1479
3392
  }
1480
- function buildHeaders(options) {
3393
+ function buildHeaders2(options) {
1481
3394
  return {
1482
3395
  "content-type": "application/json",
1483
3396
  authorization: `Bearer ${options.apiKey}`,
@@ -1485,13 +3398,13 @@ function buildHeaders(options) {
1485
3398
  ...options.project ? { "OpenAI-Project": options.project } : {}
1486
3399
  };
1487
3400
  }
1488
- function timeoutFor(request, options) {
3401
+ function timeoutFor2(request, options) {
1489
3402
  return request.execution?.timeoutMs ?? options.timeoutMs;
1490
3403
  }
1491
- function delay(ms) {
3404
+ function delay2(ms) {
1492
3405
  return new Promise((resolve) => setTimeout(resolve, ms));
1493
3406
  }
1494
- async function* parseSse(response) {
3407
+ async function* parseSse2(response) {
1495
3408
  const body = response.body;
1496
3409
  if (!body) throw new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", "OpenAI stream response had no body.");
1497
3410
  const reader = body.getReader();
@@ -1504,14 +3417,14 @@ async function* parseSse(response) {
1504
3417
  const parts = buffer.split("\n\n");
1505
3418
  buffer = parts.pop() ?? "";
1506
3419
  for (const part of parts) {
1507
- const parsed = parseSsePart(part);
3420
+ const parsed = parseSsePart2(part);
1508
3421
  if (parsed !== void 0) yield parsed;
1509
3422
  }
1510
3423
  }
1511
- const trailing = parseSsePart(buffer);
3424
+ const trailing = parseSsePart2(buffer);
1512
3425
  if (trailing !== void 0) yield trailing;
1513
3426
  }
1514
- function parseSsePart(part) {
3427
+ function parseSsePart2(part) {
1515
3428
  const data = part.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
1516
3429
  if (!data || data === "[DONE]") return void 0;
1517
3430
  try {
@@ -1521,25 +3434,25 @@ function parseSsePart(part) {
1521
3434
  }
1522
3435
  }
1523
3436
  function resolveOpenAiOptions(options) {
1524
- const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
3437
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
1525
3438
  return {
1526
- apiKey: options.apiKey ?? readEnv2("OPENAI_API_KEY") ?? "",
3439
+ apiKey: options.apiKey ?? readEnv3("OPENAI_API_KEY") ?? "",
1527
3440
  baseUrl,
1528
3441
  ...options.defaultModel ? { defaultModel: options.defaultModel } : {},
1529
3442
  ...options.organization ? { organization: options.organization } : {},
1530
3443
  ...options.project ? { project: options.project } : {},
1531
- timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
3444
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS2,
1532
3445
  maxAttempts: options.maxAttempts ?? 3,
1533
3446
  fetch: options.fetch ?? globalThis.fetch,
1534
3447
  ...options.tools ? { tools: options.tools } : {},
1535
3448
  ...options.logger ? { logger: options.logger } : {}
1536
3449
  };
1537
3450
  }
1538
- function readEnv2(name) {
3451
+ function readEnv3(name) {
1539
3452
  if (typeof process === "undefined") return void 0;
1540
3453
  return process.env[name];
1541
3454
  }
1542
- function isRecord6(value) {
3455
+ function isRecord7(value) {
1543
3456
  return typeof value === "object" && value !== null && !Array.isArray(value);
1544
3457
  }
1545
3458
 
@@ -1614,6 +3527,13 @@ function createAiDispatcher(options = {}) {
1614
3527
  };
1615
3528
  return createOpenAiProviderAdapter(openaiOptions);
1616
3529
  }
3530
+ if (provider === "cloudflare") {
3531
+ const cloudflareOptions = {
3532
+ ...options.cloudflare,
3533
+ ...options.logger && !options.cloudflare?.logger ? { logger: options.logger } : {}
3534
+ };
3535
+ return createCloudflareProviderAdapter(cloudflareOptions);
3536
+ }
1617
3537
  throw new AiDispatcherError(
1618
3538
  "PROVIDER_ADAPTER_MISSING",
1619
3539
  `No adapter registered for provider "${provider}".`
@@ -1675,22 +3595,27 @@ function createAiDispatcher(options = {}) {
1675
3595
  function providerTools(provider, options) {
1676
3596
  if (provider === "openrouter") return options.openrouter?.tools;
1677
3597
  if (provider === "bedrock") return options.bedrock?.tools;
3598
+ if (provider === "cloudflare") return options.cloudflare?.tools;
1678
3599
  return options.openai?.tools;
1679
3600
  }
1680
3601
  function callOf(call) {
1681
- if (!call.finalizeBody && !call.extractStreamReasoning && !call.suppressNestedReasoningFallback) return void 0;
3602
+ if (!call.finalizeBody && !call.extractStreamReasoning && !call.suppressNestedReasoningFallback && !call.cloudflare) {
3603
+ return void 0;
3604
+ }
1682
3605
  return call;
1683
3606
  }
1684
3607
  function attachCompileMeta(prepared, compiled) {
1685
- const record = compiled;
1686
- const headers = isRecord7(record.headers) ? redactHeaders(record.headers) : void 0;
3608
+ const record2 = compiled;
3609
+ const headers = isRecord8(record2.headers) ? redactHeaders(record2.headers) : void 0;
1687
3610
  return {
1688
- ...record,
3611
+ ...record2,
1689
3612
  ...headers ? { headers } : {},
1690
3613
  provider: prepared.provider,
1691
3614
  requestedModel: prepared.requestedModel,
1692
3615
  effectiveModel: prepared.effectiveModel,
1693
- ...prepared.resolution ? { reasoningResolution: prepared.resolution } : {}
3616
+ ...prepared.resolution ? { reasoningResolution: prepared.resolution } : {},
3617
+ ...prepared.cacheResolution ? { cacheResolution: prepared.cacheResolution } : {},
3618
+ ...prepared.cacheWarnings?.length ? { warnings: [...Array.isArray(record2.warnings) ? record2.warnings : [], ...prepared.cacheWarnings] } : {}
1694
3619
  };
1695
3620
  }
1696
3621
  function redactHeaders(headers) {
@@ -1711,10 +3636,11 @@ function log(options, event, prepared, entrypoint, streaming) {
1711
3636
  if (prepared.request.id) payload.requestId = prepared.request.id;
1712
3637
  if (prepared.resolution?.outcome) payload.outcome = prepared.resolution.outcome;
1713
3638
  if (prepared.resolution) payload.bypassed = prepared.resolution.bypassed;
3639
+ if (prepared.cacheResolution) payload.cacheOutcome = prepared.cacheResolution.outcome;
1714
3640
  if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
1715
3641
  else options.logger?.info?.(event, payload);
1716
3642
  }
1717
- function isRecord7(value) {
3643
+ function isRecord8(value) {
1718
3644
  return typeof value === "object" && value !== null && !Array.isArray(value);
1719
3645
  }
1720
3646
 
@@ -1728,6 +3654,7 @@ export {
1728
3654
  AiDispatcherError,
1729
3655
  IMPLEMENTED_AI_PROVIDERS,
1730
3656
  createAiDispatcher,
3657
+ decodeProviderMetadata,
1731
3658
  isImplementedAiProvider,
1732
3659
  providerNotImplemented,
1733
3660
  resolveProvider,