@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/router.js
CHANGED
|
@@ -3,10 +3,12 @@ import { getProviderKeys, getProviderSecret, markProviderKeySuccess, } from "../
|
|
|
3
3
|
import { anthropicProvider } from "./anthropic.js";
|
|
4
4
|
import { geminiProvider } from "./gemini.js";
|
|
5
5
|
import { groqProvider } from "./groq.js";
|
|
6
|
-
import { ProviderError, isImageInputUnsupportedError, isReasoningUnsupportedError, stripImagesFromMessages, STREAM_STALL_MARKER, } from "./http.js";
|
|
7
|
-
import { learnModelVisionCapability, markModelUnavailable, markReasoningUnsupported, modelAcceptsImages, modelSupportsVision, visionSubstitutionOrigin, } from "./capabilities.js";
|
|
6
|
+
import { ProviderError, bodyAddsInformation, buildReasoningPayload, collapseWhitespace, isImageInputUnsupportedError, isReasoningUnsupportedError, stripImagesFromMessages, MAX_ERROR_BODY_IN_MESSAGE_CHARS, STREAM_STALL_MARKER, } from "./http.js";
|
|
7
|
+
import { isReasoningUnsupported, learnModelVisionCapability, markModelUnavailable, markReasoningUnsupported, modelAcceptsImages, modelSupportsVision, visionSubstitutionOrigin, } from "./capabilities.js";
|
|
8
8
|
import { applyImageViewAvailability } from "../prompts/index.js";
|
|
9
|
+
import { fallbackEffortsFor } from "./effort-fallback.js";
|
|
9
10
|
import { markStreamEmittedBytes, streamAlreadyEmitted, streamEmittedBytes, } from "./stream-progress.js";
|
|
11
|
+
import { createStreamEventGuard, isSemanticStreamOutputEvent, } from "./stream-events.js";
|
|
10
12
|
import { attemptsPerKey, buildKeyAttemptPlan, formatKeyEventStatus, isImmediateKeySwitchError, isKeyCircleStopError, isKeyRotatableError, isQuotaKeyError, } from "./key-rotation.js";
|
|
11
13
|
import { freeProvider } from "./free.js";
|
|
12
14
|
import { nvidiaProvider } from "./nvidia.js";
|
|
@@ -22,6 +24,11 @@ import { modalProvider } from "./modal.js";
|
|
|
22
24
|
import { lightningProvider } from "./lightning.js";
|
|
23
25
|
import { tokenrouterProvider } from "./tokenrouter.js";
|
|
24
26
|
import { metaProvider } from "./meta.js";
|
|
27
|
+
import { fireworksProvider } from "./fireworks.js";
|
|
28
|
+
import { hetznerProvider } from "./hetzner.js";
|
|
29
|
+
import { orcarouterProvider } from "./orcarouter.js";
|
|
30
|
+
import { OperationUsageRecorder, runGenerationAttempt, } from "./operation-usage.js";
|
|
31
|
+
import { isOperationPolicyError, OperationLedger, operationTerminalOutcome, turnOperationPolicy, } from "./operation-ledger.js";
|
|
25
32
|
import { maskSecretTail } from "./provider.js";
|
|
26
33
|
import { getCustomProviderSync } from "./custom-providers.js";
|
|
27
34
|
import { isToolsUnsupportedError, markTextOnlyModel, } from "./tool-protocol.js";
|
|
@@ -30,6 +37,36 @@ const MAX_RETRIES = 6;
|
|
|
30
37
|
const MAX_RETRY_WAIT_MS = 120_000;
|
|
31
38
|
/** Toast replace-key so key rotation never stacks notifications. */
|
|
32
39
|
export const API_KEY_TOAST_KEY = "api-key-rotation";
|
|
40
|
+
/**
|
|
41
|
+
* A budget/policy error raised while recovering from a real failure describes
|
|
42
|
+
* the guard, not the cause. Surface the failure the caller actually needs.
|
|
43
|
+
*/
|
|
44
|
+
function successfulRequestSnapshot(provider, model, request) {
|
|
45
|
+
return structuredClone({
|
|
46
|
+
provider,
|
|
47
|
+
model,
|
|
48
|
+
messages: request.messages,
|
|
49
|
+
...(request.temperature !== undefined
|
|
50
|
+
? { temperature: request.temperature }
|
|
51
|
+
: {}),
|
|
52
|
+
...(request.thinking ? { thinking: request.thinking } : {}),
|
|
53
|
+
...(request.tools ? { tools: request.tools } : {}),
|
|
54
|
+
...(request.toolChoice !== undefined
|
|
55
|
+
? { toolChoice: request.toolChoice }
|
|
56
|
+
: {}),
|
|
57
|
+
...(request.parallelToolCalls !== undefined
|
|
58
|
+
? { parallelToolCalls: request.parallelToolCalls }
|
|
59
|
+
: {}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function preservedFailure(recoveryError, originalError) {
|
|
63
|
+
return isOperationPolicyError(recoveryError) ? originalError : recoveryError;
|
|
64
|
+
}
|
|
65
|
+
function resolveOperationLedger(options) {
|
|
66
|
+
if (options.operation)
|
|
67
|
+
return options.operation;
|
|
68
|
+
return new OperationLedger(turnOperationPolicy(), options.attemptUsage ?? new OperationUsageRecorder());
|
|
69
|
+
}
|
|
33
70
|
async function sleep(ms, signal) {
|
|
34
71
|
if (signal?.aborted)
|
|
35
72
|
throw signal.reason ?? new Error("Aborted");
|
|
@@ -54,6 +91,47 @@ async function sleep(ms, signal) {
|
|
|
54
91
|
function isRateLimited(error) {
|
|
55
92
|
return error instanceof ProviderError && error.status === 429;
|
|
56
93
|
}
|
|
94
|
+
function isServerUnavailable(error) {
|
|
95
|
+
if (!(error instanceof ProviderError))
|
|
96
|
+
return false;
|
|
97
|
+
const status = error.status ?? 0;
|
|
98
|
+
return status === 502 || status === 503 || status === 504;
|
|
99
|
+
}
|
|
100
|
+
function isServerError(error) {
|
|
101
|
+
if (!(error instanceof ProviderError))
|
|
102
|
+
return false;
|
|
103
|
+
const status = error.status ?? 0;
|
|
104
|
+
return status >= 500 && status <= 504;
|
|
105
|
+
}
|
|
106
|
+
function isReasoningRelatedServerError(error, providerId) {
|
|
107
|
+
if (!isServerError(error))
|
|
108
|
+
return false;
|
|
109
|
+
if (providerId === "tokenrouter")
|
|
110
|
+
return true;
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
const body = error instanceof ProviderError ? (error.body ?? "") : "";
|
|
113
|
+
return /reasoning|effort|thinking/i.test(`${message} ${body}`);
|
|
114
|
+
}
|
|
115
|
+
function shouldContinueEffortLadder(error, providerId) {
|
|
116
|
+
return (isReasoningUnsupportedError(error) ||
|
|
117
|
+
isReasoningRelatedServerError(error, providerId));
|
|
118
|
+
}
|
|
119
|
+
function shouldEnterEffortLadder(error, thinking, providerId, model, singleDispatch) {
|
|
120
|
+
if (isReasoningUnsupportedError(error))
|
|
121
|
+
return true;
|
|
122
|
+
if (singleDispatch)
|
|
123
|
+
return false;
|
|
124
|
+
if (!thinking?.enabled)
|
|
125
|
+
return false;
|
|
126
|
+
if (isReasoningUnsupported(providerId, model))
|
|
127
|
+
return false;
|
|
128
|
+
return isReasoningRelatedServerError(error, providerId);
|
|
129
|
+
}
|
|
130
|
+
function isCacheOnlyColdError(error) {
|
|
131
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
132
|
+
const body = error instanceof ProviderError ? (error.body ?? "") : "";
|
|
133
|
+
return /cache_only_cold|cache-only admission/i.test(`${message} ${body}`);
|
|
134
|
+
}
|
|
57
135
|
function isTransientNetworkError(error) {
|
|
58
136
|
const message = error instanceof Error ? error.message : String(error);
|
|
59
137
|
const msg = message.toLowerCase();
|
|
@@ -114,6 +192,24 @@ function failureReason(error) {
|
|
|
114
192
|
}
|
|
115
193
|
return "connection glitch";
|
|
116
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Append the raw response body a provider returned when it is not already part
|
|
197
|
+
* of `text`. Several paths (SSE error frames, non-OpenAI providers) keep the
|
|
198
|
+
* body on the error without embedding it in the message, and users need the
|
|
199
|
+
* full error received from the request to diagnose failures.
|
|
200
|
+
*/
|
|
201
|
+
function appendFullProviderBody(text, error) {
|
|
202
|
+
if (!(error instanceof ProviderError))
|
|
203
|
+
return text;
|
|
204
|
+
const body = (error.body ?? "").trim();
|
|
205
|
+
if (!body || !bodyAddsInformation(body, text))
|
|
206
|
+
return text;
|
|
207
|
+
const shown = collapseWhitespace(body);
|
|
208
|
+
const capped = shown.length > MAX_ERROR_BODY_IN_MESSAGE_CHARS
|
|
209
|
+
? `${shown.slice(0, MAX_ERROR_BODY_IN_MESSAGE_CHARS)}…`
|
|
210
|
+
: shown;
|
|
211
|
+
return `${text}\nFull response from provider: ${capped}`;
|
|
212
|
+
}
|
|
117
213
|
/**
|
|
118
214
|
* User-facing classification of provider failures (auth, capacity, disconnect,
|
|
119
215
|
* empty admission, context limit). Used by the fallback table and tests.
|
|
@@ -125,54 +221,63 @@ export function formatProviderFailureForUser(error) {
|
|
|
125
221
|
// it needlessly hard to tell OpenAI and Gemini failures apart.
|
|
126
222
|
const exactError = error.message.replace(/\s+/g, " ").trim();
|
|
127
223
|
const withExactError = (guidance) => exactError ? `${guidance}\nExact provider error: ${exactError}` : guidance;
|
|
224
|
+
const withFullBody = (text) => appendFullProviderBody(text, error);
|
|
128
225
|
const status = error.status ?? 0;
|
|
129
226
|
if (status === 429) {
|
|
130
|
-
return withExactError("Model is rate limited (429). Try another provider/model or switch to a paid plan.");
|
|
227
|
+
return withFullBody(withExactError("Model is rate limited (429). Try another provider/model or switch to a paid plan."));
|
|
131
228
|
}
|
|
132
229
|
if (status === 401 || status === 403) {
|
|
133
|
-
return withExactError(`Authentication/authorization failed (${status}). Check the API key with \`clai providers\` or set the provider env var.`);
|
|
230
|
+
return withFullBody(withExactError(`Authentication/authorization failed (${status}). Check the API key with \`clai providers\` or set the provider env var.`));
|
|
134
231
|
}
|
|
135
232
|
if (status === 402) {
|
|
136
|
-
return withExactError("Insufficient credits / payment required (402). Try another API key for this provider, top up the account, or switch provider.");
|
|
233
|
+
return withFullBody(withExactError("Insufficient credits / payment required (402). Try another API key for this provider, top up the account, or switch provider."));
|
|
137
234
|
}
|
|
138
235
|
if (status === 404) {
|
|
139
|
-
return withExactError("Model or endpoint not found (404). Run `/model list` or pick another model.");
|
|
236
|
+
return withFullBody(withExactError("Model or endpoint not found (404). Run `/model list` or pick another model."));
|
|
140
237
|
}
|
|
141
238
|
if (status === 413) {
|
|
142
|
-
return withExactError("Request exceeded the provider input limit (413). Wait for auto-compact, run `/compact`, or continue with a smaller turn.");
|
|
239
|
+
return withFullBody(withExactError("Request exceeded the provider input limit (413). Wait for auto-compact, run `/compact`, or continue with a smaller turn."));
|
|
143
240
|
}
|
|
144
241
|
if (status === 422) {
|
|
145
|
-
return withExactError("Provider rejected the request body (422). Model name or parameters may be incompatible — try another model.");
|
|
242
|
+
return withFullBody(withExactError("Provider rejected the request body (422). Model name or parameters may be incompatible — try another model."));
|
|
146
243
|
}
|
|
147
244
|
if (status === 503 || status === 502 || status === 504) {
|
|
148
|
-
|
|
245
|
+
if (isCacheOnlyColdError(error)) {
|
|
246
|
+
return withFullBody(withExactError(`Gateway cache admission rejected (${status}; cache_only_cold): the route is cold or overloaded and retried automatically with backoff. If it persists, retry shortly or switch provider/model.`));
|
|
247
|
+
}
|
|
248
|
+
return withFullBody(withExactError(`Upstream provider unavailable (${status}). Retry shortly or switch provider/model; free-tier models are often capacity-constrained.`));
|
|
149
249
|
}
|
|
150
250
|
if (status >= 500 && status < 600) {
|
|
151
|
-
return withExactError(`Upstream provider error (${status}). Retry or switch with \`/provider\` / \`/model\`.`);
|
|
251
|
+
return withFullBody(withExactError(`Upstream provider error (${status}). Retry or switch with \`/provider\` / \`/model\`.`));
|
|
152
252
|
}
|
|
153
253
|
}
|
|
154
254
|
const message = (error instanceof Error ? error.message : String(error))
|
|
155
255
|
.replace(/\s+/g, " ")
|
|
156
256
|
.trim();
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
257
|
+
const generic = (() => {
|
|
258
|
+
if (!message) {
|
|
259
|
+
return "Provider returned an empty failure (no tokens billed). Model may be unavailable, overloaded, or rejected before admission — try another model.";
|
|
260
|
+
}
|
|
261
|
+
if (/socket connection was closed|econnreset|premature close|unexpected end of file/i.test(message)) {
|
|
262
|
+
return `${message} — connection dropped mid-request (common on free/unstable routes). Retry or switch model; long contexts increase disconnect risk.`;
|
|
263
|
+
}
|
|
264
|
+
if (new RegExp(STREAM_STALL_MARKER, "i").test(message)) {
|
|
265
|
+
return `${message}`;
|
|
266
|
+
}
|
|
267
|
+
if (/stream stalled|request timed out before any response/i.test(message)) {
|
|
268
|
+
return `${message} — no tokens arrived in time. Free models and large contexts fail more often; retry or use a more reliable model.`;
|
|
269
|
+
}
|
|
270
|
+
if (/no completion text|response was empty|empty response|returned no text/i.test(message)) {
|
|
271
|
+
return `${message} — provider accepted the request but returned no content. Retry once; if it persists, switch model.`;
|
|
272
|
+
}
|
|
273
|
+
if (/fetch failed|network error|etimedout|enotfound|econnrefused/i.test(message)) {
|
|
274
|
+
return `${message} — network/DNS failure reaching the provider. Check connectivity and provider base URL.`;
|
|
275
|
+
}
|
|
276
|
+
return message;
|
|
277
|
+
})();
|
|
278
|
+
// Statuses without a dedicated branch above (e.g. 400, or stream errors
|
|
279
|
+
// with no HTTP status) still deserve the full response body.
|
|
280
|
+
return appendFullProviderBody(generic, error);
|
|
176
281
|
}
|
|
177
282
|
function summarizeProviderError(error) {
|
|
178
283
|
return formatProviderFailureForUser(error);
|
|
@@ -262,6 +367,9 @@ export const providers = {
|
|
|
262
367
|
lightning: lightningProvider,
|
|
263
368
|
tokenrouter: tokenrouterProvider,
|
|
264
369
|
meta: metaProvider,
|
|
370
|
+
fireworks: fireworksProvider,
|
|
371
|
+
hetzner: hetznerProvider,
|
|
372
|
+
orcarouter: orcarouterProvider,
|
|
265
373
|
};
|
|
266
374
|
const fallbackOrder = [
|
|
267
375
|
"free",
|
|
@@ -281,6 +389,9 @@ const fallbackOrder = [
|
|
|
281
389
|
"lightning",
|
|
282
390
|
"tokenrouter",
|
|
283
391
|
"meta",
|
|
392
|
+
"fireworks",
|
|
393
|
+
"hetzner",
|
|
394
|
+
"orcarouter",
|
|
284
395
|
];
|
|
285
396
|
/**
|
|
286
397
|
* Built-in provider ids in fallback preference order. Custom (user-defined)
|
|
@@ -357,6 +468,9 @@ function authForSlot(providerId, value) {
|
|
|
357
468
|
function withoutReasoning(request) {
|
|
358
469
|
return { ...request, thinking: undefined };
|
|
359
470
|
}
|
|
471
|
+
function reasoningWireKey(thinking, style, model, providerId) {
|
|
472
|
+
return JSON.stringify(buildReasoningPayload(thinking, style, model, providerId));
|
|
473
|
+
}
|
|
360
474
|
function makeKeyEmitter(onStatus, onKeyEvent) {
|
|
361
475
|
return (event) => {
|
|
362
476
|
onKeyEvent?.(event);
|
|
@@ -370,39 +484,102 @@ function makeKeyEmitter(onStatus, onKeyEvent) {
|
|
|
370
484
|
}
|
|
371
485
|
};
|
|
372
486
|
}
|
|
373
|
-
|
|
487
|
+
const SELF_RECORDED_PROVIDERS = new Set([
|
|
488
|
+
"agentrouter",
|
|
489
|
+
"bynara",
|
|
490
|
+
"meta",
|
|
491
|
+
]);
|
|
492
|
+
async function runRecordedProviderAttempt(input) {
|
|
493
|
+
if (SELF_RECORDED_PROVIDERS.has(input.providerId))
|
|
494
|
+
return input.run();
|
|
495
|
+
return runGenerationAttempt(input.request, {
|
|
496
|
+
provider: input.providerId,
|
|
497
|
+
model: input.model,
|
|
498
|
+
mode: input.mode,
|
|
499
|
+
reason: input.reason,
|
|
500
|
+
}, input.run);
|
|
501
|
+
}
|
|
502
|
+
async function tryCompleteOnce(provider, providerId, request, model, auth, reason, onStatus, singleDispatch = false) {
|
|
374
503
|
const activeRequest = { ...request, provider: providerId, model };
|
|
504
|
+
const runAttempt = (candidate, attemptReason) => {
|
|
505
|
+
const attemptRequest = { ...candidate, attemptReason };
|
|
506
|
+
return runRecordedProviderAttempt({
|
|
507
|
+
providerId,
|
|
508
|
+
model: attemptRequest.model ?? model,
|
|
509
|
+
mode: "complete",
|
|
510
|
+
reason: attemptReason,
|
|
511
|
+
request: attemptRequest,
|
|
512
|
+
run: () => provider.complete(attemptRequest, auth),
|
|
513
|
+
});
|
|
514
|
+
};
|
|
375
515
|
try {
|
|
376
|
-
const result = await
|
|
516
|
+
const result = await runAttempt(activeRequest, reason);
|
|
377
517
|
if (hasImageInput(activeRequest)) {
|
|
378
518
|
learnModelVisionCapability(providerId, model, true);
|
|
379
519
|
}
|
|
380
520
|
return result;
|
|
381
521
|
}
|
|
382
522
|
catch (error) {
|
|
523
|
+
// A pinned single-dispatch operation records the capability verdict for the
|
|
524
|
+
// next operation but never spends a second physical request on it.
|
|
383
525
|
if (activeRequest.tools?.length && isToolsUnsupportedError(error)) {
|
|
384
526
|
markTextOnlyModel(providerId, model);
|
|
527
|
+
if (singleDispatch)
|
|
528
|
+
throw error;
|
|
385
529
|
const textRequest = {
|
|
386
530
|
...activeRequest,
|
|
387
531
|
tools: undefined,
|
|
388
532
|
toolChoice: undefined,
|
|
389
533
|
parallelToolCalls: undefined,
|
|
390
534
|
};
|
|
391
|
-
return await
|
|
535
|
+
return await runAttempt(textRequest, "adaptation");
|
|
392
536
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
537
|
+
if (shouldEnterEffortLadder(error, activeRequest.thinking, providerId, model, singleDispatch)) {
|
|
538
|
+
if (singleDispatch) {
|
|
539
|
+
markReasoningUnsupported(providerId, model);
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
const thinking = activeRequest.thinking;
|
|
543
|
+
if (thinking?.enabled) {
|
|
544
|
+
const style = provider.reasoningStyle ?? "none";
|
|
545
|
+
const seen = new Set([
|
|
546
|
+
reasoningWireKey(thinking, style, model, providerId),
|
|
547
|
+
]);
|
|
548
|
+
for (const effort of fallbackEffortsFor(thinking.effort)) {
|
|
549
|
+
const candidate = { ...thinking, effort };
|
|
550
|
+
const key = reasoningWireKey(candidate, style, model, providerId);
|
|
551
|
+
if (seen.has(key))
|
|
552
|
+
continue;
|
|
553
|
+
seen.add(key);
|
|
554
|
+
onStatus?.(`ℹ ${providerId}/${model} rejected reasoning effort — retrying with ${effort}`);
|
|
555
|
+
const retryRequest = {
|
|
556
|
+
...activeRequest,
|
|
557
|
+
thinking: candidate,
|
|
558
|
+
};
|
|
559
|
+
try {
|
|
560
|
+
return await runAttempt(retryRequest, "adaptation");
|
|
561
|
+
}
|
|
562
|
+
catch (retryError) {
|
|
563
|
+
if (!shouldContinueEffortLadder(retryError, providerId))
|
|
564
|
+
throw retryError;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
396
568
|
markReasoningUnsupported(providerId, model);
|
|
397
|
-
|
|
569
|
+
onStatus?.(`ℹ ${providerId}/${model} rejected reasoning options — retrying without them`);
|
|
570
|
+
return await runAttempt(withoutReasoning(activeRequest), "adaptation");
|
|
398
571
|
}
|
|
399
572
|
if (hasImageInput(activeRequest) && isImageInputUnsupportedError(error)) {
|
|
400
573
|
learnModelVisionCapability(providerId, model, false);
|
|
401
|
-
|
|
574
|
+
if (singleDispatch)
|
|
575
|
+
throw error;
|
|
576
|
+
return await runAttempt(withoutImages(activeRequest), "adaptation");
|
|
402
577
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
578
|
+
if (!singleDispatch) {
|
|
579
|
+
const restored = revertVisionSubstitution(providerId, model, activeRequest, error);
|
|
580
|
+
if (restored) {
|
|
581
|
+
return await runAttempt(restored.request, "adaptation");
|
|
582
|
+
}
|
|
406
583
|
}
|
|
407
584
|
throw error;
|
|
408
585
|
}
|
|
@@ -472,27 +649,57 @@ function revertVisionSubstitution(providerId, model, request, error) {
|
|
|
472
649
|
request: requestForRoute(restoredRequest, providerId, original),
|
|
473
650
|
};
|
|
474
651
|
}
|
|
475
|
-
async function tryStreamOnce(provider, providerId, request, model, auth, onToken, onStatus) {
|
|
652
|
+
async function tryStreamOnce(provider, providerId, request, model, auth, onToken, onStatus, reason, singleDispatch = false, onSuccessfulRequest) {
|
|
476
653
|
let emittedBytes = 0;
|
|
477
654
|
let emittedToolArgumentBytes = 0;
|
|
478
655
|
const onToolCallDelta = request.onToolCallDelta;
|
|
656
|
+
const downstreamEvents = request.onStreamEvent;
|
|
657
|
+
let guard = createStreamEventGuard();
|
|
658
|
+
const startedToolCallIndexes = new Set();
|
|
659
|
+
const emitEvent = (event) => {
|
|
660
|
+
guard.accept(event);
|
|
661
|
+
if (event.type === "reasoning_delta" || event.type === "commentary_delta") {
|
|
662
|
+
emittedBytes += event.text.length;
|
|
663
|
+
}
|
|
664
|
+
downstreamEvents?.(event);
|
|
665
|
+
};
|
|
479
666
|
const activeRequest = {
|
|
480
667
|
...request,
|
|
481
668
|
provider: providerId,
|
|
482
669
|
model,
|
|
483
|
-
...(onToolCallDelta
|
|
670
|
+
...(onToolCallDelta || downstreamEvents
|
|
484
671
|
? {
|
|
485
672
|
onToolCallDelta: (delta) => {
|
|
486
673
|
const argumentBytes = delta.argumentsBytes ?? 0;
|
|
487
674
|
emittedBytes += Math.max(delta.name?.length ?? 0, argumentBytes - emittedToolArgumentBytes, 1);
|
|
488
675
|
emittedToolArgumentBytes = Math.max(emittedToolArgumentBytes, argumentBytes);
|
|
489
|
-
|
|
676
|
+
if (delta.name !== undefined &&
|
|
677
|
+
!startedToolCallIndexes.has(delta.index)) {
|
|
678
|
+
startedToolCallIndexes.add(delta.index);
|
|
679
|
+
emitEvent({
|
|
680
|
+
type: "tool_call_started",
|
|
681
|
+
index: delta.index,
|
|
682
|
+
...(delta.id ? { id: delta.id } : {}),
|
|
683
|
+
name: delta.name,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
emitEvent({
|
|
687
|
+
type: "tool_arguments_delta",
|
|
688
|
+
index: delta.index,
|
|
689
|
+
...(delta.id ? { id: delta.id } : {}),
|
|
690
|
+
argumentsBytes: argumentBytes,
|
|
691
|
+
});
|
|
692
|
+
onToolCallDelta?.(delta);
|
|
490
693
|
},
|
|
491
694
|
}
|
|
492
695
|
: {}),
|
|
696
|
+
...(downstreamEvents ? { onStreamEvent: emitEvent } : {}),
|
|
493
697
|
};
|
|
494
698
|
const emit = (token) => {
|
|
699
|
+
if (!token)
|
|
700
|
+
return;
|
|
495
701
|
emittedBytes += token.length;
|
|
702
|
+
emitEvent({ type: "answer_delta", text: token });
|
|
496
703
|
onToken(token);
|
|
497
704
|
};
|
|
498
705
|
const learnVisionOnSuccess = () => {
|
|
@@ -500,14 +707,62 @@ async function tryStreamOnce(provider, providerId, request, model, auth, onToken
|
|
|
500
707
|
learnModelVisionCapability(providerId, model, true);
|
|
501
708
|
}
|
|
502
709
|
};
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
710
|
+
const runAttempt = async (candidate, attemptReason) => {
|
|
711
|
+
guard = createStreamEventGuard();
|
|
712
|
+
startedToolCallIndexes.clear();
|
|
713
|
+
const attemptRequest = { ...candidate, attemptReason };
|
|
714
|
+
const result = await runRecordedProviderAttempt({
|
|
715
|
+
providerId,
|
|
716
|
+
model: attemptRequest.model ?? model,
|
|
717
|
+
mode: "stream",
|
|
718
|
+
reason: attemptReason,
|
|
719
|
+
request: attemptRequest,
|
|
720
|
+
run: async () => {
|
|
721
|
+
let result;
|
|
722
|
+
if (provider.stream) {
|
|
723
|
+
result = await provider.stream(attemptRequest, auth, emit);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
result = await provider.complete(attemptRequest, auth);
|
|
727
|
+
emit(result.text);
|
|
728
|
+
}
|
|
729
|
+
for (const [index, call] of (result.toolCalls ?? []).entries()) {
|
|
730
|
+
if (!startedToolCallIndexes.has(index)) {
|
|
731
|
+
startedToolCallIndexes.add(index);
|
|
732
|
+
emitEvent({
|
|
733
|
+
type: "tool_call_started",
|
|
734
|
+
index,
|
|
735
|
+
...(call.id ? { id: call.id } : {}),
|
|
736
|
+
name: call.name,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
emitEvent({
|
|
740
|
+
type: "tool_call_completed",
|
|
741
|
+
index,
|
|
742
|
+
...(call.id ? { id: call.id } : {}),
|
|
743
|
+
name: call.name,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (result.usage) {
|
|
747
|
+
emitEvent({ type: "usage_observed", usage: result.usage });
|
|
748
|
+
}
|
|
749
|
+
emitEvent({
|
|
750
|
+
type: "provider_terminal",
|
|
751
|
+
...(result.finishReason
|
|
752
|
+
? { finishReason: result.finishReason }
|
|
753
|
+
: {}),
|
|
754
|
+
});
|
|
755
|
+
return result;
|
|
756
|
+
},
|
|
757
|
+
});
|
|
758
|
+
try {
|
|
759
|
+
onSuccessfulRequest?.(successfulRequestSnapshot(result.provider || providerId, result.model || attemptRequest.model || model, attemptRequest));
|
|
508
760
|
}
|
|
509
|
-
|
|
510
|
-
|
|
761
|
+
catch { }
|
|
762
|
+
return result;
|
|
763
|
+
};
|
|
764
|
+
try {
|
|
765
|
+
const result = await runAttempt(activeRequest, reason);
|
|
511
766
|
learnVisionOnSuccess();
|
|
512
767
|
return result;
|
|
513
768
|
}
|
|
@@ -516,6 +771,8 @@ async function tryStreamOnce(provider, providerId, request, model, auth, onToken
|
|
|
516
771
|
activeRequest.tools?.length &&
|
|
517
772
|
isToolsUnsupportedError(error)) {
|
|
518
773
|
markTextOnlyModel(providerId, model);
|
|
774
|
+
if (singleDispatch)
|
|
775
|
+
throw markStreamEmittedBytes(error, emittedBytes);
|
|
519
776
|
onStatus?.(`ℹ ${providerId}/${model} does not support native tools — falling back to text protocol`);
|
|
520
777
|
const textRequest = {
|
|
521
778
|
...activeRequest,
|
|
@@ -524,69 +781,84 @@ async function tryStreamOnce(provider, providerId, request, model, auth, onToken
|
|
|
524
781
|
parallelToolCalls: undefined,
|
|
525
782
|
};
|
|
526
783
|
try {
|
|
527
|
-
|
|
528
|
-
return await provider.stream(textRequest, auth, emit);
|
|
529
|
-
}
|
|
530
|
-
const result = await provider.complete(textRequest, auth);
|
|
531
|
-
emit(result.text);
|
|
532
|
-
return result;
|
|
784
|
+
return await runAttempt(textRequest, "adaptation");
|
|
533
785
|
}
|
|
534
786
|
catch (retryError) {
|
|
535
|
-
throw markStreamEmittedBytes(retryError, emittedBytes);
|
|
787
|
+
throw markStreamEmittedBytes(preservedFailure(retryError, error), emittedBytes);
|
|
536
788
|
}
|
|
537
789
|
}
|
|
538
790
|
// Model rejected a reasoning/thinking knob (e.g. chat_template_kwargs on a
|
|
539
|
-
// NIM chat template that does not accept it).
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
|
|
791
|
+
// NIM chat template that does not accept it). A parameter rejection is a
|
|
792
|
+
// request-time 4xx, so no tokens have streamed yet — retries are clean.
|
|
793
|
+
// Walk down the effort ladder first (max → xhigh → high → medium → low) so
|
|
794
|
+
// a model that merely rejects the highest requested depth keeps reasoning;
|
|
795
|
+
// only strip reasoning entirely once every candidate has been rejected.
|
|
796
|
+
if (emittedBytes === 0 && shouldEnterEffortLadder(error, activeRequest.thinking, providerId, model, singleDispatch)) {
|
|
797
|
+
if (singleDispatch) {
|
|
798
|
+
markReasoningUnsupported(providerId, model);
|
|
799
|
+
throw markStreamEmittedBytes(error, emittedBytes);
|
|
800
|
+
}
|
|
801
|
+
const thinking = activeRequest.thinking;
|
|
802
|
+
if (thinking?.enabled) {
|
|
803
|
+
const style = provider.reasoningStyle ?? "none";
|
|
804
|
+
const seen = new Set([
|
|
805
|
+
reasoningWireKey(thinking, style, model, providerId),
|
|
806
|
+
]);
|
|
807
|
+
for (const effort of fallbackEffortsFor(thinking.effort)) {
|
|
808
|
+
const candidate = { ...thinking, effort };
|
|
809
|
+
const key = reasoningWireKey(candidate, style, model, providerId);
|
|
810
|
+
if (seen.has(key))
|
|
811
|
+
continue;
|
|
812
|
+
seen.add(key);
|
|
813
|
+
onStatus?.(`ℹ ${providerId}/${model} rejected reasoning effort — retrying with ${effort}`);
|
|
814
|
+
const retryRequest = {
|
|
815
|
+
...activeRequest,
|
|
816
|
+
thinking: candidate,
|
|
817
|
+
};
|
|
818
|
+
try {
|
|
819
|
+
return await runAttempt(retryRequest, "adaptation");
|
|
820
|
+
}
|
|
821
|
+
catch (retryError) {
|
|
822
|
+
if (!shouldContinueEffortLadder(retryError, providerId)) {
|
|
823
|
+
throw markStreamEmittedBytes(preservedFailure(retryError, error), emittedBytes);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
543
828
|
markReasoningUnsupported(providerId, model);
|
|
544
829
|
onStatus?.(`ℹ ${providerId}/${model} rejected reasoning options — retrying without them`);
|
|
545
830
|
const retryRequest = withoutReasoning(activeRequest);
|
|
546
831
|
try {
|
|
547
|
-
|
|
548
|
-
return await provider.stream(retryRequest, auth, emit);
|
|
549
|
-
}
|
|
550
|
-
const result = await provider.complete(retryRequest, auth);
|
|
551
|
-
emit(result.text);
|
|
552
|
-
return result;
|
|
832
|
+
return await runAttempt(retryRequest, "adaptation");
|
|
553
833
|
}
|
|
554
834
|
catch (retryError) {
|
|
555
|
-
throw markStreamEmittedBytes(retryError, emittedBytes);
|
|
835
|
+
throw markStreamEmittedBytes(preservedFailure(retryError, error), emittedBytes);
|
|
556
836
|
}
|
|
557
837
|
}
|
|
558
838
|
if (emittedBytes === 0 &&
|
|
559
839
|
hasImageInput(activeRequest) &&
|
|
560
840
|
isImageInputUnsupportedError(error)) {
|
|
561
841
|
learnModelVisionCapability(providerId, model, false);
|
|
842
|
+
if (singleDispatch)
|
|
843
|
+
throw markStreamEmittedBytes(error, emittedBytes);
|
|
562
844
|
onStatus?.(`ℹ ${providerId}/${model} rejected image input — retrying without the attached image(s)`);
|
|
563
845
|
const textOnlyRequest = withoutImages(activeRequest);
|
|
564
846
|
try {
|
|
565
|
-
|
|
566
|
-
return await provider.stream(textOnlyRequest, auth, emit);
|
|
567
|
-
}
|
|
568
|
-
const result = await provider.complete(textOnlyRequest, auth);
|
|
569
|
-
emit(result.text);
|
|
570
|
-
return result;
|
|
847
|
+
return await runAttempt(textOnlyRequest, "adaptation");
|
|
571
848
|
}
|
|
572
849
|
catch (retryError) {
|
|
573
|
-
throw markStreamEmittedBytes(retryError, emittedBytes);
|
|
850
|
+
throw markStreamEmittedBytes(preservedFailure(retryError, error), emittedBytes);
|
|
574
851
|
}
|
|
575
852
|
}
|
|
576
|
-
if (emittedBytes === 0) {
|
|
853
|
+
if (emittedBytes === 0 && !singleDispatch) {
|
|
577
854
|
const restored = revertVisionSubstitution(providerId, model, activeRequest, error);
|
|
578
855
|
if (restored) {
|
|
579
856
|
onStatus?.(`ℹ ${providerId}/${model} is not available on this account — falling back to ${restored.original}`);
|
|
580
857
|
try {
|
|
581
|
-
|
|
582
|
-
return await provider.stream(restored.request, auth, emit);
|
|
583
|
-
}
|
|
584
|
-
const result = await provider.complete(restored.request, auth);
|
|
585
|
-
emit(result.text);
|
|
586
|
-
return result;
|
|
858
|
+
return await runAttempt(restored.request, "adaptation");
|
|
587
859
|
}
|
|
588
860
|
catch (retryError) {
|
|
589
|
-
throw markStreamEmittedBytes(retryError, emittedBytes);
|
|
861
|
+
throw markStreamEmittedBytes(preservedFailure(retryError, error), emittedBytes);
|
|
590
862
|
}
|
|
591
863
|
}
|
|
592
864
|
}
|
|
@@ -600,12 +872,16 @@ async function tryStreamOnce(provider, providerId, request, model, auth, onToken
|
|
|
600
872
|
*/
|
|
601
873
|
async function runWithKeyRotation(opts) {
|
|
602
874
|
const { providerId, provider, request, model, emitKey } = opts;
|
|
875
|
+
const singleDispatch = opts.singleDispatch === true;
|
|
603
876
|
const multi = await getProviderKeys(providerId);
|
|
604
877
|
const slots = multi.keys;
|
|
605
878
|
if (slots.length === 0) {
|
|
606
879
|
throw new Error("no API key configured");
|
|
607
880
|
}
|
|
608
|
-
const
|
|
881
|
+
const fullPlan = buildKeyAttemptPlan(slots.length, multi.activeIndex).filter((index) => !slots[index].disabled);
|
|
882
|
+
// A pinned operation uses the sticky key only: rotating would send the same
|
|
883
|
+
// large prompt again under a different credential.
|
|
884
|
+
const plan = singleDispatch ? fullPlan.slice(0, 1) : fullPlan;
|
|
609
885
|
if (plan.length === 0) {
|
|
610
886
|
throw new Error(`all ${slots.length} API key${slots.length === 1 ? "" : "s"} for ${providerId} are disabled — re-enable one to use this provider`);
|
|
611
887
|
}
|
|
@@ -613,12 +889,14 @@ async function runWithKeyRotation(opts) {
|
|
|
613
889
|
const multiKey = enabledCount > 1;
|
|
614
890
|
// Single key: time-increasing retries (MAX_RETRIES+1 attempts).
|
|
615
891
|
// Multi key: 2 attempts per key (initial + one retry), then next key.
|
|
616
|
-
const maxPerKey =
|
|
892
|
+
const maxPerKey = singleDispatch
|
|
893
|
+
? 1
|
|
894
|
+
: attemptsPerKey(enabledCount, (opts.maxRetries ?? MAX_RETRIES) + 1);
|
|
617
895
|
let lastError;
|
|
618
896
|
// Endpoint failover: a provider can carry several base URLs (e.g. Modal
|
|
619
897
|
// workspaces). On an auth/quota error the active endpoint may simply be the
|
|
620
898
|
// wrong workspace for the key, so rotate the endpoint too before giving up.
|
|
621
|
-
const storedEndpoints = providerUsesEndpoints(providerId)
|
|
899
|
+
const storedEndpoints = providerUsesEndpoints(providerId) && !singleDispatch
|
|
622
900
|
? getProviderEndpoints(providerId)
|
|
623
901
|
: undefined;
|
|
624
902
|
const endpointUrls = (storedEndpoints?.urls ?? []).filter((url) => !(storedEndpoints?.disabledUrls ?? []).includes(url));
|
|
@@ -654,13 +932,14 @@ async function runWithKeyRotation(opts) {
|
|
|
654
932
|
}
|
|
655
933
|
for (let attempt = 0; attempt < maxPerKey; attempt++) {
|
|
656
934
|
request.signal?.throwIfAborted();
|
|
935
|
+
const attemptReason = planIdx === 0 && attempt === 0 ? opts.initialAttemptReason : "retry";
|
|
657
936
|
try {
|
|
658
937
|
let result;
|
|
659
938
|
if (opts.mode === "stream") {
|
|
660
|
-
result = await tryStreamOnce(provider, providerId, request, model, auth, opts.onToken ?? (() => { }), opts.onStatus);
|
|
939
|
+
result = await tryStreamOnce(provider, providerId, request, model, auth, opts.onToken ?? (() => { }), opts.onStatus, attemptReason, singleDispatch, opts.onSuccessfulRequest);
|
|
661
940
|
}
|
|
662
941
|
else {
|
|
663
|
-
result = await tryCompleteOnce(provider, providerId, request, model, auth);
|
|
942
|
+
result = await tryCompleteOnce(provider, providerId, request, model, auth, attemptReason, opts.onStatus, singleDispatch);
|
|
664
943
|
}
|
|
665
944
|
// Sticky success only for stored multi-key (not env-only synthetic).
|
|
666
945
|
if (multi.source !== "env" && multi.source !== "local") {
|
|
@@ -682,7 +961,13 @@ async function runWithKeyRotation(opts) {
|
|
|
682
961
|
return result;
|
|
683
962
|
}
|
|
684
963
|
catch (error) {
|
|
964
|
+
const previousError = lastError;
|
|
685
965
|
lastError = error;
|
|
966
|
+
// An admission guard that fires while recovering from a real failure
|
|
967
|
+
// hides that failure. Report the cause the user can act on.
|
|
968
|
+
if (isOperationPolicyError(error) && previousError !== undefined) {
|
|
969
|
+
throw previousError;
|
|
970
|
+
}
|
|
686
971
|
// 404/422: other keys for the same model will not help.
|
|
687
972
|
if (isKeyCircleStopError(error)) {
|
|
688
973
|
throw error;
|
|
@@ -716,7 +1001,7 @@ async function runWithKeyRotation(opts) {
|
|
|
716
1001
|
}
|
|
717
1002
|
const canRetrySame = attempt + 1 < maxPerKey;
|
|
718
1003
|
if (canRetrySame) {
|
|
719
|
-
const wait = isRateLimited(error)
|
|
1004
|
+
const wait = isRateLimited(error) || isServerUnavailable(error)
|
|
720
1005
|
? retryWaitMs(error, attempt)
|
|
721
1006
|
: networkRetryWaitMs(attempt);
|
|
722
1007
|
if (wait > MAX_RETRY_WAIT_MS) {
|
|
@@ -777,15 +1062,19 @@ async function runWithKeyRotation(opts) {
|
|
|
777
1062
|
const err = lastError ?? new Error("all API keys failed");
|
|
778
1063
|
throw err;
|
|
779
1064
|
}
|
|
780
|
-
|
|
1065
|
+
async function completeWithProviderOperation(request, options, ledger) {
|
|
781
1066
|
const config = getConfig();
|
|
1067
|
+
const singleDispatch = options.singleDispatch === true;
|
|
782
1068
|
const requested = request.provider ?? config.defaultProvider;
|
|
783
1069
|
const providerImpl = getProvider(requested);
|
|
784
1070
|
const isDefaultModel = !request.model || request.model === providerImpl.defaultModel;
|
|
785
|
-
const fallbackEnabled =
|
|
1071
|
+
const fallbackEnabled = !singleDispatch &&
|
|
1072
|
+
config.providerFallback &&
|
|
786
1073
|
(await requestedRealKeyCount(requested)) !== 1 &&
|
|
787
1074
|
(isDefaultModel || request.allowModelFallback === true);
|
|
788
|
-
const order =
|
|
1075
|
+
const order = singleDispatch
|
|
1076
|
+
? [requested]
|
|
1077
|
+
: buildFallbackChain(requested, config.freeOnly, fallbackEnabled, request.preferModelFallback === true);
|
|
789
1078
|
const failures = [];
|
|
790
1079
|
const emitKey = makeKeyEmitter(options?.onStatus, options?.onKeyEvent);
|
|
791
1080
|
for (const providerId of order) {
|
|
@@ -800,7 +1089,10 @@ export async function completeWithProvider(request, options) {
|
|
|
800
1089
|
const model = providerId === requested
|
|
801
1090
|
? (request.model ?? provider.defaultModel)
|
|
802
1091
|
: provider.defaultModel;
|
|
803
|
-
const routeRequest =
|
|
1092
|
+
const routeRequest = {
|
|
1093
|
+
...requestForRoute(request, providerId, model),
|
|
1094
|
+
attemptUsage: ledger,
|
|
1095
|
+
};
|
|
804
1096
|
try {
|
|
805
1097
|
const result = await runWithKeyRotation({
|
|
806
1098
|
providerId,
|
|
@@ -809,17 +1101,26 @@ export async function completeWithProvider(request, options) {
|
|
|
809
1101
|
model,
|
|
810
1102
|
emitKey,
|
|
811
1103
|
mode: "complete",
|
|
1104
|
+
initialAttemptReason: providerId === requested ? "initial" : "fallback",
|
|
812
1105
|
...(options?.onStatus ? { onStatus: options.onStatus } : {}),
|
|
813
1106
|
...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
|
|
1107
|
+
...(singleDispatch ? { singleDispatch: true } : {}),
|
|
814
1108
|
});
|
|
815
1109
|
if (providerId !== requested) {
|
|
816
|
-
|
|
817
|
-
|
|
1110
|
+
if (options?.adoptFallback === true) {
|
|
1111
|
+
setDefaultProvider(providerId);
|
|
1112
|
+
setProviderModel(providerId, result.model || model);
|
|
1113
|
+
}
|
|
818
1114
|
options?.onStatus?.(`switching to ${providerId}/${result.model || model} after ${requested} failed`);
|
|
819
1115
|
}
|
|
820
1116
|
return result;
|
|
821
1117
|
}
|
|
822
1118
|
catch (error) {
|
|
1119
|
+
if (isOperationPolicyError(error)) {
|
|
1120
|
+
if (failures.length === 0)
|
|
1121
|
+
throw error;
|
|
1122
|
+
throw aggregateProviderError(`No provider could complete the request.${formatFailures(failures)}`, failures);
|
|
1123
|
+
}
|
|
823
1124
|
failures.push({
|
|
824
1125
|
provider: providerId,
|
|
825
1126
|
message: failureMessageFor(providerId, error),
|
|
@@ -833,18 +1134,68 @@ export async function completeWithProvider(request, options) {
|
|
|
833
1134
|
}
|
|
834
1135
|
throw aggregateProviderError(`No provider could complete the request.${formatFailures(failures)}`, failures);
|
|
835
1136
|
}
|
|
836
|
-
|
|
1137
|
+
function attachOperationUsageToError(error, snapshot) {
|
|
1138
|
+
if (error && typeof error === "object" && Object.isExtensible(error)) {
|
|
1139
|
+
try {
|
|
1140
|
+
Object.defineProperty(error, "operationUsage", {
|
|
1141
|
+
configurable: true,
|
|
1142
|
+
enumerable: false,
|
|
1143
|
+
value: snapshot,
|
|
1144
|
+
});
|
|
1145
|
+
return error;
|
|
1146
|
+
}
|
|
1147
|
+
catch { }
|
|
1148
|
+
}
|
|
1149
|
+
const message = error instanceof Error
|
|
1150
|
+
? error.message
|
|
1151
|
+
: typeof error === "string" && error.trim()
|
|
1152
|
+
? error
|
|
1153
|
+
: "Provider operation failed";
|
|
1154
|
+
const wrapped = new Error(message, { cause: error });
|
|
1155
|
+
Object.defineProperty(wrapped, "operationUsage", {
|
|
1156
|
+
configurable: true,
|
|
1157
|
+
enumerable: false,
|
|
1158
|
+
value: snapshot,
|
|
1159
|
+
});
|
|
1160
|
+
return wrapped;
|
|
1161
|
+
}
|
|
1162
|
+
export async function completeWithProvider(request, options = {}) {
|
|
1163
|
+
const ledger = resolveOperationLedger(options);
|
|
1164
|
+
try {
|
|
1165
|
+
const result = await completeWithProviderOperation(request, options, ledger);
|
|
1166
|
+
ledger.settle("completed");
|
|
1167
|
+
return { ...result, operationUsage: ledger.snapshot() };
|
|
1168
|
+
}
|
|
1169
|
+
catch (error) {
|
|
1170
|
+
ledger.settle(operationTerminalOutcome(error, request.signal));
|
|
1171
|
+
throw attachOperationUsageToError(error, ledger.snapshot());
|
|
1172
|
+
}
|
|
1173
|
+
finally {
|
|
1174
|
+
options.onOperationUsage?.(ledger.snapshot());
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async function streamWithProviderOperation(request, onToken, onStatusOrOptions, ledger) {
|
|
837
1178
|
const options = typeof onStatusOrOptions === "function"
|
|
838
1179
|
? { onStatus: onStatusOrOptions }
|
|
839
1180
|
: onStatusOrOptions ?? {};
|
|
1181
|
+
const operation = ledger ?? resolveOperationLedger(options);
|
|
1182
|
+
const relayToken = (token) => {
|
|
1183
|
+
operation.noteSemanticOutput();
|
|
1184
|
+
onToken(token);
|
|
1185
|
+
};
|
|
1186
|
+
const downstreamToolDelta = request.onToolCallDelta;
|
|
840
1187
|
const config = getConfig();
|
|
1188
|
+
const singleDispatch = options.singleDispatch === true;
|
|
841
1189
|
const requested = request.provider ?? config.defaultProvider;
|
|
842
1190
|
const providerImpl = getProvider(requested);
|
|
843
1191
|
const isDefaultModel = !request.model || request.model === providerImpl.defaultModel;
|
|
844
|
-
const fallbackEnabled =
|
|
1192
|
+
const fallbackEnabled = !singleDispatch &&
|
|
1193
|
+
config.providerFallback &&
|
|
845
1194
|
(await requestedRealKeyCount(requested)) !== 1 &&
|
|
846
1195
|
(isDefaultModel || request.allowModelFallback === true);
|
|
847
|
-
const order =
|
|
1196
|
+
const order = singleDispatch
|
|
1197
|
+
? [requested]
|
|
1198
|
+
: buildFallbackChain(requested, config.freeOnly, fallbackEnabled, request.preferModelFallback === true);
|
|
848
1199
|
const failures = [];
|
|
849
1200
|
const emitStatus = options.onStatus ?? ((message) => onToken(message));
|
|
850
1201
|
const emitKey = makeKeyEmitter(emitStatus, options.onKeyEvent);
|
|
@@ -859,7 +1210,28 @@ export async function streamWithProvider(request, onToken, onStatusOrOptions) {
|
|
|
859
1210
|
const model = providerId === requested
|
|
860
1211
|
? (request.model ?? provider.defaultModel)
|
|
861
1212
|
: provider.defaultModel;
|
|
862
|
-
const routeRequest =
|
|
1213
|
+
const routeRequest = {
|
|
1214
|
+
...requestForRoute(request, providerId, model),
|
|
1215
|
+
attemptUsage: operation,
|
|
1216
|
+
...(downstreamToolDelta
|
|
1217
|
+
? {
|
|
1218
|
+
onToolCallDelta: (delta) => {
|
|
1219
|
+
operation.noteSemanticOutput();
|
|
1220
|
+
downstreamToolDelta(delta);
|
|
1221
|
+
},
|
|
1222
|
+
}
|
|
1223
|
+
: {}),
|
|
1224
|
+
...(options.onStreamEvent
|
|
1225
|
+
? {
|
|
1226
|
+
onStreamEvent: (event) => {
|
|
1227
|
+
if (isSemanticStreamOutputEvent(event)) {
|
|
1228
|
+
operation.noteSemanticOutput();
|
|
1229
|
+
}
|
|
1230
|
+
options.onStreamEvent(event);
|
|
1231
|
+
},
|
|
1232
|
+
}
|
|
1233
|
+
: {}),
|
|
1234
|
+
};
|
|
863
1235
|
try {
|
|
864
1236
|
const result = await runWithKeyRotation({
|
|
865
1237
|
providerId,
|
|
@@ -868,18 +1240,30 @@ export async function streamWithProvider(request, onToken, onStatusOrOptions) {
|
|
|
868
1240
|
model,
|
|
869
1241
|
emitKey,
|
|
870
1242
|
mode: "stream",
|
|
871
|
-
|
|
1243
|
+
initialAttemptReason: providerId === requested ? "initial" : "fallback",
|
|
1244
|
+
onToken: relayToken,
|
|
872
1245
|
onStatus: emitStatus,
|
|
873
1246
|
...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
|
|
1247
|
+
...(singleDispatch ? { singleDispatch: true } : {}),
|
|
1248
|
+
...(options.onSuccessfulRequest
|
|
1249
|
+
? { onSuccessfulRequest: options.onSuccessfulRequest }
|
|
1250
|
+
: {}),
|
|
874
1251
|
});
|
|
875
1252
|
if (providerId !== requested) {
|
|
876
|
-
|
|
877
|
-
|
|
1253
|
+
if (options.adoptFallback === true) {
|
|
1254
|
+
setDefaultProvider(providerId);
|
|
1255
|
+
setProviderModel(providerId, result.model || model);
|
|
1256
|
+
}
|
|
878
1257
|
options.onStatus?.(`switching to ${providerId}/${result.model || model} after ${requested} failed`);
|
|
879
1258
|
}
|
|
880
1259
|
return result;
|
|
881
1260
|
}
|
|
882
1261
|
catch (error) {
|
|
1262
|
+
if (isOperationPolicyError(error)) {
|
|
1263
|
+
if (failures.length === 0)
|
|
1264
|
+
throw error;
|
|
1265
|
+
throw aggregateProviderError(`No provider could stream the request.${formatFailures(failures)}`, failures, streamEmittedBytes(error));
|
|
1266
|
+
}
|
|
883
1267
|
failures.push({
|
|
884
1268
|
provider: providerId,
|
|
885
1269
|
message: failureMessageFor(providerId, error),
|
|
@@ -897,6 +1281,24 @@ export async function streamWithProvider(request, onToken, onStatusOrOptions) {
|
|
|
897
1281
|
}
|
|
898
1282
|
throw aggregateProviderError(`No provider could stream the request.${formatFailures(failures)}`, failures);
|
|
899
1283
|
}
|
|
1284
|
+
export async function streamWithProvider(request, onToken, onStatusOrOptions) {
|
|
1285
|
+
const options = typeof onStatusOrOptions === "function"
|
|
1286
|
+
? { onStatus: onStatusOrOptions }
|
|
1287
|
+
: onStatusOrOptions ?? {};
|
|
1288
|
+
const ledger = resolveOperationLedger(options);
|
|
1289
|
+
try {
|
|
1290
|
+
const result = await streamWithProviderOperation(request, onToken, options, ledger);
|
|
1291
|
+
ledger.settle("completed");
|
|
1292
|
+
return { ...result, operationUsage: ledger.snapshot() };
|
|
1293
|
+
}
|
|
1294
|
+
catch (error) {
|
|
1295
|
+
ledger.settle(operationTerminalOutcome(error, request.signal));
|
|
1296
|
+
throw attachOperationUsageToError(error, ledger.snapshot());
|
|
1297
|
+
}
|
|
1298
|
+
finally {
|
|
1299
|
+
options.onOperationUsage?.(ledger.snapshot());
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
900
1302
|
export async function pingProvider(providerId, secretOverride) {
|
|
901
1303
|
const provider = getProvider(providerId);
|
|
902
1304
|
const resolved = await providerAuth(providerId);
|