@x12i/ai-dispatcher 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,13 +41,19 @@ __export(index_exports, {
41
41
  isImplementedAiProvider: () => isImplementedAiProvider,
42
42
  normalizeMetadata: () => import_openrouter_runtime4.normalizeMetadata,
43
43
  providerNotImplemented: () => providerNotImplemented,
44
- registerHostIntents: () => import_ai_profiles2.registerHostIntents,
45
- resolveAIProfile: () => import_ai_profiles2.resolveAIProfile,
44
+ registerHostIntents: () => import_ai_profiles4.registerHostIntents,
45
+ resolveAIProfile: () => import_ai_profiles4.resolveAIProfile,
46
46
  resolveProvider: () => resolveProvider,
47
47
  stripReasoningHistoryFields: () => stripReasoningHistoryFields
48
48
  });
49
49
  module.exports = __toCommonJS(index_exports);
50
- var import_ai_profiles2 = require("@x12i/ai-profiles");
50
+
51
+ // ../node_modules/tsup/assets/cjs_shims.js
52
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
53
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
54
+
55
+ // src/index.ts
56
+ var import_ai_profiles4 = require("@x12i/ai-profiles");
51
57
  var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
52
58
 
53
59
  // src/create-dispatcher.ts
@@ -162,7 +168,7 @@ async function openTrack(settings, model) {
162
168
  try {
163
169
  tokenCounter = await loadTokenizer(tokenizer);
164
170
  const framing = framingFor(settings);
165
- const resolved = (0, import_core.resolveSettings)({
171
+ const resolved2 = (0, import_core.resolveSettings)({
166
172
  files: [],
167
173
  env: {},
168
174
  factory: {
@@ -177,8 +183,8 @@ async function openTrack(settings, model) {
177
183
  });
178
184
  const approvals = settings.approvals;
179
185
  const client = (0, import_core.createCoreClient)({
180
- config: resolved.config,
181
- explanation: resolved.explanation,
186
+ config: resolved2.config,
187
+ explanation: resolved2.explanation,
182
188
  ...approvals && approvals.length > 0 ? { policy: { approvals: [...approvals] } } : {},
183
189
  ...tokenCounter ? { tokenCounter } : {},
184
190
  warn: () => void 0
@@ -232,10 +238,10 @@ async function encodeRequestText(request, track) {
232
238
  if (typeof request.input === "string") {
233
239
  request.input = await encodeString(track, request.input);
234
240
  } else if (Array.isArray(request.input)) {
235
- for (let index = 0; index < request.input.length; index += 1) {
236
- const item = request.input[index];
241
+ for (let index2 = 0; index2 < request.input.length; index2 += 1) {
242
+ const item = request.input[index2];
237
243
  if (typeof item === "string") {
238
- request.input[index] = await encodeString(track, item);
244
+ request.input[index2] = await encodeString(track, item);
239
245
  } else if (item) {
240
246
  await encodeContentRecord(item, track);
241
247
  }
@@ -432,9 +438,9 @@ function walk(current, tokens) {
432
438
  }
433
439
  if (Array.isArray(current)) {
434
440
  if (!/^\d+$/.test(head)) return [];
435
- const index = Number(head);
436
- if (index >= current.length) return [];
437
- return walk(current[index], rest);
441
+ const index2 = Number(head);
442
+ if (index2 >= current.length) return [];
443
+ return walk(current[index2], rest);
438
444
  }
439
445
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
440
446
  return walk(current[head], rest);
@@ -469,9 +475,9 @@ function collectParents(current, tokens) {
469
475
  }
470
476
  if (Array.isArray(current)) {
471
477
  if (!/^\d+$/.test(head)) return [];
472
- const index = Number(head);
473
- if (index >= current.length) return [];
474
- return collectParents(current[index], rest);
478
+ const index2 = Number(head);
479
+ if (index2 >= current.length) return [];
480
+ return collectParents(current[index2], rest);
475
481
  }
476
482
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
477
483
  return collectParents(current[head], rest);
@@ -485,8 +491,8 @@ function deletePointer(root, pointer) {
485
491
  for (const parent of parents) {
486
492
  if (Array.isArray(parent)) {
487
493
  if (!/^\d+$/.test(last)) continue;
488
- const index = Number(last);
489
- if (index < parent.length) parent.splice(index, 1);
494
+ const index2 = Number(last);
495
+ if (index2 < parent.length) parent.splice(index2, 1);
490
496
  continue;
491
497
  }
492
498
  if (isRecord2(parent) && Object.prototype.hasOwnProperty.call(parent, last)) {
@@ -518,6 +524,38 @@ function extractStreamReasoningText(event, response) {
518
524
  }
519
525
  return firstConcatenatedText(event, response.streamReasoningPaths);
520
526
  }
527
+ function readReportedEffort(root) {
528
+ if (!isRecord3(root)) return void 0;
529
+ const fromRoot = effortOn(root.reasoning) ?? namedEffort(root.reasoning_effort);
530
+ if (fromRoot) return fromRoot;
531
+ if (Array.isArray(root.choices)) {
532
+ for (const choice of root.choices) {
533
+ if (!isRecord3(choice)) continue;
534
+ const message = isRecord3(choice.message) ? choice.message : void 0;
535
+ const fromMessage = message ? effortOn(message.reasoning) ?? namedEffort(message.reasoning_effort) : void 0;
536
+ if (fromMessage) return fromMessage;
537
+ const fromChoice = effortOn(choice.reasoning) ?? namedEffort(choice.reasoning_effort);
538
+ if (fromChoice) return fromChoice;
539
+ }
540
+ }
541
+ if (Array.isArray(root.output)) {
542
+ for (const item of root.output) {
543
+ if (!isRecord3(item) || item.type !== "reasoning") continue;
544
+ const effort = namedEffort(item.effort);
545
+ if (effort) return effort;
546
+ }
547
+ }
548
+ return void 0;
549
+ }
550
+ function effortOn(value) {
551
+ if (!isRecord3(value)) return void 0;
552
+ return namedEffort(value.effort);
553
+ }
554
+ function namedEffort(value) {
555
+ if (typeof value !== "string") return void 0;
556
+ const trimmed = value.trim();
557
+ return trimmed || void 0;
558
+ }
521
559
  function readReasoningTokens(root, paths) {
522
560
  for (const path of paths) {
523
561
  const absolute = firstNumber(root, [path]);
@@ -602,7 +640,10 @@ function splitThinkTags(text) {
602
640
  // src/apply-resolution.ts
603
641
  function applyResolution(response, prepared) {
604
642
  const result = response;
605
- if (prepared.resolution) result.reasoningResolution = prepared.resolution;
643
+ if (prepared.resolution) {
644
+ const reportedEffort = readReportedEffort(response.raw?.response);
645
+ result.reasoningResolution = reportedEffort ? { ...prepared.resolution, reportedEffort } : prepared.resolution;
646
+ }
606
647
  if (prepared.cacheResolution) result.cacheResolution = prepared.cacheResolution;
607
648
  if (prepared.cacheWarnings?.length) {
608
649
  result.warnings = [...result.warnings, ...prepared.cacheWarnings];
@@ -984,7 +1025,7 @@ function isRecord5(value) {
984
1025
  }
985
1026
 
986
1027
  // src/prepare-request.ts
987
- var import_ai_profiles = require("@x12i/ai-profiles");
1028
+ var import_ai_profiles3 = require("@x12i/ai-profiles");
988
1029
 
989
1030
  // src/cache-apply.ts
990
1031
  var BREAKPOINT = { mode: "explicit" };
@@ -1067,38 +1108,38 @@ function placeBedrockBreakpoint(body, ttl) {
1067
1108
  function markMessages(body, field, markerKey, markerValue, textType) {
1068
1109
  const messages = body[field];
1069
1110
  if (!Array.isArray(messages)) return { body, placed: false };
1070
- const index = stableMessageIndex(messages);
1071
- if (index < 0) return { body, placed: false };
1072
- const marked = markMessage(messages[index], markerKey, markerValue, textType);
1111
+ const index2 = stableMessageIndex(messages);
1112
+ if (index2 < 0) return { body, placed: false };
1113
+ const marked = markMessage(messages[index2], markerKey, markerValue, textType);
1073
1114
  if (!marked) return { body, placed: false };
1074
1115
  const next = messages.slice();
1075
- next[index] = marked;
1116
+ next[index2] = marked;
1076
1117
  return { placed: true, body: { ...body, [field]: next } };
1077
1118
  }
1078
1119
  function markInput(body, markerKey, markerValue, textType) {
1079
1120
  if (!Array.isArray(body.input)) return { body, placed: false };
1080
- const index = stableInputIndex(body.input);
1081
- if (index < 0) return { body, placed: false };
1082
- const marked = markMessage(body.input[index], markerKey, markerValue, textType);
1121
+ const index2 = stableInputIndex(body.input);
1122
+ if (index2 < 0) return { body, placed: false };
1123
+ const marked = markMessage(body.input[index2], markerKey, markerValue, textType);
1083
1124
  if (!marked) return { body, placed: false };
1084
1125
  const next = body.input.slice();
1085
- next[index] = marked;
1126
+ next[index2] = marked;
1086
1127
  return { placed: true, body: { ...body, input: next } };
1087
1128
  }
1088
1129
  function stableMessageIndex(messages) {
1089
1130
  let lastSystem = -1;
1090
- for (let index = 0; index < messages.length; index += 1) {
1091
- const message = messages[index];
1092
- if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index;
1131
+ for (let index2 = 0; index2 < messages.length; index2 += 1) {
1132
+ const message = messages[index2];
1133
+ if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index2;
1093
1134
  }
1094
1135
  return lastSystem;
1095
1136
  }
1096
1137
  function stableInputIndex(items) {
1097
1138
  let lastStable = -1;
1098
- for (let index = 0; index < items.length; index += 1) {
1099
- const item = items[index];
1139
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
1140
+ const item = items[index2];
1100
1141
  if (!isPlainRecord(item) || !hasText(item.content)) continue;
1101
- if (item.role === "system" || item.role === "developer") lastStable = index;
1142
+ if (item.role === "system" || item.role === "developer") lastStable = index2;
1102
1143
  }
1103
1144
  return lastStable;
1104
1145
  }
@@ -1115,10 +1156,10 @@ function markContent(content, markerKey, markerValue, textType) {
1115
1156
  }
1116
1157
  if (!Array.isArray(content)) return void 0;
1117
1158
  const parts = content.map((part) => isPlainRecord(part) ? { ...part } : part);
1118
- for (let index = parts.length - 1; index >= 0; index -= 1) {
1119
- const part = parts[index];
1159
+ for (let index2 = parts.length - 1; index2 >= 0; index2 -= 1) {
1160
+ const part = parts[index2];
1120
1161
  if (!isPlainRecord(part) || typeof part.text !== "string" || !part.text.trim()) continue;
1121
- parts[index] = { ...part, [markerKey]: markerValue };
1162
+ parts[index2] = { ...part, [markerKey]: markerValue };
1122
1163
  return parts;
1123
1164
  }
1124
1165
  return void 0;
@@ -1274,11 +1315,11 @@ function resolveCloudflarePromptCache(args) {
1274
1315
  warnings: []
1275
1316
  };
1276
1317
  }
1277
- const resolved = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1278
- const ttl = resolved.plan.ttl ?? requestedTtl;
1318
+ const resolved2 = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1319
+ const ttl = resolved2.plan.ttl ?? requestedTtl;
1279
1320
  return {
1280
- ...resolved,
1281
- plan: { ...resolved.plan, gateway: gatewayDirective(false, ttl, key) }
1321
+ ...resolved2,
1322
+ plan: { ...resolved2.plan, gateway: gatewayDirective(false, ttl, key) }
1282
1323
  };
1283
1324
  }
1284
1325
  function applyProfile(args) {
@@ -1483,22 +1524,22 @@ function restoreCloudflareModel(model, prefix) {
1483
1524
  return `${prefix}${model}`;
1484
1525
  }
1485
1526
  function createCloudflareProviderAdapter(options = {}, packer) {
1486
- const resolved = resolveCloudflareOptions(options, packer);
1527
+ const resolved2 = resolveCloudflareOptions(options, packer);
1487
1528
  return {
1488
1529
  compile(request, call) {
1489
- return compileCloudflareRequest(request, resolved, call, false);
1530
+ return compileCloudflareRequest(request, resolved2, call, false);
1490
1531
  },
1491
1532
  async run(request, call) {
1492
1533
  const requestId = request.id ?? "cloudflare";
1493
1534
  try {
1494
- return await runCloudflare(request, resolved, call, requestId);
1535
+ return await runCloudflare(request, resolved2, call, requestId);
1495
1536
  } catch (error) {
1496
1537
  if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1497
1538
  return failedResponse(request, requestId, error, "chat");
1498
1539
  }
1499
1540
  },
1500
1541
  executeStreamingChat(request, call) {
1501
- return streamCloudflare(request, resolved, call);
1542
+ return streamCloudflare(request, resolved2, call);
1502
1543
  }
1503
1544
  };
1504
1545
  }
@@ -2646,6 +2687,258 @@ function isRecord6(value) {
2646
2687
  return typeof value === "object" && value !== null && !Array.isArray(value);
2647
2688
  }
2648
2689
 
2690
+ // src/resolve-effort.ts
2691
+ var import_ai_profiles2 = require("@x12i/ai-profiles");
2692
+
2693
+ // src/catalog-efforts.ts
2694
+ var import_node_module = require("module");
2695
+ var import_node_fs = require("fs");
2696
+ var import_node_path = require("path");
2697
+ var import_ai_profiles = require("@x12i/ai-profiles");
2698
+ var index;
2699
+ function normalize(value) {
2700
+ return value.trim().toLowerCase();
2701
+ }
2702
+ function addKey(keys, key, row) {
2703
+ const normalized = normalize(key);
2704
+ if (!normalized) return;
2705
+ const existing = keys.get(normalized);
2706
+ if (!existing) {
2707
+ keys.set(normalized, row);
2708
+ return;
2709
+ }
2710
+ if (existing === "ambiguous") return;
2711
+ if (existing.modelId !== row.modelId) keys.set(normalized, "ambiguous");
2712
+ }
2713
+ function loadAssignmentAliases() {
2714
+ const require2 = (0, import_node_module.createRequire)(importMetaUrl);
2715
+ const entry = require2.resolve("@x12i/ai-profiles");
2716
+ const path = (0, import_node_path.join)((0, import_node_path.dirname)(entry), "data", "reasoning-directives.json");
2717
+ const document2 = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
2718
+ return document2.assignments ?? [];
2719
+ }
2720
+ function effortList(entry) {
2721
+ const reasoning = entry.openrouter.reasoning;
2722
+ const modelId = entry.openrouter.modelId;
2723
+ if (!reasoning || !Object.prototype.hasOwnProperty.call(reasoning, "supportedEfforts")) {
2724
+ return { kind: "unspecified", modelId };
2725
+ }
2726
+ if (reasoning.supportedEfforts === null) return { kind: "any", modelId };
2727
+ if (Array.isArray(reasoning.supportedEfforts)) {
2728
+ return { kind: "listed", modelId, efforts: reasoning.supportedEfforts };
2729
+ }
2730
+ return { kind: "unspecified", modelId };
2731
+ }
2732
+ function buildIndex() {
2733
+ const catalog = (0, import_ai_profiles.loadBundledModelsCatalog)();
2734
+ const keys = /* @__PURE__ */ new Map();
2735
+ const byCatalogId = /* @__PURE__ */ new Map();
2736
+ for (const entry of catalog.models) {
2737
+ const row = effortList(entry);
2738
+ byCatalogId.set(normalize(entry.openrouter.modelId), row);
2739
+ addKey(keys, entry.openrouter.modelId, row);
2740
+ if (entry.openrouter.canonicalSlug) addKey(keys, entry.openrouter.canonicalSlug, row);
2741
+ addKey(keys, `${entry.direct.provider}/${entry.direct.modelId}`, row);
2742
+ addKey(keys, `openrouter/${entry.openrouter.modelId}`, row);
2743
+ addKey(keys, entry.direct.modelId, row);
2744
+ }
2745
+ for (const assignment of loadAssignmentAliases()) {
2746
+ const row = byCatalogId.get(normalize(assignment.catalogModelId));
2747
+ if (!row) continue;
2748
+ for (const alias of assignment.aliases ?? []) addKey(keys, alias, row);
2749
+ }
2750
+ return keys;
2751
+ }
2752
+ function lookupCatalogEfforts(model) {
2753
+ if (!index) index = buildIndex();
2754
+ const direct = index.get(normalize(model));
2755
+ if (direct && direct !== "ambiguous") return direct;
2756
+ const gateway = normalize(model);
2757
+ if (gateway.startsWith("openrouter/")) {
2758
+ const stripped = index.get(gateway.slice("openrouter/".length));
2759
+ if (stripped && stripped !== "ambiguous") return stripped;
2760
+ }
2761
+ return void 0;
2762
+ }
2763
+
2764
+ // src/resolve-effort.ts
2765
+ var EMPTY_RESPONSE = {
2766
+ reasoningTextPaths: [],
2767
+ streamReasoningKeys: [],
2768
+ streamReasoningPaths: [],
2769
+ stripFromHistoryPaths: [],
2770
+ stripThinkTags: false
2771
+ };
2772
+ var MAPPER_EFFORTS = ["high", "xhigh", "max", "medium", "low", "minimal"];
2773
+ function isRecord7(value) {
2774
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2775
+ }
2776
+ function readBodyEffort(body) {
2777
+ const reasoning = body.reasoning;
2778
+ if (isRecord7(reasoning) && typeof reasoning.effort === "string") return reasoning.effort;
2779
+ const fields = body.additionalModelRequestFields;
2780
+ if (!isRecord7(fields)) return void 0;
2781
+ const output = fields.output_config;
2782
+ if (isRecord7(output) && typeof output.effort === "string") return output.effort;
2783
+ return void 0;
2784
+ }
2785
+ function writeBodyEffort(body, effort) {
2786
+ const reasoning = body.reasoning;
2787
+ if (isRecord7(reasoning) && typeof reasoning.effort === "string") {
2788
+ reasoning.effort = effort;
2789
+ return true;
2790
+ }
2791
+ const fields = body.additionalModelRequestFields;
2792
+ if (!isRecord7(fields)) return false;
2793
+ const output = fields.output_config;
2794
+ if (isRecord7(output) && typeof output.effort === "string") {
2795
+ output.effort = effort;
2796
+ return true;
2797
+ }
2798
+ return false;
2799
+ }
2800
+ function unsupported(model, effort, supported) {
2801
+ return new AiDispatcherError(
2802
+ "EFFORT_UNSUPPORTED",
2803
+ `Model '${model}' does not accept reasoning effort '${effort}'. Supported efforts: ${supported.join(", ")}.`,
2804
+ { model, effort, supportedEfforts: [...supported] }
2805
+ );
2806
+ }
2807
+ function ignoredDirective() {
2808
+ return {
2809
+ outcome: "ignored",
2810
+ body: {},
2811
+ response: {
2812
+ reasoningTextPaths: [...EMPTY_RESPONSE.reasoningTextPaths],
2813
+ streamReasoningKeys: [...EMPTY_RESPONSE.streamReasoningKeys],
2814
+ streamReasoningPaths: [...EMPTY_RESPONSE.streamReasoningPaths],
2815
+ stripFromHistoryPaths: [...EMPTY_RESPONSE.stripFromHistoryPaths],
2816
+ stripThinkTags: EMPTY_RESPONSE.stripThinkTags
2817
+ }
2818
+ };
2819
+ }
2820
+ function toDispatcherError(error) {
2821
+ return new AiDispatcherError(error.code, error.message, error.details);
2822
+ }
2823
+ function resolveMapping(input, ignoreUnknown) {
2824
+ try {
2825
+ return (0, import_ai_profiles2.resolveReasoning)({
2826
+ target: input.target,
2827
+ model: input.model,
2828
+ effort: input.effort
2829
+ });
2830
+ } catch (error) {
2831
+ if (!(error instanceof import_ai_profiles2.AIProfilesError)) throw error;
2832
+ if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2833
+ return ignoredDirective();
2834
+ }
2835
+ if (error.code === "INVALID_EFFORT") return "bad-key";
2836
+ if (error.code === "REASONING_MAPPING_INVALID" && error.message.includes("missing effort")) return "bad-key";
2837
+ throw toDispatcherError(error);
2838
+ }
2839
+ }
2840
+ function mappingOrThrow(input, ignoreUnknown) {
2841
+ const mapped = resolveMapping(input, ignoreUnknown);
2842
+ if (mapped === "bad-key") {
2843
+ throw new AiDispatcherError(
2844
+ "INVALID_EFFORT",
2845
+ `Invalid reasoning effort '${input.effort}'.`,
2846
+ { target: input.target, effort: input.effort, model: input.model }
2847
+ );
2848
+ }
2849
+ return mapped;
2850
+ }
2851
+ function sendAsWritten(directive, effort, target) {
2852
+ if (directive.outcome === "ignored") return directive;
2853
+ const copy = structuredClone(directive);
2854
+ const wrote = writeBodyEffort(copy.body, effort);
2855
+ if (!wrote && target !== "bedrock") {
2856
+ const reasoning = isRecord7(copy.body.reasoning) ? copy.body.reasoning : {};
2857
+ copy.body = { ...copy.body, reasoning: { ...reasoning, effort } };
2858
+ }
2859
+ copy.outcome = "applied";
2860
+ return copy;
2861
+ }
2862
+ function directiveForListedEffort(input) {
2863
+ const direct = resolveMapping(input, input.ignoreUnknown);
2864
+ if (direct !== "bad-key") {
2865
+ if (direct.outcome === "ignored") return direct;
2866
+ const sent = readBodyEffort(direct.body);
2867
+ if (sent === input.effort) {
2868
+ if (direct.outcome === "degraded") {
2869
+ const copy = structuredClone(direct);
2870
+ copy.outcome = "applied";
2871
+ return copy;
2872
+ }
2873
+ return direct;
2874
+ }
2875
+ if (sent) return sendAsWritten(direct, input.effort, input.target);
2876
+ }
2877
+ const proxies = MAPPER_EFFORTS.filter((candidate) => {
2878
+ if (candidate === input.effort) return false;
2879
+ if (input.supported === null) return true;
2880
+ return input.supported.includes(candidate);
2881
+ });
2882
+ for (const proxy of proxies) {
2883
+ const mapped = resolveMapping({ ...input, effort: proxy }, input.ignoreUnknown);
2884
+ if (mapped === "bad-key" || mapped.outcome === "ignored") continue;
2885
+ if (!readBodyEffort(mapped.body)) continue;
2886
+ return sendAsWritten(mapped, input.effort, input.target);
2887
+ }
2888
+ return mappingOrThrow(input, input.ignoreUnknown);
2889
+ }
2890
+ function sentOf(directive) {
2891
+ if (directive.outcome === "ignored") return void 0;
2892
+ return readBodyEffort(directive.body);
2893
+ }
2894
+ function resolved(directive) {
2895
+ const sentEffort = sentOf(directive);
2896
+ return sentEffort ? { directive, sentEffort } : { directive };
2897
+ }
2898
+ function resolveEffortDirective(input) {
2899
+ const effort = input.effort.trim();
2900
+ const row = lookupCatalogEfforts(input.model);
2901
+ if (!row) {
2902
+ return resolved(mappingOrThrow(input, input.ignoreUnknown));
2903
+ }
2904
+ if (row.kind === "unspecified") {
2905
+ const directive2 = mappingOrThrow({ ...input, effort }, input.ignoreUnknown);
2906
+ const sent = sentOf(directive2);
2907
+ if (!input.degrade && directive2.outcome === "degraded" && sent && sent !== effort) {
2908
+ throw new AiDispatcherError(
2909
+ "EFFORT_UNSUPPORTED",
2910
+ `Model '${input.model}' does not accept reasoning effort '${effort}' unless degradeReasoningEffort is set.`,
2911
+ { model: input.model, effort, catalogModelId: row.modelId }
2912
+ );
2913
+ }
2914
+ return resolved(directive2);
2915
+ }
2916
+ const supported = row.kind === "listed" ? row.efforts : null;
2917
+ const allowed = supported === null || supported.includes(effort);
2918
+ if (!allowed && supported) {
2919
+ if (!input.degrade) throw unsupported(input.model, effort, supported);
2920
+ const mapped = resolveMapping({ ...input, effort }, input.ignoreUnknown);
2921
+ if (mapped === "bad-key") throw unsupported(input.model, effort, supported);
2922
+ if (mapped.outcome === "ignored") return { directive: mapped };
2923
+ const sent = readBodyEffort(mapped.body);
2924
+ if (!sent || !supported.includes(sent)) throw unsupported(input.model, effort, supported);
2925
+ if (sent !== effort) {
2926
+ const copy = structuredClone(mapped);
2927
+ copy.outcome = "degraded";
2928
+ return { directive: copy, sentEffort: sent };
2929
+ }
2930
+ return { directive: mapped, sentEffort: sent };
2931
+ }
2932
+ const directive = directiveForListedEffort({
2933
+ target: input.target,
2934
+ model: input.model,
2935
+ effort,
2936
+ ignoreUnknown: input.ignoreUnknown,
2937
+ supported
2938
+ });
2939
+ return resolved(directive);
2940
+ }
2941
+
2649
2942
  // src/prepare-request.ts
2650
2943
  function prepareDispatchRequest(params) {
2651
2944
  const cloned = cloneRequest(params.request);
@@ -2655,11 +2948,13 @@ function prepareDispatchRequest(params) {
2655
2948
  applyDispatchGate(cloned);
2656
2949
  const provider = params.provider;
2657
2950
  const reasoningEffort = cloned.reasoningEffort;
2951
+ const degradeReasoningEffort = cloned.degradeReasoningEffort === true;
2658
2952
  const bedrockControls = cloned.bedrockControls;
2659
2953
  const cachePolicy = cloned.cache;
2660
2954
  const cloudflareRequest = cloned.cloudflare;
2661
2955
  delete cloned.provider;
2662
2956
  delete cloned.reasoningEffort;
2957
+ delete cloned.degradeReasoningEffort;
2663
2958
  delete cloned.bedrockControls;
2664
2959
  delete cloned.cache;
2665
2960
  delete cloned.cloudflare;
@@ -2677,6 +2972,7 @@ function prepareDispatchRequest(params) {
2677
2972
  const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
2678
2973
  const explicit = hasExplicitControl(provider, cloned, bedrockControls);
2679
2974
  let directive;
2975
+ let sentEffort;
2680
2976
  let catalogPrefix;
2681
2977
  if (!explicit && reasoningEffort !== void 0) {
2682
2978
  if (!requestedModel) {
@@ -2688,13 +2984,29 @@ function prepareDispatchRequest(params) {
2688
2984
  if (provider === "cloudflare") {
2689
2985
  const query = cloudflareCatalogQuery(requestedModel);
2690
2986
  catalogPrefix = query.prefix;
2691
- directive = resolveCatalog({ target: query.target, model: query.model, effort: reasoningEffort }, true);
2987
+ const resolved2 = resolveEffortDirective({
2988
+ target: query.target,
2989
+ model: query.model,
2990
+ effort: reasoningEffort,
2991
+ ignoreUnknown: true,
2992
+ degrade: degradeReasoningEffort
2993
+ });
2994
+ directive = resolved2.directive;
2995
+ sentEffort = resolved2.sentEffort;
2692
2996
  } else if (isReasoningTarget(provider)) {
2693
- directive = resolveCatalog({ target: provider, model: requestedModel, effort: reasoningEffort }, false);
2997
+ const resolved2 = resolveEffortDirective({
2998
+ target: provider,
2999
+ model: requestedModel,
3000
+ effort: reasoningEffort,
3001
+ ignoreUnknown: false,
3002
+ degrade: degradeReasoningEffort
3003
+ });
3004
+ directive = resolved2.directive;
3005
+ sentEffort = resolved2.sentEffort;
2694
3006
  }
2695
3007
  }
2696
3008
  if (provider === "openrouter") {
2697
- applyNestedReasoning(cloned, reasoningEffort);
3009
+ applyNestedReasoning(cloned, reasoningEffort, degradeReasoningEffort);
2698
3010
  }
2699
3011
  if (directive?.model) {
2700
3012
  cloned.model = provider === "cloudflare" ? restoreCloudflareModel(directive.model, catalogPrefix) : directive.model;
@@ -2728,7 +3040,7 @@ function prepareDispatchRequest(params) {
2728
3040
  if (overlay || cacheTouchesBody) {
2729
3041
  const bodyOverlay = overlay;
2730
3042
  call.finalizeBody = (body) => {
2731
- const merged = bodyOverlay ? (0, import_ai_profiles.mergeReasoningBody)(body, bodyOverlay) : body;
3043
+ const merged = bodyOverlay ? (0, import_ai_profiles3.mergeReasoningBody)(body, bodyOverlay) : body;
2732
3044
  if (!cacheTouchesBody || !cachePlan) return merged;
2733
3045
  const applied = applyPromptCache(merged, cachePlan);
2734
3046
  if (cachePlan.placeBreakpoint && !applied.placed) {
@@ -2755,6 +3067,7 @@ function prepareDispatchRequest(params) {
2755
3067
  }
2756
3068
  const resolution = reasoningEffort !== void 0 || explicit ? buildResolution({
2757
3069
  requestedEffort: reasoningEffort,
3070
+ sentEffort: explicit ? explicitSentEffort(provider, cloned, bedrockControls) : sentEffort,
2758
3071
  target: provider,
2759
3072
  requestedModel,
2760
3073
  effectiveModel,
@@ -2787,29 +3100,6 @@ function noteUnplacedCache(resolution, warnings, target, model) {
2787
3100
  details: { target, model }
2788
3101
  });
2789
3102
  }
2790
- function resolveCatalog(input, ignoreUnknown) {
2791
- try {
2792
- return (0, import_ai_profiles.resolveReasoning)(input);
2793
- } catch (error) {
2794
- if (error instanceof import_ai_profiles.AIProfilesError) {
2795
- if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2796
- return {
2797
- outcome: "ignored",
2798
- body: {},
2799
- response: {
2800
- reasoningTextPaths: [],
2801
- streamReasoningKeys: [],
2802
- streamReasoningPaths: [],
2803
- stripFromHistoryPaths: [],
2804
- stripThinkTags: false
2805
- }
2806
- };
2807
- }
2808
- throw new AiDispatcherError(error.code, error.message, error.details);
2809
- }
2810
- throw error;
2811
- }
2812
- }
2813
3103
  function isReasoningTarget(provider) {
2814
3104
  return provider === "openrouter" || provider === "bedrock" || provider === "openai";
2815
3105
  }
@@ -2817,6 +3107,15 @@ function hasExplicitControl(provider, request, bedrockControls) {
2817
3107
  if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
2818
3108
  return request.reasoning !== void 0;
2819
3109
  }
3110
+ function explicitSentEffort(provider, request, bedrockControls) {
3111
+ if (provider === "bedrock") {
3112
+ const output = bedrockControls?.additionalModelRequestFields?.output_config;
3113
+ if (isRecord8(output) && typeof output.effort === "string") return output.effort;
3114
+ return void 0;
3115
+ }
3116
+ if (isRecord8(request.reasoning) && typeof request.reasoning.effort === "string") return request.reasoning.effort;
3117
+ return void 0;
3118
+ }
2820
3119
  function buildResolution(params) {
2821
3120
  return {
2822
3121
  target: params.target,
@@ -2824,19 +3123,20 @@ function buildResolution(params) {
2824
3123
  effectiveModel: params.effectiveModel,
2825
3124
  bypassed: params.bypassed,
2826
3125
  ...params.requestedEffort ? { requestedEffort: params.requestedEffort } : {},
3126
+ ...params.sentEffort ? { sentEffort: params.sentEffort } : {},
2827
3127
  ...params.outcome ? { outcome: params.outcome } : {}
2828
3128
  };
2829
3129
  }
2830
- function applyNestedReasoning(request, parentEffort) {
3130
+ function applyNestedReasoning(request, parentEffort, degrade) {
2831
3131
  const serverTools = request.serverTools;
2832
3132
  if (!serverTools) return;
2833
- if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort);
3133
+ if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort, degrade);
2834
3134
  if (serverTools.advisor) {
2835
- serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort)) : settleNested(serverTools.advisor, "advisor", parentEffort);
3135
+ serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort, degrade)) : settleNested(serverTools.advisor, "advisor", parentEffort, degrade);
2836
3136
  }
2837
- if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort);
3137
+ if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort, degrade);
2838
3138
  }
2839
- function settleNested(policy, kind, parentEffort) {
3139
+ function settleNested(policy, kind, parentEffort, degrade) {
2840
3140
  const current = policy;
2841
3141
  const childEffort = current.reasoningEffort;
2842
3142
  const next = { ...current };
@@ -2846,12 +3146,18 @@ function settleNested(policy, kind, parentEffort) {
2846
3146
  if (!effort) return next;
2847
3147
  const model = kind === "fusion" ? next.judgeModel : next.model;
2848
3148
  if (!model) return next;
2849
- const directive = resolveCatalog({ target: "openrouter", model, effort }, false);
3149
+ const directive = resolveEffortDirective({
3150
+ target: "openrouter",
3151
+ model,
3152
+ effort,
3153
+ ignoreUnknown: false,
3154
+ degrade
3155
+ }).directive;
2850
3156
  if (directive.model) {
2851
3157
  if (kind === "fusion") next.judgeModel = directive.model;
2852
3158
  else next.model = directive.model;
2853
3159
  }
2854
- if (isRecord7(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
3160
+ if (isRecord8(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
2855
3161
  return next;
2856
3162
  }
2857
3163
  function applyPromptPrefix(request, prefix) {
@@ -2888,10 +3194,10 @@ function prefixMessages(messages, prefix) {
2888
3194
  return false;
2889
3195
  }
2890
3196
  function prefixInputItems(items, prefix) {
2891
- for (let index = 0; index < items.length; index += 1) {
2892
- const item = items[index];
3197
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
3198
+ const item = items[index2];
2893
3199
  if (typeof item === "string") {
2894
- items[index] = prefix + item;
3200
+ items[index2] = prefix + item;
2895
3201
  return true;
2896
3202
  }
2897
3203
  if (!item || item.role === "system") continue;
@@ -2901,7 +3207,7 @@ function prefixInputItems(items, prefix) {
2901
3207
  }
2902
3208
  if (!Array.isArray(item.content)) continue;
2903
3209
  for (const part of item.content) {
2904
- if (!isRecord7(part) || typeof part.text !== "string") continue;
3210
+ if (!isRecord8(part) || typeof part.text !== "string") continue;
2905
3211
  if (part.type === "text" || part.type === "input_text") {
2906
3212
  part.text = prefix + part.text;
2907
3213
  return true;
@@ -2962,7 +3268,7 @@ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
2962
3268
  function applyRoleStamp(request) {
2963
3269
  const stamp = request.role;
2964
3270
  delete request.role;
2965
- if (!isRecord7(stamp)) return;
3271
+ if (!isRecord8(stamp)) return;
2966
3272
  const identity = {};
2967
3273
  for (const field of ROLE_STAMP_FIELDS) {
2968
3274
  const value = stamp[field];
@@ -2977,8 +3283,8 @@ function applyRoleStamp(request) {
2977
3283
  }
2978
3284
  function absorbOverrideMetadata(request) {
2979
3285
  const overrides = request.rawOpenRouterOverrides;
2980
- if (!isRecord7(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
2981
- if (isRecord7(overrides.metadata)) {
3286
+ if (!isRecord8(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
3287
+ if (isRecord8(overrides.metadata)) {
2982
3288
  const current = { ...request.metadata ?? {} };
2983
3289
  const extra = {};
2984
3290
  for (const [key, value] of Object.entries(overrides.metadata)) {
@@ -3023,7 +3329,7 @@ function hasOwnKeys(value) {
3023
3329
  function hasStreamMetadata(directive) {
3024
3330
  return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
3025
3331
  }
3026
- function isRecord7(value) {
3332
+ function isRecord8(value) {
3027
3333
  return typeof value === "object" && value !== null && !Array.isArray(value);
3028
3334
  }
3029
3335
  function readEnv2(name) {
@@ -3170,22 +3476,22 @@ var import_openrouter_runtime2 = require("@x12i/openrouter-runtime");
3170
3476
  var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
3171
3477
  var DEFAULT_TIMEOUT_MS2 = 12e4;
3172
3478
  function createOpenAiProviderAdapter(options = {}, packer) {
3173
- const resolved = resolveOpenAiOptions(options, packer);
3479
+ const resolved2 = resolveOpenAiOptions(options, packer);
3174
3480
  return {
3175
3481
  compile(request, call) {
3176
- return compileOpenAiRequest(request, resolved, call, false);
3482
+ return compileOpenAiRequest(request, resolved2, call, false);
3177
3483
  },
3178
3484
  async run(request, call) {
3179
3485
  const requestId = request.id ?? "openai";
3180
3486
  try {
3181
- return await runOpenAi(request, resolved, call, requestId);
3487
+ return await runOpenAi(request, resolved2, call, requestId);
3182
3488
  } catch (error) {
3183
3489
  if (error instanceof AiDispatcherError && isConfigError2(error.code)) throw error;
3184
3490
  return failedResponse2(request, requestId, error);
3185
3491
  }
3186
3492
  },
3187
3493
  executeStreamingChat(request, call) {
3188
- return streamOpenAi(request, resolved, call);
3494
+ return streamOpenAi(request, resolved2, call);
3189
3495
  }
3190
3496
  };
3191
3497
  }
@@ -3342,7 +3648,7 @@ async function runOpenAi(request, options, call, requestId) {
3342
3648
  let response;
3343
3649
  for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
3344
3650
  response = await sendWithRetries2(active, options, timeoutFor2(request, options));
3345
- const calls = functionCalls(isRecord8(response) ? response : {});
3651
+ const calls = functionCalls(isRecord9(response) ? response : {});
3346
3652
  if (!calls.length) break;
3347
3653
  if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
3348
3654
  return normalizeOpenAiResponse({
@@ -3362,7 +3668,7 @@ async function runOpenAi(request, options, call, requestId) {
3362
3668
  results.push(await executeOpenAiTool(item, request, options));
3363
3669
  }
3364
3670
  functionUsage.push(...results);
3365
- const record2 = isRecord8(response) ? response : {};
3671
+ const record2 = isRecord9(response) ? response : {};
3366
3672
  active = {
3367
3673
  ...active,
3368
3674
  body: clean2({
@@ -3477,7 +3783,7 @@ async function* streamOpenAi(request, options, call) {
3477
3783
  throw failure;
3478
3784
  }
3479
3785
  for await (const event of parseSse2(response)) {
3480
- if (!isRecord8(event)) continue;
3786
+ if (!isRecord9(event)) continue;
3481
3787
  if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
3482
3788
  text += event.delta;
3483
3789
  yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
@@ -3496,7 +3802,7 @@ async function* streamOpenAi(request, options, call) {
3496
3802
  argumentsDelta: event.delta
3497
3803
  }
3498
3804
  };
3499
- } else if (event.type === "response.completed" && isRecord8(event.response)) {
3805
+ } else if (event.type === "response.completed" && isRecord9(event.response)) {
3500
3806
  const usage = normalizeUsage2(event.response.usage);
3501
3807
  if (usage) yield { type: "stream.usage", requestId, data: { usage } };
3502
3808
  } else if (event.type === "error") {
@@ -3590,7 +3896,7 @@ function isConfigError2(code) {
3590
3896
  }
3591
3897
  function asTerminalError2(error) {
3592
3898
  if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
3593
- const status = isRecord8(error.details) ? error.details.status : void 0;
3899
+ const status = isRecord9(error.details) ? error.details.status : void 0;
3594
3900
  if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
3595
3901
  return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
3596
3902
  }
@@ -3598,7 +3904,7 @@ function asTerminalError2(error) {
3598
3904
  return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
3599
3905
  }
3600
3906
  function normalizeOpenAiResponse(params) {
3601
- const record2 = isRecord8(params.response) ? params.response : {};
3907
+ const record2 = isRecord9(params.response) ? params.response : {};
3602
3908
  const text = answerText2(record2);
3603
3909
  const calls = params.functionTools ?? functionCalls(record2);
3604
3910
  const usage = normalizeUsage2(record2.usage);
@@ -3625,9 +3931,9 @@ function answerText2(response) {
3625
3931
  if (Array.isArray(output)) {
3626
3932
  const parts = [];
3627
3933
  for (const item of output) {
3628
- if (!isRecord8(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3934
+ if (!isRecord9(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3629
3935
  for (const part of item.content) {
3630
- if (isRecord8(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3936
+ if (isRecord9(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3631
3937
  }
3632
3938
  }
3633
3939
  if (parts.length > 0) return parts.join("");
@@ -3638,7 +3944,7 @@ function functionCalls(response) {
3638
3944
  if (!Array.isArray(response.output)) return [];
3639
3945
  const calls = [];
3640
3946
  for (const item of response.output) {
3641
- if (!isRecord8(item) || item.type !== "function_call") continue;
3947
+ if (!isRecord9(item) || item.type !== "function_call") continue;
3642
3948
  const name = typeof item.name === "string" ? item.name : "function";
3643
3949
  const callId = typeof item.call_id === "string" ? item.call_id : name;
3644
3950
  let args = item.arguments;
@@ -3670,7 +3976,7 @@ function toolUsage(calls) {
3670
3976
  };
3671
3977
  }
3672
3978
  function normalizeUsage2(usage) {
3673
- if (!isRecord8(usage)) return void 0;
3979
+ if (!isRecord9(usage)) return void 0;
3674
3980
  const inputTokens = numberValue2(usage.input_tokens);
3675
3981
  const outputTokens = numberValue2(usage.output_tokens);
3676
3982
  const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
@@ -3722,16 +4028,16 @@ function modelOf2(compiled) {
3722
4028
  return typeof compiled.body.model === "string" ? compiled.body.model : "";
3723
4029
  }
3724
4030
  function messageFrom2(body) {
3725
- if (!isRecord8(body)) return void 0;
4031
+ if (!isRecord9(body)) return void 0;
3726
4032
  if (typeof body.message === "string") return body.message;
3727
- if (isRecord8(body.error) && typeof body.error.message === "string") return body.error.message;
4033
+ if (isRecord9(body.error) && typeof body.error.message === "string") return body.error.message;
3728
4034
  return void 0;
3729
4035
  }
3730
4036
  function numberValue2(value) {
3731
4037
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3732
4038
  }
3733
4039
  function attachProviderMetadata(body, request, packer) {
3734
- const fromBody = isRecord8(body.metadata) ? body.metadata : void 0;
4040
+ const fromBody = isRecord9(body.metadata) ? body.metadata : void 0;
3735
4041
  const packed = (0, import_openrouter_runtime2.packProviderRecord)(request.metadata, request.agentId, fromBody, packer);
3736
4042
  if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
3737
4043
  if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
@@ -3805,7 +4111,7 @@ function readEnv3(name) {
3805
4111
  if (typeof process === "undefined") return void 0;
3806
4112
  return process.env[name];
3807
4113
  }
3808
- function isRecord8(value) {
4114
+ function isRecord9(value) {
3809
4115
  return typeof value === "object" && value !== null && !Array.isArray(value);
3810
4116
  }
3811
4117
 
@@ -3987,7 +4293,7 @@ function callOf(call) {
3987
4293
  }
3988
4294
  function attachCompileMeta(prepared, compiled) {
3989
4295
  const record2 = compiled;
3990
- const headers = isRecord9(record2.headers) ? redactHeaders(record2.headers) : void 0;
4296
+ const headers = isRecord10(record2.headers) ? redactHeaders(record2.headers) : void 0;
3991
4297
  const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
3992
4298
  return {
3993
4299
  ...record2,
@@ -4023,7 +4329,7 @@ function log(options, event, prepared, entrypoint, streaming) {
4023
4329
  if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
4024
4330
  else options.logger?.info?.(event, payload);
4025
4331
  }
4026
- function isRecord9(value) {
4332
+ function isRecord10(value) {
4027
4333
  return typeof value === "object" && value !== null && !Array.isArray(value);
4028
4334
  }
4029
4335