@code-yeongyu/senpi 2026.7.20 → 2026.7.22

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 (495) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +32 -0
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +409 -68
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/auth-storage.js +1 -1
  11. package/dist/core/auth-storage.js.map +1 -1
  12. package/dist/core/compaction/compaction.d.ts +2 -2
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +67 -16
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/compaction/stream-watchdog.d.ts +31 -0
  17. package/dist/core/compaction/stream-watchdog.d.ts.map +1 -0
  18. package/dist/core/compaction/stream-watchdog.js +78 -0
  19. package/dist/core/compaction/stream-watchdog.js.map +1 -0
  20. package/dist/core/extensions/builtin/btw/index.d.ts +3 -0
  21. package/dist/core/extensions/builtin/btw/index.d.ts.map +1 -0
  22. package/dist/core/extensions/builtin/btw/index.js +120 -0
  23. package/dist/core/extensions/builtin/btw/index.js.map +1 -0
  24. package/dist/core/extensions/builtin/btw/panel.d.ts +21 -0
  25. package/dist/core/extensions/builtin/btw/panel.d.ts.map +1 -0
  26. package/dist/core/extensions/builtin/btw/panel.js +61 -0
  27. package/dist/core/extensions/builtin/btw/panel.js.map +1 -0
  28. package/dist/core/extensions/builtin/btw/side-query.d.ts +31 -0
  29. package/dist/core/extensions/builtin/btw/side-query.d.ts.map +1 -0
  30. package/dist/core/extensions/builtin/btw/side-query.js +68 -0
  31. package/dist/core/extensions/builtin/btw/side-query.js.map +1 -0
  32. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/compaction/index.js +47 -11
  34. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  35. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/openai-remote.js +3 -1
  37. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/speculative.d.ts +17 -0
  39. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/speculative.js +93 -26
  41. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  42. package/dist/core/extensions/builtin/config-reload/index.d.ts +39 -0
  43. package/dist/core/extensions/builtin/config-reload/index.d.ts.map +1 -0
  44. package/dist/core/extensions/builtin/config-reload/index.js +659 -0
  45. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -0
  46. package/dist/core/extensions/builtin/config-reload/log.d.ts +55 -0
  47. package/dist/core/extensions/builtin/config-reload/log.d.ts.map +1 -0
  48. package/dist/core/extensions/builtin/config-reload/log.js +128 -0
  49. package/dist/core/extensions/builtin/config-reload/log.js.map +1 -0
  50. package/dist/core/extensions/builtin/config-reload/protocol.d.ts +71 -0
  51. package/dist/core/extensions/builtin/config-reload/protocol.d.ts.map +1 -0
  52. package/dist/core/extensions/builtin/config-reload/protocol.js +83 -0
  53. package/dist/core/extensions/builtin/config-reload/protocol.js.map +1 -0
  54. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts +43 -0
  55. package/dist/core/extensions/builtin/config-reload/watch-engine.d.ts.map +1 -0
  56. package/dist/core/extensions/builtin/config-reload/watch-engine.js +298 -0
  57. package/dist/core/extensions/builtin/config-reload/watch-engine.js.map +1 -0
  58. package/dist/core/extensions/builtin/goal/command-registration.d.ts +2 -2
  59. package/dist/core/extensions/builtin/goal/command-registration.d.ts.map +1 -1
  60. package/dist/core/extensions/builtin/goal/command-registration.js +3 -4
  61. package/dist/core/extensions/builtin/goal/command-registration.js.map +1 -1
  62. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  63. package/dist/core/extensions/builtin/goal/index.js +11 -27
  64. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  65. package/dist/core/extensions/builtin/goal/tool-registration.d.ts +2 -2
  66. package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
  67. package/dist/core/extensions/builtin/goal/tool-registration.js +2 -3
  68. package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
  69. package/dist/core/extensions/builtin/goal/turn-usage.d.ts +20 -0
  70. package/dist/core/extensions/builtin/goal/turn-usage.d.ts.map +1 -0
  71. package/dist/core/extensions/builtin/goal/turn-usage.js +61 -0
  72. package/dist/core/extensions/builtin/goal/turn-usage.js.map +1 -0
  73. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  74. package/dist/core/extensions/builtin/index.js +9 -0
  75. package/dist/core/extensions/builtin/index.js.map +1 -1
  76. package/dist/core/extensions/builtin/look-at/arguments.d.ts +27 -0
  77. package/dist/core/extensions/builtin/look-at/arguments.d.ts.map +1 -0
  78. package/dist/core/extensions/builtin/look-at/arguments.js +109 -0
  79. package/dist/core/extensions/builtin/look-at/arguments.js.map +1 -0
  80. package/dist/core/extensions/builtin/look-at/commands.d.ts +9 -0
  81. package/dist/core/extensions/builtin/look-at/commands.d.ts.map +1 -0
  82. package/dist/core/extensions/builtin/look-at/commands.js +102 -0
  83. package/dist/core/extensions/builtin/look-at/commands.js.map +1 -0
  84. package/dist/core/extensions/builtin/look-at/image-input.d.ts +21 -0
  85. package/dist/core/extensions/builtin/look-at/image-input.d.ts.map +1 -0
  86. package/dist/core/extensions/builtin/look-at/image-input.js +151 -0
  87. package/dist/core/extensions/builtin/look-at/image-input.js.map +1 -0
  88. package/dist/core/extensions/builtin/look-at/index.d.ts +3 -0
  89. package/dist/core/extensions/builtin/look-at/index.d.ts.map +1 -0
  90. package/dist/core/extensions/builtin/look-at/index.js +58 -0
  91. package/dist/core/extensions/builtin/look-at/index.js.map +1 -0
  92. package/dist/core/extensions/builtin/look-at/model-selector.d.ts +9 -0
  93. package/dist/core/extensions/builtin/look-at/model-selector.d.ts.map +1 -0
  94. package/dist/core/extensions/builtin/look-at/model-selector.js +77 -0
  95. package/dist/core/extensions/builtin/look-at/model-selector.js.map +1 -0
  96. package/dist/core/extensions/builtin/look-at/prompts.d.ts +5 -0
  97. package/dist/core/extensions/builtin/look-at/prompts.d.ts.map +1 -0
  98. package/dist/core/extensions/builtin/look-at/prompts.js +19 -0
  99. package/dist/core/extensions/builtin/look-at/prompts.js.map +1 -0
  100. package/dist/core/extensions/builtin/look-at/render.d.ts +26 -0
  101. package/dist/core/extensions/builtin/look-at/render.d.ts.map +1 -0
  102. package/dist/core/extensions/builtin/look-at/render.js +70 -0
  103. package/dist/core/extensions/builtin/look-at/render.js.map +1 -0
  104. package/dist/core/extensions/builtin/look-at/runner.d.ts +16 -0
  105. package/dist/core/extensions/builtin/look-at/runner.d.ts.map +1 -0
  106. package/dist/core/extensions/builtin/look-at/runner.js +108 -0
  107. package/dist/core/extensions/builtin/look-at/runner.js.map +1 -0
  108. package/dist/core/extensions/builtin/look-at/settings.d.ts +13 -0
  109. package/dist/core/extensions/builtin/look-at/settings.d.ts.map +1 -0
  110. package/dist/core/extensions/builtin/look-at/settings.js +20 -0
  111. package/dist/core/extensions/builtin/look-at/settings.js.map +1 -0
  112. package/dist/core/extensions/builtin/mcp/commands.d.ts.map +1 -1
  113. package/dist/core/extensions/builtin/mcp/commands.js +2 -0
  114. package/dist/core/extensions/builtin/mcp/commands.js.map +1 -1
  115. package/dist/core/extensions/builtin/mcp/config-schema.d.ts +6 -0
  116. package/dist/core/extensions/builtin/mcp/config-schema.d.ts.map +1 -1
  117. package/dist/core/extensions/builtin/mcp/config-schema.js +1 -0
  118. package/dist/core/extensions/builtin/mcp/config-schema.js.map +1 -1
  119. package/dist/core/extensions/builtin/mcp/config.d.ts.map +1 -1
  120. package/dist/core/extensions/builtin/mcp/config.js +9 -1
  121. package/dist/core/extensions/builtin/mcp/config.js.map +1 -1
  122. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  123. package/dist/core/extensions/builtin/mcp/service.js +22 -6
  124. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  125. package/dist/core/extensions/builtin/mcp/startup-race.d.ts +10 -0
  126. package/dist/core/extensions/builtin/mcp/startup-race.d.ts.map +1 -1
  127. package/dist/core/extensions/builtin/mcp/startup-race.js +18 -1
  128. package/dist/core/extensions/builtin/mcp/startup-race.js.map +1 -1
  129. package/dist/core/extensions/builtin/model-fallback/index.d.ts +4 -0
  130. package/dist/core/extensions/builtin/model-fallback/index.d.ts.map +1 -0
  131. package/dist/core/extensions/builtin/model-fallback/index.js +55 -0
  132. package/dist/core/extensions/builtin/model-fallback/index.js.map +1 -0
  133. package/dist/core/extensions/builtin/model-fallback/settings.d.ts +6 -0
  134. package/dist/core/extensions/builtin/model-fallback/settings.d.ts.map +1 -0
  135. package/dist/core/extensions/builtin/model-fallback/settings.js +11 -0
  136. package/dist/core/extensions/builtin/model-fallback/settings.js.map +1 -0
  137. package/dist/core/extensions/builtin/model-fallback/ui.d.ts +12 -0
  138. package/dist/core/extensions/builtin/model-fallback/ui.d.ts.map +1 -0
  139. package/dist/core/extensions/builtin/model-fallback/ui.js +72 -0
  140. package/dist/core/extensions/builtin/model-fallback/ui.js.map +1 -0
  141. package/dist/core/extensions/builtin/nested-agents-md/index.d.ts.map +1 -1
  142. package/dist/core/extensions/builtin/nested-agents-md/index.js +4 -1
  143. package/dist/core/extensions/builtin/nested-agents-md/index.js.map +1 -1
  144. package/dist/core/extensions/builtin/rules/index.d.ts.map +1 -1
  145. package/dist/core/extensions/builtin/rules/index.js +4 -1
  146. package/dist/core/extensions/builtin/rules/index.js.map +1 -1
  147. package/dist/core/extensions/builtin/terminal/output-format.d.ts +28 -0
  148. package/dist/core/extensions/builtin/terminal/output-format.d.ts.map +1 -0
  149. package/dist/core/extensions/builtin/terminal/output-format.js +74 -0
  150. package/dist/core/extensions/builtin/terminal/output-format.js.map +1 -0
  151. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +3 -0
  152. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts.map +1 -1
  153. package/dist/core/extensions/builtin/terminal/runtime-session.js +22 -2
  154. package/dist/core/extensions/builtin/terminal/runtime-session.js.map +1 -1
  155. package/dist/core/extensions/builtin/terminal/shared.d.ts +7 -0
  156. package/dist/core/extensions/builtin/terminal/shared.d.ts.map +1 -1
  157. package/dist/core/extensions/builtin/terminal/shared.js +14 -0
  158. package/dist/core/extensions/builtin/terminal/shared.js.map +1 -1
  159. package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts +5 -1
  160. package/dist/core/extensions/builtin/terminal/tools/bash-output.d.ts.map +1 -1
  161. package/dist/core/extensions/builtin/terminal/tools/bash-output.js +88 -6
  162. package/dist/core/extensions/builtin/terminal/tools/bash-output.js.map +1 -1
  163. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts +12 -1
  164. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
  165. package/dist/core/extensions/builtin/terminal/tools/bash.js +82 -8
  166. package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
  167. package/dist/core/extensions/builtin/terminal/tools/render.d.ts +16 -0
  168. package/dist/core/extensions/builtin/terminal/tools/render.d.ts.map +1 -0
  169. package/dist/core/extensions/builtin/terminal/tools/render.js +30 -0
  170. package/dist/core/extensions/builtin/terminal/tools/render.js.map +1 -0
  171. package/dist/core/extensions/builtin/terminal/tools/spawn.d.ts +2 -0
  172. package/dist/core/extensions/builtin/terminal/tools/spawn.d.ts.map +1 -1
  173. package/dist/core/extensions/builtin/terminal/tools/spawn.js +1 -1
  174. package/dist/core/extensions/builtin/terminal/tools/spawn.js.map +1 -1
  175. package/dist/core/extensions/builtin/todotools/tools/todo.d.ts.map +1 -1
  176. package/dist/core/extensions/builtin/todotools/tools/todo.js +22 -6
  177. package/dist/core/extensions/builtin/todotools/tools/todo.js.map +1 -1
  178. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.d.ts +1 -0
  179. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.d.ts.map +1 -1
  180. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.js +13 -4
  181. package/dist/core/extensions/builtin/webfetch/webfetch/fetcher.js.map +1 -1
  182. package/dist/core/extensions/builtin/webfetch/webfetch/renderers.d.ts.map +1 -1
  183. package/dist/core/extensions/builtin/webfetch/webfetch/renderers.js +17 -2
  184. package/dist/core/extensions/builtin/webfetch/webfetch/renderers.js.map +1 -1
  185. package/dist/core/extensions/builtin/webfetch/webfetch/tool.d.ts +8 -1
  186. package/dist/core/extensions/builtin/webfetch/webfetch/tool.d.ts.map +1 -1
  187. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +45 -3
  188. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js.map +1 -1
  189. package/dist/core/extensions/builtin/websearch/websearch/renderers.d.ts.map +1 -1
  190. package/dist/core/extensions/builtin/websearch/websearch/renderers.js +13 -4
  191. package/dist/core/extensions/builtin/websearch/websearch/renderers.js.map +1 -1
  192. package/dist/core/extensions/builtin/websearch/websearch/search.d.ts +3 -2
  193. package/dist/core/extensions/builtin/websearch/websearch/search.d.ts.map +1 -1
  194. package/dist/core/extensions/builtin/websearch/websearch/search.js +2 -1
  195. package/dist/core/extensions/builtin/websearch/websearch/search.js.map +1 -1
  196. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts.map +1 -1
  197. package/dist/core/extensions/builtin/websearch/websearch/tool.js +17 -1
  198. package/dist/core/extensions/builtin/websearch/websearch/tool.js.map +1 -1
  199. package/dist/core/extensions/builtin/websearch/websearch/types.d.ts +2 -0
  200. package/dist/core/extensions/builtin/websearch/websearch/types.d.ts.map +1 -1
  201. package/dist/core/extensions/builtin/websearch/websearch/types.js.map +1 -1
  202. package/dist/core/extensions/runner.d.ts +6 -0
  203. package/dist/core/extensions/runner.d.ts.map +1 -1
  204. package/dist/core/extensions/runner.js +71 -3
  205. package/dist/core/extensions/runner.js.map +1 -1
  206. package/dist/core/extensions/types.d.ts +96 -10
  207. package/dist/core/extensions/types.d.ts.map +1 -1
  208. package/dist/core/extensions/types.js.map +1 -1
  209. package/dist/core/model-config-schema.d.ts +1942 -0
  210. package/dist/core/model-config-schema.d.ts.map +1 -0
  211. package/dist/core/model-config-schema.js +197 -0
  212. package/dist/core/model-config-schema.js.map +1 -0
  213. package/dist/core/model-config.d.ts +2 -578
  214. package/dist/core/model-config.d.ts.map +1 -1
  215. package/dist/core/model-config.js +1 -194
  216. package/dist/core/model-config.js.map +1 -1
  217. package/dist/core/model-resolver.d.ts.map +1 -1
  218. package/dist/core/model-resolver.js +1 -0
  219. package/dist/core/model-resolver.js.map +1 -1
  220. package/dist/core/model-runtime.d.ts.map +1 -1
  221. package/dist/core/model-runtime.js +9 -3
  222. package/dist/core/model-runtime.js.map +1 -1
  223. package/dist/core/provider-composer.d.ts +1 -0
  224. package/dist/core/provider-composer.d.ts.map +1 -1
  225. package/dist/core/provider-composer.js +3 -2
  226. package/dist/core/provider-composer.js.map +1 -1
  227. package/dist/core/provider-display-names.d.ts.map +1 -1
  228. package/dist/core/provider-display-names.js +1 -0
  229. package/dist/core/provider-display-names.js.map +1 -1
  230. package/dist/core/retry-fallback/chains.d.ts +29 -0
  231. package/dist/core/retry-fallback/chains.d.ts.map +1 -0
  232. package/dist/core/retry-fallback/chains.js +109 -0
  233. package/dist/core/retry-fallback/chains.js.map +1 -0
  234. package/dist/core/retry-fallback/controller.d.ts +77 -0
  235. package/dist/core/retry-fallback/controller.d.ts.map +1 -0
  236. package/dist/core/retry-fallback/controller.js +157 -0
  237. package/dist/core/retry-fallback/controller.js.map +1 -0
  238. package/dist/core/retry-fallback/cooldown.d.ts +21 -0
  239. package/dist/core/retry-fallback/cooldown.d.ts.map +1 -0
  240. package/dist/core/retry-fallback/cooldown.js +49 -0
  241. package/dist/core/retry-fallback/cooldown.js.map +1 -0
  242. package/dist/core/retry-fallback/log.d.ts +10 -0
  243. package/dist/core/retry-fallback/log.d.ts.map +1 -0
  244. package/dist/core/retry-fallback/log.js +128 -0
  245. package/dist/core/retry-fallback/log.js.map +1 -0
  246. package/dist/core/retry-fallback/validate.d.ts +8 -0
  247. package/dist/core/retry-fallback/validate.d.ts.map +1 -0
  248. package/dist/core/retry-fallback/validate.js +63 -0
  249. package/dist/core/retry-fallback/validate.js.map +1 -0
  250. package/dist/core/sdk.d.ts.map +1 -1
  251. package/dist/core/sdk.js +1 -0
  252. package/dist/core/sdk.js.map +1 -1
  253. package/dist/core/session-manager.d.ts +39 -3
  254. package/dist/core/session-manager.d.ts.map +1 -1
  255. package/dist/core/session-manager.js +125 -16
  256. package/dist/core/session-manager.js.map +1 -1
  257. package/dist/core/settings-manager.d.ts +33 -0
  258. package/dist/core/settings-manager.d.ts.map +1 -1
  259. package/dist/core/settings-manager.js +153 -6
  260. package/dist/core/settings-manager.js.map +1 -1
  261. package/dist/modes/interactive/components/footer.d.ts +2 -1
  262. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  263. package/dist/modes/interactive/components/footer.js +30 -20
  264. package/dist/modes/interactive/components/footer.js.map +1 -1
  265. package/dist/modes/interactive/components/todo-strike.d.ts +8 -0
  266. package/dist/modes/interactive/components/todo-strike.d.ts.map +1 -0
  267. package/dist/modes/interactive/components/todo-strike.js +35 -0
  268. package/dist/modes/interactive/components/todo-strike.js.map +1 -0
  269. package/dist/modes/interactive/components/tool-execution-renderer.d.ts.map +1 -1
  270. package/dist/modes/interactive/components/tool-execution-renderer.js +8 -1
  271. package/dist/modes/interactive/components/tool-execution-renderer.js.map +1 -1
  272. package/dist/modes/interactive/components/tool-execution.d.ts +4 -0
  273. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  274. package/dist/modes/interactive/components/tool-execution.js +51 -2
  275. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  276. package/dist/modes/interactive/interactive-mode.d.ts +6 -0
  277. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  278. package/dist/modes/interactive/interactive-mode.js +124 -4
  279. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  280. package/dist/modes/interactive/tool-progress.d.ts +8 -0
  281. package/dist/modes/interactive/tool-progress.d.ts.map +1 -0
  282. package/dist/modes/interactive/tool-progress.js +26 -0
  283. package/dist/modes/interactive/tool-progress.js.map +1 -0
  284. package/dist/modes/interactive/tool-result-reveal.d.ts +18 -0
  285. package/dist/modes/interactive/tool-result-reveal.d.ts.map +1 -0
  286. package/dist/modes/interactive/tool-result-reveal.js +155 -0
  287. package/dist/modes/interactive/tool-result-reveal.js.map +1 -0
  288. package/dist/utils/fs-watch.d.ts +2 -2
  289. package/dist/utils/fs-watch.d.ts.map +1 -1
  290. package/dist/utils/fs-watch.js +2 -2
  291. package/dist/utils/fs-watch.js.map +1 -1
  292. package/docs/extensions.md +79 -1
  293. package/docs/mcp.md +10 -1
  294. package/docs/models.md +102 -1
  295. package/docs/providers.md +3 -1
  296. package/docs/settings.md +35 -0
  297. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
  298. package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
  299. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +13 -1
  300. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  301. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +76 -8
  302. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  303. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  304. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +26 -1
  305. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  306. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  307. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +24 -2
  308. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  309. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +5 -0
  310. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  311. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  312. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  313. package/node_modules/@earendil-works/pi-ai/README.md +2 -0
  314. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  315. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +68 -25
  316. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  317. package/node_modules/@earendil-works/pi-ai/dist/api/lazy.d.ts.map +1 -1
  318. package/node_modules/@earendil-works/pi-ai/dist/api/lazy.js +33 -6
  319. package/node_modules/@earendil-works/pi-ai/dist/api/lazy.js.map +1 -1
  320. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  321. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +6 -1
  322. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  323. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
  324. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +62 -52
  325. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
  326. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts +19 -0
  327. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts.map +1 -0
  328. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +50 -0
  329. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js.map +1 -0
  330. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts +2 -17
  331. package/node_modules/@earendil-works/pi-ai/dist/compat.d.ts.map +1 -1
  332. package/node_modules/@earendil-works/pi-ai/dist/compat.js +3 -52
  333. package/node_modules/@earendil-works/pi-ai/dist/compat.js.map +1 -1
  334. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  335. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
  336. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  337. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +45 -0
  338. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  339. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
  340. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  341. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +5 -0
  342. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  343. package/node_modules/@earendil-works/pi-ai/dist/index.js +5 -0
  344. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  345. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts +27 -0
  346. package/node_modules/@earendil-works/pi-ai/dist/model.d.ts.map +1 -0
  347. package/node_modules/@earendil-works/pi-ai/dist/model.js +2 -0
  348. package/node_modules/@earendil-works/pi-ai/dist/model.js.map +1 -0
  349. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +1178 -1072
  350. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  351. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2 -0
  352. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  353. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.d.ts +3 -0
  354. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.d.ts.map +1 -0
  355. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.js +15 -0
  356. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.js.map +1 -0
  357. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts +64 -0
  358. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.d.ts.map +1 -0
  359. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js +5 -0
  360. package/node_modules/@earendil-works/pi-ai/dist/providers/alibaba-token-plan.models.js.map +1 -0
  361. package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
  362. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +2 -0
  363. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
  364. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -0
  365. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
  366. package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  367. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  368. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  369. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  370. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts +1 -0
  371. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts.map +1 -1
  372. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +1 -0
  373. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js.map +1 -1
  374. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts +8 -0
  375. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.d.ts.map +1 -1
  376. package/node_modules/@earendil-works/pi-ai/dist/providers/google.models.js.map +1 -1
  377. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts +0 -4
  378. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts.map +1 -1
  379. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js.map +1 -1
  380. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts +12 -4
  381. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.d.ts.map +1 -1
  382. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js.map +1 -1
  383. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +20 -4
  384. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  385. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  386. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +16 -0
  387. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  388. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  389. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts +1 -0
  390. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.d.ts.map +1 -1
  391. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js +8 -0
  392. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/index.js.map +1 -1
  393. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.d.ts +20 -0
  394. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.d.ts.map +1 -0
  395. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.js +79 -0
  396. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-stream-helpers.js.map +1 -0
  397. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.d.ts +2 -18
  398. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.d.ts.map +1 -1
  399. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.js +4 -218
  400. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-scanner.js.map +1 -1
  401. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.d.ts +38 -0
  402. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.d.ts.map +1 -0
  403. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.js +210 -0
  404. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/invoke-tag-syntax.js.map +1 -0
  405. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.d.ts +9 -0
  406. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.d.ts.map +1 -0
  407. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.js +231 -0
  408. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-stream.js.map +1 -0
  409. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.d.ts +38 -0
  410. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.d.ts.map +1 -0
  411. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.js +84 -0
  412. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/recovery-wrapper-state.js.map +1 -0
  413. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.d.ts.map +1 -1
  414. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.js +20 -57
  415. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream-boundary.js.map +1 -1
  416. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.d.ts.map +1 -1
  417. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.js +16 -99
  418. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/anthropic-xml/stream.js.map +1 -1
  419. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.d.ts +1 -0
  420. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.d.ts.map +1 -1
  421. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.js +1 -0
  422. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/index.js.map +1 -1
  423. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.d.ts +5 -0
  424. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.d.ts.map +1 -0
  425. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.js +6 -0
  426. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/protocols/antml/recovery-stream.js.map +1 -0
  427. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.d.ts +19 -0
  428. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.d.ts.map +1 -0
  429. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.js +185 -0
  430. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-code-mask.js.map +1 -0
  431. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.d.ts +11 -0
  432. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.d.ts.map +1 -0
  433. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.js +24 -0
  434. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-content-lifecycle.js.map +1 -0
  435. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.d.ts +4 -0
  436. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.d.ts.map +1 -0
  437. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.js +13 -0
  438. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-diagnostics.js.map +1 -0
  439. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.d.ts +15 -0
  440. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.d.ts.map +1 -0
  441. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.js +37 -0
  442. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-event-stream.js.map +1 -0
  443. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.d.ts +4 -0
  444. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.d.ts.map +1 -0
  445. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.js +61 -0
  446. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-message-snapshot.js.map +1 -0
  447. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.d.ts +29 -0
  448. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.d.ts.map +1 -0
  449. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.js +148 -0
  450. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-native-projection.js.map +1 -0
  451. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.d.ts +5 -0
  452. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.d.ts.map +1 -0
  453. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.js +36 -0
  454. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-failure.js.map +1 -0
  455. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.d.ts +15 -0
  456. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.d.ts.map +1 -0
  457. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.js +88 -0
  458. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-terminal.js.map +1 -0
  459. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.d.ts +3 -0
  460. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.d.ts.map +1 -0
  461. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.js +233 -0
  462. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-stream-wrapper.js.map +1 -0
  463. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.d.ts +23 -0
  464. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.d.ts.map +1 -0
  465. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.js +77 -0
  466. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/recovery-text-projection.js.map +1 -0
  467. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.d.ts +5 -0
  468. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.d.ts.map +1 -0
  469. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.js +63 -0
  470. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-message-metadata.js.map +1 -0
  471. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.d.ts +13 -0
  472. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.d.ts.map +1 -0
  473. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.js +52 -0
  474. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-thinking-projection.js.map +1 -0
  475. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.d.ts +41 -0
  476. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.d.ts.map +1 -0
  477. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.js +209 -0
  478. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper-shared.js.map +1 -0
  479. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper.d.ts.map +1 -1
  480. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper.js +70 -346
  481. package/node_modules/@earendil-works/pi-ai/dist/tool-call-middleware/stream-wrapper.js.map +1 -1
  482. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +18 -23
  483. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  484. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  485. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  486. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +5 -1
  487. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  488. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.d.ts +4 -0
  489. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.d.ts.map +1 -0
  490. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.js +6 -0
  491. package/node_modules/@earendil-works/pi-ai/dist/utils/stop-details.js.map +1 -0
  492. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  493. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  494. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  495. package/package.json +6 -6
