@pentoshi/clai 4.1.0 → 4.2.1
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 +14 -0
- package/dist/agent/compaction-executor.d.ts +68 -0
- package/dist/agent/compaction-executor.js +343 -0
- package/dist/agent/compaction-executor.js.map +1 -0
- package/dist/agent/context-breakdown.js +2 -0
- package/dist/agent/context-breakdown.js.map +1 -1
- package/dist/agent/context-manager.d.ts +23 -9
- package/dist/agent/context-manager.js +72 -43
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/events.d.ts +3 -0
- package/dist/agent/loop-guard.d.ts +2 -0
- package/dist/agent/loop-guard.js +48 -3
- package/dist/agent/loop-guard.js.map +1 -1
- package/dist/agent/prompt-composer.js +6 -6
- package/dist/agent/prompt-composer.js.map +1 -1
- package/dist/agent/reliability-policy.d.ts +1 -0
- package/dist/agent/reliability-policy.js +6 -3
- package/dist/agent/reliability-policy.js.map +1 -1
- package/dist/agent/request-accounting.d.ts +97 -0
- package/dist/agent/request-accounting.js +207 -0
- package/dist/agent/request-accounting.js.map +1 -0
- package/dist/agent/request-budget.d.ts +2 -2
- package/dist/agent/request-budget.js +2 -4
- package/dist/agent/request-budget.js.map +1 -1
- package/dist/agent/runner.d.ts +8 -2
- package/dist/agent/runner.js +409 -178
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session-title.d.ts +0 -26
- package/dist/agent/session-title.js +0 -80
- package/dist/agent/session-title.js.map +1 -1
- package/dist/agent/tool-history.d.ts +2 -2
- package/dist/agent/tool-history.js +19 -6
- package/dist/agent/tool-history.js.map +1 -1
- package/dist/app/adapters/agent-event-adapter.js +19 -0
- package/dist/app/adapters/agent-event-adapter.js.map +1 -1
- package/dist/app/adapters/current-agent-adapter.js +4 -0
- package/dist/app/adapters/current-agent-adapter.js.map +1 -1
- package/dist/app/commands/catalog.js +24 -0
- package/dist/app/commands/catalog.js.map +1 -1
- package/dist/app/controllers/cancel-coordinator.d.ts +51 -0
- package/dist/app/controllers/cancel-coordinator.js +48 -0
- package/dist/app/controllers/cancel-coordinator.js.map +1 -0
- package/dist/app/controllers/session-compact-helper.d.ts +19 -2
- package/dist/app/controllers/session-compact-helper.js +100 -127
- package/dist/app/controllers/session-compact-helper.js.map +1 -1
- package/dist/app/controllers/session-context-usage.d.ts +32 -15
- package/dist/app/controllers/session-context-usage.js +186 -51
- package/dist/app/controllers/session-context-usage.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +20 -11
- package/dist/app/controllers/session-controller.js +111 -90
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/session-naming.d.ts +31 -0
- package/dist/app/controllers/session-naming.js +173 -0
- package/dist/app/controllers/session-naming.js.map +1 -0
- package/dist/app/controllers/session-persistence.d.ts +2 -2
- package/dist/app/controllers/session-persistence.js +3 -6
- package/dist/app/controllers/session-persistence.js.map +1 -1
- package/dist/app/controllers/session-turn-request.d.ts +2 -1
- package/dist/app/controllers/session-turn-request.js +3 -0
- package/dist/app/controllers/session-turn-request.js.map +1 -1
- package/dist/app/controllers/turn-controller.d.ts +2 -1
- package/dist/app/controllers/turn-controller.js +1 -0
- package/dist/app/controllers/turn-controller.js.map +1 -1
- package/dist/app/events/app-event.d.ts +9 -0
- package/dist/app/events/app-event.js.map +1 -1
- package/dist/app/ports/agent-port.d.ts +8 -1
- package/dist/classic/app/ClassicApp.js +2 -1
- package/dist/classic/app/ClassicApp.js.map +1 -1
- package/dist/classic/app/app-wiring.js +1 -2
- package/dist/classic/app/app-wiring.js.map +1 -1
- package/dist/classic/app/wiring-interactions.js +2 -7
- package/dist/classic/app/wiring-interactions.js.map +1 -1
- package/dist/classic/blocks/tool-lines.js +2 -1
- package/dist/classic/blocks/tool-lines.js.map +1 -1
- package/dist/classic/input/cancel-ladder.d.ts +2 -19
- package/dist/classic/input/cancel-ladder.js +13 -20
- package/dist/classic/input/cancel-ladder.js.map +1 -1
- package/dist/commands/providers.js +2 -0
- package/dist/commands/providers.js.map +1 -1
- package/dist/llm/adapters/anthropic-tools.d.ts +31 -14
- package/dist/llm/adapters/anthropic-tools.js +124 -29
- package/dist/llm/adapters/anthropic-tools.js.map +1 -1
- package/dist/llm/adapters/gemini-tools.d.ts +16 -4
- package/dist/llm/adapters/gemini-tools.js +117 -16
- package/dist/llm/adapters/gemini-tools.js.map +1 -1
- package/dist/llm/adapters/openai-tools.d.ts +13 -2
- package/dist/llm/adapters/openai-tools.js +58 -11
- package/dist/llm/adapters/openai-tools.js.map +1 -1
- package/dist/llm/agentrouter.js +68 -45
- package/dist/llm/agentrouter.js.map +1 -1
- package/dist/llm/anthropic.js +69 -36
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/aws-mantle.js +70 -32
- package/dist/llm/aws-mantle.js.map +1 -1
- package/dist/llm/bynara.js +26 -69
- package/dist/llm/bynara.js.map +1 -1
- package/dist/llm/capabilities.d.ts +2 -1
- package/dist/llm/capabilities.js +47 -0
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/context-snapshot.d.ts +100 -0
- package/dist/llm/context-snapshot.js +277 -0
- package/dist/llm/context-snapshot.js.map +1 -0
- package/dist/llm/context-windows.d.ts +10 -0
- package/dist/llm/context-windows.js +129 -0
- package/dist/llm/context-windows.js.map +1 -0
- package/dist/llm/custom-provider-profile.d.ts +75 -0
- package/dist/llm/custom-provider-profile.js +558 -0
- package/dist/llm/custom-provider-profile.js.map +1 -0
- package/dist/llm/custom-providers.d.ts +13 -1
- package/dist/llm/custom-providers.js +77 -25
- package/dist/llm/custom-providers.js.map +1 -1
- package/dist/llm/effort-fallback.d.ts +44 -0
- package/dist/llm/effort-fallback.js +101 -0
- package/dist/llm/effort-fallback.js.map +1 -0
- package/dist/llm/fireworks.js +4 -0
- package/dist/llm/fireworks.js.map +1 -1
- package/dist/llm/free.js +4 -0
- package/dist/llm/free.js.map +1 -1
- package/dist/llm/gemini.d.ts +1 -1
- package/dist/llm/gemini.js +89 -51
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/groq.js +4 -0
- package/dist/llm/groq.js.map +1 -1
- package/dist/llm/hetzner.js +4 -0
- package/dist/llm/hetzner.js.map +1 -1
- package/dist/llm/http.d.ts +49 -10
- package/dist/llm/http.js +417 -153
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/kimchi.js +4 -0
- package/dist/llm/kimchi.js.map +1 -1
- package/dist/llm/lightning.js +4 -0
- package/dist/llm/lightning.js.map +1 -1
- package/dist/llm/meta.d.ts +2 -0
- package/dist/llm/meta.js +811 -655
- package/dist/llm/meta.js.map +1 -1
- package/dist/llm/modal.js +4 -0
- package/dist/llm/modal.js.map +1 -1
- package/dist/llm/model-families.d.ts +9 -0
- package/dist/llm/model-families.js +39 -0
- package/dist/llm/model-families.js.map +1 -0
- package/dist/llm/nvidia.js +4 -0
- package/dist/llm/nvidia.js.map +1 -1
- package/dist/llm/ollama.js +44 -40
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/openai.js +4 -0
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/openrouter.js +4 -0
- package/dist/llm/openrouter.js.map +1 -1
- package/dist/llm/operation-ledger.d.ts +60 -0
- package/dist/llm/operation-ledger.js +136 -0
- package/dist/llm/operation-ledger.js.map +1 -0
- package/dist/llm/operation-usage.d.ts +32 -0
- package/dist/llm/operation-usage.js +172 -0
- package/dist/llm/operation-usage.js.map +1 -0
- package/dist/llm/orcarouter.d.ts +7 -0
- package/dist/llm/orcarouter.js +166 -0
- package/dist/llm/orcarouter.js.map +1 -0
- package/dist/llm/provider-profile-layers.d.ts +7 -0
- package/dist/llm/provider-profile-layers.js +961 -0
- package/dist/llm/provider-profile-layers.js.map +1 -0
- package/dist/llm/provider-profile.d.ts +181 -0
- package/dist/llm/provider-profile.js +329 -0
- package/dist/llm/provider-profile.js.map +1 -0
- package/dist/llm/provider-profiles.d.ts +13 -0
- package/dist/llm/provider-profiles.js +86 -0
- package/dist/llm/provider-profiles.js.map +1 -0
- package/dist/llm/provider.d.ts +2 -0
- package/dist/llm/provider.js +67 -0
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/qwen-cloud.js +4 -0
- package/dist/llm/qwen-cloud.js.map +1 -1
- package/dist/llm/reasoning-artifacts.d.ts +101 -0
- package/dist/llm/reasoning-artifacts.js +440 -0
- package/dist/llm/reasoning-artifacts.js.map +1 -0
- package/dist/llm/request-fingerprint.d.ts +10 -0
- package/dist/llm/request-fingerprint.js +313 -0
- package/dist/llm/request-fingerprint.js.map +1 -0
- package/dist/llm/request-plan.d.ts +119 -0
- package/dist/llm/request-plan.js +279 -0
- package/dist/llm/request-plan.js.map +1 -0
- package/dist/llm/router.d.ts +19 -1
- package/dist/llm/router.js +447 -75
- package/dist/llm/router.js.map +1 -1
- package/dist/llm/stream-events.d.ts +47 -0
- package/dist/llm/stream-events.js +55 -0
- package/dist/llm/stream-events.js.map +1 -0
- package/dist/llm/stream-terminal.d.ts +30 -0
- package/dist/llm/stream-terminal.js +51 -0
- package/dist/llm/stream-terminal.js.map +1 -0
- package/dist/llm/token-estimate-calibration.d.ts +23 -0
- package/dist/llm/token-estimate-calibration.js +140 -0
- package/dist/llm/token-estimate-calibration.js.map +1 -0
- package/dist/llm/token-usage.d.ts +35 -5
- package/dist/llm/token-usage.js +212 -162
- package/dist/llm/token-usage.js.map +1 -1
- package/dist/llm/tokenrouter.js +8 -2
- package/dist/llm/tokenrouter.js.map +1 -1
- package/dist/modes/ask.js +11 -5
- package/dist/modes/ask.js.map +1 -1
- package/dist/noninteractive/start-noninteractive.js +31 -2
- package/dist/noninteractive/start-noninteractive.js.map +1 -1
- package/dist/noninteractive/stream-blocks.js +15 -4
- package/dist/noninteractive/stream-blocks.js.map +1 -1
- package/dist/prompts/embedded.js +1 -1
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/system.agent.md +6 -0
- package/dist/store/config.js +5 -1
- package/dist/store/config.js.map +1 -1
- package/dist/store/history.d.ts +2 -0
- package/dist/store/history.js +19 -10
- package/dist/store/history.js.map +1 -1
- package/dist/store/keys.js +11 -1
- package/dist/store/keys.js.map +1 -1
- package/dist/store/plan.js +20 -14
- package/dist/store/plan.js.map +1 -1
- package/dist/tools/elevated-shell.d.ts +1 -1
- package/dist/tools/elevated-shell.js +24 -72
- package/dist/tools/elevated-shell.js.map +1 -1
- package/dist/tools/nmap-runner.js +30 -36
- package/dist/tools/nmap-runner.js.map +1 -1
- package/dist/tools/sudo-session.d.ts +83 -0
- package/dist/tools/sudo-session.js +124 -0
- package/dist/tools/sudo-session.js.map +1 -0
- package/dist/tui-v2/app/App.js +9 -24
- package/dist/tui-v2/app/App.js.map +1 -1
- package/dist/tui-v2/components/status/context-limit-chip.js +2 -2
- package/dist/tui-v2/components/status/context-limit-chip.js.map +1 -1
- package/dist/tui-v2/components/transcript/intro-card.js +2 -1
- package/dist/tui-v2/components/transcript/intro-card.js.map +1 -1
- package/dist/tui-v2/composer/composer-editor.js +2 -1
- package/dist/tui-v2/composer/composer-editor.js.map +1 -1
- package/dist/types.d.ts +156 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/ui-core/bootstrap/composition-root.d.ts +2 -0
- package/dist/ui-core/bootstrap/composition-root.js +26 -3
- package/dist/ui-core/bootstrap/composition-root.js.map +1 -1
- package/dist/ui-core/commands/picker-commands.js +11 -5
- package/dist/ui-core/commands/picker-commands.js.map +1 -1
- package/dist/ui-core/commands/session-commands.js +41 -5
- package/dist/ui-core/commands/session-commands.js.map +1 -1
- package/dist/ui-core/plan/plan-lifecycle.js +3 -2
- package/dist/ui-core/plan/plan-lifecycle.js.map +1 -1
- package/dist/ui-core/rendering/context-limit.js +4 -1
- package/dist/ui-core/rendering/context-limit.js.map +1 -1
- package/dist/ui-core/rendering/duration.d.ts +2 -1
- package/dist/ui-core/rendering/duration.js +13 -5
- package/dist/ui-core/rendering/duration.js.map +1 -1
- package/dist/ui-core/rendering/intro-header.js +2 -2
- package/dist/ui-core/rendering/intro-header.js.map +1 -1
- package/dist/ui-core/rendering/theme.d.ts +1 -0
- package/dist/ui-core/rendering/theme.js +2 -0
- package/dist/ui-core/rendering/theme.js.map +1 -1
- package/dist/ui-core/state/transcript-hydrate.js +3 -3
- package/dist/ui-core/state/transcript-hydrate.js.map +1 -1
- package/dist/ui-core/state/transcript-reducer.d.ts +5 -0
- package/dist/ui-core/state/transcript-reducer.js +76 -3
- package/dist/ui-core/state/transcript-reducer.js.map +1 -1
- package/dist/ui-core/state/transcript-store.d.ts +10 -1
- package/dist/ui-core/state/transcript-store.js +22 -1
- package/dist/ui-core/state/transcript-store.js.map +1 -1
- package/dist/ui-core/state/transcript-types.d.ts +2 -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 +6 -1
package/dist/llm/http.js
CHANGED
|
@@ -2,8 +2,15 @@ import { modelAcceptsImages, modelSupportsThinking, isReasoningUnsupported, mode
|
|
|
2
2
|
import { resolveSampling } from "./sampling.js";
|
|
3
3
|
import { accumulateOpenAiToolCallDelta, finalizeOpenAiToolCalls, fromWireName, parseOpenAiMessageToolCalls, } from "./tool-protocol.js";
|
|
4
4
|
import { openAiToolBodyFields, toOpenAiToolMessages, } from "./adapters/openai-tools.js";
|
|
5
|
-
import { parseOpenAiUsage } from "./token-usage.js";
|
|
6
|
-
import {
|
|
5
|
+
import { parseFireworksUsage, parseOpenAiUsage, } from "./token-usage.js";
|
|
6
|
+
import { generationFetch } from "./operation-usage.js";
|
|
7
|
+
import { isOperationPolicyError } from "./operation-ledger.js";
|
|
8
|
+
import { emitStreamReasoningArtifacts, emitStreamReasoningDelta, } from "./stream-events.js";
|
|
9
|
+
import { CHAT_COMPLETIONS_STREAM_TERMINAL, requireTerminalProof, } from "./stream-terminal.js";
|
|
10
|
+
import { createReasoningArtifact, createReasoningArtifactProvenance, } from "./reasoning-artifacts.js";
|
|
11
|
+
import { classifyBynaraModel, classifyNvidiaModel, } from "./model-families.js";
|
|
12
|
+
import { compileRequestPlan, } from "./request-plan.js";
|
|
13
|
+
import { singleLeadingSystemMessages } from "./system-messages.js";
|
|
7
14
|
export class ProviderError extends Error {
|
|
8
15
|
status;
|
|
9
16
|
body;
|
|
@@ -190,8 +197,8 @@ export function ingestOpenAiModelCatalog(provider, payload) {
|
|
|
190
197
|
: [];
|
|
191
198
|
return ingestModelCatalogEntries(provider, entries);
|
|
192
199
|
}
|
|
193
|
-
export async function readJson(response) {
|
|
194
|
-
const text = await readBodyCapped(response, MAX_JSON_RESPONSE_BYTES);
|
|
200
|
+
export async function readJson(response, signal) {
|
|
201
|
+
const text = await readBodyCapped(response, MAX_JSON_RESPONSE_BYTES, signal);
|
|
195
202
|
if (!response.ok) {
|
|
196
203
|
let detail = "";
|
|
197
204
|
let extractedMessage = "";
|
|
@@ -285,18 +292,30 @@ export function bodyAddsInformation(bodyText, extracted) {
|
|
|
285
292
|
.trim();
|
|
286
293
|
return rest.length > 0;
|
|
287
294
|
}
|
|
288
|
-
async function readBodyCapped(response, maxBytes) {
|
|
295
|
+
async function readBodyCapped(response, maxBytes, signal) {
|
|
289
296
|
const reader = response.body?.getReader();
|
|
290
297
|
if (!reader) {
|
|
298
|
+
if (signal?.aborted) {
|
|
299
|
+
throw signal.reason ?? new Error("Response body read aborted");
|
|
300
|
+
}
|
|
291
301
|
const text = await response.text();
|
|
292
302
|
return text.length > maxBytes ? text.slice(0, maxBytes) : text;
|
|
293
303
|
}
|
|
294
304
|
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
295
305
|
let collected = "";
|
|
296
306
|
let bytesRead = 0;
|
|
307
|
+
const cancelOnAbort = () => {
|
|
308
|
+
void reader.cancel(signal?.reason).catch(() => undefined);
|
|
309
|
+
};
|
|
310
|
+
signal?.addEventListener("abort", cancelOnAbort, { once: true });
|
|
297
311
|
try {
|
|
298
312
|
while (bytesRead < maxBytes) {
|
|
299
|
-
const { done, value } =
|
|
313
|
+
const { done, value } = signal
|
|
314
|
+
? await readWithAbort(reader, signal)
|
|
315
|
+
: await reader.read();
|
|
316
|
+
if (signal?.aborted) {
|
|
317
|
+
throw signal.reason ?? new Error("Response body read aborted");
|
|
318
|
+
}
|
|
300
319
|
if (done)
|
|
301
320
|
break;
|
|
302
321
|
if (!value)
|
|
@@ -321,6 +340,7 @@ async function readBodyCapped(response, maxBytes) {
|
|
|
321
340
|
collected += decoder.decode();
|
|
322
341
|
}
|
|
323
342
|
finally {
|
|
343
|
+
signal?.removeEventListener("abort", cancelOnAbort);
|
|
324
344
|
try {
|
|
325
345
|
reader.releaseLock();
|
|
326
346
|
}
|
|
@@ -343,8 +363,8 @@ async function readBodyCapped(response, maxBytes) {
|
|
|
343
363
|
* re-generated the same prefix before one happened to finish inside the window.
|
|
344
364
|
*/
|
|
345
365
|
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 240_000;
|
|
346
|
-
export const THINKING_STREAM_IDLE_TIMEOUT_MS =
|
|
347
|
-
export const THINKING_STREAM_INITIAL_IDLE_TIMEOUT_MS =
|
|
366
|
+
export const THINKING_STREAM_IDLE_TIMEOUT_MS = 900_000;
|
|
367
|
+
export const THINKING_STREAM_INITIAL_IDLE_TIMEOUT_MS = 900_000;
|
|
348
368
|
export function streamIdleBudgets(reasoningEnabled) {
|
|
349
369
|
const idleTimeoutMs = reasoningEnabled
|
|
350
370
|
? THINKING_STREAM_IDLE_TIMEOUT_MS
|
|
@@ -528,6 +548,79 @@ export async function* readStreamLines(response, options = {}) {
|
|
|
528
548
|
}
|
|
529
549
|
}
|
|
530
550
|
}
|
|
551
|
+
const DEFAULT_COMPATIBLE_REASONING_ARTIFACT_POLICY = {
|
|
552
|
+
scope: "all-history",
|
|
553
|
+
persistence: "tool-turn",
|
|
554
|
+
};
|
|
555
|
+
function artifactRaw(value) {
|
|
556
|
+
if (typeof value === "string")
|
|
557
|
+
return value;
|
|
558
|
+
if (Array.isArray(value))
|
|
559
|
+
return value;
|
|
560
|
+
if (value && typeof value === "object") {
|
|
561
|
+
return value;
|
|
562
|
+
}
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
function compatibleReasoningArtifacts(input) {
|
|
566
|
+
const policy = input.policy ?? DEFAULT_COMPATIBLE_REASONING_ARTIFACT_POLICY;
|
|
567
|
+
const provenance = createReasoningArtifactProvenance({
|
|
568
|
+
provider: input.providerId,
|
|
569
|
+
model: input.model,
|
|
570
|
+
dialect: "openai-compatible",
|
|
571
|
+
endpoint: input.baseUrl,
|
|
572
|
+
});
|
|
573
|
+
const artifacts = [];
|
|
574
|
+
const primaryToolPosition = input.toolCalls.length > 0 ? 0 : undefined;
|
|
575
|
+
if (input.reasoning?.text) {
|
|
576
|
+
artifacts.push(createReasoningArtifact({
|
|
577
|
+
kind: "plaintext",
|
|
578
|
+
raw: input.reasoning.text,
|
|
579
|
+
displaySummary: input.reasoning.text,
|
|
580
|
+
provenance,
|
|
581
|
+
replay: policy,
|
|
582
|
+
position: {
|
|
583
|
+
sequence: input.reasoning.sequence,
|
|
584
|
+
placement: primaryToolPosition === undefined ? "assistant" : "before-tool-call",
|
|
585
|
+
...(primaryToolPosition === undefined
|
|
586
|
+
? {}
|
|
587
|
+
: { toolCallIndex: primaryToolPosition }),
|
|
588
|
+
},
|
|
589
|
+
}));
|
|
590
|
+
}
|
|
591
|
+
for (const detail of input.details ?? []) {
|
|
592
|
+
artifacts.push(createReasoningArtifact({
|
|
593
|
+
kind: "structured-details",
|
|
594
|
+
raw: detail.raw,
|
|
595
|
+
provenance,
|
|
596
|
+
replay: policy,
|
|
597
|
+
position: {
|
|
598
|
+
sequence: detail.sequence,
|
|
599
|
+
placement: primaryToolPosition === undefined ? "assistant" : "before-tool-call",
|
|
600
|
+
...(primaryToolPosition === undefined
|
|
601
|
+
? {}
|
|
602
|
+
: { toolCallIndex: primaryToolPosition }),
|
|
603
|
+
},
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
for (const signature of input.thoughtSignatures ?? []) {
|
|
607
|
+
const toolCallIndex = signature.toolCallIndex;
|
|
608
|
+
artifacts.push(createReasoningArtifact({
|
|
609
|
+
kind: "thought-signature",
|
|
610
|
+
raw: signature.raw,
|
|
611
|
+
provenance,
|
|
612
|
+
replay: toolCallIndex === undefined
|
|
613
|
+
? { scope: "none", persistence: "never" }
|
|
614
|
+
: { scope: "tool-turn", persistence: "tool-turn" },
|
|
615
|
+
position: {
|
|
616
|
+
sequence: signature.sequence,
|
|
617
|
+
placement: toolCallIndex === undefined ? "assistant" : "on-tool-call",
|
|
618
|
+
...(toolCallIndex === undefined ? {} : { toolCallIndex }),
|
|
619
|
+
},
|
|
620
|
+
}));
|
|
621
|
+
}
|
|
622
|
+
return artifacts.length ? artifacts : undefined;
|
|
623
|
+
}
|
|
531
624
|
/** Map shared OpenAI-compatible payload → CompletionResult (includes usage). */
|
|
532
625
|
export function toCompletionResult(provider, model, payload) {
|
|
533
626
|
return {
|
|
@@ -537,6 +630,10 @@ export function toCompletionResult(provider, model, payload) {
|
|
|
537
630
|
...(payload.toolCalls?.length ? { toolCalls: payload.toolCalls } : {}),
|
|
538
631
|
...(payload.finishReason ? { finishReason: payload.finishReason } : {}),
|
|
539
632
|
...(payload.usage ? { usage: payload.usage } : {}),
|
|
633
|
+
...(payload.reasoningBlock ? { reasoningBlock: payload.reasoningBlock } : {}),
|
|
634
|
+
...(payload.reasoningArtifacts
|
|
635
|
+
? { reasoningArtifacts: payload.reasoningArtifacts }
|
|
636
|
+
: {}),
|
|
540
637
|
};
|
|
541
638
|
}
|
|
542
639
|
/**
|
|
@@ -589,7 +686,7 @@ export function createSseFrameAssembler(options) {
|
|
|
589
686
|
},
|
|
590
687
|
};
|
|
591
688
|
}
|
|
592
|
-
export function toOpenAiMessages(messages, supportsVision = true) {
|
|
689
|
+
export function toOpenAiMessages(messages, supportsVision = true, replay) {
|
|
593
690
|
return toOpenAiToolMessages(messages, (message) => {
|
|
594
691
|
if (supportsVision &&
|
|
595
692
|
message.images &&
|
|
@@ -609,44 +706,12 @@ export function toOpenAiMessages(messages, supportsVision = true) {
|
|
|
609
706
|
return parts;
|
|
610
707
|
}
|
|
611
708
|
return message.content;
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
export function classifyNvidiaModel(model) {
|
|
615
|
-
const m = model.toLowerCase();
|
|
616
|
-
if (/kimi-k2(?:\.6|-thinking|-instruct)?/.test(m))
|
|
617
|
-
return "kimi-thinking";
|
|
618
|
-
if (/deepseek-v4/.test(m))
|
|
619
|
-
return "deepseek-v4";
|
|
620
|
-
// Match newer Nemotron-3 (uses enable_thinking + reasoning_budget) before
|
|
621
|
-
// the legacy Nemotron pattern below — the older `nemotron` bucket would
|
|
622
|
-
// otherwise swallow these too.
|
|
623
|
-
if (/nemotron-3/.test(m))
|
|
624
|
-
return "nemotron-3";
|
|
625
|
-
if (/glm-?[345]/.test(m))
|
|
626
|
-
return "glm-thinking";
|
|
627
|
-
if (/gemma-?[34]/.test(m))
|
|
628
|
-
return "enable-thinking";
|
|
629
|
-
if (/deepseek-(?:v3|r1)|nemotron/.test(m))
|
|
630
|
-
return "thinking";
|
|
631
|
-
if (/gpt-oss|qwen3|mistral-(?:medium|small|large)-(?:[3-9]|\d{2,})/.test(m))
|
|
632
|
-
return "effort-only";
|
|
633
|
-
return "none";
|
|
709
|
+
}, replay);
|
|
634
710
|
}
|
|
711
|
+
export { classifyBynaraModel, classifyNvidiaModel, } from "./model-families.js";
|
|
635
712
|
function supportsOpenRouterReasoning(model) {
|
|
636
713
|
return /:thinking|deepseek-r1|qwen3|kimi-k2|claude-(?:opus|sonnet|haiku)-4|gpt-5|(?:^|\/)o[134]|grok.*reasoner/i.test(model);
|
|
637
714
|
}
|
|
638
|
-
export function classifyBynaraModel(model) {
|
|
639
|
-
const m = model.toLowerCase();
|
|
640
|
-
if (/kimi/.test(m))
|
|
641
|
-
return "kimi";
|
|
642
|
-
if (/deepseek/.test(m))
|
|
643
|
-
return "deepseek";
|
|
644
|
-
if (/agnes/.test(m))
|
|
645
|
-
return "agnes";
|
|
646
|
-
if (/stepfun|step-3/.test(m))
|
|
647
|
-
return "stepfun";
|
|
648
|
-
return "none";
|
|
649
|
-
}
|
|
650
715
|
const MODAL_DEFAULT_EFFORTS = ["low", "high", "max"];
|
|
651
716
|
const MODAL_EFFORT_PREFERENCE = {
|
|
652
717
|
minimal: ["minimal", "low", "medium", "high", "max"],
|
|
@@ -978,7 +1043,7 @@ export function isOpenAiReasoningModel(model) {
|
|
|
978
1043
|
/(?:^|\/)o[134](?:\.|-|$)/.test(m) ||
|
|
979
1044
|
/muse-spark/.test(m));
|
|
980
1045
|
}
|
|
981
|
-
|
|
1046
|
+
function emitChatCompletionsBody(options) {
|
|
982
1047
|
// Skip reasoning knobs entirely for models observed to reject them this
|
|
983
1048
|
// session (see isReasoningUnsupportedError). This is how thinking degrades
|
|
984
1049
|
// gracefully: the request still runs, just without the unsupported option.
|
|
@@ -1017,8 +1082,16 @@ export function buildChatBody(options) {
|
|
|
1017
1082
|
: (options.maxTokens ?? defaultMaxTokens);
|
|
1018
1083
|
const body = {
|
|
1019
1084
|
model: options.model,
|
|
1020
|
-
messages: toOpenAiMessages(options.messages, options.supportsVision
|
|
1085
|
+
messages: toOpenAiMessages(singleLeadingSystemMessages(options.messages), options.supportsVision, options.replayTarget
|
|
1086
|
+
? {
|
|
1087
|
+
target: options.replayTarget,
|
|
1088
|
+
observe: options.reasoningArtifactReplayObserver,
|
|
1089
|
+
}
|
|
1090
|
+
: undefined),
|
|
1021
1091
|
stream: options.stream,
|
|
1092
|
+
...(options.providerId === "fireworks"
|
|
1093
|
+
? { perf_metrics_in_response: true }
|
|
1094
|
+
: {}),
|
|
1022
1095
|
...(reasoningModel
|
|
1023
1096
|
? { max_completion_tokens: effectiveMaxTokens }
|
|
1024
1097
|
: { max_tokens: effectiveMaxTokens }),
|
|
@@ -1043,6 +1116,35 @@ export function buildChatBody(options) {
|
|
|
1043
1116
|
}
|
|
1044
1117
|
return JSON.stringify(body);
|
|
1045
1118
|
}
|
|
1119
|
+
export function buildChatBody(options) {
|
|
1120
|
+
return emitChatCompletionsBody(options);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Serializes a compiled canonical plan onto the Chat Completions wire. Wire
|
|
1124
|
+
* dialect knobs that the plan intentionally does not model (gateway reasoning
|
|
1125
|
+
* style, stream-usage flag, replay observer) stay serializer-side extras.
|
|
1126
|
+
*/
|
|
1127
|
+
export function chatCompletionsBodyFromPlan(plan, extras = {}) {
|
|
1128
|
+
return emitChatCompletionsBody({
|
|
1129
|
+
model: plan.route.model,
|
|
1130
|
+
providerId: plan.route.provider,
|
|
1131
|
+
messages: [...plan.timeline.messages],
|
|
1132
|
+
maxTokens: plan.controls.requestedMaxTokens,
|
|
1133
|
+
temperature: plan.controls.temperature,
|
|
1134
|
+
stream: plan.controls.stream,
|
|
1135
|
+
includeStreamUsage: extras.includeStreamUsage,
|
|
1136
|
+
reasoning: plan.controls.reasoning,
|
|
1137
|
+
reasoningStyle: extras.reasoningStyle,
|
|
1138
|
+
supportsVision: plan.images.visionAccepted,
|
|
1139
|
+
tools: plan.tools.definitions.length
|
|
1140
|
+
? [...plan.tools.definitions]
|
|
1141
|
+
: undefined,
|
|
1142
|
+
toolChoice: plan.tools.choice,
|
|
1143
|
+
parallelToolCalls: plan.tools.parallelToolCalls,
|
|
1144
|
+
replayTarget: plan.replay.target,
|
|
1145
|
+
reasoningArtifactReplayObserver: extras.reasoningArtifactReplayObserver,
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1046
1148
|
function sentReasoningEffort(requestBody) {
|
|
1047
1149
|
try {
|
|
1048
1150
|
const body = JSON.parse(requestBody);
|
|
@@ -1066,35 +1168,27 @@ function privateReasoningNote(provider, requestBody, reasoningTokens) {
|
|
|
1066
1168
|
const effortText = effort ? ` at ${effort} effort` : "";
|
|
1067
1169
|
return `Reasoning is private on ${provider}: the model reasoned${effortText} and used ${reasoningTokens.toLocaleString("en-US")} reasoning tokens, but the API returns no reasoning text to display.`;
|
|
1068
1170
|
}
|
|
1069
|
-
function foldReasoning(provider, requestBody, text, reasoning, usage) {
|
|
1070
|
-
if (reasoning && reasoning.trim()) {
|
|
1071
|
-
return `${wrapReasoning(reasoning)}${text}`;
|
|
1072
|
-
}
|
|
1073
|
-
const tokens = usage?.reasoningTokens ?? 0;
|
|
1074
|
-
if (tokens > 0) {
|
|
1075
|
-
return `${wrapReasoning(privateReasoningNote(provider, requestBody, tokens))}${text}`;
|
|
1076
|
-
}
|
|
1077
|
-
return text;
|
|
1078
|
-
}
|
|
1079
1171
|
export async function openAiCompatibleComplete(options) {
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1172
|
+
const plan = compileRequestPlan({
|
|
1173
|
+
provider: options.providerId,
|
|
1082
1174
|
model: options.model,
|
|
1083
|
-
providerId: options.providerId,
|
|
1084
1175
|
messages: options.messages,
|
|
1085
|
-
maxTokens: options.maxTokens,
|
|
1086
|
-
temperature: options.temperature,
|
|
1087
1176
|
stream: false,
|
|
1177
|
+
endpoint: options.baseUrl,
|
|
1088
1178
|
reasoning: options.reasoning,
|
|
1089
|
-
reasoningStyle: options.reasoningStyle,
|
|
1090
|
-
supportsVision,
|
|
1091
1179
|
tools: options.tools,
|
|
1092
1180
|
toolChoice: options.toolChoice,
|
|
1093
1181
|
parallelToolCalls: options.parallelToolCalls,
|
|
1182
|
+
temperature: options.temperature,
|
|
1183
|
+
maxTokens: options.maxTokens,
|
|
1184
|
+
});
|
|
1185
|
+
const requestBody = chatCompletionsBodyFromPlan(plan, {
|
|
1186
|
+
reasoningStyle: options.reasoningStyle,
|
|
1187
|
+
reasoningArtifactReplayObserver: options.reasoningArtifactReplayObserver,
|
|
1094
1188
|
});
|
|
1095
1189
|
let response;
|
|
1096
1190
|
try {
|
|
1097
|
-
response = await
|
|
1191
|
+
response = await generationFetch(`${options.baseUrl}/chat/completions`, {
|
|
1098
1192
|
method: "POST",
|
|
1099
1193
|
signal: options.signal ?? null,
|
|
1100
1194
|
headers: {
|
|
@@ -1112,6 +1206,8 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1112
1206
|
catch (error) {
|
|
1113
1207
|
if (error instanceof Error && error.name === "AbortError")
|
|
1114
1208
|
throw error;
|
|
1209
|
+
if (isOperationPolicyError(error))
|
|
1210
|
+
throw error;
|
|
1115
1211
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1116
1212
|
throw new ProviderError(`${options.provider} request could not be sent (${msg}). Check connectivity to ${options.baseUrl}.`);
|
|
1117
1213
|
}
|
|
@@ -1134,14 +1230,42 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1134
1230
|
}
|
|
1135
1231
|
// If the API returns reasoning separately, prepend it inside <think>
|
|
1136
1232
|
// tags so the existing thinking parser can pick it up uniformly.
|
|
1137
|
-
|
|
1233
|
+
// Fireworks exposes cache metrics in headers for complete calls and can also
|
|
1234
|
+
// include them in perf_metrics. Other compatible routes use their standard
|
|
1235
|
+
// usage object plus any explicitly configured aliases.
|
|
1236
|
+
const usage = options.providerId === "fireworks"
|
|
1237
|
+
? parseFireworksUsage(data.usage, data.perf_metrics, response.headers)
|
|
1238
|
+
: parseOpenAiUsage(data.usage, options.usageAliases);
|
|
1138
1239
|
const reasoning = message?.reasoning_content ?? message?.reasoning;
|
|
1240
|
+
const detailsRaw = artifactRaw(message?.reasoning_details);
|
|
1241
|
+
const thoughtSignature = message?.extra_content?.google?.thought_signature;
|
|
1242
|
+
const reasoningArtifacts = compatibleReasoningArtifacts({
|
|
1243
|
+
providerId: options.providerId,
|
|
1244
|
+
model: options.model,
|
|
1245
|
+
baseUrl: options.baseUrl,
|
|
1246
|
+
toolCalls,
|
|
1247
|
+
policy: options.reasoningArtifactPolicy,
|
|
1248
|
+
...(typeof reasoning === "string" && reasoning
|
|
1249
|
+
? { reasoning: { text: reasoning, sequence: 0 } }
|
|
1250
|
+
: {}),
|
|
1251
|
+
...(detailsRaw ? { details: [{ raw: detailsRaw, sequence: 1 }] } : {}),
|
|
1252
|
+
...(thoughtSignature
|
|
1253
|
+
? {
|
|
1254
|
+
thoughtSignatures: [
|
|
1255
|
+
{
|
|
1256
|
+
raw: thoughtSignature,
|
|
1257
|
+
sequence: 2,
|
|
1258
|
+
...(toolCalls.length ? { toolCallIndex: 0 } : {}),
|
|
1259
|
+
},
|
|
1260
|
+
],
|
|
1261
|
+
}
|
|
1262
|
+
: {}),
|
|
1263
|
+
});
|
|
1139
1264
|
if (typeof reasoning === "string" && reasoning.trim()) {
|
|
1140
1265
|
learnModelEmitsReasoning(options.providerId, options.model);
|
|
1141
1266
|
}
|
|
1142
|
-
const full = foldReasoning(options.provider, requestBody, text, reasoning, usage);
|
|
1143
1267
|
return {
|
|
1144
|
-
text
|
|
1268
|
+
text,
|
|
1145
1269
|
...(toolCalls.length ? { toolCalls } : {}),
|
|
1146
1270
|
...(choice?.finish_reason
|
|
1147
1271
|
? { finishReason: choice.finish_reason }
|
|
@@ -1149,6 +1273,10 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1149
1273
|
? { finishReason: "tool_calls" }
|
|
1150
1274
|
: {}),
|
|
1151
1275
|
...(usage ? { usage } : {}),
|
|
1276
|
+
...(typeof reasoning === "string" && reasoning
|
|
1277
|
+
? { reasoningBlock: { text: reasoning } }
|
|
1278
|
+
: {}),
|
|
1279
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1152
1280
|
};
|
|
1153
1281
|
}
|
|
1154
1282
|
export async function openAiCompatibleStream(options) {
|
|
@@ -1229,25 +1357,27 @@ export async function openAiCompatibleStream(options) {
|
|
|
1229
1357
|
};
|
|
1230
1358
|
const onCallerAbort = () => idleController.abort(options.signal?.reason);
|
|
1231
1359
|
options.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1360
|
+
const plan = compileRequestPlan({
|
|
1361
|
+
provider: options.providerId,
|
|
1234
1362
|
model: options.model,
|
|
1235
|
-
providerId: options.providerId,
|
|
1236
1363
|
messages: options.messages,
|
|
1237
|
-
maxTokens: options.maxTokens,
|
|
1238
|
-
temperature: options.temperature,
|
|
1239
1364
|
stream: true,
|
|
1240
|
-
|
|
1365
|
+
endpoint: options.baseUrl,
|
|
1241
1366
|
reasoning: options.reasoning,
|
|
1242
|
-
reasoningStyle: options.reasoningStyle,
|
|
1243
|
-
supportsVision,
|
|
1244
1367
|
tools: options.tools,
|
|
1245
1368
|
toolChoice: options.toolChoice,
|
|
1246
1369
|
parallelToolCalls: options.parallelToolCalls,
|
|
1370
|
+
temperature: options.temperature,
|
|
1371
|
+
maxTokens: options.maxTokens,
|
|
1372
|
+
});
|
|
1373
|
+
const requestBody = chatCompletionsBodyFromPlan(plan, {
|
|
1374
|
+
reasoningStyle: options.reasoningStyle,
|
|
1375
|
+
includeStreamUsage: options.includeStreamUsage,
|
|
1376
|
+
reasoningArtifactReplayObserver: options.reasoningArtifactReplayObserver,
|
|
1247
1377
|
});
|
|
1248
1378
|
let response;
|
|
1249
1379
|
try {
|
|
1250
|
-
response = await
|
|
1380
|
+
response = await generationFetch(`${options.baseUrl}/chat/completions`, {
|
|
1251
1381
|
method: "POST",
|
|
1252
1382
|
signal: idleController.signal,
|
|
1253
1383
|
headers: {
|
|
@@ -1290,35 +1420,84 @@ export async function openAiCompatibleStream(options) {
|
|
|
1290
1420
|
}
|
|
1291
1421
|
const contentType = response.headers.get("content-type") ?? "";
|
|
1292
1422
|
if (response.status === 202 || /\bapplication\/json\b/i.test(contentType)) {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1423
|
+
try {
|
|
1424
|
+
const data = await readJson(response, idleController.signal);
|
|
1425
|
+
if (response.status === 202) {
|
|
1426
|
+
const requestId = data.requestId ?? data.id;
|
|
1427
|
+
throw new ProviderError(`${options.provider} returned a pending async response${requestId ? ` (${requestId})` : ""}; streaming did not start.`, response.status, JSON.stringify(data).slice(0, 1_000));
|
|
1428
|
+
}
|
|
1429
|
+
const choice = data.choices?.[0];
|
|
1430
|
+
const message = choice?.message;
|
|
1431
|
+
const toolCalls = parseOpenAiMessageToolCalls(message?.tool_calls);
|
|
1432
|
+
const text = message?.content ?? "";
|
|
1433
|
+
const jsonUsage = options.providerId === "fireworks"
|
|
1434
|
+
? parseFireworksUsage(data.usage, data.perf_metrics, response.headers)
|
|
1435
|
+
: parseOpenAiUsage(data.usage, options.usageAliases);
|
|
1436
|
+
const reasoning = message?.reasoning_content ?? message?.reasoning;
|
|
1437
|
+
const detailsRaw = artifactRaw(message?.reasoning_details);
|
|
1438
|
+
const thoughtSignature = message?.extra_content?.google?.thought_signature;
|
|
1439
|
+
const reasoningArtifacts = compatibleReasoningArtifacts({
|
|
1440
|
+
providerId: options.providerId,
|
|
1441
|
+
model: options.model,
|
|
1442
|
+
baseUrl: options.baseUrl,
|
|
1443
|
+
toolCalls,
|
|
1444
|
+
policy: options.reasoningArtifactPolicy,
|
|
1445
|
+
...(typeof reasoning === "string" && reasoning
|
|
1446
|
+
? { reasoning: { text: reasoning, sequence: 0 } }
|
|
1447
|
+
: {}),
|
|
1448
|
+
...(detailsRaw ? { details: [{ raw: detailsRaw, sequence: 1 }] } : {}),
|
|
1449
|
+
...(thoughtSignature
|
|
1450
|
+
? {
|
|
1451
|
+
thoughtSignatures: [
|
|
1452
|
+
{
|
|
1453
|
+
raw: thoughtSignature,
|
|
1454
|
+
sequence: 2,
|
|
1455
|
+
...(toolCalls.length ? { toolCallIndex: 0 } : {}),
|
|
1456
|
+
},
|
|
1457
|
+
],
|
|
1458
|
+
}
|
|
1459
|
+
: {}),
|
|
1460
|
+
});
|
|
1461
|
+
if (text.trim() ||
|
|
1462
|
+
toolCalls.length > 0 ||
|
|
1463
|
+
(typeof reasoning === "string" && reasoning.trim())) {
|
|
1464
|
+
emitStreamReasoningArtifacts(options.onStreamEvent, reasoningArtifacts);
|
|
1465
|
+
if (text)
|
|
1466
|
+
options.onToken(text);
|
|
1467
|
+
return {
|
|
1468
|
+
text,
|
|
1469
|
+
...(toolCalls.length ? { toolCalls } : {}),
|
|
1470
|
+
...(choice?.finish_reason
|
|
1471
|
+
? { finishReason: choice.finish_reason }
|
|
1472
|
+
: toolCalls.length
|
|
1473
|
+
? { finishReason: "tool_calls" }
|
|
1474
|
+
: {}),
|
|
1475
|
+
...(jsonUsage ? { usage: jsonUsage } : {}),
|
|
1476
|
+
...(typeof reasoning === "string" && reasoning
|
|
1477
|
+
? { reasoningBlock: { text: reasoning } }
|
|
1317
1478
|
: {}),
|
|
1318
|
-
|
|
1319
|
-
|
|
1479
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
throw new ProviderError(`${options.provider} returned JSON instead of an SSE stream, but no completion text was present.`, response.status, JSON.stringify(data).slice(0, 1_000));
|
|
1483
|
+
}
|
|
1484
|
+
catch (error) {
|
|
1485
|
+
if (idleFired) {
|
|
1486
|
+
const seconds = Math.round(firedBudgetMs / 1000);
|
|
1487
|
+
if (firedWatchdog === "transport" || !sawTransportActivity) {
|
|
1488
|
+
if (!sawTransportActivity) {
|
|
1489
|
+
throw new ProviderError(`${options.provider} request timed out before any response (${seconds}s) — no data arrived on the connection.`);
|
|
1490
|
+
}
|
|
1491
|
+
throw new ProviderError(`${options.provider} stream transport timeout (${seconds}s) — no data arrived on the connection after it had started.`);
|
|
1492
|
+
}
|
|
1493
|
+
throw new ProviderError(`${options.provider} stream stalled — ${STREAM_STALL_MARKER} for ${seconds}s`);
|
|
1494
|
+
}
|
|
1495
|
+
throw error;
|
|
1496
|
+
}
|
|
1497
|
+
finally {
|
|
1498
|
+
clearIdleTimers();
|
|
1499
|
+
options.signal?.removeEventListener("abort", onCallerAbort);
|
|
1320
1500
|
}
|
|
1321
|
-
throw new ProviderError(`${options.provider} returned JSON instead of an SSE stream, but no completion text was present.`, response.status, JSON.stringify(data).slice(0, 1_000));
|
|
1322
1501
|
}
|
|
1323
1502
|
const decoder = new TextDecoder();
|
|
1324
1503
|
const reader = response.body.getReader();
|
|
@@ -1328,30 +1507,63 @@ export async function openAiCompatibleStream(options) {
|
|
|
1328
1507
|
let reasoningSeen = "";
|
|
1329
1508
|
let contentWireSeen = "";
|
|
1330
1509
|
let reasoningWireSeen = "";
|
|
1331
|
-
let inReasoning = false;
|
|
1332
1510
|
let finishReason;
|
|
1333
|
-
let
|
|
1511
|
+
let terminalSignal;
|
|
1512
|
+
const terminalPolicy = options.streamTerminal ?? CHAT_COMPLETIONS_STREAM_TERMINAL;
|
|
1513
|
+
const emittedByteCounts = () => {
|
|
1514
|
+
let toolArgumentBytes = 0;
|
|
1515
|
+
for (const state of toolCallState.values()) {
|
|
1516
|
+
toolArgumentBytes += state.arguments.length;
|
|
1517
|
+
}
|
|
1518
|
+
return {
|
|
1519
|
+
answerBytes: visible.length,
|
|
1520
|
+
reasoningBytes: reasoningSeen.length,
|
|
1521
|
+
toolArgumentBytes,
|
|
1522
|
+
};
|
|
1523
|
+
};
|
|
1524
|
+
let streamUsage = options.providerId === "fireworks"
|
|
1525
|
+
? parseFireworksUsage(undefined, undefined, response.headers)
|
|
1526
|
+
: undefined;
|
|
1334
1527
|
const toolCallState = new Map();
|
|
1528
|
+
let reasoningArtifactSequence;
|
|
1529
|
+
let nextArtifactSequence = 0;
|
|
1530
|
+
let lastToolCallIndex;
|
|
1531
|
+
const structuredDetails = [];
|
|
1532
|
+
const thoughtSignatures = [];
|
|
1533
|
+
const pendingThoughtSignatures = [];
|
|
1534
|
+
const finalReasoningArtifacts = (toolCalls) => {
|
|
1535
|
+
if (pendingThoughtSignatures.length) {
|
|
1536
|
+
const toolCallIndex = toolCalls.length ? 0 : undefined;
|
|
1537
|
+
for (const capture of pendingThoughtSignatures.splice(0)) {
|
|
1538
|
+
thoughtSignatures.push(toolCallIndex === undefined
|
|
1539
|
+
? capture
|
|
1540
|
+
: { ...capture, toolCallIndex });
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return compatibleReasoningArtifacts({
|
|
1544
|
+
providerId: options.providerId,
|
|
1545
|
+
model: options.model,
|
|
1546
|
+
baseUrl: options.baseUrl,
|
|
1547
|
+
toolCalls,
|
|
1548
|
+
policy: options.reasoningArtifactPolicy,
|
|
1549
|
+
...(reasoningSeen
|
|
1550
|
+
? {
|
|
1551
|
+
reasoning: {
|
|
1552
|
+
text: reasoningSeen,
|
|
1553
|
+
sequence: reasoningArtifactSequence ?? 0,
|
|
1554
|
+
},
|
|
1555
|
+
}
|
|
1556
|
+
: {}),
|
|
1557
|
+
...(structuredDetails.length ? { details: structuredDetails } : {}),
|
|
1558
|
+
...(thoughtSignatures.length ? { thoughtSignatures } : {}),
|
|
1559
|
+
});
|
|
1560
|
+
};
|
|
1335
1561
|
const normalizeChannelDelta = (token, seen) => {
|
|
1336
1562
|
if (seen.length >= 64 && token.length >= seen.length && token.startsWith(seen)) {
|
|
1337
1563
|
return { delta: token.slice(seen.length), seen: token };
|
|
1338
1564
|
}
|
|
1339
1565
|
return { delta: token, seen: seen + token };
|
|
1340
1566
|
};
|
|
1341
|
-
const enterReasoning = () => {
|
|
1342
|
-
if (inReasoning)
|
|
1343
|
-
return;
|
|
1344
|
-
inReasoning = true;
|
|
1345
|
-
full += REASONING_OPEN;
|
|
1346
|
-
options.onToken(REASONING_OPEN);
|
|
1347
|
-
};
|
|
1348
|
-
const exitReasoning = () => {
|
|
1349
|
-
if (!inReasoning)
|
|
1350
|
-
return;
|
|
1351
|
-
inReasoning = false;
|
|
1352
|
-
full += REASONING_CLOSE;
|
|
1353
|
-
options.onToken(REASONING_CLOSE);
|
|
1354
|
-
};
|
|
1355
1567
|
const emitPrivateReasoningNote = (hasToolCalls) => {
|
|
1356
1568
|
if (reasoningSeen.trim())
|
|
1357
1569
|
return;
|
|
@@ -1361,10 +1573,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1361
1573
|
if (tokens <= 0)
|
|
1362
1574
|
return;
|
|
1363
1575
|
const note = privateReasoningNote(options.provider, requestBody, tokens);
|
|
1364
|
-
|
|
1365
|
-
full += note;
|
|
1366
|
-
options.onToken(note);
|
|
1367
|
-
exitReasoning();
|
|
1576
|
+
emitStreamReasoningDelta(options.onStreamEvent, note);
|
|
1368
1577
|
};
|
|
1369
1578
|
/**
|
|
1370
1579
|
* Reasoning-echo suppression.
|
|
@@ -1388,8 +1597,6 @@ export async function openAiCompatibleStream(options) {
|
|
|
1388
1597
|
const emitVisible = (text) => {
|
|
1389
1598
|
if (!text)
|
|
1390
1599
|
return;
|
|
1391
|
-
if (inReasoning)
|
|
1392
|
-
exitReasoning();
|
|
1393
1600
|
visible += text;
|
|
1394
1601
|
full += text;
|
|
1395
1602
|
options.onToken(text);
|
|
@@ -1402,9 +1609,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1402
1609
|
const emitReasoningEcho = (text) => {
|
|
1403
1610
|
if (!text)
|
|
1404
1611
|
return;
|
|
1405
|
-
|
|
1406
|
-
full += text;
|
|
1407
|
-
options.onToken(text);
|
|
1612
|
+
emitStreamReasoningDelta(options.onStreamEvent, text);
|
|
1408
1613
|
};
|
|
1409
1614
|
/** Release a still-undecided hold-back as the answer (stream ended early). */
|
|
1410
1615
|
const flushEchoBuffer = () => {
|
|
@@ -1498,11 +1703,19 @@ export async function openAiCompatibleStream(options) {
|
|
|
1498
1703
|
if (payload === undefined)
|
|
1499
1704
|
continue;
|
|
1500
1705
|
if (payload === "[DONE]") {
|
|
1706
|
+
terminalSignal = "done-sentinel";
|
|
1707
|
+
requireTerminalProof({
|
|
1708
|
+
provider: options.provider,
|
|
1709
|
+
policy: terminalPolicy,
|
|
1710
|
+
signal: terminalSignal,
|
|
1711
|
+
...emittedByteCounts(),
|
|
1712
|
+
});
|
|
1501
1713
|
flushEchoBuffer();
|
|
1502
|
-
exitReasoning();
|
|
1503
1714
|
cleanup();
|
|
1504
1715
|
const toolCalls = finalizeOpenAiToolCalls(toolCallState);
|
|
1505
1716
|
emitPrivateReasoningNote(toolCalls.length > 0);
|
|
1717
|
+
const reasoningArtifacts = finalReasoningArtifacts(toolCalls);
|
|
1718
|
+
emitStreamReasoningArtifacts(options.onStreamEvent, reasoningArtifacts);
|
|
1506
1719
|
if (!visible.trim() && toolCalls.length === 0) {
|
|
1507
1720
|
// Thinking models (Kimi/Moonshot via Mantle, etc.) often emit only
|
|
1508
1721
|
// reasoning, sometimes with tool sentinels inside <think>. Returning
|
|
@@ -1513,6 +1726,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1513
1726
|
text: full,
|
|
1514
1727
|
...(finishReason ? { finishReason } : { finishReason: "stop" }),
|
|
1515
1728
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1729
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1730
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1516
1731
|
};
|
|
1517
1732
|
}
|
|
1518
1733
|
throw new ProviderError(`${options.provider} completed without a visible answer.`);
|
|
@@ -1526,6 +1741,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1526
1741
|
? { finishReason: "tool_calls" }
|
|
1527
1742
|
: {}),
|
|
1528
1743
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1744
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1745
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1529
1746
|
};
|
|
1530
1747
|
}
|
|
1531
1748
|
let parsed;
|
|
@@ -1549,13 +1766,18 @@ export async function openAiCompatibleStream(options) {
|
|
|
1549
1766
|
throw new ProviderError(`${options.provider} stream error: ${detail}`, undefined, payload.slice(0, 500));
|
|
1550
1767
|
}
|
|
1551
1768
|
{
|
|
1552
|
-
const chunkUsage =
|
|
1769
|
+
const chunkUsage = options.providerId === "fireworks"
|
|
1770
|
+
? parseFireworksUsage(parsed.usage, parsed.perf_metrics, response.headers)
|
|
1771
|
+
: parseOpenAiUsage(parsed.usage, options.usageAliases);
|
|
1553
1772
|
if (chunkUsage)
|
|
1554
1773
|
streamUsage = chunkUsage;
|
|
1555
1774
|
const choice = parsed.choices?.[0];
|
|
1556
1775
|
const delta = choice?.delta;
|
|
1557
1776
|
const reasoningToken = delta?.reasoning_content ?? delta?.reasoning;
|
|
1558
1777
|
const token = delta?.content;
|
|
1778
|
+
const detailRaw = artifactRaw(delta?.reasoning_details);
|
|
1779
|
+
const thoughtSignature = delta?.extra_content?.google?.thought_signature;
|
|
1780
|
+
const artifactSequence = nextArtifactSequence++;
|
|
1559
1781
|
const toolProgress = delta?.tool_calls?.some((toolCall) => Boolean(toolCall.id ||
|
|
1560
1782
|
toolCall.function?.name ||
|
|
1561
1783
|
toolCall.function?.arguments));
|
|
@@ -1563,63 +1785,101 @@ export async function openAiCompatibleStream(options) {
|
|
|
1563
1785
|
chunkUsage ||
|
|
1564
1786
|
reasoningToken ||
|
|
1565
1787
|
token ||
|
|
1788
|
+
detailRaw ||
|
|
1789
|
+
thoughtSignature ||
|
|
1566
1790
|
toolProgress) {
|
|
1567
1791
|
resetIdleTimer();
|
|
1568
1792
|
}
|
|
1569
|
-
if (choice?.finish_reason)
|
|
1793
|
+
if (choice?.finish_reason) {
|
|
1570
1794
|
finishReason = choice.finish_reason;
|
|
1795
|
+
terminalSignal = "finish-reason";
|
|
1796
|
+
}
|
|
1571
1797
|
if (reasoningToken) {
|
|
1572
1798
|
const normalized = normalizeChannelDelta(reasoningToken, reasoningWireSeen);
|
|
1573
1799
|
reasoningWireSeen = normalized.seen;
|
|
1574
1800
|
if (normalized.delta) {
|
|
1801
|
+
reasoningArtifactSequence ??= artifactSequence;
|
|
1575
1802
|
if (!reasoningSeen) {
|
|
1576
1803
|
learnModelEmitsReasoning(options.providerId, options.model);
|
|
1577
1804
|
}
|
|
1578
|
-
enterReasoning();
|
|
1579
1805
|
reasoningSeen += normalized.delta;
|
|
1580
|
-
|
|
1581
|
-
options.onToken(normalized.delta);
|
|
1806
|
+
emitStreamReasoningDelta(options.onStreamEvent, normalized.delta);
|
|
1582
1807
|
}
|
|
1583
1808
|
}
|
|
1809
|
+
if (detailRaw) {
|
|
1810
|
+
structuredDetails.push({ raw: detailRaw, sequence: artifactSequence });
|
|
1811
|
+
}
|
|
1584
1812
|
if (token) {
|
|
1585
1813
|
const normalized = normalizeChannelDelta(token, contentWireSeen);
|
|
1586
1814
|
contentWireSeen = normalized.seen;
|
|
1587
1815
|
if (normalized.delta)
|
|
1588
1816
|
handleContentToken(normalized.delta);
|
|
1589
1817
|
}
|
|
1818
|
+
const deltaToolCallIndices = [];
|
|
1590
1819
|
if (delta?.tool_calls?.length) {
|
|
1591
1820
|
for (const tc of delta.tool_calls) {
|
|
1592
1821
|
const accInfo = accumulateOpenAiToolCallDelta(toolCallState, tc);
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
accInfo.
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1822
|
+
deltaToolCallIndices.push(accInfo.index);
|
|
1823
|
+
lastToolCallIndex = accInfo.index;
|
|
1824
|
+
if (options.onToolCallDelta) {
|
|
1825
|
+
const wire = accInfo.name;
|
|
1826
|
+
const canonical = wire
|
|
1827
|
+
? (fromWireName(wire) ?? wire)
|
|
1828
|
+
: undefined;
|
|
1829
|
+
const largeArgTick = !accInfo.nameBecameKnown &&
|
|
1830
|
+
accInfo.argumentsBytes > 0 &&
|
|
1831
|
+
accInfo.argumentsBytes % 4096 <
|
|
1832
|
+
(typeof tc.function?.arguments === "string"
|
|
1833
|
+
? tc.function.arguments.length
|
|
1834
|
+
: 0);
|
|
1835
|
+
if (accInfo.nameBecameKnown || largeArgTick) {
|
|
1836
|
+
options.onToolCallDelta({
|
|
1837
|
+
index: accInfo.index,
|
|
1838
|
+
...(accInfo.id !== undefined ? { id: accInfo.id } : {}),
|
|
1839
|
+
...(canonical !== undefined ? { name: canonical } : {}),
|
|
1840
|
+
argumentsBytes: accInfo.argumentsBytes,
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1612
1843
|
}
|
|
1613
1844
|
}
|
|
1614
1845
|
}
|
|
1846
|
+
const signatureToolCallIndex = deltaToolCallIndices[0] ?? lastToolCallIndex;
|
|
1847
|
+
if (thoughtSignature) {
|
|
1848
|
+
const capture = {
|
|
1849
|
+
raw: thoughtSignature,
|
|
1850
|
+
sequence: artifactSequence,
|
|
1851
|
+
...(signatureToolCallIndex === undefined
|
|
1852
|
+
? {}
|
|
1853
|
+
: { toolCallIndex: signatureToolCallIndex }),
|
|
1854
|
+
};
|
|
1855
|
+
if (signatureToolCallIndex === undefined) {
|
|
1856
|
+
pendingThoughtSignatures.push(capture);
|
|
1857
|
+
}
|
|
1858
|
+
else {
|
|
1859
|
+
thoughtSignatures.push(capture);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
if (deltaToolCallIndices.length && pendingThoughtSignatures.length) {
|
|
1863
|
+
const toolCallIndex = deltaToolCallIndices[0];
|
|
1864
|
+
for (const capture of pendingThoughtSignatures.splice(0)) {
|
|
1865
|
+
thoughtSignatures.push({ ...capture, toolCallIndex });
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1615
1868
|
}
|
|
1616
1869
|
}
|
|
1617
1870
|
}
|
|
1618
1871
|
flushEchoBuffer();
|
|
1619
|
-
exitReasoning();
|
|
1620
1872
|
cleanup();
|
|
1873
|
+
requireTerminalProof({
|
|
1874
|
+
provider: options.provider,
|
|
1875
|
+
policy: terminalPolicy,
|
|
1876
|
+
signal: terminalSignal,
|
|
1877
|
+
...emittedByteCounts(),
|
|
1878
|
+
});
|
|
1621
1879
|
const toolCalls = finalizeOpenAiToolCalls(toolCallState);
|
|
1622
1880
|
emitPrivateReasoningNote(toolCalls.length > 0);
|
|
1881
|
+
const reasoningArtifacts = finalReasoningArtifacts(toolCalls);
|
|
1882
|
+
emitStreamReasoningArtifacts(options.onStreamEvent, reasoningArtifacts);
|
|
1623
1883
|
if (!visible.trim() && toolCalls.length === 0) {
|
|
1624
1884
|
// See [DONE] branch — hand thinking-only streams to the runner so it can
|
|
1625
1885
|
// salvage sentinel tool blocks (or nudge) instead of hard-failing.
|
|
@@ -1628,6 +1888,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1628
1888
|
text: full,
|
|
1629
1889
|
...(finishReason ? { finishReason } : { finishReason: "stop" }),
|
|
1630
1890
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1891
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1892
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1631
1893
|
};
|
|
1632
1894
|
}
|
|
1633
1895
|
throw new ProviderError(`${options.provider} completed without a visible answer.`);
|
|
@@ -1641,6 +1903,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1641
1903
|
? { finishReason: "tool_calls" }
|
|
1642
1904
|
: {}),
|
|
1643
1905
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1906
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1907
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1644
1908
|
};
|
|
1645
1909
|
}
|
|
1646
1910
|
catch (error) {
|