@code-yeongyu/senpi 2026.9.27-4 → 2026.9.28

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.
Files changed (271) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/bundle/chunks/{app-server-command-3GVJZFFH.js → app-server-command-TOPTGVQB.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-4SUOOZZ4.js → azure-openai-responses-FCZNBRWE.js} +1 -1
  4. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  5. package/dist/bundle/chunks/{chunk-PSCNHY7D.js → chunk-3GMFUEU7.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-HBG6SQU5.js → chunk-62XHQXCZ.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-SLL375O7.js → chunk-7DY2JPUX.js} +1 -1
  8. package/dist/bundle/chunks/chunk-7YUGCTEV.js +15 -0
  9. package/dist/bundle/chunks/{chunk-FWBSGMAE.js → chunk-A4UMPXQF.js} +1 -1
  10. package/dist/bundle/chunks/chunk-BBK6SSYT.js +2 -0
  11. package/dist/bundle/chunks/{chunk-C2425O2Q.js → chunk-CNQ4JB45.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-27IFXX3B.js → chunk-DRN4FQCE.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-22MGY5JQ.js → chunk-EHSZ7UCW.js} +1 -1
  14. package/dist/bundle/chunks/chunk-FHJYIU3U.js +5 -0
  15. package/dist/bundle/chunks/chunk-GJUCSTAG.js +19 -0
  16. package/dist/bundle/chunks/{chunk-VMKAYXHL.js → chunk-KIOTFUG6.js} +25 -20
  17. package/dist/bundle/chunks/{chunk-2BMD37YV.js → chunk-LUPPSEQW.js} +1 -1
  18. package/dist/bundle/chunks/chunk-OAIVTS5X.js +8 -0
  19. package/dist/bundle/chunks/{chunk-NRGMT74E.js → chunk-QOVUQZO4.js} +1 -1
  20. package/dist/bundle/chunks/chunk-SN4ZHPMN.js +135 -0
  21. package/dist/bundle/chunks/{chunk-IKXPBYSO.js → chunk-UJFLZ2M2.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-4T4GFAGK.js → chunk-VFSXUT7K.js} +1 -1
  23. package/dist/bundle/chunks/{cli-main-66NFBKVH.js → cli-main-ARR54ET6.js} +1 -1
  24. package/dist/bundle/chunks/{google-generative-ai-ZTGKZTGU.js → google-generative-ai-LFCBHG3Y.js} +1 -1
  25. package/dist/bundle/chunks/{google-vertex-CCZ376YK.js → google-vertex-C474FXU7.js} +1 -1
  26. package/dist/bundle/chunks/{help-fast-path-NZ3I2UEN.js → help-fast-path-3U3YDEZ6.js} +1 -1
  27. package/dist/bundle/chunks/{host-command-RORPAC5X.js → host-command-6CJ7YM6W.js} +1 -1
  28. package/dist/bundle/chunks/{host-lifecycle-UDMJGLOR.js → host-lifecycle-RZIMOFDM.js} +1 -1
  29. package/dist/bundle/chunks/host-lifecycle.js +6 -6
  30. package/dist/bundle/chunks/interactive-host-runtime-A3GEIFTU.js +4 -0
  31. package/dist/bundle/chunks/{interactive-mode-LOAADKLH.js → interactive-mode-ITSFLGF6.js} +1 -1
  32. package/dist/bundle/chunks/{list-tips-S5CANO5W.js → list-tips-FPXDXN7R.js} +1 -1
  33. package/dist/bundle/chunks/multi-session-host-B4WMZE2G.js +21 -0
  34. package/dist/bundle/chunks/{openai-codex-responses-YSGZZDD5.js → openai-codex-responses-2LDIFL6Z.js} +1 -1
  35. package/dist/bundle/chunks/{openai-completions-KWHRWCBN.js → openai-completions-7KZLPKYP.js} +1 -1
  36. package/dist/bundle/chunks/{openai-images-6U5FCN5S.js → openai-images-I7DR7ERP.js} +1 -1
  37. package/dist/bundle/chunks/{openai-responses-7PWMBGYX.js → openai-responses-LCHS3AF3.js} +1 -1
  38. package/dist/bundle/chunks/{openrouter-images-QIKWQZUD.js → openrouter-images-IPNDRK6C.js} +1 -1
  39. package/dist/bundle/chunks/{package-manager-cli-5EEDA35I.js → package-manager-cli-DXHQ6RKA.js} +1 -1
  40. package/dist/bundle/chunks/{rotation-stream-KZG4OVR7.js → rotation-stream-ZVJEGA2G.js} +1 -1
  41. package/dist/bundle/chunks/{rpc-mode-HOEA5NGQ.js → rpc-mode-PFPNP65F.js} +1 -1
  42. package/dist/bundle/chunks/session-picker-KOPOV6Q4.js +2 -0
  43. package/dist/bundle/chunks/session-worker.js +311 -302
  44. package/dist/bundle/cli.js +1 -1
  45. package/dist/bundle/index.js +1 -1
  46. package/dist/bundle/rpc-entry.js +1 -1
  47. package/dist/cli/args.js +1 -1
  48. package/dist/cli/args.js.map +1 -1
  49. package/dist/cli/host-command.d.ts +8 -1
  50. package/dist/cli/host-command.d.ts.map +1 -1
  51. package/dist/cli/host-command.js +66 -14
  52. package/dist/cli/host-command.js.map +1 -1
  53. package/dist/core/agent-session.d.ts +18 -1
  54. package/dist/core/agent-session.d.ts.map +1 -1
  55. package/dist/core/agent-session.js +119 -3
  56. package/dist/core/agent-session.js.map +1 -1
  57. package/dist/core/retry-fallback/candidates.d.ts +26 -0
  58. package/dist/core/retry-fallback/candidates.d.ts.map +1 -0
  59. package/dist/core/retry-fallback/candidates.js +41 -0
  60. package/dist/core/retry-fallback/candidates.js.map +1 -0
  61. package/dist/core/retry-fallback/circuit-probes.d.ts +30 -0
  62. package/dist/core/retry-fallback/circuit-probes.d.ts.map +1 -0
  63. package/dist/core/retry-fallback/circuit-probes.js +60 -0
  64. package/dist/core/retry-fallback/circuit-probes.js.map +1 -0
  65. package/dist/core/retry-fallback/circuit.d.ts +101 -0
  66. package/dist/core/retry-fallback/circuit.d.ts.map +1 -0
  67. package/dist/core/retry-fallback/circuit.js +175 -0
  68. package/dist/core/retry-fallback/circuit.js.map +1 -0
  69. package/dist/core/retry-fallback/controller-types.d.ts +58 -0
  70. package/dist/core/retry-fallback/controller-types.d.ts.map +1 -0
  71. package/dist/core/retry-fallback/controller-types.js +2 -0
  72. package/dist/core/retry-fallback/controller-types.js.map +1 -0
  73. package/dist/core/retry-fallback/controller.d.ts +21 -49
  74. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  75. package/dist/core/retry-fallback/controller.js +88 -42
  76. package/dist/core/retry-fallback/controller.js.map +1 -1
  77. package/dist/core/sdk.d.ts.map +1 -1
  78. package/dist/core/sdk.js +25 -17
  79. package/dist/core/sdk.js.map +1 -1
  80. package/dist/core/session-failure-report.d.ts +33 -0
  81. package/dist/core/session-failure-report.d.ts.map +1 -0
  82. package/dist/core/session-failure-report.js +62 -0
  83. package/dist/core/session-failure-report.js.map +1 -0
  84. package/dist/core/settings-manager.d.ts +3 -0
  85. package/dist/core/settings-manager.d.ts.map +1 -1
  86. package/dist/core/settings-manager.js +4 -0
  87. package/dist/core/settings-manager.js.map +1 -1
  88. package/dist/core/slash-commands.d.ts.map +1 -1
  89. package/dist/core/slash-commands.js +1 -0
  90. package/dist/core/slash-commands.js.map +1 -1
  91. package/dist/index.d.ts +1 -1
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/modes/index.d.ts +1 -0
  96. package/dist/modes/index.d.ts.map +1 -1
  97. package/dist/modes/index.js +2 -0
  98. package/dist/modes/index.js.map +1 -1
  99. package/dist/modes/interactive/help-content.d.ts.map +1 -1
  100. package/dist/modes/interactive/help-content.js +3 -0
  101. package/dist/modes/interactive/help-content.js.map +1 -1
  102. package/dist/modes/interactive/interactive-host-attach.d.ts +10 -0
  103. package/dist/modes/interactive/interactive-host-attach.d.ts.map +1 -0
  104. package/dist/modes/interactive/interactive-host-attach.js +14 -0
  105. package/dist/modes/interactive/interactive-host-attach.js.map +1 -0
  106. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  107. package/dist/modes/interactive/interactive-host-runtime.js +3 -6
  108. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  110. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  111. package/dist/modes/interactive/interactive-mode.js +66 -1
  112. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/dist/modes/interactive/session-failure-info.d.ts +3 -0
  114. package/dist/modes/interactive/session-failure-info.d.ts.map +1 -0
  115. package/dist/modes/interactive/session-failure-info.js +21 -0
  116. package/dist/modes/interactive/session-failure-info.js.map +1 -0
  117. package/dist/modes/interactive/tips/catalog/model-tips.d.ts +7 -7
  118. package/dist/modes/interactive/tips/catalog/model-tips.d.ts.map +1 -1
  119. package/dist/modes/interactive/tips/catalog/model-tips.js +7 -1
  120. package/dist/modes/interactive/tips/catalog/model-tips.js.map +1 -1
  121. package/dist/modes/interactive/tips/catalog/session-tips.d.ts.map +1 -1
  122. package/dist/modes/interactive/tips/catalog/session-tips.js +1 -1
  123. package/dist/modes/interactive/tips/catalog/session-tips.js.map +1 -1
  124. package/dist/modes/rpc/host-attach-hold.d.ts +16 -0
  125. package/dist/modes/rpc/host-attach-hold.d.ts.map +1 -0
  126. package/dist/modes/rpc/host-attach-hold.js +9 -0
  127. package/dist/modes/rpc/host-attach-hold.js.map +1 -0
  128. package/dist/modes/rpc/host-cleanup-paths.d.ts +20 -0
  129. package/dist/modes/rpc/host-cleanup-paths.d.ts.map +1 -0
  130. package/dist/modes/rpc/host-cleanup-paths.js +13 -0
  131. package/dist/modes/rpc/host-cleanup-paths.js.map +1 -0
  132. package/dist/modes/rpc/host-client-occupancy.d.ts +24 -0
  133. package/dist/modes/rpc/host-client-occupancy.d.ts.map +1 -0
  134. package/dist/modes/rpc/host-client-occupancy.js +53 -0
  135. package/dist/modes/rpc/host-client-occupancy.js.map +1 -0
  136. package/dist/modes/rpc/host-daemon-paths.d.ts +61 -8
  137. package/dist/modes/rpc/host-daemon-paths.d.ts.map +1 -1
  138. package/dist/modes/rpc/host-daemon-paths.js +128 -11
  139. package/dist/modes/rpc/host-daemon-paths.js.map +1 -1
  140. package/dist/modes/rpc/host-daemon-registration.d.ts +11 -1
  141. package/dist/modes/rpc/host-daemon-registration.d.ts.map +1 -1
  142. package/dist/modes/rpc/host-daemon-registration.js +28 -15
  143. package/dist/modes/rpc/host-daemon-registration.js.map +1 -1
  144. package/dist/modes/rpc/host-endpoints.d.ts +12 -0
  145. package/dist/modes/rpc/host-endpoints.d.ts.map +1 -0
  146. package/dist/modes/rpc/host-endpoints.js +59 -0
  147. package/dist/modes/rpc/host-endpoints.js.map +1 -0
  148. package/dist/modes/rpc/host-ensure-lock.d.ts +11 -0
  149. package/dist/modes/rpc/host-ensure-lock.d.ts.map +1 -0
  150. package/dist/modes/rpc/host-ensure-lock.js +49 -0
  151. package/dist/modes/rpc/host-ensure-lock.js.map +1 -0
  152. package/dist/modes/rpc/host-ensure.d.ts +7 -0
  153. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  154. package/dist/modes/rpc/host-ensure.js +42 -65
  155. package/dist/modes/rpc/host-ensure.js.map +1 -1
  156. package/dist/modes/rpc/host-gc-evidence.d.ts +18 -0
  157. package/dist/modes/rpc/host-gc-evidence.d.ts.map +1 -0
  158. package/dist/modes/rpc/host-gc-evidence.js +128 -0
  159. package/dist/modes/rpc/host-gc-evidence.js.map +1 -0
  160. package/dist/modes/rpc/host-gc.d.ts +28 -0
  161. package/dist/modes/rpc/host-gc.d.ts.map +1 -0
  162. package/dist/modes/rpc/host-gc.js +110 -0
  163. package/dist/modes/rpc/host-gc.js.map +1 -0
  164. package/dist/modes/rpc/host-generations.d.ts +10 -2
  165. package/dist/modes/rpc/host-generations.d.ts.map +1 -1
  166. package/dist/modes/rpc/host-generations.js +12 -7
  167. package/dist/modes/rpc/host-generations.js.map +1 -1
  168. package/dist/modes/rpc/host-handoff.d.ts +6 -0
  169. package/dist/modes/rpc/host-handoff.d.ts.map +1 -1
  170. package/dist/modes/rpc/host-handoff.js +31 -4
  171. package/dist/modes/rpc/host-handoff.js.map +1 -1
  172. package/dist/modes/rpc/host-lifecycle-policy.d.ts +53 -0
  173. package/dist/modes/rpc/host-lifecycle-policy.d.ts.map +1 -0
  174. package/dist/modes/rpc/host-lifecycle-policy.js +64 -0
  175. package/dist/modes/rpc/host-lifecycle-policy.js.map +1 -0
  176. package/dist/modes/rpc/host-lifecycle.d.ts +1 -44
  177. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  178. package/dist/modes/rpc/host-lifecycle.js +23 -85
  179. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  180. package/dist/modes/rpc/host-observe-request.d.ts +17 -0
  181. package/dist/modes/rpc/host-observe-request.d.ts.map +1 -0
  182. package/dist/modes/rpc/host-observe-request.js +30 -0
  183. package/dist/modes/rpc/host-observe-request.js.map +1 -0
  184. package/dist/modes/rpc/host-probe.d.ts +12 -0
  185. package/dist/modes/rpc/host-probe.d.ts.map +1 -1
  186. package/dist/modes/rpc/host-probe.js +28 -1
  187. package/dist/modes/rpc/host-probe.js.map +1 -1
  188. package/dist/modes/rpc/host-protocol-info.d.ts +2 -0
  189. package/dist/modes/rpc/host-protocol-info.d.ts.map +1 -1
  190. package/dist/modes/rpc/host-protocol-info.js +1 -0
  191. package/dist/modes/rpc/host-protocol-info.js.map +1 -1
  192. package/dist/modes/rpc/host-readiness.d.ts +22 -0
  193. package/dist/modes/rpc/host-readiness.d.ts.map +1 -0
  194. package/dist/modes/rpc/host-readiness.js +44 -0
  195. package/dist/modes/rpc/host-readiness.js.map +1 -0
  196. package/dist/modes/rpc/host-runner.d.ts +13 -0
  197. package/dist/modes/rpc/host-runner.d.ts.map +1 -1
  198. package/dist/modes/rpc/host-runner.js +23 -4
  199. package/dist/modes/rpc/host-runner.js.map +1 -1
  200. package/dist/modes/rpc/host-status-all.d.ts +31 -0
  201. package/dist/modes/rpc/host-status-all.d.ts.map +1 -0
  202. package/dist/modes/rpc/host-status-all.js +78 -0
  203. package/dist/modes/rpc/host-status-all.js.map +1 -0
  204. package/dist/modes/rpc/host-status-rows.d.ts +35 -0
  205. package/dist/modes/rpc/host-status-rows.d.ts.map +1 -0
  206. package/dist/modes/rpc/host-status-rows.js +42 -0
  207. package/dist/modes/rpc/host-status-rows.js.map +1 -0
  208. package/dist/modes/rpc/host-status.d.ts +32 -1
  209. package/dist/modes/rpc/host-status.d.ts.map +1 -1
  210. package/dist/modes/rpc/host-status.js +33 -27
  211. package/dist/modes/rpc/host-status.js.map +1 -1
  212. package/dist/modes/rpc/host-successor.d.ts +3 -1
  213. package/dist/modes/rpc/host-successor.d.ts.map +1 -1
  214. package/dist/modes/rpc/host-successor.js +105 -42
  215. package/dist/modes/rpc/host-successor.js.map +1 -1
  216. package/dist/modes/rpc/host-watchdog.d.ts +6 -0
  217. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  218. package/dist/modes/rpc/host-watchdog.js +12 -13
  219. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  220. package/dist/modes/rpc/loop-lag-watchdog.d.ts +16 -0
  221. package/dist/modes/rpc/loop-lag-watchdog.d.ts.map +1 -1
  222. package/dist/modes/rpc/loop-lag-watchdog.js +34 -2
  223. package/dist/modes/rpc/loop-lag-watchdog.js.map +1 -1
  224. package/dist/modes/rpc/multi-session-host.d.ts +2 -1
  225. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  226. package/dist/modes/rpc/multi-session-host.js +33 -6
  227. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  228. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  229. package/dist/modes/rpc/rpc-mode.js +2 -2
  230. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  231. package/dist/modes/rpc/rpc-types.d.ts +16 -0
  232. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  233. package/dist/modes/rpc/rpc-types.js.map +1 -1
  234. package/dist/modes/rpc/session-command-router.d.ts +9 -1
  235. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  236. package/dist/modes/rpc/session-command-router.js +4 -1
  237. package/dist/modes/rpc/session-command-router.js.map +1 -1
  238. package/dist/modes/rpc/session-path-key.d.ts +3 -1
  239. package/dist/modes/rpc/session-path-key.d.ts.map +1 -1
  240. package/dist/modes/rpc/session-path-key.js +3 -1
  241. package/dist/modes/rpc/session-path-key.js.map +1 -1
  242. package/docs/rpc.md +186 -19
  243. package/docs/settings.md +25 -1
  244. package/docs/usage.md +5 -2
  245. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  246. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  247. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  248. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  249. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts +6 -0
  251. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.d.ts.map +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js +36 -0
  253. package/node_modules/@earendil-works/pi-ai/dist/utils/error-body.js.map +1 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts +1 -0
  255. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  256. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +14 -1
  257. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  258. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  259. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  260. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  261. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  262. package/package.json +7 -7
  263. package/dist/bundle/chunks/chunk-3DEXCYNN.js +0 -135
  264. package/dist/bundle/chunks/chunk-5X4KSPK5.js +0 -12
  265. package/dist/bundle/chunks/chunk-KMD5ZONU.js +0 -5
  266. package/dist/bundle/chunks/chunk-PEHZYGYA.js +0 -8
  267. package/dist/bundle/chunks/chunk-PRC5XIIH.js +0 -2
  268. package/dist/bundle/chunks/chunk-QKY33652.js +0 -18
  269. package/dist/bundle/chunks/interactive-host-runtime-TIFT2SKR.js +0 -4
  270. package/dist/bundle/chunks/multi-session-host-GRHKB7Q2.js +0 -21
  271. package/dist/bundle/chunks/session-picker-FTFCQYHH.js +0 -2
