@code-yeongyu/senpi 2026.8.26 → 2026.8.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 (297) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +3 -3
  3. package/dist/cli/args.d.ts +3 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +14 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +10 -1
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/bash-executor.d.ts.map +1 -1
  11. package/dist/core/bash-executor.js +67 -29
  12. package/dist/core/bash-executor.js.map +1 -1
  13. package/dist/core/credential-accounts.d.ts +31 -0
  14. package/dist/core/credential-accounts.d.ts.map +1 -0
  15. package/dist/core/credential-accounts.js +125 -0
  16. package/dist/core/credential-accounts.js.map +1 -0
  17. package/dist/core/credential-pool/classify.d.ts +42 -0
  18. package/dist/core/credential-pool/classify.d.ts.map +1 -0
  19. package/dist/core/credential-pool/classify.js +75 -0
  20. package/dist/core/credential-pool/classify.js.map +1 -0
  21. package/dist/core/credential-pool/env-slots.d.ts +22 -0
  22. package/dist/core/credential-pool/env-slots.d.ts.map +1 -0
  23. package/dist/core/credential-pool/env-slots.js +37 -0
  24. package/dist/core/credential-pool/env-slots.js.map +1 -0
  25. package/dist/core/credential-pool/failover.d.ts +54 -0
  26. package/dist/core/credential-pool/failover.d.ts.map +1 -0
  27. package/dist/core/credential-pool/failover.js +106 -0
  28. package/dist/core/credential-pool/failover.js.map +1 -0
  29. package/dist/core/credential-pool/rotation-stream.d.ts +43 -0
  30. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -0
  31. package/dist/core/credential-pool/rotation-stream.js +96 -0
  32. package/dist/core/credential-pool/rotation-stream.js.map +1 -0
  33. package/dist/core/credential-pool/state-store.d.ts +89 -0
  34. package/dist/core/credential-pool/state-store.d.ts.map +1 -0
  35. package/dist/core/credential-pool/state-store.js +166 -0
  36. package/dist/core/credential-pool/state-store.js.map +1 -0
  37. package/dist/core/extensions/builtin/account/index.d.ts +9 -0
  38. package/dist/core/extensions/builtin/account/index.d.ts.map +1 -0
  39. package/dist/core/extensions/builtin/account/index.js +69 -0
  40. package/dist/core/extensions/builtin/account/index.js.map +1 -0
  41. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/index.js +67 -0
  43. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  44. package/dist/core/extensions/builtin/compaction/log.d.ts +1 -1
  45. package/dist/core/extensions/builtin/compaction/log.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/compaction/log.js +1 -0
  47. package/dist/core/extensions/builtin/compaction/log.js.map +1 -1
  48. package/dist/core/extensions/builtin/config-reload/index.js +14 -22
  49. package/dist/core/extensions/builtin/config-reload/index.js.map +1 -1
  50. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  51. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
  53. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  54. package/dist/core/extensions/builtin/goal/continuation.d.ts +8 -1
  55. package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
  56. package/dist/core/extensions/builtin/goal/continuation.js +15 -0
  57. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  58. package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js +2 -2
  59. package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js.map +1 -1
  60. package/dist/core/extensions/builtin/goal/format.d.ts +6 -1
  61. package/dist/core/extensions/builtin/goal/format.d.ts.map +1 -1
  62. package/dist/core/extensions/builtin/goal/format.js +6 -0
  63. package/dist/core/extensions/builtin/goal/format.js.map +1 -1
  64. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +5 -2
  66. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  67. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  68. package/dist/core/extensions/builtin/goal/monitor-continuation.js +31 -7
  69. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  70. package/dist/core/extensions/builtin/goal/persistence.js +2 -0
  71. package/dist/core/extensions/builtin/goal/persistence.js.map +1 -1
  72. package/dist/core/extensions/builtin/goal/renderers.d.ts +14 -0
  73. package/dist/core/extensions/builtin/goal/renderers.d.ts.map +1 -0
  74. package/dist/core/extensions/builtin/goal/renderers.js +153 -0
  75. package/dist/core/extensions/builtin/goal/renderers.js.map +1 -0
  76. package/dist/core/extensions/builtin/goal/store.d.ts +6 -2
  77. package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
  78. package/dist/core/extensions/builtin/goal/store.js +8 -2
  79. package/dist/core/extensions/builtin/goal/store.js.map +1 -1
  80. package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
  81. package/dist/core/extensions/builtin/goal/tool-registration.js +16 -6
  82. package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
  83. package/dist/core/extensions/builtin/goal/types.d.ts +1 -0
  84. package/dist/core/extensions/builtin/goal/types.d.ts.map +1 -1
  85. package/dist/core/extensions/builtin/goal/types.js.map +1 -1
  86. package/dist/core/extensions/builtin/imagegen/index.d.ts.map +1 -1
  87. package/dist/core/extensions/builtin/imagegen/index.js +10 -3
  88. package/dist/core/extensions/builtin/imagegen/index.js.map +1 -1
  89. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  90. package/dist/core/extensions/builtin/index.js +4 -0
  91. package/dist/core/extensions/builtin/index.js.map +1 -1
  92. package/dist/core/extensions/builtin/todotools/index.js +1 -1
  93. package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
  94. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts +1 -1
  95. package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts.map +1 -1
  96. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js +1 -1
  97. package/dist/core/extensions/builtin/todotools/native-todo-mirror.js.map +1 -1
  98. package/dist/core/hidden-stdout-log.d.ts +10 -0
  99. package/dist/core/hidden-stdout-log.d.ts.map +1 -1
  100. package/dist/core/hidden-stdout-log.js +36 -5
  101. package/dist/core/hidden-stdout-log.js.map +1 -1
  102. package/dist/core/model-config-schema.d.ts +85 -12
  103. package/dist/core/model-config-schema.d.ts.map +1 -1
  104. package/dist/core/model-config-schema.js +25 -0
  105. package/dist/core/model-config-schema.js.map +1 -1
  106. package/dist/core/model-runtime.d.ts +34 -1
  107. package/dist/core/model-runtime.d.ts.map +1 -1
  108. package/dist/core/model-runtime.js +98 -5
  109. package/dist/core/model-runtime.js.map +1 -1
  110. package/dist/core/tools/output-accumulator.d.ts +2 -0
  111. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  112. package/dist/core/tools/output-accumulator.js +32 -7
  113. package/dist/core/tools/output-accumulator.js.map +1 -1
  114. package/dist/index.d.ts +1 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +1 -1
  117. package/dist/index.js.map +1 -1
  118. package/dist/main.d.ts.map +1 -1
  119. package/dist/main.js +38 -3
  120. package/dist/main.js.map +1 -1
  121. package/dist/modes/app-server/server/account.d.ts.map +1 -1
  122. package/dist/modes/app-server/server/account.js +5 -5
  123. package/dist/modes/app-server/server/account.js.map +1 -1
  124. package/dist/modes/index.d.ts +1 -0
  125. package/dist/modes/index.d.ts.map +1 -1
  126. package/dist/modes/index.js +1 -0
  127. package/dist/modes/index.js.map +1 -1
  128. package/dist/modes/interactive/components/footer.d.ts +11 -3
  129. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/footer.js +31 -1
  131. package/dist/modes/interactive/components/footer.js.map +1 -1
  132. package/dist/modes/interactive/components/tool-execution-fallback.d.ts.map +1 -1
  133. package/dist/modes/interactive/components/tool-execution-fallback.js +82 -1
  134. package/dist/modes/interactive/components/tool-execution-fallback.js.map +1 -1
  135. package/dist/modes/interactive/grok/chrome.d.ts +4 -4
  136. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  137. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  138. package/dist/modes/interactive/grok/footer.d.ts +3 -3
  139. package/dist/modes/interactive/grok/footer.d.ts.map +1 -1
  140. package/dist/modes/interactive/grok/footer.js.map +1 -1
  141. package/dist/modes/interactive/interactive-host-runtime.d.ts +44 -0
  142. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -0
  143. package/dist/modes/interactive/interactive-host-runtime.js +271 -0
  144. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -0
  145. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  146. package/dist/modes/interactive/interactive-mode.js +65 -21
  147. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  149. package/dist/modes/rpc/connection-handler.js +19 -6
  150. package/dist/modes/rpc/connection-handler.js.map +1 -1
  151. package/dist/modes/rpc/host-ensure.d.ts +41 -0
  152. package/dist/modes/rpc/host-ensure.d.ts.map +1 -0
  153. package/dist/modes/rpc/host-ensure.js +245 -0
  154. package/dist/modes/rpc/host-ensure.js.map +1 -0
  155. package/dist/modes/rpc/host-lifecycle.d.ts +57 -0
  156. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -0
  157. package/dist/modes/rpc/host-lifecycle.js +491 -0
  158. package/dist/modes/rpc/host-lifecycle.js.map +1 -0
  159. package/dist/modes/rpc/host-watchdog.d.ts +33 -0
  160. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -0
  161. package/dist/modes/rpc/host-watchdog.js +142 -0
  162. package/dist/modes/rpc/host-watchdog.js.map +1 -0
  163. package/dist/modes/rpc/multi-session-host.d.ts +7 -1
  164. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  165. package/dist/modes/rpc/multi-session-host.js +206 -32
  166. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  167. package/dist/modes/rpc/rpc-client.d.ts +41 -3
  168. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  169. package/dist/modes/rpc/rpc-client.js +125 -14
  170. package/dist/modes/rpc/rpc-client.js.map +1 -1
  171. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  172. package/dist/modes/rpc/rpc-mode.js +1 -1
  173. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  174. package/dist/modes/rpc/rpc-types.d.ts +6 -2
  175. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  176. package/dist/modes/rpc/rpc-types.js.map +1 -1
  177. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  178. package/dist/modes/rpc/session-command-router.js +24 -6
  179. package/dist/modes/rpc/session-command-router.js.map +1 -1
  180. package/dist/modes/rpc/session-event-writer.d.ts +13 -2
  181. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  182. package/dist/modes/rpc/session-event-writer.js +65 -25
  183. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  184. package/dist/modes/rpc/session-registry.d.ts +9 -0
  185. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  186. package/dist/modes/rpc/session-registry.js +31 -3
  187. package/dist/modes/rpc/session-registry.js.map +1 -1
  188. package/docs/providers.md +48 -0
  189. package/docs/rpc.md +59 -5
  190. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +44 -0
  191. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  192. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +13 -1
  193. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +15 -1
  194. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +711 -1
  195. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +14 -1
  196. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -10
  197. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +9 -3
  198. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +6 -1
  199. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
  200. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +20 -4
  201. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
  202. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts +1 -1
  203. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts.map +1 -1
  204. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js.map +1 -1
  205. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts +21 -0
  206. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts.map +1 -0
  207. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js +27 -0
  208. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js.map +1 -0
  209. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts +24 -0
  210. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts.map +1 -1
  211. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js.map +1 -1
  212. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts.map +1 -1
  213. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js +7 -2
  214. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js.map +1 -1
  215. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  216. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts +1 -1
  217. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts.map +1 -1
  218. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js +46 -14
  219. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js.map +1 -1
  220. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +11 -0
  221. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  222. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +30 -16
  223. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  224. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts +2 -1
  225. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts.map +1 -1
  226. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js +2 -1
  227. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js.map +1 -1
  228. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts +16 -0
  229. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts.map +1 -0
  230. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js +75 -0
  231. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js.map +1 -0
  232. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +45 -0
  233. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -0
  234. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +81 -0
  235. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -0
  236. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts +31 -0
  237. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts.map +1 -0
  238. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js +65 -0
  239. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js.map +1 -0
  240. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +12 -0
  241. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +37 -0
  243. package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
  244. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts +6 -0
  245. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
  246. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +32 -6
  247. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts +3 -2
  249. package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts.map +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/auth/types.js.map +1 -1
  251. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +6 -0
  252. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +6 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +75 -0
  256. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  257. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +75 -0
  258. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  259. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  260. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  261. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  262. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  263. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  264. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  265. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  266. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  267. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  268. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
  269. package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
  270. package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
  271. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  272. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  273. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  274. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  275. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  276. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  277. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  278. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  279. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  280. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  281. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  282. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  283. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  284. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  285. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +4 -0
  286. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  287. package/node_modules/@earendil-works/pi-ai/package.json +5 -5
  288. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  289. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  290. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +5 -0
  291. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  292. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +90 -1
  293. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  294. package/node_modules/@earendil-works/pi-tui/dist/tui.js +1 -1
  295. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  296. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  297. package/package.json +7 -7
