@pentoshi/clai 4.2.1 → 4.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.
Files changed (164) hide show
  1. package/README.md +14 -16
  2. package/dist/agent/compaction-summary.d.ts +1 -1
  3. package/dist/agent/compaction-summary.js +1 -1
  4. package/dist/agent/compaction-summary.js.map +1 -1
  5. package/dist/agent/runner.js +4 -4
  6. package/dist/agent/runner.js.map +1 -1
  7. package/dist/agent/tool-output-formatting.d.ts +6 -0
  8. package/dist/agent/tool-output-formatting.js +126 -40
  9. package/dist/agent/tool-output-formatting.js.map +1 -1
  10. package/dist/app/adapters/agent-event-adapter.d.ts +4 -0
  11. package/dist/app/adapters/agent-event-adapter.js +28 -3
  12. package/dist/app/adapters/agent-event-adapter.js.map +1 -1
  13. package/dist/app/commands/catalog.js +4 -21
  14. package/dist/app/commands/catalog.js.map +1 -1
  15. package/dist/app/controllers/session-controller.js +7 -0
  16. package/dist/app/controllers/session-controller.js.map +1 -1
  17. package/dist/app/events/app-event.d.ts +2 -0
  18. package/dist/app/events/app-event.js.map +1 -1
  19. package/dist/classic/app/use-feed.js +2 -1
  20. package/dist/classic/app/use-feed.js.map +1 -1
  21. package/dist/commands/providers.js +0 -4
  22. package/dist/commands/providers.js.map +1 -1
  23. package/dist/llm/adapters/openai-tools.d.ts +1 -0
  24. package/dist/llm/adapters/openai-tools.js +4 -1
  25. package/dist/llm/adapters/openai-tools.js.map +1 -1
  26. package/dist/llm/agentrouter.js +2 -0
  27. package/dist/llm/agentrouter.js.map +1 -1
  28. package/dist/llm/aws-mantle.js +2 -0
  29. package/dist/llm/aws-mantle.js.map +1 -1
  30. package/dist/llm/bynara.js +2 -0
  31. package/dist/llm/bynara.js.map +1 -1
  32. package/dist/llm/capabilities.d.ts +19 -0
  33. package/dist/llm/capabilities.js +165 -79
  34. package/dist/llm/capabilities.js.map +1 -1
  35. package/dist/llm/catalog-facts.d.ts +23 -0
  36. package/dist/llm/catalog-facts.js +253 -0
  37. package/dist/llm/catalog-facts.js.map +1 -0
  38. package/dist/llm/catalog-prefetch.d.ts +5 -0
  39. package/dist/llm/catalog-prefetch.js +38 -0
  40. package/dist/llm/catalog-prefetch.js.map +1 -0
  41. package/dist/llm/context-windows.d.ts +2 -0
  42. package/dist/llm/context-windows.js +20 -6
  43. package/dist/llm/context-windows.js.map +1 -1
  44. package/dist/llm/custom-profile-resolver.d.ts +5 -0
  45. package/dist/llm/custom-profile-resolver.js +8 -0
  46. package/dist/llm/custom-profile-resolver.js.map +1 -0
  47. package/dist/llm/custom-provider-profile.d.ts +7 -2
  48. package/dist/llm/custom-provider-profile.js +38 -8
  49. package/dist/llm/custom-provider-profile.js.map +1 -1
  50. package/dist/llm/custom-providers.js +2 -0
  51. package/dist/llm/custom-providers.js.map +1 -1
  52. package/dist/llm/effort-fallback.js +3 -0
  53. package/dist/llm/effort-fallback.js.map +1 -1
  54. package/dist/llm/fireworks.js +2 -0
  55. package/dist/llm/fireworks.js.map +1 -1
  56. package/dist/llm/free.js +2 -0
  57. package/dist/llm/free.js.map +1 -1
  58. package/dist/llm/hetzner.js +2 -0
  59. package/dist/llm/hetzner.js.map +1 -1
  60. package/dist/llm/http.d.ts +21 -2
  61. package/dist/llm/http.js +100 -141
  62. package/dist/llm/http.js.map +1 -1
  63. package/dist/llm/learned-capabilities.d.ts +22 -0
  64. package/dist/llm/learned-capabilities.js +147 -0
  65. package/dist/llm/learned-capabilities.js.map +1 -0
  66. package/dist/llm/lightning.js +2 -0
  67. package/dist/llm/lightning.js.map +1 -1
  68. package/dist/llm/modal.js +2 -0
  69. package/dist/llm/modal.js.map +1 -1
  70. package/dist/llm/model-families.d.ts +20 -0
  71. package/dist/llm/model-families.js +227 -19
  72. package/dist/llm/model-families.js.map +1 -1
  73. package/dist/llm/nvidia.js +2 -0
  74. package/dist/llm/nvidia.js.map +1 -1
  75. package/dist/llm/openai.js +2 -0
  76. package/dist/llm/openai.js.map +1 -1
  77. package/dist/llm/openrouter.js +2 -0
  78. package/dist/llm/openrouter.js.map +1 -1
  79. package/dist/llm/orcarouter.js +2 -0
  80. package/dist/llm/orcarouter.js.map +1 -1
  81. package/dist/llm/provider-model-layers.d.ts +3 -0
  82. package/dist/llm/provider-model-layers.js +522 -0
  83. package/dist/llm/provider-model-layers.js.map +1 -0
  84. package/dist/llm/provider-profile-layers.d.ts +4 -1
  85. package/dist/llm/provider-profile-layers.js +8 -523
  86. package/dist/llm/provider-profile-layers.js.map +1 -1
  87. package/dist/llm/provider-profile.d.ts +13 -1
  88. package/dist/llm/provider-profile.js +18 -0
  89. package/dist/llm/provider-profile.js.map +1 -1
  90. package/dist/llm/provider-profiles.d.ts +5 -1
  91. package/dist/llm/provider-profiles.js +177 -4
  92. package/dist/llm/provider-profiles.js.map +1 -1
  93. package/dist/llm/provider.js +0 -15
  94. package/dist/llm/provider.js.map +1 -1
  95. package/dist/llm/qwen-cloud.js +2 -0
  96. package/dist/llm/qwen-cloud.js.map +1 -1
  97. package/dist/llm/reasoning-artifacts.d.ts +1 -0
  98. package/dist/llm/reasoning-artifacts.js +7 -5
  99. package/dist/llm/reasoning-artifacts.js.map +1 -1
  100. package/dist/llm/reasoning-controls.d.ts +16 -0
  101. package/dist/llm/reasoning-controls.js +210 -0
  102. package/dist/llm/reasoning-controls.js.map +1 -0
  103. package/dist/llm/reasoning-errors.d.ts +4 -0
  104. package/dist/llm/reasoning-errors.js +45 -0
  105. package/dist/llm/reasoning-errors.js.map +1 -0
  106. package/dist/llm/request-plan.d.ts +8 -6
  107. package/dist/llm/request-plan.js +47 -13
  108. package/dist/llm/request-plan.js.map +1 -1
  109. package/dist/llm/route-dialect-registry.d.ts +8 -0
  110. package/dist/llm/route-dialect-registry.js +33 -0
  111. package/dist/llm/route-dialect-registry.js.map +1 -0
  112. package/dist/llm/route-vocabulary.d.ts +2 -0
  113. package/dist/llm/route-vocabulary.js +23 -0
  114. package/dist/llm/route-vocabulary.js.map +1 -0
  115. package/dist/llm/router.d.ts +2 -0
  116. package/dist/llm/router.js +70 -20
  117. package/dist/llm/router.js.map +1 -1
  118. package/dist/llm/tokenrouter.js +2 -0
  119. package/dist/llm/tokenrouter.js.map +1 -1
  120. package/dist/store/config.d.ts +11 -0
  121. package/dist/store/config.js +21 -3
  122. package/dist/store/config.js.map +1 -1
  123. package/dist/store/keys.d.ts +1 -1
  124. package/dist/store/keys.js +14 -3
  125. package/dist/store/keys.js.map +1 -1
  126. package/dist/store/session-workspace.d.ts +1 -0
  127. package/dist/store/session-workspace.js +21 -1
  128. package/dist/store/session-workspace.js.map +1 -1
  129. package/dist/tools/definitions.js +5 -10
  130. package/dist/tools/definitions.js.map +1 -1
  131. package/dist/tools/output-selection.js +47 -3
  132. package/dist/tools/output-selection.js.map +1 -1
  133. package/dist/tools/web/content-encoding.d.ts +6 -0
  134. package/dist/tools/web/content-encoding.js +57 -0
  135. package/dist/tools/web/content-encoding.js.map +1 -0
  136. package/dist/tools/web/fetch-core.js +23 -1
  137. package/dist/tools/web/fetch-core.js.map +1 -1
  138. package/dist/tui-v2/components/picker/picker.js +1 -1
  139. package/dist/tui-v2/components/picker/picker.js.map +1 -1
  140. package/dist/types.d.ts +2 -1
  141. package/dist/types.js +0 -2
  142. package/dist/types.js.map +1 -1
  143. package/dist/ui-core/commands/command-handlers.js +2 -3
  144. package/dist/ui-core/commands/command-handlers.js.map +1 -1
  145. package/dist/ui-core/commands/picker-commands.js +34 -11
  146. package/dist/ui-core/commands/picker-commands.js.map +1 -1
  147. package/dist/ui-core/commands/session-commands.d.ts +2 -3
  148. package/dist/ui-core/commands/session-commands.js +44 -17
  149. package/dist/ui-core/commands/session-commands.js.map +1 -1
  150. package/dist/ui-core/rendering/format-help.js +1 -1
  151. package/dist/ui-core/rendering/format-help.js.map +1 -1
  152. package/dist/ui-core/state/transcript-reducer.js +78 -19
  153. package/dist/ui-core/state/transcript-reducer.js.map +1 -1
  154. package/dist/ui-core/state/transcript-types.d.ts +1 -0
  155. package/dist/ui-core/state/transcript-types.js.map +1 -1
  156. package/dist/version.generated.d.ts +2 -2
  157. package/dist/version.generated.js +2 -2
  158. package/package.json +1 -2
  159. package/dist/llm/groq.d.ts +0 -24
  160. package/dist/llm/groq.js +0 -155
  161. package/dist/llm/groq.js.map +0 -1
  162. package/dist/llm/kimchi.d.ts +0 -2
  163. package/dist/llm/kimchi.js +0 -96
  164. package/dist/llm/kimchi.js.map +0 -1
