@pentoshi/clai 4.0.3 → 4.2.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 +45 -9
- 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/confirm-port.js +6 -3
- package/dist/agent/confirm-port.js.map +1 -1
- 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 +412 -181
- 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/agent/turn-outcome.d.ts +1 -1
- package/dist/agent/turn-outcome.js +2 -12
- package/dist/agent/turn-outcome.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 +23 -16
- 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 +22 -11
- package/dist/app/controllers/session-controller.js +137 -92
- 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-continuation.d.ts +0 -6
- package/dist/app/controllers/turn-continuation.js +4 -21
- package/dist/app/controllers/turn-continuation.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/app/ports/transcript-item.d.ts +9 -1
- package/dist/classic/app/ClassicApp.js +3 -3
- 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 +4 -9
- package/dist/classic/app/wiring-interactions.js.map +1 -1
- package/dist/classic/blocks/TurnSummaryBlock.d.ts +3 -0
- package/dist/classic/blocks/TurnSummaryBlock.js +6 -0
- package/dist/classic/blocks/TurnSummaryBlock.js.map +1 -0
- package/dist/classic/blocks/tool-lines.js +2 -1
- package/dist/classic/blocks/tool-lines.js.map +1 -1
- package/dist/classic/blocks/turn-summary-lines.d.ts +3 -0
- package/dist/classic/blocks/turn-summary-lines.js +7 -0
- package/dist/classic/blocks/turn-summary-lines.js.map +1 -0
- package/dist/classic/chrome/status-rows.d.ts +0 -1
- package/dist/classic/chrome/status-rows.js +0 -12
- package/dist/classic/chrome/status-rows.js.map +1 -1
- package/dist/classic/feed/Feed.js +2 -0
- package/dist/classic/feed/Feed.js.map +1 -1
- package/dist/classic/feed/feed-blocks.d.ts +1 -1
- package/dist/classic/feed/feed-blocks.js +3 -0
- package/dist/classic/feed/feed-blocks.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 +56 -1
- 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.d.ts +3 -0
- package/dist/llm/fireworks.js +103 -0
- package/dist/llm/fireworks.js.map +1 -0
- 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.d.ts +3 -0
- package/dist/llm/hetzner.js +98 -0
- package/dist/llm/hetzner.js.map +1 -0
- package/dist/llm/http.d.ts +60 -10
- package/dist/llm/http.js +465 -156
- 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 +839 -624
- 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 +198 -21
- 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 +504 -102
- 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 +13 -6
- 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 +7 -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/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/fs.d.ts +5 -3
- package/dist/tools/fs.js +8 -5
- package/dist/tools/fs.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/plan/plan-view.js +5 -3
- package/dist/tui-v2/components/plan/plan-view.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/status/status-line.js +1 -14
- package/dist/tui-v2/components/status/status-line.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/components/transcript/notice-row.d.ts +3 -1
- package/dist/tui-v2/components/transcript/notice-row.js +28 -3
- package/dist/tui-v2/components/transcript/notice-row.js.map +1 -1
- package/dist/tui-v2/components/transcript/thinking-block.d.ts +0 -5
- package/dist/tui-v2/components/transcript/thinking-block.js +3 -8
- package/dist/tui-v2/components/transcript/thinking-block.js.map +1 -1
- package/dist/tui-v2/components/transcript/tool-card.js +16 -7
- package/dist/tui-v2/components/transcript/tool-card.js.map +1 -1
- package/dist/tui-v2/components/transcript/transcript-row.d.ts +0 -2
- package/dist/tui-v2/components/transcript/transcript-row.js +7 -3
- package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -1
- package/dist/tui-v2/components/transcript/transcript-view.js +1 -3
- package/dist/tui-v2/components/transcript/transcript-view.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 +3 -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 +5 -0
- package/dist/ui-core/rendering/duration.js +43 -0
- package/dist/ui-core/rendering/duration.js.map +1 -0
- 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/rendering/transcript-semantic.js +2 -0
- package/dist/ui-core/rendering/transcript-semantic.js.map +1 -1
- package/dist/ui-core/state/transcript-compaction.js +2 -0
- package/dist/ui-core/state/transcript-compaction.js.map +1 -1
- package/dist/ui-core/state/transcript-hydrate.js +34 -4
- 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 +96 -8
- 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 +9 -1
- package/dist/ui-core/state/transcript-types.js +3 -1
- 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,10 +197,11 @@ 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 = "";
|
|
204
|
+
let extractedMessage = "";
|
|
197
205
|
try {
|
|
198
206
|
const body = JSON.parse(text);
|
|
199
207
|
const error = body.error;
|
|
@@ -223,10 +231,23 @@ export async function readJson(response) {
|
|
|
223
231
|
detail = ` — ${msg}`;
|
|
224
232
|
}
|
|
225
233
|
}
|
|
234
|
+
extractedMessage = msg;
|
|
226
235
|
}
|
|
227
236
|
catch {
|
|
228
|
-
|
|
229
|
-
|
|
237
|
+
// Non-JSON body — the raw text is surfaced via the full-response rule
|
|
238
|
+
// below, so nothing is lost here.
|
|
239
|
+
}
|
|
240
|
+
// Show the full error received from the request: the extracted
|
|
241
|
+
// `error.message` alone routinely hides the actionable part of a gateway
|
|
242
|
+
// error (upstream detail, codes, validation fields). Append the complete
|
|
243
|
+
// body whenever it carries anything beyond that extracted message.
|
|
244
|
+
const bodyText = text.slice(0, MAX_ERROR_BODY_CHARS);
|
|
245
|
+
if (bodyAddsInformation(bodyText, extractedMessage)) {
|
|
246
|
+
const shown = collapseWhitespace(bodyText);
|
|
247
|
+
const capped = shown.length > MAX_ERROR_BODY_IN_MESSAGE_CHARS
|
|
248
|
+
? `${shown.slice(0, MAX_ERROR_BODY_IN_MESSAGE_CHARS)}…`
|
|
249
|
+
: shown;
|
|
250
|
+
detail = `${detail} — full response: ${capped}`;
|
|
230
251
|
}
|
|
231
252
|
const retryAfterSeconds = parseRetryAfterHeader(response.headers.get("retry-after")) ??
|
|
232
253
|
parseRetryHintFromBody(text);
|
|
@@ -234,24 +255,67 @@ export async function readJson(response) {
|
|
|
234
255
|
? ` (retry after ${Math.ceil(retryAfterSeconds)}s)`
|
|
235
256
|
: "";
|
|
236
257
|
const codeHint = statusCodeHint(response.status);
|
|
237
|
-
throw new ProviderError(`Provider request failed with HTTP ${response.status}${retryHint}${detail}${codeHint}`, response.status,
|
|
258
|
+
throw new ProviderError(`Provider request failed with HTTP ${response.status}${retryHint}${detail}${codeHint}`, response.status, bodyText, retryAfterSeconds);
|
|
238
259
|
}
|
|
239
260
|
return JSON.parse(text);
|
|
240
261
|
}
|
|
241
262
|
/** Hard cap on a JSON response body so a misbehaving provider can't OOM us. */
|
|
242
263
|
const MAX_JSON_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
243
|
-
|
|
264
|
+
/** How much of a provider error body we retain on the ProviderError. */
|
|
265
|
+
export const MAX_ERROR_BODY_CHARS = 8_000;
|
|
266
|
+
/** How much of that body is embedded in the user-visible error message. */
|
|
267
|
+
export const MAX_ERROR_BODY_IN_MESSAGE_CHARS = 2_000;
|
|
268
|
+
export function collapseWhitespace(text) {
|
|
269
|
+
return text.replace(/\s+/g, " ").trim();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* True when the raw response body carries information beyond the message we
|
|
273
|
+
* already extracted from it (extra JSON fields, non-JSON payloads, …). Pure
|
|
274
|
+
* `{"error":{"message": …}}` envelopes are redundant and stay compact.
|
|
275
|
+
*/
|
|
276
|
+
export function bodyAddsInformation(bodyText, extracted) {
|
|
277
|
+
const collapsed = collapseWhitespace(bodyText);
|
|
278
|
+
if (!collapsed)
|
|
279
|
+
return false;
|
|
280
|
+
if (!extracted)
|
|
281
|
+
return true;
|
|
282
|
+
const normalizedExtracted = collapseWhitespace(extracted);
|
|
283
|
+
// The user-facing text already carries the entire body verbatim.
|
|
284
|
+
if (normalizedExtracted.includes(collapsed))
|
|
285
|
+
return false;
|
|
286
|
+
if (!collapsed.includes(normalizedExtracted))
|
|
287
|
+
return true;
|
|
288
|
+
const rest = collapsed
|
|
289
|
+
.replace(normalizedExtracted, " ")
|
|
290
|
+
.replace(/\b(errors?|message|msg|detail|details|type|code|status)\b/gi, " ")
|
|
291
|
+
.replace(/[{}\[\]":,]/g, " ")
|
|
292
|
+
.trim();
|
|
293
|
+
return rest.length > 0;
|
|
294
|
+
}
|
|
295
|
+
async function readBodyCapped(response, maxBytes, signal) {
|
|
244
296
|
const reader = response.body?.getReader();
|
|
245
297
|
if (!reader) {
|
|
298
|
+
if (signal?.aborted) {
|
|
299
|
+
throw signal.reason ?? new Error("Response body read aborted");
|
|
300
|
+
}
|
|
246
301
|
const text = await response.text();
|
|
247
302
|
return text.length > maxBytes ? text.slice(0, maxBytes) : text;
|
|
248
303
|
}
|
|
249
304
|
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
250
305
|
let collected = "";
|
|
251
306
|
let bytesRead = 0;
|
|
307
|
+
const cancelOnAbort = () => {
|
|
308
|
+
void reader.cancel(signal?.reason).catch(() => undefined);
|
|
309
|
+
};
|
|
310
|
+
signal?.addEventListener("abort", cancelOnAbort, { once: true });
|
|
252
311
|
try {
|
|
253
312
|
while (bytesRead < maxBytes) {
|
|
254
|
-
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
|
+
}
|
|
255
319
|
if (done)
|
|
256
320
|
break;
|
|
257
321
|
if (!value)
|
|
@@ -276,6 +340,7 @@ async function readBodyCapped(response, maxBytes) {
|
|
|
276
340
|
collected += decoder.decode();
|
|
277
341
|
}
|
|
278
342
|
finally {
|
|
343
|
+
signal?.removeEventListener("abort", cancelOnAbort);
|
|
279
344
|
try {
|
|
280
345
|
reader.releaseLock();
|
|
281
346
|
}
|
|
@@ -298,8 +363,8 @@ async function readBodyCapped(response, maxBytes) {
|
|
|
298
363
|
* re-generated the same prefix before one happened to finish inside the window.
|
|
299
364
|
*/
|
|
300
365
|
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 240_000;
|
|
301
|
-
export const THINKING_STREAM_IDLE_TIMEOUT_MS =
|
|
302
|
-
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;
|
|
303
368
|
export function streamIdleBudgets(reasoningEnabled) {
|
|
304
369
|
const idleTimeoutMs = reasoningEnabled
|
|
305
370
|
? THINKING_STREAM_IDLE_TIMEOUT_MS
|
|
@@ -483,6 +548,79 @@ export async function* readStreamLines(response, options = {}) {
|
|
|
483
548
|
}
|
|
484
549
|
}
|
|
485
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
|
+
}
|
|
486
624
|
/** Map shared OpenAI-compatible payload → CompletionResult (includes usage). */
|
|
487
625
|
export function toCompletionResult(provider, model, payload) {
|
|
488
626
|
return {
|
|
@@ -492,6 +630,10 @@ export function toCompletionResult(provider, model, payload) {
|
|
|
492
630
|
...(payload.toolCalls?.length ? { toolCalls: payload.toolCalls } : {}),
|
|
493
631
|
...(payload.finishReason ? { finishReason: payload.finishReason } : {}),
|
|
494
632
|
...(payload.usage ? { usage: payload.usage } : {}),
|
|
633
|
+
...(payload.reasoningBlock ? { reasoningBlock: payload.reasoningBlock } : {}),
|
|
634
|
+
...(payload.reasoningArtifacts
|
|
635
|
+
? { reasoningArtifacts: payload.reasoningArtifacts }
|
|
636
|
+
: {}),
|
|
495
637
|
};
|
|
496
638
|
}
|
|
497
639
|
/**
|
|
@@ -544,7 +686,7 @@ export function createSseFrameAssembler(options) {
|
|
|
544
686
|
},
|
|
545
687
|
};
|
|
546
688
|
}
|
|
547
|
-
export function toOpenAiMessages(messages, supportsVision = true) {
|
|
689
|
+
export function toOpenAiMessages(messages, supportsVision = true, replay) {
|
|
548
690
|
return toOpenAiToolMessages(messages, (message) => {
|
|
549
691
|
if (supportsVision &&
|
|
550
692
|
message.images &&
|
|
@@ -564,44 +706,12 @@ export function toOpenAiMessages(messages, supportsVision = true) {
|
|
|
564
706
|
return parts;
|
|
565
707
|
}
|
|
566
708
|
return message.content;
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
export function classifyNvidiaModel(model) {
|
|
570
|
-
const m = model.toLowerCase();
|
|
571
|
-
if (/kimi-k2(?:\.6|-thinking|-instruct)?/.test(m))
|
|
572
|
-
return "kimi-thinking";
|
|
573
|
-
if (/deepseek-v4/.test(m))
|
|
574
|
-
return "deepseek-v4";
|
|
575
|
-
// Match newer Nemotron-3 (uses enable_thinking + reasoning_budget) before
|
|
576
|
-
// the legacy Nemotron pattern below — the older `nemotron` bucket would
|
|
577
|
-
// otherwise swallow these too.
|
|
578
|
-
if (/nemotron-3/.test(m))
|
|
579
|
-
return "nemotron-3";
|
|
580
|
-
if (/glm-?[345]/.test(m))
|
|
581
|
-
return "glm-thinking";
|
|
582
|
-
if (/gemma-?[34]/.test(m))
|
|
583
|
-
return "enable-thinking";
|
|
584
|
-
if (/deepseek-(?:v3|r1)|nemotron/.test(m))
|
|
585
|
-
return "thinking";
|
|
586
|
-
if (/gpt-oss|qwen3|mistral-(?:medium|small|large)-(?:[3-9]|\d{2,})/.test(m))
|
|
587
|
-
return "effort-only";
|
|
588
|
-
return "none";
|
|
709
|
+
}, replay);
|
|
589
710
|
}
|
|
711
|
+
export { classifyBynaraModel, classifyNvidiaModel, } from "./model-families.js";
|
|
590
712
|
function supportsOpenRouterReasoning(model) {
|
|
591
713
|
return /:thinking|deepseek-r1|qwen3|kimi-k2|claude-(?:opus|sonnet|haiku)-4|gpt-5|(?:^|\/)o[134]|grok.*reasoner/i.test(model);
|
|
592
714
|
}
|
|
593
|
-
export function classifyBynaraModel(model) {
|
|
594
|
-
const m = model.toLowerCase();
|
|
595
|
-
if (/kimi/.test(m))
|
|
596
|
-
return "kimi";
|
|
597
|
-
if (/deepseek/.test(m))
|
|
598
|
-
return "deepseek";
|
|
599
|
-
if (/agnes/.test(m))
|
|
600
|
-
return "agnes";
|
|
601
|
-
if (/stepfun|step-3/.test(m))
|
|
602
|
-
return "stepfun";
|
|
603
|
-
return "none";
|
|
604
|
-
}
|
|
605
715
|
const MODAL_DEFAULT_EFFORTS = ["low", "high", "max"];
|
|
606
716
|
const MODAL_EFFORT_PREFERENCE = {
|
|
607
717
|
minimal: ["minimal", "low", "medium", "high", "max"],
|
|
@@ -933,7 +1043,7 @@ export function isOpenAiReasoningModel(model) {
|
|
|
933
1043
|
/(?:^|\/)o[134](?:\.|-|$)/.test(m) ||
|
|
934
1044
|
/muse-spark/.test(m));
|
|
935
1045
|
}
|
|
936
|
-
|
|
1046
|
+
function emitChatCompletionsBody(options) {
|
|
937
1047
|
// Skip reasoning knobs entirely for models observed to reject them this
|
|
938
1048
|
// session (see isReasoningUnsupportedError). This is how thinking degrades
|
|
939
1049
|
// gracefully: the request still runs, just without the unsupported option.
|
|
@@ -972,8 +1082,16 @@ export function buildChatBody(options) {
|
|
|
972
1082
|
: (options.maxTokens ?? defaultMaxTokens);
|
|
973
1083
|
const body = {
|
|
974
1084
|
model: options.model,
|
|
975
|
-
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),
|
|
976
1091
|
stream: options.stream,
|
|
1092
|
+
...(options.providerId === "fireworks"
|
|
1093
|
+
? { perf_metrics_in_response: true }
|
|
1094
|
+
: {}),
|
|
977
1095
|
...(reasoningModel
|
|
978
1096
|
? { max_completion_tokens: effectiveMaxTokens }
|
|
979
1097
|
: { max_tokens: effectiveMaxTokens }),
|
|
@@ -998,6 +1116,35 @@ export function buildChatBody(options) {
|
|
|
998
1116
|
}
|
|
999
1117
|
return JSON.stringify(body);
|
|
1000
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
|
+
}
|
|
1001
1148
|
function sentReasoningEffort(requestBody) {
|
|
1002
1149
|
try {
|
|
1003
1150
|
const body = JSON.parse(requestBody);
|
|
@@ -1021,35 +1168,27 @@ function privateReasoningNote(provider, requestBody, reasoningTokens) {
|
|
|
1021
1168
|
const effortText = effort ? ` at ${effort} effort` : "";
|
|
1022
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.`;
|
|
1023
1170
|
}
|
|
1024
|
-
function foldReasoning(provider, requestBody, text, reasoning, usage) {
|
|
1025
|
-
if (reasoning && reasoning.trim()) {
|
|
1026
|
-
return `${wrapReasoning(reasoning)}${text}`;
|
|
1027
|
-
}
|
|
1028
|
-
const tokens = usage?.reasoningTokens ?? 0;
|
|
1029
|
-
if (tokens > 0) {
|
|
1030
|
-
return `${wrapReasoning(privateReasoningNote(provider, requestBody, tokens))}${text}`;
|
|
1031
|
-
}
|
|
1032
|
-
return text;
|
|
1033
|
-
}
|
|
1034
1171
|
export async function openAiCompatibleComplete(options) {
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1172
|
+
const plan = compileRequestPlan({
|
|
1173
|
+
provider: options.providerId,
|
|
1037
1174
|
model: options.model,
|
|
1038
|
-
providerId: options.providerId,
|
|
1039
1175
|
messages: options.messages,
|
|
1040
|
-
maxTokens: options.maxTokens,
|
|
1041
|
-
temperature: options.temperature,
|
|
1042
1176
|
stream: false,
|
|
1177
|
+
endpoint: options.baseUrl,
|
|
1043
1178
|
reasoning: options.reasoning,
|
|
1044
|
-
reasoningStyle: options.reasoningStyle,
|
|
1045
|
-
supportsVision,
|
|
1046
1179
|
tools: options.tools,
|
|
1047
1180
|
toolChoice: options.toolChoice,
|
|
1048
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,
|
|
1049
1188
|
});
|
|
1050
1189
|
let response;
|
|
1051
1190
|
try {
|
|
1052
|
-
response = await
|
|
1191
|
+
response = await generationFetch(`${options.baseUrl}/chat/completions`, {
|
|
1053
1192
|
method: "POST",
|
|
1054
1193
|
signal: options.signal ?? null,
|
|
1055
1194
|
headers: {
|
|
@@ -1067,6 +1206,8 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1067
1206
|
catch (error) {
|
|
1068
1207
|
if (error instanceof Error && error.name === "AbortError")
|
|
1069
1208
|
throw error;
|
|
1209
|
+
if (isOperationPolicyError(error))
|
|
1210
|
+
throw error;
|
|
1070
1211
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1071
1212
|
throw new ProviderError(`${options.provider} request could not be sent (${msg}). Check connectivity to ${options.baseUrl}.`);
|
|
1072
1213
|
}
|
|
@@ -1089,14 +1230,42 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1089
1230
|
}
|
|
1090
1231
|
// If the API returns reasoning separately, prepend it inside <think>
|
|
1091
1232
|
// tags so the existing thinking parser can pick it up uniformly.
|
|
1092
|
-
|
|
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);
|
|
1093
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
|
+
});
|
|
1094
1264
|
if (typeof reasoning === "string" && reasoning.trim()) {
|
|
1095
1265
|
learnModelEmitsReasoning(options.providerId, options.model);
|
|
1096
1266
|
}
|
|
1097
|
-
const full = foldReasoning(options.provider, requestBody, text, reasoning, usage);
|
|
1098
1267
|
return {
|
|
1099
|
-
text
|
|
1268
|
+
text,
|
|
1100
1269
|
...(toolCalls.length ? { toolCalls } : {}),
|
|
1101
1270
|
...(choice?.finish_reason
|
|
1102
1271
|
? { finishReason: choice.finish_reason }
|
|
@@ -1104,6 +1273,10 @@ export async function openAiCompatibleComplete(options) {
|
|
|
1104
1273
|
? { finishReason: "tool_calls" }
|
|
1105
1274
|
: {}),
|
|
1106
1275
|
...(usage ? { usage } : {}),
|
|
1276
|
+
...(typeof reasoning === "string" && reasoning
|
|
1277
|
+
? { reasoningBlock: { text: reasoning } }
|
|
1278
|
+
: {}),
|
|
1279
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1107
1280
|
};
|
|
1108
1281
|
}
|
|
1109
1282
|
export async function openAiCompatibleStream(options) {
|
|
@@ -1184,25 +1357,27 @@ export async function openAiCompatibleStream(options) {
|
|
|
1184
1357
|
};
|
|
1185
1358
|
const onCallerAbort = () => idleController.abort(options.signal?.reason);
|
|
1186
1359
|
options.signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1360
|
+
const plan = compileRequestPlan({
|
|
1361
|
+
provider: options.providerId,
|
|
1189
1362
|
model: options.model,
|
|
1190
|
-
providerId: options.providerId,
|
|
1191
1363
|
messages: options.messages,
|
|
1192
|
-
maxTokens: options.maxTokens,
|
|
1193
|
-
temperature: options.temperature,
|
|
1194
1364
|
stream: true,
|
|
1195
|
-
|
|
1365
|
+
endpoint: options.baseUrl,
|
|
1196
1366
|
reasoning: options.reasoning,
|
|
1197
|
-
reasoningStyle: options.reasoningStyle,
|
|
1198
|
-
supportsVision,
|
|
1199
1367
|
tools: options.tools,
|
|
1200
1368
|
toolChoice: options.toolChoice,
|
|
1201
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,
|
|
1202
1377
|
});
|
|
1203
1378
|
let response;
|
|
1204
1379
|
try {
|
|
1205
|
-
response = await
|
|
1380
|
+
response = await generationFetch(`${options.baseUrl}/chat/completions`, {
|
|
1206
1381
|
method: "POST",
|
|
1207
1382
|
signal: idleController.signal,
|
|
1208
1383
|
headers: {
|
|
@@ -1245,35 +1420,84 @@ export async function openAiCompatibleStream(options) {
|
|
|
1245
1420
|
}
|
|
1246
1421
|
const contentType = response.headers.get("content-type") ?? "";
|
|
1247
1422
|
if (response.status === 202 || /\bapplication\/json\b/i.test(contentType)) {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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 } }
|
|
1272
1478
|
: {}),
|
|
1273
|
-
|
|
1274
|
-
|
|
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);
|
|
1275
1500
|
}
|
|
1276
|
-
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));
|
|
1277
1501
|
}
|
|
1278
1502
|
const decoder = new TextDecoder();
|
|
1279
1503
|
const reader = response.body.getReader();
|
|
@@ -1283,30 +1507,63 @@ export async function openAiCompatibleStream(options) {
|
|
|
1283
1507
|
let reasoningSeen = "";
|
|
1284
1508
|
let contentWireSeen = "";
|
|
1285
1509
|
let reasoningWireSeen = "";
|
|
1286
|
-
let inReasoning = false;
|
|
1287
1510
|
let finishReason;
|
|
1288
|
-
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;
|
|
1289
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
|
+
};
|
|
1290
1561
|
const normalizeChannelDelta = (token, seen) => {
|
|
1291
1562
|
if (seen.length >= 64 && token.length >= seen.length && token.startsWith(seen)) {
|
|
1292
1563
|
return { delta: token.slice(seen.length), seen: token };
|
|
1293
1564
|
}
|
|
1294
1565
|
return { delta: token, seen: seen + token };
|
|
1295
1566
|
};
|
|
1296
|
-
const enterReasoning = () => {
|
|
1297
|
-
if (inReasoning)
|
|
1298
|
-
return;
|
|
1299
|
-
inReasoning = true;
|
|
1300
|
-
full += REASONING_OPEN;
|
|
1301
|
-
options.onToken(REASONING_OPEN);
|
|
1302
|
-
};
|
|
1303
|
-
const exitReasoning = () => {
|
|
1304
|
-
if (!inReasoning)
|
|
1305
|
-
return;
|
|
1306
|
-
inReasoning = false;
|
|
1307
|
-
full += REASONING_CLOSE;
|
|
1308
|
-
options.onToken(REASONING_CLOSE);
|
|
1309
|
-
};
|
|
1310
1567
|
const emitPrivateReasoningNote = (hasToolCalls) => {
|
|
1311
1568
|
if (reasoningSeen.trim())
|
|
1312
1569
|
return;
|
|
@@ -1316,10 +1573,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1316
1573
|
if (tokens <= 0)
|
|
1317
1574
|
return;
|
|
1318
1575
|
const note = privateReasoningNote(options.provider, requestBody, tokens);
|
|
1319
|
-
|
|
1320
|
-
full += note;
|
|
1321
|
-
options.onToken(note);
|
|
1322
|
-
exitReasoning();
|
|
1576
|
+
emitStreamReasoningDelta(options.onStreamEvent, note);
|
|
1323
1577
|
};
|
|
1324
1578
|
/**
|
|
1325
1579
|
* Reasoning-echo suppression.
|
|
@@ -1343,8 +1597,6 @@ export async function openAiCompatibleStream(options) {
|
|
|
1343
1597
|
const emitVisible = (text) => {
|
|
1344
1598
|
if (!text)
|
|
1345
1599
|
return;
|
|
1346
|
-
if (inReasoning)
|
|
1347
|
-
exitReasoning();
|
|
1348
1600
|
visible += text;
|
|
1349
1601
|
full += text;
|
|
1350
1602
|
options.onToken(text);
|
|
@@ -1357,9 +1609,7 @@ export async function openAiCompatibleStream(options) {
|
|
|
1357
1609
|
const emitReasoningEcho = (text) => {
|
|
1358
1610
|
if (!text)
|
|
1359
1611
|
return;
|
|
1360
|
-
|
|
1361
|
-
full += text;
|
|
1362
|
-
options.onToken(text);
|
|
1612
|
+
emitStreamReasoningDelta(options.onStreamEvent, text);
|
|
1363
1613
|
};
|
|
1364
1614
|
/** Release a still-undecided hold-back as the answer (stream ended early). */
|
|
1365
1615
|
const flushEchoBuffer = () => {
|
|
@@ -1453,11 +1703,19 @@ export async function openAiCompatibleStream(options) {
|
|
|
1453
1703
|
if (payload === undefined)
|
|
1454
1704
|
continue;
|
|
1455
1705
|
if (payload === "[DONE]") {
|
|
1706
|
+
terminalSignal = "done-sentinel";
|
|
1707
|
+
requireTerminalProof({
|
|
1708
|
+
provider: options.provider,
|
|
1709
|
+
policy: terminalPolicy,
|
|
1710
|
+
signal: terminalSignal,
|
|
1711
|
+
...emittedByteCounts(),
|
|
1712
|
+
});
|
|
1456
1713
|
flushEchoBuffer();
|
|
1457
|
-
exitReasoning();
|
|
1458
1714
|
cleanup();
|
|
1459
1715
|
const toolCalls = finalizeOpenAiToolCalls(toolCallState);
|
|
1460
1716
|
emitPrivateReasoningNote(toolCalls.length > 0);
|
|
1717
|
+
const reasoningArtifacts = finalReasoningArtifacts(toolCalls);
|
|
1718
|
+
emitStreamReasoningArtifacts(options.onStreamEvent, reasoningArtifacts);
|
|
1461
1719
|
if (!visible.trim() && toolCalls.length === 0) {
|
|
1462
1720
|
// Thinking models (Kimi/Moonshot via Mantle, etc.) often emit only
|
|
1463
1721
|
// reasoning, sometimes with tool sentinels inside <think>. Returning
|
|
@@ -1468,6 +1726,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1468
1726
|
text: full,
|
|
1469
1727
|
...(finishReason ? { finishReason } : { finishReason: "stop" }),
|
|
1470
1728
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1729
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1730
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1471
1731
|
};
|
|
1472
1732
|
}
|
|
1473
1733
|
throw new ProviderError(`${options.provider} completed without a visible answer.`);
|
|
@@ -1481,6 +1741,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1481
1741
|
? { finishReason: "tool_calls" }
|
|
1482
1742
|
: {}),
|
|
1483
1743
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1744
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1745
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1484
1746
|
};
|
|
1485
1747
|
}
|
|
1486
1748
|
let parsed;
|
|
@@ -1504,13 +1766,18 @@ export async function openAiCompatibleStream(options) {
|
|
|
1504
1766
|
throw new ProviderError(`${options.provider} stream error: ${detail}`, undefined, payload.slice(0, 500));
|
|
1505
1767
|
}
|
|
1506
1768
|
{
|
|
1507
|
-
const chunkUsage =
|
|
1769
|
+
const chunkUsage = options.providerId === "fireworks"
|
|
1770
|
+
? parseFireworksUsage(parsed.usage, parsed.perf_metrics, response.headers)
|
|
1771
|
+
: parseOpenAiUsage(parsed.usage, options.usageAliases);
|
|
1508
1772
|
if (chunkUsage)
|
|
1509
1773
|
streamUsage = chunkUsage;
|
|
1510
1774
|
const choice = parsed.choices?.[0];
|
|
1511
1775
|
const delta = choice?.delta;
|
|
1512
1776
|
const reasoningToken = delta?.reasoning_content ?? delta?.reasoning;
|
|
1513
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++;
|
|
1514
1781
|
const toolProgress = delta?.tool_calls?.some((toolCall) => Boolean(toolCall.id ||
|
|
1515
1782
|
toolCall.function?.name ||
|
|
1516
1783
|
toolCall.function?.arguments));
|
|
@@ -1518,63 +1785,101 @@ export async function openAiCompatibleStream(options) {
|
|
|
1518
1785
|
chunkUsage ||
|
|
1519
1786
|
reasoningToken ||
|
|
1520
1787
|
token ||
|
|
1788
|
+
detailRaw ||
|
|
1789
|
+
thoughtSignature ||
|
|
1521
1790
|
toolProgress) {
|
|
1522
1791
|
resetIdleTimer();
|
|
1523
1792
|
}
|
|
1524
|
-
if (choice?.finish_reason)
|
|
1793
|
+
if (choice?.finish_reason) {
|
|
1525
1794
|
finishReason = choice.finish_reason;
|
|
1795
|
+
terminalSignal = "finish-reason";
|
|
1796
|
+
}
|
|
1526
1797
|
if (reasoningToken) {
|
|
1527
1798
|
const normalized = normalizeChannelDelta(reasoningToken, reasoningWireSeen);
|
|
1528
1799
|
reasoningWireSeen = normalized.seen;
|
|
1529
1800
|
if (normalized.delta) {
|
|
1801
|
+
reasoningArtifactSequence ??= artifactSequence;
|
|
1530
1802
|
if (!reasoningSeen) {
|
|
1531
1803
|
learnModelEmitsReasoning(options.providerId, options.model);
|
|
1532
1804
|
}
|
|
1533
|
-
enterReasoning();
|
|
1534
1805
|
reasoningSeen += normalized.delta;
|
|
1535
|
-
|
|
1536
|
-
options.onToken(normalized.delta);
|
|
1806
|
+
emitStreamReasoningDelta(options.onStreamEvent, normalized.delta);
|
|
1537
1807
|
}
|
|
1538
1808
|
}
|
|
1809
|
+
if (detailRaw) {
|
|
1810
|
+
structuredDetails.push({ raw: detailRaw, sequence: artifactSequence });
|
|
1811
|
+
}
|
|
1539
1812
|
if (token) {
|
|
1540
1813
|
const normalized = normalizeChannelDelta(token, contentWireSeen);
|
|
1541
1814
|
contentWireSeen = normalized.seen;
|
|
1542
1815
|
if (normalized.delta)
|
|
1543
1816
|
handleContentToken(normalized.delta);
|
|
1544
1817
|
}
|
|
1818
|
+
const deltaToolCallIndices = [];
|
|
1545
1819
|
if (delta?.tool_calls?.length) {
|
|
1546
1820
|
for (const tc of delta.tool_calls) {
|
|
1547
1821
|
const accInfo = accumulateOpenAiToolCallDelta(toolCallState, tc);
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
accInfo.
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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
|
+
}
|
|
1567
1843
|
}
|
|
1568
1844
|
}
|
|
1569
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
|
+
}
|
|
1570
1868
|
}
|
|
1571
1869
|
}
|
|
1572
1870
|
}
|
|
1573
1871
|
flushEchoBuffer();
|
|
1574
|
-
exitReasoning();
|
|
1575
1872
|
cleanup();
|
|
1873
|
+
requireTerminalProof({
|
|
1874
|
+
provider: options.provider,
|
|
1875
|
+
policy: terminalPolicy,
|
|
1876
|
+
signal: terminalSignal,
|
|
1877
|
+
...emittedByteCounts(),
|
|
1878
|
+
});
|
|
1576
1879
|
const toolCalls = finalizeOpenAiToolCalls(toolCallState);
|
|
1577
1880
|
emitPrivateReasoningNote(toolCalls.length > 0);
|
|
1881
|
+
const reasoningArtifacts = finalReasoningArtifacts(toolCalls);
|
|
1882
|
+
emitStreamReasoningArtifacts(options.onStreamEvent, reasoningArtifacts);
|
|
1578
1883
|
if (!visible.trim() && toolCalls.length === 0) {
|
|
1579
1884
|
// See [DONE] branch — hand thinking-only streams to the runner so it can
|
|
1580
1885
|
// salvage sentinel tool blocks (or nudge) instead of hard-failing.
|
|
@@ -1583,6 +1888,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1583
1888
|
text: full,
|
|
1584
1889
|
...(finishReason ? { finishReason } : { finishReason: "stop" }),
|
|
1585
1890
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1891
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1892
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1586
1893
|
};
|
|
1587
1894
|
}
|
|
1588
1895
|
throw new ProviderError(`${options.provider} completed without a visible answer.`);
|
|
@@ -1596,6 +1903,8 @@ export async function openAiCompatibleStream(options) {
|
|
|
1596
1903
|
? { finishReason: "tool_calls" }
|
|
1597
1904
|
: {}),
|
|
1598
1905
|
...(streamUsage ? { usage: streamUsage } : {}),
|
|
1906
|
+
...(reasoningSeen ? { reasoningBlock: { text: reasoningSeen } } : {}),
|
|
1907
|
+
...(reasoningArtifacts ? { reasoningArtifacts } : {}),
|
|
1599
1908
|
};
|
|
1600
1909
|
}
|
|
1601
1910
|
catch (error) {
|