@x12i/ai-dispatcher 3.1.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,12 +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,
44
+ registerHostIntents: () => import_ai_profiles4.registerHostIntents,
45
+ resolveAIProfile: () => import_ai_profiles4.resolveAIProfile,
45
46
  resolveProvider: () => resolveProvider,
46
47
  stripReasoningHistoryFields: () => stripReasoningHistoryFields
47
48
  });
48
49
  module.exports = __toCommonJS(index_exports);
49
- var import_ai_profiles2 = require("@x12i/ai-profiles");
50
+ var import_ai_profiles4 = require("@x12i/ai-profiles");
50
51
  var import_openrouter_runtime4 = require("@x12i/openrouter-runtime");
51
52
 
52
53
  // src/create-dispatcher.ts
@@ -161,7 +162,7 @@ async function openTrack(settings, model) {
161
162
  try {
162
163
  tokenCounter = await loadTokenizer(tokenizer);
163
164
  const framing = framingFor(settings);
164
- const resolved = (0, import_core.resolveSettings)({
165
+ const resolved2 = (0, import_core.resolveSettings)({
165
166
  files: [],
166
167
  env: {},
167
168
  factory: {
@@ -176,8 +177,8 @@ async function openTrack(settings, model) {
176
177
  });
177
178
  const approvals = settings.approvals;
178
179
  const client = (0, import_core.createCoreClient)({
179
- config: resolved.config,
180
- explanation: resolved.explanation,
180
+ config: resolved2.config,
181
+ explanation: resolved2.explanation,
181
182
  ...approvals && approvals.length > 0 ? { policy: { approvals: [...approvals] } } : {},
182
183
  ...tokenCounter ? { tokenCounter } : {},
183
184
  warn: () => void 0
@@ -231,10 +232,10 @@ async function encodeRequestText(request, track) {
231
232
  if (typeof request.input === "string") {
232
233
  request.input = await encodeString(track, request.input);
233
234
  } else if (Array.isArray(request.input)) {
234
- for (let index = 0; index < request.input.length; index += 1) {
235
- const item = request.input[index];
235
+ for (let index2 = 0; index2 < request.input.length; index2 += 1) {
236
+ const item = request.input[index2];
236
237
  if (typeof item === "string") {
237
- request.input[index] = await encodeString(track, item);
238
+ request.input[index2] = await encodeString(track, item);
238
239
  } else if (item) {
239
240
  await encodeContentRecord(item, track);
240
241
  }
@@ -431,9 +432,9 @@ function walk(current, tokens) {
431
432
  }
432
433
  if (Array.isArray(current)) {
433
434
  if (!/^\d+$/.test(head)) return [];
434
- const index = Number(head);
435
- if (index >= current.length) return [];
436
- return walk(current[index], rest);
435
+ const index2 = Number(head);
436
+ if (index2 >= current.length) return [];
437
+ return walk(current[index2], rest);
437
438
  }
438
439
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
439
440
  return walk(current[head], rest);
@@ -468,9 +469,9 @@ function collectParents(current, tokens) {
468
469
  }
469
470
  if (Array.isArray(current)) {
470
471
  if (!/^\d+$/.test(head)) return [];
471
- const index = Number(head);
472
- if (index >= current.length) return [];
473
- return collectParents(current[index], rest);
472
+ const index2 = Number(head);
473
+ if (index2 >= current.length) return [];
474
+ return collectParents(current[index2], rest);
474
475
  }
475
476
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
476
477
  return collectParents(current[head], rest);
@@ -484,8 +485,8 @@ function deletePointer(root, pointer) {
484
485
  for (const parent of parents) {
485
486
  if (Array.isArray(parent)) {
486
487
  if (!/^\d+$/.test(last)) continue;
487
- const index = Number(last);
488
- if (index < parent.length) parent.splice(index, 1);
488
+ const index2 = Number(last);
489
+ if (index2 < parent.length) parent.splice(index2, 1);
489
490
  continue;
490
491
  }
491
492
  if (isRecord2(parent) && Object.prototype.hasOwnProperty.call(parent, last)) {
@@ -517,6 +518,38 @@ function extractStreamReasoningText(event, response) {
517
518
  }
518
519
  return firstConcatenatedText(event, response.streamReasoningPaths);
519
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
+ }
520
553
  function readReasoningTokens(root, paths) {
521
554
  for (const path of paths) {
522
555
  const absolute = firstNumber(root, [path]);
@@ -601,7 +634,10 @@ function splitThinkTags(text) {
601
634
  // src/apply-resolution.ts
602
635
  function applyResolution(response, prepared) {
603
636
  const result = response;
604
- 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
+ }
605
641
  if (prepared.cacheResolution) result.cacheResolution = prepared.cacheResolution;
606
642
  if (prepared.cacheWarnings?.length) {
607
643
  result.warnings = [...result.warnings, ...prepared.cacheWarnings];
@@ -983,7 +1019,7 @@ function isRecord5(value) {
983
1019
  }
984
1020
 
985
1021
  // src/prepare-request.ts
986
- var import_ai_profiles = require("@x12i/ai-profiles");
1022
+ var import_ai_profiles3 = require("@x12i/ai-profiles");
987
1023
 
988
1024
  // src/cache-apply.ts
989
1025
  var BREAKPOINT = { mode: "explicit" };
@@ -1066,38 +1102,38 @@ function placeBedrockBreakpoint(body, ttl) {
1066
1102
  function markMessages(body, field, markerKey, markerValue, textType) {
1067
1103
  const messages = body[field];
1068
1104
  if (!Array.isArray(messages)) return { body, placed: false };
1069
- const index = stableMessageIndex(messages);
1070
- if (index < 0) return { body, placed: false };
1071
- 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);
1072
1108
  if (!marked) return { body, placed: false };
1073
1109
  const next = messages.slice();
1074
- next[index] = marked;
1110
+ next[index2] = marked;
1075
1111
  return { placed: true, body: { ...body, [field]: next } };
1076
1112
  }
1077
1113
  function markInput(body, markerKey, markerValue, textType) {
1078
1114
  if (!Array.isArray(body.input)) return { body, placed: false };
1079
- const index = stableInputIndex(body.input);
1080
- if (index < 0) return { body, placed: false };
1081
- 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);
1082
1118
  if (!marked) return { body, placed: false };
1083
1119
  const next = body.input.slice();
1084
- next[index] = marked;
1120
+ next[index2] = marked;
1085
1121
  return { placed: true, body: { ...body, input: next } };
1086
1122
  }
1087
1123
  function stableMessageIndex(messages) {
1088
1124
  let lastSystem = -1;
1089
- for (let index = 0; index < messages.length; index += 1) {
1090
- const message = messages[index];
1091
- 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;
1092
1128
  }
1093
1129
  return lastSystem;
1094
1130
  }
1095
1131
  function stableInputIndex(items) {
1096
1132
  let lastStable = -1;
1097
- for (let index = 0; index < items.length; index += 1) {
1098
- const item = items[index];
1133
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
1134
+ const item = items[index2];
1099
1135
  if (!isPlainRecord(item) || !hasText(item.content)) continue;
1100
- if (item.role === "system" || item.role === "developer") lastStable = index;
1136
+ if (item.role === "system" || item.role === "developer") lastStable = index2;
1101
1137
  }
1102
1138
  return lastStable;
1103
1139
  }
@@ -1114,10 +1150,10 @@ function markContent(content, markerKey, markerValue, textType) {
1114
1150
  }
1115
1151
  if (!Array.isArray(content)) return void 0;
1116
1152
  const parts = content.map((part) => isPlainRecord(part) ? { ...part } : part);
1117
- for (let index = parts.length - 1; index >= 0; index -= 1) {
1118
- const part = parts[index];
1153
+ for (let index2 = parts.length - 1; index2 >= 0; index2 -= 1) {
1154
+ const part = parts[index2];
1119
1155
  if (!isPlainRecord(part) || typeof part.text !== "string" || !part.text.trim()) continue;
1120
- parts[index] = { ...part, [markerKey]: markerValue };
1156
+ parts[index2] = { ...part, [markerKey]: markerValue };
1121
1157
  return parts;
1122
1158
  }
1123
1159
  return void 0;
@@ -1273,11 +1309,11 @@ function resolveCloudflarePromptCache(args) {
1273
1309
  warnings: []
1274
1310
  };
1275
1311
  }
1276
- const resolved = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1277
- const ttl = resolved.plan.ttl ?? requestedTtl;
1312
+ const resolved2 = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1313
+ const ttl = resolved2.plan.ttl ?? requestedTtl;
1278
1314
  return {
1279
- ...resolved,
1280
- plan: { ...resolved.plan, gateway: gatewayDirective(false, ttl, key) }
1315
+ ...resolved2,
1316
+ plan: { ...resolved2.plan, gateway: gatewayDirective(false, ttl, key) }
1281
1317
  };
1282
1318
  }
1283
1319
  function applyProfile(args) {
@@ -1482,22 +1518,22 @@ function restoreCloudflareModel(model, prefix) {
1482
1518
  return `${prefix}${model}`;
1483
1519
  }
1484
1520
  function createCloudflareProviderAdapter(options = {}, packer) {
1485
- const resolved = resolveCloudflareOptions(options, packer);
1521
+ const resolved2 = resolveCloudflareOptions(options, packer);
1486
1522
  return {
1487
1523
  compile(request, call) {
1488
- return compileCloudflareRequest(request, resolved, call, false);
1524
+ return compileCloudflareRequest(request, resolved2, call, false);
1489
1525
  },
1490
1526
  async run(request, call) {
1491
1527
  const requestId = request.id ?? "cloudflare";
1492
1528
  try {
1493
- return await runCloudflare(request, resolved, call, requestId);
1529
+ return await runCloudflare(request, resolved2, call, requestId);
1494
1530
  } catch (error) {
1495
1531
  if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1496
1532
  return failedResponse(request, requestId, error, "chat");
1497
1533
  }
1498
1534
  },
1499
1535
  executeStreamingChat(request, call) {
1500
- return streamCloudflare(request, resolved, call);
1536
+ return streamCloudflare(request, resolved2, call);
1501
1537
  }
1502
1538
  };
1503
1539
  }
@@ -2645,6 +2681,259 @@ function isRecord6(value) {
2645
2681
  return typeof value === "object" && value !== null && !Array.isArray(value);
2646
2682
  }
2647
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
+
2648
2937
  // src/prepare-request.ts
2649
2938
  function prepareDispatchRequest(params) {
2650
2939
  const cloned = cloneRequest(params.request);
@@ -2654,11 +2943,13 @@ function prepareDispatchRequest(params) {
2654
2943
  applyDispatchGate(cloned);
2655
2944
  const provider = params.provider;
2656
2945
  const reasoningEffort = cloned.reasoningEffort;
2946
+ const degradeReasoningEffort = cloned.degradeReasoningEffort === true;
2657
2947
  const bedrockControls = cloned.bedrockControls;
2658
2948
  const cachePolicy = cloned.cache;
2659
2949
  const cloudflareRequest = cloned.cloudflare;
2660
2950
  delete cloned.provider;
2661
2951
  delete cloned.reasoningEffort;
2952
+ delete cloned.degradeReasoningEffort;
2662
2953
  delete cloned.bedrockControls;
2663
2954
  delete cloned.cache;
2664
2955
  delete cloned.cloudflare;
@@ -2676,6 +2967,7 @@ function prepareDispatchRequest(params) {
2676
2967
  const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
2677
2968
  const explicit = hasExplicitControl(provider, cloned, bedrockControls);
2678
2969
  let directive;
2970
+ let sentEffort;
2679
2971
  let catalogPrefix;
2680
2972
  if (!explicit && reasoningEffort !== void 0) {
2681
2973
  if (!requestedModel) {
@@ -2687,13 +2979,29 @@ function prepareDispatchRequest(params) {
2687
2979
  if (provider === "cloudflare") {
2688
2980
  const query = cloudflareCatalogQuery(requestedModel);
2689
2981
  catalogPrefix = query.prefix;
2690
- 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;
2691
2991
  } else if (isReasoningTarget(provider)) {
2692
- 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;
2693
3001
  }
2694
3002
  }
2695
3003
  if (provider === "openrouter") {
2696
- applyNestedReasoning(cloned, reasoningEffort);
3004
+ applyNestedReasoning(cloned, reasoningEffort, degradeReasoningEffort);
2697
3005
  }
2698
3006
  if (directive?.model) {
2699
3007
  cloned.model = provider === "cloudflare" ? restoreCloudflareModel(directive.model, catalogPrefix) : directive.model;
@@ -2727,7 +3035,7 @@ function prepareDispatchRequest(params) {
2727
3035
  if (overlay || cacheTouchesBody) {
2728
3036
  const bodyOverlay = overlay;
2729
3037
  call.finalizeBody = (body) => {
2730
- const merged = bodyOverlay ? (0, import_ai_profiles.mergeReasoningBody)(body, bodyOverlay) : body;
3038
+ const merged = bodyOverlay ? (0, import_ai_profiles3.mergeReasoningBody)(body, bodyOverlay) : body;
2731
3039
  if (!cacheTouchesBody || !cachePlan) return merged;
2732
3040
  const applied = applyPromptCache(merged, cachePlan);
2733
3041
  if (cachePlan.placeBreakpoint && !applied.placed) {
@@ -2754,6 +3062,7 @@ function prepareDispatchRequest(params) {
2754
3062
  }
2755
3063
  const resolution = reasoningEffort !== void 0 || explicit ? buildResolution({
2756
3064
  requestedEffort: reasoningEffort,
3065
+ sentEffort: explicit ? explicitSentEffort(provider, cloned, bedrockControls) : sentEffort,
2757
3066
  target: provider,
2758
3067
  requestedModel,
2759
3068
  effectiveModel,
@@ -2786,29 +3095,6 @@ function noteUnplacedCache(resolution, warnings, target, model) {
2786
3095
  details: { target, model }
2787
3096
  });
2788
3097
  }
2789
- function resolveCatalog(input, ignoreUnknown) {
2790
- try {
2791
- return (0, import_ai_profiles.resolveReasoning)(input);
2792
- } catch (error) {
2793
- if (error instanceof import_ai_profiles.AIProfilesError) {
2794
- if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2795
- return {
2796
- outcome: "ignored",
2797
- body: {},
2798
- response: {
2799
- reasoningTextPaths: [],
2800
- streamReasoningKeys: [],
2801
- streamReasoningPaths: [],
2802
- stripFromHistoryPaths: [],
2803
- stripThinkTags: false
2804
- }
2805
- };
2806
- }
2807
- throw new AiDispatcherError(error.code, error.message, error.details);
2808
- }
2809
- throw error;
2810
- }
2811
- }
2812
3098
  function isReasoningTarget(provider) {
2813
3099
  return provider === "openrouter" || provider === "bedrock" || provider === "openai";
2814
3100
  }
@@ -2816,6 +3102,15 @@ function hasExplicitControl(provider, request, bedrockControls) {
2816
3102
  if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
2817
3103
  return request.reasoning !== void 0;
2818
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
+ }
2819
3114
  function buildResolution(params) {
2820
3115
  return {
2821
3116
  target: params.target,
@@ -2823,19 +3118,20 @@ function buildResolution(params) {
2823
3118
  effectiveModel: params.effectiveModel,
2824
3119
  bypassed: params.bypassed,
2825
3120
  ...params.requestedEffort ? { requestedEffort: params.requestedEffort } : {},
3121
+ ...params.sentEffort ? { sentEffort: params.sentEffort } : {},
2826
3122
  ...params.outcome ? { outcome: params.outcome } : {}
2827
3123
  };
2828
3124
  }
2829
- function applyNestedReasoning(request, parentEffort) {
3125
+ function applyNestedReasoning(request, parentEffort, degrade) {
2830
3126
  const serverTools = request.serverTools;
2831
3127
  if (!serverTools) return;
2832
- if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort);
3128
+ if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort, degrade);
2833
3129
  if (serverTools.advisor) {
2834
- 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);
2835
3131
  }
2836
- if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort);
3132
+ if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort, degrade);
2837
3133
  }
2838
- function settleNested(policy, kind, parentEffort) {
3134
+ function settleNested(policy, kind, parentEffort, degrade) {
2839
3135
  const current = policy;
2840
3136
  const childEffort = current.reasoningEffort;
2841
3137
  const next = { ...current };
@@ -2845,12 +3141,18 @@ function settleNested(policy, kind, parentEffort) {
2845
3141
  if (!effort) return next;
2846
3142
  const model = kind === "fusion" ? next.judgeModel : next.model;
2847
3143
  if (!model) return next;
2848
- 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;
2849
3151
  if (directive.model) {
2850
3152
  if (kind === "fusion") next.judgeModel = directive.model;
2851
3153
  else next.model = directive.model;
2852
3154
  }
2853
- if (isRecord7(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
3155
+ if (isRecord8(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
2854
3156
  return next;
2855
3157
  }
2856
3158
  function applyPromptPrefix(request, prefix) {
@@ -2887,10 +3189,10 @@ function prefixMessages(messages, prefix) {
2887
3189
  return false;
2888
3190
  }
2889
3191
  function prefixInputItems(items, prefix) {
2890
- for (let index = 0; index < items.length; index += 1) {
2891
- const item = items[index];
3192
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
3193
+ const item = items[index2];
2892
3194
  if (typeof item === "string") {
2893
- items[index] = prefix + item;
3195
+ items[index2] = prefix + item;
2894
3196
  return true;
2895
3197
  }
2896
3198
  if (!item || item.role === "system") continue;
@@ -2900,7 +3202,7 @@ function prefixInputItems(items, prefix) {
2900
3202
  }
2901
3203
  if (!Array.isArray(item.content)) continue;
2902
3204
  for (const part of item.content) {
2903
- if (!isRecord7(part) || typeof part.text !== "string") continue;
3205
+ if (!isRecord8(part) || typeof part.text !== "string") continue;
2904
3206
  if (part.type === "text" || part.type === "input_text") {
2905
3207
  part.text = prefix + part.text;
2906
3208
  return true;
@@ -2961,7 +3263,7 @@ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
2961
3263
  function applyRoleStamp(request) {
2962
3264
  const stamp = request.role;
2963
3265
  delete request.role;
2964
- if (!isRecord7(stamp)) return;
3266
+ if (!isRecord8(stamp)) return;
2965
3267
  const identity = {};
2966
3268
  for (const field of ROLE_STAMP_FIELDS) {
2967
3269
  const value = stamp[field];
@@ -2976,8 +3278,8 @@ function applyRoleStamp(request) {
2976
3278
  }
2977
3279
  function absorbOverrideMetadata(request) {
2978
3280
  const overrides = request.rawOpenRouterOverrides;
2979
- if (!isRecord7(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
2980
- if (isRecord7(overrides.metadata)) {
3281
+ if (!isRecord8(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
3282
+ if (isRecord8(overrides.metadata)) {
2981
3283
  const current = { ...request.metadata ?? {} };
2982
3284
  const extra = {};
2983
3285
  for (const [key, value] of Object.entries(overrides.metadata)) {
@@ -3022,7 +3324,7 @@ function hasOwnKeys(value) {
3022
3324
  function hasStreamMetadata(directive) {
3023
3325
  return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
3024
3326
  }
3025
- function isRecord7(value) {
3327
+ function isRecord8(value) {
3026
3328
  return typeof value === "object" && value !== null && !Array.isArray(value);
3027
3329
  }
3028
3330
  function readEnv2(name) {
@@ -3169,22 +3471,22 @@ var import_openrouter_runtime2 = require("@x12i/openrouter-runtime");
3169
3471
  var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
3170
3472
  var DEFAULT_TIMEOUT_MS2 = 12e4;
3171
3473
  function createOpenAiProviderAdapter(options = {}, packer) {
3172
- const resolved = resolveOpenAiOptions(options, packer);
3474
+ const resolved2 = resolveOpenAiOptions(options, packer);
3173
3475
  return {
3174
3476
  compile(request, call) {
3175
- return compileOpenAiRequest(request, resolved, call, false);
3477
+ return compileOpenAiRequest(request, resolved2, call, false);
3176
3478
  },
3177
3479
  async run(request, call) {
3178
3480
  const requestId = request.id ?? "openai";
3179
3481
  try {
3180
- return await runOpenAi(request, resolved, call, requestId);
3482
+ return await runOpenAi(request, resolved2, call, requestId);
3181
3483
  } catch (error) {
3182
3484
  if (error instanceof AiDispatcherError && isConfigError2(error.code)) throw error;
3183
3485
  return failedResponse2(request, requestId, error);
3184
3486
  }
3185
3487
  },
3186
3488
  executeStreamingChat(request, call) {
3187
- return streamOpenAi(request, resolved, call);
3489
+ return streamOpenAi(request, resolved2, call);
3188
3490
  }
3189
3491
  };
3190
3492
  }
@@ -3341,7 +3643,7 @@ async function runOpenAi(request, options, call, requestId) {
3341
3643
  let response;
3342
3644
  for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
3343
3645
  response = await sendWithRetries2(active, options, timeoutFor2(request, options));
3344
- const calls = functionCalls(isRecord8(response) ? response : {});
3646
+ const calls = functionCalls(isRecord9(response) ? response : {});
3345
3647
  if (!calls.length) break;
3346
3648
  if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
3347
3649
  return normalizeOpenAiResponse({
@@ -3361,7 +3663,7 @@ async function runOpenAi(request, options, call, requestId) {
3361
3663
  results.push(await executeOpenAiTool(item, request, options));
3362
3664
  }
3363
3665
  functionUsage.push(...results);
3364
- const record2 = isRecord8(response) ? response : {};
3666
+ const record2 = isRecord9(response) ? response : {};
3365
3667
  active = {
3366
3668
  ...active,
3367
3669
  body: clean2({
@@ -3476,7 +3778,7 @@ async function* streamOpenAi(request, options, call) {
3476
3778
  throw failure;
3477
3779
  }
3478
3780
  for await (const event of parseSse2(response)) {
3479
- if (!isRecord8(event)) continue;
3781
+ if (!isRecord9(event)) continue;
3480
3782
  if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
3481
3783
  text += event.delta;
3482
3784
  yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
@@ -3495,7 +3797,7 @@ async function* streamOpenAi(request, options, call) {
3495
3797
  argumentsDelta: event.delta
3496
3798
  }
3497
3799
  };
3498
- } else if (event.type === "response.completed" && isRecord8(event.response)) {
3800
+ } else if (event.type === "response.completed" && isRecord9(event.response)) {
3499
3801
  const usage = normalizeUsage2(event.response.usage);
3500
3802
  if (usage) yield { type: "stream.usage", requestId, data: { usage } };
3501
3803
  } else if (event.type === "error") {
@@ -3589,7 +3891,7 @@ function isConfigError2(code) {
3589
3891
  }
3590
3892
  function asTerminalError2(error) {
3591
3893
  if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
3592
- const status = isRecord8(error.details) ? error.details.status : void 0;
3894
+ const status = isRecord9(error.details) ? error.details.status : void 0;
3593
3895
  if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
3594
3896
  return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
3595
3897
  }
@@ -3597,7 +3899,7 @@ function asTerminalError2(error) {
3597
3899
  return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
3598
3900
  }
3599
3901
  function normalizeOpenAiResponse(params) {
3600
- const record2 = isRecord8(params.response) ? params.response : {};
3902
+ const record2 = isRecord9(params.response) ? params.response : {};
3601
3903
  const text = answerText2(record2);
3602
3904
  const calls = params.functionTools ?? functionCalls(record2);
3603
3905
  const usage = normalizeUsage2(record2.usage);
@@ -3624,9 +3926,9 @@ function answerText2(response) {
3624
3926
  if (Array.isArray(output)) {
3625
3927
  const parts = [];
3626
3928
  for (const item of output) {
3627
- 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;
3628
3930
  for (const part of item.content) {
3629
- 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);
3630
3932
  }
3631
3933
  }
3632
3934
  if (parts.length > 0) return parts.join("");
@@ -3637,7 +3939,7 @@ function functionCalls(response) {
3637
3939
  if (!Array.isArray(response.output)) return [];
3638
3940
  const calls = [];
3639
3941
  for (const item of response.output) {
3640
- if (!isRecord8(item) || item.type !== "function_call") continue;
3942
+ if (!isRecord9(item) || item.type !== "function_call") continue;
3641
3943
  const name = typeof item.name === "string" ? item.name : "function";
3642
3944
  const callId = typeof item.call_id === "string" ? item.call_id : name;
3643
3945
  let args = item.arguments;
@@ -3669,7 +3971,7 @@ function toolUsage(calls) {
3669
3971
  };
3670
3972
  }
3671
3973
  function normalizeUsage2(usage) {
3672
- if (!isRecord8(usage)) return void 0;
3974
+ if (!isRecord9(usage)) return void 0;
3673
3975
  const inputTokens = numberValue2(usage.input_tokens);
3674
3976
  const outputTokens = numberValue2(usage.output_tokens);
3675
3977
  const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
@@ -3721,16 +4023,16 @@ function modelOf2(compiled) {
3721
4023
  return typeof compiled.body.model === "string" ? compiled.body.model : "";
3722
4024
  }
3723
4025
  function messageFrom2(body) {
3724
- if (!isRecord8(body)) return void 0;
4026
+ if (!isRecord9(body)) return void 0;
3725
4027
  if (typeof body.message === "string") return body.message;
3726
- 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;
3727
4029
  return void 0;
3728
4030
  }
3729
4031
  function numberValue2(value) {
3730
4032
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3731
4033
  }
3732
4034
  function attachProviderMetadata(body, request, packer) {
3733
- const fromBody = isRecord8(body.metadata) ? body.metadata : void 0;
4035
+ const fromBody = isRecord9(body.metadata) ? body.metadata : void 0;
3734
4036
  const packed = (0, import_openrouter_runtime2.packProviderRecord)(request.metadata, request.agentId, fromBody, packer);
3735
4037
  if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
3736
4038
  if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
@@ -3804,7 +4106,7 @@ function readEnv3(name) {
3804
4106
  if (typeof process === "undefined") return void 0;
3805
4107
  return process.env[name];
3806
4108
  }
3807
- function isRecord8(value) {
4109
+ function isRecord9(value) {
3808
4110
  return typeof value === "object" && value !== null && !Array.isArray(value);
3809
4111
  }
3810
4112
 
@@ -3986,7 +4288,7 @@ function callOf(call) {
3986
4288
  }
3987
4289
  function attachCompileMeta(prepared, compiled) {
3988
4290
  const record2 = compiled;
3989
- const headers = isRecord9(record2.headers) ? redactHeaders(record2.headers) : void 0;
4291
+ const headers = isRecord10(record2.headers) ? redactHeaders(record2.headers) : void 0;
3990
4292
  const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
3991
4293
  return {
3992
4294
  ...record2,
@@ -4022,7 +4324,7 @@ function log(options, event, prepared, entrypoint, streaming) {
4022
4324
  if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
4023
4325
  else options.logger?.info?.(event, payload);
4024
4326
  }
4025
- function isRecord9(value) {
4327
+ function isRecord10(value) {
4026
4328
  return typeof value === "object" && value !== null && !Array.isArray(value);
4027
4329
  }
4028
4330
 
@@ -4046,6 +4348,7 @@ function stripReasoningHistoryFields(message, paths) {
4046
4348
  normalizeMetadata,
4047
4349
  providerNotImplemented,
4048
4350
  registerHostIntents,
4351
+ resolveAIProfile,
4049
4352
  resolveProvider,
4050
4353
  stripReasoningHistoryFields
4051
4354
  });