@@ -1,7 +1,8 @@
1
1
  import type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from "./types.ts";
2
2
  /**
3
3
  * Default in-memory credential store. Apps inject persistent stores.
4
- * Keyed by `Provider.id`, one credential per provider; see `CredentialStore`.
4
+ * Keyed by `Provider.id`, one entry per provider (possibly a pooled one);
5
+ * see `CredentialStore`.
5
6
  * Writes are serialized per provider through a promise chain.
6
7
  */
7
8
  export declare class InMemoryCredentialStore implements CredentialStore {
@@ -1 +1 @@
1
- {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../src/auth/credential-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAC9D,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,MAAM,CAAuC;IAErD,8FAA8F;IAC9F,OAAO,CAAC,OAAO;IAgBT,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKzF,IAAI,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC;IAK9E,MAAM,CACL,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,KAAK,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,EACxE,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAclC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CASzE"}
1
+ {"version":3,"file":"credential-store.d.ts","sourceRoot":"","sources":["../../src/auth/credential-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEpG;;;;;GAKG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAC9D,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,MAAM,CAAuC;IAErD,8FAA8F;IAC9F,OAAO,CAAC,OAAO;IAgBT,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKzF,IAAI,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC;IAK9E,MAAM,CACL,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,KAAK,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,EACxE,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAclC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CASzE"}
@@ -1,7 +1,8 @@
1
1
  import { operationSignal, raceWithAbortSignal } from "../utils/abort.js";
2
2
  /**
3
3
  * Default in-memory credential store. Apps inject persistent stores.
4
- * Keyed by `Provider.id`, one credential per provider; see `CredentialStore`.
4
+ * Keyed by `Provider.id`, one entry per provider (possibly a pooled one);
5
+ * see `CredentialStore`.
5
6
  * Writes are serialized per provider through a promise chain.
6
7
  */
7
8
  export class InMemoryCredentialStore {
@@ -1 +1 @@
1
- {"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/auth/credential-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGzE;;;;GAIG;AACH,MAAM,OAAO,uBAAuB;IAApC;QACS,gBAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC5C,WAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAwDtD,CAAC;IAtDA,8FAA8F;IACtF,OAAO,CAAI,UAAkB,EAAE,IAAsB,EAAE,OAA8B;QAC5F,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,IAAI,EAAE,CAAC;QACf,CAAC,CAAC,EAAE,CAAC;QACL,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAClC,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACnB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,OAAO,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAkB,EAAE,OAA8B;QAC5D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA8B;QACxC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,MAAM,CACL,UAAkB,EAClB,EAAwE,EACxE,OAA8B;QAE9B,OAAO,IAAI,CAAC,OAAO,CAClB,UAAU,EACV,KAAK,IAAI,EAAE;YACV,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACjD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;YAC/B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAC/D,OAAO,IAAI,IAAI,OAAO,CAAC;QACxB,CAAC,EACD,OAAO,CACP,CAAC;IACH,CAAC;IAED,MAAM,CAAC,UAAkB,EAAE,OAA8B;QACxD,OAAO,IAAI,CAAC,OAAO,CAClB,UAAU,EACV,KAAK,IAAI,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACrC,CAAC,EACD,OAAO,CACP,CAAC;IACH,CAAC;CACD","sourcesContent":["import { operationSignal, raceWithAbortSignal } from \"../utils/abort.ts\";\nimport type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from \"./types.ts\";\n\n/**\n * Default in-memory credential store. Apps inject persistent stores.\n * Keyed by `Provider.id`, one credential per provider; see `CredentialStore`.\n * Writes are serialized per provider through a promise chain.\n */\nexport class InMemoryCredentialStore implements CredentialStore {\n\tprivate credentials = new Map<string, Credential>();\n\tprivate chains = new Map<string, Promise<unknown>>();\n\n\t/** Serialize tasks per provider id without releasing the chain before active work settles. */\n\tprivate enqueue<T>(providerId: string, task: () => Promise<T>, options?: AuthOperationOptions): Promise<T> {\n\t\tconst signal = operationSignal(options?.signal);\n\t\tconst previous = this.chains.get(providerId) ?? Promise.resolve();\n\t\tconst queued = (async () => {\n\t\t\tawait previous.catch(() => {});\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn task();\n\t\t})();\n\t\tconst tail = queued.catch(() => {});\n\t\tthis.chains.set(providerId, tail);\n\t\tvoid tail.then(() => {\n\t\t\tif (this.chains.get(providerId) === tail) this.chains.delete(providerId);\n\t\t});\n\t\treturn raceWithAbortSignal(queued, signal);\n\t}\n\n\tasync read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined> {\n\t\toptions?.signal?.throwIfAborted();\n\t\treturn this.credentials.get(providerId);\n\t}\n\n\tasync list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]> {\n\t\toptions?.signal?.throwIfAborted();\n\t\treturn [...this.credentials].map(([providerId, credential]) => ({ providerId, type: credential.type }));\n\t}\n\n\tmodify(\n\t\tproviderId: string,\n\t\tfn: (current: Credential | undefined) => Promise<Credential | undefined>,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined> {\n\t\treturn this.enqueue(\n\t\t\tproviderId,\n\t\t\tasync () => {\n\t\t\t\tconst current = this.credentials.get(providerId);\n\t\t\t\tconst next = await fn(current);\n\t\t\t\toptions?.signal?.throwIfAborted();\n\t\t\t\tif (next !== undefined) this.credentials.set(providerId, next);\n\t\t\t\treturn next ?? current;\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n\n\tdelete(providerId: string, options?: AuthOperationOptions): Promise<void> {\n\t\treturn this.enqueue(\n\t\t\tproviderId,\n\t\t\tasync () => {\n\t\t\t\tthis.credentials.delete(providerId);\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/auth/credential-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGzE;;;;;GAKG;AACH,MAAM,OAAO,uBAAuB;IAApC;QACS,gBAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC5C,WAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAwDtD,CAAC;IAtDA,8FAA8F;IACtF,OAAO,CAAI,UAAkB,EAAE,IAAsB,EAAE,OAA8B;QAC5F,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,IAAI,EAAE,CAAC;QACf,CAAC,CAAC,EAAE,CAAC;QACL,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAClC,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACnB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;QACH,OAAO,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAkB,EAAE,OAA8B;QAC5D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA8B;QACxC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QAClC,OAAO,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,MAAM,CACL,UAAkB,EAClB,EAAwE,EACxE,OAA8B;QAE9B,OAAO,IAAI,CAAC,OAAO,CAClB,UAAU,EACV,KAAK,IAAI,EAAE;YACV,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACjD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;YAC/B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAC/D,OAAO,IAAI,IAAI,OAAO,CAAC;QACxB,CAAC,EACD,OAAO,CACP,CAAC;IACH,CAAC;IAED,MAAM,CAAC,UAAkB,EAAE,OAA8B;QACxD,OAAO,IAAI,CAAC,OAAO,CAClB,UAAU,EACV,KAAK,IAAI,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACrC,CAAC,EACD,OAAO,CACP,CAAC;IACH,CAAC;CACD","sourcesContent":["import { operationSignal, raceWithAbortSignal } from \"../utils/abort.ts\";\nimport type { AuthOperationOptions, Credential, CredentialInfo, CredentialStore } from \"./types.ts\";\n\n/**\n * Default in-memory credential store. Apps inject persistent stores.\n * Keyed by `Provider.id`, one entry per provider (possibly a pooled one);\n * see `CredentialStore`.\n * Writes are serialized per provider through a promise chain.\n */\nexport class InMemoryCredentialStore implements CredentialStore {\n\tprivate credentials = new Map<string, Credential>();\n\tprivate chains = new Map<string, Promise<unknown>>();\n\n\t/** Serialize tasks per provider id without releasing the chain before active work settles. */\n\tprivate enqueue<T>(providerId: string, task: () => Promise<T>, options?: AuthOperationOptions): Promise<T> {\n\t\tconst signal = operationSignal(options?.signal);\n\t\tconst previous = this.chains.get(providerId) ?? Promise.resolve();\n\t\tconst queued = (async () => {\n\t\t\tawait previous.catch(() => {});\n\t\t\tsignal.throwIfAborted();\n\t\t\treturn task();\n\t\t})();\n\t\tconst tail = queued.catch(() => {});\n\t\tthis.chains.set(providerId, tail);\n\t\tvoid tail.then(() => {\n\t\t\tif (this.chains.get(providerId) === tail) this.chains.delete(providerId);\n\t\t});\n\t\treturn raceWithAbortSignal(queued, signal);\n\t}\n\n\tasync read(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined> {\n\t\toptions?.signal?.throwIfAborted();\n\t\treturn this.credentials.get(providerId);\n\t}\n\n\tasync list(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]> {\n\t\toptions?.signal?.throwIfAborted();\n\t\treturn [...this.credentials].map(([providerId, credential]) => ({ providerId, type: credential.type }));\n\t}\n\n\tmodify(\n\t\tproviderId: string,\n\t\tfn: (current: Credential | undefined) => Promise<Credential | undefined>,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined> {\n\t\treturn this.enqueue(\n\t\t\tproviderId,\n\t\t\tasync () => {\n\t\t\t\tconst current = this.credentials.get(providerId);\n\t\t\t\tconst next = await fn(current);\n\t\t\t\toptions?.signal?.throwIfAborted();\n\t\t\t\tif (next !== undefined) this.credentials.set(providerId, next);\n\t\t\t\treturn next ?? current;\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n\n\tdelete(providerId: string, options?: AuthOperationOptions): Promise<void> {\n\t\treturn this.enqueue(\n\t\t\tproviderId,\n\t\t\tasync () => {\n\t\t\t\tthis.credentials.delete(providerId);\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t}\n}\n"]}
@@ -0,0 +1,16 @@
1
+ export type PoolFailureAction = "rotate" | "retry" | "fail";
2
+ export type PoolBlockReason = "auth_error" | "rate_limit";
3
+ export type PoolFailureClassification = {
4
+ action: PoolFailureAction;
5
+ blockReason?: PoolBlockReason;
6
+ retryAfterMs?: number;
7
+ };
8
+ /**
9
+ * Three-way in-lane failure taxonomy. Rotation is reserved for failures another
10
+ * account can plausibly absorb (rate/capacity and per-account auth); transient
11
+ * transport failures stay on the same slot for the outer retry policy, and
12
+ * everything unrecognized default-denies to `fail` so the model fallback chain
13
+ * above this engine keeps owning unknown errors.
14
+ */
15
+ export declare function classifyPoolFailure(error: unknown): PoolFailureClassification;
16
+ //# sourceMappingURL=classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../../src/auth/pool/classify.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5D,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,YAAY,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAmDF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,yBAAyB,CAU7E"}
@@ -0,0 +1,75 @@
1
+ const MAX_NESTING_DEPTH = 3;
2
+ function fieldOf(value, key) {
3
+ if (typeof value !== "object" || value === null)
4
+ return undefined;
5
+ const entry = Object.entries(value).find(([candidate]) => candidate === key);
6
+ return entry?.[1];
7
+ }
8
+ function statusOf(error, depth = 0) {
9
+ if (depth >= MAX_NESTING_DEPTH)
10
+ return undefined;
11
+ for (const key of ["status", "statusCode"]) {
12
+ const value = fieldOf(error, key);
13
+ if (typeof value === "number")
14
+ return value;
15
+ }
16
+ const nested = fieldOf(error, "error");
17
+ return nested === undefined ? undefined : statusOf(nested, depth + 1);
18
+ }
19
+ function messageOf(error, depth = 0) {
20
+ if (typeof error === "string")
21
+ return error;
22
+ if (error instanceof Error)
23
+ return error.message;
24
+ if (depth >= MAX_NESTING_DEPTH)
25
+ return String(error);
26
+ const message = fieldOf(error, "message");
27
+ if (typeof message === "string")
28
+ return message;
29
+ const nested = fieldOf(error, "error");
30
+ return nested === undefined ? String(error) : messageOf(nested, depth + 1);
31
+ }
32
+ function retryAfterMsOf(error, text) {
33
+ const explicit = fieldOf(error, "retryAfterMs");
34
+ if (typeof explicit === "number" && Number.isFinite(explicit) && explicit > 0)
35
+ return Math.ceil(explicit);
36
+ const milliseconds = text.match(/\bretry[-_ ]?after[-_ ]?ms\s*[:=]\s*(\d+(?:\.\d+)?)/i);
37
+ if (milliseconds?.[1] !== undefined)
38
+ return Math.ceil(Number(milliseconds[1]));
39
+ const seconds = text.match(/\bretry[-_ ]?after\s*[:=]\s*(\d+(?:\.\d+)?)/i);
40
+ return seconds?.[1] === undefined ? undefined : Math.ceil(Number(seconds[1]) * 1_000);
41
+ }
42
+ const RATE_LIMIT_TEXT = /rate.?limit|resource_exhausted|quota|too many requests|overloaded/i;
43
+ const AUTH_TEXT = /invalid.?api.?key|unauthorized|authentication|forbidden|token (?:is )?expired|revoked/i;
44
+ const TRANSIENT_TEXT = /econnreset|econnrefused|etimedout|socket hang up|fetch failed|network error|internal server error|service unavailable|bad gateway|gateway timeout/i;
45
+ function rotateOnRateLimit(error, text) {
46
+ const retryAfterMs = retryAfterMsOf(error, text);
47
+ return retryAfterMs === undefined
48
+ ? { action: "rotate", blockReason: "rate_limit" }
49
+ : { action: "rotate", blockReason: "rate_limit", retryAfterMs };
50
+ }
51
+ /**
52
+ * Three-way in-lane failure taxonomy. Rotation is reserved for failures another
53
+ * account can plausibly absorb (rate/capacity and per-account auth); transient
54
+ * transport failures stay on the same slot for the outer retry policy, and
55
+ * everything unrecognized default-denies to `fail` so the model fallback chain
56
+ * above this engine keeps owning unknown errors.
57
+ */
58
+ export function classifyPoolFailure(error) {
59
+ const text = messageOf(error);
60
+ const status = statusOf(error);
61
+ if (status === 429 || status === 529)
62
+ return rotateOnRateLimit(error, text);
63
+ if (status === 401 || status === 403)
64
+ return { action: "rotate", blockReason: "auth_error" };
65
+ if (status !== undefined && status >= 500 && status < 600)
66
+ return { action: "retry" };
67
+ if (RATE_LIMIT_TEXT.test(text))
68
+ return rotateOnRateLimit(error, text);
69
+ if (AUTH_TEXT.test(text))
70
+ return { action: "rotate", blockReason: "auth_error" };
71
+ if (TRANSIENT_TEXT.test(text))
72
+ return { action: "retry" };
73
+ return { action: "fail" };
74
+ }
75
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../../src/auth/pool/classify.ts"],"names":[],"mappings":"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","sourcesContent":["export type PoolFailureAction = \"rotate\" | \"retry\" | \"fail\";\n\nexport type PoolBlockReason = \"auth_error\" | \"rate_limit\";\n\nexport type PoolFailureClassification = {\n\taction: PoolFailureAction;\n\tblockReason?: PoolBlockReason;\n\tretryAfterMs?: number;\n};\n\nconst MAX_NESTING_DEPTH = 3;\n\nfunction fieldOf(value: unknown, key: string): unknown {\n\tif (typeof value !== \"object\" || value === null) return undefined;\n\tconst entry = Object.entries(value).find(([candidate]) => candidate === key);\n\treturn entry?.[1];\n}\n\nfunction statusOf(error: unknown, depth = 0): number | undefined {\n\tif (depth >= MAX_NESTING_DEPTH) return undefined;\n\tfor (const key of [\"status\", \"statusCode\"]) {\n\t\tconst value = fieldOf(error, key);\n\t\tif (typeof value === \"number\") return value;\n\t}\n\tconst nested = fieldOf(error, \"error\");\n\treturn nested === undefined ? undefined : statusOf(nested, depth + 1);\n}\n\nfunction messageOf(error: unknown, depth = 0): string {\n\tif (typeof error === \"string\") return error;\n\tif (error instanceof Error) return error.message;\n\tif (depth >= MAX_NESTING_DEPTH) return String(error);\n\tconst message = fieldOf(error, \"message\");\n\tif (typeof message === \"string\") return message;\n\tconst nested = fieldOf(error, \"error\");\n\treturn nested === undefined ? String(error) : messageOf(nested, depth + 1);\n}\n\nfunction retryAfterMsOf(error: unknown, text: string): number | undefined {\n\tconst explicit = fieldOf(error, \"retryAfterMs\");\n\tif (typeof explicit === \"number\" && Number.isFinite(explicit) && explicit > 0) return Math.ceil(explicit);\n\tconst milliseconds = text.match(/\\bretry[-_ ]?after[-_ ]?ms\\s*[:=]\\s*(\\d+(?:\\.\\d+)?)/i);\n\tif (milliseconds?.[1] !== undefined) return Math.ceil(Number(milliseconds[1]));\n\tconst seconds = text.match(/\\bretry[-_ ]?after\\s*[:=]\\s*(\\d+(?:\\.\\d+)?)/i);\n\treturn seconds?.[1] === undefined ? undefined : Math.ceil(Number(seconds[1]) * 1_000);\n}\n\nconst RATE_LIMIT_TEXT = /rate.?limit|resource_exhausted|quota|too many requests|overloaded/i;\nconst AUTH_TEXT = /invalid.?api.?key|unauthorized|authentication|forbidden|token (?:is )?expired|revoked/i;\nconst TRANSIENT_TEXT =\n\t/econnreset|econnrefused|etimedout|socket hang up|fetch failed|network error|internal server error|service unavailable|bad gateway|gateway timeout/i;\n\nfunction rotateOnRateLimit(error: unknown, text: string): PoolFailureClassification {\n\tconst retryAfterMs = retryAfterMsOf(error, text);\n\treturn retryAfterMs === undefined\n\t\t? { action: \"rotate\", blockReason: \"rate_limit\" }\n\t\t: { action: \"rotate\", blockReason: \"rate_limit\", retryAfterMs };\n}\n\n/**\n * Three-way in-lane failure taxonomy. Rotation is reserved for failures another\n * account can plausibly absorb (rate/capacity and per-account auth); transient\n * transport failures stay on the same slot for the outer retry policy, and\n * everything unrecognized default-denies to `fail` so the model fallback chain\n * above this engine keeps owning unknown errors.\n */\nexport function classifyPoolFailure(error: unknown): PoolFailureClassification {\n\tconst text = messageOf(error);\n\tconst status = statusOf(error);\n\tif (status === 429 || status === 529) return rotateOnRateLimit(error, text);\n\tif (status === 401 || status === 403) return { action: \"rotate\", blockReason: \"auth_error\" };\n\tif (status !== undefined && status >= 500 && status < 600) return { action: \"retry\" };\n\tif (RATE_LIMIT_TEXT.test(text)) return rotateOnRateLimit(error, text);\n\tif (AUTH_TEXT.test(text)) return { action: \"rotate\", blockReason: \"auth_error\" };\n\tif (TRANSIENT_TEXT.test(text)) return { action: \"retry\" };\n\treturn { action: \"fail\" };\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { type PoolFailureClassification } from "./classify.ts";
2
+ import type { SelectableSlot } from "./select.ts";
3
+ /** Must stay byte-identical to the claude-sdk-oauth marker AgentSession already honors. */
4
+ export declare const TURN_RETRY_SUPPRESSION_PREFIX = "senpi:no-turn-retry:";
5
+ export declare const DEFAULT_SLOT_BLOCK_MS = 60000;
6
+ export declare const MAX_SLOT_BLOCK_MS: number;
7
+ export type PoolFailoverEvent<TSlot extends SelectableSlot> = {
8
+ slot: TSlot;
9
+ nextSlot?: TSlot;
10
+ classification: PoolFailureClassification;
11
+ attempt: number;
12
+ committedOutput: boolean;
13
+ };
14
+ export type PoolFailoverOptions<TEvent, TSlot extends SelectableSlot> = {
15
+ slots: readonly TSlot[];
16
+ select: (slots: readonly TSlot[]) => TSlot;
17
+ runAttempt: (slot: TSlot) => AsyncIterable<TEvent> | Promise<AsyncIterable<TEvent>>;
18
+ classify?: (error: unknown) => PoolFailureClassification;
19
+ /**
20
+ * Rotation is transparent only before committed output. Absent, every yielded
21
+ * event counts as committed (default-DENY), so silent mid-stream rotation
22
+ * requires an explicit opt-in from a caller that knows which events are
23
+ * bookkeeping rather than user-visible output.
24
+ */
25
+ isCommittedOutput?: (event: TEvent) => boolean;
26
+ persistBlock?: (slot: TSlot) => void | Promise<void>;
27
+ onRotate?: (event: PoolFailoverEvent<TSlot>) => void | Promise<void>;
28
+ now?: () => number;
29
+ baseBlockMs?: number;
30
+ };
31
+ export declare class PoolFailoverError extends Error {
32
+ readonly classification: PoolFailureClassification;
33
+ readonly original: unknown;
34
+ readonly suppressTurnRetry: boolean;
35
+ constructor(classification: PoolFailureClassification, original: unknown, suppressTurnRetry: boolean);
36
+ }
37
+ /**
38
+ * Runs at most one attempt per slot. A rotation is transparent only while no
39
+ * committed output has reached the caller; afterwards the classified error is
40
+ * thrown with the turn-retry suppression marker so the session layer never
41
+ * replays a partially delivered turn. Non-rotate classes throw immediately and
42
+ * compose with the retry/model-fallback machinery above this engine.
43
+ */
44
+ export declare function runSlotFailover<TEvent, TSlot extends SelectableSlot>(options: PoolFailoverOptions<TEvent, TSlot>): AsyncGenerator<TEvent>;
45
+ //# sourceMappingURL=failover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failover.d.ts","sourceRoot":"","sources":["../../../src/auth/pool/failover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,yBAAyB,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,2FAA2F;AAC3F,eAAO,MAAM,6BAA6B,yBAAyB,CAAC;AACpE,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C,eAAO,MAAM,iBAAiB,QAAuB,CAAC;AAEtD,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,cAAc,IAAI;IAC7D,IAAI,EAAE,KAAK,CAAC;IACZ,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,cAAc,EAAE,yBAAyB,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,MAAM,EAAE,KAAK,SAAS,cAAc,IAAI;IACvE,KAAK,EAAE,SAAS,KAAK,EAAE,CAAC;IACxB,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,KAAK,KAAK,CAAC;IAC3C,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IACpF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,yBAAyB,CAAC;IACzD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,qBAAa,iBAAkB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,cAAc,EAAE,yBAAyB,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;gBAExB,cAAc,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,OAAO;CAQpG;AAoBD;;;;;;GAMG;AACH,wBAAuB,eAAe,CAAC,MAAM,EAAE,KAAK,SAAS,cAAc,EAC1E,OAAO,EAAE,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,GACzC,cAAc,CAAC,MAAM,CAAC,CA4CxB"}
@@ -0,0 +1,81 @@
1
+ import { classifyPoolFailure } from "./classify.js";
2
+ /** Must stay byte-identical to the claude-sdk-oauth marker AgentSession already honors. */
3
+ export const TURN_RETRY_SUPPRESSION_PREFIX = "senpi:no-turn-retry:";
4
+ export const DEFAULT_SLOT_BLOCK_MS = 60_000;
5
+ export const MAX_SLOT_BLOCK_MS = 48 * 60 * 60 * 1_000;
6
+ export class PoolFailoverError extends Error {
7
+ constructor(classification, original, suppressTurnRetry) {
8
+ const detail = original instanceof Error ? original.message : String(original);
9
+ super(`${suppressTurnRetry ? TURN_RETRY_SUPPRESSION_PREFIX : ""}${detail}`);
10
+ this.name = "PoolFailoverError";
11
+ this.classification = classification;
12
+ this.original = original;
13
+ this.suppressTurnRetry = suppressTurnRetry;
14
+ }
15
+ }
16
+ function blockedSlot(slot, classification, now, attempt, baseBlockMs) {
17
+ if (classification.blockReason === "auth_error") {
18
+ // An auth block has no expiry: only a re-login rewrites the slot.
19
+ const next = { ...slot, blockReason: "auth_error" };
20
+ delete next.blockedUntil;
21
+ return next;
22
+ }
23
+ const fallback = Math.min(MAX_SLOT_BLOCK_MS, baseBlockMs * 2 ** attempt);
24
+ const duration = Math.min(MAX_SLOT_BLOCK_MS, classification.retryAfterMs ?? fallback);
25
+ return { ...slot, blockedUntil: now + duration, blockReason: "rate_limit" };
26
+ }
27
+ /**
28
+ * Runs at most one attempt per slot. A rotation is transparent only while no
29
+ * committed output has reached the caller; afterwards the classified error is
30
+ * thrown with the turn-retry suppression marker so the session layer never
31
+ * replays a partially delivered turn. Non-rotate classes throw immediately and
32
+ * compose with the retry/model-fallback machinery above this engine.
33
+ */
34
+ export async function* runSlotFailover(options) {
35
+ const now = options.now ?? Date.now;
36
+ const classify = options.classify ?? classifyPoolFailure;
37
+ const committed = options.isCommittedOutput ?? (() => true);
38
+ const baseBlockMs = options.baseBlockMs ?? DEFAULT_SLOT_BLOCK_MS;
39
+ let slots = [...options.slots];
40
+ let lastError;
41
+ for (let attempt = 0; attempt < options.slots.length; attempt++) {
42
+ const slot = options.select(slots);
43
+ let committedOutput = false;
44
+ try {
45
+ const attemptStream = await options.runAttempt(slot);
46
+ for await (const event of attemptStream) {
47
+ committedOutput ||= committed(event);
48
+ yield event;
49
+ }
50
+ return;
51
+ }
52
+ catch (error) {
53
+ const classification = classify(error);
54
+ const failure = new PoolFailoverError(classification, error, committedOutput);
55
+ lastError = failure;
56
+ if (classification.action !== "rotate")
57
+ throw failure;
58
+ const blocked = blockedSlot(slot, classification, now(), attempt, baseBlockMs);
59
+ slots = slots.map((candidate) => (candidate.name === blocked.name ? blocked : candidate));
60
+ await options.persistBlock?.(blocked);
61
+ const rotation = {
62
+ slot: blocked,
63
+ classification,
64
+ attempt: attempt + 1,
65
+ committedOutput,
66
+ };
67
+ try {
68
+ if (!committedOutput && attempt + 1 < slots.length) {
69
+ rotation.nextSlot = options.select(slots);
70
+ }
71
+ }
72
+ finally {
73
+ await options.onRotate?.(rotation);
74
+ }
75
+ if (committedOutput)
76
+ throw failure;
77
+ }
78
+ }
79
+ throw lastError ?? new Error("Credential pool failover exhausted without an attempt");
80
+ }
81
+ //# sourceMappingURL=failover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failover.js","sourceRoot":"","sources":["../../../src/auth/pool/failover.ts"],"names":[],"mappings":"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","sourcesContent":["import { classifyPoolFailure, type PoolFailureClassification } from \"./classify.ts\";\nimport type { SelectableSlot } from \"./select.ts\";\n\n/** Must stay byte-identical to the claude-sdk-oauth marker AgentSession already honors. */\nexport const TURN_RETRY_SUPPRESSION_PREFIX = \"senpi:no-turn-retry:\";\nexport const DEFAULT_SLOT_BLOCK_MS = 60_000;\nexport const MAX_SLOT_BLOCK_MS = 48 * 60 * 60 * 1_000;\n\nexport type PoolFailoverEvent<TSlot extends SelectableSlot> = {\n\tslot: TSlot;\n\tnextSlot?: TSlot;\n\tclassification: PoolFailureClassification;\n\tattempt: number;\n\tcommittedOutput: boolean;\n};\n\nexport type PoolFailoverOptions<TEvent, TSlot extends SelectableSlot> = {\n\tslots: readonly TSlot[];\n\tselect: (slots: readonly TSlot[]) => TSlot;\n\trunAttempt: (slot: TSlot) => AsyncIterable<TEvent> | Promise<AsyncIterable<TEvent>>;\n\tclassify?: (error: unknown) => PoolFailureClassification;\n\t/**\n\t * Rotation is transparent only before committed output. Absent, every yielded\n\t * event counts as committed (default-DENY), so silent mid-stream rotation\n\t * requires an explicit opt-in from a caller that knows which events are\n\t * bookkeeping rather than user-visible output.\n\t */\n\tisCommittedOutput?: (event: TEvent) => boolean;\n\tpersistBlock?: (slot: TSlot) => void | Promise<void>;\n\tonRotate?: (event: PoolFailoverEvent<TSlot>) => void | Promise<void>;\n\tnow?: () => number;\n\tbaseBlockMs?: number;\n};\n\nexport class PoolFailoverError extends Error {\n\treadonly classification: PoolFailureClassification;\n\treadonly original: unknown;\n\treadonly suppressTurnRetry: boolean;\n\n\tconstructor(classification: PoolFailureClassification, original: unknown, suppressTurnRetry: boolean) {\n\t\tconst detail = original instanceof Error ? original.message : String(original);\n\t\tsuper(`${suppressTurnRetry ? TURN_RETRY_SUPPRESSION_PREFIX : \"\"}${detail}`);\n\t\tthis.name = \"PoolFailoverError\";\n\t\tthis.classification = classification;\n\t\tthis.original = original;\n\t\tthis.suppressTurnRetry = suppressTurnRetry;\n\t}\n}\n\nfunction blockedSlot<TSlot extends SelectableSlot>(\n\tslot: TSlot,\n\tclassification: PoolFailureClassification,\n\tnow: number,\n\tattempt: number,\n\tbaseBlockMs: number,\n): TSlot {\n\tif (classification.blockReason === \"auth_error\") {\n\t\t// An auth block has no expiry: only a re-login rewrites the slot.\n\t\tconst next = { ...slot, blockReason: \"auth_error\" };\n\t\tdelete next.blockedUntil;\n\t\treturn next;\n\t}\n\tconst fallback = Math.min(MAX_SLOT_BLOCK_MS, baseBlockMs * 2 ** attempt);\n\tconst duration = Math.min(MAX_SLOT_BLOCK_MS, classification.retryAfterMs ?? fallback);\n\treturn { ...slot, blockedUntil: now + duration, blockReason: \"rate_limit\" };\n}\n\n/**\n * Runs at most one attempt per slot. A rotation is transparent only while no\n * committed output has reached the caller; afterwards the classified error is\n * thrown with the turn-retry suppression marker so the session layer never\n * replays a partially delivered turn. Non-rotate classes throw immediately and\n * compose with the retry/model-fallback machinery above this engine.\n */\nexport async function* runSlotFailover<TEvent, TSlot extends SelectableSlot>(\n\toptions: PoolFailoverOptions<TEvent, TSlot>,\n): AsyncGenerator<TEvent> {\n\tconst now = options.now ?? Date.now;\n\tconst classify = options.classify ?? classifyPoolFailure;\n\tconst committed = options.isCommittedOutput ?? (() => true);\n\tconst baseBlockMs = options.baseBlockMs ?? DEFAULT_SLOT_BLOCK_MS;\n\tlet slots = [...options.slots];\n\tlet lastError: PoolFailoverError | undefined;\n\n\tfor (let attempt = 0; attempt < options.slots.length; attempt++) {\n\t\tconst slot = options.select(slots);\n\t\tlet committedOutput = false;\n\t\ttry {\n\t\t\tconst attemptStream = await options.runAttempt(slot);\n\t\t\tfor await (const event of attemptStream) {\n\t\t\t\tcommittedOutput ||= committed(event);\n\t\t\t\tyield event;\n\t\t\t}\n\t\t\treturn;\n\t\t} catch (error) {\n\t\t\tconst classification = classify(error);\n\t\t\tconst failure = new PoolFailoverError(classification, error, committedOutput);\n\t\t\tlastError = failure;\n\t\t\tif (classification.action !== \"rotate\") throw failure;\n\n\t\t\tconst blocked = blockedSlot(slot, classification, now(), attempt, baseBlockMs);\n\t\t\tslots = slots.map((candidate) => (candidate.name === blocked.name ? blocked : candidate));\n\t\t\tawait options.persistBlock?.(blocked);\n\t\t\tconst rotation: PoolFailoverEvent<TSlot> = {\n\t\t\t\tslot: blocked,\n\t\t\t\tclassification,\n\t\t\t\tattempt: attempt + 1,\n\t\t\t\tcommittedOutput,\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tif (!committedOutput && attempt + 1 < slots.length) {\n\t\t\t\t\trotation.nextSlot = options.select(slots);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tawait options.onRotate?.(rotation);\n\t\t\t}\n\t\t\tif (committedOutput) throw failure;\n\t\t}\n\t}\n\tthrow lastError ?? new Error(\"Credential pool failover exhausted without an attempt\");\n}\n"]}
@@ -0,0 +1,31 @@
1
+ export type SlotHasher = (input: string) => bigint;
2
+ export type SelectableSlot = {
3
+ name: string;
4
+ blockedUntil?: number;
5
+ blockReason?: string;
6
+ };
7
+ export declare const DEFAULT_POOL_AFFINITY_KEY = "credential-pool-default";
8
+ export declare class AllSlotsBlockedError extends Error {
9
+ readonly soonestUnblockAt: number | undefined;
10
+ constructor(soonestUnblockAt: number | undefined);
11
+ }
12
+ export type SlotSelectionOptions = {
13
+ /** Injected so this module stays browser-safe; callers pick the hash (sha256 for oracle parity). */
14
+ hasher: SlotHasher;
15
+ affinityKey?: string;
16
+ sessionId?: string;
17
+ pinnedSlot?: string;
18
+ now?: number;
19
+ };
20
+ export declare function getPoolAffinityKey(options: Pick<SlotSelectionOptions, "affinityKey" | "sessionId">): string;
21
+ /**
22
+ * HRW ordering hashes `key\0slot.name` exactly like the claude-sdk-oauth
23
+ * affinity oracle, so a sha256 hasher reproduces its winner sequence and no
24
+ * live session remaps when a pool migrates onto this engine.
25
+ */
26
+ export declare function rendezvousOrder<T extends SelectableSlot>(key: string, slots: readonly T[], hasher: SlotHasher): T[];
27
+ /** Elapsed rate/capacity blocks clear; auth blocks persist until a login rewrites the slot. */
28
+ export declare function clearExpiredSlotBlocks(slots: readonly SelectableSlot[], now?: number): SelectableSlot[];
29
+ /** Selects a pinned or HRW-ranked slot with no pool-global selection state. */
30
+ export declare function selectSlot<T extends SelectableSlot>(slots: readonly T[], options: SlotSelectionOptions): T;
31
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/auth/pool/select.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;AAEnD,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AAEnE,qBAAa,oBAAqB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;gBAElC,gBAAgB,EAAE,MAAM,GAAG,SAAS;CAShD;AAED,MAAM,MAAM,oBAAoB,GAAG;IAClC,oGAAoG;IACpG,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,aAAa,GAAG,WAAW,CAAC,GAAG,MAAM,CAE3G;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,GAAG,CAAC,EAAE,CAKnH;AAMD,+FAA+F;AAC/F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,GAAG,SAAa,GAAG,cAAc,EAAE,CAQ3G;AAmBD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,CAAC,CAY1G"}
@@ -0,0 +1,65 @@
1
+ export const DEFAULT_POOL_AFFINITY_KEY = "credential-pool-default";
2
+ export class AllSlotsBlockedError extends Error {
3
+ constructor(soonestUnblockAt) {
4
+ super(soonestUnblockAt === undefined
5
+ ? "All credential slots are blocked until re-login."
6
+ : `All credential slots are blocked until ${new Date(soonestUnblockAt).toISOString()}.`);
7
+ this.name = "AllSlotsBlockedError";
8
+ this.soonestUnblockAt = soonestUnblockAt;
9
+ }
10
+ }
11
+ export function getPoolAffinityKey(options) {
12
+ return options.affinityKey ?? options.sessionId ?? DEFAULT_POOL_AFFINITY_KEY;
13
+ }
14
+ /**
15
+ * HRW ordering hashes `key\0slot.name` exactly like the claude-sdk-oauth
16
+ * affinity oracle, so a sha256 hasher reproduces its winner sequence and no
17
+ * live session remaps when a pool migrates onto this engine.
18
+ */
19
+ export function rendezvousOrder(key, slots, hasher) {
20
+ return [...slots]
21
+ .map((slot) => ({ slot, score: hasher(`${key}\0${slot.name}`) }))
22
+ .sort((left, right) => (right.score > left.score ? 1 : right.score < left.score ? -1 : 0))
23
+ .map(({ slot }) => slot);
24
+ }
25
+ function isBlocked(slot, now) {
26
+ return slot.blockReason === "auth_error" || (slot.blockedUntil !== undefined && slot.blockedUntil > now);
27
+ }
28
+ /** Elapsed rate/capacity blocks clear; auth blocks persist until a login rewrites the slot. */
29
+ export function clearExpiredSlotBlocks(slots, now = Date.now()) {
30
+ return slots.map((slot) => {
31
+ if (slot.blockReason !== "auth_error" && slot.blockedUntil !== undefined && slot.blockedUntil <= now) {
32
+ const { blockedUntil: _blockedUntil, blockReason: _blockReason, ...available } = slot;
33
+ return available;
34
+ }
35
+ return slot;
36
+ });
37
+ }
38
+ function selectUnblocked(slots, options, now) {
39
+ const pinned = options.pinnedSlot === undefined ? undefined : slots.find((slot) => slot.name === options.pinnedSlot);
40
+ if (pinned && !isBlocked(pinned, now))
41
+ return pinned;
42
+ return rendezvousOrder(getPoolAffinityKey(options), slots, options.hasher).find((slot) => !isBlocked(slot, now));
43
+ }
44
+ function soonestUnblockAt(slots, now) {
45
+ const candidates = slots
46
+ .map((slot) => slot.blockedUntil)
47
+ .filter((value) => value !== undefined && value > now);
48
+ return candidates.length === 0 ? undefined : Math.min(...candidates);
49
+ }
50
+ /** Selects a pinned or HRW-ranked slot with no pool-global selection state. */
51
+ export function selectSlot(slots, options) {
52
+ const now = options.now ?? Date.now();
53
+ const selected = selectUnblocked(slots, options, now);
54
+ if (selected)
55
+ return selected;
56
+ // A stale persisted rate-limit entry must not dead-end the pool: retry once
57
+ // against the cleared view, then map the winner back to the caller's slot.
58
+ const cleared = clearExpiredSlotBlocks(slots, now);
59
+ const retried = selectUnblocked(cleared, { ...options, now }, now);
60
+ const original = retried === undefined ? undefined : slots.find((slot) => slot.name === retried.name);
61
+ if (original)
62
+ return original;
63
+ throw new AllSlotsBlockedError(soonestUnblockAt(slots, now));
64
+ }
65
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/auth/pool/select.ts"],"names":[],"mappings":"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","sourcesContent":["export type SlotHasher = (input: string) => bigint;\n\nexport type SelectableSlot = {\n\tname: string;\n\tblockedUntil?: number;\n\tblockReason?: string;\n};\n\nexport const DEFAULT_POOL_AFFINITY_KEY = \"credential-pool-default\";\n\nexport class AllSlotsBlockedError extends Error {\n\treadonly soonestUnblockAt: number | undefined;\n\n\tconstructor(soonestUnblockAt: number | undefined) {\n\t\tsuper(\n\t\t\tsoonestUnblockAt === undefined\n\t\t\t\t? \"All credential slots are blocked until re-login.\"\n\t\t\t\t: `All credential slots are blocked until ${new Date(soonestUnblockAt).toISOString()}.`,\n\t\t);\n\t\tthis.name = \"AllSlotsBlockedError\";\n\t\tthis.soonestUnblockAt = soonestUnblockAt;\n\t}\n}\n\nexport type SlotSelectionOptions = {\n\t/** Injected so this module stays browser-safe; callers pick the hash (sha256 for oracle parity). */\n\thasher: SlotHasher;\n\taffinityKey?: string;\n\tsessionId?: string;\n\tpinnedSlot?: string;\n\tnow?: number;\n};\n\nexport function getPoolAffinityKey(options: Pick<SlotSelectionOptions, \"affinityKey\" | \"sessionId\">): string {\n\treturn options.affinityKey ?? options.sessionId ?? DEFAULT_POOL_AFFINITY_KEY;\n}\n\n/**\n * HRW ordering hashes `key\\0slot.name` exactly like the claude-sdk-oauth\n * affinity oracle, so a sha256 hasher reproduces its winner sequence and no\n * live session remaps when a pool migrates onto this engine.\n */\nexport function rendezvousOrder<T extends SelectableSlot>(key: string, slots: readonly T[], hasher: SlotHasher): T[] {\n\treturn [...slots]\n\t\t.map((slot) => ({ slot, score: hasher(`${key}\\0${slot.name}`) }))\n\t\t.sort((left, right) => (right.score > left.score ? 1 : right.score < left.score ? -1 : 0))\n\t\t.map(({ slot }) => slot);\n}\n\nfunction isBlocked(slot: SelectableSlot, now: number): boolean {\n\treturn slot.blockReason === \"auth_error\" || (slot.blockedUntil !== undefined && slot.blockedUntil > now);\n}\n\n/** Elapsed rate/capacity blocks clear; auth blocks persist until a login rewrites the slot. */\nexport function clearExpiredSlotBlocks(slots: readonly SelectableSlot[], now = Date.now()): SelectableSlot[] {\n\treturn slots.map((slot) => {\n\t\tif (slot.blockReason !== \"auth_error\" && slot.blockedUntil !== undefined && slot.blockedUntil <= now) {\n\t\t\tconst { blockedUntil: _blockedUntil, blockReason: _blockReason, ...available } = slot;\n\t\t\treturn available;\n\t\t}\n\t\treturn slot;\n\t});\n}\n\nfunction selectUnblocked<T extends SelectableSlot>(\n\tslots: readonly T[],\n\toptions: SlotSelectionOptions,\n\tnow: number,\n): T | undefined {\n\tconst pinned = options.pinnedSlot === undefined ? undefined : slots.find((slot) => slot.name === options.pinnedSlot);\n\tif (pinned && !isBlocked(pinned, now)) return pinned;\n\treturn rendezvousOrder(getPoolAffinityKey(options), slots, options.hasher).find((slot) => !isBlocked(slot, now));\n}\n\nfunction soonestUnblockAt(slots: readonly SelectableSlot[], now: number): number | undefined {\n\tconst candidates = slots\n\t\t.map((slot) => slot.blockedUntil)\n\t\t.filter((value): value is number => value !== undefined && value > now);\n\treturn candidates.length === 0 ? undefined : Math.min(...candidates);\n}\n\n/** Selects a pinned or HRW-ranked slot with no pool-global selection state. */\nexport function selectSlot<T extends SelectableSlot>(slots: readonly T[], options: SlotSelectionOptions): T {\n\tconst now = options.now ?? Date.now();\n\tconst selected = selectUnblocked(slots, options, now);\n\tif (selected) return selected;\n\n\t// A stale persisted rate-limit entry must not dead-end the pool: retry once\n\t// against the cleared view, then map the winner back to the caller's slot.\n\tconst cleared = clearExpiredSlotBlocks(slots, now);\n\tconst retried = selectUnblocked(cleared, { ...options, now }, now);\n\tconst original = retried === undefined ? undefined : slots.find((slot) => slot.name === retried.name);\n\tif (original) return original;\n\tthrow new AllSlotsBlockedError(soonestUnblockAt(slots, now));\n}\n"]}
@@ -34,6 +34,12 @@ export declare function upsertSlot(credential: PooledCredential | undefined, slo
34
34
  */
35
35
  export declare function removeSlot(credential: PooledCredential | undefined, name: string): PooledCredential | undefined;
36
36
  export declare function pinSlot(credential: PooledCredential, name: string): PooledCredential;
37
+ /**
38
+ * Projects one named slot onto the flat credential shape for request-scoped
39
+ * resolution; pool bookkeeping fields are stripped so provider handlers see a
40
+ * plain credential and never write pool state back through the projection.
41
+ */
42
+ export declare function projectSlot(credential: PooledCredential | undefined, name: string): Credential | undefined;
37
43
  /**
38
44
  * Appends an unnamed flat credential to a pool as a generated `login-N` slot. A
39
45
  * flat or absent current entry keeps today's whole-write shape so no existing
@@ -46,5 +52,11 @@ export declare function appendLoginSlot(current: PooledCredential | undefined, f
46
52
  * flat fields, while every sibling and the pin survive byte-identical. A flat
47
53
  * current entry keeps today's whole-write shape.
48
54
  */
55
+ /**
56
+ * Merges a rotated OAuth credential into the NAMED slot. The flat top-level
57
+ * projection rotates only when it mirrored that slot's previous material, so
58
+ * refreshing a secondary slot never disturbs what an older binary reads.
59
+ */
60
+ export declare function mergeRefreshedSlot(current: PooledCredential, name: string, refreshed: Credential): Credential;
49
61
  export declare function mergeRefreshed(current: PooledCredential, refreshed: Credential): Credential;
50
62
  //# sourceMappingURL=slots.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../../src/auth/pool/slots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE9D,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAC3C,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtD;AAmBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,GAAG,cAAc,EAAE,CAIpF;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE3G;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAc3G;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAO/G;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAGpF;AAwBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAKnG;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAe3F"}
1
+ {"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../../src/auth/pool/slots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE9D,MAAM,MAAM,cAAc,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAC3C,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtD;AAmBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,GAAG,cAAc,EAAE,CAIpF;AAED,wBAAgB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE3G;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAc3G;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAO/G;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAGpF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAU1G;AAwBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAKnG;AAED;;;;;GAKG;AACH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAS7G;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAe3F"}
@@ -71,6 +71,25 @@ export function pinSlot(credential, name) {
71
71
  assertValidSlotName(name);
72
72
  return { ...credential, pinned: name };
73
73
  }
74
+ /**
75
+ * Projects one named slot onto the flat credential shape for request-scoped
76
+ * resolution; pool bookkeeping fields are stripped so provider handlers see a
77
+ * plain credential and never write pool state back through the projection.
78
+ */
79
+ export function projectSlot(credential, name) {
80
+ if (!credential)
81
+ return undefined;
82
+ const slot = findSlot(credential, name);
83
+ if (!slot)
84
+ return undefined;
85
+ const { accounts: _accounts, pinned: _pinned, ...flat } = credential;
86
+ if (flat.type === "oauth") {
87
+ if (slot.access === undefined || slot.refresh === undefined || slot.expires === undefined)
88
+ return undefined;
89
+ return { ...flat, access: slot.access, refresh: slot.refresh, expires: slot.expires };
90
+ }
91
+ return { ...flat, key: slot.key };
92
+ }
74
93
  function slotFromFlatCredentialNamed(credential, name) {
75
94
  if (credential.type === "oauth") {
76
95
  return {
@@ -109,6 +128,24 @@ export function appendLoginSlot(current, flat) {
109
128
  * flat fields, while every sibling and the pin survive byte-identical. A flat
110
129
  * current entry keeps today's whole-write shape.
111
130
  */
131
+ /**
132
+ * Merges a rotated OAuth credential into the NAMED slot. The flat top-level
133
+ * projection rotates only when it mirrored that slot's previous material, so
134
+ * refreshing a secondary slot never disturbs what an older binary reads.
135
+ */
136
+ export function mergeRefreshedSlot(current, name, refreshed) {
137
+ if (refreshed.type !== "oauth" || current.type !== "oauth")
138
+ return current;
139
+ if (!Array.isArray(current.accounts) || current.accounts.length === 0)
140
+ return mergeRefreshed(current, refreshed);
141
+ const target = current.accounts.find((slot) => slot.name === name);
142
+ if (!target)
143
+ return current;
144
+ const rotated = { access: refreshed.access, refresh: refreshed.refresh, expires: refreshed.expires };
145
+ const accounts = current.accounts.map((slot) => (slot === target ? { ...slot, ...rotated } : slot));
146
+ const mirrorsFlat = target.access === current.access || target.refresh === current.refresh;
147
+ return mirrorsFlat ? { ...current, ...rotated, accounts } : { ...current, accounts };
148
+ }
112
149
  export function mergeRefreshed(current, refreshed) {
113
150
  if (!Array.isArray(current.accounts) || current.accounts.length === 0) {
114
151
  return refreshed;
@@ -1 +1 @@
1
- {"version":3,"file":"slots.js","sourceRoot":"","sources":["../../../src/auth/pool/slots.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Credential } from \"../types.ts\";\n\nexport type { Credential };\n\nexport const DEFAULT_SLOT_NAME = \"default\";\n\nexport type CredentialSlotSource = \"login\" | \"import\" | \"env\";\n\nexport type CredentialSlot = {\n\tname: string;\n\tsource?: CredentialSlotSource;\n\tkey?: string;\n\taccess?: string;\n\trefresh?: string;\n\texpires?: number;\n};\n\nexport type PooledCredential = Credential & {\n\taccounts?: CredentialSlot[];\n\tpinned?: string;\n};\n\nconst SLOT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\nexport function assertValidSlotName(name: string): void {\n\tif (!SLOT_NAME_PATTERN.test(name)) {\n\t\tthrow new Error(\n\t\t\t`Invalid account name '${name}': use letters, digits, '-' or '_', starting with a letter or digit`,\n\t\t);\n\t}\n}\n\nfunction storedSlots(credential: PooledCredential): CredentialSlot[] {\n\treturn Array.isArray(credential.accounts) ? credential.accounts : [];\n}\n\nfunction slotFromFlatCredential(credential: PooledCredential): CredentialSlot {\n\tif (credential.type === \"oauth\") {\n\t\treturn {\n\t\t\tname: DEFAULT_SLOT_NAME,\n\t\t\tsource: \"login\",\n\t\t\taccess: credential.access,\n\t\t\trefresh: credential.refresh,\n\t\t\texpires: credential.expires,\n\t\t};\n\t}\n\treturn { name: DEFAULT_SLOT_NAME, source: \"login\", key: credential.key };\n}\n\n/**\n * A flat credential written by a build predating pools is read as a one-slot pool\n * without writing anything back; the caller decides whether a write ever happens.\n */\nexport function listSlots(credential: PooledCredential | undefined): CredentialSlot[] {\n\tif (!credential) return [];\n\tconst slots = storedSlots(credential);\n\treturn slots.length > 0 ? [...slots] : [slotFromFlatCredential(credential)];\n}\n\nexport function findSlot(credential: PooledCredential | undefined, name: string): CredentialSlot | undefined {\n\treturn listSlots(credential).find((slot) => slot.name === name);\n}\n\n/**\n * Replaces or appends one slot while every sibling, the pin, and the flat\n * top-level credential survive untouched. The flat fields stay as written so a\n * build that ignores `accounts` still authenticates from them.\n */\nexport function upsertSlot(credential: PooledCredential | undefined, slot: CredentialSlot): PooledCredential {\n\tassertValidSlotName(slot.name);\n\tconst base: PooledCredential =\n\t\tcredential ??\n\t\t(slot.access !== undefined || slot.refresh !== undefined\n\t\t\t? { type: \"oauth\", access: slot.access ?? \"\", refresh: slot.refresh ?? \"\", expires: slot.expires ?? 0 }\n\t\t\t: { type: \"api_key\", key: slot.key });\n\tconst existing = listSlots(base);\n\tconst index = existing.findIndex((candidate) => candidate.name === slot.name);\n\tconst accounts =\n\t\tindex >= 0\n\t\t\t? existing.map((candidate) => (candidate.name === slot.name ? { ...candidate, ...slot } : candidate))\n\t\t\t: [...existing, slot];\n\treturn { ...base, accounts };\n}\n\n/**\n * Removes one slot. The credential is dropped entirely once its last slot is gone,\n * and a pin naming the removed slot is cleared so selection never points at a slot\n * that no longer exists.\n */\nexport function removeSlot(credential: PooledCredential | undefined, name: string): PooledCredential | undefined {\n\tif (!credential) return undefined;\n\tconst accounts = listSlots(credential).filter((slot) => slot.name !== name);\n\tif (accounts.length === 0) return undefined;\n\tconst next: PooledCredential = { ...credential, accounts };\n\tif (next.pinned === name) delete next.pinned;\n\treturn next;\n}\n\nexport function pinSlot(credential: PooledCredential, name: string): PooledCredential {\n\tassertValidSlotName(name);\n\treturn { ...credential, pinned: name };\n}\n\nfunction slotFromFlatCredentialNamed(credential: Credential, name: string): CredentialSlot {\n\tif (credential.type === \"oauth\") {\n\t\treturn {\n\t\t\tname,\n\t\t\tsource: \"login\",\n\t\t\taccess: credential.access,\n\t\t\trefresh: credential.refresh,\n\t\t\texpires: credential.expires,\n\t\t};\n\t}\n\treturn { name, source: \"login\", key: credential.key };\n}\n\nfunction nextLoginSlotName(credential: PooledCredential): string {\n\tconst taken = new Set(listSlots(credential).map((slot) => slot.name));\n\tfor (let index = 2; index < 1000; index++) {\n\t\tconst candidate = `login-${index}`;\n\t\tif (!taken.has(candidate)) return candidate;\n\t}\n\tthrow new Error(\"Credential pool is full\");\n}\n\n/**\n * Appends an unnamed flat credential to a pool as a generated `login-N` slot. A\n * flat or absent current entry keeps today's whole-write shape so no existing\n * user's stored bytes change until a second credential actually exists.\n */\nexport function appendLoginSlot(current: PooledCredential | undefined, flat: Credential): Credential {\n\tif (!current || !Array.isArray(current.accounts) || current.accounts.length === 0) {\n\t\treturn flat;\n\t}\n\treturn upsertSlot(current, slotFromFlatCredentialNamed(flat, nextLoginSlotName(current)));\n}\n\n/**\n * Merges a rotated OAuth credential back into the pool: the slot whose material\n * matches the pre-refresh flat fields is updated in place together with those\n * flat fields, while every sibling and the pin survive byte-identical. A flat\n * current entry keeps today's whole-write shape.\n */\nexport function mergeRefreshed(current: PooledCredential, refreshed: Credential): Credential {\n\tif (!Array.isArray(current.accounts) || current.accounts.length === 0) {\n\t\treturn refreshed;\n\t}\n\tif (refreshed.type !== \"oauth\" || current.type !== \"oauth\") return refreshed;\n\tconst target = current.accounts.find((slot) => slot.access === current.access || slot.refresh === current.refresh);\n\tconst rotated = {\n\t\taccess: refreshed.access,\n\t\trefresh: refreshed.refresh,\n\t\texpires: refreshed.expires,\n\t};\n\tconst accounts = target\n\t\t? current.accounts.map((slot) => (slot === target ? { ...slot, ...rotated } : slot))\n\t\t: current.accounts;\n\treturn { ...current, ...rotated, accounts };\n}\n"]}
1
+ {"version":3,"file":"slots.js","sourceRoot":"","sources":["../../../src/auth/pool/slots.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Credential } from \"../types.ts\";\n\nexport type { Credential };\n\nexport const DEFAULT_SLOT_NAME = \"default\";\n\nexport type CredentialSlotSource = \"login\" | \"import\" | \"env\";\n\nexport type CredentialSlot = {\n\tname: string;\n\tsource?: CredentialSlotSource;\n\tkey?: string;\n\taccess?: string;\n\trefresh?: string;\n\texpires?: number;\n};\n\nexport type PooledCredential = Credential & {\n\taccounts?: CredentialSlot[];\n\tpinned?: string;\n};\n\nconst SLOT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;\n\nexport function assertValidSlotName(name: string): void {\n\tif (!SLOT_NAME_PATTERN.test(name)) {\n\t\tthrow new Error(\n\t\t\t`Invalid account name '${name}': use letters, digits, '-' or '_', starting with a letter or digit`,\n\t\t);\n\t}\n}\n\nfunction storedSlots(credential: PooledCredential): CredentialSlot[] {\n\treturn Array.isArray(credential.accounts) ? credential.accounts : [];\n}\n\nfunction slotFromFlatCredential(credential: PooledCredential): CredentialSlot {\n\tif (credential.type === \"oauth\") {\n\t\treturn {\n\t\t\tname: DEFAULT_SLOT_NAME,\n\t\t\tsource: \"login\",\n\t\t\taccess: credential.access,\n\t\t\trefresh: credential.refresh,\n\t\t\texpires: credential.expires,\n\t\t};\n\t}\n\treturn { name: DEFAULT_SLOT_NAME, source: \"login\", key: credential.key };\n}\n\n/**\n * A flat credential written by a build predating pools is read as a one-slot pool\n * without writing anything back; the caller decides whether a write ever happens.\n */\nexport function listSlots(credential: PooledCredential | undefined): CredentialSlot[] {\n\tif (!credential) return [];\n\tconst slots = storedSlots(credential);\n\treturn slots.length > 0 ? [...slots] : [slotFromFlatCredential(credential)];\n}\n\nexport function findSlot(credential: PooledCredential | undefined, name: string): CredentialSlot | undefined {\n\treturn listSlots(credential).find((slot) => slot.name === name);\n}\n\n/**\n * Replaces or appends one slot while every sibling, the pin, and the flat\n * top-level credential survive untouched. The flat fields stay as written so a\n * build that ignores `accounts` still authenticates from them.\n */\nexport function upsertSlot(credential: PooledCredential | undefined, slot: CredentialSlot): PooledCredential {\n\tassertValidSlotName(slot.name);\n\tconst base: PooledCredential =\n\t\tcredential ??\n\t\t(slot.access !== undefined || slot.refresh !== undefined\n\t\t\t? { type: \"oauth\", access: slot.access ?? \"\", refresh: slot.refresh ?? \"\", expires: slot.expires ?? 0 }\n\t\t\t: { type: \"api_key\", key: slot.key });\n\tconst existing = listSlots(base);\n\tconst index = existing.findIndex((candidate) => candidate.name === slot.name);\n\tconst accounts =\n\t\tindex >= 0\n\t\t\t? existing.map((candidate) => (candidate.name === slot.name ? { ...candidate, ...slot } : candidate))\n\t\t\t: [...existing, slot];\n\treturn { ...base, accounts };\n}\n\n/**\n * Removes one slot. The credential is dropped entirely once its last slot is gone,\n * and a pin naming the removed slot is cleared so selection never points at a slot\n * that no longer exists.\n */\nexport function removeSlot(credential: PooledCredential | undefined, name: string): PooledCredential | undefined {\n\tif (!credential) return undefined;\n\tconst accounts = listSlots(credential).filter((slot) => slot.name !== name);\n\tif (accounts.length === 0) return undefined;\n\tconst next: PooledCredential = { ...credential, accounts };\n\tif (next.pinned === name) delete next.pinned;\n\treturn next;\n}\n\nexport function pinSlot(credential: PooledCredential, name: string): PooledCredential {\n\tassertValidSlotName(name);\n\treturn { ...credential, pinned: name };\n}\n\n/**\n * Projects one named slot onto the flat credential shape for request-scoped\n * resolution; pool bookkeeping fields are stripped so provider handlers see a\n * plain credential and never write pool state back through the projection.\n */\nexport function projectSlot(credential: PooledCredential | undefined, name: string): Credential | undefined {\n\tif (!credential) return undefined;\n\tconst slot = findSlot(credential, name);\n\tif (!slot) return undefined;\n\tconst { accounts: _accounts, pinned: _pinned, ...flat } = credential;\n\tif (flat.type === \"oauth\") {\n\t\tif (slot.access === undefined || slot.refresh === undefined || slot.expires === undefined) return undefined;\n\t\treturn { ...flat, access: slot.access, refresh: slot.refresh, expires: slot.expires };\n\t}\n\treturn { ...flat, key: slot.key };\n}\n\nfunction slotFromFlatCredentialNamed(credential: Credential, name: string): CredentialSlot {\n\tif (credential.type === \"oauth\") {\n\t\treturn {\n\t\t\tname,\n\t\t\tsource: \"login\",\n\t\t\taccess: credential.access,\n\t\t\trefresh: credential.refresh,\n\t\t\texpires: credential.expires,\n\t\t};\n\t}\n\treturn { name, source: \"login\", key: credential.key };\n}\n\nfunction nextLoginSlotName(credential: PooledCredential): string {\n\tconst taken = new Set(listSlots(credential).map((slot) => slot.name));\n\tfor (let index = 2; index < 1000; index++) {\n\t\tconst candidate = `login-${index}`;\n\t\tif (!taken.has(candidate)) return candidate;\n\t}\n\tthrow new Error(\"Credential pool is full\");\n}\n\n/**\n * Appends an unnamed flat credential to a pool as a generated `login-N` slot. A\n * flat or absent current entry keeps today's whole-write shape so no existing\n * user's stored bytes change until a second credential actually exists.\n */\nexport function appendLoginSlot(current: PooledCredential | undefined, flat: Credential): Credential {\n\tif (!current || !Array.isArray(current.accounts) || current.accounts.length === 0) {\n\t\treturn flat;\n\t}\n\treturn upsertSlot(current, slotFromFlatCredentialNamed(flat, nextLoginSlotName(current)));\n}\n\n/**\n * Merges a rotated OAuth credential back into the pool: the slot whose material\n * matches the pre-refresh flat fields is updated in place together with those\n * flat fields, while every sibling and the pin survive byte-identical. A flat\n * current entry keeps today's whole-write shape.\n */\n/**\n * Merges a rotated OAuth credential into the NAMED slot. The flat top-level\n * projection rotates only when it mirrored that slot's previous material, so\n * refreshing a secondary slot never disturbs what an older binary reads.\n */\nexport function mergeRefreshedSlot(current: PooledCredential, name: string, refreshed: Credential): Credential {\n\tif (refreshed.type !== \"oauth\" || current.type !== \"oauth\") return current;\n\tif (!Array.isArray(current.accounts) || current.accounts.length === 0) return mergeRefreshed(current, refreshed);\n\tconst target = current.accounts.find((slot) => slot.name === name);\n\tif (!target) return current;\n\tconst rotated = { access: refreshed.access, refresh: refreshed.refresh, expires: refreshed.expires };\n\tconst accounts = current.accounts.map((slot) => (slot === target ? { ...slot, ...rotated } : slot));\n\tconst mirrorsFlat = target.access === current.access || target.refresh === current.refresh;\n\treturn mirrorsFlat ? { ...current, ...rotated, accounts } : { ...current, accounts };\n}\n\nexport function mergeRefreshed(current: PooledCredential, refreshed: Credential): Credential {\n\tif (!Array.isArray(current.accounts) || current.accounts.length === 0) {\n\t\treturn refreshed;\n\t}\n\tif (refreshed.type !== \"oauth\" || current.type !== \"oauth\") return refreshed;\n\tconst target = current.accounts.find((slot) => slot.access === current.access || slot.refresh === current.refresh);\n\tconst rotated = {\n\t\taccess: refreshed.access,\n\t\trefresh: refreshed.refresh,\n\t\texpires: refreshed.expires,\n\t};\n\tconst accounts = target\n\t\t? current.accounts.map((slot) => (slot === target ? { ...slot, ...rotated } : slot))\n\t\t: current.accounts;\n\treturn { ...current, ...rotated, accounts };\n}\n"]}
@@ -6,6 +6,12 @@ export interface AuthResolutionOverrides {
6
6
  env?: ProviderEnv;
7
7
  /** Require this much remaining OAuth-token validity; defaults to five minutes. */
8
8
  minOAuthValidityMs?: number;
9
+ /**
10
+ * Resolve against one named slot of a pooled credential. A missing entry or
11
+ * slot resolves to undefined rather than falling back to another account or
12
+ * ambient env, so a slot-scoped request can never silently switch identities.
13
+ */
14
+ slotName?: string;
9
15
  signal?: AbortSignal;
10
16
  }
11
17
  export declare class ModelsError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EAEV,eAAe,EAGf,YAAY,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7G,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,qBAAa,WAAY,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKjF;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,EAC5C,WAAW,EAAE,eAAe,EAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,CAAC,EAAE,uBAAuB,GACjC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAMjC"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EAEV,eAAe,EAGf,YAAY,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7G,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,qBAAa,WAAY,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;gBAEnB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKjF;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,EAC5C,WAAW,EAAE,eAAe,EAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,CAAC,EAAE,uBAAuB,GACjC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAMjC"}