package/dist/llm/http.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { modelAcceptsImages, modelSupportsThinking, isReasoningUnsupported, modelReasoningEfforts, learnModelEmitsReasoning, registerModelCatalog, } from "./capabilities.js";
2
+ import { catalogEffortList, catalogEntriesFromPayload, parseCatalogFacts, } from "./catalog-facts.js";
2
3
  import { resolveSampling } from "./sampling.js";
3
4
  import { accumulateOpenAiToolCallDelta, finalizeOpenAiToolCalls, fromWireName, parseOpenAiMessageToolCalls, } from "./tool-protocol.js";
4
5
  import { openAiToolBodyFields, toOpenAiToolMessages, } from "./adapters/openai-tools.js";
@@ -7,6 +8,9 @@ import { generationFetch } from "./operation-usage.js";
7
8
  import { isOperationPolicyError } from "./operation-ledger.js";
8
9
  import { emitStreamReasoningArtifacts, emitStreamReasoningDelta, } from "./stream-events.js";
9
10
  import { CHAT_COMPLETIONS_STREAM_TERMINAL, requireTerminalProof, } from "./stream-terminal.js";
11
+ import { modelMaxOutputTokens } from "./context-windows.js";
12
+ import { inBandBadRequestStatus, isMissingReasoningContentError, } from "./reasoning-errors.js";
13
+ import { emitReasoningControls, } from "./reasoning-controls.js";
10
14
  import { createReasoningArtifact, createReasoningArtifactProvenance, } from "./reasoning-artifacts.js";