@@ -1,58 +1,14 @@
1
1
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
2
  import { type Api, type Model } from "@earendil-works/pi-ai";
3
- import type { SelectorCooldowns } from "./cooldown.ts";
4
- import type { FallbackLogger } from "./log.ts";
5
- export interface ActiveFallbackState {
6
- chainKey: string;
7
- originalSelector: string;
8
- originalThinkingLevel?: ThinkingLevel;
9
- lastAppliedThinkingLevel?: ThinkingLevel;
10
- /** Pin provenance: refusal contributions release on compaction, billing never. */
11
- pinnedByRefusal: boolean;
12
- pinnedByBilling: boolean;
13
- /** Derived OR of the two provenance flags - the only field consumers read. */
14
- pinned: boolean;
15
- }
16
- type FallbackReason = "transient" | "refusal" | "hard-error" | "billing";
17
- interface FallbackSettings {
18
- modelFallback: boolean;
19
- chains: Readonly<Record<string, readonly string[]>>;
20
- }
21
- export interface RetryFallbackControllerDeps {
22
- getSettings(): FallbackSettings;
23
- registry: {
24
- find(provider: string, id: string): Model<Api> | undefined;
25
- getAll(): Model<Api>[];
26
- /** Ranks bare-selector expansion: OAuth-credential providers come first. */
27
- isUsingOAuth?(model: Model<Api>): boolean;
28
- /** Filters bare-selector expansion: a definitive `false` keeps a lane that can never serve out of the chain. */
29
- isFallbackEligible?(model: Model<Api>): boolean;
30
- };
31
- cooldowns: SelectorCooldowns;
32
- logger: FallbackLogger;
33
- switchModel(model: Model<Api>, thinking: ThinkingLevel, reason: "fallback" | "fallback-revert"): Promise<void>;
34
- emit(event: {
35
- type: "retry_fallback_applied";
36
- from: string;
37
- to: string;
38
- chainKey: string;
39
- reason: FallbackReason;
40
- } | {
41
- type: "retry_fallback_reverted";
42
- from: string;
43
- to: string;
44
- }): void;
45
- getCurrentSelector(): {
46
- model: Model<Api>;
47
- thinkingLevel?: ThinkingLevel;
48
- } | undefined;
49
- isAuthAvailable(provider: string): boolean;
50
- }
3
+ import { CircuitProbes } from "./circuit-probes.ts";
4
+ import type { ActiveFallbackState, CircuitFailure, FallbackReason, RetryFallbackControllerDeps } from "./controller-types.ts";
5
+ export type { ActiveFallbackState, RetryFallbackControllerDeps } from "./controller-types.ts";
51
6
  export declare class RetryFallbackController {
52
7
  private readonly deps;
53
8
  private readonly triedSelectors;
54
9
  private state;
55
10
  private lastExhaustedChainKey;
11
+ readonly probes: CircuitProbes;
56
12
  private canonicalCache;
57
13
  constructor(deps: RetryFallbackControllerDeps);
58
14
  get activeState(): Readonly<ActiveFallbackState> | undefined;
@@ -92,13 +48,29 @@ export declare class RetryFallbackController {
92
48
  notifyCompactionApplied(): boolean;
93
49
  /** A user-driven model change abandons the fallback window entirely. */
94
50
  clearForManualModelChange(model: Model<Api>): void;
51
+ /**
52
+ * Records a failure that makes the entry unusable for every session - billing
53
+ * or quota exhaustion, or a transient failure that spent its budget or failed a
54
+ * half-open probe - independent of whether a fallback candidate remains.
55
+ */
56
+ noteHealthFailure(model: Model<Api>, thinkingLevel: ThinkingLevel | undefined, failure: CircuitFailure): void;
57
+ /**
58
+ * Turn-boundary skip: when the current entry's circuit was opened by this or a
59
+ * sibling session, move to the next closed entry without spending a request on
60
+ * it. With no closed entry left the current one stays and probes, so the chain
61
+ * never refuses a turn; a half-open current entry is claimed for this session.
62
+ */
63
+ rerouteAroundOpenCircuit(): Promise<boolean>;
95
64
  tryFallback(reason: FallbackReason, failure: {
96
65
  errorMessage?: string;
97
66
  retryAfterMs?: number;
98
67
  }): Promise<boolean>;
68
+ private applyCandidate;
69
+ /** The chain governing `current`, when fallback is enabled; a model's own chain wins over an active one. */
70
+ private managedChainKey;
99
71
  private nextCandidate;
72
+ private switchOrRelease;
100
73
  private selectThinking;
101
74
  private skip;
102
75
  }
103
- export {};
104
76
  //# sourceMappingURL=controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,KAAK,GAAG,EAAsB,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAWjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EACX,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,2BAA2B,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAE9F,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,qBAAqB,CAAqB;IAClD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAQ/B,OAAO,CAAC,cAAc,CAAsD;IAE5E,YAAY,IAAI,EAAE,2BAA2B,EAG5C;IAED,IAAI,WAAW,IAAI,QAAQ,CAAC,mBAAmB,CAAC,GAAG,SAAS,CAE3D;IAED,IAAI,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAE1C;IAED,SAAS,IAAI,IAAI,CAGhB;IAED,KAAK,IAAI,IAAI,CAIZ;IAED,OAAO,CAAC,eAAe;IASvB,cAAc,IAAI,OAAO,CAExB;IAED;;;;;OAKG;IACH,kBAAkB,IAAI,OAAO,CAK5B;IAED;;;;;OAKG;IACG,mBAAmB,CAAC,YAAY,EAAE,iBAAiB,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA4BrF;IAED;;;OAGG;IACH,uBAAuB,IAAI,IAAI,CAE9B;IAED;;;;;;;;OAQG;IACH,uBAAuB,IAAI,OAAO,CAajC;IAED,wEAAwE;IACxE,yBAAyB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAQjD;IAED;;;;OAIG;IACH,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,aAAa,GAAG,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAG5G;IAED;;;;;OAKG;IACG,wBAAwB,IAAI,OAAO,CAAC,OAAO,CAAC,CAUjD;IAEK,WAAW,CAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,OAAO,CAAC,CAelB;YAEa,cAAc;IA2B5B,4GAA4G;IAC5G,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;YA2BP,eAAe;IAa7B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,IAAI;CAGZ"}
@@ -1,9 +1,12 @@
1
1
  import { clampThinkingLevel } from "@earendil-works/pi-ai";
2
- import { baseSelector, candidatesAfter, canonicalizeFallbackChains, formatSelector, parseFallbackSelector, resolveChainKey, } from "./chains.js";
2
+ import { firstUsableCandidate } from "./candidates.js";
3
+ import { baseSelector, canonicalizeFallbackChains, formatSelector, parseFallbackSelector, resolveChainKey, } from "./chains.js";
4
+ import { CircuitProbes } from "./circuit-probes.js";
3
5
  export class RetryFallbackController {
4
6
  constructor(deps) {
5
7
  this.triedSelectors = new Set();
6
8
  this.deps = deps;
9
+ this.probes = new CircuitProbes(deps.circuits);
7
10
  }
8
11
  get activeState() {
9
12
  return this.state;
@@ -55,7 +58,12 @@ export class RetryFallbackController {
55
58
  const state = this.state;
56
59
  if (!state || state.pinned || revertPolicy !== "cooldown-expiry")
57
60
  return false;
58
- if (this.deps.cooldowns.isSuppressed(state.originalSelector))
61
+ // An entry the breaker tracks recovers on the circuit's clock; the per-session
62
+ // cooldown only governs entries the breaker never opened (hard errors, breaker off).
63
+ const suppressed = this.probes.governs(state.originalSelector)
64
+ ? this.probes.isOpen(state.originalSelector)
65
+ : this.deps.cooldowns.isSuppressed(state.originalSelector);
66
+ if (suppressed)
59
67
  return false;
60
68
  const selector = parseFallbackSelector(state.originalSelector, this.deps.registry);
61
69
  if (!selector || !this.deps.isAuthAvailable(selector.provider))
@@ -64,13 +72,15 @@ export class RetryFallbackController {
64
72
  const current = this.deps.getCurrentSelector();
65
73
  if (!model || !current)
66
74
  return false;
75
+ if (this.probes.admit(state.originalSelector) === "open")
76
+ return false;
67
77
  // User override wins: only restore the original thinking level when the
68
78
  // current level still equals the level the fallback switch applied. A manual
69
79
  // setThinkingLevel clears lastAppliedThinkingLevel (see noteManualThinkingLevel).
70
80
  const thinking = current.thinkingLevel === state.lastAppliedThinkingLevel
71
81
  ? (state.originalThinkingLevel ?? current.thinkingLevel ?? "off")
72
82
  : (current.thinkingLevel ?? "off");
73
- await this.deps.switchModel(model, thinking, "fallback-revert");
83
+ await this.switchOrRelease(model, thinking, "fallback-revert");
74
84
  const from = formatSelector(current.model);
75
85
  this.state = undefined;
76
86
  this.deps.logger.info("fallback_reverted", { from, to: state.originalSelector });
@@ -117,9 +127,46 @@ export class RetryFallbackController {
117
127
  }
118
128
  this.state = undefined;
119
129
  this.deps.cooldowns.clear(formatSelector(model));
130
+ this.probes.release();
131
+ this.deps.circuits?.close(formatSelector(model));
132
+ }
133
+ /**
134
+ * Records a failure that makes the entry unusable for every session - billing
135
+ * or quota exhaustion, or a transient failure that spent its budget or failed a
136
+ * half-open probe - independent of whether a fallback candidate remains.
137
+ */
138
+ noteHealthFailure(model, thinkingLevel, failure) {
139
+ if (!this.managedChainKey({ model, thinkingLevel }))
140
+ return;
141
+ this.probes.noteFailure(formatSelector(model), failure);
142
+ }
143
+ /**
144
+ * Turn-boundary skip: when the current entry's circuit was opened by this or a
145
+ * sibling session, move to the next closed entry without spending a request on
146
+ * it. With no closed entry left the current one stays and probes, so the chain
147
+ * never refuses a turn; a half-open current entry is claimed for this session.
148
+ */
149
+ async rerouteAroundOpenCircuit() {
150
+ const current = this.deps.getCurrentSelector();
151
+ if (!this.deps.circuits || !current)
152
+ return false;
153
+ const currentBase = formatSelector(current.model);
154
+ if (this.probes.admit(currentBase) !== "open")
155
+ return false;
156
+ const candidate = this.nextCandidate(false, true);
157
+ if (!candidate || candidate.circuitOpen)
158
+ return false;
159
+ this.deps.logger.info("circuit_open_skip", { selector: currentBase });
160
+ await this.applyCandidate(current, candidate, "transient");
161
+ return true;
120
162
  }
121
163
  async tryFallback(reason, failure) {
122
164
  const current = this.deps.getCurrentSelector();
165
+ // Only a transient failure that spent its budget opens the shared circuit
166
+ // here; billing/quota exhaustion is recorded at the failure itself (see
167
+ // noteHealthFailure) so the final chain entry opens too.
168
+ if (current && reason === "transient")
169
+ this.noteHealthFailure(current.model, current.thinkingLevel, failure);
123
170
  const candidate = this.nextCandidate(false, true);
124
171
  if (!current || !candidate)
125
172
  return false;
@@ -128,8 +175,13 @@ export class RetryFallbackController {
128
175
  this.deps.cooldowns.note(currentBase, failure);
129
176
  this.deps.logger.info("cooldown_noted", { selector: currentBase, errorMessage: failure.errorMessage });
130
177
  }
178
+ await this.applyCandidate(current, candidate, reason);
179
+ return true;
180
+ }
181
+ async applyCandidate(current, candidate, reason) {
131
182
  const thinking = this.selectThinking(candidate.selector, candidate.model, current.thinkingLevel);
132
- await this.deps.switchModel(candidate.model, thinking, "fallback");
183
+ this.probes.admit(baseSelector(candidate.selector));
184
+ await this.switchOrRelease(candidate.model, thinking, "fallback");
133
185
  this.triedSelectors.add(baseSelector(candidate.selector));
134
186
  const from = formatSelector(current.model);
135
187
  const to = formatSelector(candidate.model);
@@ -147,60 +199,54 @@ export class RetryFallbackController {
147
199
  };
148
200
  this.deps.logger.info("fallback_applied", { from, to, chainKey: candidate.chainKey, reason });
149
201
  this.deps.emit({ type: "retry_fallback_applied", from, to, chainKey: candidate.chainKey, reason });
150
- return true;
151
202
  }
152
- nextCandidate(reserve = true, logDecision = reserve) {
153
- const settings = this.deps.getSettings();
154
- const current = this.deps.getCurrentSelector();
155
- if (!settings.modelFallback || !current)
203
+ /** The chain governing `current`, when fallback is enabled; a model's own chain wins over an active one. */
204
+ managedChainKey(current) {
205
+ if (!this.deps.getSettings().modelFallback)
156
206
  return undefined;
157
207
  const chains = this.canonicalChains();
158
- // A model's own chain wins; models without an explicitly configured chain
159
- // do not enter an implicit fallback lane.
160
208
  const chainKey = resolveChainKey(current.model, current.thinkingLevel, chains) ?? this.state?.chainKey;
161
- const entries = chainKey ? chains[chainKey] : undefined;
209
+ return chainKey && chains[chainKey] ? chainKey : undefined;
210
+ }
211
+ nextCandidate(reserve = true, logDecision = reserve) {
212
+ const current = this.deps.getCurrentSelector();
213
+ if (!this.deps.getSettings().modelFallback || !current)
214
+ return undefined;
215
+ // Models without an explicitly configured chain do not enter an implicit fallback lane.
216
+ const chainKey = this.managedChainKey(current);
217
+ const entries = chainKey ? this.canonicalChains()[chainKey] : undefined;
162
218
  if (!chainKey || !entries) {
163
219
  if (logDecision)
164
220
  this.deps.logger.debug("no_chain", { selector: formatSelector(current.model) });
165
221
  return undefined;
166
222
  }
167
- for (const raw of candidatesAfter(entries, formatSelector(current.model, current.thinkingLevel))) {
168
- const selector = parseFallbackSelector(raw, this.deps.registry);
169
- if (!selector) {
170
- this.skip(raw, "unknown");
171
- continue;
172
- }
173
- if (selector.provider === current.model.provider && selector.id === current.model.id) {
174
- this.skip(raw, "self");
175
- continue;
176
- }
177
- const base = baseSelector(selector);
178
- if (this.triedSelectors.has(base)) {
179
- this.skip(raw, "tried");
180
- continue;
181
- }
182
- if (this.deps.cooldowns.isSuppressed(base)) {
183
- this.skip(raw, "suppressed");
184
- continue;
185
- }
186
- if (!this.deps.isAuthAvailable(selector.provider)) {
187
- this.skip(raw, "unauthenticated");
188
- continue;
189
- }
190
- const model = this.deps.registry.find(selector.provider, selector.id);
191
- if (!model) {
192
- this.skip(raw, "unknown");
193
- continue;
194
- }
223
+ const candidate = firstUsableCandidate(entries, current, {
224
+ registry: this.deps.registry,
225
+ tried: this.triedSelectors,
226
+ isSuppressed: (base) => this.deps.cooldowns.isSuppressed(base),
227
+ isAuthAvailable: (provider) => this.deps.isAuthAvailable(provider),
228
+ isCircuitOpen: (base) => this.deps.circuits?.isOpen(base) ?? false,
229
+ skip: (raw, skipReason) => this.skip(raw, skipReason),
230
+ });
231
+ if (candidate) {
195
232
  if (reserve)
196
- this.triedSelectors.add(base);
197
- return { chainKey, selector, model };
233
+ this.triedSelectors.add(baseSelector(candidate.selector));
234
+ return { chainKey, ...candidate };
198
235
  }
199
236
  this.lastExhaustedChainKey = chainKey;
200
237
  if (logDecision)
201
238
  this.deps.logger.info("candidates_exhausted", { chainKey });
202
239
  return undefined;
203
240
  }
241
+ async switchOrRelease(model, thinking, reason) {
242
+ try {
243
+ await this.deps.switchModel(model, thinking, reason);
244
+ }
245
+ catch (error) {
246
+ this.probes.release();
247
+ throw error;
248
+ }
249
+ }
204
250
  selectThinking(selector, model, inherited) {
205
251
  const requested = selector.thinkingLevel ?? inherited ?? "off";
206
252
  // Canonical clamp walks to the NEAREST supported level. Picking the highest
@@ -1 +1 @@
1
- {"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,kBAAkB,EAAc,MAAM,uBAAuB,CAAC;AACjF,OAAO,EACN,YAAY,EACZ,eAAe,EACf,0BAA0B,EAG1B,cAAc,EACd,qBAAqB,EACrB,eAAe,GACf,MAAM,aAAa,CAAC;AAmDrB,MAAM,OAAO,uBAAuB;IAcnC,YAAY,IAAiC;QAZ5B,mBAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QAanD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAED,IAAI,iBAAiB;QACpB,OAAO,IAAI,CAAC,qBAAqB,CAAC;IACnC,CAAC;IAED,SAAS;QACR,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;IACxC,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAChC,IAAI,CAAC,SAAS,EAAE,CAAC;IAClB,CAAC;IAEO,eAAe;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,cAAc,EAAE,GAAG,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;QACxE,MAAM,SAAS,GAAG,0BAA0B,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzE,IAAI,CAAC,cAAc,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,cAAc;QACb,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,kBAAkB;QACjB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,OAAO,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,KAAK,SAAS,CAAC;IACpF,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,mBAAmB,CAAC,YAAyC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,YAAY,KAAK,iBAAiB;YAAE,OAAO,KAAK,CAAC;QAC/E,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,gBAAgB,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3E,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnF,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QACrC,wEAAwE;QACxE,6EAA6E;QAC7E,kFAAkF;QAClF,MAAM,QAAQ,GACb,OAAO,CAAC,aAAa,KAAK,KAAK,CAAC,wBAAwB;YACvD,CAAC,CAAC,CAAC,KAAK,CAAC,qBAAqB,IAAI,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC;YACjE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAChE,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,uBAAuB;QACtB,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,CAAC,wBAAwB,GAAG,SAAS,CAAC;IACjE,CAAC;IAED;;;;;;;;OAQG;IACH,uBAAuB;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC;QAC9B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;QAC/B,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC;QACrC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE;YAC7C,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,OAAO,EAAE,YAAY;SACrB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACb,CAAC;IAED,wEAAwE;IACxE,yBAAyB,CAAC,KAAiB;QAC1C,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,WAAW,CAChB,MAAsB,EACtB,OAAyD;QAEzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC/E,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QACjG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACnE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,eAAe,GAAG,KAAK,EAAE,eAAe,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,CAAC;QAChF,MAAM,eAAe,GAAG,KAAK,EAAE,eAAe,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,CAAC;QAChF,IAAI,CAAC,KAAK,GAAG;YACZ,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,IAAI,IAAI;YACjD,qBAAqB,EAAE,KAAK,EAAE,qBAAqB,IAAI,OAAO,CAAC,aAAa;YAC5E,wBAAwB,EAAE,QAAQ;YAClC,eAAe;YACf,eAAe;YACf,MAAM,EAAE,eAAe,IAAI,eAAe;SAC1C,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACnG,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,aAAa,CACpB,OAAO,GAAG,IAAI,EACd,WAAW,GAAG,OAAO;QAErB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,aAAa,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,0EAA0E;QAC1E,0CAA0C;QAC1C,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC;QACvG,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACxD,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3B,IAAI,WAAW;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACjG,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAClG,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC1B,SAAS;YACV,CAAC;YACD,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBACtF,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACvB,SAAS;YACV,CAAC;YACD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACxB,SAAS;YACV,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBAC7B,SAAS;YACV,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;gBAClC,SAAS;YACV,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACZ,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC1B,SAAS;YACV,CAAC;YACD,IAAI,OAAO;gBAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,qBAAqB,GAAG,QAAQ,CAAC;QACtC,IAAI,WAAW;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7E,OAAO,SAAS,CAAC;IAClB,CAAC;IAEO,cAAc,CACrB,QAA0B,EAC1B,KAAiB,EACjB,SAAoC;QAEpC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,IAAI,SAAS,IAAI,KAAK,CAAC;QAC/D,4EAA4E;QAC5E,8EAA8E;QAC9E,6BAA6B;QAC7B,OAAO,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC7C,CAAC;IAEO,IAAI,CAAC,SAAiB,EAAE,UAAkB;QACjD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IACxE,CAAC;CACD","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, clampThinkingLevel, type Model } from \"@earendil-works/pi-ai\";\nimport {\n\tbaseSelector,\n\tcandidatesAfter,\n\tcanonicalizeFallbackChains,\n\ttype FallbackChains,\n\ttype FallbackSelector,\n\tformatSelector,\n\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\t/** Pin provenance: refusal contributions release on compaction, billing never. */\n\tpinnedByRefusal: boolean;\n\tpinnedByBilling: boolean;\n\t/** Derived OR of the two provenance flags - the only field consumers read. */\n\tpinned: boolean;\n}\n\ntype FallbackReason = \"transient\" | \"refusal\" | \"hard-error\" | \"billing\";\n\ninterface FallbackSettings {\n\tmodelFallback: boolean;\n\tchains: Readonly<Record<string, readonly string[]>>;\n}\n\nexport interface RetryFallbackControllerDeps {\n\tgetSettings(): FallbackSettings;\n\tregistry: {\n\t\tfind(provider: string, id: string): Model<Api> | undefined;\n\t\tgetAll(): Model<Api>[];\n\t\t/** Ranks bare-selector expansion: OAuth-credential providers come first. */\n\t\tisUsingOAuth?(model: Model<Api>): boolean;\n\t\t/** Filters bare-selector expansion: a definitive `false` keeps a lane that can never serve out of the chain. */\n\t\tisFallbackEligible?(model: Model<Api>): boolean;\n\t};\n\tcooldowns: SelectorCooldowns;\n\tlogger: FallbackLogger;\n\tswitchModel(model: Model<Api>, thinking: ThinkingLevel, reason: \"fallback\" | \"fallback-revert\"): Promise<void>;\n\temit(\n\t\tevent:\n\t\t\t| {\n\t\t\t\t\ttype: \"retry_fallback_applied\";\n\t\t\t\t\tfrom: string;\n\t\t\t\t\tto: string;\n\t\t\t\t\tchainKey: string;\n\t\t\t\t\treason: FallbackReason;\n\t\t\t }\n\t\t\t| { type: \"retry_fallback_reverted\"; from: string; to: string },\n\t): void;\n\tgetCurrentSelector(): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined;\n\tisAuthAvailable(provider: string): boolean;\n}\n\nexport class RetryFallbackController {\n\tprivate readonly deps: RetryFallbackControllerDeps;\n\tprivate readonly triedSelectors = new Set<string>();\n\tprivate state: ActiveFallbackState | undefined;\n\tprivate lastExhaustedChainKey: string | undefined;\n\t// Content-keyed memo of canonicalizeFallbackChains. Provider-error handling calls\n\t// canTryFallback/nextCandidate several times per error; without this each call\n\t// re-expands bare selectors and re-probes registry eligibility over the full\n\t// model set. Keying on the serialized chains content means an unchanged config\n\t// reuses the canonical result, while a chains edit invalidates immediately.\n\t// Registry mutations without a chains change are not tracked here; they are rare\n\t// and in practice coincide with a settings reload that replaces the chains object.\n\tprivate canonicalCache: { key: string; chains: FallbackChains } | undefined;\n\n\tconstructor(deps: RetryFallbackControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tget activeState(): Readonly<ActiveFallbackState> | undefined {\n\t\treturn this.state;\n\t}\n\n\tget exhaustedChainKey(): string | undefined {\n\t\treturn this.lastExhaustedChainKey;\n\t}\n\n\tresetTurn(): void {\n\t\tthis.triedSelectors.clear();\n\t\tthis.lastExhaustedChainKey = undefined;\n\t}\n\n\tclear(): void {\n\t\tthis.state = undefined;\n\t\tthis.canonicalCache = undefined;\n\t\tthis.resetTurn();\n\t}\n\n\tprivate canonicalChains(): FallbackChains {\n\t\tconst chains = this.deps.getSettings().chains;\n\t\tconst key = JSON.stringify(chains);\n\t\tif (this.canonicalCache?.key === key) return this.canonicalCache.chains;\n\t\tconst canonical = canonicalizeFallbackChains(chains, this.deps.registry);\n\t\tthis.canonicalCache = { key, chains: canonical };\n\t\treturn canonical;\n\t}\n\n\tcanTryFallback(): boolean {\n\t\treturn this.nextCandidate(false) !== undefined;\n\t}\n\n\t/**\n\t * Whether a chain exists for the current model at all, regardless of whether\n\t * a candidate is usable right now. `canTryFallback()` answers the latter and\n\t * goes false on cooldown or exhaustion, so it cannot tell a UI \"you never\n\t * configured a chain\" apart from \"the chain is spent\".\n\t */\n\thasConfiguredChain(): boolean {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!current) return false;\n\t\tconst chains = this.canonicalChains();\n\t\treturn resolveChainKey(current.model, current.thinkingLevel, chains) !== undefined;\n\t}\n\n\t/**\n\t * Revert to the chain's original model at a turn boundary. Only fires for\n\t * unpinned state under the cooldown-expiry policy once the original selector\n\t * is no longer suppressed and is still usable; pinned (refusal) state and the\n\t * \"never\" policy always hold the fallback model.\n\t */\n\tasync maybeRestorePrimary(revertPolicy: \"cooldown-expiry\" | \"never\"): Promise<boolean> {\n\t\tconst state = this.state;\n\t\tif (!state || state.pinned || revertPolicy !== \"cooldown-expiry\") return false;\n\t\tif (this.deps.cooldowns.isSuppressed(state.originalSelector)) return false;\n\t\tconst selector = parseFallbackSelector(state.originalSelector, this.deps.registry);\n\t\tif (!selector || !this.deps.isAuthAvailable(selector.provider)) return false;\n\t\tconst model = this.deps.registry.find(selector.provider, selector.id);\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!model || !current) return false;\n\t\t// User override wins: only restore the original thinking level when the\n\t\t// current level still equals the level the fallback switch applied. A manual\n\t\t// setThinkingLevel clears lastAppliedThinkingLevel (see noteManualThinkingLevel).\n\t\tconst thinking =\n\t\t\tcurrent.thinkingLevel === state.lastAppliedThinkingLevel\n\t\t\t\t? (state.originalThinkingLevel ?? current.thinkingLevel ?? \"off\")\n\t\t\t\t: (current.thinkingLevel ?? \"off\");\n\t\tawait this.deps.switchModel(model, thinking, \"fallback-revert\");\n\t\tconst from = formatSelector(current.model);\n\t\tthis.state = undefined;\n\t\tthis.deps.logger.info(\"fallback_reverted\", { from, to: state.originalSelector });\n\t\tthis.deps.emit({ type: \"retry_fallback_reverted\", from, to: state.originalSelector });\n\t\treturn true;\n\t}\n\n\t/**\n\t * A user-driven setThinkingLevel makes the current level a deliberate choice,\n\t * so the revert restore-rule must no longer treat it as fallback-applied.\n\t */\n\tnoteManualThinkingLevel(): void {\n\t\tif (this.state) this.state.lastAppliedThinkingLevel = undefined;\n\t}\n\n\t/**\n\t * A senpi-owned compaction successfully rewrote the conversation context, the\n\t * one safe moment to re-attempt a refusal-pinned fallback's original model:\n\t * the refusal assumption (\"the same context refuses again\") no longer holds.\n\t * Refusal contributions clear; billing contributions never release - retrying\n\t * the same account never recovers it. Returns true only when the overall pin\n\t * transitioned true -> false, i.e. the caller may now restore the primary via\n\t * the existing maybeRestorePrimary gate.\n\t */\n\tnotifyCompactionApplied(): boolean {\n\t\tconst state = this.state;\n\t\tif (!state) return false;\n\t\tstate.pinnedByRefusal = false;\n\t\tconst wasPinned = state.pinned;\n\t\tstate.pinned = state.pinnedByBilling;\n\t\tif (!wasPinned || state.pinned) return false;\n\t\tthis.deps.logger.info(\"refusal_pin_released\", {\n\t\t\tchainKey: state.chainKey,\n\t\t\toriginalSelector: state.originalSelector,\n\t\t\ttrigger: \"compaction\",\n\t\t});\n\t\treturn true;\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(false, true);\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\tthis.triedSelectors.add(baseSelector(candidate.selector));\n\t\tconst from = formatSelector(current.model);\n\t\tconst to = formatSelector(candidate.model);\n\t\tconst prior = this.state;\n\t\tconst pinnedByRefusal = prior?.pinnedByRefusal === true || reason === \"refusal\";\n\t\tconst pinnedByBilling = prior?.pinnedByBilling === true || reason === \"billing\";\n\t\tthis.state = {\n\t\t\tchainKey: candidate.chainKey,\n\t\t\toriginalSelector: prior?.originalSelector ?? from,\n\t\t\toriginalThinkingLevel: prior?.originalThinkingLevel ?? current.thinkingLevel,\n\t\t\tlastAppliedThinkingLevel: thinking,\n\t\t\tpinnedByRefusal,\n\t\t\tpinnedByBilling,\n\t\t\tpinned: pinnedByRefusal || pinnedByBilling,\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\tlogDecision = reserve,\n\t): { chainKey: string; selector: FallbackSelector; model: Model<Api> } | undefined {\n\t\tconst settings = this.deps.getSettings();\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!settings.modelFallback || !current) return undefined;\n\t\tconst chains = this.canonicalChains();\n\t\t// A model's own chain wins; models without an explicitly configured chain\n\t\t// do not enter an implicit fallback lane.\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 (logDecision) 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 (logDecision) this.deps.logger.info(\"candidates_exhausted\", { chainKey });\n\t\treturn undefined;\n\t}\n\n\tprivate selectThinking(\n\t\tselector: FallbackSelector,\n\t\tmodel: Model<Api>,\n\t\tinherited: ThinkingLevel | undefined,\n\t): ThinkingLevel {\n\t\tconst requested = selector.thinkingLevel ?? inherited ?? \"off\";\n\t\t// Canonical clamp walks to the NEAREST supported level. Picking the highest\n\t\t// supported level instead would escalate an \"off\" request to max reasoning on\n\t\t// always-on fallback models.\n\t\treturn clampThinkingLevel(model, requested);\n\t}\n\n\tprivate skip(candidate: string, skipReason: string): void {\n\t\tthis.deps.logger.debug(\"candidate_skipped\", { candidate, skipReason });\n\t}\n}\n"]}
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, clampThinkingLevel, type Model } from \"@earendil-works/pi-ai\";\nimport { firstUsableCandidate, type UsableCandidate } from \"./candidates.ts\";\nimport {\n\tbaseSelector,\n\tcanonicalizeFallbackChains,\n\ttype FallbackChains,\n\ttype FallbackSelector,\n\tformatSelector,\n\tparseFallbackSelector,\n\tresolveChainKey,\n} from \"./chains.ts\";\nimport { CircuitProbes } from \"./circuit-probes.ts\";\nimport type {\n\tActiveFallbackState,\n\tCircuitFailure,\n\tFallbackReason,\n\tRetryFallbackControllerDeps,\n} from \"./controller-types.ts\";\n\nexport type { ActiveFallbackState, RetryFallbackControllerDeps } from \"./controller-types.ts\";\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\treadonly probes: CircuitProbes;\n\t// Content-keyed memo of canonicalizeFallbackChains. Provider-error handling calls\n\t// canTryFallback/nextCandidate several times per error; without this each call\n\t// re-expands bare selectors and re-probes registry eligibility over the full\n\t// model set. Keying on the serialized chains content means an unchanged config\n\t// reuses the canonical result, while a chains edit invalidates immediately.\n\t// Registry mutations without a chains change are not tracked here; they are rare\n\t// and in practice coincide with a settings reload that replaces the chains object.\n\tprivate canonicalCache: { key: string; chains: FallbackChains } | undefined;\n\n\tconstructor(deps: RetryFallbackControllerDeps) {\n\t\tthis.deps = deps;\n\t\tthis.probes = new CircuitProbes(deps.circuits);\n\t}\n\n\tget activeState(): Readonly<ActiveFallbackState> | undefined {\n\t\treturn this.state;\n\t}\n\n\tget exhaustedChainKey(): string | undefined {\n\t\treturn this.lastExhaustedChainKey;\n\t}\n\n\tresetTurn(): void {\n\t\tthis.triedSelectors.clear();\n\t\tthis.lastExhaustedChainKey = undefined;\n\t}\n\n\tclear(): void {\n\t\tthis.state = undefined;\n\t\tthis.canonicalCache = undefined;\n\t\tthis.resetTurn();\n\t}\n\n\tprivate canonicalChains(): FallbackChains {\n\t\tconst chains = this.deps.getSettings().chains;\n\t\tconst key = JSON.stringify(chains);\n\t\tif (this.canonicalCache?.key === key) return this.canonicalCache.chains;\n\t\tconst canonical = canonicalizeFallbackChains(chains, this.deps.registry);\n\t\tthis.canonicalCache = { key, chains: canonical };\n\t\treturn canonical;\n\t}\n\n\tcanTryFallback(): boolean {\n\t\treturn this.nextCandidate(false) !== undefined;\n\t}\n\n\t/**\n\t * Whether a chain exists for the current model at all, regardless of whether\n\t * a candidate is usable right now. `canTryFallback()` answers the latter and\n\t * goes false on cooldown or exhaustion, so it cannot tell a UI \"you never\n\t * configured a chain\" apart from \"the chain is spent\".\n\t */\n\thasConfiguredChain(): boolean {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!current) return false;\n\t\tconst chains = this.canonicalChains();\n\t\treturn resolveChainKey(current.model, current.thinkingLevel, chains) !== undefined;\n\t}\n\n\t/**\n\t * Revert to the chain's original model at a turn boundary. Only fires for\n\t * unpinned state under the cooldown-expiry policy once the original selector\n\t * is no longer suppressed and is still usable; pinned (refusal) state and the\n\t * \"never\" policy always hold the fallback model.\n\t */\n\tasync maybeRestorePrimary(revertPolicy: \"cooldown-expiry\" | \"never\"): Promise<boolean> {\n\t\tconst state = this.state;\n\t\tif (!state || state.pinned || revertPolicy !== \"cooldown-expiry\") return false;\n\t\t// An entry the breaker tracks recovers on the circuit's clock; the per-session\n\t\t// cooldown only governs entries the breaker never opened (hard errors, breaker off).\n\t\tconst suppressed = this.probes.governs(state.originalSelector)\n\t\t\t? this.probes.isOpen(state.originalSelector)\n\t\t\t: this.deps.cooldowns.isSuppressed(state.originalSelector);\n\t\tif (suppressed) 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\tif (this.probes.admit(state.originalSelector) === \"open\") 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.switchOrRelease(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/**\n\t * A senpi-owned compaction successfully rewrote the conversation context, the\n\t * one safe moment to re-attempt a refusal-pinned fallback's original model:\n\t * the refusal assumption (\"the same context refuses again\") no longer holds.\n\t * Refusal contributions clear; billing contributions never release - retrying\n\t * the same account never recovers it. Returns true only when the overall pin\n\t * transitioned true -> false, i.e. the caller may now restore the primary via\n\t * the existing maybeRestorePrimary gate.\n\t */\n\tnotifyCompactionApplied(): boolean {\n\t\tconst state = this.state;\n\t\tif (!state) return false;\n\t\tstate.pinnedByRefusal = false;\n\t\tconst wasPinned = state.pinned;\n\t\tstate.pinned = state.pinnedByBilling;\n\t\tif (!wasPinned || state.pinned) return false;\n\t\tthis.deps.logger.info(\"refusal_pin_released\", {\n\t\t\tchainKey: state.chainKey,\n\t\t\toriginalSelector: state.originalSelector,\n\t\t\ttrigger: \"compaction\",\n\t\t});\n\t\treturn true;\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\tthis.probes.release();\n\t\tthis.deps.circuits?.close(formatSelector(model));\n\t}\n\n\t/**\n\t * Records a failure that makes the entry unusable for every session - billing\n\t * or quota exhaustion, or a transient failure that spent its budget or failed a\n\t * half-open probe - independent of whether a fallback candidate remains.\n\t */\n\tnoteHealthFailure(model: Model<Api>, thinkingLevel: ThinkingLevel | undefined, failure: CircuitFailure): void {\n\t\tif (!this.managedChainKey({ model, thinkingLevel })) return;\n\t\tthis.probes.noteFailure(formatSelector(model), failure);\n\t}\n\n\t/**\n\t * Turn-boundary skip: when the current entry's circuit was opened by this or a\n\t * sibling session, move to the next closed entry without spending a request on\n\t * it. With no closed entry left the current one stays and probes, so the chain\n\t * never refuses a turn; a half-open current entry is claimed for this session.\n\t */\n\tasync rerouteAroundOpenCircuit(): Promise<boolean> {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!this.deps.circuits || !current) return false;\n\t\tconst currentBase = formatSelector(current.model);\n\t\tif (this.probes.admit(currentBase) !== \"open\") return false;\n\t\tconst candidate = this.nextCandidate(false, true);\n\t\tif (!candidate || candidate.circuitOpen) return false;\n\t\tthis.deps.logger.info(\"circuit_open_skip\", { selector: currentBase });\n\t\tawait this.applyCandidate(current, candidate, \"transient\");\n\t\treturn true;\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\t// Only a transient failure that spent its budget opens the shared circuit\n\t\t// here; billing/quota exhaustion is recorded at the failure itself (see\n\t\t// noteHealthFailure) so the final chain entry opens too.\n\t\tif (current && reason === \"transient\") this.noteHealthFailure(current.model, current.thinkingLevel, failure);\n\t\tconst candidate = this.nextCandidate(false, true);\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\t\tawait this.applyCandidate(current, candidate, reason);\n\t\treturn true;\n\t}\n\n\tprivate async applyCandidate(\n\t\tcurrent: { model: Model<Api>; thinkingLevel?: ThinkingLevel },\n\t\tcandidate: { chainKey: string } & UsableCandidate,\n\t\treason: FallbackReason,\n\t): Promise<void> {\n\t\tconst thinking = this.selectThinking(candidate.selector, candidate.model, current.thinkingLevel);\n\t\tthis.probes.admit(baseSelector(candidate.selector));\n\t\tawait this.switchOrRelease(candidate.model, thinking, \"fallback\");\n\t\tthis.triedSelectors.add(baseSelector(candidate.selector));\n\t\tconst from = formatSelector(current.model);\n\t\tconst to = formatSelector(candidate.model);\n\t\tconst prior = this.state;\n\t\tconst pinnedByRefusal = prior?.pinnedByRefusal === true || reason === \"refusal\";\n\t\tconst pinnedByBilling = prior?.pinnedByBilling === true || reason === \"billing\";\n\t\tthis.state = {\n\t\t\tchainKey: candidate.chainKey,\n\t\t\toriginalSelector: prior?.originalSelector ?? from,\n\t\t\toriginalThinkingLevel: prior?.originalThinkingLevel ?? current.thinkingLevel,\n\t\t\tlastAppliedThinkingLevel: thinking,\n\t\t\tpinnedByRefusal,\n\t\t\tpinnedByBilling,\n\t\t\tpinned: pinnedByRefusal || pinnedByBilling,\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}\n\n\t/** The chain governing `current`, when fallback is enabled; a model's own chain wins over an active one. */\n\tprivate managedChainKey(current: { model: Model<Api>; thinkingLevel?: ThinkingLevel }): string | undefined {\n\t\tif (!this.deps.getSettings().modelFallback) return undefined;\n\t\tconst chains = this.canonicalChains();\n\t\tconst chainKey = resolveChainKey(current.model, current.thinkingLevel, chains) ?? this.state?.chainKey;\n\t\treturn chainKey && chains[chainKey] ? chainKey : undefined;\n\t}\n\n\tprivate nextCandidate(reserve = true, logDecision = reserve): ({ chainKey: string } & UsableCandidate) | undefined {\n\t\tconst current = this.deps.getCurrentSelector();\n\t\tif (!this.deps.getSettings().modelFallback || !current) return undefined;\n\t\t// Models without an explicitly configured chain do not enter an implicit fallback lane.\n\t\tconst chainKey = this.managedChainKey(current);\n\t\tconst entries = chainKey ? this.canonicalChains()[chainKey] : undefined;\n\t\tif (!chainKey || !entries) {\n\t\t\tif (logDecision) this.deps.logger.debug(\"no_chain\", { selector: formatSelector(current.model) });\n\t\t\treturn undefined;\n\t\t}\n\t\tconst candidate = firstUsableCandidate(entries, current, {\n\t\t\tregistry: this.deps.registry,\n\t\t\ttried: this.triedSelectors,\n\t\t\tisSuppressed: (base) => this.deps.cooldowns.isSuppressed(base),\n\t\t\tisAuthAvailable: (provider) => this.deps.isAuthAvailable(provider),\n\t\t\tisCircuitOpen: (base) => this.deps.circuits?.isOpen(base) ?? false,\n\t\t\tskip: (raw, skipReason) => this.skip(raw, skipReason),\n\t\t});\n\t\tif (candidate) {\n\t\t\tif (reserve) this.triedSelectors.add(baseSelector(candidate.selector));\n\t\t\treturn { chainKey, ...candidate };\n\t\t}\n\t\tthis.lastExhaustedChainKey = chainKey;\n\t\tif (logDecision) this.deps.logger.info(\"candidates_exhausted\", { chainKey });\n\t\treturn undefined;\n\t}\n\n\tprivate async switchOrRelease(\n\t\tmodel: Model<Api>,\n\t\tthinking: ThinkingLevel,\n\t\treason: \"fallback\" | \"fallback-revert\",\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait this.deps.switchModel(model, thinking, reason);\n\t\t} catch (error) {\n\t\t\tthis.probes.release();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate selectThinking(\n\t\tselector: FallbackSelector,\n\t\tmodel: Model<Api>,\n\t\tinherited: ThinkingLevel | undefined,\n\t): ThinkingLevel {\n\t\tconst requested = selector.thinkingLevel ?? inherited ?? \"off\";\n\t\t// Canonical clamp walks to the NEAREST supported level. Picking the highest\n\t\t// supported level instead would escalate an \"off\" request to max reasoning on\n\t\t// always-on fallback models.\n\t\treturn clampThinkingLevel(model, requested);\n\t}\n\n\tprivate skip(candidate: string, skipReason: string): void {\n\t\tthis.deps.logger.debug(\"candidate_skipped\", { candidate, skipReason });\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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"}
package/dist/core/sdk.js CHANGED
@@ -377,27 +377,35 @@ export async function createAgentSession(options = {}) {
377
377
  ? existingSession.messages.reduce((total, message) => total + estimateTokens(message), 0)
378
378
  : 0;
379
379
  try {
380
- session.assertModelUsable(undefined, liveContextTokens, hasExistingSession ? { includeSpeculationLead: false, admission: "resume" } : { admission: "start" });
381
- }
382
- catch (error) {
383
- if (!hasExistingSession ||
384
- !(error instanceof ModelUsabilityBudgetError) ||
385
- !session.settingsManager.getCompactionEnabled()) {
386
- throw error;
380
+ try {
381
+ session.assertModelUsable(undefined, liveContextTokens, hasExistingSession ? { includeSpeculationLead: false, admission: "resume" } : { admission: "start" });
387
382
  }
388
- if (error.projection.liveContextTokens > error.projection.contextWindow) {
389
- const plan = planResumeSlice({
390
- entries: session.sessionManager.getBranch(),
391
- projection: error.projection,
392
- });
393
- if (!plan)
383
+ catch (error) {
384
+ if (!hasExistingSession ||
385
+ !(error instanceof ModelUsabilityBudgetError) ||
386
+ !session.settingsManager.getCompactionEnabled()) {
394
387
  throw error;
395
- session.applyResumeSlice(plan);
396
- }
397
- else {
398
- session.admitResumeCompactionRequired(error.projection);
388
+ }
389
+ if (error.projection.liveContextTokens > error.projection.contextWindow) {
390
+ const plan = planResumeSlice({
391
+ entries: session.sessionManager.getBranch(),
392
+ projection: error.projection,
393
+ });
394
+ if (!plan)
395
+ throw error;
396
+ session.applyResumeSlice(plan);
397
+ }
398
+ else {
399
+ session.admitResumeCompactionRequired(error.projection);
400
+ }
399
401
  }
400
402
  }
403
+ catch (error) {
404
+ // A refused startup returns no session to its caller, so nothing else would ever
405
+ // release what the constructed session holds (its shared fallback breaker, writer).
406
+ session.dispose();
407
+ throw error;
408
+ }
401
409
  sessionRef.current = session;
402
410
  const extensionsResult = resourceLoader.getExtensions();
403
411
  return {