@code-yeongyu/senpi 2026.8.6 → 2026.8.9
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 +104 -0
- package/dist/core/agent-session.d.ts +10 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +71 -50
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/index.d.ts +2 -2
- package/dist/core/extensions/builtin/bash-timeout/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/index.js +17 -20
- package/dist/core/extensions/builtin/bash-timeout/index.js.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +7 -12
- package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts.map +1 -1
- package/dist/core/extensions/builtin/bash-timeout/timeout.js +11 -19
- package/dist/core/extensions/builtin/bash-timeout/timeout.js.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts +2 -0
- package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation-types.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +76 -35
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/prompt.d.ts +1 -1
- package/dist/core/extensions/builtin/goal/prompt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/prompt.js +21 -7
- package/dist/core/extensions/builtin/goal/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/goal/wait-progress.d.ts +3 -1
- package/dist/core/extensions/builtin/goal/wait-progress.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/wait-progress.js +27 -3
- package/dist/core/extensions/builtin/goal/wait-progress.js.map +1 -1
- package/dist/core/extensions/builtin/goal/wait-ticker.d.ts +4 -3
- package/dist/core/extensions/builtin/goal/wait-ticker.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/wait-ticker.js +6 -6
- package/dist/core/extensions/builtin/goal/wait-ticker.js.map +1 -1
- package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
- package/dist/core/extensions/builtin/import-repro.js +9 -0
- package/dist/core/extensions/builtin/import-repro.js.map +1 -1
- package/dist/core/extensions/builtin/model-fallback/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/model-fallback/settings.js +21 -1
- package/dist/core/extensions/builtin/model-fallback/settings.js.map +1 -1
- package/dist/core/extensions/builtin/resumption-channel-event.d.ts +13 -0
- package/dist/core/extensions/builtin/resumption-channel-event.d.ts.map +1 -0
- package/dist/core/extensions/builtin/resumption-channel-event.js +13 -0
- package/dist/core/extensions/builtin/resumption-channel-event.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/extension.js +20 -0
- package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +5 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/session-bundle.js +19 -2
- package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/bash.js +24 -10
- package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts +2 -0
- package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts +11 -0
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.js +16 -0
- package/dist/core/extensions/builtin/terminal/tools/foreground-window.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts +28 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts.map +1 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js +75 -0
- package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js.map +1 -0
- package/dist/core/extensions/builtin/tps.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tps.js +3 -3
- package/dist/core/extensions/builtin/tps.js.map +1 -1
- package/dist/core/retry-fallback/chains.d.ts +13 -1
- package/dist/core/retry-fallback/chains.d.ts.map +1 -1
- package/dist/core/retry-fallback/chains.js +80 -8
- package/dist/core/retry-fallback/chains.js.map +1 -1
- package/dist/core/retry-fallback/controller.d.ts +2 -0
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/retry-fallback/expansion.d.ts +46 -0
- package/dist/core/retry-fallback/expansion.d.ts.map +1 -0
- package/dist/core/retry-fallback/expansion.js +113 -0
- package/dist/core/retry-fallback/expansion.js.map +1 -0
- package/dist/core/retry-fallback/hint-policy.d.ts +14 -0
- package/dist/core/retry-fallback/hint-policy.d.ts.map +1 -1
- package/dist/core/retry-fallback/hint-policy.js +14 -0
- package/dist/core/retry-fallback/hint-policy.js.map +1 -1
- package/dist/core/retry-fallback/settings.d.ts +6 -0
- package/dist/core/retry-fallback/settings.d.ts.map +1 -1
- package/dist/core/retry-fallback/settings.js +12 -10
- package/dist/core/retry-fallback/settings.js.map +1 -1
- package/dist/core/retry-fallback/validate.d.ts.map +1 -1
- package/dist/core/retry-fallback/validate.js +14 -1
- package/dist/core/retry-fallback/validate.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +18 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/favorite-models-selector.d.ts +4 -0
- package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/favorite-models-selector.js +23 -7
- package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +11 -7
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +25 -6
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-search-rank.d.ts +13 -0
- package/dist/modes/interactive/model-search-rank.d.ts.map +1 -0
- package/dist/modes/interactive/model-search-rank.js +200 -0
- package/dist/modes/interactive/model-search-rank.js.map +1 -0
- package/dist/modes/interactive/model-search.d.ts +0 -5
- package/dist/modes/interactive/model-search.d.ts.map +1 -1
- package/dist/modes/interactive/model-search.js +0 -9
- package/dist/modes/interactive/model-search.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +3 -2
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +1 -1
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js +3 -3
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/docs/settings.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +36 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/resumption-channel.ts +29 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +26 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +2 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +4 -7
- package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- 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/github-copilot.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/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.js +8 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts +12 -0
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js +34 -2
- package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chains.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAavF,SAAS,eAAe,CAAC,MAA2B;IACnD,OAAO,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAW;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAEtE,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,SAAS,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1D,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IACjE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAW,EAAE,MAA2B;IAC7E,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,MAAM,eAAe,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,CAAC,eAAe;QAAE,OAAO,SAAS,CAAC;IAEvC,MAAM,SAAS,GAAG,4BAA4B,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAChE,IAAI,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC;IACzE,CAAC;IAED,MAAM,UAAU,GAAG,eAAe,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IACvE,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5E,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IAEjD,iFAAiF;IACjF,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACzG,MAAM,MAAM,GAAG,iBAAiB,CAC/B,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,YAAY,IAAI,eAAe,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,YAAY,EACjG,cAAc,EACd,EAAE,iCAAiC,EAAE,KAAK,EAAE,CAC5C,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IACtD,IAAI,eAAe,CAAC,aAAa,IAAI,CAAC,MAAM,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IAE7E,OAAO;QACN,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;QAC/B,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE;QACnB,aAAa,EAAE,MAAM,CAAC,aAAa;KACnC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAE,aAA6B;IAC9E,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;IAC7C,OAAO,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAmD;IAC/E,OAAO,GAAG,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;AAC9C,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,0BAA0B,CAAC,MAAsB,EAAE,MAA2B;IAC7F,MAAM,SAAS,GAAsC,EAAE,CAAC;IAExD,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,SAAS;QAEpD,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YAClD,MAAM,WAAW,GAAG,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACzD,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,SAAS,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,GAAG,gBAAgB,CAAC;QAC/D,CAAC;IACF,CAAC;IAED,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,eAAe,CAC9B,YAAwB,EACxB,eAA0C,EAC1C,MAAsB;IAEtB,MAAM,IAAI,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,SAAS,oBAAoB,CAAC,QAA0B;IACvD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,OAAO,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5E,CAAC;AAED,SAAS,cAAc,CAAC,QAAmC;IAC1D,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAE9E,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACjD,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9C,IAAI,SAAS,KAAK,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC;IAClG,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,eAAe,CAAC,QAAmC;IAC3D,OAAO,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;AACpH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC9B,YAA+B,EAC/B,eAA0C;IAE1C,MAAM,KAAK,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC;IACpF,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,YAAY,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IAEjE,MAAM,IAAI,GAAG,cAAc,CAAC,eAAe,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;IACpF,OAAO,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;AAC5E,CAAC","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\";\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 = readonly Model<Api>[] | { getAll(): Model<Api>[] };\n\nfunction availableModels(lookup: FallbackModelLookup): Model<Api>[] {\n\treturn \"getAll\" in lookup ? lookup.getAll() : [...lookup];\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/** Converts validated configuration to canonical selector strings for runtime lookup. */\nexport function canonicalizeFallbackChains(chains: FallbackChains, lookup: FallbackModelLookup): FallbackChains {\n\tconst canonical: Record<string, readonly string[]> = {};\n\n\tfor (const [key, entries] of Object.entries(chains)) {\n\t\tconst parsedKey = parseFallbackSelector(key, lookup);\n\t\tif (!parsedKey || !Array.isArray(entries)) continue;\n\n\t\tconst canonicalEntries = entries.flatMap((entry) => {\n\t\t\tconst parsedEntry = parseFallbackSelector(entry, lookup);\n\t\t\treturn parsedEntry ? [formatParsedSelector(parsedEntry)] : [];\n\t\t});\n\t\tif (canonicalEntries.length > 0) {\n\t\t\tcanonical[formatParsedSelector(parsedKey)] = canonicalEntries;\n\t\t}\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 { 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 };\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};\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};\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"]}
|
|
@@ -19,6 +19,8 @@ export interface RetryFallbackControllerDeps {
|
|
|
19
19
|
registry: {
|
|
20
20
|
find(provider: string, id: string): Model<Api> | undefined;
|
|
21
21
|
getAll(): Model<Api>[];
|
|
22
|
+
/** Ranks bare-selector expansion: OAuth-credential providers come first. */
|
|
23
|
+
isUsingOAuth?(model: Model<Api>): boolean;
|
|
22
24
|
};
|
|
23
25
|
cooldowns: SelectorCooldowns;
|
|
24
26
|
logger: FallbackLogger;
|
|
@@ -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,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAWxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,CAAC,EAAE,aAAa,CAAC;IACtC,wBAAwB,CAAC,EAAE,aAAa,CAAC;IACzC,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,KAAK,cAAc,GAAG,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;AAEzE,UAAU,gBAAgB;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,2BAA2B;IAC3C,WAAW,IAAI,gBAAgB,CAAC;IAChC,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -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, Model } from \"@earendil-works/pi-ai\";\nimport { getSupportedThinkingLevels } from \"../thinking-levels.ts\";\nimport {\n\tbaseSelector,\n\tcandidatesAfter,\n\tcanonicalizeFallbackChains,\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: { find(provider: string, id: string): Model<Api> | undefined; getAll(): Model<Api>[] };\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\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.resetTurn();\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 = canonicalizeFallbackChains(this.deps.getSettings().chains, this.deps.registry);\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 = canonicalizeFallbackChains(settings.chains, this.deps.registry);\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\tconst supported = getSupportedThinkingLevels(model);\n\t\treturn supported.includes(requested) ? requested : (supported[supported.length - 1] ?? \"off\");\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":"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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 {\n\tbaseSelector,\n\tcandidatesAfter,\n\tcanonicalizeFallbackChains,\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};\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\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.resetTurn();\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 = canonicalizeFallbackChains(this.deps.getSettings().chains, this.deps.registry);\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 = canonicalizeFallbackChains(settings.chains, this.deps.registry);\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\tconst supported = getSupportedThinkingLevels(model);\n\t\treturn supported.includes(requested) ? requested : (supported[supported.length - 1] ?? \"off\");\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"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
export interface BareSelectorParts {
|
|
3
|
+
/** Model id without a provider prefix, e.g. `claude-opus-5`. */
|
|
4
|
+
family: string;
|
|
5
|
+
thinkingLevel?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Auth-tier probe. Providers holding an OAuth credential outrank API-key ones. */
|
|
8
|
+
export type FallbackAuthLookup = (model: Model<Api>) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Auth tiers for bare expansion. OAuth first (a logged-in subscription is the
|
|
11
|
+
* account the user actually wants used), then any configured credential, then
|
|
12
|
+
* everything else. This ranks rather than filters: the runtime already skips
|
|
13
|
+
* unauthenticated candidates, and dropping them here would erase the chain
|
|
14
|
+
* whenever availability is not yet known.
|
|
15
|
+
*/
|
|
16
|
+
export interface FallbackAuthTiers {
|
|
17
|
+
isUsingOAuth: FallbackAuthLookup;
|
|
18
|
+
hasConfiguredAuth?: FallbackAuthLookup;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* How many providers one bare selector may fan out to. A shipped default must
|
|
22
|
+
* stay a short, readable chain: senpi's builtin catalog publishes popular models
|
|
23
|
+
* under a dozen providers, and expanding to all of them turned `/fallback` into
|
|
24
|
+
* an unreadable wall and the policy into "try every gateway that resells this".
|
|
25
|
+
* Two keeps the useful property - a second account for the same model - without
|
|
26
|
+
* the noise.
|
|
27
|
+
*/
|
|
28
|
+
declare const MAX_PROVIDERS_PER_FAMILY = 2;
|
|
29
|
+
/** A selector is bare when it carries no provider prefix and no wildcard. */
|
|
30
|
+
export declare function parseBareSelector(raw: string): BareSelectorParts | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Conservative family match. A bare id matches its exact model and dash-suffixed
|
|
33
|
+
* variants (`k3` -> `k3-256k`) but never an arbitrary substring, so `claude-fable-5`
|
|
34
|
+
* cannot capture `not-claude-fable-5`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function matchesFamily(model: Model<Api>, family: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Ranks one model per eligible provider: OAuth-credential providers first (a
|
|
39
|
+
* logged-in subscription is the account the user actually wants used), then the
|
|
40
|
+
* fixed precedence table, then alphabetically.
|
|
41
|
+
*/
|
|
42
|
+
export declare function rankFamilyModels(models: readonly Model<Api>[], family: string, tiers: FallbackAuthTiers, options?: {
|
|
43
|
+
limit?: number;
|
|
44
|
+
}): Model<Api>[];
|
|
45
|
+
export { MAX_PROVIDERS_PER_FAMILY };
|
|
46
|
+
//# sourceMappingURL=expansion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expansion.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/expansion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAiBxD,MAAM,WAAW,iBAAiB;IACjC,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IACjC,YAAY,EAAE,kBAAkB,CAAC;IACjC,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;CACvC;AAQD;;;;;;;GAOG;AACH,QAAA,MAAM,wBAAwB,IAAI,CAAC;AAEnC,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAW5E;AAWD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGxE;AAoBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,KAAK,CAAC,GAAG,CAAC,EAAE,CAyBd;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isValidThinkingLevel } from "../../cli/args.js";
|
|
2
|
+
/**
|
|
3
|
+
* Providers that never receive a bare selector. OpenRouter re-publishes other
|
|
4
|
+
* vendors' catalogs under namespaced ids, so family expansion would silently
|
|
5
|
+
* route a shipped default through a third-party reseller the user never chose.
|
|
6
|
+
* An explicit `openrouter/...` selector the user wrote is unaffected.
|
|
7
|
+
*/
|
|
8
|
+
const BARE_EXPANSION_DENYLIST = new Set(["openrouter", "openrouter-images"]);
|
|
9
|
+
/**
|
|
10
|
+
* Deterministic tie-break inside one auth tier. Earlier wins. Providers absent
|
|
11
|
+
* from the table sort after every listed one, alphabetically.
|
|
12
|
+
*/
|
|
13
|
+
const PROVIDER_PRECEDENCE = ["claude-sdk-oauth", "anthropic", "kimi-coding"];
|
|
14
|
+
function authTier(model, tiers) {
|
|
15
|
+
if (tiers.isUsingOAuth(model))
|
|
16
|
+
return 0;
|
|
17
|
+
if (tiers.hasConfiguredAuth?.(model) === true)
|
|
18
|
+
return 1;
|
|
19
|
+
return 2;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* How many providers one bare selector may fan out to. A shipped default must
|
|
23
|
+
* stay a short, readable chain: senpi's builtin catalog publishes popular models
|
|
24
|
+
* under a dozen providers, and expanding to all of them turned `/fallback` into
|
|
25
|
+
* an unreadable wall and the policy into "try every gateway that resells this".
|
|
26
|
+
* Two keeps the useful property - a second account for the same model - without
|
|
27
|
+
* the noise.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_PROVIDERS_PER_FAMILY = 2;
|
|
30
|
+
/** A selector is bare when it carries no provider prefix and no wildcard. */
|
|
31
|
+
export function parseBareSelector(raw) {
|
|
32
|
+
const trimmed = raw.trim();
|
|
33
|
+
if (!trimmed || trimmed.includes("/") || trimmed.includes("*"))
|
|
34
|
+
return undefined;
|
|
35
|
+
const lastColon = trimmed.lastIndexOf(":");
|
|
36
|
+
if (lastColon === -1)
|
|
37
|
+
return { family: trimmed.toLowerCase() };
|
|
38
|
+
const suffix = trimmed.slice(lastColon + 1).toLowerCase();
|
|
39
|
+
if (!isValidThinkingLevel(suffix))
|
|
40
|
+
return { family: trimmed.toLowerCase() };
|
|
41
|
+
const family = trimmed.slice(0, lastColon).trim().toLowerCase();
|
|
42
|
+
return family ? { family, thinkingLevel: suffix } : undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Strips a provider-side namespace so Bedrock's `global.anthropic.claude-opus-5`
|
|
46
|
+
* and a gateway's `anthropic/claude-opus-5` both reduce to `claude-opus-5`.
|
|
47
|
+
*/
|
|
48
|
+
function withoutNamespace(modelId) {
|
|
49
|
+
const cut = Math.max(modelId.lastIndexOf("."), modelId.lastIndexOf("/"));
|
|
50
|
+
return cut === -1 ? modelId : modelId.slice(cut + 1);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Conservative family match. A bare id matches its exact model and dash-suffixed
|
|
54
|
+
* variants (`k3` -> `k3-256k`) but never an arbitrary substring, so `claude-fable-5`
|
|
55
|
+
* cannot capture `not-claude-fable-5`.
|
|
56
|
+
*/
|
|
57
|
+
export function matchesFamily(model, family) {
|
|
58
|
+
const candidates = [model.id.toLowerCase(), withoutNamespace(model.id.toLowerCase())];
|
|
59
|
+
return candidates.some((id) => id === family || id.startsWith(`${family}-`));
|
|
60
|
+
}
|
|
61
|
+
/** Exact id wins, then the shortest variant, then alphabetical. */
|
|
62
|
+
function pickVariant(models, family) {
|
|
63
|
+
const ranked = [...models].sort((left, right) => {
|
|
64
|
+
const exact = Number(withoutNamespace(right.id.toLowerCase()) === family) -
|
|
65
|
+
Number(withoutNamespace(left.id.toLowerCase()) === family);
|
|
66
|
+
if (exact !== 0)
|
|
67
|
+
return exact;
|
|
68
|
+
if (left.id.length !== right.id.length)
|
|
69
|
+
return left.id.length - right.id.length;
|
|
70
|
+
return left.id.localeCompare(right.id);
|
|
71
|
+
});
|
|
72
|
+
return ranked[0];
|
|
73
|
+
}
|
|
74
|
+
function precedenceIndex(provider) {
|
|
75
|
+
const index = PROVIDER_PRECEDENCE.indexOf(provider.toLowerCase());
|
|
76
|
+
return index === -1 ? PROVIDER_PRECEDENCE.length : index;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Ranks one model per eligible provider: OAuth-credential providers first (a
|
|
80
|
+
* logged-in subscription is the account the user actually wants used), then the
|
|
81
|
+
* fixed precedence table, then alphabetically.
|
|
82
|
+
*/
|
|
83
|
+
export function rankFamilyModels(models, family, tiers, options = {}) {
|
|
84
|
+
const byProvider = new Map();
|
|
85
|
+
for (const model of models) {
|
|
86
|
+
if (BARE_EXPANSION_DENYLIST.has(model.provider.toLowerCase()))
|
|
87
|
+
continue;
|
|
88
|
+
if (!matchesFamily(model, family))
|
|
89
|
+
continue;
|
|
90
|
+
const bucket = byProvider.get(model.provider);
|
|
91
|
+
if (bucket)
|
|
92
|
+
bucket.push(model);
|
|
93
|
+
else
|
|
94
|
+
byProvider.set(model.provider, [model]);
|
|
95
|
+
}
|
|
96
|
+
const picked = [...byProvider.values()].flatMap((variants) => {
|
|
97
|
+
const variant = pickVariant(variants, family);
|
|
98
|
+
return variant ? [variant] : [];
|
|
99
|
+
});
|
|
100
|
+
const ranked = picked.sort((left, right) => {
|
|
101
|
+
const tier = authTier(left, tiers) - authTier(right, tiers);
|
|
102
|
+
if (tier !== 0)
|
|
103
|
+
return tier;
|
|
104
|
+
const precedence = precedenceIndex(left.provider) - precedenceIndex(right.provider);
|
|
105
|
+
if (precedence !== 0)
|
|
106
|
+
return precedence;
|
|
107
|
+
return left.provider.localeCompare(right.provider);
|
|
108
|
+
});
|
|
109
|
+
const limit = options.limit ?? ranked.length;
|
|
110
|
+
return ranked.slice(0, limit);
|
|
111
|
+
}
|
|
112
|
+
export { MAX_PROVIDERS_PER_FAMILY };
|
|
113
|
+
//# sourceMappingURL=expansion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expansion.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/expansion.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { isValidThinkingLevel } from \"../../cli/args.ts\";\n\n/**\n * Providers that never receive a bare selector. OpenRouter re-publishes other\n * vendors' catalogs under namespaced ids, so family expansion would silently\n * route a shipped default through a third-party reseller the user never chose.\n * An explicit `openrouter/...` selector the user wrote is unaffected.\n */\nconst BARE_EXPANSION_DENYLIST: ReadonlySet<string> = new Set([\"openrouter\", \"openrouter-images\"]);\n\n/**\n * Deterministic tie-break inside one auth tier. Earlier wins. Providers absent\n * from the table sort after every listed one, alphabetically.\n */\nconst PROVIDER_PRECEDENCE: readonly string[] = [\"claude-sdk-oauth\", \"anthropic\", \"kimi-coding\"];\n\nexport interface BareSelectorParts {\n\t/** Model id without a provider prefix, e.g. `claude-opus-5`. */\n\tfamily: string;\n\tthinkingLevel?: string;\n}\n\n/** Auth-tier probe. Providers holding an OAuth credential outrank API-key ones. */\nexport type FallbackAuthLookup = (model: Model<Api>) => boolean;\n\n/**\n * Auth tiers for bare expansion. OAuth first (a logged-in subscription is the\n * account the user actually wants used), then any configured credential, then\n * everything else. This ranks rather than filters: the runtime already skips\n * unauthenticated candidates, and dropping them here would erase the chain\n * whenever availability is not yet known.\n */\nexport interface FallbackAuthTiers {\n\tisUsingOAuth: FallbackAuthLookup;\n\thasConfiguredAuth?: FallbackAuthLookup;\n}\n\nfunction authTier(model: Model<Api>, tiers: FallbackAuthTiers): number {\n\tif (tiers.isUsingOAuth(model)) return 0;\n\tif (tiers.hasConfiguredAuth?.(model) === true) return 1;\n\treturn 2;\n}\n\n/**\n * How many providers one bare selector may fan out to. A shipped default must\n * stay a short, readable chain: senpi's builtin catalog publishes popular models\n * under a dozen providers, and expanding to all of them turned `/fallback` into\n * an unreadable wall and the policy into \"try every gateway that resells this\".\n * Two keeps the useful property - a second account for the same model - without\n * the noise.\n */\nconst MAX_PROVIDERS_PER_FAMILY = 2;\n\n/** A selector is bare when it carries no provider prefix and no wildcard. */\nexport function parseBareSelector(raw: string): BareSelectorParts | undefined {\n\tconst trimmed = raw.trim();\n\tif (!trimmed || trimmed.includes(\"/\") || trimmed.includes(\"*\")) return undefined;\n\n\tconst lastColon = trimmed.lastIndexOf(\":\");\n\tif (lastColon === -1) return { family: trimmed.toLowerCase() };\n\n\tconst suffix = trimmed.slice(lastColon + 1).toLowerCase();\n\tif (!isValidThinkingLevel(suffix)) return { family: trimmed.toLowerCase() };\n\tconst family = trimmed.slice(0, lastColon).trim().toLowerCase();\n\treturn family ? { family, thinkingLevel: suffix } : undefined;\n}\n\n/**\n * Strips a provider-side namespace so Bedrock's `global.anthropic.claude-opus-5`\n * and a gateway's `anthropic/claude-opus-5` both reduce to `claude-opus-5`.\n */\nfunction withoutNamespace(modelId: string): string {\n\tconst cut = Math.max(modelId.lastIndexOf(\".\"), modelId.lastIndexOf(\"/\"));\n\treturn cut === -1 ? modelId : modelId.slice(cut + 1);\n}\n\n/**\n * Conservative family match. A bare id matches its exact model and dash-suffixed\n * variants (`k3` -> `k3-256k`) but never an arbitrary substring, so `claude-fable-5`\n * cannot capture `not-claude-fable-5`.\n */\nexport function matchesFamily(model: Model<Api>, family: string): boolean {\n\tconst candidates = [model.id.toLowerCase(), withoutNamespace(model.id.toLowerCase())];\n\treturn candidates.some((id) => id === family || id.startsWith(`${family}-`));\n}\n\n/** Exact id wins, then the shortest variant, then alphabetical. */\nfunction pickVariant(models: readonly Model<Api>[], family: string): Model<Api> | undefined {\n\tconst ranked = [...models].sort((left, right) => {\n\t\tconst exact =\n\t\t\tNumber(withoutNamespace(right.id.toLowerCase()) === family) -\n\t\t\tNumber(withoutNamespace(left.id.toLowerCase()) === family);\n\t\tif (exact !== 0) return exact;\n\t\tif (left.id.length !== right.id.length) return left.id.length - right.id.length;\n\t\treturn left.id.localeCompare(right.id);\n\t});\n\treturn ranked[0];\n}\n\nfunction precedenceIndex(provider: string): number {\n\tconst index = PROVIDER_PRECEDENCE.indexOf(provider.toLowerCase());\n\treturn index === -1 ? PROVIDER_PRECEDENCE.length : index;\n}\n\n/**\n * Ranks one model per eligible provider: OAuth-credential providers first (a\n * logged-in subscription is the account the user actually wants used), then the\n * fixed precedence table, then alphabetically.\n */\nexport function rankFamilyModels(\n\tmodels: readonly Model<Api>[],\n\tfamily: string,\n\ttiers: FallbackAuthTiers,\n\toptions: { limit?: number } = {},\n): Model<Api>[] {\n\tconst byProvider = new Map<string, Model<Api>[]>();\n\tfor (const model of models) {\n\t\tif (BARE_EXPANSION_DENYLIST.has(model.provider.toLowerCase())) continue;\n\t\tif (!matchesFamily(model, family)) continue;\n\t\tconst bucket = byProvider.get(model.provider);\n\t\tif (bucket) bucket.push(model);\n\t\telse byProvider.set(model.provider, [model]);\n\t}\n\n\tconst picked = [...byProvider.values()].flatMap((variants) => {\n\t\tconst variant = pickVariant(variants, family);\n\t\treturn variant ? [variant] : [];\n\t});\n\n\tconst ranked = picked.sort((left, right) => {\n\t\tconst tier = authTier(left, tiers) - authTier(right, tiers);\n\t\tif (tier !== 0) return tier;\n\t\tconst precedence = precedenceIndex(left.provider) - precedenceIndex(right.provider);\n\t\tif (precedence !== 0) return precedence;\n\t\treturn left.provider.localeCompare(right.provider);\n\t});\n\n\tconst limit = options.limit ?? ranked.length;\n\treturn ranked.slice(0, limit);\n}\n\nexport { MAX_PROVIDERS_PER_FAMILY };\n"]}
|
|
@@ -26,4 +26,18 @@ export interface InTurnResult {
|
|
|
26
26
|
demoteToProbeBack: boolean;
|
|
27
27
|
}
|
|
28
28
|
export declare function nextInTurnDelayMs(state: InTurnState, hintMs: number | undefined, baseDelayMs: number, hintedWaitCapMs: number, nowMs: number): InTurnResult;
|
|
29
|
+
export type DegradedRateLimitAction = {
|
|
30
|
+
kind: "in-turn";
|
|
31
|
+
delayMs: number;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "fail";
|
|
34
|
+
hintMs: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Policy for a 429-class failure when no fallback candidate is usable: no-hint
|
|
38
|
+
* (and tier1) failures retry in-turn on the exponential schedule, tier2 hints
|
|
39
|
+
* retry in-turn with the wait clamped to the in-turn cap, and only tier3
|
|
40
|
+
* (probe-back-max or longer) hinted waits stay terminal.
|
|
41
|
+
*/
|
|
42
|
+
export declare function degradeWithoutFallback(tier: HintTier, hintMs: number | undefined, attempt: number, baseDelayMs: number, hintedWaitCapMs: number): DegradedRateLimitAction;
|
|
29
43
|
//# sourceMappingURL=hint-policy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hint-policy.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/hint-policy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,uBAAuB,GAAG,eAAe,GAAG,2BAA2B,GAAG,qBAAqB,CAAC;AAEvH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC3D,QAAQ,CAKV;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAE1G;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,WAAW,GAAG,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,GACX,YAAY,CAsCd"}
|
|
1
|
+
{"version":3,"file":"hint-policy.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/hint-policy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,QAAQ,GAAG,uBAAuB,GAAG,eAAe,GAAG,2BAA2B,GAAG,qBAAqB,CAAC;AAEvH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,EAAE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC3D,QAAQ,CAKV;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAE1G;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,WAAW,GAAG,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,GACX,YAAY,CAsCd;AAED,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9G;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,GACrB,uBAAuB,CAMzB"}
|
|
@@ -51,4 +51,18 @@ export function nextInTurnDelayMs(state, hintMs, baseDelayMs, hintedWaitCapMs, n
|
|
|
51
51
|
demoteToProbeBack: false,
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Policy for a 429-class failure when no fallback candidate is usable: no-hint
|
|
56
|
+
* (and tier1) failures retry in-turn on the exponential schedule, tier2 hints
|
|
57
|
+
* retry in-turn with the wait clamped to the in-turn cap, and only tier3
|
|
58
|
+
* (probe-back-max or longer) hinted waits stay terminal.
|
|
59
|
+
*/
|
|
60
|
+
export function degradeWithoutFallback(tier, hintMs, attempt, baseDelayMs, hintedWaitCapMs) {
|
|
61
|
+
if (tier === "tier3-fallback-only")
|
|
62
|
+
return { kind: "fail", hintMs: hintMs ?? 0 };
|
|
63
|
+
if (tier === "tier2-fallback-probe-back") {
|
|
64
|
+
return { kind: "in-turn", delayMs: Math.min(hintMs ?? hintedWaitCapMs, hintedWaitCapMs) };
|
|
65
|
+
}
|
|
66
|
+
return { kind: "in-turn", delayMs: baseDelayMs * 2 ** (attempt - 1) };
|
|
67
|
+
}
|
|
54
68
|
//# sourceMappingURL=hint-policy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hint-policy.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/hint-policy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,UAAU,uBAAuB,CACtC,MAA0B,EAC1B,QAA6D;IAE7D,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,uBAAuB,CAAC;IACzD,IAAI,MAAM,IAAI,QAAQ,CAAC,eAAe;QAAE,OAAO,eAAe,CAAC;IAC/D,IAAI,MAAM,GAAG,QAAQ,CAAC,cAAc;QAAE,OAAO,2BAA2B,CAAC;IACzE,OAAO,qBAAqB,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc,EAAE,KAAa;IAC9D,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,GAAG,MAAM,EAAE,CAAC;AACjF,CAAC;AAmBD,MAAM,UAAU,iBAAiB,CAChC,KAAkB,EAClB,MAA0B,EAC1B,WAAmB,EACnB,eAAuB,EACvB,KAAa;IAEb,0DAA0D;IAC1D,IAAI,KAAK,CAAC,UAAU,KAAK,WAAW,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC;QAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,KAAK,CAAC,sBAAsB,GAAG,SAAS,CAAC;QAC/D,OAAO;YACN,OAAO,EAAE,SAAS;YAClB,UAAU,EAAE,MAAM;YAClB,cAAc,EAAE,UAAU;YAC1B,sBAAsB,EAAE,aAAa;YACrC,iBAAiB,EAAE,aAAa,GAAG,eAAe;SAClD,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,IAAI,KAAK,CAAC,UAAU,KAAK,MAAM,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;QAClC,MAAM,aAAa,GAAG,KAAK,CAAC,sBAAsB,GAAG,KAAK,CAAC;QAC3D,OAAO;YACN,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,WAAW;YACvB,cAAc,EAAE,UAAU;YAC1B,sBAAsB,EAAE,aAAa;YACrC,iBAAiB,EAAE,aAAa,GAAG,eAAe;SAClD,CAAC;IACH,CAAC;IAED,kGAAkG;IAClG,MAAM,KAAK,GAAG,MAAM,IAAI,WAAW,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IAC/D,OAAO;QACN,OAAO,EAAE,KAAK;QACd,UAAU,EAAE,MAAM;QAClB,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;QACpD,iBAAiB,EAAE,KAAK;KACxB,CAAC;AACH,CAAC","sourcesContent":["/**\n * Pure tier-policy and probe-schedule decisions for hint-aware 429 retry.\n * No timers, no I/O — all time values are injected by the caller.\n */\n\nexport type HintTier = \"no-hint-fast-fallback\" | \"tier1-in-turn\" | \"tier2-fallback-probe-back\" | \"tier3-fallback-only\";\n\nexport function classifyRateLimitedWait(\n\thintMs: number | undefined,\n\tsettings: { hintedWaitCapMs: number; probeBackMaxMs: number },\n): HintTier {\n\tif (hintMs === undefined) return \"no-hint-fast-fallback\";\n\tif (hintMs <= settings.hintedWaitCapMs) return \"tier1-in-turn\";\n\tif (hintMs < settings.probeBackMaxMs) return \"tier2-fallback-probe-back\";\n\treturn \"tier3-fallback-only\";\n}\n\nexport function probeBackSchedule(hintMs: number, nowMs: number): { firstAtMs: number; deadlineMs: number } {\n\treturn { firstAtMs: nowMs + Math.ceil(hintMs / 2), deadlineMs: nowMs + hintMs };\n}\n\nexport type ProbePhase = \"idle\" | \"half-used\" | \"done\";\n\nexport interface InTurnState {\n\tprobePhase: ProbePhase;\n\thintDeadlineMs?: number;\n\tattempt: number;\n\tcumulativeHintedWaitMs: number;\n}\n\nexport interface InTurnResult {\n\tdelayMs: number;\n\tprobePhase: \"half-used\" | \"done\";\n\thintDeadlineMs?: number;\n\tcumulativeHintedWaitMs: number;\n\tdemoteToProbeBack: boolean;\n}\n\nexport function nextInTurnDelayMs(\n\tstate: InTurnState,\n\thintMs: number | undefined,\n\tbaseDelayMs: number,\n\thintedWaitCapMs: number,\n\tnowMs: number,\n): InTurnResult {\n\t// Phase: half-used -> consecutive 429 with deadline sleep\n\tif (state.probePhase === \"half-used\") {\n\t\tconst deadlineMs = hintMs !== undefined ? nowMs + hintMs : (state.hintDeadlineMs ?? nowMs);\n\t\tconst remaining = Math.max(0, deadlineMs - nowMs);\n\t\tconst newCumulative = state.cumulativeHintedWaitMs + remaining;\n\t\treturn {\n\t\t\tdelayMs: remaining,\n\t\t\tprobePhase: \"done\",\n\t\t\thintDeadlineMs: deadlineMs,\n\t\t\tcumulativeHintedWaitMs: newCumulative,\n\t\t\tdemoteToProbeBack: newCumulative > hintedWaitCapMs,\n\t\t};\n\t}\n\n\t// Phase: idle -> first hinted 429, probe at hint/2\n\tif (state.probePhase === \"idle\" && hintMs !== undefined) {\n\t\tconst delay = Math.ceil(hintMs / 2);\n\t\tconst deadlineMs = nowMs + hintMs;\n\t\tconst newCumulative = state.cumulativeHintedWaitMs + delay;\n\t\treturn {\n\t\t\tdelayMs: delay,\n\t\t\tprobePhase: \"half-used\",\n\t\t\thintDeadlineMs: deadlineMs,\n\t\t\tcumulativeHintedWaitMs: newCumulative,\n\t\t\tdemoteToProbeBack: newCumulative > hintedWaitCapMs,\n\t\t};\n\t}\n\n\t// Phase: done (or idle without hint = non-429 / no-hint fallback) -> exponential or hint override\n\tconst delay = hintMs ?? baseDelayMs * 2 ** (state.attempt - 1);\n\treturn {\n\t\tdelayMs: delay,\n\t\tprobePhase: \"done\",\n\t\thintDeadlineMs: state.hintDeadlineMs,\n\t\tcumulativeHintedWaitMs: state.cumulativeHintedWaitMs,\n\t\tdemoteToProbeBack: false,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hint-policy.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/hint-policy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure tier-policy and probe-schedule decisions for hint-aware 429 retry.\n * No timers, no I/O — all time values are injected by the caller.\n */\n\nexport type HintTier = \"no-hint-fast-fallback\" | \"tier1-in-turn\" | \"tier2-fallback-probe-back\" | \"tier3-fallback-only\";\n\nexport function classifyRateLimitedWait(\n\thintMs: number | undefined,\n\tsettings: { hintedWaitCapMs: number; probeBackMaxMs: number },\n): HintTier {\n\tif (hintMs === undefined) return \"no-hint-fast-fallback\";\n\tif (hintMs <= settings.hintedWaitCapMs) return \"tier1-in-turn\";\n\tif (hintMs < settings.probeBackMaxMs) return \"tier2-fallback-probe-back\";\n\treturn \"tier3-fallback-only\";\n}\n\nexport function probeBackSchedule(hintMs: number, nowMs: number): { firstAtMs: number; deadlineMs: number } {\n\treturn { firstAtMs: nowMs + Math.ceil(hintMs / 2), deadlineMs: nowMs + hintMs };\n}\n\nexport type ProbePhase = \"idle\" | \"half-used\" | \"done\";\n\nexport interface InTurnState {\n\tprobePhase: ProbePhase;\n\thintDeadlineMs?: number;\n\tattempt: number;\n\tcumulativeHintedWaitMs: number;\n}\n\nexport interface InTurnResult {\n\tdelayMs: number;\n\tprobePhase: \"half-used\" | \"done\";\n\thintDeadlineMs?: number;\n\tcumulativeHintedWaitMs: number;\n\tdemoteToProbeBack: boolean;\n}\n\nexport function nextInTurnDelayMs(\n\tstate: InTurnState,\n\thintMs: number | undefined,\n\tbaseDelayMs: number,\n\thintedWaitCapMs: number,\n\tnowMs: number,\n): InTurnResult {\n\t// Phase: half-used -> consecutive 429 with deadline sleep\n\tif (state.probePhase === \"half-used\") {\n\t\tconst deadlineMs = hintMs !== undefined ? nowMs + hintMs : (state.hintDeadlineMs ?? nowMs);\n\t\tconst remaining = Math.max(0, deadlineMs - nowMs);\n\t\tconst newCumulative = state.cumulativeHintedWaitMs + remaining;\n\t\treturn {\n\t\t\tdelayMs: remaining,\n\t\t\tprobePhase: \"done\",\n\t\t\thintDeadlineMs: deadlineMs,\n\t\t\tcumulativeHintedWaitMs: newCumulative,\n\t\t\tdemoteToProbeBack: newCumulative > hintedWaitCapMs,\n\t\t};\n\t}\n\n\t// Phase: idle -> first hinted 429, probe at hint/2\n\tif (state.probePhase === \"idle\" && hintMs !== undefined) {\n\t\tconst delay = Math.ceil(hintMs / 2);\n\t\tconst deadlineMs = nowMs + hintMs;\n\t\tconst newCumulative = state.cumulativeHintedWaitMs + delay;\n\t\treturn {\n\t\t\tdelayMs: delay,\n\t\t\tprobePhase: \"half-used\",\n\t\t\thintDeadlineMs: deadlineMs,\n\t\t\tcumulativeHintedWaitMs: newCumulative,\n\t\t\tdemoteToProbeBack: newCumulative > hintedWaitCapMs,\n\t\t};\n\t}\n\n\t// Phase: done (or idle without hint = non-429 / no-hint fallback) -> exponential or hint override\n\tconst delay = hintMs ?? baseDelayMs * 2 ** (state.attempt - 1);\n\treturn {\n\t\tdelayMs: delay,\n\t\tprobePhase: \"done\",\n\t\thintDeadlineMs: state.hintDeadlineMs,\n\t\tcumulativeHintedWaitMs: state.cumulativeHintedWaitMs,\n\t\tdemoteToProbeBack: false,\n\t};\n}\n\nexport type DegradedRateLimitAction = { kind: \"in-turn\"; delayMs: number } | { kind: \"fail\"; hintMs: number };\n\n/**\n * Policy for a 429-class failure when no fallback candidate is usable: no-hint\n * (and tier1) failures retry in-turn on the exponential schedule, tier2 hints\n * retry in-turn with the wait clamped to the in-turn cap, and only tier3\n * (probe-back-max or longer) hinted waits stay terminal.\n */\nexport function degradeWithoutFallback(\n\ttier: HintTier,\n\thintMs: number | undefined,\n\tattempt: number,\n\tbaseDelayMs: number,\n\thintedWaitCapMs: number,\n): DegradedRateLimitAction {\n\tif (tier === \"tier3-fallback-only\") return { kind: \"fail\", hintMs: hintMs ?? 0 };\n\tif (tier === \"tier2-fallback-probe-back\") {\n\t\treturn { kind: \"in-turn\", delayMs: Math.min(hintMs ?? hintedWaitCapMs, hintedWaitCapMs) };\n\t}\n\treturn { kind: \"in-turn\", delayMs: baseDelayMs * 2 ** (attempt - 1) };\n}\n"]}
|
|
@@ -23,6 +23,12 @@ export interface ResolvedRetryFallbackSettings {
|
|
|
23
23
|
chains: FallbackChains;
|
|
24
24
|
revertPolicy: "cooldown-expiry" | "never";
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Shipped defaults are declared as model families (bare ids, no provider prefix).
|
|
28
|
+
* `canonicalizeFallbackChains` expands them against the live registry, so the
|
|
29
|
+
* chain follows Fable 5 whichever provider serves it - the builtin Anthropic
|
|
30
|
+
* provider, the Claude SDK OAuth extension, a gateway, or Bedrock.
|
|
31
|
+
*/
|
|
26
32
|
export declare const DEFAULT_FALLBACK_CHAINS: FallbackChains;
|
|
27
33
|
export declare function resolveRetryFallbackSettings(settings: RetrySettings | undefined): ResolvedRetryFallbackSettings;
|
|
28
34
|
export declare function resolveAbortServerSideFallback(settings: RetrySettings | undefined): boolean;
|
|
@@ -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,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,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,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,cAErC,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"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shipped defaults are declared as model families (bare ids, no provider prefix).
|
|
3
|
+
* `canonicalizeFallbackChains` expands them against the live registry, so the
|
|
4
|
+
* chain follows Fable 5 whichever provider serves it - the builtin Anthropic
|
|
5
|
+
* provider, the Claude SDK OAuth extension, a gateway, or Bedrock.
|
|
6
|
+
*/
|
|
1
7
|
export const DEFAULT_FALLBACK_CHAINS = {
|
|
2
|
-
"
|
|
3
|
-
"apitopia/kimi-k3-unlocked:max",
|
|
4
|
-
"anthropic/claude-opus-5:xhigh",
|
|
5
|
-
"anthropic/claude-opus-4-8:xhigh",
|
|
6
|
-
],
|
|
8
|
+
"claude-fable-5": ["k3:max", "claude-opus-5:xhigh", "claude-opus-4-8:xhigh"],
|
|
7
9
|
};
|
|
8
10
|
function cloneDefaultFallbackChains() {
|
|
9
11
|
const chains = {};
|
|
@@ -24,7 +26,8 @@ function isStringArray(value) {
|
|
|
24
26
|
* chain: that left the defaulted model with no client chain at all, which in
|
|
25
27
|
* turn disabled the server-fallback abort and handed model choice back to the
|
|
26
28
|
* provider. A same-named key still replaces that default outright (never a
|
|
27
|
-
* union), and an explicit empty array removes a default the user does not want
|
|
29
|
+
* union), and an explicit empty array removes a default the user does not want -
|
|
30
|
+
* as a tombstone honored at both bare-family and canonical-provider granularity.
|
|
28
31
|
*
|
|
29
32
|
* Cross-scope replacement (global vs project) stays wholesale and is handled by
|
|
30
33
|
* `deepMergeSettings`; this only resolves defaults against the merged result.
|
|
@@ -38,10 +41,9 @@ function resolveFallbackChains(value) {
|
|
|
38
41
|
for (const [key, entries] of Object.entries(value)) {
|
|
39
42
|
if (!isStringArray(entries))
|
|
40
43
|
return cloneDefaultFallbackChains();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
44
|
+
// An empty list stays in the map as a tombstone: canonicalization needs it to
|
|
45
|
+
// suppress the expanded default for that family or provider variant, and
|
|
46
|
+
// dropping it here would let the shipped default reappear after expansion.
|
|
45
47
|
chains[key] = [...entries];
|
|
46
48
|
}
|
|
47
49
|
return chains;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/settings.ts"],"names":[],"mappings":"AA6BA,MAAM,CAAC,MAAM,uBAAuB,GAAmB;IACtD,
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/settings.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FallbackChains } from \"./chains.ts\";\n\nexport interface ProviderRetrySettings {\n\ttimeoutMs?: number;\n\tstreamStartTimeoutMs?: number;\n\tstreamRetryTimeoutMs?: number; // first-request liveness cap after a provider timeout; 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\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\"claude-fable-5\": [\"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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAKxD,MAAM,WAAW,qBAAqB;IACrC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3D,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;CACvB;AAsCD,yFAAyF;AACzF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,qBAAqB,GAAG,MAAM,EAAE,CA6DjG"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getSupportedThinkingLevels } from "../thinking-levels.js";
|
|
2
2
|
import { baseSelector, parseFallbackSelector } from "./chains.js";
|
|
3
|
+
import { matchesFamily, parseBareSelector } from "./expansion.js";
|
|
3
4
|
function isPlainObject(value) {
|
|
4
5
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
5
6
|
return false;
|
|
@@ -38,7 +39,12 @@ export function validateFallbackChains(chains, registry) {
|
|
|
38
39
|
for (const [key, entries] of Object.entries(chains)) {
|
|
39
40
|
const hasProvider = key.includes("/");
|
|
40
41
|
const hasWildcard = key.includes("*");
|
|
41
|
-
|
|
42
|
+
// A bare key names a model family and applies to every provider serving it.
|
|
43
|
+
const bareKey = hasWildcard ? undefined : parseBareSelector(key);
|
|
44
|
+
const bareKeyMatches = bareKey ? models.some((model) => matchesFamily(model, bareKey.family)) : false;
|
|
45
|
+
if (!hasProvider && !bareKeyMatches && !hasWildcard) {
|
|
46
|
+
// A bare key naming no registered model is indistinguishable from the old
|
|
47
|
+
// role syntax, so it keeps the message that tells the user what to write.
|
|
42
48
|
warnings.push(`Fallback chain key "${key}" must use a provider/model selector; roles are unsupported.`);
|
|
43
49
|
}
|
|
44
50
|
if (hasWildcard)
|
|
@@ -61,6 +67,13 @@ export function validateFallbackChains(chains, registry) {
|
|
|
61
67
|
continue;
|
|
62
68
|
}
|
|
63
69
|
for (const entry of entries) {
|
|
70
|
+
const bareEntry = parseBareSelector(entry);
|
|
71
|
+
if (bareEntry) {
|
|
72
|
+
if (!models.some((model) => matchesFamily(model, bareEntry.family))) {
|
|
73
|
+
warnings.push(invalidSelectorWarning("entry", entry, key));
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
64
77
|
const parsedEntry = parseFallbackSelector(entry, models);
|
|
65
78
|
const entryModel = parsedEntry ? registry.find(parsedEntry.provider, parsedEntry.id) : undefined;
|
|
66
79
|
if (!parsedEntry || !entryModel) {
|