@pentoshi/clai 4.2.2 → 4.4.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/README.md +15 -17
- package/dist/agent/compaction-summary.d.ts +1 -1
- package/dist/agent/compaction-summary.js +1 -1
- package/dist/agent/compaction-summary.js.map +1 -1
- package/dist/agent/runner.js +12 -13
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tool-output-formatting.d.ts +6 -0
- package/dist/agent/tool-output-formatting.js +126 -40
- package/dist/agent/tool-output-formatting.js.map +1 -1
- package/dist/app/adapters/agent-event-adapter.d.ts +4 -0
- package/dist/app/adapters/agent-event-adapter.js +28 -3
- package/dist/app/adapters/agent-event-adapter.js.map +1 -1
- package/dist/app/commands/catalog.js +8 -26
- package/dist/app/commands/catalog.js.map +1 -1
- package/dist/app/commands/registry.js +0 -1
- package/dist/app/commands/registry.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +3 -0
- package/dist/app/controllers/session-controller.js +18 -25
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/session-persistence.d.ts +4 -1
- package/dist/app/controllers/session-persistence.js +23 -5
- package/dist/app/controllers/session-persistence.js.map +1 -1
- package/dist/app/controllers/session-responder.d.ts +1 -0
- package/dist/app/controllers/session-responder.js +8 -0
- package/dist/app/controllers/session-responder.js.map +1 -1
- package/dist/app/controllers/session-usage-ledger.d.ts +63 -0
- package/dist/app/controllers/session-usage-ledger.js +232 -0
- package/dist/app/controllers/session-usage-ledger.js.map +1 -0
- package/dist/app/events/app-event.d.ts +2 -0
- package/dist/app/events/app-event.js.map +1 -1
- package/dist/classic/app/ClassicApp.js +2 -1
- package/dist/classic/app/ClassicApp.js.map +1 -1
- package/dist/classic/app/use-feed.js +2 -1
- package/dist/classic/app/use-feed.js.map +1 -1
- package/dist/classic/chrome/toast-rows.d.ts +1 -0
- package/dist/classic/chrome/toast-rows.js +18 -0
- package/dist/classic/chrome/toast-rows.js.map +1 -1
- package/dist/commands/providers.js +0 -4
- package/dist/commands/providers.js.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/llm/adapters/openai-tools.d.ts +1 -0
- package/dist/llm/adapters/openai-tools.js +4 -1
- package/dist/llm/adapters/openai-tools.js.map +1 -1
- package/dist/llm/agentrouter.js +2 -0
- package/dist/llm/agentrouter.js.map +1 -1
- package/dist/llm/aws-mantle.js +2 -0
- package/dist/llm/aws-mantle.js.map +1 -1
- package/dist/llm/bynara.js +2 -0
- package/dist/llm/bynara.js.map +1 -1
- package/dist/llm/capabilities.d.ts +19 -0
- package/dist/llm/capabilities.js +165 -79
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/catalog-facts.d.ts +23 -0
- package/dist/llm/catalog-facts.js +253 -0
- package/dist/llm/catalog-facts.js.map +1 -0
- package/dist/llm/catalog-prefetch.d.ts +5 -0
- package/dist/llm/catalog-prefetch.js +38 -0
- package/dist/llm/catalog-prefetch.js.map +1 -0
- package/dist/llm/context-windows.d.ts +2 -0
- package/dist/llm/context-windows.js +20 -6
- package/dist/llm/context-windows.js.map +1 -1
- package/dist/llm/custom-profile-resolver.d.ts +5 -0
- package/dist/llm/custom-profile-resolver.js +8 -0
- package/dist/llm/custom-profile-resolver.js.map +1 -0
- package/dist/llm/custom-provider-profile.d.ts +7 -2
- package/dist/llm/custom-provider-profile.js +40 -10
- package/dist/llm/custom-provider-profile.js.map +1 -1
- package/dist/llm/custom-providers.js +2 -0
- package/dist/llm/custom-providers.js.map +1 -1
- package/dist/llm/effort-fallback.js +3 -0
- package/dist/llm/effort-fallback.js.map +1 -1
- package/dist/llm/fireworks.js +2 -0
- package/dist/llm/fireworks.js.map +1 -1
- package/dist/llm/free.js +2 -0
- package/dist/llm/free.js.map +1 -1
- package/dist/llm/hetzner.js +2 -0
- package/dist/llm/hetzner.js.map +1 -1
- package/dist/llm/http.d.ts +21 -2
- package/dist/llm/http.js +133 -141
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/key-rotation.d.ts +1 -0
- package/dist/llm/key-rotation.js +8 -0
- package/dist/llm/key-rotation.js.map +1 -1
- package/dist/llm/learned-capabilities.d.ts +22 -0
- package/dist/llm/learned-capabilities.js +147 -0
- package/dist/llm/learned-capabilities.js.map +1 -0
- package/dist/llm/lightning.js +2 -0
- package/dist/llm/lightning.js.map +1 -1
- package/dist/llm/modal.js +2 -0
- package/dist/llm/modal.js.map +1 -1
- package/dist/llm/model-families.d.ts +20 -0
- package/dist/llm/model-families.js +227 -19
- package/dist/llm/model-families.js.map +1 -1
- package/dist/llm/nvidia.js +2 -0
- package/dist/llm/nvidia.js.map +1 -1
- package/dist/llm/openai.js +2 -0
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/openrouter.js +2 -0
- package/dist/llm/openrouter.js.map +1 -1
- package/dist/llm/orcarouter.js +2 -0
- package/dist/llm/orcarouter.js.map +1 -1
- package/dist/llm/provider-model-layers.d.ts +3 -0
- package/dist/llm/provider-model-layers.js +522 -0
- package/dist/llm/provider-model-layers.js.map +1 -0
- package/dist/llm/provider-profile-layers.d.ts +4 -1
- package/dist/llm/provider-profile-layers.js +22 -536
- package/dist/llm/provider-profile-layers.js.map +1 -1
- package/dist/llm/provider-profile.d.ts +14 -1
- package/dist/llm/provider-profile.js +23 -0
- package/dist/llm/provider-profile.js.map +1 -1
- package/dist/llm/provider-profiles.d.ts +5 -1
- package/dist/llm/provider-profiles.js +178 -5
- package/dist/llm/provider-profiles.js.map +1 -1
- package/dist/llm/provider.js +0 -15
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/qwen-cloud.js +2 -0
- package/dist/llm/qwen-cloud.js.map +1 -1
- package/dist/llm/reasoning-artifacts.d.ts +1 -0
- package/dist/llm/reasoning-artifacts.js +7 -5
- package/dist/llm/reasoning-artifacts.js.map +1 -1
- package/dist/llm/reasoning-controls.d.ts +16 -0
- package/dist/llm/reasoning-controls.js +210 -0
- package/dist/llm/reasoning-controls.js.map +1 -0
- package/dist/llm/reasoning-errors.d.ts +4 -0
- package/dist/llm/reasoning-errors.js +45 -0
- package/dist/llm/reasoning-errors.js.map +1 -0
- package/dist/llm/request-plan.d.ts +8 -6
- package/dist/llm/request-plan.js +47 -13
- package/dist/llm/request-plan.js.map +1 -1
- package/dist/llm/route-dialect-registry.d.ts +8 -0
- package/dist/llm/route-dialect-registry.js +33 -0
- package/dist/llm/route-dialect-registry.js.map +1 -0
- package/dist/llm/route-vocabulary.d.ts +2 -0
- package/dist/llm/route-vocabulary.js +23 -0
- package/dist/llm/route-vocabulary.js.map +1 -0
- package/dist/llm/router.d.ts +2 -0
- package/dist/llm/router.js +70 -20
- package/dist/llm/router.js.map +1 -1
- package/dist/llm/stream-terminal.js +2 -1
- package/dist/llm/stream-terminal.js.map +1 -1
- package/dist/llm/tokenrouter.js +2 -0
- package/dist/llm/tokenrouter.js.map +1 -1
- package/dist/store/config.d.ts +11 -0
- package/dist/store/config.js +21 -3
- package/dist/store/config.js.map +1 -1
- package/dist/store/history.d.ts +1 -0
- package/dist/store/history.js +7 -0
- package/dist/store/history.js.map +1 -1
- package/dist/store/keys.d.ts +1 -1
- package/dist/store/keys.js +1 -1
- package/dist/store/scope.d.ts +8 -0
- package/dist/store/scope.js +138 -1
- package/dist/store/scope.js.map +1 -1
- package/dist/store/session-workspace.d.ts +1 -0
- package/dist/store/session-workspace.js +21 -1
- package/dist/store/session-workspace.js.map +1 -1
- package/dist/tools/definitions.js +5 -10
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/interactive-session-tools.js +5 -5
- package/dist/tools/interactive-session-tools.js.map +1 -1
- package/dist/tools/output-selection.js +47 -3
- package/dist/tools/output-selection.js.map +1 -1
- package/dist/tools/registry.js +2 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/web/content-encoding.d.ts +6 -0
- package/dist/tools/web/content-encoding.js +57 -0
- package/dist/tools/web/content-encoding.js.map +1 -0
- package/dist/tools/web/fetch-core.js +23 -1
- package/dist/tools/web/fetch-core.js.map +1 -1
- package/dist/tui-v2/components/picker/picker.js +1 -1
- package/dist/tui-v2/components/picker/picker.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +1 -1
- package/dist/ui-core/bootstrap/composition-root.js +8 -2
- package/dist/ui-core/bootstrap/composition-root.js.map +1 -1
- package/dist/ui-core/commands/command-handlers.js +3 -3
- package/dist/ui-core/commands/command-handlers.js.map +1 -1
- package/dist/ui-core/commands/config-commands.js +17 -14
- package/dist/ui-core/commands/config-commands.js.map +1 -1
- package/dist/ui-core/commands/picker-commands.js +34 -11
- package/dist/ui-core/commands/picker-commands.js.map +1 -1
- package/dist/ui-core/commands/session-commands.d.ts +3 -3
- package/dist/ui-core/commands/session-commands.js +64 -17
- package/dist/ui-core/commands/session-commands.js.map +1 -1
- package/dist/ui-core/rendering/format-help.js +2 -2
- package/dist/ui-core/rendering/format-help.js.map +1 -1
- package/dist/ui-core/rendering/format-usage.d.ts +6 -0
- package/dist/ui-core/rendering/format-usage.js +116 -0
- package/dist/ui-core/rendering/format-usage.js.map +1 -0
- package/dist/ui-core/state/transcript-reducer.js +78 -19
- package/dist/ui-core/state/transcript-reducer.js.map +1 -1
- package/dist/ui-core/state/transcript-types.d.ts +1 -0
- package/dist/ui-core/state/transcript-types.js.map +1 -1
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +1 -2
- package/dist/llm/groq.d.ts +0 -24
- package/dist/llm/groq.js +0 -155
- package/dist/llm/groq.js.map +0 -1
- package/dist/llm/kimchi.d.ts +0 -2
- package/dist/llm/kimchi.js +0 -96
- 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";
|
|
@@ -23,6 +27,31 @@ export class ProviderError extends Error {
|
|
|
23
27
|
this.name = "ProviderError";
|
|
24
28
|
}
|
|
25
29
|
}
|
|
30
|
+
const USAGE_INPUT_KEYS = [
|
|
31
|
+
"prompt_tokens",
|
|
32
|
+
"promptTokens",
|
|
33
|
+
"input_tokens",
|
|
34
|
+
"inputTokens",
|
|
35
|
+
];
|
|
36
|
+
const USAGE_OUTPUT_KEYS = [
|
|
37
|
+
"completion_tokens",
|
|
38
|
+
"completionTokens",
|
|
39
|
+
"output_tokens",
|
|
40
|
+
"outputTokens",
|
|
41
|
+
];
|
|
42
|
+
function isFinalUsageFrame(parsed) {
|
|
43
|
+
if (Array.isArray(parsed.choices) && parsed.choices.length > 0)
|
|
44
|
+
return false;
|
|
45
|
+
const usage = parsed.usage;
|
|
46
|
+
if (!usage || typeof usage !== "object" || Array.isArray(usage))
|
|
47
|
+
return false;
|
|
48
|
+
const raw = usage;
|
|
49
|
+
const reported = (keys) => keys.some((key) => {
|
|
50
|
+
const value = raw[key];
|
|
51
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
52
|
+
});
|
|
53
|
+
return reported(USAGE_INPUT_KEYS) && reported(USAGE_OUTPUT_KEYS);
|
|
54
|
+
}
|
|
26
55
|
function parseRetryAfterHeader(value) {
|
|
27
56
|
if (!value)
|
|
28
57
|
return undefined;
|
|
@@ -68,116 +97,26 @@ function statusCodeHint(status) {
|
|
|
68
97
|
}
|
|
69
98
|
return "";
|
|
70
99
|
}
|
|
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
100
|
export function catalogEntryVision(entry) {
|
|
95
|
-
|
|
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;
|
|
101
|
+
return parseCatalogFacts(entry)?.vision;
|
|
163
102
|
}
|
|
164
103
|
export function ingestModelCatalogEntries(provider, entries) {
|
|
165
104
|
const models = [];
|
|
166
105
|
const seen = new Set();
|
|
167
106
|
for (const entry of entries) {
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
107
|
+
const facts = parseCatalogFacts(entry);
|
|
108
|
+
if (!facts)
|
|
170
109
|
continue;
|
|
171
|
-
const id =
|
|
172
|
-
if (
|
|
110
|
+
const id = facts.id;
|
|
111
|
+
if (seen.has(id))
|
|
173
112
|
continue;
|
|
174
113
|
seen.add(id);
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const reasoningEfforts = typeof entry === "string" ? undefined : catalogEntryReasoningEfforts(entry);
|
|
114
|
+
const reasoning = facts.reasoning?.supported;
|
|
115
|
+
const reasoningEfforts = catalogEffortList(facts.reasoning?.supportedEfforts);
|
|
178
116
|
models.push({
|
|
179
117
|
id,
|
|
180
|
-
|
|
118
|
+
facts,
|
|
119
|
+
...(facts.vision === undefined ? {} : { vision: facts.vision }),
|
|
181
120
|
...(reasoning === undefined ? {} : { reasoning }),
|
|
182
121
|
...(reasoningEfforts === undefined ? {} : { reasoningEfforts }),
|
|
183
122
|
});
|
|
@@ -187,15 +126,7 @@ export function ingestModelCatalogEntries(provider, entries) {
|
|
|
187
126
|
return models.map((model) => model.id).sort();
|
|
188
127
|
}
|
|
189
128
|
export function ingestOpenAiModelCatalog(provider, payload) {
|
|
190
|
-
|
|
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);
|
|
129
|
+
return ingestModelCatalogEntries(provider, catalogEntriesFromPayload(payload));
|
|
199
130
|
}
|
|
200
131
|
export async function readJson(response, signal) {
|
|
201
132
|
const text = await readBodyCapped(response, MAX_JSON_RESPONSE_BYTES, signal);
|
|
@@ -552,6 +483,15 @@ const DEFAULT_COMPATIBLE_REASONING_ARTIFACT_POLICY = {
|
|
|
552
483
|
scope: "all-history",
|
|
553
484
|
persistence: "tool-turn",
|
|
554
485
|
};
|
|
486
|
+
const PRESERVED_FINAL_TURN_ARTIFACT_POLICY = {
|
|
487
|
+
scope: "all-history",
|
|
488
|
+
persistence: "all-turns",
|
|
489
|
+
};
|
|
490
|
+
export function compatibleArtifactPolicyFor(preservation) {
|
|
491
|
+
return preservation === "required"
|
|
492
|
+
? PRESERVED_FINAL_TURN_ARTIFACT_POLICY
|
|
493
|
+
: DEFAULT_COMPATIBLE_REASONING_ARTIFACT_POLICY;
|
|
494
|
+
}
|
|
555
495
|
function artifactRaw(value) {
|
|
556
496
|
if (typeof value === "string")
|
|
557
497
|
return value;
|
|
@@ -726,7 +666,16 @@ function pickAdvertisedEffort(effort, advertised) {
|
|
|
726
666
|
return (order.find((candidate) => advertised.includes(candidate)) ??
|
|
727
667
|
advertised[advertised.length - 1]);
|
|
728
668
|
}
|
|
729
|
-
export function buildReasoningPayload(reasoning, style, model, providerId) {
|
|
669
|
+
export function buildReasoningPayload(reasoning, style, model, providerId, control) {
|
|
670
|
+
if (control) {
|
|
671
|
+
return {
|
|
672
|
+
...emitReasoningControls({
|
|
673
|
+
profile: control.profile,
|
|
674
|
+
preference: reasoning,
|
|
675
|
+
willReplayReasoning: control.willReplayReasoning,
|
|
676
|
+
}),
|
|
677
|
+
};
|
|
678
|
+
}
|
|
730
679
|
if (style === "none")
|
|
731
680
|
return {};
|
|
732
681
|
const enabled = Boolean(reasoning?.enabled);
|
|
@@ -866,18 +815,6 @@ export function buildReasoningPayload(reasoning, style, model, providerId) {
|
|
|
866
815
|
// off, so compaction would still buy hidden reasoning tokens. vLLM's
|
|
867
816
|
// StepFun template honours this explicit per-request switch.
|
|
868
817
|
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
818
|
case "nvidia": {
|
|
882
819
|
const kind = classifyNvidiaModel(model ?? "");
|
|
883
820
|
switch (kind) {
|
|
@@ -964,6 +901,8 @@ export function buildReasoningPayload(reasoning, style, model, providerId) {
|
|
|
964
901
|
* payload and retries so an unsupported option never fails the whole request.
|
|
965
902
|
*/
|
|
966
903
|
export function isReasoningUnsupportedError(error) {
|
|
904
|
+
if (isMissingReasoningContentError(error))
|
|
905
|
+
return false;
|
|
967
906
|
const status = error && typeof error === "object" && "status" in error
|
|
968
907
|
? Number(error.status)
|
|
969
908
|
: undefined;
|
|
@@ -1043,30 +982,46 @@ export function isOpenAiReasoningModel(model) {
|
|
|
1043
982
|
/(?:^|\/)o[134](?:\.|-|$)/.test(m) ||
|
|
1044
983
|
/muse-spark/.test(m));
|
|
1045
984
|
}
|
|
985
|
+
const DEFAULT_REASONING_OUTPUT_FLOOR = 16_384;
|
|
986
|
+
function outputBudgetWithReasoning(requested, options) {
|
|
987
|
+
const floor = options.control?.profile.reasoning.minOutputTokens ??
|
|
988
|
+
DEFAULT_REASONING_OUTPUT_FLOOR;
|
|
989
|
+
const ceiling = modelMaxOutputTokens(options.providerId, options.model, options.outputTokenLimit);
|
|
990
|
+
const floored = Math.max(requested, floor);
|
|
991
|
+
return ceiling !== undefined ? Math.min(floored, ceiling) : floored;
|
|
992
|
+
}
|
|
1046
993
|
function emitChatCompletionsBody(options) {
|
|
1047
994
|
// Skip reasoning knobs entirely for models observed to reject them this
|
|
1048
995
|
// session (see isReasoningUnsupportedError). This is how thinking degrades
|
|
1049
996
|
// gracefully: the request still runs, just without the unsupported option.
|
|
1050
|
-
const capabilityDeniesThinking = options.
|
|
997
|
+
const capabilityDeniesThinking = options.control === undefined &&
|
|
998
|
+
options.providerId !== undefined &&
|
|
1051
999
|
Boolean(options.reasoning?.enabled) &&
|
|
1052
1000
|
!modelSupportsThinking(options.providerId, options.model);
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1001
|
+
const legacyControlDenied = options.control === undefined &&
|
|
1002
|
+
options.providerId !== undefined &&
|
|
1003
|
+
isReasoningUnsupported(options.providerId, options.model);
|
|
1004
|
+
const reasoning = legacyControlDenied ||
|
|
1005
|
+
capabilityDeniesThinking ||
|
|
1006
|
+
options.control?.suppressed === true
|
|
1056
1007
|
? {}
|
|
1057
|
-
: buildReasoningPayload(options.reasoning, options.reasoningStyle ?? "none", options.model, options.providerId);
|
|
1008
|
+
: buildReasoningPayload(options.reasoning, options.reasoningStyle ?? "none", options.model, options.providerId, options.control);
|
|
1058
1009
|
const reasoningOn = Boolean(options.reasoning?.enabled);
|
|
1059
1010
|
// Kimchi exposes this model as `minimax-m3`; NVIDIA uses the longer
|
|
1060
1011
|
// `minimaxai/minimax-m3` ID. Both need the larger default output budget.
|
|
1061
1012
|
const isMinimaxM3 = /minimax-m3/i.test(options.model);
|
|
1062
1013
|
const defaultMaxTokens = isMinimaxM3 ? 8_192 : reasoningOn ? 8_192 : 4_096;
|
|
1063
1014
|
// One declarative sampling policy; explicit caller value wins.
|
|
1064
|
-
const sampling =
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1015
|
+
const sampling = options.resolvedSampling ?? {
|
|
1016
|
+
...resolveSampling({
|
|
1017
|
+
model: options.model,
|
|
1018
|
+
reasoningEnabled: reasoningOn,
|
|
1019
|
+
requestedTemperature: options.temperature,
|
|
1020
|
+
}),
|
|
1021
|
+
};
|
|
1069
1022
|
const reasoningModel = isOpenAiReasoningModel(options.model);
|
|
1023
|
+
const emitTemperature = sampling.temperature !== undefined &&
|
|
1024
|
+
(options.resolvedSampling !== undefined || !reasoningModel);
|
|
1070
1025
|
// Claude extended thinking via AgentRouter maps reasoning_effort to an
|
|
1071
1026
|
// Anthropic `thinking.budget_tokens`, and the gateway (Bedrock) rejects the
|
|
1072
1027
|
// request with HTTP 400 unless `max_tokens > budget_tokens`. Some upstream
|
|
@@ -1077,15 +1032,20 @@ function emitChatCompletionsBody(options) {
|
|
|
1077
1032
|
const claudeThinking = reasoningOn &&
|
|
1078
1033
|
options.reasoningStyle === "agentrouter" &&
|
|
1079
1034
|
/claude/i.test(options.model);
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1035
|
+
const requestedMaxTokens = options.maxTokens ?? defaultMaxTokens;
|
|
1036
|
+
const claudeFloored = claudeThinking
|
|
1037
|
+
? Math.max(requestedMaxTokens, 32_000)
|
|
1038
|
+
: requestedMaxTokens;
|
|
1039
|
+
const effectiveMaxTokens = reasoningOn
|
|
1040
|
+
? outputBudgetWithReasoning(claudeFloored, options)
|
|
1041
|
+
: claudeFloored;
|
|
1083
1042
|
const body = {
|
|
1084
1043
|
model: options.model,
|
|
1085
1044
|
messages: toOpenAiMessages(singleLeadingSystemMessages(options.messages), options.supportsVision, options.replayTarget
|
|
1086
1045
|
? {
|
|
1087
1046
|
target: options.replayTarget,
|
|
1088
1047
|
observe: options.reasoningArtifactReplayObserver,
|
|
1048
|
+
...(options.forceReasoningReplay ? { forceScope: true } : {}),
|
|
1089
1049
|
}
|
|
1090
1050
|
: undefined),
|
|
1091
1051
|
stream: options.stream,
|
|
@@ -1098,7 +1058,7 @@ function emitChatCompletionsBody(options) {
|
|
|
1098
1058
|
// gpt-5.x / o1 / o3 / o4 only accept the default temperature (1) and
|
|
1099
1059
|
// reject any explicit value — omit the field entirely rather than send
|
|
1100
1060
|
// our 0.2 default and get a 400.
|
|
1101
|
-
...(
|
|
1061
|
+
...(emitTemperature ? { temperature: sampling.temperature } : {}),
|
|
1102
1062
|
...reasoning,
|
|
1103
1063
|
...openAiToolBodyFields({
|
|
1104
1064
|
tools: options.tools,
|
|
@@ -1106,7 +1066,7 @@ function emitChatCompletionsBody(options) {
|
|
|
1106
1066
|
parallelToolCalls: options.parallelToolCalls,
|
|
1107
1067
|
}),
|
|
1108
1068
|
};
|
|
1109
|
-
if (
|
|
1069
|
+
if (emitTemperature && sampling.topP !== undefined) {
|
|
1110
1070
|
body.top_p = sampling.topP;
|
|
1111
1071
|
}
|
|
1112
1072
|
// OpenAI + many OpenAI-compatible gateways attach usage on the final SSE
|
|
@@ -1136,6 +1096,21 @@ export function chatCompletionsBodyFromPlan(plan, extras = {}) {
|
|
|
1136
1096
|
reasoning: plan.controls.reasoning,
|
|
1137
1097
|
reasoningStyle: extras.reasoningStyle,
|
|
1138
1098
|
supportsVision: plan.images.visionAccepted,
|
|
1099
|
+
control: {
|
|
1100
|
+
profile: {
|
|
1101
|
+
reasoning: plan.policy.reasoning,
|
|
1102
|
+
capabilities: { acceptedParameters: plan.policy.acceptedParameters },
|
|
1103
|
+
},
|
|
1104
|
+
willReplayReasoning: plan.replay.decisions.some((entry) => entry.decision.action === "replayed"),
|
|
1105
|
+
suppressed: plan.controls.controlSuppression !== undefined,
|
|
1106
|
+
},
|
|
1107
|
+
resolvedSampling: {
|
|
1108
|
+
temperature: plan.controls.temperature,
|
|
1109
|
+
topP: plan.controls.topP,
|
|
1110
|
+
},
|
|
1111
|
+
...(plan.policy.limits.outputTokens !== undefined
|
|
1112
|
+
? { outputTokenLimit: plan.policy.limits.outputTokens }
|
|
1113
|
+
: {}),
|
|
1139
1114
|
tools: plan.tools.definitions.length
|
|
1140
1115
|
? [...plan.tools.definitions]
|
|
1141
1116
|
: undefined,
|
|
@@ -1143,6 +1118,7 @@ export function chatCompletionsBodyFromPlan(plan, extras = {}) {
|
|
|
1143
1118
|
parallelToolCalls: plan.tools.parallelToolCalls,
|
|
1144
1119
|
replayTarget: plan.replay.target,
|
|
1145
1120
|
reasoningArtifactReplayObserver: extras.reasoningArtifactReplayObserver,
|
|
1121
|
+
...(extras.forceReasoningReplay ? { forceReasoningReplay: true } : {}),
|
|
1146
1122
|
});
|
|
1147
1123
|
}
|
|
1148
1124
|
function sentReasoningEffort(requestBody) {
|
|
@@ -1185,6 +1161,7 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1185
1161
|
const requestBody = chatCompletionsBodyFromPlan(plan, {
|
|
1186
1162
|
reasoningStyle: options.reasoningStyle,
|
|
1187
1163
|
reasoningArtifactReplayObserver: options.reasoningArtifactReplayObserver,
|
|
1164
|
+
...(options.forceReasoningReplay ? { forceReasoningReplay: true } : {}),
|
|
1188
1165
|
});
|
|
1189
1166
|
let response;
|
|
1190
1167
|
try {
|
|
@@ -1244,7 +1221,8 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1244
1221
|
model: options.model,
|
|
1245
1222
|
baseUrl: options.baseUrl,
|
|
1246
1223
|
toolCalls,
|
|
1247
|
-
policy: options.reasoningArtifactPolicy
|
|
1224
|
+
policy: options.reasoningArtifactPolicy ??
|
|
1225
|
+
compatibleArtifactPolicyFor(plan.policy.reasoning.finalTurnPreservation),
|
|
1248
1226
|
...(typeof reasoning === "string" && reasoning
|
|
1249
1227
|
? { reasoning: { text: reasoning, sequence: 0 } }
|
|
1250
1228
|
: {}),
|
|
@@ -1374,6 +1352,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1374
1352
|
reasoningStyle: options.reasoningStyle,
|
|
1375
1353
|
includeStreamUsage: options.includeStreamUsage,
|
|
1376
1354
|
reasoningArtifactReplayObserver: options.reasoningArtifactReplayObserver,
|
|
1355
|
+
...(options.forceReasoningReplay ? { forceReasoningReplay: true } : {}),
|
|
1377
1356
|
});
|
|
1378
1357
|
let response;
|
|
1379
1358
|
try {
|
|
@@ -1441,7 +1420,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1441
1420
|
model: options.model,
|
|
1442
1421
|
baseUrl: options.baseUrl,
|
|
1443
1422
|
toolCalls,
|
|
1444
|
-
policy: options.reasoningArtifactPolicy
|
|
1423
|
+
policy: options.reasoningArtifactPolicy ??
|
|
1424
|
+
compatibleArtifactPolicyFor(plan.policy.reasoning.finalTurnPreservation),
|
|
1445
1425
|
...(typeof reasoning === "string" && reasoning
|
|
1446
1426
|
? { reasoning: { text: reasoning, sequence: 0 } }
|
|
1447
1427
|
: {}),
|
|
@@ -1509,7 +1489,10 @@ export async function openAiCompatibleStream(options) {
|
|
|
1509
1489
|
let reasoningWireSeen = "";
|
|
1510
1490
|
let finishReason;
|
|
1511
1491
|
let terminalSignal;
|
|
1512
|
-
const terminalPolicy = options.streamTerminal ??
|
|
1492
|
+
const terminalPolicy = options.streamTerminal ??
|
|
1493
|
+
(plan.policy.terminal.proofs.length > 0
|
|
1494
|
+
? plan.policy.terminal
|
|
1495
|
+
: CHAT_COMPLETIONS_STREAM_TERMINAL);
|
|
1513
1496
|
const emittedByteCounts = () => {
|
|
1514
1497
|
let toolArgumentBytes = 0;
|
|
1515
1498
|
for (const state of toolCallState.values()) {
|
|
@@ -1545,7 +1528,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1545
1528
|
model: options.model,
|
|
1546
1529
|
baseUrl: options.baseUrl,
|
|
1547
1530
|
toolCalls,
|
|
1548
|
-
policy: options.reasoningArtifactPolicy
|
|
1531
|
+
policy: options.reasoningArtifactPolicy ??
|
|
1532
|
+
compatibleArtifactPolicyFor(plan.policy.reasoning.finalTurnPreservation),
|
|
1549
1533
|
...(reasoningSeen
|
|
1550
1534
|
? {
|
|
1551
1535
|
reasoning: {
|
|
@@ -1763,7 +1747,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1763
1747
|
const detail = typeof parsed.error === "string"
|
|
1764
1748
|
? parsed.error
|
|
1765
1749
|
: (parsed.error.message ?? parsed.error.type ?? "unknown error");
|
|
1766
|
-
throw new ProviderError(`${options.provider} stream error: ${detail}`,
|
|
1750
|
+
throw new ProviderError(`${options.provider} stream error: ${detail}`, inBandBadRequestStatus(parsed.error), payload.slice(0, 500));
|
|
1767
1751
|
}
|
|
1768
1752
|
{
|
|
1769
1753
|
const chunkUsage = options.providerId === "fireworks"
|
|
@@ -1771,6 +1755,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1771
1755
|
: parseOpenAiUsage(parsed.usage, options.usageAliases);
|
|
1772
1756
|
if (chunkUsage)
|
|
1773
1757
|
streamUsage = chunkUsage;
|
|
1758
|
+
const finalUsageFrame = isFinalUsageFrame(parsed);
|
|
1774
1759
|
const choice = parsed.choices?.[0];
|
|
1775
1760
|
const delta = choice?.delta;
|
|
1776
1761
|
const reasoningToken = delta?.reasoning_content ?? delta?.reasoning;
|
|
@@ -1790,10 +1775,17 @@ export async function openAiCompatibleStream(options) {
|
|
|
1790
1775
|
toolProgress) {
|
|
1791
1776
|
resetIdleTimer();
|
|
1792
1777
|
}
|
|
1778
|
+
if (terminalSignal === "usage-chunk" &&
|
|
1779
|
+
(token || reasoningToken || toolProgress)) {
|
|
1780
|
+
terminalSignal = undefined;
|
|
1781
|
+
}
|
|
1793
1782
|
if (choice?.finish_reason) {
|
|
1794
1783
|
finishReason = choice.finish_reason;
|
|
1795
1784
|
terminalSignal = "finish-reason";
|
|
1796
1785
|
}
|
|
1786
|
+
else if (finalUsageFrame && terminalSignal === undefined) {
|
|
1787
|
+
terminalSignal = "usage-chunk";
|
|
1788
|
+
}
|
|
1797
1789
|
if (reasoningToken) {
|
|
1798
1790
|
const normalized = normalizeChannelDelta(reasoningToken, reasoningWireSeen);
|
|
1799
1791
|
reasoningWireSeen = normalized.seen;
|