@@ -0,0 +1,109 @@
1
+ import { isValidThinkingLevel } from "../../cli/args.js";
2
+ import { findExactModelReferenceMatch, parseModelPattern } from "../model-resolver.js";
3
+ function availableModels(lookup) {
4
+ return "getAll" in lookup ? lookup.getAll() : [...lookup];
5
+ }
6
+ function selectorReference(raw) {
7
+ const trimmed = raw.trim();
8
+ if (!trimmed.includes("/") || trimmed.includes("*"))
9
+ return undefined;
10
+ const lastColon = trimmed.lastIndexOf(":");
11
+ if (lastColon === -1)
12
+ return { reference: trimmed };
13
+ const prefix = trimmed.slice(0, lastColon);
14
+ const suffix = trimmed.slice(lastColon + 1).toLowerCase();
15
+ if (!isValidThinkingLevel(suffix))
16
+ return { reference: trimmed };
17
+ return { reference: prefix, thinkingLevel: suffix };
18
+ }
19
+ /**
20
+ * Resolves only complete provider/model selectors. The full input is resolved first
21
+ * so colons belonging to a model id remain part of that id; aliases then resolve
22
+ * against the model-id portion while retaining the explicitly selected provider.
23
+ */
24
+ export function parseFallbackSelector(raw, lookup) {
25
+ const models = availableModels(lookup);
26
+ const trimmed = raw.trim();
27
+ const parsedReference = selectorReference(trimmed);
28
+ if (!parsedReference)
29
+ return undefined;
30
+ const fullMatch = findExactModelReferenceMatch(trimmed, models);
31
+ if (fullMatch) {
32
+ return { raw: trimmed, provider: fullMatch.provider, id: fullMatch.id };
33
+ }
34
+ const slashIndex = parsedReference.reference.indexOf("/");
35
+ const provider = parsedReference.reference.slice(0, slashIndex).trim();
36
+ const modelPattern = parsedReference.reference.slice(slashIndex + 1).trim();
37
+ if (!provider || !modelPattern)
38
+ return undefined;
39
+ const parsed = parseModelPattern(parsedReference.thinkingLevel ? `${modelPattern}:${parsedReference.thinkingLevel}` : modelPattern, models, { allowInvalidThinkingLevelFallback: false });
40
+ if (!parsed.model || parsed.warning || parsed.model.provider.toLowerCase() !== provider.toLowerCase())
41
+ return undefined;
42
+ if (parsedReference.thinkingLevel && !parsed.thinkingLevel)
43
+ return undefined;
44
+ return {
45
+ raw: trimmed,
46
+ provider: parsed.model.provider,
47
+ id: parsed.model.id,
48
+ thinkingLevel: parsed.thinkingLevel,
49
+ };
50
+ }
51
+ export function formatSelector(model, thinkingLevel) {
52
+ const base = `${model.provider}/${model.id}`;
53
+ return thinkingLevel ? `${base}:${thinkingLevel}` : base;
54
+ }
55
+ export function baseSelector(selector) {
56
+ return `${selector.provider}/${selector.id}`;
57
+ }
58
+ /** Converts validated configuration to canonical selector strings for runtime lookup. */
59
+ export function canonicalizeFallbackChains(chains, lookup) {
60
+ const canonical = {};
61
+ for (const [key, entries] of Object.entries(chains)) {
62
+ const parsedKey = parseFallbackSelector(key, lookup);
63
+ if (!parsedKey || !Array.isArray(entries))
64
+ continue;
65
+ const canonicalEntries = entries.flatMap((entry) => {
66
+ const parsedEntry = parseFallbackSelector(entry, lookup);
67
+ return parsedEntry ? [formatParsedSelector(parsedEntry)] : [];
68
+ });
69
+ canonical[formatParsedSelector(parsedKey)] = canonicalEntries;
70
+ }
71
+ return canonical;
72
+ }
73
+ export function resolveChainKey(currentModel, currentThinking, chains) {
74
+ const base = formatSelector(currentModel);
75
+ const exact = currentThinking ? `${base}:${currentThinking}` : base;
76
+ if (Object.hasOwn(chains, exact))
77
+ return exact;
78
+ return Object.hasOwn(chains, base) ? base : undefined;
79
+ }
80
+ function formatParsedSelector(selector) {
81
+ const base = baseSelector(selector);
82
+ return selector.thinkingLevel ? `${base}:${selector.thinkingLevel}` : base;
83
+ }
84
+ function normalizedBase(selector) {
85
+ if (typeof selector !== "string")
86
+ return baseSelector(selector).toLowerCase();
87
+ const normalized = selector.trim().toLowerCase();
88
+ const lastColon = normalized.lastIndexOf(":");
89
+ if (lastColon === -1 || !isValidThinkingLevel(normalized.slice(lastColon + 1)))
90
+ return normalized;
91
+ return normalized.slice(0, lastColon);
92
+ }
93
+ function normalizedExact(selector) {
94
+ return typeof selector === "string" ? selector.trim().toLowerCase() : formatParsedSelector(selector).toLowerCase();
95
+ }
96
+ /**
97
+ * Returns entries after the current fallback. A primary or unknown selector starts
98
+ * from the beginning, which also makes re-entry after stale runtime state safe.
99
+ */
100
+ export function candidatesAfter(chainEntries, currentSelector) {
101
+ const exact = normalizedExact(currentSelector);
102
+ const exactIndex = chainEntries.findIndex((entry) => entry.toLowerCase() === exact);
103
+ if (exactIndex !== -1)
104
+ return chainEntries.slice(exactIndex + 1);
105
+ const base = normalizedBase(currentSelector);
106
+ const baseIndex = chainEntries.findIndex((entry) => normalizedBase(entry) === base);
107
+ return baseIndex === -1 ? chainEntries : chainEntries.slice(baseIndex + 1);
108
+ }
109
+ //# sourceMappingURL=chains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chains.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { isValidThinkingLevel } from \"../../cli/args.ts\";\nimport { findExactModelReferenceMatch, parseModelPattern } from \"../model-resolver.ts\";\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\tconst parsed = parseModelPattern(\n\t\tparsedReference.thinkingLevel ? `${modelPattern}:${parsedReference.thinkingLevel}` : modelPattern,\n\t\tmodels,\n\t\t{ allowInvalidThinkingLevelFallback: false },\n\t);\n\tif (!parsed.model || parsed.warning || parsed.model.provider.toLowerCase() !== provider.toLowerCase())\n\t\treturn 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\tcanonical[formatParsedSelector(parsedKey)] = canonicalEntries;\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"]}
@@ -0,0 +1,77 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
+ import type { Api, 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
+ pinned: boolean;
11
+ }
12
+ type FallbackReason = "transient" | "refusal" | "hard-error";
13
+ interface FallbackSettings {
14
+ modelFallback: boolean;
15
+ chains: Readonly<Record<string, readonly string[]>>;
16
+ }
17
+ export interface RetryFallbackControllerDeps {
18
+ getSettings(): FallbackSettings;
19
+ registry: {
20
+ find(provider: string, id: string): Model<Api> | undefined;
21
+ getAll(): Model<Api>[];
22
+ };
23
+ cooldowns: SelectorCooldowns;
24
+ logger: FallbackLogger;
25
+ switchModel(model: Model<Api>, thinking: ThinkingLevel, reason: "fallback" | "fallback-revert"): Promise<void>;
26
+ emit(event: {
27
+ type: "retry_fallback_applied";
28
+ from: string;
29
+ to: string;
30
+ chainKey: string;
31
+ reason: FallbackReason;
32
+ } | {
33
+ type: "retry_fallback_reverted";
34
+ from: string;
35
+ to: string;
36
+ }): void;
37
+ getCurrentSelector(): {
38
+ model: Model<Api>;
39
+ thinkingLevel?: ThinkingLevel;
40
+ } | undefined;
41
+ isAuthAvailable(provider: string): boolean;
42
+ }
43
+ export declare class RetryFallbackController {
44
+ private readonly deps;
45
+ private readonly triedSelectors;
46
+ private state;
47
+ private lastExhaustedChainKey;
48
+ constructor(deps: RetryFallbackControllerDeps);
49
+ get activeState(): Readonly<ActiveFallbackState> | undefined;
50
+ get exhaustedChainKey(): string | undefined;
51
+ resetTurn(): void;
52
+ clear(): void;
53
+ canTryFallback(): boolean;
54
+ /**
55
+ * Revert to the chain's original model at a turn boundary. Only fires for
56
+ * unpinned state under the cooldown-expiry policy once the original selector
57
+ * is no longer suppressed and is still usable; pinned (refusal) state and the
58
+ * "never" policy always hold the fallback model.
59
+ */
60
+ maybeRestorePrimary(revertPolicy: "cooldown-expiry" | "never"): Promise<boolean>;
61
+ /**
62
+ * A user-driven setThinkingLevel makes the current level a deliberate choice,
63
+ * so the revert restore-rule must no longer treat it as fallback-applied.
64
+ */
65
+ noteManualThinkingLevel(): void;
66
+ /** A user-driven model change abandons the fallback window entirely. */
67
+ clearForManualModelChange(model: Model<Api>): void;
68
+ tryFallback(reason: FallbackReason, failure: {
69
+ errorMessage?: string;
70
+ retryAfterMs?: number;
71
+ }): Promise<boolean>;
72
+ private nextCandidate;
73
+ private selectThinking;
74
+ private skip;
75
+ }
76
+ export {};
77
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ import { getSupportedThinkingLevels } from "../thinking-levels.js";
2
+ import { baseSelector, candidatesAfter, canonicalizeFallbackChains, formatSelector, parseFallbackSelector, resolveChainKey, } from "./chains.js";
3
+ export class RetryFallbackController {
4
+ constructor(deps) {
5
+ this.triedSelectors = new Set();
6
+ this.deps = deps;
7
+ }
8
+ get activeState() {
9
+ return this.state;
10
+ }
11
+ get exhaustedChainKey() {
12
+ return this.lastExhaustedChainKey;
13
+ }
14
+ resetTurn() {
15
+ this.triedSelectors.clear();
16
+ this.lastExhaustedChainKey = undefined;
17
+ }
18
+ clear() {
19
+ this.state = undefined;
20
+ this.resetTurn();
21
+ }
22
+ canTryFallback() {
23
+ return this.nextCandidate(false) !== undefined;
24
+ }
25
+ /**
26
+ * Revert to the chain's original model at a turn boundary. Only fires for
27
+ * unpinned state under the cooldown-expiry policy once the original selector
28
+ * is no longer suppressed and is still usable; pinned (refusal) state and the
29
+ * "never" policy always hold the fallback model.
30
+ */
31
+ async maybeRestorePrimary(revertPolicy) {
32
+ const state = this.state;
33
+ if (!state || state.pinned || revertPolicy !== "cooldown-expiry")
34
+ return false;
35
+ if (this.deps.cooldowns.isSuppressed(state.originalSelector))
36
+ return false;
37
+ const selector = parseFallbackSelector(state.originalSelector, this.deps.registry);
38
+ if (!selector || !this.deps.isAuthAvailable(selector.provider))
39
+ return false;
40
+ const model = this.deps.registry.find(selector.provider, selector.id);
41
+ const current = this.deps.getCurrentSelector();
42
+ if (!model || !current)
43
+ return false;
44
+ // User override wins: only restore the original thinking level when the
45
+ // current level still equals the level the fallback switch applied. A manual
46
+ // setThinkingLevel clears lastAppliedThinkingLevel (see noteManualThinkingLevel).
47
+ const thinking = current.thinkingLevel === state.lastAppliedThinkingLevel
48
+ ? (state.originalThinkingLevel ?? current.thinkingLevel ?? "off")
49
+ : (current.thinkingLevel ?? "off");
50
+ await this.deps.switchModel(model, thinking, "fallback-revert");
51
+ const from = formatSelector(current.model);
52
+ this.state = undefined;
53
+ this.deps.logger.info("fallback_reverted", { from, to: state.originalSelector });
54
+ this.deps.emit({ type: "retry_fallback_reverted", from, to: state.originalSelector });
55
+ return true;
56
+ }
57
+ /**
58
+ * A user-driven setThinkingLevel makes the current level a deliberate choice,
59
+ * so the revert restore-rule must no longer treat it as fallback-applied.
60
+ */
61
+ noteManualThinkingLevel() {
62
+ if (this.state)
63
+ this.state.lastAppliedThinkingLevel = undefined;
64
+ }
65
+ /** A user-driven model change abandons the fallback window entirely. */
66
+ clearForManualModelChange(model) {
67
+ if (this.state) {
68
+ this.deps.logger.info("fallback_cleared_manual", { selector: formatSelector(model) });
69
+ }
70
+ this.state = undefined;
71
+ this.deps.cooldowns.clear(formatSelector(model));
72
+ }
73
+ async tryFallback(reason, failure) {
74
+ const current = this.deps.getCurrentSelector();
75
+ const candidate = this.nextCandidate();
76
+ if (!current || !candidate)
77
+ return false;
78
+ const currentBase = formatSelector(current.model);
79
+ if (reason === "transient" || reason === "hard-error") {
80
+ this.deps.cooldowns.note(currentBase, failure);
81
+ this.deps.logger.info("cooldown_noted", { selector: currentBase, errorMessage: failure.errorMessage });
82
+ }
83
+ const thinking = this.selectThinking(candidate.selector, candidate.model, current.thinkingLevel);
84
+ await this.deps.switchModel(candidate.model, thinking, "fallback");
85
+ const from = formatSelector(current.model);
86
+ const to = formatSelector(candidate.model);
87
+ this.state = {
88
+ chainKey: candidate.chainKey,
89
+ originalSelector: this.state?.originalSelector ?? from,
90
+ originalThinkingLevel: this.state?.originalThinkingLevel ?? current.thinkingLevel,
91
+ lastAppliedThinkingLevel: thinking,
92
+ pinned: this.state?.pinned === true || reason === "refusal",
93
+ };
94
+ this.deps.logger.info("fallback_applied", { from, to, chainKey: candidate.chainKey, reason });
95
+ this.deps.emit({ type: "retry_fallback_applied", from, to, chainKey: candidate.chainKey, reason });
96
+ return true;
97
+ }
98
+ nextCandidate(reserve = true) {
99
+ const settings = this.deps.getSettings();
100
+ const current = this.deps.getCurrentSelector();
101
+ if (!settings.modelFallback || !current)
102
+ return undefined;
103
+ const chains = canonicalizeFallbackChains(settings.chains, this.deps.registry);
104
+ const chainKey = resolveChainKey(current.model, current.thinkingLevel, chains) ?? this.state?.chainKey;
105
+ const entries = chainKey ? chains[chainKey] : undefined;
106
+ if (!chainKey || !entries) {
107
+ if (reserve)
108
+ this.deps.logger.debug("no_chain", { selector: formatSelector(current.model) });
109
+ return undefined;
110
+ }
111
+ for (const raw of candidatesAfter(entries, formatSelector(current.model, current.thinkingLevel))) {
112
+ const selector = parseFallbackSelector(raw, this.deps.registry);
113
+ if (!selector) {
114
+ this.skip(raw, "unknown");
115
+ continue;
116
+ }
117
+ if (selector.provider === current.model.provider && selector.id === current.model.id) {
118
+ this.skip(raw, "self");
119
+ continue;
120
+ }
121
+ const base = baseSelector(selector);
122
+ if (this.triedSelectors.has(base)) {
123
+ this.skip(raw, "tried");
124
+ continue;
125
+ }
126
+ if (this.deps.cooldowns.isSuppressed(base)) {
127
+ this.skip(raw, "suppressed");
128
+ continue;
129
+ }
130
+ if (!this.deps.isAuthAvailable(selector.provider)) {
131
+ this.skip(raw, "unauthenticated");
132
+ continue;
133
+ }
134
+ const model = this.deps.registry.find(selector.provider, selector.id);
135
+ if (!model) {
136
+ this.skip(raw, "unknown");
137
+ continue;
138
+ }
139
+ if (reserve)
140
+ this.triedSelectors.add(base);
141
+ return { chainKey, selector, model };
142
+ }
143
+ this.lastExhaustedChainKey = chainKey;
144
+ if (reserve)
145
+ this.deps.logger.info("candidates_exhausted", { chainKey });
146
+ return undefined;
147
+ }
148
+ selectThinking(selector, model, inherited) {
149
+ const requested = selector.thinkingLevel ?? inherited ?? "off";
150
+ const supported = getSupportedThinkingLevels(model);
151
+ return supported.includes(requested) ? requested : (supported[supported.length - 1] ?? "off");
152
+ }
153
+ skip(candidate, skipReason) {
154
+ this.deps.logger.debug("candidate_skipped", { candidate, skipReason });
155
+ }
156
+ }
157
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/controller.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,EACN,YAAY,EACZ,eAAe,EACf,0BAA0B,EAE1B,cAAc,EACd,qBAAqB,EACrB,eAAe,GACf,MAAM,aAAa,CAAC;AAwCrB,MAAM,OAAO,uBAAuB;IAMnC,YAAY,IAAiC;QAJ5B,mBAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QAKnD,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,SAAS,EAAE,CAAC;IAClB,CAAC;IAED,cAAc;QACb,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;IAChD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,mBAAmB,CAAC,YAAyC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,YAAY,KAAK,iBAAiB;YAAE,OAAO,KAAK,CAAC;QAC/E,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,gBAAgB,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3E,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnF,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QACrC,wEAAwE;QACxE,6EAA6E;QAC7E,kFAAkF;QAClF,MAAM,QAAQ,GACb,OAAO,CAAC,aAAa,KAAK,KAAK,CAAC,wBAAwB;YACvD,CAAC,CAAC,CAAC,KAAK,CAAC,qBAAqB,IAAI,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC;YACjE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,IAAI,KAAK,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAChE,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;OAGG;IACH,uBAAuB;QACtB,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,CAAC,wBAAwB,GAAG,SAAS,CAAC;IACjE,CAAC;IAED,wEAAwE;IACxE,yBAAyB,CAAC,KAAiB;QAC1C,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,WAAW,CAChB,MAAsB,EACtB,OAAyD;QAEzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;YACvD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;QACjG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG;YACZ,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,gBAAgB,EAAE,IAAI,CAAC,KAAK,EAAE,gBAAgB,IAAI,IAAI;YACtD,qBAAqB,EAAE,IAAI,CAAC,KAAK,EAAE,qBAAqB,IAAI,OAAO,CAAC,aAAa;YACjF,wBAAwB,EAAE,QAAQ;YAClC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;SAC3D,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACnG,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,aAAa,CACpB,OAAO,GAAG,IAAI;QAEd,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,aAAa,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,MAAM,GAAG,0BAA0B,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/E,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,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC7F,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,eAAe,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAClG,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC1B,SAAS;YACV,CAAC;YACD,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBACtF,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACvB,SAAS;YACV,CAAC;YACD,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;gBACxB,SAAS;YACV,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBAC7B,SAAS;YACV,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;gBAClC,SAAS;YACV,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACZ,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC1B,SAAS;YACV,CAAC;YACD,IAAI,OAAO;gBAAE,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,qBAAqB,GAAG,QAAQ,CAAC;QACtC,IAAI,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzE,OAAO,SAAS,CAAC;IAClB,CAAC;IAEO,cAAc,CACrB,QAA0B,EAC1B,KAAiB,EACjB,SAAoC;QAEpC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,IAAI,SAAS,IAAI,KAAK,CAAC;QAC/D,MAAM,SAAS,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;QACpD,OAAO,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/F,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, 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\";\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 * 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\") {\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\",\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,21 @@
1
+ /**
2
+ * Runtime-only selector suppression. A SelectorCooldowns instance belongs to one
3
+ * AgentSession and is deliberately never persisted to settings or session files.
4
+ * Callers provide canonical base selectors without a thinking suffix.
5
+ */
6
+ export declare class SelectorCooldowns {
7
+ private readonly expiresAtBySelector;
8
+ private readonly now;
9
+ private readonly random;
10
+ constructor(now: () => number, random?: () => number);
11
+ note(baseSelector: string, failure: {
12
+ retryAfterMs?: number;
13
+ errorMessage?: string;
14
+ }): void;
15
+ isSuppressed(baseSelector: string): boolean;
16
+ clear(baseSelector: string): void;
17
+ clearAll(): void;
18
+ private durationFor;
19
+ private capacityJitterMs;
20
+ }
21
+ //# sourceMappingURL=cooldown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cooldown.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/cooldown.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA6B;IACjE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,YAAY,GAAG,EAAE,MAAM,MAAM,EAAE,MAAM,GAAE,MAAM,MAAoB,EAGhE;IAED,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAE1F;IAED,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAM1C;IAED,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEhC;IAED,QAAQ,IAAI,IAAI,CAEf;IAED,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,gBAAgB;CAGxB"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Runtime-only selector suppression. A SelectorCooldowns instance belongs to one
3
+ * AgentSession and is deliberately never persisted to settings or session files.
4
+ * Callers provide canonical base selectors without a thinking suffix.
5
+ */
6
+ export class SelectorCooldowns {
7
+ constructor(now, random = Math.random) {
8
+ this.expiresAtBySelector = new Map();
9
+ this.now = now;
10
+ this.random = random;
11
+ }
12
+ note(baseSelector, failure) {
13
+ this.expiresAtBySelector.set(baseSelector, this.now() + this.durationFor(failure));
14
+ }
15
+ isSuppressed(baseSelector) {
16
+ const expiresAt = this.expiresAtBySelector.get(baseSelector);
17
+ if (expiresAt === undefined)
18
+ return false;
19
+ if (this.now() < expiresAt)
20
+ return true;
21
+ this.expiresAtBySelector.delete(baseSelector);
22
+ return false;
23
+ }
24
+ clear(baseSelector) {
25
+ this.expiresAtBySelector.delete(baseSelector);
26
+ }
27
+ clearAll() {
28
+ this.expiresAtBySelector.clear();
29
+ }
30
+ durationFor({ retryAfterMs, errorMessage }) {
31
+ if (retryAfterMs !== undefined && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
32
+ return retryAfterMs;
33
+ }
34
+ const message = errorMessage?.toLowerCase() ?? "";
35
+ if (/usage[- ]limit|quota|insufficient_quota|billing/.test(message))
36
+ return 30 * 60_000;
37
+ if (/rate[ -]?limit|429|too many requests/.test(message))
38
+ return 30_000;
39
+ if (/overloaded|capacity/.test(message))
40
+ return 45_000 + this.capacityJitterMs();
41
+ if (/5xx|\b5\d\d\b|server|internal error/.test(message))
42
+ return 20_000;
43
+ return 5 * 60_000;
44
+ }
45
+ capacityJitterMs() {
46
+ return Math.round(Math.min(1, Math.max(0, this.random())) * 30_000);
47
+ }
48
+ }
49
+ //# sourceMappingURL=cooldown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cooldown.js","sourceRoot":"","sources":["../../../src/core/retry-fallback/cooldown.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,iBAAiB;IAK7B,YAAY,GAAiB,EAAE,MAAM,GAAiB,IAAI,CAAC,MAAM;QAJhD,wBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAC;QAKhE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAED,IAAI,CAAC,YAAoB,EAAE,OAAyD;QACnF,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,YAAY,CAAC,YAAoB;QAChC,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC7D,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAC1C,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YAAE,OAAO,IAAI,CAAC;QACxC,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC;IACd,CAAC;IAED,KAAK,CAAC,YAAoB;QACzB,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC;IAED,QAAQ;QACP,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC;IAEO,WAAW,CAAC,EAAE,YAAY,EAAE,YAAY,EAAoD;QACnG,IAAI,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrF,OAAO,YAAY,CAAC;QACrB,CAAC;QAED,MAAM,OAAO,GAAG,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAClD,IAAI,iDAAiD,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QACxF,IAAI,sCAAsC,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC;QACxE,IAAI,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACjF,IAAI,qCAAqC,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC;QACvE,OAAO,CAAC,GAAG,MAAM,CAAC;IACnB,CAAC;IAEO,gBAAgB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACrE,CAAC;CACD","sourcesContent":["/**\n * Runtime-only selector suppression. A SelectorCooldowns instance belongs to one\n * AgentSession and is deliberately never persisted to settings or session files.\n * Callers provide canonical base selectors without a thinking suffix.\n */\nexport class SelectorCooldowns {\n\tprivate readonly expiresAtBySelector = new Map<string, number>();\n\tprivate readonly now: () => number;\n\tprivate readonly random: () => number;\n\n\tconstructor(now: () => number, random: () => number = Math.random) {\n\t\tthis.now = now;\n\t\tthis.random = random;\n\t}\n\n\tnote(baseSelector: string, failure: { retryAfterMs?: number; errorMessage?: string }): void {\n\t\tthis.expiresAtBySelector.set(baseSelector, this.now() + this.durationFor(failure));\n\t}\n\n\tisSuppressed(baseSelector: string): boolean {\n\t\tconst expiresAt = this.expiresAtBySelector.get(baseSelector);\n\t\tif (expiresAt === undefined) return false;\n\t\tif (this.now() < expiresAt) return true;\n\t\tthis.expiresAtBySelector.delete(baseSelector);\n\t\treturn false;\n\t}\n\n\tclear(baseSelector: string): void {\n\t\tthis.expiresAtBySelector.delete(baseSelector);\n\t}\n\n\tclearAll(): void {\n\t\tthis.expiresAtBySelector.clear();\n\t}\n\n\tprivate durationFor({ retryAfterMs, errorMessage }: { retryAfterMs?: number; errorMessage?: string }): number {\n\t\tif (retryAfterMs !== undefined && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {\n\t\t\treturn retryAfterMs;\n\t\t}\n\n\t\tconst message = errorMessage?.toLowerCase() ?? \"\";\n\t\tif (/usage[- ]limit|quota|insufficient_quota|billing/.test(message)) return 30 * 60_000;\n\t\tif (/rate[ -]?limit|429|too many requests/.test(message)) return 30_000;\n\t\tif (/overloaded|capacity/.test(message)) return 45_000 + this.capacityJitterMs();\n\t\tif (/5xx|\\b5\\d\\d\\b|server|internal error/.test(message)) return 20_000;\n\t\treturn 5 * 60_000;\n\t}\n\n\tprivate capacityJitterMs(): number {\n\t\treturn Math.round(Math.min(1, Math.max(0, this.random())) * 30_000);\n\t}\n}\n"]}
@@ -0,0 +1,10 @@
1
+ export interface FallbackLogger {
2
+ debug(event: string, data?: Record<string, unknown>): void;
3
+ info(event: string, data?: Record<string, unknown>): void;
4
+ warn(event: string, data?: Record<string, unknown>): void;
5
+ }
6
+ export interface FallbackLoggerOptions {
7
+ maxBytes?: number;
8
+ }
9
+ export declare function createFallbackLogger(agentDir: string, options?: FallbackLoggerOptions): FallbackLogger;
10
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/log.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC9B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAsB1G"}
@@ -0,0 +1,128 @@
1
+ import { chmodSync, closeSync, mkdirSync, openSync, renameSync, rmSync, statSync, writeSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
4
+ const MAX_STRING_LENGTH = 200;
5
+ const BLOCKED_KEY = /^(?:__proto__|constructor|prototype|headers?|env(?:ironment)?|authorization|credential(?:s)?|password|secret|token|api_?key|client_?secret)$/i;
6
+ const ALLOWED_DATA_KEY = /^(?:selector|candidate|currentSelector|originalSelector|from|to|model|chainKey|reason|skipReason|classification|thinkingLevel|durationMs|retryAfterMs|error|errorMessage|warning)$/;
7
+ const SENSITIVE_TEXT = /((?:authorization\s*[:=]\s*(?:bearer|basic)\s+)|(?:bearer\s+)|(?:[?&](?:api[_-]?key|token|secret|password|auth(?:orization)?)=))[^\s&,"'}\]]+/gi;
8
+ export function createFallbackLogger(agentDir, options = {}) {
9
+ const filePath = join(agentDir, "logs", "fallback.log");
10
+ const maxBytes = validMaxBytes(options.maxBytes);
11
+ let reportedWriteFailure = false;
12
+ function log(level, event, data) {
13
+ try {
14
+ const line = formatLine(level, event, data);
15
+ writeLine(filePath, line, maxBytes);
16
+ }
17
+ catch (error) {
18
+ if (!reportedWriteFailure) {
19
+ reportedWriteFailure = true;
20
+ console.error("Unable to write retry fallback debug log", error);
21
+ }
22
+ }
23
+ }
24
+ return {
25
+ debug: (event, data) => log("debug", event, data),
26
+ info: (event, data) => log("info", event, data),
27
+ warn: (event, data) => log("warn", event, data),
28
+ };
29
+ }
30
+ function validMaxBytes(value) {
31
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : DEFAULT_MAX_BYTES;
32
+ }
33
+ function formatLine(level, event, data) {
34
+ const entry = {
35
+ ts: new Date().toISOString(),
36
+ level,
37
+ event: safeText(event),
38
+ };
39
+ if (data !== undefined) {
40
+ for (const [key, value] of safeEntries(data)) {
41
+ if (ALLOWED_DATA_KEY.test(key) && !BLOCKED_KEY.test(key)) {
42
+ const safeValue = serializeValue(value, new WeakSet());
43
+ if (safeValue !== undefined)
44
+ entry[key] = safeValue;
45
+ }
46
+ }
47
+ }
48
+ return JSON.stringify(entry);
49
+ }
50
+ function writeLine(filePath, line, maxBytes) {
51
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
52
+ const text = `${line}\n`;
53
+ if (fileExceedsCap(filePath, Buffer.byteLength(text), maxBytes)) {
54
+ rmSync(`${filePath}.1`, { force: true });
55
+ renameSync(filePath, `${filePath}.1`);
56
+ chmodSync(`${filePath}.1`, 0o600);
57
+ }
58
+ const descriptor = openSync(filePath, "a", 0o600);
59
+ try {
60
+ writeSync(descriptor, text);
61
+ }
62
+ finally {
63
+ closeSync(descriptor);
64
+ }
65
+ chmodSync(filePath, 0o600);
66
+ }
67
+ function fileExceedsCap(filePath, incomingBytes, maxBytes) {
68
+ try {
69
+ return statSync(filePath).size + incomingBytes > maxBytes;
70
+ }
71
+ catch (error) {
72
+ if (isMissingFileError(error))
73
+ return false;
74
+ throw error;
75
+ }
76
+ }
77
+ function isMissingFileError(error) {
78
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
79
+ }
80
+ function serializeValue(value, seen) {
81
+ if (typeof value === "string")
82
+ return safeText(value);
83
+ if (typeof value === "bigint")
84
+ return value.toString();
85
+ if (typeof value === "number" || typeof value === "boolean" || value === null)
86
+ return value;
87
+ if (typeof value === "undefined")
88
+ return undefined;
89
+ if (typeof value === "function" || typeof value === "symbol")
90
+ return String(value);
91
+ if (seen.has(value))
92
+ return "[Circular]";
93
+ seen.add(value);
94
+ if (Array.isArray(value))
95
+ return value.map((item) => serializeValue(item, seen) ?? null);
96
+ const result = {};
97
+ for (const [key, item] of safeEntries(value)) {
98
+ if (!BLOCKED_KEY.test(key)) {
99
+ const safeValue = serializeValue(item, seen);
100
+ if (safeValue !== undefined)
101
+ result[key] = safeValue;
102
+ }
103
+ }
104
+ return result;
105
+ }
106
+ function safeEntries(value) {
107
+ try {
108
+ const entries = [];
109
+ for (const key of Object.keys(value)) {
110
+ try {
111
+ const item = Reflect.get(value, key);
112
+ entries.push([key, item]);
113
+ }
114
+ catch {
115
+ entries.push([key, "[Unreadable]"]);
116
+ }
117
+ }
118
+ return entries;
119
+ }
120
+ catch {
121
+ return [["unserializable", "[Unreadable]"]];
122
+ }
123
+ }
124
+ function safeText(value) {
125
+ const redacted = value.replace(SENSITIVE_TEXT, "$1[redacted]");
126
+ return redacted.length <= MAX_STRING_LENGTH ? redacted : `${redacted.slice(0, MAX_STRING_LENGTH - 3)}...`;
127
+ }
128
+ //# sourceMappingURL=log.js.map