@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { registerHostIntents } from "@x12i/ai-profiles";
2
+ import { registerHostIntents, resolveAIProfile } from "@x12i/ai-profiles";
3
3
  import {
4
4
  decodeProviderMetadata,
5
5
  METADATA_NORMALIZATION_MAP,
@@ -123,7 +123,7 @@ async function openTrack(settings, model) {
123
123
  try {
124
124
  tokenCounter = await loadTokenizer(tokenizer);
125
125
  const framing = framingFor(settings);
126
- const resolved = resolveSettings({
126
+ const resolved2 = resolveSettings({
127
127
  files: [],
128
128
  env: {},
129
129
  factory: {
@@ -138,8 +138,8 @@ async function openTrack(settings, model) {
138
138
  });
139
139
  const approvals = settings.approvals;
140
140
  const client = createCoreClient({
141
- config: resolved.config,
142
- explanation: resolved.explanation,
141
+ config: resolved2.config,
142
+ explanation: resolved2.explanation,
143
143
  ...approvals && approvals.length > 0 ? { policy: { approvals: [...approvals] } } : {},
144
144
  ...tokenCounter ? { tokenCounter } : {},
145
145
  warn: () => void 0
@@ -193,10 +193,10 @@ async function encodeRequestText(request, track) {
193
193
  if (typeof request.input === "string") {
194
194
  request.input = await encodeString(track, request.input);
195
195
  } else if (Array.isArray(request.input)) {
196
- for (let index = 0; index < request.input.length; index += 1) {
197
- const item = request.input[index];
196
+ for (let index2 = 0; index2 < request.input.length; index2 += 1) {
197
+ const item = request.input[index2];
198
198
  if (typeof item === "string") {
199
- request.input[index] = await encodeString(track, item);
199
+ request.input[index2] = await encodeString(track, item);
200
200
  } else if (item) {
201
201
  await encodeContentRecord(item, track);
202
202
  }
@@ -393,9 +393,9 @@ function walk(current, tokens) {
393
393
  }
394
394
  if (Array.isArray(current)) {
395
395
  if (!/^\d+$/.test(head)) return [];
396
- const index = Number(head);
397
- if (index >= current.length) return [];
398
- return walk(current[index], rest);
396
+ const index2 = Number(head);
397
+ if (index2 >= current.length) return [];
398
+ return walk(current[index2], rest);
399
399
  }
400
400
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
401
401
  return walk(current[head], rest);
@@ -430,9 +430,9 @@ function collectParents(current, tokens) {
430
430
  }
431
431
  if (Array.isArray(current)) {
432
432
  if (!/^\d+$/.test(head)) return [];
433
- const index = Number(head);
434
- if (index >= current.length) return [];
435
- return collectParents(current[index], rest);
433
+ const index2 = Number(head);
434
+ if (index2 >= current.length) return [];
435
+ return collectParents(current[index2], rest);
436
436
  }
437
437
  if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, head)) return [];
438
438
  return collectParents(current[head], rest);
@@ -446,8 +446,8 @@ function deletePointer(root, pointer) {
446
446
  for (const parent of parents) {
447
447
  if (Array.isArray(parent)) {
448
448
  if (!/^\d+$/.test(last)) continue;
449
- const index = Number(last);
450
- if (index < parent.length) parent.splice(index, 1);
449
+ const index2 = Number(last);
450
+ if (index2 < parent.length) parent.splice(index2, 1);
451
451
  continue;
452
452
  }
453
453
  if (isRecord2(parent) && Object.prototype.hasOwnProperty.call(parent, last)) {
@@ -479,6 +479,38 @@ function extractStreamReasoningText(event, response) {
479
479
  }
480
480
  return firstConcatenatedText(event, response.streamReasoningPaths);
481
481
  }
482
+ function readReportedEffort(root) {
483
+ if (!isRecord3(root)) return void 0;
484
+ const fromRoot = effortOn(root.reasoning) ?? namedEffort(root.reasoning_effort);
485
+ if (fromRoot) return fromRoot;
486
+ if (Array.isArray(root.choices)) {
487
+ for (const choice of root.choices) {
488
+ if (!isRecord3(choice)) continue;
489
+ const message = isRecord3(choice.message) ? choice.message : void 0;
490
+ const fromMessage = message ? effortOn(message.reasoning) ?? namedEffort(message.reasoning_effort) : void 0;
491
+ if (fromMessage) return fromMessage;
492
+ const fromChoice = effortOn(choice.reasoning) ?? namedEffort(choice.reasoning_effort);
493
+ if (fromChoice) return fromChoice;
494
+ }
495
+ }
496
+ if (Array.isArray(root.output)) {
497
+ for (const item of root.output) {
498
+ if (!isRecord3(item) || item.type !== "reasoning") continue;
499
+ const effort = namedEffort(item.effort);
500
+ if (effort) return effort;
501
+ }
502
+ }
503
+ return void 0;
504
+ }
505
+ function effortOn(value) {
506
+ if (!isRecord3(value)) return void 0;
507
+ return namedEffort(value.effort);
508
+ }
509
+ function namedEffort(value) {
510
+ if (typeof value !== "string") return void 0;
511
+ const trimmed = value.trim();
512
+ return trimmed || void 0;
513
+ }
482
514
  function readReasoningTokens(root, paths) {
483
515
  for (const path of paths) {
484
516
  const absolute = firstNumber(root, [path]);
@@ -563,7 +595,10 @@ function splitThinkTags(text) {
563
595
  // src/apply-resolution.ts
564
596
  function applyResolution(response, prepared) {
565
597
  const result = response;
566
- if (prepared.resolution) result.reasoningResolution = prepared.resolution;
598
+ if (prepared.resolution) {
599
+ const reportedEffort = readReportedEffort(response.raw?.response);
600
+ result.reasoningResolution = reportedEffort ? { ...prepared.resolution, reportedEffort } : prepared.resolution;
601
+ }
567
602
  if (prepared.cacheResolution) result.cacheResolution = prepared.cacheResolution;
568
603
  if (prepared.cacheWarnings?.length) {
569
604
  result.warnings = [...result.warnings, ...prepared.cacheWarnings];
@@ -946,9 +981,7 @@ function isRecord5(value) {
946
981
 
947
982
  // src/prepare-request.ts
948
983
  import {
949
- AIProfilesError,
950
- mergeReasoningBody,
951
- resolveReasoning
984
+ mergeReasoningBody
952
985
  } from "@x12i/ai-profiles";
953
986
 
954
987
  // src/cache-apply.ts
@@ -1032,38 +1065,38 @@ function placeBedrockBreakpoint(body, ttl) {
1032
1065
  function markMessages(body, field, markerKey, markerValue, textType) {
1033
1066
  const messages = body[field];
1034
1067
  if (!Array.isArray(messages)) return { body, placed: false };
1035
- const index = stableMessageIndex(messages);
1036
- if (index < 0) return { body, placed: false };
1037
- const marked = markMessage(messages[index], markerKey, markerValue, textType);
1068
+ const index2 = stableMessageIndex(messages);
1069
+ if (index2 < 0) return { body, placed: false };
1070
+ const marked = markMessage(messages[index2], markerKey, markerValue, textType);
1038
1071
  if (!marked) return { body, placed: false };
1039
1072
  const next = messages.slice();
1040
- next[index] = marked;
1073
+ next[index2] = marked;
1041
1074
  return { placed: true, body: { ...body, [field]: next } };
1042
1075
  }
1043
1076
  function markInput(body, markerKey, markerValue, textType) {
1044
1077
  if (!Array.isArray(body.input)) return { body, placed: false };
1045
- const index = stableInputIndex(body.input);
1046
- if (index < 0) return { body, placed: false };
1047
- const marked = markMessage(body.input[index], markerKey, markerValue, textType);
1078
+ const index2 = stableInputIndex(body.input);
1079
+ if (index2 < 0) return { body, placed: false };
1080
+ const marked = markMessage(body.input[index2], markerKey, markerValue, textType);
1048
1081
  if (!marked) return { body, placed: false };
1049
1082
  const next = body.input.slice();
1050
- next[index] = marked;
1083
+ next[index2] = marked;
1051
1084
  return { placed: true, body: { ...body, input: next } };
1052
1085
  }
1053
1086
  function stableMessageIndex(messages) {
1054
1087
  let lastSystem = -1;
1055
- for (let index = 0; index < messages.length; index += 1) {
1056
- const message = messages[index];
1057
- if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index;
1088
+ for (let index2 = 0; index2 < messages.length; index2 += 1) {
1089
+ const message = messages[index2];
1090
+ if (isPlainRecord(message) && message.role === "system" && hasText(message.content)) lastSystem = index2;
1058
1091
  }
1059
1092
  return lastSystem;
1060
1093
  }
1061
1094
  function stableInputIndex(items) {
1062
1095
  let lastStable = -1;
1063
- for (let index = 0; index < items.length; index += 1) {
1064
- const item = items[index];
1096
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
1097
+ const item = items[index2];
1065
1098
  if (!isPlainRecord(item) || !hasText(item.content)) continue;
1066
- if (item.role === "system" || item.role === "developer") lastStable = index;
1099
+ if (item.role === "system" || item.role === "developer") lastStable = index2;
1067
1100
  }
1068
1101
  return lastStable;
1069
1102
  }
@@ -1080,10 +1113,10 @@ function markContent(content, markerKey, markerValue, textType) {
1080
1113
  }
1081
1114
  if (!Array.isArray(content)) return void 0;
1082
1115
  const parts = content.map((part) => isPlainRecord(part) ? { ...part } : part);
1083
- for (let index = parts.length - 1; index >= 0; index -= 1) {
1084
- const part = parts[index];
1116
+ for (let index2 = parts.length - 1; index2 >= 0; index2 -= 1) {
1117
+ const part = parts[index2];
1085
1118
  if (!isPlainRecord(part) || typeof part.text !== "string" || !part.text.trim()) continue;
1086
- parts[index] = { ...part, [markerKey]: markerValue };
1119
+ parts[index2] = { ...part, [markerKey]: markerValue };
1087
1120
  return parts;
1088
1121
  }
1089
1122
  return void 0;
@@ -1239,11 +1272,11 @@ function resolveCloudflarePromptCache(args) {
1239
1272
  warnings: []
1240
1273
  };
1241
1274
  }
1242
- const resolved = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1243
- const ttl = resolved.plan.ttl ?? requestedTtl;
1275
+ const resolved2 = applyProfile({ input, mode, key, requestedTtl, resolutionBase, model, profile });
1276
+ const ttl = resolved2.plan.ttl ?? requestedTtl;
1244
1277
  return {
1245
- ...resolved,
1246
- plan: { ...resolved.plan, gateway: gatewayDirective(false, ttl, key) }
1278
+ ...resolved2,
1279
+ plan: { ...resolved2.plan, gateway: gatewayDirective(false, ttl, key) }
1247
1280
  };
1248
1281
  }
1249
1282
  function applyProfile(args) {
@@ -1450,22 +1483,22 @@ function restoreCloudflareModel(model, prefix) {
1450
1483
  return `${prefix}${model}`;
1451
1484
  }
1452
1485
  function createCloudflareProviderAdapter(options = {}, packer) {
1453
- const resolved = resolveCloudflareOptions(options, packer);
1486
+ const resolved2 = resolveCloudflareOptions(options, packer);
1454
1487
  return {
1455
1488
  compile(request, call) {
1456
- return compileCloudflareRequest(request, resolved, call, false);
1489
+ return compileCloudflareRequest(request, resolved2, call, false);
1457
1490
  },
1458
1491
  async run(request, call) {
1459
1492
  const requestId = request.id ?? "cloudflare";
1460
1493
  try {
1461
- return await runCloudflare(request, resolved, call, requestId);
1494
+ return await runCloudflare(request, resolved2, call, requestId);
1462
1495
  } catch (error) {
1463
1496
  if (error instanceof AiDispatcherError && isConfigError(error.code)) throw error;
1464
1497
  return failedResponse(request, requestId, error, "chat");
1465
1498
  }
1466
1499
  },
1467
1500
  executeStreamingChat(request, call) {
1468
- return streamCloudflare(request, resolved, call);
1501
+ return streamCloudflare(request, resolved2, call);
1469
1502
  }
1470
1503
  };
1471
1504
  }
@@ -2613,6 +2646,261 @@ function isRecord6(value) {
2613
2646
  return typeof value === "object" && value !== null && !Array.isArray(value);
2614
2647
  }
2615
2648
 
2649
+ // src/resolve-effort.ts
2650
+ import {
2651
+ AIProfilesError,
2652
+ resolveReasoning
2653
+ } from "@x12i/ai-profiles";
2654
+
2655
+ // src/catalog-efforts.ts
2656
+ import { createRequire } from "module";
2657
+ import { readFileSync } from "fs";
2658
+ import { dirname, join } from "path";
2659
+ import { loadBundledModelsCatalog } from "@x12i/ai-profiles";
2660
+ var index;
2661
+ function normalize(value) {
2662
+ return value.trim().toLowerCase();
2663
+ }
2664
+ function addKey(keys, key, row) {
2665
+ const normalized = normalize(key);
2666
+ if (!normalized) return;
2667
+ const existing = keys.get(normalized);
2668
+ if (!existing) {
2669
+ keys.set(normalized, row);
2670
+ return;
2671
+ }
2672
+ if (existing === "ambiguous") return;
2673
+ if (existing.modelId !== row.modelId) keys.set(normalized, "ambiguous");
2674
+ }
2675
+ function loadAssignmentAliases() {
2676
+ const require2 = createRequire(import.meta.url);
2677
+ const entry = require2.resolve("@x12i/ai-profiles");
2678
+ const path = join(dirname(entry), "data", "reasoning-directives.json");
2679
+ const document = JSON.parse(readFileSync(path, "utf8"));
2680
+ return document.assignments ?? [];
2681
+ }
2682
+ function effortList(entry) {
2683
+ const reasoning = entry.openrouter.reasoning;
2684
+ const modelId = entry.openrouter.modelId;
2685
+ if (!reasoning || !Object.prototype.hasOwnProperty.call(reasoning, "supportedEfforts")) {
2686
+ return { kind: "unspecified", modelId };
2687
+ }
2688
+ if (reasoning.supportedEfforts === null) return { kind: "any", modelId };
2689
+ if (Array.isArray(reasoning.supportedEfforts)) {
2690
+ return { kind: "listed", modelId, efforts: reasoning.supportedEfforts };
2691
+ }
2692
+ return { kind: "unspecified", modelId };
2693
+ }
2694
+ function buildIndex() {
2695
+ const catalog = loadBundledModelsCatalog();
2696
+ const keys = /* @__PURE__ */ new Map();
2697
+ const byCatalogId = /* @__PURE__ */ new Map();
2698
+ for (const entry of catalog.models) {
2699
+ const row = effortList(entry);
2700
+ byCatalogId.set(normalize(entry.openrouter.modelId), row);
2701
+ addKey(keys, entry.openrouter.modelId, row);
2702
+ if (entry.openrouter.canonicalSlug) addKey(keys, entry.openrouter.canonicalSlug, row);
2703
+ addKey(keys, `${entry.direct.provider}/${entry.direct.modelId}`, row);
2704
+ addKey(keys, `openrouter/${entry.openrouter.modelId}`, row);
2705
+ addKey(keys, entry.direct.modelId, row);
2706
+ }
2707
+ for (const assignment of loadAssignmentAliases()) {
2708
+ const row = byCatalogId.get(normalize(assignment.catalogModelId));
2709
+ if (!row) continue;
2710
+ for (const alias of assignment.aliases ?? []) addKey(keys, alias, row);
2711
+ }
2712
+ return keys;
2713
+ }
2714
+ function lookupCatalogEfforts(model) {
2715
+ if (!index) index = buildIndex();
2716
+ const direct = index.get(normalize(model));
2717
+ if (direct && direct !== "ambiguous") return direct;
2718
+ const gateway = normalize(model);
2719
+ if (gateway.startsWith("openrouter/")) {
2720
+ const stripped = index.get(gateway.slice("openrouter/".length));
2721
+ if (stripped && stripped !== "ambiguous") return stripped;
2722
+ }
2723
+ return void 0;
2724
+ }
2725
+
2726
+ // src/resolve-effort.ts
2727
+ var EMPTY_RESPONSE = {
2728
+ reasoningTextPaths: [],
2729
+ streamReasoningKeys: [],
2730
+ streamReasoningPaths: [],
2731
+ stripFromHistoryPaths: [],
2732
+ stripThinkTags: false
2733
+ };
2734
+ var MAPPER_EFFORTS = ["high", "xhigh", "max", "medium", "low", "minimal"];
2735
+ function isRecord7(value) {
2736
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2737
+ }
2738
+ function readBodyEffort(body) {
2739
+ const reasoning = body.reasoning;
2740
+ if (isRecord7(reasoning) && typeof reasoning.effort === "string") return reasoning.effort;
2741
+ const fields = body.additionalModelRequestFields;
2742
+ if (!isRecord7(fields)) return void 0;
2743
+ const output = fields.output_config;
2744
+ if (isRecord7(output) && typeof output.effort === "string") return output.effort;
2745
+ return void 0;
2746
+ }
2747
+ function writeBodyEffort(body, effort) {
2748
+ const reasoning = body.reasoning;
2749
+ if (isRecord7(reasoning) && typeof reasoning.effort === "string") {
2750
+ reasoning.effort = effort;
2751
+ return true;
2752
+ }
2753
+ const fields = body.additionalModelRequestFields;
2754
+ if (!isRecord7(fields)) return false;
2755
+ const output = fields.output_config;
2756
+ if (isRecord7(output) && typeof output.effort === "string") {
2757
+ output.effort = effort;
2758
+ return true;
2759
+ }
2760
+ return false;
2761
+ }
2762
+ function unsupported(model, effort, supported) {
2763
+ return new AiDispatcherError(
2764
+ "EFFORT_UNSUPPORTED",
2765
+ `Model '${model}' does not accept reasoning effort '${effort}'. Supported efforts: ${supported.join(", ")}.`,
2766
+ { model, effort, supportedEfforts: [...supported] }
2767
+ );
2768
+ }
2769
+ function ignoredDirective() {
2770
+ return {
2771
+ outcome: "ignored",
2772
+ body: {},
2773
+ response: {
2774
+ reasoningTextPaths: [...EMPTY_RESPONSE.reasoningTextPaths],
2775
+ streamReasoningKeys: [...EMPTY_RESPONSE.streamReasoningKeys],
2776
+ streamReasoningPaths: [...EMPTY_RESPONSE.streamReasoningPaths],
2777
+ stripFromHistoryPaths: [...EMPTY_RESPONSE.stripFromHistoryPaths],
2778
+ stripThinkTags: EMPTY_RESPONSE.stripThinkTags
2779
+ }
2780
+ };
2781
+ }
2782
+ function toDispatcherError(error) {
2783
+ return new AiDispatcherError(error.code, error.message, error.details);
2784
+ }
2785
+ function resolveMapping(input, ignoreUnknown) {
2786
+ try {
2787
+ return resolveReasoning({
2788
+ target: input.target,
2789
+ model: input.model,
2790
+ effort: input.effort
2791
+ });
2792
+ } catch (error) {
2793
+ if (!(error instanceof AIProfilesError)) throw error;
2794
+ if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2795
+ return ignoredDirective();
2796
+ }
2797
+ if (error.code === "INVALID_EFFORT") return "bad-key";
2798
+ if (error.code === "REASONING_MAPPING_INVALID" && error.message.includes("missing effort")) return "bad-key";
2799
+ throw toDispatcherError(error);
2800
+ }
2801
+ }
2802
+ function mappingOrThrow(input, ignoreUnknown) {
2803
+ const mapped = resolveMapping(input, ignoreUnknown);
2804
+ if (mapped === "bad-key") {
2805
+ throw new AiDispatcherError(
2806
+ "INVALID_EFFORT",
2807
+ `Invalid reasoning effort '${input.effort}'.`,
2808
+ { target: input.target, effort: input.effort, model: input.model }
2809
+ );
2810
+ }
2811
+ return mapped;
2812
+ }
2813
+ function sendAsWritten(directive, effort, target) {
2814
+ if (directive.outcome === "ignored") return directive;
2815
+ const copy = structuredClone(directive);
2816
+ const wrote = writeBodyEffort(copy.body, effort);
2817
+ if (!wrote && target !== "bedrock") {
2818
+ const reasoning = isRecord7(copy.body.reasoning) ? copy.body.reasoning : {};
2819
+ copy.body = { ...copy.body, reasoning: { ...reasoning, effort } };
2820
+ }
2821
+ copy.outcome = "applied";
2822
+ return copy;
2823
+ }
2824
+ function directiveForListedEffort(input) {
2825
+ const direct = resolveMapping(input, input.ignoreUnknown);
2826
+ if (direct !== "bad-key") {
2827
+ if (direct.outcome === "ignored") return direct;
2828
+ const sent = readBodyEffort(direct.body);
2829
+ if (sent === input.effort) {
2830
+ if (direct.outcome === "degraded") {
2831
+ const copy = structuredClone(direct);
2832
+ copy.outcome = "applied";
2833
+ return copy;
2834
+ }
2835
+ return direct;
2836
+ }
2837
+ if (sent) return sendAsWritten(direct, input.effort, input.target);
2838
+ }
2839
+ const proxies = MAPPER_EFFORTS.filter((candidate) => {
2840
+ if (candidate === input.effort) return false;
2841
+ if (input.supported === null) return true;
2842
+ return input.supported.includes(candidate);
2843
+ });
2844
+ for (const proxy of proxies) {
2845
+ const mapped = resolveMapping({ ...input, effort: proxy }, input.ignoreUnknown);
2846
+ if (mapped === "bad-key" || mapped.outcome === "ignored") continue;
2847
+ if (!readBodyEffort(mapped.body)) continue;
2848
+ return sendAsWritten(mapped, input.effort, input.target);
2849
+ }
2850
+ return mappingOrThrow(input, input.ignoreUnknown);
2851
+ }
2852
+ function sentOf(directive) {
2853
+ if (directive.outcome === "ignored") return void 0;
2854
+ return readBodyEffort(directive.body);
2855
+ }
2856
+ function resolved(directive) {
2857
+ const sentEffort = sentOf(directive);
2858
+ return sentEffort ? { directive, sentEffort } : { directive };
2859
+ }
2860
+ function resolveEffortDirective(input) {
2861
+ const effort = input.effort.trim();
2862
+ const row = lookupCatalogEfforts(input.model);
2863
+ if (!row) {
2864
+ return resolved(mappingOrThrow(input, input.ignoreUnknown));
2865
+ }
2866
+ if (row.kind === "unspecified") {
2867
+ const directive2 = mappingOrThrow({ ...input, effort }, input.ignoreUnknown);
2868
+ const sent = sentOf(directive2);
2869
+ if (!input.degrade && directive2.outcome === "degraded" && sent && sent !== effort) {
2870
+ throw new AiDispatcherError(
2871
+ "EFFORT_UNSUPPORTED",
2872
+ `Model '${input.model}' does not accept reasoning effort '${effort}' unless degradeReasoningEffort is set.`,
2873
+ { model: input.model, effort, catalogModelId: row.modelId }
2874
+ );
2875
+ }
2876
+ return resolved(directive2);
2877
+ }
2878
+ const supported = row.kind === "listed" ? row.efforts : null;
2879
+ const allowed = supported === null || supported.includes(effort);
2880
+ if (!allowed && supported) {
2881
+ if (!input.degrade) throw unsupported(input.model, effort, supported);
2882
+ const mapped = resolveMapping({ ...input, effort }, input.ignoreUnknown);
2883
+ if (mapped === "bad-key") throw unsupported(input.model, effort, supported);
2884
+ if (mapped.outcome === "ignored") return { directive: mapped };
2885
+ const sent = readBodyEffort(mapped.body);
2886
+ if (!sent || !supported.includes(sent)) throw unsupported(input.model, effort, supported);
2887
+ if (sent !== effort) {
2888
+ const copy = structuredClone(mapped);
2889
+ copy.outcome = "degraded";
2890
+ return { directive: copy, sentEffort: sent };
2891
+ }
2892
+ return { directive: mapped, sentEffort: sent };
2893
+ }
2894
+ const directive = directiveForListedEffort({
2895
+ target: input.target,
2896
+ model: input.model,
2897
+ effort,
2898
+ ignoreUnknown: input.ignoreUnknown,
2899
+ supported
2900
+ });
2901
+ return resolved(directive);
2902
+ }
2903
+
2616
2904
  // src/prepare-request.ts
2617
2905
  function prepareDispatchRequest(params) {
2618
2906
  const cloned = cloneRequest(params.request);
@@ -2622,11 +2910,13 @@ function prepareDispatchRequest(params) {
2622
2910
  applyDispatchGate(cloned);
2623
2911
  const provider = params.provider;
2624
2912
  const reasoningEffort = cloned.reasoningEffort;
2913
+ const degradeReasoningEffort = cloned.degradeReasoningEffort === true;
2625
2914
  const bedrockControls = cloned.bedrockControls;
2626
2915
  const cachePolicy = cloned.cache;
2627
2916
  const cloudflareRequest = cloned.cloudflare;
2628
2917
  delete cloned.provider;
2629
2918
  delete cloned.reasoningEffort;
2919
+ delete cloned.degradeReasoningEffort;
2630
2920
  delete cloned.bedrockControls;
2631
2921
  delete cloned.cache;
2632
2922
  delete cloned.cloudflare;
@@ -2644,6 +2934,7 @@ function prepareDispatchRequest(params) {
2644
2934
  const requestedModel = cloned.model ?? defaultModelFor(provider, params.options) ?? "";
2645
2935
  const explicit = hasExplicitControl(provider, cloned, bedrockControls);
2646
2936
  let directive;
2937
+ let sentEffort;
2647
2938
  let catalogPrefix;
2648
2939
  if (!explicit && reasoningEffort !== void 0) {
2649
2940
  if (!requestedModel) {
@@ -2655,13 +2946,29 @@ function prepareDispatchRequest(params) {
2655
2946
  if (provider === "cloudflare") {
2656
2947
  const query = cloudflareCatalogQuery(requestedModel);
2657
2948
  catalogPrefix = query.prefix;
2658
- directive = resolveCatalog({ target: query.target, model: query.model, effort: reasoningEffort }, true);
2949
+ const resolved2 = resolveEffortDirective({
2950
+ target: query.target,
2951
+ model: query.model,
2952
+ effort: reasoningEffort,
2953
+ ignoreUnknown: true,
2954
+ degrade: degradeReasoningEffort
2955
+ });
2956
+ directive = resolved2.directive;
2957
+ sentEffort = resolved2.sentEffort;
2659
2958
  } else if (isReasoningTarget(provider)) {
2660
- directive = resolveCatalog({ target: provider, model: requestedModel, effort: reasoningEffort }, false);
2959
+ const resolved2 = resolveEffortDirective({
2960
+ target: provider,
2961
+ model: requestedModel,
2962
+ effort: reasoningEffort,
2963
+ ignoreUnknown: false,
2964
+ degrade: degradeReasoningEffort
2965
+ });
2966
+ directive = resolved2.directive;
2967
+ sentEffort = resolved2.sentEffort;
2661
2968
  }
2662
2969
  }
2663
2970
  if (provider === "openrouter") {
2664
- applyNestedReasoning(cloned, reasoningEffort);
2971
+ applyNestedReasoning(cloned, reasoningEffort, degradeReasoningEffort);
2665
2972
  }
2666
2973
  if (directive?.model) {
2667
2974
  cloned.model = provider === "cloudflare" ? restoreCloudflareModel(directive.model, catalogPrefix) : directive.model;
@@ -2722,6 +3029,7 @@ function prepareDispatchRequest(params) {
2722
3029
  }
2723
3030
  const resolution = reasoningEffort !== void 0 || explicit ? buildResolution({
2724
3031
  requestedEffort: reasoningEffort,
3032
+ sentEffort: explicit ? explicitSentEffort(provider, cloned, bedrockControls) : sentEffort,
2725
3033
  target: provider,
2726
3034
  requestedModel,
2727
3035
  effectiveModel,
@@ -2754,29 +3062,6 @@ function noteUnplacedCache(resolution, warnings, target, model) {
2754
3062
  details: { target, model }
2755
3063
  });
2756
3064
  }
2757
- function resolveCatalog(input, ignoreUnknown) {
2758
- try {
2759
- return resolveReasoning(input);
2760
- } catch (error) {
2761
- if (error instanceof AIProfilesError) {
2762
- if (ignoreUnknown && (error.code === "UNKNOWN_MODEL" || error.code === "REASONING_MAPPING_MISSING")) {
2763
- return {
2764
- outcome: "ignored",
2765
- body: {},
2766
- response: {
2767
- reasoningTextPaths: [],
2768
- streamReasoningKeys: [],
2769
- streamReasoningPaths: [],
2770
- stripFromHistoryPaths: [],
2771
- stripThinkTags: false
2772
- }
2773
- };
2774
- }
2775
- throw new AiDispatcherError(error.code, error.message, error.details);
2776
- }
2777
- throw error;
2778
- }
2779
- }
2780
3065
  function isReasoningTarget(provider) {
2781
3066
  return provider === "openrouter" || provider === "bedrock" || provider === "openai";
2782
3067
  }
@@ -2784,6 +3069,15 @@ function hasExplicitControl(provider, request, bedrockControls) {
2784
3069
  if (provider === "bedrock") return bedrockControls?.additionalModelRequestFields !== void 0;
2785
3070
  return request.reasoning !== void 0;
2786
3071
  }
3072
+ function explicitSentEffort(provider, request, bedrockControls) {
3073
+ if (provider === "bedrock") {
3074
+ const output = bedrockControls?.additionalModelRequestFields?.output_config;
3075
+ if (isRecord8(output) && typeof output.effort === "string") return output.effort;
3076
+ return void 0;
3077
+ }
3078
+ if (isRecord8(request.reasoning) && typeof request.reasoning.effort === "string") return request.reasoning.effort;
3079
+ return void 0;
3080
+ }
2787
3081
  function buildResolution(params) {
2788
3082
  return {
2789
3083
  target: params.target,
@@ -2791,19 +3085,20 @@ function buildResolution(params) {
2791
3085
  effectiveModel: params.effectiveModel,
2792
3086
  bypassed: params.bypassed,
2793
3087
  ...params.requestedEffort ? { requestedEffort: params.requestedEffort } : {},
3088
+ ...params.sentEffort ? { sentEffort: params.sentEffort } : {},
2794
3089
  ...params.outcome ? { outcome: params.outcome } : {}
2795
3090
  };
2796
3091
  }
2797
- function applyNestedReasoning(request, parentEffort) {
3092
+ function applyNestedReasoning(request, parentEffort, degrade) {
2798
3093
  const serverTools = request.serverTools;
2799
3094
  if (!serverTools) return;
2800
- if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort);
3095
+ if (serverTools.fusion) serverTools.fusion = settleNested(serverTools.fusion, "fusion", parentEffort, degrade);
2801
3096
  if (serverTools.advisor) {
2802
- serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort)) : settleNested(serverTools.advisor, "advisor", parentEffort);
3097
+ serverTools.advisor = Array.isArray(serverTools.advisor) ? serverTools.advisor.map((advisor) => settleNested(advisor, "advisor", parentEffort, degrade)) : settleNested(serverTools.advisor, "advisor", parentEffort, degrade);
2803
3098
  }
2804
- if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort);
3099
+ if (serverTools.subagent) serverTools.subagent = settleNested(serverTools.subagent, "subagent", parentEffort, degrade);
2805
3100
  }
2806
- function settleNested(policy, kind, parentEffort) {
3101
+ function settleNested(policy, kind, parentEffort, degrade) {
2807
3102
  const current = policy;
2808
3103
  const childEffort = current.reasoningEffort;
2809
3104
  const next = { ...current };
@@ -2813,12 +3108,18 @@ function settleNested(policy, kind, parentEffort) {
2813
3108
  if (!effort) return next;
2814
3109
  const model = kind === "fusion" ? next.judgeModel : next.model;
2815
3110
  if (!model) return next;
2816
- const directive = resolveCatalog({ target: "openrouter", model, effort }, false);
3111
+ const directive = resolveEffortDirective({
3112
+ target: "openrouter",
3113
+ model,
3114
+ effort,
3115
+ ignoreUnknown: false,
3116
+ degrade
3117
+ }).directive;
2817
3118
  if (directive.model) {
2818
3119
  if (kind === "fusion") next.judgeModel = directive.model;
2819
3120
  else next.model = directive.model;
2820
3121
  }
2821
- if (isRecord7(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
3122
+ if (isRecord8(directive.body.reasoning)) next.reasoning = directive.body.reasoning;
2822
3123
  return next;
2823
3124
  }
2824
3125
  function applyPromptPrefix(request, prefix) {
@@ -2855,10 +3156,10 @@ function prefixMessages(messages, prefix) {
2855
3156
  return false;
2856
3157
  }
2857
3158
  function prefixInputItems(items, prefix) {
2858
- for (let index = 0; index < items.length; index += 1) {
2859
- const item = items[index];
3159
+ for (let index2 = 0; index2 < items.length; index2 += 1) {
3160
+ const item = items[index2];
2860
3161
  if (typeof item === "string") {
2861
- items[index] = prefix + item;
3162
+ items[index2] = prefix + item;
2862
3163
  return true;
2863
3164
  }
2864
3165
  if (!item || item.role === "system") continue;
@@ -2868,7 +3169,7 @@ function prefixInputItems(items, prefix) {
2868
3169
  }
2869
3170
  if (!Array.isArray(item.content)) continue;
2870
3171
  for (const part of item.content) {
2871
- if (!isRecord7(part) || typeof part.text !== "string") continue;
3172
+ if (!isRecord8(part) || typeof part.text !== "string") continue;
2872
3173
  if (part.type === "text" || part.type === "input_text") {
2873
3174
  part.text = prefix + part.text;
2874
3175
  return true;
@@ -2929,7 +3230,7 @@ var ROLE_STAMP_FIELDS = ["role", "roleLayer", "roleDigest", "profileChoice"];
2929
3230
  function applyRoleStamp(request) {
2930
3231
  const stamp = request.role;
2931
3232
  delete request.role;
2932
- if (!isRecord7(stamp)) return;
3233
+ if (!isRecord8(stamp)) return;
2933
3234
  const identity = {};
2934
3235
  for (const field of ROLE_STAMP_FIELDS) {
2935
3236
  const value = stamp[field];
@@ -2944,8 +3245,8 @@ function applyRoleStamp(request) {
2944
3245
  }
2945
3246
  function absorbOverrideMetadata(request) {
2946
3247
  const overrides = request.rawOpenRouterOverrides;
2947
- if (!isRecord7(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
2948
- if (isRecord7(overrides.metadata)) {
3248
+ if (!isRecord8(overrides) || !Object.prototype.hasOwnProperty.call(overrides, "metadata")) return;
3249
+ if (isRecord8(overrides.metadata)) {
2949
3250
  const current = { ...request.metadata ?? {} };
2950
3251
  const extra = {};
2951
3252
  for (const [key, value] of Object.entries(overrides.metadata)) {
@@ -2990,7 +3291,7 @@ function hasOwnKeys(value) {
2990
3291
  function hasStreamMetadata(directive) {
2991
3292
  return directive.response.streamReasoningKeys.length > 0 || directive.response.streamReasoningPaths.length > 0;
2992
3293
  }
2993
- function isRecord7(value) {
3294
+ function isRecord8(value) {
2994
3295
  return typeof value === "object" && value !== null && !Array.isArray(value);
2995
3296
  }
2996
3297
  function readEnv2(name) {
@@ -3141,22 +3442,22 @@ import {
3141
3442
  var DEFAULT_BASE_URL2 = "https://api.openai.com/v1";
3142
3443
  var DEFAULT_TIMEOUT_MS2 = 12e4;
3143
3444
  function createOpenAiProviderAdapter(options = {}, packer) {
3144
- const resolved = resolveOpenAiOptions(options, packer);
3445
+ const resolved2 = resolveOpenAiOptions(options, packer);
3145
3446
  return {
3146
3447
  compile(request, call) {
3147
- return compileOpenAiRequest(request, resolved, call, false);
3448
+ return compileOpenAiRequest(request, resolved2, call, false);
3148
3449
  },
3149
3450
  async run(request, call) {
3150
3451
  const requestId = request.id ?? "openai";
3151
3452
  try {
3152
- return await runOpenAi(request, resolved, call, requestId);
3453
+ return await runOpenAi(request, resolved2, call, requestId);
3153
3454
  } catch (error) {
3154
3455
  if (error instanceof AiDispatcherError && isConfigError2(error.code)) throw error;
3155
3456
  return failedResponse2(request, requestId, error);
3156
3457
  }
3157
3458
  },
3158
3459
  executeStreamingChat(request, call) {
3159
- return streamOpenAi(request, resolved, call);
3460
+ return streamOpenAi(request, resolved2, call);
3160
3461
  }
3161
3462
  };
3162
3463
  }
@@ -3313,7 +3614,7 @@ async function runOpenAi(request, options, call, requestId) {
3313
3614
  let response;
3314
3615
  for (let iteration = 0; iteration <= maxIterations; iteration += 1) {
3315
3616
  response = await sendWithRetries2(active, options, timeoutFor2(request, options));
3316
- const calls = functionCalls(isRecord8(response) ? response : {});
3617
+ const calls = functionCalls(isRecord9(response) ? response : {});
3317
3618
  if (!calls.length) break;
3318
3619
  if (!calls.every((item) => resolveExecutor2(item.name, request, options))) {
3319
3620
  return normalizeOpenAiResponse({
@@ -3333,7 +3634,7 @@ async function runOpenAi(request, options, call, requestId) {
3333
3634
  results.push(await executeOpenAiTool(item, request, options));
3334
3635
  }
3335
3636
  functionUsage.push(...results);
3336
- const record2 = isRecord8(response) ? response : {};
3637
+ const record2 = isRecord9(response) ? response : {};
3337
3638
  active = {
3338
3639
  ...active,
3339
3640
  body: clean2({
@@ -3448,7 +3749,7 @@ async function* streamOpenAi(request, options, call) {
3448
3749
  throw failure;
3449
3750
  }
3450
3751
  for await (const event of parseSse2(response)) {
3451
- if (!isRecord8(event)) continue;
3752
+ if (!isRecord9(event)) continue;
3452
3753
  if (event.type === "response.output_text.delta" && typeof event.delta === "string" && event.delta) {
3453
3754
  text += event.delta;
3454
3755
  yield { type: "stream.text.delta", requestId, data: { text: event.delta } };
@@ -3467,7 +3768,7 @@ async function* streamOpenAi(request, options, call) {
3467
3768
  argumentsDelta: event.delta
3468
3769
  }
3469
3770
  };
3470
- } else if (event.type === "response.completed" && isRecord8(event.response)) {
3771
+ } else if (event.type === "response.completed" && isRecord9(event.response)) {
3471
3772
  const usage = normalizeUsage2(event.response.usage);
3472
3773
  if (usage) yield { type: "stream.usage", requestId, data: { usage } };
3473
3774
  } else if (event.type === "error") {
@@ -3561,7 +3862,7 @@ function isConfigError2(code) {
3561
3862
  }
3562
3863
  function asTerminalError2(error) {
3563
3864
  if (error instanceof AiDispatcherError && error.code === "PROVIDER_RETRYABLE") {
3564
- const status = isRecord8(error.details) ? error.details.status : void 0;
3865
+ const status = isRecord9(error.details) ? error.details.status : void 0;
3565
3866
  if (status === 429) return new AiDispatcherError("PROVIDER_RATE_LIMITED", error.message, error.details);
3566
3867
  return new AiDispatcherError("PROVIDER_GATEWAY_UNREACHABLE", error.message, error.details);
3567
3868
  }
@@ -3569,7 +3870,7 @@ function asTerminalError2(error) {
3569
3870
  return new AiDispatcherError("PROVIDER_REQUEST_FAILED", error instanceof Error ? error.message : String(error));
3570
3871
  }
3571
3872
  function normalizeOpenAiResponse(params) {
3572
- const record2 = isRecord8(params.response) ? params.response : {};
3873
+ const record2 = isRecord9(params.response) ? params.response : {};
3573
3874
  const text = answerText2(record2);
3574
3875
  const calls = params.functionTools ?? functionCalls(record2);
3575
3876
  const usage = normalizeUsage2(record2.usage);
@@ -3596,9 +3897,9 @@ function answerText2(response) {
3596
3897
  if (Array.isArray(output)) {
3597
3898
  const parts = [];
3598
3899
  for (const item of output) {
3599
- if (!isRecord8(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3900
+ if (!isRecord9(item) || item.type === "reasoning" || item.type !== "message" || !Array.isArray(item.content)) continue;
3600
3901
  for (const part of item.content) {
3601
- if (isRecord8(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3902
+ if (isRecord9(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
3602
3903
  }
3603
3904
  }
3604
3905
  if (parts.length > 0) return parts.join("");
@@ -3609,7 +3910,7 @@ function functionCalls(response) {
3609
3910
  if (!Array.isArray(response.output)) return [];
3610
3911
  const calls = [];
3611
3912
  for (const item of response.output) {
3612
- if (!isRecord8(item) || item.type !== "function_call") continue;
3913
+ if (!isRecord9(item) || item.type !== "function_call") continue;
3613
3914
  const name = typeof item.name === "string" ? item.name : "function";
3614
3915
  const callId = typeof item.call_id === "string" ? item.call_id : name;
3615
3916
  let args = item.arguments;
@@ -3641,7 +3942,7 @@ function toolUsage(calls) {
3641
3942
  };
3642
3943
  }
3643
3944
  function normalizeUsage2(usage) {
3644
- if (!isRecord8(usage)) return void 0;
3945
+ if (!isRecord9(usage)) return void 0;
3645
3946
  const inputTokens = numberValue2(usage.input_tokens);
3646
3947
  const outputTokens = numberValue2(usage.output_tokens);
3647
3948
  const totalTokens = numberValue2(usage.total_tokens) ?? (inputTokens !== void 0 || outputTokens !== void 0 ? (inputTokens ?? 0) + (outputTokens ?? 0) : void 0);
@@ -3693,16 +3994,16 @@ function modelOf2(compiled) {
3693
3994
  return typeof compiled.body.model === "string" ? compiled.body.model : "";
3694
3995
  }
3695
3996
  function messageFrom2(body) {
3696
- if (!isRecord8(body)) return void 0;
3997
+ if (!isRecord9(body)) return void 0;
3697
3998
  if (typeof body.message === "string") return body.message;
3698
- if (isRecord8(body.error) && typeof body.error.message === "string") return body.error.message;
3999
+ if (isRecord9(body.error) && typeof body.error.message === "string") return body.error.message;
3699
4000
  return void 0;
3700
4001
  }
3701
4002
  function numberValue2(value) {
3702
4003
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3703
4004
  }
3704
4005
  function attachProviderMetadata(body, request, packer) {
3705
- const fromBody = isRecord8(body.metadata) ? body.metadata : void 0;
4006
+ const fromBody = isRecord9(body.metadata) ? body.metadata : void 0;
3706
4007
  const packed = packProviderRecord2(request.metadata, request.agentId, fromBody, packer);
3707
4008
  if (packed.wire) return { body: { ...body, metadata: packed.wire }, ...packed.warning ? { warning: packed.warning } : {} };
3708
4009
  if (body.metadata === void 0) return { body, ...packed.warning ? { warning: packed.warning } : {} };
@@ -3776,7 +4077,7 @@ function readEnv3(name) {
3776
4077
  if (typeof process === "undefined") return void 0;
3777
4078
  return process.env[name];
3778
4079
  }
3779
- function isRecord8(value) {
4080
+ function isRecord9(value) {
3780
4081
  return typeof value === "object" && value !== null && !Array.isArray(value);
3781
4082
  }
3782
4083
 
@@ -3960,7 +4261,7 @@ function callOf(call) {
3960
4261
  }
3961
4262
  function attachCompileMeta(prepared, compiled) {
3962
4263
  const record2 = compiled;
3963
- const headers = isRecord9(record2.headers) ? redactHeaders(record2.headers) : void 0;
4264
+ const headers = isRecord10(record2.headers) ? redactHeaders(record2.headers) : void 0;
3964
4265
  const inputResolution = prepared.inputTrack ? visibleInputResolution(prepared.inputTrack) : void 0;
3965
4266
  return {
3966
4267
  ...record2,
@@ -3996,7 +4297,7 @@ function log(options, event, prepared, entrypoint, streaming) {
3996
4297
  if (event.endsWith("compiled")) options.logger?.debug?.(event, payload);
3997
4298
  else options.logger?.info?.(event, payload);
3998
4299
  }
3999
- function isRecord9(value) {
4300
+ function isRecord10(value) {
4000
4301
  return typeof value === "object" && value !== null && !Array.isArray(value);
4001
4302
  }
4002
4303
 
@@ -4019,6 +4320,7 @@ export {
4019
4320
  normalizeMetadata,
4020
4321
  providerNotImplemented,
4021
4322
  registerHostIntents,
4323
+ resolveAIProfile,
4022
4324
  resolveProvider,
4023
4325
  stripReasoningHistoryFields
4024
4326
  };