@x12i/ai-dispatcher 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,13 +41,13 @@ __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
+ var import_ai_profiles4 = require("@x12i/ai-profiles");
51
51
  var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
52
52
 
53
53
  // src/create-dispatcher.ts
@@ -162,7 +162,7 @@ async function openTrack(settings, model) {
162
162
  try {
163
163
  tokenCounter = await loadTokenizer(tokenizer);
164
164
  const framing = framingFor(settings);
165
- const resolved = (0, import_core.resolveSettings)({
165
+ const resolved2 = (0, import_core.resolveSettings)({
166
166
  files: [],
167
167
  env: {},
168
168
  factory: {
@@ -177,8 +177,8 @@ async function openTrack(settings, model) {
177
177
  });
178
178
  const approvals = settings.approvals;
179
179
  const client = (0, import_core.createCoreClient)({
180
- config: resolved.config,
181
- explanation: resolved.explanation,
180
+ config: resolved2.config,
181
+ explanation: resolved2.explanation,
182
182
  ...approvals && approvals.length > 0 ? { policy: { approvals: [...approvals] } } : {},
183
183
  ...tokenCounter ? { tokenCounter } : {},
184
184
  warn: () => void 0
@@ -232,10 +232,10 @@ async function encodeRequestText(request, track) {
232
232
  if (typeof request.input === "string") {
233
233
  request.input = await encodeString(track, request.input);
234
234
  } else if (Array.isArray(request.input)) {
235
- for (let index = 0; index < request.input.length; index += 1) {
236
- const item = request.input[index];
235
+ for (let index2 = 0; index2 < request.input.length; index2 += 1) {
236
+ const item = request.input[index2];
237
237
  if (typeof item === "string") {
238
- request.input[index] = await encodeString(track, item);
238
+ request.input[index2] = await encodeString(track, item);
239
239
  } else if (item) {
240
240
  await encodeContentRecord(item, track);
241
241
  }
@@ -432,9 +432,9 @@ function walk(current, tokens) {
432
432
  }
433
433
  if (Array.isArray(current)) {
434
434
  if (!/^\d+$/.test(head)) return [];
435
- const index = Number(head);
436
- if (index >= current.length) return [];
437
- return walk(current[index], rest);
435
+ const index2 = Number(head);
436
+ if (index2 >= current.length) return [];
437
+ return walk(current[index2], rest);
438
438
  }
439
439
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
440
440
  return walk(current[head], rest);
@@ -469,9 +469,9 @@ function collectParents(current, tokens) {
469
469
  }
470
470
  if (Array.isArray(current)) {
471
471
  if (!/^\d+$/.test(head)) return [];
472
- const index = Number(head);
473
- if (index >= current.length) return [];
474
- return collectParents(current[index], rest);
472
+ const index2 = Number(head);
473
+ if (index2 >= current.length) return [];
474
+ return collectParents(current[index2], rest);
475
475
  }
476
476
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
477
477
  return collectParents(current[head], rest);
@@ -485,8 +485,8 @@ function deletePointer(root, pointer) {
485
485
  for (const parent of parents) {
486
486
  if (Array.isArray(parent)) {
487
487
  if (!/^\d+$/.test(last)) continue;
488
- const index = Number(last);
489
- if (index < parent.length) parent.splice(index, 1);
488
+ const index2 = Number(last);
489
+ if (index2 < parent.length) parent.splice(index2, 1);
490
490
  continue;
491
491
  }
492
492
  if (isRecord2(parent) && Object.prototype.hasOwnProperty.call(parent, last)) {
@@ -518,6 +518,38 @@ function extractStreamReasoningText(event, response) {
518
518
  }
519
519
  return firstConcatenatedText(event, response.streamReasoningPaths);
520
520
  }
521
+ function readReportedEffort(root) {
522
+ if (!isRecord3(root)) return void 0;
523
+ const fromRoot = effortOn(root.reasoning) ?? namedEffort(root.reasoning_effort);
524
+ if (fromRoot) return fromRoot;
525
+ if (Array.isArray(root.choices)) {
526
+ for (const choice of root.choices) {
527
+ if (!isRecord3(choice)) continue;
528
+ const message = isRecord3(choice.message) ? choice.message : void 0;
529
+ const fromMessage = message ? effortOn(message.reasoning) ?? namedEffort(message.reasoning_effort) : void 0;
530
+ if (fromMessage) return fromMessage;
531
+ const fromChoice = effortOn(choice.reasoning) ?? namedEffort(choice.reasoning_effort);
532
+ if (fromChoice) return fromChoice;
533
+ }
534
+ }
535
+ if (Array.isArray(root.output)) {
536
+ for (const item of root.output) {
537
+ if (!isRecord3(item) || item.type !== "reasoning") continue;
538
+ const effort = namedEffort(item.effort);
539
+ if (effort) return effort;
540
+ }
541
+ }
542
+ return void 0;
543
+ }
544
+ function effortOn(value) {
545
+ if (!isRecord3(value)) return void 0;
546
+ return namedEffort(value.effort);
547
+ }
548
+ function namedEffort(value) {
549
+ if (typeof value !== "string") return void 0;
550
+ const trimmed = value.trim();
551
+ return trimmed || void 0;
552
+ }
521
553
  function readReasoningTokens(root, paths) {
522
554
  for (const path of paths) {
523
555
  const absolute = firstNumber(root, [path]);
@@ -602,7 +634,10 @@ function splitThinkTags(text) {
602
634
  // src/apply-resolution.ts
603
635
  function applyResolution(response, prepared) {
604
636
  const result = response;
605
- if (prepared.resolution) result.reasoningResolution = prepared.resolution;
637
+ if (prepared.resolution) {
638
+ const reportedEffort = readReportedEffort(response.raw?.response);
639
+ result.reasoningResolution = reportedEffort ? { ...prepared.resolution, reportedEffort } : prepared.resolution;
640
+ }
606
641
  if (prepared.cacheResolution) result.cacheResolution = prepared.cacheResolution;
607
642
  if (prepared.cacheWarnings?.length) {
608
643
  result.warnings = [...result.warnings, ...prepared.cacheWarnings];
@@ -984,7 +1019,7 @@ function isRecord5(value) {
984
1019
  }
985
1020
 
986
1021
  // src/prepare-request.ts
987
- var import_ai_profiles = require("@x12i/ai-profiles");
1022
+ var import_ai_profiles3 = require("@x12i/ai-profiles");
988
1023
 
989
1024
  // src/cache-apply.ts
990
1025
  var BREAKPOINT = { mode: "explicit" };
@@ -1067,38 +1102,38 @@ function placeBedrockBreakpoint(body, ttl) {
1067
1102
  function markMessages(body, field, markerKey, markerValue, textType) {
1068
1103
  const messages = body[field];
1069
1104
  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);
1105
+ const index2 = stableMessageIndex(messages);
1106
+ if (index2 < 0) return { body, placed: false };
1107
+ const marked = markMessage(messages[index2], markerKey, markerValue, textType);
1073
1108
  if (!marked) return { body, placed: false };
1074
1109
  const next = messages.slice();
1075
- next[index] = marked;
1110
+ next[index2] = marked;
1076
1111
  return { placed: true, body: { ...body, [field]: next } };
1077
1112
  }
1078
1113
  function markInput(body, markerKey, markerValue, textType) {
1079
1114
  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);
1115
+ const index2 = stableInputIndex(body.input);
1116
+ if (index2 < 0) return { body, placed: false };
1117
+ const marked = markMessage(body.input[index2], markerKey, markerValue, textType);
1083
1118
  if (!marked) return { body, placed: false };
1084
1119
  const next = body.input.slice();
1085
- next[index] = marked;
1120
+ next[index2] = marked;
1086
1121
  return { placed: true, body: { ...body, input: next } };
1087
1122
  }
1088
1123
  function stableMessageIndex(messages) {
1089
1124
  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;
1125
+ for (let index2 = 0; index2 < messages.length; index2 += 1) {
1126
+ const message = messages[index2];
1127
+ if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index2;
1093
1128
  }
1094
1129
  return lastSystem;
1095
1130
  }
1096
1131
  function stableInputIndex(items) {
1097
1132
  let lastStable = -1;
1098
- for (let index = 0; index < items.length; index += 1) {
1099
- const item = items[index];
1133
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
1134
+ const item = items[index2];
1100
1135
  if (!isPlainRecord(item) || !hasText(item.content)) continue;
1101
- if (item.role === "system" || item.role === "developer") lastStable = index;
1136
+ if (item.role === "system" || item.role === "developer") lastStable = index2;
1102
1137
  }
1103
1138
  return lastStable;
1104
1139
  }
@@ -1115,10 +1150,10 @@ function markContent(content, markerKey, markerValue, textType) {
1115
1150
  }
1116
1151
  if (!Array.isArray(content)) return void 0;
1117
1152
  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];
1153
+ for (let index2 = parts.length - 1; index2 >= 0; index2 -= 1) {
1154
+ const part = parts[index2];
1120
1155
  if (!isPlainRecord(part) || typeof part.text !== "string" || !part.text.trim()) continue;
1121
- parts[index] = { ...part, [markerKey]: markerValue };
1156
+ parts[index2] = { ...part, [markerKey]: markerValue };
1122
1157
  return parts;
1123
1158
  }
1124
1159
  return void 0;
@@ -1274,11 +1309,11 @@ function resolveCloudflarePromptCache(args) {
1274
1309
  warnings: []
1275
1310
  };
1276
1311
  }
1277
- const resolved = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1278
- const ttl = resolved.plan.ttl ?? requestedTtl;
1312
+ const resolved2 = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1313
+ const ttl = resolved2.plan.ttl ?? requestedTtl;
1279
1314
  return {
1280
- ...resolved,
1281
- plan: { ...resolved.plan, gateway: gatewayDirective(false, ttl, key) }
1315
+ ...resolved2,
1316
+ plan: { ...resolved2.plan, gateway: gatewayDirective(false, ttl, key) }
1282
1317
  };
1283
1318
  }
1284
1319
  function applyProfile(args) {
@@ -1483,22 +1518,22 @@ function restoreCloudflareModel(model, prefix) {
1483
1518
  return `${prefix}${model}`;
1484
1519
  }
1485
1520
  function createCloudflareProviderAdapter(options = {}, packer) {
1486
- const resolved = resolveCloudflareOptions(options, packer);
1521
+ const resolved2 = resolveCloudflareOptions(options, packer);
1487
1522
  return {
1488
1523
  compile(request, call) {
1489
- return compileCloudflareRequest(request, resolved, call, false);
1524
+ return compileCloudflareRequest(request, resolved2, call, false);
1490
1525
  },
1491
1526
  async run(request, call) {
1492
1527
  const requestId = request.id ?? "cloudflare";
1493
1528
  try {
1494
- return await runCloudflare(request, resolved, call, requestId);
1529
+ return await runCloudflare(request, resolved2, call, requestId);
1495
1530
  } catch (error) {
1496
1531
  if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1497
1532
  return failedResponse(request, requestId, error, "chat");
1498
1533
  }
1499
1534
  },
1500
1535
  executeStreamingChat(request, call) {
1501
- return streamCloudflare(request, resolved, call);
1536
+ return streamCloudflare(request, resolved2, call);
1502
1537
  }
1503
1538
  };
1504
1539
  }
@@ -2646,6 +2681,259 @@ function isRecord6(value) {
2646
2681
  return typeof value === "object" && value !== null && !Array.isArray(value);
2647
2682
  }
2648
2683
 
2684
+ // src/resolve-effort.ts
2685
+ var import_ai_profiles2 = require("@x12i/ai-profiles");
2686
+
2687
+ // src/catalog-efforts.ts
2688
+ var import_node_module = require("module");
2689
+ var import_node_fs = require("fs");
2690
+ var import_node_path = require("path");
2691
+ var import_ai_profiles = require("@x12i/ai-profiles");
2692
+ var import_meta = {};
2693
+ var index;
2694
+ function normalize(value) {
2695
+ return value.trim().toLowerCase();
2696
+ }
2697
+ function addKey(keys, key, row) {
2698
+ const normalized = normalize(key);
2699
+ if (!normalized) return;
2700
+ const existing = keys.get(normalized);
2701
+ if (!existing) {
2702
+ keys.set(normalized, row);
2703
+ return;
2704
+ }
2705
+ if (existing === "ambiguous") return;
2706
+ if (existing.modelId !== row.modelId) keys.set(normalized, "ambiguous");
2707
+ }
2708
+ function loadAssignmentAliases() {
2709
+ const require2 = (0, import_node_module.createRequire)(import_meta.url);
2710
+ const entry = require2.resolve("@x12i/ai-profiles");
2711
+ const path = (0, import_node_path.join)((0, import_node_path.dirname)(entry), "data", "reasoning-directives.json");
2712
+ const document = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
2713
+ return document.assignments ?? [];
2714
+ }
2715
+ function effortList(entry) {
2716
+ const reasoning = entry.openrouter.reasoning;
2717
+ const modelId = entry.openrouter.modelId;
2718
+ if (!reasoning || !Object.prototype.hasOwnProperty.call(reasoning, "supportedEfforts")) {
2719
+ return { kind: "unspecified", modelId };
2720
+ }
2721
+ if (reasoning.supportedEfforts === null) return { kind: "any", modelId };
2722
+ if (Array.isArray(reasoning.supportedEfforts)) {
2723
+ return { kind: "listed", modelId, efforts: reasoning.supportedEfforts };
2724
+ }
2725
+ return { kind: "unspecified", modelId };
2726
+ }
2727
+ function buildIndex() {
2728
+ const catalog = (0, import_ai_profiles.loadBundledModelsCatalog)();
2729
+ const keys = /* @__PURE__ */ new Map();
2730
+ const byCatalogId = /* @__PURE__ */ new Map();
2731
+ for (const entry of catalog.models) {
2732
+ const row = effortList(entry);
2733
+ byCatalogId.set(normalize(entry.openrouter.modelId), row);
2734
+ addKey(keys, entry.openrouter.modelId, row);
2735
+ if (entry.openrouter.canonicalSlug) addKey(keys, entry.openrouter.canonicalSlug, row);
2736
+ addKey(keys, `${entry.direct.provider}/${entry.direct.modelId}`, row);
2737
+ addKey(keys, `openrouter/${entry.openrouter.modelId}`, row);
2738
+ addKey(keys, entry.direct.modelId, row);
2739
+ }
2740
+ for (const assignment of loadAssignmentAliases()) {
2741
+ const row = byCatalogId.get(normalize(assignment.catalogModelId));
2742
+ if (!row) continue;
2743
+ for (const alias of assignment.aliases ?? []) addKey(keys, alias, row);
2744
+ }
2745
+ return keys;
2746
+ }
2747
+ function lookupCatalogEfforts(model) {
2748
+ if (!index) index = buildIndex();
2749
+ const direct = index.get(normalize(model));
2750
+ if (direct && direct !== "ambiguous") return direct;
2751
+ const gateway = normalize(model);
2752
+ if (gateway.startsWith("openrouter/")) {
2753
+ const stripped = index.get(gateway.slice("openrouter/".length));
2754
+ if (stripped && stripped !== "ambiguous") return stripped;
2755
+ }
2756
+ return void 0;
2757
+ }
2758
+
2759
+ // src/resolve-effort.ts
2760
+ var EMPTY_RESPONSE = {
2761
+ reasoningTextPaths: [],
2762
+ streamReasoningKeys: [],
2763
+ streamReasoningPaths: [],
2764
+ stripFromHistoryPaths: [],
2765
+ stripThinkTags: false
2766
+ };
2767
+ var MAPPER_EFFORTS = ["high", "xhigh", "max", "medium", "low", "minimal"];
2768
+ function isRecord7(value) {
2769
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2770
+ }
2771
+ function readBodyEffort(body) {
2772
+ const reasoning = body.reasoning;
2773
+ if (isRecord7(reasoning) && typeof reasoning.effort === "string") return reasoning.effort;
2774
+ const fields = body.additionalModelRequestFields;
2775
+ if (!isRecord7(fields)) return void 0;
2776
+ const output = fields.output_config;
2777
+ if (isRecord7(output) && typeof output.effort === "string") return output.effort;
2778
+ return void 0;
2779
+ }
2780
+ function writeBodyEffort(body, effort) {
2781
+ const reasoning = body.reasoning;
2782
+ if (isRecord7(reasoning) && typeof reasoning.effort === "string") {
2783
+ reasoning.effort = effort;
2784
+ return true;
2785
+ }
2786
+ const fields = body.additionalModelRequestFields;
2787
+ if (!isRecord7(fields)) return false;
2788
+ const output = fields.output_config;
2789
+ if (isRecord7(output) && typeof output.effort === "string") {
2790
+ output.effort = effort;
2791
+ return true;
2792
+ }
2793
+ return false;
2794
+ }
2795
+ function unsupported(model, effort, supported) {
2796
+ return new AiDispatcherError(
2797
+ "EFFORT_UNSUPPORTED",
2798
+ `Model '${model}' does not accept reasoning effort '${effort}'. Supported efforts: ${supported.join(", ")}.`,
2799
+ { model, effort, supportedEfforts: [...supported] }
2800
+ );
2801
+ }
2802
+ function ignoredDirective() {
2803
+ return {
2804
+ outcome: "ignored",
2805
+ body: {},
2806
+ response: {
2807
+ reasoningTextPaths: [...EMPTY_RESPONSE.reasoningTextPaths],
2808
+ streamReasoningKeys: [...EMPTY_RESPONSE.streamReasoningKeys],
2809
+ streamReasoningPaths: [...EMPTY_RESPONSE.streamReasoningPaths],
2810
+ stripFromHistoryPaths: [...EMPTY_RESPONSE.stripFromHistoryPaths],
2811
+ stripThinkTags: EMPTY_RESPONSE.stripThinkTags
2812
+ }
2813
+ };
2814
+ }
2815
+ function toDispatcherError(error) {
2816
+ return new AiDispatcherError(error.code, error.message, error.details);
2817
+ }
2818
+ function resolveMapping(input, ignoreUnknown) {
2819
+ try {
2820
+ return (0, import_ai_profiles2.resolveReasoning)({
2821
+ target: input.target,
2822
+ model: input.model,
2823
+ effort: input.effort
2824
+ });
2825
+ } catch (error) {
2826
+ if (!(error instanceof import_ai_profiles2.AIProfilesError)) throw error;
2827
+ if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2828
+ return ignoredDirective();
2829
+ }
2830
+ if (error.code === "INVALID_EFFORT") return "bad-key";
2831
+ if (error.code === "REASONING_MAPPING_INVALID" && error.message.includes("missing effort")) return "bad-key";
2832
+ throw toDispatcherError(error);
2833
+ }
2834
+ }
2835
+ function mappingOrThrow(input, ignoreUnknown) {
2836
+ const mapped = resolveMapping(input, ignoreUnknown);
2837
+ if (mapped === "bad-key") {
2838
+ throw new AiDispatcherError(
2839
+ "INVALID_EFFORT",
2840
+ `Invalid reasoning effort '${input.effort}'.`,
2841
+ { target: input.target, effort: input.effort, model: input.model }
2842
+ );
2843
+ }
2844
+ return mapped;
2845
+ }
2846
+ function sendAsWritten(directive, effort, target) {
2847
+ if (directive.outcome === "ignored") return directive;
2848
+ const copy = structuredClone(directive);
2849
+ const wrote = writeBodyEffort(copy.body, effort);
2850
+ if (!wrote && target !== "bedrock") {
2851
+ const reasoning = isRecord7(copy.body.reasoning) ? copy.body.reasoning : {};
2852
+ copy.body = { ...copy.body, reasoning: { ...reasoning, effort } };
2853
+ }
2854
+ copy.outcome = "applied";
2855
+ return copy;
2856
+ }
2857
+ function directiveForListedEffort(input) {
2858
+ const direct = resolveMapping(input, input.ignoreUnknown);
2859
+ if (direct !== "bad-key") {
2860
+ if (direct.outcome === "ignored") return direct;
2861
+ const sent = readBodyEffort(direct.body);
2862
+ if (sent === input.effort) {
2863
+ if (direct.outcome === "degraded") {
2864
+ const copy = structuredClone(direct);
2865
+ copy.outcome = "applied";
2866
+ return copy;
2867
+ }
2868
+ return direct;
2869
+ }
2870
+ if (sent) return sendAsWritten(direct, input.effort, input.target);
2871
+ }
2872
+ const proxies = MAPPER_EFFORTS.filter((candidate) => {
2873
+ if (candidate === input.effort) return false;
2874
+ if (input.supported === null) return true;
2875
+ return input.supported.includes(candidate);
2876
+ });
2877
+ for (const proxy of proxies) {
2878
+ const mapped = resolveMapping({ ...input, effort: proxy }, input.ignoreUnknown);
2879
+ if (mapped === "bad-key" || mapped.outcome === "ignored") continue;
2880
+ if (!readBodyEffort(mapped.body)) continue;
2881
+ return sendAsWritten(mapped, input.effort, input.target);
2882
+ }
2883
+ return mappingOrThrow(input, input.ignoreUnknown);
2884
+ }
2885
+ function sentOf(directive) {
2886
+ if (directive.outcome === "ignored") return void 0;
2887
+ return readBodyEffort(directive.body);
2888
+ }
2889
+ function resolved(directive) {
2890
+ const sentEffort = sentOf(directive);
2891
+ return sentEffort ? { directive, sentEffort } : { directive };
2892
+ }
2893
+ function resolveEffortDirective(input) {
2894
+ const effort = input.effort.trim();
2895
+ const row = lookupCatalogEfforts(input.model);
2896
+ if (!row) {
2897
+ return resolved(mappingOrThrow(input, input.ignoreUnknown));
2898
+ }
2899
+ if (row.kind === "unspecified") {
2900
+ const directive2 = mappingOrThrow({ ...input, effort }, input.ignoreUnknown);
2901
+ const sent = sentOf(directive2);
2902
+ if (!input.degrade && directive2.outcome === "degraded" && sent && sent !== effort) {
2903
+ throw new AiDispatcherError(
2904
+ "EFFORT_UNSUPPORTED",
2905
+ `Model '${input.model}' does not accept reasoning effort '${effort}' unless degradeReasoningEffort is set.`,
2906
+ { model: input.model, effort, catalogModelId: row.modelId }
2907
+ );
2908
+ }
2909
+ return resolved(directive2);
2910
+ }
2911
+ const supported = row.kind === "listed" ? row.efforts : null;
2912
+ const allowed = supported === null || supported.includes(effort);
2913
+ if (!allowed && supported) {
2914
+ if (!input.degrade) throw unsupported(input.model, effort, supported);
2915
+ const mapped = resolveMapping({ ...input, effort }, input.ignoreUnknown);
2916
+ if (mapped === "bad-key") throw unsupported(input.model, effort, supported);
2917
+ if (mapped.outcome === "ignored") return { directive: mapped };
2918
+ const sent = readBodyEffort(mapped.body);
2919
+ if (!sent || !supported.includes(sent)) throw unsupported(input.model, effort, supported);
2920
+ if (sent !== effort) {
2921
+ const copy = structuredClone(mapped);
2922
+ copy.outcome = "degraded";
2923
+ return { directive: copy, sentEffort: sent };
2924
+ }
2925
+ return { directive: mapped, sentEffort: sent };
2926
+ }
2927
+ const directive = directiveForListedEffort({
2928
+ target: input.target,
2929
+ model: input.model,
2930
+ effort,
2931
+ ignoreUnknown: input.ignoreUnknown,
2932
+ supported
2933
+ });
2934
+ return resolved(directive);
2935
+ }
2936
+
2649
2937
  // src/prepare-request.ts
2650
2938
  function prepareDispatchRequest(params) {
2651
2939
  const cloned = cloneRequest(params.request);
@@ -2655,11 +2943,13 @@ function prepareDispatchRequest(params) {
2655
2943
  applyDispatchGate(cloned);
2656
2944
  const provider = params.provider;
2657
2945
  const reasoningEffort = cloned.reasoningEffort;
2946
+ const degradeReasoningEffort = cloned.degradeReasoningEffort === true;
2658
2947
  const bedrockControls = cloned.bedrockControls;
2659
2948
  const cachePolicy = cloned.cache;
2660
2949
  const cloudflareRequest = cloned.cloudflare;
2661
2950
  delete cloned.provider;
2662
2951
  delete cloned.reasoningEffort;
2952
+ delete cloned.degradeReasoningEffort;
2663
2953
  delete cloned.bedrockControls;
2664
2954
  delete cloned.cache;
2665
2955
  delete cloned.cloudflare;
@@ -2677,6 +2967,7 @@ function prepareDispatchRequest(params) {
2677
2967
  const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
2678
2968
  const explicit = hasExplicitControl(provider, cloned, bedrockControls);
2679
2969
  let directive;
2970
+ let sentEffort;
2680
2971
  let catalogPrefix;
2681
2972
  if (!explicit && reasoningEffort !== void 0) {
2682
2973
  if (!requestedModel) {
@@ -2688,13 +2979,29 @@ function prepareDispatchRequest(params) {
2688
2979
  if (provider === "cloudflare") {
2689
2980
  const query = cloudflareCatalogQuery(requestedModel);
2690
2981
  catalogPrefix = query.prefix;
2691
- directive = resolveCatalog({ target: query.target, model: query.model, effort: reasoningEffort }, true);
2982
+ const resolved2 = resolveEffortDirective({
2983
+ target: query.target,
2984
+ model: query.model,
2985
+ effort: reasoningEffort,
2986
+ ignoreUnknown: true,
2987
+ degrade: degradeReasoningEffort
2988
+ });
2989
+ directive = resolved2.directive;
2990
+ sentEffort = resolved2.sentEffort;
2692
2991
  } else if (isReasoningTarget(provider)) {
2693
- directive = resolveCatalog({ target: provider, model: requestedModel, effort: reasoningEffort }, false);
2992
+ const resolved2 = resolveEffortDirective({
2993
+ target: provider,
2994
+ model: requestedModel,
2995
+ effort: reasoningEffort,
2996
+ ignoreUnknown: false,
2997
+ degrade: degradeReasoningEffort
2998
+ });
2999
+ directive = resolved2.directive;
3000
+ sentEffort = resolved2.sentEffort;
2694
3001
  }
2695
3002
  }
2696
3003
  if (provider === "openrouter") {
2697
- applyNestedReasoning(cloned, reasoningEffort);
3004
+ applyNestedReasoning(cloned, reasoningEffort, degradeReasoningEffort);
2698
3005
  }
2699
3006
  if (directive?.model) {
2700
3007
  cloned.model = provider === "cloudflare" ? restoreCloudflareModel(directive.model, catalogPrefix) : directive.model;
@@ -2728,7 +3035,7 @@ function prepareDispatchRequest(params) {
2728
3035
  if (overlay || cacheTouchesBody) {
2729
3036
  const bodyOverlay = overlay;
2730
3037
  call.finalizeBody = (body) => {
2731
- const merged = bodyOverlay ? (0, import_ai_profiles.mergeReasoningBody)(body, bodyOverlay) : body;
3038
+ const merged = bodyOverlay ? (0, import_ai_profiles3.mergeReasoningBody)(body, bodyOverlay) : body;
2732
3039
  if (!cacheTouchesBody || !cachePlan) return merged;
2733
3040
  const applied = applyPromptCache(merged, cachePlan);
2734
3041
  if (cachePlan.placeBreakpoint && !applied.placed) {
@@ -2755,6 +3062,7 @@ function prepareDispatchRequest(params) {
2755
3062
  }
2756
3063
  const resolution = reasoningEffort !== void 0 || explicit ? buildResolution({
2757
3064
  requestedEffort: reasoningEffort,
3065
+ sentEffort: explicit ? explicitSentEffort(provider, cloned, bedrockControls) : sentEffort,
2758
3066
  target: provider,
2759
3067
  requestedModel,
2760
3068
  effectiveModel,
@@ -2787,29 +3095,6 @@ function noteUnplacedCache(resolution, warnings, target, model) {
2787
3095
  details: { target, model }
2788
3096
  });
2789
3097
  }
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
3098
  function isReasoningTarget(provider) {
2814
3099
  return provider === "openrouter" || provider === "bedrock" || provider === "openai";
2815
3100
  }
@@ -2817,6 +3102,15 @@ function hasExplicitControl(provider, request, bedrockControls) {
2817
3102
  if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
2818
3103
  return request.reasoning !== void 0;
2819
3104
  }
3105
+ function explicitSentEffort(provider, request, bedrockControls) {
3106
+ if (provider === "bedrock") {
3107
+ const output = bedrockControls?.additionalModelRequestFields?.output_config;
3108
+ if (isRecord8(output) && typeof output.effort === "string") return output.effort;
3109
+ return void 0;
3110
+ }
3111
+ if (isRecord8(request.reasoning) && typeof request.reasoning.effort === "string") return request.reasoning.effort;
3112
+ return void 0;
3113
+ }
2820
3114
  function buildResolution(params) {
2821
3115
  return {
2822
3116
  target: params.target,
@@ -2824,19 +3118,20 @@ function buildResolution(params) {
2824
3118
  effectiveModel: params.effectiveModel,
2825
3119
  bypassed: params.bypassed,
2826
3120
  ...params.requestedEffort ? { requestedEffort: params.requestedEffort } : {},
3121
+ ...params.sentEffort ? { sentEffort: params.sentEffort } : {},
2827
3122
  ...params.outcome ? { outcome: params.outcome } : {}
2828
3123
  };
2829
3124
  }
2830
- function applyNestedReasoning(request, parentEffort) {
3125
+ function applyNestedReasoning(request, parentEffort, degrade) {
2831
3126
  const serverTools = request.serverTools;
2832
3127
  if (!serverTools) return;
2833
- if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort);
3128
+ if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort, degrade);
2834
3129
  if (serverTools.advisor) {
2835
- serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort)) : settleNested(serverTools.advisor, "advisor", parentEffort);
3130
+ serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort, degrade)) : settleNested(serverTools.advisor, "advisor", parentEffort, degrade);
2836
3131
  }
2837
- if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort);
3132
+ if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort, degrade);
2838
3133
  }
2839
- function settleNested(policy, kind, parentEffort) {
3134
+ function settleNested(policy, kind, parentEffort, degrade) {
2840
3135
  const current = policy;
2841
3136
  const childEffort = current.reasoningEffort;
2842
3137
  const next = { ...current };
@@ -2846,12 +3141,18 @@ function settleNested(policy, kind, parentEffort) {
2846
3141
  if (!effort) return next;
2847
3142
  const model = kind === "fusion" ? next.judgeModel : next.model;
2848
3143
  if (!model) return next;
2849
- const directive = resolveCatalog({ target: "openrouter", model, effort }, false);
3144
+ const directive = resolveEffortDirective({
3145
+ target: "openrouter",
3146
+ model,
3147
+ effort,
3148
+ ignoreUnknown: false,
3149
+ degrade
3150
+ }).directive;
2850
3151
  if (directive.model) {
2851
3152
  if (kind === "fusion") next.judgeModel = directive.model;
2852
3153
  else next.model = directive.model;
2853
3154
  }
2854
- if (isRecord7(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
3155
+ if (isRecord8(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
2855
3156
  return next;
2856
3157
  }
2857
3158
  function applyPromptPrefix(request, prefix) {
@@ -2888,10 +3189,10 @@ function prefixMessages(messages, prefix) {
2888
3189
  return false;
2889
3190
  }
2890
3191
  function prefixInputItems(items, prefix) {
2891
- for (let index = 0; index < items.length; index += 1) {
2892
- const item = items[index];
3192
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
3193
+ const item = items[index2];
2893
3194
  if (typeof item === "string") {
2894
- items[index] = prefix + item;
3195
+ items[index2] = prefix + item;
2895
3196
  return true;
2896
3197
  }
2897
3198
  if (!item || item.role === "system") continue;
@@ -2901,7 +3202,7 @@ function prefixInputItems(items, prefix) {
2901
3202
  }
2902
3203
  if (!Array.isArray(item.content)) continue;
2903
3204
  for (const part of item.content) {
2904
- if (!isRecord7(part) || typeof part.text !== "string") continue;
3205
+ if (!isRecord8(part) || typeof part.text !== "string") continue;
2905
3206
  if (part.type === "text" || part.type === "input_text") {
2906
3207
  part.text = prefix + part.text;
2907
3208
  return true;
@@ -2962,7 +3263,7 @@ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
2962
3263
  function applyRoleStamp(request) {
2963
3264
  const stamp = request.role;
2964
3265
  delete request.role;
2965
- if (!isRecord7(stamp)) return;
3266
+ if (!isRecord8(stamp)) return;
2966
3267
  const identity = {};
2967
3268
  for (const field of ROLE_STAMP_FIELDS) {
2968
3269
  const value = stamp[field];
@@ -2977,8 +3278,8 @@ function applyRoleStamp(request) {
2977
3278
  }
2978
3279
  function absorbOverrideMetadata(request) {
2979
3280
  const overrides = request.rawOpenRouterOverrides;
2980
- if (!isRecord7(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
2981
- if (isRecord7(overrides.metadata)) {
3281
+ if (!isRecord8(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
3282
+ if (isRecord8(overrides.metadata)) {
2982
3283
  const current = { ...request.metadata ?? {} };
2983
3284
  const extra = {};
2984
3285
  for (const [key, value] of Object.entries(overrides.metadata)) {
@@ -3023,7 +3324,7 @@ function hasOwnKeys(value) {
3023
3324
  function hasStreamMetadata(directive) {
3024
3325
  return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
3025
3326
  }
3026
- function isRecord7(value) {
3327
+ function isRecord8(value) {
3027
3328
  return typeof value === "object" && value !== null && !Array.isArray(value);
3028
3329
  }
3029
3330
  function readEnv2(name) {
@@ -3170,22 +3471,22 @@ var import_openrouter_runtime2 = require("@x12i/openrouter-runtime");
3170
3471
  var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
3171
3472
  var DEFAULT_TIMEOUT_MS2 = 12e4;
3172
3473
  function createOpenAiProviderAdapter(options = {}, packer) {
3173
- const resolved = resolveOpenAiOptions(options, packer);
3474
+ const resolved2 = resolveOpenAiOptions(options, packer);
3174
3475
  return {
3175
3476
  compile(request, call) {
3176
- return compileOpenAiRequest(request, resolved, call, false);
3477
+ return compileOpenAiRequest(request, resolved2, call, false);
3177
3478
  },
3178
3479
  async run(request, call) {
3179
3480
  const requestId = request.id ?? "openai";
3180
3481
  try {
3181
- return await runOpenAi(request, resolved, call, requestId);
3482
+ return await runOpenAi(request, resolved2, call, requestId);
3182
3483
  } catch (error) {
3183
3484
  if (error instanceof AiDispatcherError && isConfigError2(error.code)) throw error;
3184
3485
  return failedResponse2(request, requestId, error);
3185
3486
  }
3186
3487
  },
3187
3488
  executeStreamingChat(request, call) {
3188
- return streamOpenAi(request, resolved, call);
3489
+ return streamOpenAi(request, resolved2, call);
3189
3490
  }
3190
3491
  };
3191
3492
  }
@@ -3342,7 +3643,7 @@ async function runOpenAi(request, options, call, requestId) {
3342
3643
  let response;
3343
3644
  for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
3344
3645
  response = await sendWithRetries2(active, options, timeoutFor2(request, options));
3345
- const calls = functionCalls(isRecord8(response) ? response : {});
3646
+ const calls = functionCalls(isRecord9(response) ? response : {});
3346
3647
  if (!calls.length) break;
3347
3648
  if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
3348
3649
  return normalizeOpenAiResponse({
@@ -3362,7 +3663,7 @@ async function runOpenAi(request, options, call, requestId) {
3362
3663
  results.push(await executeOpenAiTool(item, request, options));
3363
3664
  }
3364
3665
  functionUsage.push(...results);
3365
- const record2 = isRecord8(response) ? response : {};
3666
+ const record2 = isRecord9(response) ? response : {};
3366
3667
  active = {
3367
3668
  ...active,
3368
3669
  body: clean2({
@@ -3477,7 +3778,7 @@ async function* streamOpenAi(request, options, call) {
3477
3778
  throw failure;
3478
3779
  }
3479
3780
  for await (const event of parseSse2(response)) {
3480
- if (!isRecord8(event)) continue;
3781
+ if (!isRecord9(event)) continue;
3481
3782
  if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
3482
3783
  text += event.delta;
3483
3784
  yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
@@ -3496,7 +3797,7 @@ async function* streamOpenAi(request, options, call) {
3496
3797
  argumentsDelta: event.delta
3497
3798
  }
3498
3799
  };
3499
- } else if (event.type === "response.completed" && isRecord8(event.response)) {
3800
+ } else if (event.type === "response.completed" && isRecord9(event.response)) {
3500
3801
  const usage = normalizeUsage2(event.response.usage);
3501
3802
  if (usage) yield { type: "stream.usage", requestId, data: { usage } };
3502
3803
  } else if (event.type === "error") {
@@ -3590,7 +3891,7 @@ function isConfigError2(code) {
3590
3891
  }
3591
3892
  function asTerminalError2(error) {
3592
3893
  if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
3593
- const status = isRecord8(error.details) ? error.details.status : void 0;
3894
+ const status = isRecord9(error.details) ? error.details.status : void 0;
3594
3895
  if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
3595
3896
  return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
3596
3897
  }
@@ -3598,7 +3899,7 @@ function asTerminalError2(error) {
3598
3899
  return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
3599
3900
  }
3600
3901
  function normalizeOpenAiResponse(params) {
3601
- const record2 = isRecord8(params.response) ? params.response : {};
3902
+ const record2 = isRecord9(params.response) ? params.response : {};
3602
3903
  const text = answerText2(record2);
3603
3904
  const calls = params.functionTools ?? functionCalls(record2);
3604
3905
  const usage = normalizeUsage2(record2.usage);
@@ -3625,9 +3926,9 @@ function answerText2(response) {
3625
3926
  if (Array.isArray(output)) {
3626
3927
  const parts = [];
3627
3928
  for (const item of output) {
3628
- if (!isRecord8(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3929
+ if (!isRecord9(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3629
3930
  for (const part of item.content) {
3630
- if (isRecord8(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3931
+ if (isRecord9(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3631
3932
  }
3632
3933
  }
3633
3934
  if (parts.length > 0) return parts.join("");
@@ -3638,7 +3939,7 @@ function functionCalls(response) {
3638
3939
  if (!Array.isArray(response.output)) return [];
3639
3940
  const calls = [];
3640
3941
  for (const item of response.output) {
3641
- if (!isRecord8(item) || item.type !== "function_call") continue;
3942
+ if (!isRecord9(item) || item.type !== "function_call") continue;
3642
3943
  const name = typeof item.name === "string" ? item.name : "function";
3643
3944
  const callId = typeof item.call_id === "string" ? item.call_id : name;
3644
3945
  let args = item.arguments;
@@ -3670,7 +3971,7 @@ function toolUsage(calls) {
3670
3971
  };
3671
3972
  }
3672
3973
  function normalizeUsage2(usage) {
3673
- if (!isRecord8(usage)) return void 0;
3974
+ if (!isRecord9(usage)) return void 0;
3674
3975
  const inputTokens = numberValue2(usage.input_tokens);
3675
3976
  const outputTokens = numberValue2(usage.output_tokens);
3676
3977
  const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
@@ -3722,16 +4023,16 @@ function modelOf2(compiled) {
3722
4023
  return typeof compiled.body.model === "string" ? compiled.body.model : "";
3723
4024
  }
3724
4025
  function messageFrom2(body) {
3725
- if (!isRecord8(body)) return void 0;
4026
+ if (!isRecord9(body)) return void 0;
3726
4027
  if (typeof body.message === "string") return body.message;
3727
- if (isRecord8(body.error) && typeof body.error.message === "string") return body.error.message;
4028
+ if (isRecord9(body.error) && typeof body.error.message === "string") return body.error.message;
3728
4029
  return void 0;
3729
4030
  }
3730
4031
  function numberValue2(value) {
3731
4032
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3732
4033
  }
3733
4034
  function attachProviderMetadata(body, request, packer) {
3734
- const fromBody = isRecord8(body.metadata) ? body.metadata : void 0;
4035
+ const fromBody = isRecord9(body.metadata) ? body.metadata : void 0;
3735
4036
  const packed = (0, import_openrouter_runtime2.packProviderRecord)(request.metadata, request.agentId, fromBody, packer);
3736
4037
  if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
3737
4038
  if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
@@ -3805,7 +4106,7 @@ function readEnv3(name) {
3805
4106
  if (typeof process === "undefined") return void 0;
3806
4107
  return process.env[name];
3807
4108
  }
3808
- function isRecord8(value) {
4109
+ function isRecord9(value) {
3809
4110
  return typeof value === "object" && value !== null && !Array.isArray(value);
3810
4111
  }
3811
4112
 
@@ -3987,7 +4288,7 @@ function callOf(call) {
3987
4288
  }
3988
4289
  function attachCompileMeta(prepared, compiled) {
3989
4290
  const record2 = compiled;
3990
- const headers = isRecord9(record2.headers) ? redactHeaders(record2.headers) : void 0;
4291
+ const headers = isRecord10(record2.headers) ? redactHeaders(record2.headers) : void 0;
3991
4292
  const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
3992
4293
  return {
3993
4294
  ...record2,
@@ -4023,7 +4324,7 @@ function log(options, event, prepared, entrypoint, streaming) {
4023
4324
  if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
4024
4325
  else options.logger?.info?.(event, payload);
4025
4326
  }
4026
- function isRecord9(value) {
4327
+ function isRecord10(value) {
4027
4328
  return typeof value === "object" && value !== null && !Array.isArray(value);
4028
4329
  }
4029
4330