11
15
  import { classifyBynaraModel, classifyNvidiaModel, } from "./model-families.js";
12
16
  import { compileRequestPlan, } from "./request-plan.js";
@@ -68,116 +72,26 @@ function statusCodeHint(status) {
68
72
  }
69
73
  return "";
70
74
  }
71
- function modalitiesDeclareImage(value) {
72
- if (Array.isArray(value)) {
73
- const items = value.filter((item) => typeof item === "string");
74
- if (items.length === 0)
75
- return undefined;
76
- return items.some((item) => /image|vision/i.test(item));
77
- }
78
- if (typeof value === "string") {
79
- if (!/text|image|audio|video/i.test(value))
80
- return undefined;
81
- return /image|vision/i.test(value);
82
- }
83
- if (value && typeof value === "object") {
84
- const nested = value;
85
- if (typeof nested.vision === "boolean")
86
- return nested.vision;
87
- if (typeof nested.image === "boolean")
88
- return nested.image;
89
- if (nested.input !== undefined)
90
- return modalitiesDeclareImage(nested.input);
91
- }
92
- return undefined;
93
- }
94
75
  export function catalogEntryVision(entry) {
95
- if (!entry || typeof entry !== "object")
96
- return undefined;
97
- const raw = entry;
98
- for (const flag of [raw.vision, raw.supports_vision, raw.multimodal]) {
99
- if (typeof flag === "boolean")
100
- return flag;
101
- }
102
- for (const candidate of [
103
- raw.architecture?.input_modalities,
104
- raw.architecture?.modality,
105
- raw.input_modalities,
106
- raw.modalities,
107
- raw.capabilities,
108
- raw.features,
109
- ]) {
110
- const declared = modalitiesDeclareImage(candidate);
111
- if (declared !== undefined)
112
- return declared;
113
- }
114
- return undefined;
115
- }
116
- const REASONING_FEATURE_RE = /^(?:reasoning|reasoning_effort|include_reasoning|thinking|reasoning_content)$/i;
117
- function catalogEntryReasoning(entry) {
118
- const raw = entry;
119
- if (typeof raw?.reasoning === "boolean")
120
- return raw.reasoning;
121
- if (Array.isArray(raw?.reasoning_options) && raw.reasoning_options.length > 0) {
122
- return true;
123
- }
124
- for (const container of [
125
- raw?.supported_features,
126
- raw?.supported_parameters,
127
- raw?.features,
128
- raw?.capabilities,
129
- ]) {
130
- if (!Array.isArray(container))
131
- continue;
132
- const names = container.filter((value) => typeof value === "string");
133
- if (names.length === 0)
134
- continue;
135
- return names.some((name) => REASONING_FEATURE_RE.test(name.trim()));
136
- }
137
- return undefined;
138
- }
139
- function catalogEntryReasoningEfforts(entry) {
140
- const raw = entry;
141
- const collected = [];
142
- const pushValues = (values) => {
143
- if (!Array.isArray(values))
144
- return;
145
- for (const value of values) {
146
- if (typeof value === "string" && value.trim())
147
- collected.push(value.trim());
148
- }
149
- };
150
- if (Array.isArray(raw?.reasoning_options)) {
151
- for (const option of raw.reasoning_options) {
152
- const shaped = option;
153
- if (shaped?.type !== undefined && shaped.type !== "effort")
154
- continue;
155
- pushValues(shaped?.values);
156
- }
157
- }
158
- pushValues(raw?.supported_reasoning_efforts);
159
- if (raw?.reasoning_effort && typeof raw.reasoning_effort === "object") {
160
- pushValues(raw.reasoning_effort.values);
161
- }
162
- return collected.length > 0 ? collected : undefined;
76
+ return parseCatalogFacts(entry)?.vision;
163
77
  }
