@code-yeongyu/senpi 2026.8.28 → 2026.8.30
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/CHANGELOG.md +140 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +2 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +50 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +313 -25
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +3 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +155 -37
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/credential-accounts.d.ts.map +1 -1
- package/dist/core/credential-accounts.js +45 -21
- package/dist/core/credential-accounts.js.map +1 -1
- package/dist/core/credential-pool/classify.d.ts +6 -1
- package/dist/core/credential-pool/classify.d.ts.map +1 -1
- package/dist/core/credential-pool/classify.js +22 -5
- package/dist/core/credential-pool/classify.js.map +1 -1
- package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
- package/dist/core/credential-pool/env-slots.js +2 -0
- package/dist/core/credential-pool/env-slots.js.map +1 -1
- package/dist/core/credential-pool/failover.d.ts +8 -0
- package/dist/core/credential-pool/failover.d.ts.map +1 -1
- package/dist/core/credential-pool/failover.js +24 -6
- package/dist/core/credential-pool/failover.js.map +1 -1
- package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
- package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
- package/dist/core/credential-pool/rotation-stream.js +97 -10
- package/dist/core/credential-pool/rotation-stream.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
- package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
- package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
- package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/index.js +7 -0
- package/dist/core/extensions/builtin/goal/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
- package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
- package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
- package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
- package/dist/core/extensions/builtin/service-tier.js +13 -0
- package/dist/core/extensions/builtin/service-tier.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.js +4 -2
- package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
- package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/manager.js +31 -3
- package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
- package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
- package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
- package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
- package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
- package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
- package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
- package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
- package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
- package/dist/core/model-config-schema.d.ts +4 -4
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +74 -37
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/retry-fallback/chains.d.ts +23 -1
- package/dist/core/retry-fallback/chains.d.ts.map +1 -1
- package/dist/core/retry-fallback/chains.js +59 -3
- package/dist/core/retry-fallback/chains.js.map +1 -1
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js +9 -2
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/retry-fallback/settings.d.ts.map +1 -1
- package/dist/core/retry-fallback/settings.js +9 -0
- package/dist/core/retry-fallback/settings.js.map +1 -1
- package/dist/core/retry-fallback/validate.js +1 -1
- package/dist/core/retry-fallback/validate.js.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +9 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +40 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +6 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +97 -15
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +8 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +76 -34
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/main.d.ts +7 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +10 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/favorite-models-selector.js +2 -2
- package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +7 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +34 -8
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/grok/chrome.d.ts +2 -0
- package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
- package/dist/modes/interactive/grok/chrome.js.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.js +545 -40
- package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +7 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +390 -239
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/connection-handler.d.ts +8 -1
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +268 -63
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/host-ensure.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
- package/dist/modes/rpc/host-ensure.js +15 -2
- package/dist/modes/rpc/host-ensure.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +31 -12
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-watchdog.d.ts +2 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
- package/dist/modes/rpc/host-watchdog.js +23 -12
- package/dist/modes/rpc/host-watchdog.js.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +20 -8
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +91 -16
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +129 -37
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
- package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-validation.js +66 -0
- package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +3 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +241 -3
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-binding.d.ts +1 -0
- package/dist/modes/rpc/session-binding.d.ts.map +1 -1
- package/dist/modes/rpc/session-binding.js +21 -1
- package/dist/modes/rpc/session-binding.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts +22 -0
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +148 -16
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +4 -0
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +58 -7
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
- package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
- package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
- package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +17 -2
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +67 -4
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
- package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
- package/dist/modes/rpc/socket-event-fanout.js +84 -0
- package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
- package/docs/rpc.md +40 -1
- package/docs/settings.md +34 -0
- package/docs/windows.md +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +62 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +6 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
- package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -32,8 +32,30 @@ export declare function baseSelector(selector: Pick<FallbackSelector, "provider"
|
|
|
32
32
|
* Provider-qualified keys and entries keep exact semantics, and an explicit key
|
|
33
33
|
* always overrides the expansion it collides with.
|
|
34
34
|
*/
|
|
35
|
+
/**
|
|
36
|
+
* Chain key matched when no exact or base key resolves for the current model.
|
|
37
|
+
* Exists so a model without its own configured chain still has an escape lane:
|
|
38
|
+
* without it, a hard-failing upstream wedges the session terminal even though
|
|
39
|
+
* healthy fallback targets exist (desktop thread 487d7c29, 2026-08-28: nine
|
|
40
|
+
* consecutive upstream 500s, zero fallback attempts, terminal error).
|
|
41
|
+
* Users disable it with the `"*": []` tombstone.
|
|
42
|
+
*/
|
|
43
|
+
export declare const WILDCARD_CHAIN_KEY = "*";
|
|
44
|
+
/**
|
|
45
|
+
* Whether the user explicitly switched fallback OFF for this model with the
|
|
46
|
+
* documented `[]` tombstone (exact key, base key, or a bare family key that
|
|
47
|
+
* covers it). The shipped wildcard lane must never resurrect fallback against
|
|
48
|
+
* that instruction, so the wildcard gate consults this first.
|
|
49
|
+
*
|
|
50
|
+
* Bare-family matching is deliberately conservative: an ambiguous match
|
|
51
|
+
* suppresses the wildcard rather than overriding an explicit opt-out, because a
|
|
52
|
+
* surprising extra fallback hop is worse than a missing convenience lane.
|
|
53
|
+
*/
|
|
54
|
+
export declare function hasExplicitFallbackOptOut(chains: FallbackChains, currentModel: Model<Api>, currentThinking: ThinkingLevel | undefined): boolean;
|
|
35
55
|
export declare function canonicalizeFallbackChains(chains: FallbackChains, lookup: FallbackModelLookup): FallbackChains;
|
|
36
|
-
export declare function resolveChainKey(currentModel: Model<Api>, currentThinking: ThinkingLevel | undefined, chains: FallbackChains
|
|
56
|
+
export declare function resolveChainKey(currentModel: Model<Api>, currentThinking: ThinkingLevel | undefined, chains: FallbackChains, options?: {
|
|
57
|
+
allowWildcard?: boolean;
|
|
58
|
+
}): string | undefined;
|
|
37
59
|
/**
|
|
38
60
|
* Returns entries after the current fallback. A primary or unknown selector starts
|
|
39
61
|
* from the beginning, which also makes re-entry after stale runtime state safe.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAWxD,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEzE,MAAM,MAAM,mBAAmB,GAC5B,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,GACrB;IACA,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACvB,YAAY,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IAC1C,iBAAiB,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IAC/C,kBAAkB,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;CAC/C,CAAC;AA8BL,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAeD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,SAAS,CAkC5G;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,MAAM,CAGvF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,MAAM,CAExF;AAED;;;;;;;;GAQG;AACH;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,cAAc,EACtB,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EACxB,eAAe,EAAE,aAAa,GAAG,SAAS,GACxC,OAAO,CAaT;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,GAAG,cAAc,CAuE9G;AAED,wBAAgB,eAAe,CAC9B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EACxB,eAAe,EAAE,aAAa,GAAG,SAAS,EAC1C,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,MAAM,GAAG,SAAS,CAWpB;AAoBD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,eAAe,EAAE,gBAAgB,GAAG,MAAM,GACxC,SAAS,MAAM,EAAE,CAQnB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isValidThinkingLevel } from "../../cli/args.js";
|
|
2
2
|
import { findExactModelReferenceMatch, parseModelPattern } from "../model-resolver.js";
|
|
3
|
-
import { MAX_PROVIDERS_PER_FAMILY, parseBareSelector, rankFamilyModels } from "./expansion.js";
|
|
3
|
+
import { MAX_PROVIDERS_PER_FAMILY, matchesFamily, parseBareSelector, rankFamilyModels, } from "./expansion.js";
|
|
4
4
|
function availableModels(lookup) {
|
|
5
5
|
return "getAll" in lookup ? lookup.getAll() : [...lookup];
|
|
6
6
|
}
|
|
@@ -91,6 +91,43 @@ export function baseSelector(selector) {
|
|
|
91
91
|
* Provider-qualified keys and entries keep exact semantics, and an explicit key
|
|
92
92
|
* always overrides the expansion it collides with.
|
|
93
93
|
*/
|
|
94
|
+
/**
|
|
95
|
+
* Chain key matched when no exact or base key resolves for the current model.
|
|
96
|
+
* Exists so a model without its own configured chain still has an escape lane:
|
|
97
|
+
* without it, a hard-failing upstream wedges the session terminal even though
|
|
98
|
+
* healthy fallback targets exist (desktop thread 487d7c29, 2026-08-28: nine
|
|
99
|
+
* consecutive upstream 500s, zero fallback attempts, terminal error).
|
|
100
|
+
* Users disable it with the `"*": []` tombstone.
|
|
101
|
+
*/
|
|
102
|
+
export const WILDCARD_CHAIN_KEY = "*";
|
|
103
|
+
/**
|
|
104
|
+
* Whether the user explicitly switched fallback OFF for this model with the
|
|
105
|
+
* documented `[]` tombstone (exact key, base key, or a bare family key that
|
|
106
|
+
* covers it). The shipped wildcard lane must never resurrect fallback against
|
|
107
|
+
* that instruction, so the wildcard gate consults this first.
|
|
108
|
+
*
|
|
109
|
+
* Bare-family matching is deliberately conservative: an ambiguous match
|
|
110
|
+
* suppresses the wildcard rather than overriding an explicit opt-out, because a
|
|
111
|
+
* surprising extra fallback hop is worse than a missing convenience lane.
|
|
112
|
+
*/
|
|
113
|
+
export function hasExplicitFallbackOptOut(chains, currentModel, currentThinking) {
|
|
114
|
+
const base = formatSelector(currentModel).toLowerCase();
|
|
115
|
+
const exact = currentThinking ? `${base}:${currentThinking}`.toLowerCase() : base;
|
|
116
|
+
for (const [key, entries] of Object.entries(chains)) {
|
|
117
|
+
if (!Array.isArray(entries) || !isChainTombstone(entries))
|
|
118
|
+
continue;
|
|
119
|
+
if (key === WILDCARD_CHAIN_KEY)
|
|
120
|
+
continue;
|
|
121
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
122
|
+
const keyBase = normalizedBase(normalizedKey);
|
|
123
|
+
if (keyBase === base || normalizedKey === exact)
|
|
124
|
+
return true;
|
|
125
|
+
// Bare family key (no provider prefix): treat an id match as covering.
|
|
126
|
+
if (!normalizedKey.includes("/") && matchesFamily(currentModel, keyBase))
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
94
131
|
export function canonicalizeFallbackChains(chains, lookup) {
|
|
95
132
|
const models = availableModels(lookup);
|
|
96
133
|
const tiers = authTiers(lookup);
|
|
@@ -149,14 +186,33 @@ export function canonicalizeFallbackChains(chains, lookup) {
|
|
|
149
186
|
if (tombstones.has(normalizedBase(key)))
|
|
150
187
|
delete canonical[key];
|
|
151
188
|
}
|
|
189
|
+
// The wildcard key is not a model selector, so both loops above skip it.
|
|
190
|
+
// Its entries expand exactly like any other chain; a `[]` tombstone removes
|
|
191
|
+
// the lane entirely. Self-selection of the failing model is prevented at
|
|
192
|
+
// candidate time (nextCandidate's "self" skip), not here, because the
|
|
193
|
+
// wildcard has no key selector to filter against.
|
|
194
|
+
const wildcardEntries = chains[WILDCARD_CHAIN_KEY];
|
|
195
|
+
if (Array.isArray(wildcardEntries) && !isChainTombstone(wildcardEntries)) {
|
|
196
|
+
const canonicalEntries = expandEntries(wildcardEntries, WILDCARD_CHAIN_KEY);
|
|
197
|
+
if (canonicalEntries.length > 0)
|
|
198
|
+
canonical[WILDCARD_CHAIN_KEY] = canonicalEntries;
|
|
199
|
+
}
|
|
152
200
|
return canonical;
|
|
153
201
|
}
|
|
154
|
-
export function resolveChainKey(currentModel, currentThinking, chains) {
|
|
202
|
+
export function resolveChainKey(currentModel, currentThinking, chains, options) {
|
|
155
203
|
const base = formatSelector(currentModel);
|
|
156
204
|
const exact = currentThinking ? `${base}:${currentThinking}` : base;
|
|
157
205
|
if (Object.hasOwn(chains, exact))
|
|
158
206
|
return exact;
|
|
159
|
-
|
|
207
|
+
if (Object.hasOwn(chains, base))
|
|
208
|
+
return base;
|
|
209
|
+
// The wildcard is opt-in per call site: it is a last resort for a model with
|
|
210
|
+
// no chain of its own, and it must never hijack a session already walking a
|
|
211
|
+
// configured chain (the last rung of that chain usually has no key either).
|
|
212
|
+
// nextCandidate consults the active episode's chainKey before asking for it.
|
|
213
|
+
if (!options?.allowWildcard)
|
|
214
|
+
return undefined;
|
|
215
|
+
return Object.hasOwn(chains, WILDCARD_CHAIN_KEY) ? WILDCARD_CHAIN_KEY : undefined;
|
|
160
216
|
}
|
|
161
217
|
function formatParsedSelector(selector) {
|
|
162
218
|
const base = baseSelector(selector);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chains.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { isValidThinkingLevel } from \"../../cli/args.ts\";\nimport { findExactModelReferenceMatch, parseModelPattern } from \"../model-resolver.ts\";\nimport { type FallbackAuthTiers, MAX_PROVIDERS_PER_FAMILY, parseBareSelector, rankFamilyModels } from \"./expansion.ts\";\n\nexport interface FallbackSelector {\n\traw: string;\n\tprovider: string;\n\tid: string;\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport type FallbackChains = Readonly<Record<string, readonly string[]>>;\n\nexport type FallbackModelLookup =\n\t| readonly Model<Api>[]\n\t| {\n\t\t\tgetAll(): Model<Api>[];\n\t\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\t\thasConfiguredAuth?(model: Model<Api>): boolean;\n\t\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t };\n\nfunction availableModels(lookup: FallbackModelLookup): Model<Api>[] {\n\treturn \"getAll\" in lookup ? lookup.getAll() : [...lookup];\n}\n\n/**\n * Auth tier is optional so array lookups and older callers keep working; without\n * it every provider lands in the non-OAuth tier and the precedence table decides.\n */\nfunction authTiers(lookup: FallbackModelLookup): FallbackAuthTiers {\n\tif (Array.isArray(lookup)) return { isUsingOAuth: () => false };\n\tconst registry = lookup as {\n\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\thasConfiguredAuth?(model: Model<Api>): boolean;\n\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t};\n\treturn {\n\t\tisUsingOAuth: (model) => registry.isUsingOAuth?.(model) === true,\n\t\thasConfiguredAuth:\n\t\t\ttypeof registry.hasConfiguredAuth === \"function\"\n\t\t\t\t? (model) => registry.hasConfiguredAuth?.(model) === true\n\t\t\t\t: undefined,\n\t\tisFallbackEligible:\n\t\t\ttypeof registry.isFallbackEligible === \"function\"\n\t\t\t\t? (model) => registry.isFallbackEligible?.(model) !== false\n\t\t\t\t: undefined,\n\t};\n}\n\n/** An empty entry list is the documented opt-out; it survives as a tombstone. */\nexport function isChainTombstone(entries: readonly string[] | undefined): boolean {\n\treturn Array.isArray(entries) && entries.length === 0;\n}\n\nfunction selectorReference(raw: string): { reference: string; thinkingLevel?: ThinkingLevel } | undefined {\n\tconst trimmed = raw.trim();\n\tif (!trimmed.includes(\"/\") || trimmed.includes(\"*\")) return undefined;\n\n\tconst lastColon = trimmed.lastIndexOf(\":\");\n\tif (lastColon === -1) return { reference: trimmed };\n\n\tconst prefix = trimmed.slice(0, lastColon);\n\tconst suffix = trimmed.slice(lastColon + 1).toLowerCase();\n\tif (!isValidThinkingLevel(suffix)) return { reference: trimmed };\n\treturn { reference: prefix, thinkingLevel: suffix };\n}\n\n/**\n * Resolves only complete provider/model selectors. The full input is resolved first\n * so colons belonging to a model id remain part of that id; aliases then resolve\n * against the model-id portion while retaining the explicitly selected provider.\n */\nexport function parseFallbackSelector(raw: string, lookup: FallbackModelLookup): FallbackSelector | undefined {\n\tconst models = availableModels(lookup);\n\tconst trimmed = raw.trim();\n\tconst parsedReference = selectorReference(trimmed);\n\tif (!parsedReference) return undefined;\n\n\tconst fullMatch = findExactModelReferenceMatch(trimmed, models);\n\tif (fullMatch) {\n\t\treturn { raw: trimmed, provider: fullMatch.provider, id: fullMatch.id };\n\t}\n\n\tconst slashIndex = parsedReference.reference.indexOf(\"/\");\n\tconst provider = parsedReference.reference.slice(0, slashIndex).trim();\n\tconst modelPattern = parsedReference.reference.slice(slashIndex + 1).trim();\n\tif (!provider || !modelPattern) return undefined;\n\n\t// The provider is explicit, so resolve the id pattern inside that provider only.\n\t// A global lookup lets foreign ids containing the pattern (e.g. Bedrock's\n\t// \"us.anthropic.claude-opus-5\") win over the requested provider's exact id.\n\tconst providerModels = models.filter((model) => model.provider.toLowerCase() === provider.toLowerCase());\n\tconst parsed = parseModelPattern(\n\t\tparsedReference.thinkingLevel ? `${modelPattern}:${parsedReference.thinkingLevel}` : modelPattern,\n\t\tproviderModels,\n\t\t{ allowInvalidThinkingLevelFallback: false },\n\t);\n\tif (!parsed.model || parsed.warning) return undefined;\n\tif (parsedReference.thinkingLevel && !parsed.thinkingLevel) return undefined;\n\n\treturn {\n\t\traw: trimmed,\n\t\tprovider: parsed.model.provider,\n\t\tid: parsed.model.id,\n\t\tthinkingLevel: parsed.thinkingLevel,\n\t};\n}\n\nexport function formatSelector(model: Model<Api>, thinkingLevel?: ThinkingLevel): string {\n\tconst base = `${model.provider}/${model.id}`;\n\treturn thinkingLevel ? `${base}:${thinkingLevel}` : base;\n}\n\nexport function baseSelector(selector: Pick<FallbackSelector, \"provider\" | \"id\">): string {\n\treturn `${selector.provider}/${selector.id}`;\n}\n\n/**\n * Converts validated configuration to canonical selector strings for runtime lookup.\n *\n * A bare key (no provider prefix) is a model-family policy: it expands to one\n * canonical key per provider serving that family, so a chain shipped for\n * `claude-fable-5` applies no matter which provider the user attached it through.\n * Provider-qualified keys and entries keep exact semantics, and an explicit key\n * always overrides the expansion it collides with.\n */\nexport function canonicalizeFallbackChains(chains: FallbackChains, lookup: FallbackModelLookup): FallbackChains {\n\tconst models = availableModels(lookup);\n\tconst tiers = authTiers(lookup);\n\tconst canonical: Record<string, readonly string[]> = {};\n\tconst explicitKeys = new Set<string>();\n\tconst tombstones = new Set<string>();\n\n\tconst expandEntries = (entries: readonly string[], keySelector: string): string[] =>\n\t\tentries.flatMap((entry) => {\n\t\t\tconst bare = parseBareSelector(entry);\n\t\t\tif (bare) {\n\t\t\t\treturn rankFamilyModels(models, bare.family, tiers, { limit: MAX_PROVIDERS_PER_FAMILY })\n\t\t\t\t\t.map((model) =>\n\t\t\t\t\t\tbare.thinkingLevel ? `${formatSelector(model)}:${bare.thinkingLevel}` : formatSelector(model),\n\t\t\t\t\t)\n\t\t\t\t\t.filter((selector) => normalizedBase(selector) !== normalizedBase(keySelector));\n\t\t\t}\n\t\t\tconst parsedEntry = parseFallbackSelector(entry, models);\n\t\t\treturn parsedEntry ? [formatParsedSelector(parsedEntry)] : [];\n\t\t});\n\n\t// Bare keys expand first so a same-named explicit key can overwrite them.\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tconst bareKey = parseBareSelector(key);\n\t\tif (!bareKey || !Array.isArray(entries)) continue;\n\t\tif (isChainTombstone(entries)) {\n\t\t\tfor (const model of rankFamilyModels(models, bareKey.family, tiers)) {\n\t\t\t\ttombstones.add(formatSelector(model).toLowerCase());\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const model of rankFamilyModels(models, bareKey.family, tiers)) {\n\t\t\tconst keySelector = bareKey.thinkingLevel\n\t\t\t\t? `${formatSelector(model)}:${bareKey.thinkingLevel}`\n\t\t\t\t: formatSelector(model);\n\t\t\tconst canonicalEntries = expandEntries(entries, keySelector);\n\t\t\tif (canonicalEntries.length > 0) canonical[keySelector] = canonicalEntries;\n\t\t}\n\t}\n\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tif (parseBareSelector(key)) continue;\n\t\tconst parsedKey = parseFallbackSelector(key, models);\n\t\tif (!parsedKey || !Array.isArray(entries)) continue;\n\t\tconst keySelector = formatParsedSelector(parsedKey);\n\t\tif (isChainTombstone(entries)) {\n\t\t\ttombstones.add(normalizedBase(keySelector));\n\t\t\tcontinue;\n\t\t}\n\t\texplicitKeys.add(keySelector.toLowerCase());\n\t\tconst canonicalEntries = expandEntries(entries, keySelector);\n\t\tif (canonicalEntries.length > 0) canonical[keySelector] = canonicalEntries;\n\t}\n\n\tfor (const key of Object.keys(canonical)) {\n\t\tif (explicitKeys.has(key.toLowerCase())) continue;\n\t\tif (tombstones.has(normalizedBase(key))) delete canonical[key];\n\t}\n\n\treturn canonical;\n}\n\nexport function resolveChainKey(\n\tcurrentModel: Model<Api>,\n\tcurrentThinking: ThinkingLevel | undefined,\n\tchains: FallbackChains,\n): string | undefined {\n\tconst base = formatSelector(currentModel);\n\tconst exact = currentThinking ? `${base}:${currentThinking}` : base;\n\tif (Object.hasOwn(chains, exact)) return exact;\n\treturn Object.hasOwn(chains, base) ? base : undefined;\n}\n\nfunction formatParsedSelector(selector: FallbackSelector): string {\n\tconst base = baseSelector(selector);\n\treturn selector.thinkingLevel ? `${base}:${selector.thinkingLevel}` : base;\n}\n\nfunction normalizedBase(selector: FallbackSelector | string): string {\n\tif (typeof selector !== \"string\") return baseSelector(selector).toLowerCase();\n\n\tconst normalized = selector.trim().toLowerCase();\n\tconst lastColon = normalized.lastIndexOf(\":\");\n\tif (lastColon === -1 || !isValidThinkingLevel(normalized.slice(lastColon + 1))) return normalized;\n\treturn normalized.slice(0, lastColon);\n}\n\nfunction normalizedExact(selector: FallbackSelector | string): string {\n\treturn typeof selector === \"string\" ? selector.trim().toLowerCase() : formatParsedSelector(selector).toLowerCase();\n}\n\n/**\n * Returns entries after the current fallback. A primary or unknown selector starts\n * from the beginning, which also makes re-entry after stale runtime state safe.\n */\nexport function candidatesAfter(\n\tchainEntries: readonly string[],\n\tcurrentSelector: FallbackSelector | string,\n): readonly string[] {\n\tconst exact = normalizedExact(currentSelector);\n\tconst exactIndex = chainEntries.findIndex((entry) => entry.toLowerCase() === exact);\n\tif (exactIndex !== -1) return chainEntries.slice(exactIndex + 1);\n\n\tconst base = normalizedBase(currentSelector);\n\tconst baseIndex = chainEntries.findIndex((entry) => normalizedBase(entry) === base);\n\treturn baseIndex === -1 ? chainEntries : chainEntries.slice(baseIndex + 1);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"chains.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { isValidThinkingLevel } from \"../../cli/args.ts\";\nimport { findExactModelReferenceMatch, parseModelPattern } from \"../model-resolver.ts\";\nimport {\n\ttype FallbackAuthTiers,\n\tMAX_PROVIDERS_PER_FAMILY,\n\tmatchesFamily,\n\tparseBareSelector,\n\trankFamilyModels,\n} from \"./expansion.ts\";\n\nexport interface FallbackSelector {\n\traw: string;\n\tprovider: string;\n\tid: string;\n\tthinkingLevel?: ThinkingLevel;\n}\n\nexport type FallbackChains = Readonly<Record<string, readonly string[]>>;\n\nexport type FallbackModelLookup =\n\t| readonly Model<Api>[]\n\t| {\n\t\t\tgetAll(): Model<Api>[];\n\t\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\t\thasConfiguredAuth?(model: Model<Api>): boolean;\n\t\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t };\n\nfunction availableModels(lookup: FallbackModelLookup): Model<Api>[] {\n\treturn \"getAll\" in lookup ? lookup.getAll() : [...lookup];\n}\n\n/**\n * Auth tier is optional so array lookups and older callers keep working; without\n * it every provider lands in the non-OAuth tier and the precedence table decides.\n */\nfunction authTiers(lookup: FallbackModelLookup): FallbackAuthTiers {\n\tif (Array.isArray(lookup)) return { isUsingOAuth: () => false };\n\tconst registry = lookup as {\n\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\thasConfiguredAuth?(model: Model<Api>): boolean;\n\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t};\n\treturn {\n\t\tisUsingOAuth: (model) => registry.isUsingOAuth?.(model) === true,\n\t\thasConfiguredAuth:\n\t\t\ttypeof registry.hasConfiguredAuth === \"function\"\n\t\t\t\t? (model) => registry.hasConfiguredAuth?.(model) === true\n\t\t\t\t: undefined,\n\t\tisFallbackEligible:\n\t\t\ttypeof registry.isFallbackEligible === \"function\"\n\t\t\t\t? (model) => registry.isFallbackEligible?.(model) !== false\n\t\t\t\t: undefined,\n\t};\n}\n\n/** An empty entry list is the documented opt-out; it survives as a tombstone. */\nexport function isChainTombstone(entries: readonly string[] | undefined): boolean {\n\treturn Array.isArray(entries) && entries.length === 0;\n}\n\nfunction selectorReference(raw: string): { reference: string; thinkingLevel?: ThinkingLevel } | undefined {\n\tconst trimmed = raw.trim();\n\tif (!trimmed.includes(\"/\") || trimmed.includes(\"*\")) return undefined;\n\n\tconst lastColon = trimmed.lastIndexOf(\":\");\n\tif (lastColon === -1) return { reference: trimmed };\n\n\tconst prefix = trimmed.slice(0, lastColon);\n\tconst suffix = trimmed.slice(lastColon + 1).toLowerCase();\n\tif (!isValidThinkingLevel(suffix)) return { reference: trimmed };\n\treturn { reference: prefix, thinkingLevel: suffix };\n}\n\n/**\n * Resolves only complete provider/model selectors. The full input is resolved first\n * so colons belonging to a model id remain part of that id; aliases then resolve\n * against the model-id portion while retaining the explicitly selected provider.\n */\nexport function parseFallbackSelector(raw: string, lookup: FallbackModelLookup): FallbackSelector | undefined {\n\tconst models = availableModels(lookup);\n\tconst trimmed = raw.trim();\n\tconst parsedReference = selectorReference(trimmed);\n\tif (!parsedReference) return undefined;\n\n\tconst fullMatch = findExactModelReferenceMatch(trimmed, models);\n\tif (fullMatch) {\n\t\treturn { raw: trimmed, provider: fullMatch.provider, id: fullMatch.id };\n\t}\n\n\tconst slashIndex = parsedReference.reference.indexOf(\"/\");\n\tconst provider = parsedReference.reference.slice(0, slashIndex).trim();\n\tconst modelPattern = parsedReference.reference.slice(slashIndex + 1).trim();\n\tif (!provider || !modelPattern) return undefined;\n\n\t// The provider is explicit, so resolve the id pattern inside that provider only.\n\t// A global lookup lets foreign ids containing the pattern (e.g. Bedrock's\n\t// \"us.anthropic.claude-opus-5\") win over the requested provider's exact id.\n\tconst providerModels = models.filter((model) => model.provider.toLowerCase() === provider.toLowerCase());\n\tconst parsed = parseModelPattern(\n\t\tparsedReference.thinkingLevel ? `${modelPattern}:${parsedReference.thinkingLevel}` : modelPattern,\n\t\tproviderModels,\n\t\t{ allowInvalidThinkingLevelFallback: false },\n\t);\n\tif (!parsed.model || parsed.warning) return undefined;\n\tif (parsedReference.thinkingLevel && !parsed.thinkingLevel) return undefined;\n\n\treturn {\n\t\traw: trimmed,\n\t\tprovider: parsed.model.provider,\n\t\tid: parsed.model.id,\n\t\tthinkingLevel: parsed.thinkingLevel,\n\t};\n}\n\nexport function formatSelector(model: Model<Api>, thinkingLevel?: ThinkingLevel): string {\n\tconst base = `${model.provider}/${model.id}`;\n\treturn thinkingLevel ? `${base}:${thinkingLevel}` : base;\n}\n\nexport function baseSelector(selector: Pick<FallbackSelector, \"provider\" | \"id\">): string {\n\treturn `${selector.provider}/${selector.id}`;\n}\n\n/**\n * Converts validated configuration to canonical selector strings for runtime lookup.\n *\n * A bare key (no provider prefix) is a model-family policy: it expands to one\n * canonical key per provider serving that family, so a chain shipped for\n * `claude-fable-5` applies no matter which provider the user attached it through.\n * Provider-qualified keys and entries keep exact semantics, and an explicit key\n * always overrides the expansion it collides with.\n */\n/**\n * Chain key matched when no exact or base key resolves for the current model.\n * Exists so a model without its own configured chain still has an escape lane:\n * without it, a hard-failing upstream wedges the session terminal even though\n * healthy fallback targets exist (desktop thread 487d7c29, 2026-08-28: nine\n * consecutive upstream 500s, zero fallback attempts, terminal error).\n * Users disable it with the `\"*\": []` tombstone.\n */\nexport const WILDCARD_CHAIN_KEY = \"*\";\n\n/**\n * Whether the user explicitly switched fallback OFF for this model with the\n * documented `[]` tombstone (exact key, base key, or a bare family key that\n * covers it). The shipped wildcard lane must never resurrect fallback against\n * that instruction, so the wildcard gate consults this first.\n *\n * Bare-family matching is deliberately conservative: an ambiguous match\n * suppresses the wildcard rather than overriding an explicit opt-out, because a\n * surprising extra fallback hop is worse than a missing convenience lane.\n */\nexport function hasExplicitFallbackOptOut(\n\tchains: FallbackChains,\n\tcurrentModel: Model<Api>,\n\tcurrentThinking: ThinkingLevel | undefined,\n): boolean {\n\tconst base = formatSelector(currentModel).toLowerCase();\n\tconst exact = currentThinking ? `${base}:${currentThinking}`.toLowerCase() : base;\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tif (!Array.isArray(entries) || !isChainTombstone(entries)) continue;\n\t\tif (key === WILDCARD_CHAIN_KEY) continue;\n\t\tconst normalizedKey = key.trim().toLowerCase();\n\t\tconst keyBase = normalizedBase(normalizedKey);\n\t\tif (keyBase === base || normalizedKey === exact) return true;\n\t\t// Bare family key (no provider prefix): treat an id match as covering.\n\t\tif (!normalizedKey.includes(\"/\") && matchesFamily(currentModel, keyBase)) return true;\n\t}\n\treturn false;\n}\n\nexport function canonicalizeFallbackChains(chains: FallbackChains, lookup: FallbackModelLookup): FallbackChains {\n\tconst models = availableModels(lookup);\n\tconst tiers = authTiers(lookup);\n\tconst canonical: Record<string, readonly string[]> = {};\n\tconst explicitKeys = new Set<string>();\n\tconst tombstones = new Set<string>();\n\n\tconst expandEntries = (entries: readonly string[], keySelector: string): string[] =>\n\t\tentries.flatMap((entry) => {\n\t\t\tconst bare = parseBareSelector(entry);\n\t\t\tif (bare) {\n\t\t\t\treturn rankFamilyModels(models, bare.family, tiers, { limit: MAX_PROVIDERS_PER_FAMILY })\n\t\t\t\t\t.map((model) =>\n\t\t\t\t\t\tbare.thinkingLevel ? `${formatSelector(model)}:${bare.thinkingLevel}` : formatSelector(model),\n\t\t\t\t\t)\n\t\t\t\t\t.filter((selector) => normalizedBase(selector) !== normalizedBase(keySelector));\n\t\t\t}\n\t\t\tconst parsedEntry = parseFallbackSelector(entry, models);\n\t\t\treturn parsedEntry ? [formatParsedSelector(parsedEntry)] : [];\n\t\t});\n\n\t// Bare keys expand first so a same-named explicit key can overwrite them.\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tconst bareKey = parseBareSelector(key);\n\t\tif (!bareKey || !Array.isArray(entries)) continue;\n\t\tif (isChainTombstone(entries)) {\n\t\t\tfor (const model of rankFamilyModels(models, bareKey.family, tiers)) {\n\t\t\t\ttombstones.add(formatSelector(model).toLowerCase());\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const model of rankFamilyModels(models, bareKey.family, tiers)) {\n\t\t\tconst keySelector = bareKey.thinkingLevel\n\t\t\t\t? `${formatSelector(model)}:${bareKey.thinkingLevel}`\n\t\t\t\t: formatSelector(model);\n\t\t\tconst canonicalEntries = expandEntries(entries, keySelector);\n\t\t\tif (canonicalEntries.length > 0) canonical[keySelector] = canonicalEntries;\n\t\t}\n\t}\n\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tif (parseBareSelector(key)) continue;\n\t\tconst parsedKey = parseFallbackSelector(key, models);\n\t\tif (!parsedKey || !Array.isArray(entries)) continue;\n\t\tconst keySelector = formatParsedSelector(parsedKey);\n\t\tif (isChainTombstone(entries)) {\n\t\t\ttombstones.add(normalizedBase(keySelector));\n\t\t\tcontinue;\n\t\t}\n\t\texplicitKeys.add(keySelector.toLowerCase());\n\t\tconst canonicalEntries = expandEntries(entries, keySelector);\n\t\tif (canonicalEntries.length > 0) canonical[keySelector] = canonicalEntries;\n\t}\n\n\tfor (const key of Object.keys(canonical)) {\n\t\tif (explicitKeys.has(key.toLowerCase())) continue;\n\t\tif (tombstones.has(normalizedBase(key))) delete canonical[key];\n\t}\n\n\t// The wildcard key is not a model selector, so both loops above skip it.\n\t// Its entries expand exactly like any other chain; a `[]` tombstone removes\n\t// the lane entirely. Self-selection of the failing model is prevented at\n\t// candidate time (nextCandidate's \"self\" skip), not here, because the\n\t// wildcard has no key selector to filter against.\n\tconst wildcardEntries = chains[WILDCARD_CHAIN_KEY];\n\tif (Array.isArray(wildcardEntries) && !isChainTombstone(wildcardEntries)) {\n\t\tconst canonicalEntries = expandEntries(wildcardEntries, WILDCARD_CHAIN_KEY);\n\t\tif (canonicalEntries.length > 0) canonical[WILDCARD_CHAIN_KEY] = canonicalEntries;\n\t}\n\n\treturn canonical;\n}\n\nexport function resolveChainKey(\n\tcurrentModel: Model<Api>,\n\tcurrentThinking: ThinkingLevel | undefined,\n\tchains: FallbackChains,\n\toptions?: { allowWildcard?: boolean },\n): string | undefined {\n\tconst base = formatSelector(currentModel);\n\tconst exact = currentThinking ? `${base}:${currentThinking}` : base;\n\tif (Object.hasOwn(chains, exact)) return exact;\n\tif (Object.hasOwn(chains, base)) return base;\n\t// The wildcard is opt-in per call site: it is a last resort for a model with\n\t// no chain of its own, and it must never hijack a session already walking a\n\t// configured chain (the last rung of that chain usually has no key either).\n\t// nextCandidate consults the active episode's chainKey before asking for it.\n\tif (!options?.allowWildcard) return undefined;\n\treturn Object.hasOwn(chains, WILDCARD_CHAIN_KEY) ? WILDCARD_CHAIN_KEY : undefined;\n}\n\nfunction formatParsedSelector(selector: FallbackSelector): string {\n\tconst base = baseSelector(selector);\n\treturn selector.thinkingLevel ? `${base}:${selector.thinkingLevel}` : base;\n}\n\nfunction normalizedBase(selector: FallbackSelector | string): string {\n\tif (typeof selector !== \"string\") return baseSelector(selector).toLowerCase();\n\n\tconst normalized = selector.trim().toLowerCase();\n\tconst lastColon = normalized.lastIndexOf(\":\");\n\tif (lastColon === -1 || !isValidThinkingLevel(normalized.slice(lastColon + 1))) return normalized;\n\treturn normalized.slice(0, lastColon);\n}\n\nfunction normalizedExact(selector: FallbackSelector | string): string {\n\treturn typeof selector === \"string\" ? selector.trim().toLowerCase() : formatParsedSelector(selector).toLowerCase();\n}\n\n/**\n * Returns entries after the current fallback. A primary or unknown selector starts\n * from the beginning, which also makes re-entry after stale runtime state safe.\n */\nexport function candidatesAfter(\n\tchainEntries: readonly string[],\n\tcurrentSelector: FallbackSelector | string,\n): readonly string[] {\n\tconst exact = normalizedExact(currentSelector);\n\tconst exactIndex = chainEntries.findIndex((entry) => entry.toLowerCase() === exact);\n\tif (exactIndex !== -1) return chainEntries.slice(exactIndex + 1);\n\n\tconst base = normalizedBase(currentSelector);\n\tconst baseIndex = chainEntries.findIndex((entry) => normalizedBase(entry) === base);\n\treturn baseIndex === -1 ? chainEntries : chainEntries.slice(baseIndex + 1);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,KAAK,GAAG,EAAsB,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clampThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
-
import { baseSelector, candidatesAfter, canonicalizeFallbackChains, formatSelector, parseFallbackSelector, resolveChainKey, } from "./chains.js";
|
|
2
|
+
import { baseSelector, candidatesAfter, canonicalizeFallbackChains, formatSelector, hasExplicitFallbackOptOut, parseFallbackSelector, resolveChainKey, } from "./chains.js";
|
|
3
3
|
export class RetryFallbackController {
|
|
4
4
|
constructor(deps) {
|
|
5
5
|
this.triedSelectors = new Set();
|
|
@@ -124,7 +124,14 @@ export class RetryFallbackController {
|
|
|
124
124
|
if (!settings.modelFallback || !current)
|
|
125
125
|
return undefined;
|
|
126
126
|
const chains = this.canonicalChains();
|
|
127
|
-
|
|
127
|
+
// Order matters: a model's own chain wins, then the active episode keeps
|
|
128
|
+
// owning its walk (its last rung usually has no key of its own), and only
|
|
129
|
+
// a session with neither falls back to the wildcard lane.
|
|
130
|
+
const chainKey = resolveChainKey(current.model, current.thinkingLevel, chains) ??
|
|
131
|
+
this.state?.chainKey ??
|
|
132
|
+
(hasExplicitFallbackOptOut(settings.chains, current.model, current.thinkingLevel)
|
|
133
|
+
? undefined
|
|
134
|
+
: resolveChainKey(current.model, current.thinkingLevel, chains, { allowWildcard: true }));
|
|
128
135
|
const entries = chainKey ? chains[chainKey] : undefined;
|
|
129
136
|
if (!chainKey || !entries) {
|
|
130
137
|
if (reserve)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, clampThinkingLevel, type Model } from \"@earendil-works/pi-ai\";\nimport {\n\tbaseSelector,\n\tcandidatesAfter,\n\tcanonicalizeFallbackChains,\n\ttype FallbackChains,\n\ttype FallbackSelector,\n\tformatSelector,\n\tparseFallbackSelector,\n\tresolveChainKey,\n} from \"./chains.ts\";\nimport type { SelectorCooldowns } from \"./cooldown.ts\";\nimport type { FallbackLogger } from \"./log.ts\";\n\nexport interface ActiveFallbackState {\n\tchainKey: string;\n\toriginalSelector: string;\n\toriginalThinkingLevel?: ThinkingLevel;\n\tlastAppliedThinkingLevel?: ThinkingLevel;\n\tpinned: boolean;\n}\n\ntype FallbackReason = \"transient\" | \"refusal\" | \"hard-error\" | \"billing\";\n\ninterface FallbackSettings {\n\tmodelFallback: boolean;\n\tchains: Readonly<Record<string, readonly string[]>>;\n}\n\nexport interface RetryFallbackControllerDeps {\n\tgetSettings(): FallbackSettings;\n\tregistry: {\n\t\tfind(provider: string, id: string): Model<Api> | undefined;\n\t\tgetAll(): Model<Api>[];\n\t\t/** Ranks bare-selector expansion: OAuth-credential providers come first. */\n\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\t/** Filters bare-selector expansion: a definitive `false` keeps a lane that can never serve out of the chain. */\n\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t};\n\tcooldowns: SelectorCooldowns;\n\tlogger: FallbackLogger;\n\tswitchModel(model: Model<Api>, thinking: ThinkingLevel, reason: \"fallback\" | \"fallback-revert\"): Promise<void>;\n\temit(\n\t\tevent:\n\t\t\t| {\n\t\t\t\t\ttype: \"retry_fallback_applied\";\n\t\t\t\t\tfrom: string;\n\t\t\t\t\tto: string;\n\t\t\t\t\tchainKey: string;\n\t\t\t\t\treason: FallbackReason;\n\t\t\t }\n\t\t\t| { type: \"retry_fallback_reverted\"; from: string; to: string },\n\t): void;\n\tgetCurrentSelector(): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined;\n\tisAuthAvailable(provider: string): boolean;\n}\n\nexport class RetryFallbackController {\n\tprivate readonly deps: RetryFallbackControllerDeps;\n\tprivate readonly triedSelectors = new Set<string>();\n\tprivate state: ActiveFallbackState | undefined;\n\tprivate lastExhaustedChainKey: string | undefined;\n\t// Content-keyed memo of canonicalizeFallbackChains. Provider-error handling calls\n\t// canTryFallback/nextCandidate several times per error; without this each call\n\t// re-expands bare selectors and re-probes registry eligibility over the full\n\t// model set. Keying on the serialized chains content means an unchanged config\n\t// reuses the canonical result, while a chains edit invalidates immediately.\n\t// Registry mutations without a chains change are not tracked here; they are rare\n\t// and in practice coincide with a settings reload that replaces the chains object.\n\tprivate canonicalCache: { key: string; chains: FallbackChains } | undefined;\n\n\tconstructor(deps: RetryFallbackControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tget activeState(): Readonly<ActiveFallbackState> | undefined {\n\t\treturn this.state;\n\t}\n\n\tget exhaustedChainKey(): string | undefined {\n\t\treturn this.lastExhaustedChainKey;\n\t}\n\n\tresetTurn(): void {\n\t\tthis.triedSelectors.clear();\n\t\tthis.lastExhaustedChainKey = undefined;\n\t}\n\n\tclear(): void {\n\t\tthis.state = undefined;\n\t\tthis.canonicalCache = undefined;\n\t\tthis.resetTurn();\n\t}\n\n\tprivate canonicalChains(): FallbackChains {\n\t\tconst chains = this.deps.getSettings().chains;\n\t\tconst key = JSON.stringify(chains);\n\t\tif (this.canonicalCache?.key === key) return this.canonicalCache.chains;\n\t\tconst canonical = canonicalizeFallbackChains(chains, this.deps.registry);\n\t\tthis.canonicalCache = { key, chains: canonical };\n\t\treturn canonical;\n\t}\n\n\tcanTryFallback(): boolean {\n\t\treturn this.nextCandidate(false) !== undefined;\n\t}\n\n\t/**\n\t * Whether a chain exists for the current model at all, regardless of whether\n\t * a candidate is usable right now. `canTryFallback()` answers the latter and\n\t * goes false on cooldown or exhaustion, so it cannot tell a UI \"you never\n\t * configured a chain\" apart from \"the chain is spent\".\n\t */\n\thasConfiguredChain(): boolean {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!current) return false;\n\t\tconst chains = this.canonicalChains();\n\t\treturn resolveChainKey(current.model, current.thinkingLevel, chains) !== undefined;\n\t}\n\n\t/**\n\t * Revert to the chain's original model at a turn boundary. Only fires for\n\t * unpinned state under the cooldown-expiry policy once the original selector\n\t * is no longer suppressed and is still usable; pinned (refusal) state and the\n\t * \"never\" policy always hold the fallback model.\n\t */\n\tasync maybeRestorePrimary(revertPolicy: \"cooldown-expiry\" | \"never\"): Promise<boolean> {\n\t\tconst state = this.state;\n\t\tif (!state || state.pinned || revertPolicy !== \"cooldown-expiry\") return false;\n\t\tif (this.deps.cooldowns.isSuppressed(state.originalSelector)) return false;\n\t\tconst selector = parseFallbackSelector(state.originalSelector, this.deps.registry);\n\t\tif (!selector || !this.deps.isAuthAvailable(selector.provider)) return false;\n\t\tconst model = this.deps.registry.find(selector.provider, selector.id);\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!model || !current) return false;\n\t\t// User override wins: only restore the original thinking level when the\n\t\t// current level still equals the level the fallback switch applied. A manual\n\t\t// setThinkingLevel clears lastAppliedThinkingLevel (see noteManualThinkingLevel).\n\t\tconst thinking =\n\t\t\tcurrent.thinkingLevel === state.lastAppliedThinkingLevel\n\t\t\t\t? (state.originalThinkingLevel ?? current.thinkingLevel ?? \"off\")\n\t\t\t\t: (current.thinkingLevel ?? \"off\");\n\t\tawait this.deps.switchModel(model, thinking, \"fallback-revert\");\n\t\tconst from = formatSelector(current.model);\n\t\tthis.state = undefined;\n\t\tthis.deps.logger.info(\"fallback_reverted\", { from, to: state.originalSelector });\n\t\tthis.deps.emit({ type: \"retry_fallback_reverted\", from, to: state.originalSelector });\n\t\treturn true;\n\t}\n\n\t/**\n\t * A user-driven setThinkingLevel makes the current level a deliberate choice,\n\t * so the revert restore-rule must no longer treat it as fallback-applied.\n\t */\n\tnoteManualThinkingLevel(): void {\n\t\tif (this.state) this.state.lastAppliedThinkingLevel = undefined;\n\t}\n\n\t/** A user-driven model change abandons the fallback window entirely. */\n\tclearForManualModelChange(model: Model<Api>): void {\n\t\tif (this.state) {\n\t\t\tthis.deps.logger.info(\"fallback_cleared_manual\", { selector: formatSelector(model) });\n\t\t}\n\t\tthis.state = undefined;\n\t\tthis.deps.cooldowns.clear(formatSelector(model));\n\t}\n\n\tasync tryFallback(\n\t\treason: FallbackReason,\n\t\tfailure: { errorMessage?: string; retryAfterMs?: number },\n\t): Promise<boolean> {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tconst candidate = this.nextCandidate();\n\t\tif (!current || !candidate) return false;\n\t\tconst currentBase = formatSelector(current.model);\n\t\tif (reason === \"transient\" || reason === \"hard-error\" || reason === \"billing\") {\n\t\t\tthis.deps.cooldowns.note(currentBase, failure);\n\t\t\tthis.deps.logger.info(\"cooldown_noted\", { selector: currentBase, errorMessage: failure.errorMessage });\n\t\t}\n\n\t\tconst thinking = this.selectThinking(candidate.selector, candidate.model, current.thinkingLevel);\n\t\tawait this.deps.switchModel(candidate.model, thinking, \"fallback\");\n\t\tconst from = formatSelector(current.model);\n\t\tconst to = formatSelector(candidate.model);\n\t\tthis.state = {\n\t\t\tchainKey: candidate.chainKey,\n\t\t\toriginalSelector: this.state?.originalSelector ?? from,\n\t\t\toriginalThinkingLevel: this.state?.originalThinkingLevel ?? current.thinkingLevel,\n\t\t\tlastAppliedThinkingLevel: thinking,\n\t\t\tpinned: this.state?.pinned === true || reason === \"refusal\" || reason === \"billing\",\n\t\t};\n\t\tthis.deps.logger.info(\"fallback_applied\", { from, to, chainKey: candidate.chainKey, reason });\n\t\tthis.deps.emit({ type: \"retry_fallback_applied\", from, to, chainKey: candidate.chainKey, reason });\n\t\treturn true;\n\t}\n\n\tprivate nextCandidate(\n\t\treserve = true,\n\t): { chainKey: string; selector: FallbackSelector; model: Model<Api> } | undefined {\n\t\tconst settings = this.deps.getSettings();\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!settings.modelFallback || !current) return undefined;\n\t\tconst chains = this.canonicalChains();\n\t\tconst chainKey = resolveChainKey(current.model, current.thinkingLevel, chains) ?? this.state?.chainKey;\n\t\tconst entries = chainKey ? chains[chainKey] : undefined;\n\t\tif (!chainKey || !entries) {\n\t\t\tif (reserve) this.deps.logger.debug(\"no_chain\", { selector: formatSelector(current.model) });\n\t\t\treturn undefined;\n\t\t}\n\t\tfor (const raw of candidatesAfter(entries, formatSelector(current.model, current.thinkingLevel))) {\n\t\t\tconst selector = parseFallbackSelector(raw, this.deps.registry);\n\t\t\tif (!selector) {\n\t\t\t\tthis.skip(raw, \"unknown\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (selector.provider === current.model.provider && selector.id === current.model.id) {\n\t\t\t\tthis.skip(raw, \"self\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst base = baseSelector(selector);\n\t\t\tif (this.triedSelectors.has(base)) {\n\t\t\t\tthis.skip(raw, \"tried\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.deps.cooldowns.isSuppressed(base)) {\n\t\t\t\tthis.skip(raw, \"suppressed\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!this.deps.isAuthAvailable(selector.provider)) {\n\t\t\t\tthis.skip(raw, \"unauthenticated\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst model = this.deps.registry.find(selector.provider, selector.id);\n\t\t\tif (!model) {\n\t\t\t\tthis.skip(raw, \"unknown\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (reserve) this.triedSelectors.add(base);\n\t\t\treturn { chainKey, selector, model };\n\t\t}\n\t\tthis.lastExhaustedChainKey = chainKey;\n\t\tif (reserve) this.deps.logger.info(\"candidates_exhausted\", { chainKey });\n\t\treturn undefined;\n\t}\n\n\tprivate selectThinking(\n\t\tselector: FallbackSelector,\n\t\tmodel: Model<Api>,\n\t\tinherited: ThinkingLevel | undefined,\n\t): ThinkingLevel {\n\t\tconst requested = selector.thinkingLevel ?? inherited ?? \"off\";\n\t\t// Canonical clamp walks to the NEAREST supported level. Picking the highest\n\t\t// supported level instead would escalate an \"off\" request to max reasoning on\n\t\t// always-on fallback models.\n\t\treturn clampThinkingLevel(model, requested);\n\t}\n\n\tprivate skip(candidate: string, skipReason: string): void {\n\t\tthis.deps.logger.debug(\"candidate_skipped\", { candidate, skipReason });\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,kBAAkB,EAAc,MAAM,uBAAuB,CAAC;AACjF,OAAO,EACN,YAAY,EACZ,eAAe,EACf,0BAA0B,EAG1B,cAAc,EACd,yBAAyB,EACzB,qBAAqB,EACrB,eAAe,GACf,MAAM,aAAa,CAAC;AA+CrB,MAAM,OAAO,uBAAuB;IAcnC,YAAY,IAAiC;QAZ5B,mBAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QAanD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAED,IAAI,iBAAiB;QACpB,OAAO,IAAI,CAAC,qBAAqB,CAAC;IACnC,CAAC;IAED,SAAS;QACR,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;IACxC,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAChC,IAAI,CAAC,SAAS,EAAE,CAAC;IAClB,CAAC;IAEO,eAAe;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,cAAc,EAAE,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QACxE,MAAM,SAAS,GAAG,0BAA0B,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzE,IAAI,CAAC,cAAc,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,cAAc;QACb,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,kBAAkB;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,OAAO,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,KAAK,SAAS,CAAC;IACpF,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,mBAAmB,CAAC,YAAyC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,YAAY,KAAK,iBAAiB;YAAE,OAAO,KAAK,CAAC;QAC/E,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,gBAAgB,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3E,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnF,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QACrC,wEAAwE;QACxE,6EAA6E;QAC7E,kFAAkF;QAClF,MAAM,QAAQ,GACb,OAAO,CAAC,aAAa,KAAK,KAAK,CAAC,wBAAwB;YACvD,CAAC,CAAC,CAAC,KAAK,CAAC,qBAAqB,IAAI,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC;YACjE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAChE,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,uBAAuB;QACtB,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,CAAC,wBAAwB,GAAG,SAAS,CAAC;IACjE,CAAC;IAED,wEAAwE;IACxE,yBAAyB,CAAC,KAAiB;QAC1C,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,WAAW,CAChB,MAAsB,EACtB,OAAyD;QAEzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/E,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QACjG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG;YACZ,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,gBAAgB,EAAE,IAAI,CAAC,KAAK,EAAE,gBAAgB,IAAI,IAAI;YACtD,qBAAqB,EAAE,IAAI,CAAC,KAAK,EAAE,qBAAqB,IAAI,OAAO,CAAC,aAAa;YACjF,wBAAwB,EAAE,QAAQ;YAClC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;SACnF,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACnG,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,aAAa,CACpB,OAAO,GAAG,IAAI;QAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,aAAa,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,yEAAyE;QACzE,0EAA0E;QAC1E,0DAA0D;QAC1D,MAAM,QAAQ,GACb,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC;YAC7D,IAAI,CAAC,KAAK,EAAE,QAAQ;YACpB,CAAC,yBAAyB,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC;gBAChF,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC5F,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxD,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3B,IAAI,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC7F,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAClG,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC1B,SAAS;YACV,CAAC;YACD,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBACtF,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACvB,SAAS;YACV,CAAC;YACD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACxB,SAAS;YACV,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBAC7B,SAAS;YACV,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;gBAClC,SAAS;YACV,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACZ,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC1B,SAAS;YACV,CAAC;YACD,IAAI,OAAO;gBAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,qBAAqB,GAAG,QAAQ,CAAC;QACtC,IAAI,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzE,OAAO,SAAS,CAAC;IAClB,CAAC;IAEO,cAAc,CACrB,QAA0B,EAC1B,KAAiB,EACjB,SAAoC;QAEpC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,IAAI,SAAS,IAAI,KAAK,CAAC;QAC/D,4EAA4E;QAC5E,8EAA8E;QAC9E,6BAA6B;QAC7B,OAAO,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC7C,CAAC;IAEO,IAAI,CAAC,SAAiB,EAAE,UAAkB;QACjD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IACxE,CAAC;CACD","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, clampThinkingLevel, type Model } from \"@earendil-works/pi-ai\";\nimport {\n\tbaseSelector,\n\tcandidatesAfter,\n\tcanonicalizeFallbackChains,\n\ttype FallbackChains,\n\ttype FallbackSelector,\n\tformatSelector,\n\thasExplicitFallbackOptOut,\n\tparseFallbackSelector,\n\tresolveChainKey,\n} from \"./chains.ts\";\nimport type { SelectorCooldowns } from \"./cooldown.ts\";\nimport type { FallbackLogger } from \"./log.ts\";\n\nexport interface ActiveFallbackState {\n\tchainKey: string;\n\toriginalSelector: string;\n\toriginalThinkingLevel?: ThinkingLevel;\n\tlastAppliedThinkingLevel?: ThinkingLevel;\n\tpinned: boolean;\n}\n\ntype FallbackReason = \"transient\" | \"refusal\" | \"hard-error\" | \"billing\";\n\ninterface FallbackSettings {\n\tmodelFallback: boolean;\n\tchains: Readonly<Record<string, readonly string[]>>;\n}\n\nexport interface RetryFallbackControllerDeps {\n\tgetSettings(): FallbackSettings;\n\tregistry: {\n\t\tfind(provider: string, id: string): Model<Api> | undefined;\n\t\tgetAll(): Model<Api>[];\n\t\t/** Ranks bare-selector expansion: OAuth-credential providers come first. */\n\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\t/** Filters bare-selector expansion: a definitive `false` keeps a lane that can never serve out of the chain. */\n\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t};\n\tcooldowns: SelectorCooldowns;\n\tlogger: FallbackLogger;\n\tswitchModel(model: Model<Api>, thinking: ThinkingLevel, reason: \"fallback\" | \"fallback-revert\"): Promise<void>;\n\temit(\n\t\tevent:\n\t\t\t| {\n\t\t\t\t\ttype: \"retry_fallback_applied\";\n\t\t\t\t\tfrom: string;\n\t\t\t\t\tto: string;\n\t\t\t\t\tchainKey: string;\n\t\t\t\t\treason: FallbackReason;\n\t\t\t }\n\t\t\t| { type: \"retry_fallback_reverted\"; from: string; to: string },\n\t): void;\n\tgetCurrentSelector(): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined;\n\tisAuthAvailable(provider: string): boolean;\n}\n\nexport class RetryFallbackController {\n\tprivate readonly deps: RetryFallbackControllerDeps;\n\tprivate readonly triedSelectors = new Set<string>();\n\tprivate state: ActiveFallbackState | undefined;\n\tprivate lastExhaustedChainKey: string | undefined;\n\t// Content-keyed memo of canonicalizeFallbackChains. Provider-error handling calls\n\t// canTryFallback/nextCandidate several times per error; without this each call\n\t// re-expands bare selectors and re-probes registry eligibility over the full\n\t// model set. Keying on the serialized chains content means an unchanged config\n\t// reuses the canonical result, while a chains edit invalidates immediately.\n\t// Registry mutations without a chains change are not tracked here; they are rare\n\t// and in practice coincide with a settings reload that replaces the chains object.\n\tprivate canonicalCache: { key: string; chains: FallbackChains } | undefined;\n\n\tconstructor(deps: RetryFallbackControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tget activeState(): Readonly<ActiveFallbackState> | undefined {\n\t\treturn this.state;\n\t}\n\n\tget exhaustedChainKey(): string | undefined {\n\t\treturn this.lastExhaustedChainKey;\n\t}\n\n\tresetTurn(): void {\n\t\tthis.triedSelectors.clear();\n\t\tthis.lastExhaustedChainKey = undefined;\n\t}\n\n\tclear(): void {\n\t\tthis.state = undefined;\n\t\tthis.canonicalCache = undefined;\n\t\tthis.resetTurn();\n\t}\n\n\tprivate canonicalChains(): FallbackChains {\n\t\tconst chains = this.deps.getSettings().chains;\n\t\tconst key = JSON.stringify(chains);\n\t\tif (this.canonicalCache?.key === key) return this.canonicalCache.chains;\n\t\tconst canonical = canonicalizeFallbackChains(chains, this.deps.registry);\n\t\tthis.canonicalCache = { key, chains: canonical };\n\t\treturn canonical;\n\t}\n\n\tcanTryFallback(): boolean {\n\t\treturn this.nextCandidate(false) !== undefined;\n\t}\n\n\t/**\n\t * Whether a chain exists for the current model at all, regardless of whether\n\t * a candidate is usable right now. `canTryFallback()` answers the latter and\n\t * goes false on cooldown or exhaustion, so it cannot tell a UI \"you never\n\t * configured a chain\" apart from \"the chain is spent\".\n\t */\n\thasConfiguredChain(): boolean {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!current) return false;\n\t\tconst chains = this.canonicalChains();\n\t\treturn resolveChainKey(current.model, current.thinkingLevel, chains) !== undefined;\n\t}\n\n\t/**\n\t * Revert to the chain's original model at a turn boundary. Only fires for\n\t * unpinned state under the cooldown-expiry policy once the original selector\n\t * is no longer suppressed and is still usable; pinned (refusal) state and the\n\t * \"never\" policy always hold the fallback model.\n\t */\n\tasync maybeRestorePrimary(revertPolicy: \"cooldown-expiry\" | \"never\"): Promise<boolean> {\n\t\tconst state = this.state;\n\t\tif (!state || state.pinned || revertPolicy !== \"cooldown-expiry\") return false;\n\t\tif (this.deps.cooldowns.isSuppressed(state.originalSelector)) return false;\n\t\tconst selector = parseFallbackSelector(state.originalSelector, this.deps.registry);\n\t\tif (!selector || !this.deps.isAuthAvailable(selector.provider)) return false;\n\t\tconst model = this.deps.registry.find(selector.provider, selector.id);\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!model || !current) return false;\n\t\t// User override wins: only restore the original thinking level when the\n\t\t// current level still equals the level the fallback switch applied. A manual\n\t\t// setThinkingLevel clears lastAppliedThinkingLevel (see noteManualThinkingLevel).\n\t\tconst thinking =\n\t\t\tcurrent.thinkingLevel === state.lastAppliedThinkingLevel\n\t\t\t\t? (state.originalThinkingLevel ?? current.thinkingLevel ?? \"off\")\n\t\t\t\t: (current.thinkingLevel ?? \"off\");\n\t\tawait this.deps.switchModel(model, thinking, \"fallback-revert\");\n\t\tconst from = formatSelector(current.model);\n\t\tthis.state = undefined;\n\t\tthis.deps.logger.info(\"fallback_reverted\", { from, to: state.originalSelector });\n\t\tthis.deps.emit({ type: \"retry_fallback_reverted\", from, to: state.originalSelector });\n\t\treturn true;\n\t}\n\n\t/**\n\t * A user-driven setThinkingLevel makes the current level a deliberate choice,\n\t * so the revert restore-rule must no longer treat it as fallback-applied.\n\t */\n\tnoteManualThinkingLevel(): void {\n\t\tif (this.state) this.state.lastAppliedThinkingLevel = undefined;\n\t}\n\n\t/** A user-driven model change abandons the fallback window entirely. */\n\tclearForManualModelChange(model: Model<Api>): void {\n\t\tif (this.state) {\n\t\t\tthis.deps.logger.info(\"fallback_cleared_manual\", { selector: formatSelector(model) });\n\t\t}\n\t\tthis.state = undefined;\n\t\tthis.deps.cooldowns.clear(formatSelector(model));\n\t}\n\n\tasync tryFallback(\n\t\treason: FallbackReason,\n\t\tfailure: { errorMessage?: string; retryAfterMs?: number },\n\t): Promise<boolean> {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tconst candidate = this.nextCandidate();\n\t\tif (!current || !candidate) return false;\n\t\tconst currentBase = formatSelector(current.model);\n\t\tif (reason === \"transient\" || reason === \"hard-error\" || reason === \"billing\") {\n\t\t\tthis.deps.cooldowns.note(currentBase, failure);\n\t\t\tthis.deps.logger.info(\"cooldown_noted\", { selector: currentBase, errorMessage: failure.errorMessage });\n\t\t}\n\n\t\tconst thinking = this.selectThinking(candidate.selector, candidate.model, current.thinkingLevel);\n\t\tawait this.deps.switchModel(candidate.model, thinking, \"fallback\");\n\t\tconst from = formatSelector(current.model);\n\t\tconst to = formatSelector(candidate.model);\n\t\tthis.state = {\n\t\t\tchainKey: candidate.chainKey,\n\t\t\toriginalSelector: this.state?.originalSelector ?? from,\n\t\t\toriginalThinkingLevel: this.state?.originalThinkingLevel ?? current.thinkingLevel,\n\t\t\tlastAppliedThinkingLevel: thinking,\n\t\t\tpinned: this.state?.pinned === true || reason === \"refusal\" || reason === \"billing\",\n\t\t};\n\t\tthis.deps.logger.info(\"fallback_applied\", { from, to, chainKey: candidate.chainKey, reason });\n\t\tthis.deps.emit({ type: \"retry_fallback_applied\", from, to, chainKey: candidate.chainKey, reason });\n\t\treturn true;\n\t}\n\n\tprivate nextCandidate(\n\t\treserve = true,\n\t): { chainKey: string; selector: FallbackSelector; model: Model<Api> } | undefined {\n\t\tconst settings = this.deps.getSettings();\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!settings.modelFallback || !current) return undefined;\n\t\tconst chains = this.canonicalChains();\n\t\t// Order matters: a model's own chain wins, then the active episode keeps\n\t\t// owning its walk (its last rung usually has no key of its own), and only\n\t\t// a session with neither falls back to the wildcard lane.\n\t\tconst chainKey =\n\t\t\tresolveChainKey(current.model, current.thinkingLevel, chains) ??\n\t\t\tthis.state?.chainKey ??\n\t\t\t(hasExplicitFallbackOptOut(settings.chains, current.model, current.thinkingLevel)\n\t\t\t\t? undefined\n\t\t\t\t: resolveChainKey(current.model, current.thinkingLevel, chains, { allowWildcard: true }));\n\t\tconst entries = chainKey ? chains[chainKey] : undefined;\n\t\tif (!chainKey || !entries) {\n\t\t\tif (reserve) this.deps.logger.debug(\"no_chain\", { selector: formatSelector(current.model) });\n\t\t\treturn undefined;\n\t\t}\n\t\tfor (const raw of candidatesAfter(entries, formatSelector(current.model, current.thinkingLevel))) {\n\t\t\tconst selector = parseFallbackSelector(raw, this.deps.registry);\n\t\t\tif (!selector) {\n\t\t\t\tthis.skip(raw, \"unknown\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (selector.provider === current.model.provider && selector.id === current.model.id) {\n\t\t\t\tthis.skip(raw, \"self\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst base = baseSelector(selector);\n\t\t\tif (this.triedSelectors.has(base)) {\n\t\t\t\tthis.skip(raw, \"tried\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.deps.cooldowns.isSuppressed(base)) {\n\t\t\t\tthis.skip(raw, \"suppressed\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!this.deps.isAuthAvailable(selector.provider)) {\n\t\t\t\tthis.skip(raw, \"unauthenticated\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst model = this.deps.registry.find(selector.provider, selector.id);\n\t\t\tif (!model) {\n\t\t\t\tthis.skip(raw, \"unknown\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (reserve) this.triedSelectors.add(base);\n\t\t\treturn { chainKey, selector, model };\n\t\t}\n\t\tthis.lastExhaustedChainKey = chainKey;\n\t\tif (reserve) this.deps.logger.info(\"candidates_exhausted\", { chainKey });\n\t\treturn undefined;\n\t}\n\n\tprivate selectThinking(\n\t\tselector: FallbackSelector,\n\t\tmodel: Model<Api>,\n\t\tinherited: ThinkingLevel | undefined,\n\t): ThinkingLevel {\n\t\tconst requested = selector.thinkingLevel ?? inherited ?? \"off\";\n\t\t// Canonical clamp walks to the NEAREST supported level. Picking the highest\n\t\t// supported level instead would escalate an \"off\" request to max reasoning on\n\t\t// always-on fallback models.\n\t\treturn clampThinkingLevel(model, requested);\n\t}\n\n\tprivate skip(candidate: string, skipReason: string): void {\n\t\tthis.deps.logger.debug(\"candidate_skipped\", { candidate, skipReason });\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,uBAAuB,EAAE,mBAAmB,CAAC,CAAC;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC;IACnD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC7C,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,EAAE,iBAAiB,GAAG,OAAO,CAAC;CAC1C;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,uBAAuB,EAAE,mBAAmB,CAAC,CAAC;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,oBAAoB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC;IACnD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC7C,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,YAAY,EAAE,iBAAiB,GAAG,OAAO,CAAC;CAC1C;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,cAcrC,CAAC;AA6CF,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,GAAG,6BAA6B,CAM/G;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAE3F;AAED,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAClD,eAAO,MAAM,yBAAyB,UAAY,CAAC;AAEnD,MAAM,WAAW,0BAA0B;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,GAAG,SAAS,GAAG,0BAA0B,CAezG"}
|
|
@@ -9,6 +9,15 @@ export const DEFAULT_FALLBACK_CHAINS = {
|
|
|
9
9
|
// vendor-prefixed id `kimi-k3` (e.g. OpenCode Go), which the conservative `k3`
|
|
10
10
|
// family matcher intentionally cannot capture (issue #793).
|
|
11
11
|
"claude-fable-5": ["k3:max", "kimi-k3:max", "claude-opus-5:xhigh", "claude-opus-4-8:xhigh"],
|
|
12
|
+
// Last-resort lane for models with no chain of their own. resolveChainKey
|
|
13
|
+
// falls through exact -> base -> "*", so any current model whose upstream
|
|
14
|
+
// hard-fails can still escape instead of wedging the session terminal
|
|
15
|
+
// (desktop thread 487d7c29, 2026-08-28: nine consecutive upstream 500s on a
|
|
16
|
+
// chainless model, zero fallback attempts). Same lane order as the Fable
|
|
17
|
+
// default: cheap family escape first, then the Opus tiers. The candidate
|
|
18
|
+
// walk skips the failing model itself ("self"), tried selectors, cooldowns,
|
|
19
|
+
// and unauthenticated providers. Disable with the `"*": []` tombstone.
|
|
20
|
+
"*": ["k3:max", "kimi-k3:max", "claude-opus-5:xhigh", "claude-opus-4-8:xhigh"],
|
|
12
21
|
};
|
|
13
22
|
function cloneDefaultFallbackChains() {
|
|
14
23
|
const chains = {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/settings.ts"],"names":[],"mappings":"AA8BA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAmB;IACtD,8EAA8E;IAC9E,+EAA+E;IAC/E,4DAA4D;IAC5D,gBAAgB,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,qBAAqB,EAAE,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/settings.ts"],"names":[],"mappings":"AA8BA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAmB;IACtD,8EAA8E;IAC9E,+EAA+E;IAC/E,4DAA4D;IAC5D,gBAAgB,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,qBAAqB,EAAE,uBAAuB,CAAC;IAC3F,0EAA0E;IAC1E,0EAA0E;IAC1E,sEAAsE;IACtE,4EAA4E;IAC5E,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,GAAG,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,qBAAqB,EAAE,uBAAuB,CAAC;CAC9E,CAAC;AAEF,SAAS,0BAA0B;IAClC,MAAM,MAAM,GAAsC,EAAE,CAAC;IACrD,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,EAAE,CAAC;QACtE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACpC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,qBAAqB,CAAC,KAAc;IAC5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,0BAA0B,EAAE,CAAC;IAC7D,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,0BAA0B,EAAE,CAAC;IAE/D,MAAM,MAAM,GAAsC,0BAA0B,EAAE,CAAC;IAC/E,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,OAAO,0BAA0B,EAAE,CAAC;QACjE,8EAA8E;QAC9E,yEAAyE;QACzE,2EAA2E;QAC3E,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,QAAmC;IAC/E,OAAO;QACN,aAAa,EAAE,OAAO,QAAQ,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI;QAC3F,MAAM,EAAE,qBAAqB,CAAC,QAAQ,EAAE,cAAc,CAAC;QACvD,YAAY,EAAE,QAAQ,EAAE,oBAAoB,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB;KACtF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,QAAmC;IACjF,OAAO,OAAO,QAAQ,EAAE,uBAAuB,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC;AACzG,CAAC;AAED,MAAM,CAAC,MAAM,0BAA0B,GAAG,OAAO,CAAC;AAClD,MAAM,CAAC,MAAM,yBAAyB,GAAG,SAAS,CAAC;AAOnD,MAAM,UAAU,yBAAyB,CAAC,QAAmC;IAC5E,MAAM,eAAe,GACpB,OAAO,QAAQ,EAAE,eAAe,KAAK,QAAQ,IAAI,QAAQ,CAAC,eAAe,IAAI,CAAC;QAC7E,CAAC,CAAC,QAAQ,CAAC,eAAe;QAC1B,CAAC,CAAC,0BAA0B,CAAC;IAC/B,MAAM,cAAc,GACnB,OAAO,QAAQ,EAAE,cAAc,KAAK,QAAQ,IAAI,QAAQ,CAAC,cAAc,IAAI,CAAC;QAC3E,CAAC,CAAC,QAAQ,CAAC,cAAc;QACzB,CAAC,CAAC,yBAAyB,CAAC;IAE9B,IAAI,cAAc,IAAI,eAAe,EAAE,CAAC;QACvC,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,cAAc,EAAE,yBAAyB,EAAE,CAAC;IACnG,CAAC;IAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import type { FallbackChains } from \"./chains.ts\";\n\nexport interface ProviderRetrySettings {\n\ttimeoutMs?: number;\n\tstreamStartTimeoutMs?: number;\n\tstreamRetryTimeoutMs?: number; // retry-continuation watchdog cap after a provider timeout; reconciled to max(cap, streamStartTimeoutMs) so a granted stream-start budget is never cut short; default: 30000, 0 disables\n\tmaxRetries?: number;\n\tmaxRetryDelayMs?: number;\n}\n\nexport interface RetrySettings {\n\tenabled?: boolean;\n\tmaxRetries?: number;\n\tbaseDelayMs?: number;\n\tprovider?: ProviderRetrySettings;\n\tproviders?: Record<string, import(\"./profile-override.ts\").RetryPolicyOverride>;\n\tmodelFallback?: boolean;\n\tfallbackChains?: Record<string, string[]>;\n\tfallbackRevertPolicy?: \"cooldown-expiry\" | \"never\";\n\tabortServerSideFallback?: boolean;\n\thintedWaitCapMs?: number;\n\tprobeBackMaxMs?: number;\n}\n\nexport interface ResolvedRetryFallbackSettings {\n\tmodelFallback: boolean;\n\tchains: FallbackChains;\n\trevertPolicy: \"cooldown-expiry\" | \"never\";\n}\n\n/**\n * Shipped defaults are declared as model families (bare ids, no provider prefix).\n * `canonicalizeFallbackChains` expands them against the live registry, so the\n * chain follows Fable 5 whichever provider serves it - the builtin Anthropic\n * provider, the Claude SDK OAuth extension, a gateway, or Bedrock.\n */\nexport const DEFAULT_FALLBACK_CHAINS: FallbackChains = {\n\t// `kimi-k3:max` is an alias entry for providers that expose Kimi K3 under the\n\t// vendor-prefixed id `kimi-k3` (e.g. OpenCode Go), which the conservative `k3`\n\t// family matcher intentionally cannot capture (issue #793).\n\t\"claude-fable-5\": [\"k3:max\", \"kimi-k3:max\", \"claude-opus-5:xhigh\", \"claude-opus-4-8:xhigh\"],\n\t// Last-resort lane for models with no chain of their own. resolveChainKey\n\t// falls through exact -> base -> \"*\", so any current model whose upstream\n\t// hard-fails can still escape instead of wedging the session terminal\n\t// (desktop thread 487d7c29, 2026-08-28: nine consecutive upstream 500s on a\n\t// chainless model, zero fallback attempts). Same lane order as the Fable\n\t// default: cheap family escape first, then the Opus tiers. The candidate\n\t// walk skips the failing model itself (\"self\"), tried selectors, cooldowns,\n\t// and unauthenticated providers. Disable with the `\"*\": []` tombstone.\n\t\"*\": [\"k3:max\", \"kimi-k3:max\", \"claude-opus-5:xhigh\", \"claude-opus-4-8:xhigh\"],\n};\n\nfunction cloneDefaultFallbackChains(): Record<string, readonly string[]> {\n\tconst chains: Record<string, readonly string[]> = {};\n\tfor (const [key, entries] of Object.entries(DEFAULT_FALLBACK_CHAINS)) {\n\t\tchains[key] = [...entries];\n\t}\n\treturn chains;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n\treturn Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\n/**\n * User chains layer over the shipped defaults per key instead of replacing the\n * whole map. Configuring an unrelated model must not silently delete a default\n * chain: that left the defaulted model with no client chain at all, which in\n * turn disabled the server-fallback abort and handed model choice back to the\n * provider. A same-named key still replaces that default outright (never a\n * union), and an explicit empty array removes a default the user does not want -\n * as a tombstone honored at both bare-family and canonical-provider granularity.\n *\n * Cross-scope replacement (global vs project) stays wholesale and is handled by\n * `deepMergeSettings`; this only resolves defaults against the merged result.\n */\nfunction resolveFallbackChains(value: unknown): FallbackChains {\n\tif (value === undefined) return cloneDefaultFallbackChains();\n\tif (!isPlainObject(value)) return cloneDefaultFallbackChains();\n\n\tconst chains: Record<string, readonly string[]> = cloneDefaultFallbackChains();\n\tfor (const [key, entries] of Object.entries(value)) {\n\t\tif (!isStringArray(entries)) return cloneDefaultFallbackChains();\n\t\t// An empty list stays in the map as a tombstone: canonicalization needs it to\n\t\t// suppress the expanded default for that family or provider variant, and\n\t\t// dropping it here would let the shipped default reappear after expansion.\n\t\tchains[key] = [...entries];\n\t}\n\treturn chains;\n}\n\nexport function resolveRetryFallbackSettings(settings: RetrySettings | undefined): ResolvedRetryFallbackSettings {\n\treturn {\n\t\tmodelFallback: typeof settings?.modelFallback === \"boolean\" ? settings.modelFallback : true,\n\t\tchains: resolveFallbackChains(settings?.fallbackChains),\n\t\trevertPolicy: settings?.fallbackRevertPolicy === \"never\" ? \"never\" : \"cooldown-expiry\",\n\t};\n}\n\nexport function resolveAbortServerSideFallback(settings: RetrySettings | undefined): boolean {\n\treturn typeof settings?.abortServerSideFallback === \"boolean\" ? settings.abortServerSideFallback : true;\n}\n\nexport const DEFAULT_HINTED_WAIT_CAP_MS = 300_000;\nexport const DEFAULT_PROBE_BACK_MAX_MS = 3_600_000;\n\nexport interface ResolvedHintPolicySettings {\n\thintedWaitCapMs: number;\n\tprobeBackMaxMs: number;\n}\n\nexport function resolveHintPolicySettings(settings: RetrySettings | undefined): ResolvedHintPolicySettings {\n\tconst hintedWaitCapMs =\n\t\ttypeof settings?.hintedWaitCapMs === \"number\" && settings.hintedWaitCapMs >= 0\n\t\t\t? settings.hintedWaitCapMs\n\t\t\t: DEFAULT_HINTED_WAIT_CAP_MS;\n\tconst probeBackMaxMs =\n\t\ttypeof settings?.probeBackMaxMs === \"number\" && settings.probeBackMaxMs >= 0\n\t\t\t? settings.probeBackMaxMs\n\t\t\t: DEFAULT_PROBE_BACK_MAX_MS;\n\n\tif (probeBackMaxMs <= hintedWaitCapMs) {\n\t\treturn { hintedWaitCapMs: DEFAULT_HINTED_WAIT_CAP_MS, probeBackMaxMs: DEFAULT_PROBE_BACK_MAX_MS };\n\t}\n\n\treturn { hintedWaitCapMs, probeBackMaxMs };\n}\n"]}
|
|
@@ -47,7 +47,7 @@ export function validateFallbackChains(chains, registry) {
|
|
|
47
47
|
// role syntax, so it keeps the message that tells the user what to write.
|
|
48
48
|
warnings.push(`Fallback chain key "${key}" must use a provider/model selector; roles are unsupported.`);
|
|
49
49
|
}
|
|
50
|
-
if (hasWildcard)
|
|
50
|
+
if (hasWildcard && key !== "*")
|
|
51
51
|
warnings.push(`Fallback chain key "${key}" cannot contain wildcards.`);
|
|
52
52
|
const parsedKey = hasProvider && !hasWildcard ? parseFallbackSelector(key, models) : undefined;
|
|
53
53
|
const keyModel = parsedKey ? registry.find(parsedKey.provider, parsedKey.id) : undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/validate.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/validate.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { getSupportedThinkingLevels } from \"../thinking-levels.ts\";\nimport { baseSelector, parseFallbackSelector } from \"./chains.ts\";\nimport { matchesFamily, parseBareSelector } from \"./expansion.ts\";\n\nexport interface FallbackModelRegistry {\n\tfind(provider: string, id: string): Model<Api> | undefined;\n\tgetAll(): Model<Api>[];\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\nfunction invalidSelectorWarning(kind: \"key\" | \"entry\", selector: string, key?: string): string {\n\tconst subject =\n\t\tkind === \"key\" ? `Fallback chain key \"${selector}\"` : `Fallback chain entry \"${selector}\" for \"${key}\"`;\n\treturn `${subject} is not a valid or known model selector.`;\n}\n\nfunction validateThinkingLevel(\n\tselector: { thinkingLevel?: ThinkingLevel; provider: string; id: string },\n\tmodel: Model<Api>,\n\tsubject: string,\n\twarnings: string[],\n): void {\n\tif (selector.thinkingLevel && !getSupportedThinkingLevels(model).includes(selector.thinkingLevel)) {\n\t\twarnings.push(\n\t\t\t`${subject} uses thinking level \"${selector.thinkingLevel}\", which is unsupported by ${selector.provider}/${selector.id}.`,\n\t\t);\n\t}\n}\n\n/**\n * Names the offending value so a single log line identifies the producer. The\n * bare form of this warning fired repeatedly in the wild with no way to tell\n * which settings value caused it.\n */\nfunction describeValue(value: unknown): string {\n\tif (value === null) return \"null\";\n\tif (Array.isArray(value)) return \"an array\";\n\treturn `a ${typeof value}`;\n}\n\n/** Returns configuration warnings without changing malformed fallback-chain settings. */\nexport function validateFallbackChains(chains: unknown, registry: FallbackModelRegistry): string[] {\n\tif (chains === undefined) return [];\n\tif (!isPlainObject(chains)) return [`Fallback chains must be a plain object, but got ${describeValue(chains)}.`];\n\n\tconst warnings: string[] = [];\n\tconst models = registry.getAll();\n\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tconst hasProvider = key.includes(\"/\");\n\t\tconst hasWildcard = key.includes(\"*\");\n\t\t// A bare key names a model family and applies to every provider serving it.\n\t\tconst bareKey = hasWildcard ? undefined : parseBareSelector(key);\n\t\tconst bareKeyMatches = bareKey ? models.some((model) => matchesFamily(model, bareKey.family)) : false;\n\t\tif (!hasProvider && !bareKeyMatches && !hasWildcard) {\n\t\t\t// A bare key naming no registered model is indistinguishable from the old\n\t\t\t// role syntax, so it keeps the message that tells the user what to write.\n\t\t\twarnings.push(`Fallback chain key \"${key}\" must use a provider/model selector; roles are unsupported.`);\n\t\t}\n\t\tif (hasWildcard && key !== \"*\") warnings.push(`Fallback chain key \"${key}\" cannot contain wildcards.`);\n\n\t\tconst parsedKey = hasProvider && !hasWildcard ? parseFallbackSelector(key, models) : undefined;\n\t\tconst keyModel = parsedKey ? registry.find(parsedKey.provider, parsedKey.id) : undefined;\n\t\tif (hasProvider && !hasWildcard && (!parsedKey || !keyModel)) {\n\t\t\twarnings.push(invalidSelectorWarning(\"key\", key));\n\t\t}\n\t\tif (parsedKey && keyModel) {\n\t\t\tvalidateThinkingLevel(parsedKey, keyModel, `Fallback chain key \"${key}\"`, warnings);\n\t\t}\n\n\t\tif (!Array.isArray(entries) || entries.some((entry) => typeof entry !== \"string\")) {\n\t\t\twarnings.push(`Fallback chain \"${key}\" entries must be an array of strings.`);\n\t\t\tcontinue;\n\t\t}\n\t\t// An empty array is the documented opt-out that removes a shipped default\n\t\t// chain, so it is a valid instruction rather than a malformed entry.\n\t\tif (entries.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const entry of entries) {\n\t\t\tconst bareEntry = parseBareSelector(entry);\n\t\t\tif (bareEntry) {\n\t\t\t\tif (!models.some((model) => matchesFamily(model, bareEntry.family))) {\n\t\t\t\t\twarnings.push(invalidSelectorWarning(\"entry\", entry, key));\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst parsedEntry = parseFallbackSelector(entry, models);\n\t\t\tconst entryModel = parsedEntry ? registry.find(parsedEntry.provider, parsedEntry.id) : undefined;\n\t\t\tif (!parsedEntry || !entryModel) {\n\t\t\t\twarnings.push(invalidSelectorWarning(\"entry\", entry, key));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (parsedKey && baseSelector(parsedEntry) === baseSelector(parsedKey)) {\n\t\t\t\twarnings.push(`Fallback chain entry \"${entry}\" for \"${key}\" cannot reference the same model.`);\n\t\t\t}\n\t\t\tvalidateThinkingLevel(parsedEntry, entryModel, `Fallback chain entry \"${entry}\"`, warnings);\n\t\t}\n\t}\n\n\treturn warnings;\n}\n"]}
|
package/dist/core/sdk.js
CHANGED
|
@@ -97,7 +97,7 @@ export async function createAgentSession(options = {}) {
|
|
|
97
97
|
const authStorage = options.authStorage ?? options.modelRegistry?.authStorage ?? AuthStorage.create(authPath);
|
|
98
98
|
const modelRuntime = options.modelRuntime ??
|
|
99
99
|
options.modelRegistry?.modelRuntime ??
|
|
100
|
-
(await ModelRuntime.create({ credentials: authStorage, modelsPath }));
|
|
100
|
+
(await ModelRuntime.create({ credentials: authStorage, modelsPath, agentDir }));
|
|
101
101
|
const modelRegistry = options.modelRegistry ?? new ModelRegistry(modelRuntime, authStorage);
|
|
102
102
|
const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
|
|
103
103
|
const sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));
|