164
78
  export function ingestModelCatalogEntries(provider, entries) {
165
79
  const models = [];
166
80
  const seen = new Set();
167
81
  for (const entry of entries) {
168
- const raw = typeof entry === "string" ? entry : entry?.id;
169
- if (typeof raw !== "string")
82
+ const facts = parseCatalogFacts(entry);
83
+ if (!facts)
170
84
  continue;
171
- const id = raw.trim();
172
- if (id.length === 0 || seen.has(id))
85
+ const id = facts.id;
86
+ if (seen.has(id))
173
87
  continue;
174
88
  seen.add(id);
175
- const vision = typeof entry === "string" ? undefined : catalogEntryVision(entry);
176
- const reasoning = typeof entry === "string" ? undefined : catalogEntryReasoning(entry);
177
- const reasoningEfforts = typeof entry === "string" ? undefined : catalogEntryReasoningEfforts(entry);
89
+ const reasoning = facts.reasoning?.supported;
90
+ const reasoningEfforts = catalogEffortList(facts.reasoning?.supportedEfforts);
178
91
  models.push({
179
92
  id,
180
- ...(vision === undefined ? {} : { vision }),
93
+ facts,
94
+ ...(facts.vision === undefined ? {} : { vision: facts.vision }),
181
95
  ...(reasoning === undefined ? {} : { reasoning }),
182
96
  ...(reasoningEfforts === undefined ? {} : { reasoningEfforts }),
183
97
  });
@@ -187,15 +101,7 @@ export function ingestModelCatalogEntries(provider, entries) {
187
101
  return models.map((model) => model.id).sort();
188
102
  }
189
103
  export function ingestOpenAiModelCatalog(provider, payload) {
190
- const container = payload;
191
- const entries = Array.isArray(payload)
192
- ? payload
193
- : Array.isArray(container?.data)
194
- ? container.data
195
- : Array.isArray(container?.models)
196
- ? container.models
197
- : [];
198
- return ingestModelCatalogEntries(provider, entries);
104
+ return ingestModelCatalogEntries(provider, catalogEntriesFromPayload(payload));
199
105
  }
200
106
  export async function readJson(response, signal) {
201
107
  const text = await readBodyCapped(response, MAX_JSON_RESPONSE_BYTES, signal);
@@ -552,6 +458,15 @@ const DEFAULT_COMPATIBLE_REASONING_ARTIFACT_POLICY = {
552
458
  scope: "all-history",
553
459
  persistence: "tool-turn",
554
460
  };
461
+ const PRESERVED_FINAL_TURN_ARTIFACT_POLICY = {
462
+ scope: "all-history",
463
+ persistence: "all-turns",
464
+ };
465
+ export function compatibleArtifactPolicyFor(preservation) {
466
+ return preservation === "required"
467
+ ? PRESERVED_FINAL_TURN_ARTIFACT_POLICY
468
+ : DEFAULT_COMPATIBLE_REASONING_ARTIFACT_POLICY;
469
+ }
555
470
  function artifactRaw(value) {
556
471
  if (typeof value === "string")
557
472
  return value;
@@ -726,7 +641,16 @@ function pickAdvertisedEffort(effort, advertised) {
726
641
  return (order.find((candidate) => advertised.includes(candidate)) ??
727
642
  advertised[advertised.length - 1]);
728
643
  }
729
- export function buildReasoningPayload(reasoning, style, model, providerId) {
644
+ export function buildReasoningPayload(reasoning, style, model, providerId, control) {
645
+ if (control) {
646
+ return {
647
+ ...emitReasoningControls({
648
+ profile: control.profile,
649
+ preference: reasoning,
650
+ willReplayReasoning: control.willReplayReasoning,
651
+ }),
652
+ };
653
+ }
730
654
  if (style === "none")
731
655
  return {};
732
656
  const enabled = Boolean(reasoning?.enabled);
@@ -866,18 +790,6 @@ export function buildReasoningPayload(reasoning, style, model, providerId) {
866
790
  // off, so compaction would still buy hidden reasoning tokens. vLLM's
867
791
  // StepFun template honours this explicit per-request switch.
868
792
  return { chat_template_kwargs: { enable_thinking: enabled } };
869
- case "groq": {
870
- const m = (model ?? "").toLowerCase();
871
- if (/qwen\/qwen3-32b/.test(m)) {
872
- return { reasoning_effort: enabled ? "default" : "none" };
873
- }
874
- if (/openai\/gpt-oss-(?:20b|120b)/.test(m)) {
875
- return enabled
876
- ? { reasoning_effort: clampEffort(effort), include_reasoning: true }
877
- : { reasoning_effort: "low", include_reasoning: false };
878
- }
879
- return {};
880
- }
881
793
  case "nvidia": {
882
794
  const kind = classifyNvidiaModel(model ?? "");
883
795
  switch (kind) {
@@ -964,6 +876,8 @@ export function buildReasoningPayload(reasoning, style, model, providerId) {
964
876
  * payload and retries so an unsupported option never fails the whole request.
965
877
  */
966
878
  export function isReasoningUnsupportedError(error) {
879
+ if (isMissingReasoningContentError(error))
880
+ return false;
967
881
  const status = error && typeof error === "object" && "status" in error
968
882
  ? Number(error.status)
969
883
  : undefined;
@@ -1043,30 +957,46 @@ export function isOpenAiReasoningModel(model) {
1043
957
  /(?:^|\/)o[134](?:\.|-|$)/.test(m) ||
1044
958
  /muse-spark/.test(m));
1045
959
  }
960
+ const DEFAULT_REASONING_OUTPUT_FLOOR = 16_384;
961
+ function outputBudgetWithReasoning(requested, options) {
962
+ const floor = options.control?.profile.reasoning.minOutputTokens ??
963
+ DEFAULT_REASONING_OUTPUT_FLOOR;
964
+ const ceiling = modelMaxOutputTokens(options.providerId, options.model, options.outputTokenLimit);
965
+ const floored = Math.max(requested, floor);
966
+ return ceiling !== undefined ? Math.min(floored, ceiling) : floored;
967
+ }
1046
968
  function emitChatCompletionsBody(options) {
1047
969
  // Skip reasoning knobs entirely for models observed to reject them this
1048
970
  // session (see isReasoningUnsupportedError). This is how thinking degrades
1049
971
  // gracefully: the request still runs, just without the unsupported option.
1050
- const capabilityDeniesThinking = options.providerId !== undefined &&
972
+ const capabilityDeniesThinking = options.control === undefined &&
973
+ options.providerId !== undefined &&
1051
974
  Boolean(options.reasoning?.enabled) &&
1052
975
  !modelSupportsThinking(options.providerId, options.model);
1053
- const reasoning = (options.providerId !== undefined &&
1054
- isReasoningUnsupported(options.providerId, options.model)) ||
1055
- capabilityDeniesThinking
976
+ const legacyControlDenied = options.control === undefined &&
977
+ options.providerId !== undefined &&
978
+ isReasoningUnsupported(options.providerId, options.model);
979
+ const reasoning = legacyControlDenied ||
980
+ capabilityDeniesThinking ||
981
+ options.control?.suppressed === true
1056
982
  ? {}
1057
- : buildReasoningPayload(options.reasoning, options.reasoningStyle ?? "none", options.model, options.providerId);
983
+ : buildReasoningPayload(options.reasoning, options.reasoningStyle ?? "none", options.model, options.providerId, options.control);
1058
984
  const reasoningOn = Boolean(options.reasoning?.enabled);
1059
985
  // Kimchi exposes this model as `minimax-m3`; NVIDIA uses the longer
1060
986
  // `minimaxai/minimax-m3` ID. Both need the larger default output budget.
1061
987
  const isMinimaxM3 = /minimax-m3/i.test(options.model);
1062
988
  const defaultMaxTokens = isMinimaxM3 ? 8_192 : reasoningOn ? 8_192 : 4_096;
1063
989
  // One declarative sampling policy; explicit caller value wins.
1064
- const sampling = resolveSampling({
1065
- model: options.model,
1066
- reasoningEnabled: reasoningOn,
1067
- requestedTemperature: options.temperature,
1068
- });
990
+ const sampling = options.resolvedSampling ?? {
991
+ ...resolveSampling({
992
+ model: options.model,
993
+ reasoningEnabled: reasoningOn,
994
+ requestedTemperature: options.temperature,
995
+ }),
996
+ };
1069
997
  const reasoningModel = isOpenAiReasoningModel(options.model);
998
+ const emitTemperature = sampling.temperature !== undefined &&
999
+ (options.resolvedSampling !== undefined || !reasoningModel);
1070
1000
  // Claude extended thinking via AgentRouter maps reasoning_effort to an
1071
1001
  // Anthropic `thinking.budget_tokens`, and the gateway (Bedrock) rejects the
1072
1002
  // request with HTTP 400 unless `max_tokens > budget_tokens`. Some upstream
@@ -1077,15 +1007,20 @@ function emitChatCompletionsBody(options) {
1077
1007
  const claudeThinking = reasoningOn &&
1078
1008
  options.reasoningStyle === "agentrouter" &&
1079
1009
  /claude/i.test(options.model);
1080
- const effectiveMaxTokens = claudeThinking
1081
- ? Math.max(options.maxTokens ?? defaultMaxTokens, 32_000)
1082
- : (options.maxTokens ?? defaultMaxTokens);
1010
+ const requestedMaxTokens = options.maxTokens ?? defaultMaxTokens;
1011
+ const claudeFloored = claudeThinking
1012
+ ? Math.max(requestedMaxTokens, 32_000)
1013
+ : requestedMaxTokens;
1014
+ const effectiveMaxTokens = reasoningOn
1015
+ ? outputBudgetWithReasoning(claudeFloored, options)
1016
+ : claudeFloored;
1083
1017
  const body = {
1084
1018
  model: options.model,
1085
1019
  messages: toOpenAiMessages(singleLeadingSystemMessages(options.messages), options.supportsVision, options.replayTarget
1086
1020
  ? {
1087
1021
  target: options.replayTarget,
1088
1022
  observe: options.reasoningArtifactReplayObserver,
1023
+ ...(options.forceReasoningReplay ? { forceScope: true } : {}),
1089
1024
  }
1090
1025
  : undefined),
1091
1026
  stream: options.stream,
@@ -1098,7 +1033,7 @@ function emitChatCompletionsBody(options) {
1098
1033
  // gpt-5.x / o1 / o3 / o4 only accept the default temperature (1) and
1099
1034
  // reject any explicit value — omit the field entirely rather than send
1100
1035
  // our 0.2 default and get a 400.
1101
- ...(reasoningModel ? {} : { temperature: sampling.temperature }),
1036
+ ...(emitTemperature ? { temperature: sampling.temperature } : {}),
1102
1037
  ...reasoning,
1103
1038
  ...openAiToolBodyFields({
1104
1039
  tools: options.tools,
@@ -1106,7 +1041,7 @@ function emitChatCompletionsBody(options) {
1106
1041
  parallelToolCalls: options.parallelToolCalls,
1107
1042
  }),
1108
1043
  };
1109
- if (!reasoningModel && sampling.topP !== undefined) {
1044
+ if (emitTemperature && sampling.topP !== undefined) {
1110
1045
  body.top_p = sampling.topP;
1111
1046
  }
1112
1047
  // OpenAI + many OpenAI-compatible gateways attach usage on the final SSE
@@ -1136,6 +1071,21 @@ export function chatCompletionsBodyFromPlan(plan, extras = {}) {
1136
1071
  reasoning: plan.controls.reasoning,
1137
1072
  reasoningStyle: extras.reasoningStyle,
1138
1073
  supportsVision: plan.images.visionAccepted,
1074
+ control: {
1075
+ profile: {
1076
+ reasoning: plan.policy.reasoning,
1077
+ capabilities: { acceptedParameters: plan.policy.acceptedParameters },
1078
+ },
1079
+ willReplayReasoning: plan.replay.decisions.some((entry) => entry.decision.action === "replayed"),
1080
+ suppressed: plan.controls.controlSuppression !== undefined,
1081
+ },
1082
+ resolvedSampling: {
1083
+ temperature: plan.controls.temperature,
1084
+ topP: plan.controls.topP,
1085
+ },
1086
+ ...(plan.policy.limits.outputTokens !== undefined
1087
+ ? { outputTokenLimit: plan.policy.limits.outputTokens }
1088
+ : {}),
1139
1089
  tools: plan.tools.definitions.length
1140
1090
  ? [...plan.tools.definitions]
1141
1091
  : undefined,
@@ -1143,6 +1093,7 @@ export function chatCompletionsBodyFromPlan(plan, extras = {}) {
1143
1093
  parallelToolCalls: plan.tools.parallelToolCalls,
1144
1094
  replayTarget: plan.replay.target,
1145
1095
  reasoningArtifactReplayObserver: extras.reasoningArtifactReplayObserver,
1096
+ ...(extras.forceReasoningReplay ? { forceReasoningReplay: true } : {}),
1146
1097
  });
1147
1098
  }
1148
1099
  function sentReasoningEffort(requestBody) {
@@ -1185,6 +1136,7 @@ export async function openAiCompatibleComplete(options) {
1185
1136
  const requestBody = chatCompletionsBodyFromPlan(plan, {
1186
1137
  reasoningStyle: options.reasoningStyle,
1187
1138
  reasoningArtifactReplayObserver: options.reasoningArtifactReplayObserver,
1139
+ ...(options.forceReasoningReplay ? { forceReasoningReplay: true } : {}),
1188
1140
  });
1189
1141
  let response;
1190
1142
  try {
@@ -1244,7 +1196,8 @@ export async function openAiCompatibleComplete(options) {
1244
1196
  model: options.model,
1245
1197
  baseUrl: options.baseUrl,
1246
1198
  toolCalls,
1247
- policy: options.reasoningArtifactPolicy,
1199
+ policy: options.reasoningArtifactPolicy ??
1200
+ compatibleArtifactPolicyFor(plan.policy.reasoning.finalTurnPreservation),
1248
1201
  ...(typeof reasoning === "string" && reasoning
1249
1202
  ? { reasoning: { text: reasoning, sequence: 0 } }
1250
1203
  : {}),
@@ -1374,6 +1327,7 @@ export async function openAiCompatibleStream(options) {
1374
1327
  reasoningStyle: options.reasoningStyle,
1375
1328
  includeStreamUsage: options.includeStreamUsage,
1376
1329
  reasoningArtifactReplayObserver: options.reasoningArtifactReplayObserver,
1330
+ ...(options.forceReasoningReplay ? { forceReasoningReplay: true } : {}),
1377
1331
  });
1378
1332
  let response;
1379
1333
  try {
@@ -1441,7 +1395,8 @@ export async function openAiCompatibleStream(options) {
1441
1395
  model: options.model,
1442
1396
  baseUrl: options.baseUrl,
1443
1397
  toolCalls,
1444
- policy: options.reasoningArtifactPolicy,
1398
+ policy: options.reasoningArtifactPolicy ??
1399
+ compatibleArtifactPolicyFor(plan.policy.reasoning.finalTurnPreservation),
1445
1400
  ...(typeof reasoning === "string" && reasoning
1446
1401
  ? { reasoning: { text: reasoning, sequence: 0 } }
1447
1402
  : {}),
@@ -1509,7 +1464,10 @@ export async function openAiCompatibleStream(options) {
1509
1464
  let reasoningWireSeen = "";
1510
1465
  let finishReason;
1511
1466
  let terminalSignal;
1512
- const terminalPolicy = options.streamTerminal ?? CHAT_COMPLETIONS_STREAM_TERMINAL;
1467
+ const terminalPolicy = options.streamTerminal ??
1468
+ (plan.policy.terminal.proofs.length > 0
1469
+ ? plan.policy.terminal
1470
+ : CHAT_COMPLETIONS_STREAM_TERMINAL);
1513
1471
  const emittedByteCounts = () => {
1514
1472
  let toolArgumentBytes = 0;
1515
1473
  for (const state of toolCallState.values()) {
@@ -1545,7 +1503,8 @@ export async function openAiCompatibleStream(options) {
1545
1503
  model: options.model,
1546
1504
  baseUrl: options.baseUrl,
1547
1505
  toolCalls,
1548
- policy: options.reasoningArtifactPolicy,
1506
+ policy: options.reasoningArtifactPolicy ??
1507
+ compatibleArtifactPolicyFor(plan.policy.reasoning.finalTurnPreservation),
1549
1508
  ...(reasoningSeen
1550
1509
  ? {
1551
1510
  reasoning: {
@@ -1763,7 +1722,7 @@ export async function openAiCompatibleStream(options) {
1763
1722
  const detail = typeof parsed.error === "string"
1764
1723
  ? parsed.error
1765
1724
  : (parsed.error.message ?? parsed.error.type ?? "unknown error");
1766
- throw new ProviderError(`${options.provider} stream error: ${detail}`, undefined, payload.slice(0, 500));
1725
+ throw new ProviderError(`${options.provider} stream error: ${detail}`, inBandBadRequestStatus(parsed.error), payload.slice(0, 500));
1767
1726
  }
1768
1727
  {
1769
1728
  const chunkUsage = options.providerId === "fireworks"