apex-code 0.0.1-alpha.0 → 0.0.1-alpha.2

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 (426) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +66 -681
  3. package/dist/cli/args.d.ts +5 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +45 -6
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/auth-command.d.ts.map +1 -1
  8. package/dist/cli/auth-command.js +3 -3
  9. package/dist/cli/auth-command.js.map +1 -1
  10. package/dist/cli/cost-command.d.ts +19 -0
  11. package/dist/cli/cost-command.d.ts.map +1 -0
  12. package/dist/cli/cost-command.js +107 -0
  13. package/dist/cli/cost-command.js.map +1 -0
  14. package/dist/cli/startup-ui.d.ts +1 -1
  15. package/dist/cli/startup-ui.d.ts.map +1 -1
  16. package/dist/cli/startup-ui.js +1 -2
  17. package/dist/cli/startup-ui.js.map +1 -1
  18. package/dist/cli.d.ts.map +1 -1
  19. package/dist/cli.js +45 -16
  20. package/dist/cli.js.map +1 -1
  21. package/dist/config.d.ts +2 -2
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/config.js +14 -7
  24. package/dist/config.js.map +1 -1
  25. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  26. package/dist/core/agent-session-runtime.js +2 -0
  27. package/dist/core/agent-session-runtime.js.map +1 -1
  28. package/dist/core/agent-session-services.d.ts +9 -0
  29. package/dist/core/agent-session-services.d.ts.map +1 -1
  30. package/dist/core/agent-session-services.js +30 -4
  31. package/dist/core/agent-session-services.js.map +1 -1
  32. package/dist/core/agent-session.d.ts +46 -0
  33. package/dist/core/agent-session.d.ts.map +1 -1
  34. package/dist/core/agent-session.js +137 -19
  35. package/dist/core/agent-session.js.map +1 -1
  36. package/dist/core/auth-storage.d.ts.map +1 -1
  37. package/dist/core/auth-storage.js +6 -6
  38. package/dist/core/auth-storage.js.map +1 -1
  39. package/dist/core/bash-executor.d.ts.map +1 -1
  40. package/dist/core/bash-executor.js +1 -1
  41. package/dist/core/bash-executor.js.map +1 -1
  42. package/dist/core/context/deferred-schemas.d.ts +92 -0
  43. package/dist/core/context/deferred-schemas.d.ts.map +1 -0
  44. package/dist/core/context/deferred-schemas.js +74 -0
  45. package/dist/core/context/deferred-schemas.js.map +1 -0
  46. package/dist/core/context/eviction.d.ts +47 -0
  47. package/dist/core/context/eviction.d.ts.map +1 -0
  48. package/dist/core/context/eviction.js +83 -0
  49. package/dist/core/context/eviction.js.map +1 -0
  50. package/dist/core/context/pipeline.d.ts +90 -0
  51. package/dist/core/context/pipeline.d.ts.map +1 -0
  52. package/dist/core/context/pipeline.js +109 -0
  53. package/dist/core/context/pipeline.js.map +1 -0
  54. package/dist/core/credential-failover.d.ts +30 -0
  55. package/dist/core/credential-failover.d.ts.map +1 -0
  56. package/dist/core/credential-failover.js +46 -0
  57. package/dist/core/credential-failover.js.map +1 -0
  58. package/dist/core/credential-pool.d.ts +60 -0
  59. package/dist/core/credential-pool.d.ts.map +1 -0
  60. package/dist/core/credential-pool.js +84 -0
  61. package/dist/core/credential-pool.js.map +1 -0
  62. package/dist/core/delegation/agents.d.ts +15 -0
  63. package/dist/core/delegation/agents.d.ts.map +1 -0
  64. package/dist/core/delegation/agents.js +55 -0
  65. package/dist/core/delegation/agents.js.map +1 -0
  66. package/dist/core/delegation/ceiling.d.ts +29 -0
  67. package/dist/core/delegation/ceiling.d.ts.map +1 -0
  68. package/dist/core/delegation/ceiling.js +34 -0
  69. package/dist/core/delegation/ceiling.js.map +1 -0
  70. package/dist/core/delegation/runtime.d.ts +94 -0
  71. package/dist/core/delegation/runtime.d.ts.map +1 -0
  72. package/dist/core/delegation/runtime.js +117 -0
  73. package/dist/core/delegation/runtime.js.map +1 -0
  74. package/dist/core/durable-state/daemon.d.ts +37 -0
  75. package/dist/core/durable-state/daemon.d.ts.map +1 -0
  76. package/dist/core/durable-state/daemon.js +62 -0
  77. package/dist/core/durable-state/daemon.js.map +1 -0
  78. package/dist/core/durable-state/provenance.d.ts +7 -0
  79. package/dist/core/durable-state/provenance.d.ts.map +1 -0
  80. package/dist/core/durable-state/provenance.js +24 -0
  81. package/dist/core/durable-state/provenance.js.map +1 -0
  82. package/dist/core/durable-state/sqlite.d.ts +84 -0
  83. package/dist/core/durable-state/sqlite.d.ts.map +1 -0
  84. package/dist/core/durable-state/sqlite.js +331 -0
  85. package/dist/core/durable-state/sqlite.js.map +1 -0
  86. package/dist/core/environment.d.ts +12 -0
  87. package/dist/core/environment.d.ts.map +1 -0
  88. package/dist/core/environment.js +69 -0
  89. package/dist/core/environment.js.map +1 -0
  90. package/dist/core/evidence-policy.d.ts +17 -0
  91. package/dist/core/evidence-policy.d.ts.map +1 -0
  92. package/dist/core/evidence-policy.js +25 -0
  93. package/dist/core/evidence-policy.js.map +1 -0
  94. package/dist/core/evidence.d.ts +16 -0
  95. package/dist/core/evidence.d.ts.map +1 -0
  96. package/dist/core/evidence.js +51 -0
  97. package/dist/core/evidence.js.map +1 -0
  98. package/dist/core/experimental.d.ts.map +1 -1
  99. package/dist/core/experimental.js +2 -1
  100. package/dist/core/experimental.js.map +1 -1
  101. package/dist/core/extensions/types.d.ts +2 -0
  102. package/dist/core/extensions/types.d.ts.map +1 -1
  103. package/dist/core/extensions/types.js.map +1 -1
  104. package/dist/core/index.d.ts +1 -0
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js +1 -0
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/model-config.d.ts +8 -0
  109. package/dist/core/model-config.d.ts.map +1 -1
  110. package/dist/core/model-config.js +21 -5
  111. package/dist/core/model-config.js.map +1 -1
  112. package/dist/core/model-resolver.d.ts +20 -0
  113. package/dist/core/model-resolver.d.ts.map +1 -1
  114. package/dist/core/model-resolver.js +59 -0
  115. package/dist/core/model-resolver.js.map +1 -1
  116. package/dist/core/model-runtime.d.ts +73 -0
  117. package/dist/core/model-runtime.d.ts.map +1 -1
  118. package/dist/core/model-runtime.js +225 -8
  119. package/dist/core/model-runtime.js.map +1 -1
  120. package/dist/core/observability/aggregate.d.ts +21 -0
  121. package/dist/core/observability/aggregate.d.ts.map +1 -0
  122. package/dist/core/observability/aggregate.js +36 -0
  123. package/dist/core/observability/aggregate.js.map +1 -0
  124. package/dist/core/observability/otlp.d.ts +66 -0
  125. package/dist/core/observability/otlp.d.ts.map +1 -0
  126. package/dist/core/observability/otlp.js +95 -0
  127. package/dist/core/observability/otlp.js.map +1 -0
  128. package/dist/core/package-manager.d.ts.map +1 -1
  129. package/dist/core/package-manager.js +3 -2
  130. package/dist/core/package-manager.js.map +1 -1
  131. package/dist/core/permissions/gate.d.ts +35 -0
  132. package/dist/core/permissions/gate.d.ts.map +1 -0
  133. package/dist/core/permissions/gate.js +72 -0
  134. package/dist/core/permissions/gate.js.map +1 -0
  135. package/dist/core/permissions/modes.d.ts +18 -0
  136. package/dist/core/permissions/modes.d.ts.map +1 -0
  137. package/dist/core/permissions/modes.js +57 -0
  138. package/dist/core/permissions/modes.js.map +1 -0
  139. package/dist/core/permissions/responder.d.ts +30 -0
  140. package/dist/core/permissions/responder.d.ts.map +1 -0
  141. package/dist/core/permissions/responder.js +32 -0
  142. package/dist/core/permissions/responder.js.map +1 -0
  143. package/dist/core/permissions/rules.d.ts +34 -0
  144. package/dist/core/permissions/rules.d.ts.map +1 -0
  145. package/dist/core/permissions/rules.js +74 -0
  146. package/dist/core/permissions/rules.js.map +1 -0
  147. package/dist/core/permissions/startup.d.ts +33 -0
  148. package/dist/core/permissions/startup.d.ts.map +1 -0
  149. package/dist/core/permissions/startup.js +55 -0
  150. package/dist/core/permissions/startup.js.map +1 -0
  151. package/dist/core/permissions/store.d.ts +114 -0
  152. package/dist/core/permissions/store.d.ts.map +1 -0
  153. package/dist/core/permissions/store.js +229 -0
  154. package/dist/core/permissions/store.js.map +1 -0
  155. package/dist/core/project-trust.d.ts.map +1 -1
  156. package/dist/core/project-trust.js +1 -1
  157. package/dist/core/project-trust.js.map +1 -1
  158. package/dist/core/provider-attribution.d.ts.map +1 -1
  159. package/dist/core/provider-attribution.js +6 -7
  160. package/dist/core/provider-attribution.js.map +1 -1
  161. package/dist/core/remote-catalog-provider.d.ts +2 -2
  162. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  163. package/dist/core/remote-catalog-provider.js +20 -16
  164. package/dist/core/remote-catalog-provider.js.map +1 -1
  165. package/dist/core/sandbox/child-entry.d.ts +2 -0
  166. package/dist/core/sandbox/child-entry.d.ts.map +1 -0
  167. package/dist/core/sandbox/child-entry.js +11 -0
  168. package/dist/core/sandbox/child-entry.js.map +1 -0
  169. package/dist/core/sandbox/cli-launch.d.ts +27 -0
  170. package/dist/core/sandbox/cli-launch.d.ts.map +1 -0
  171. package/dist/core/sandbox/cli-launch.js +133 -0
  172. package/dist/core/sandbox/cli-launch.js.map +1 -0
  173. package/dist/core/sandbox/cli-supervisor.d.ts +25 -0
  174. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -0
  175. package/dist/core/sandbox/cli-supervisor.js +57 -0
  176. package/dist/core/sandbox/cli-supervisor.js.map +1 -0
  177. package/dist/core/sandbox/linux-backend.d.ts +18 -0
  178. package/dist/core/sandbox/linux-backend.d.ts.map +1 -0
  179. package/dist/core/sandbox/linux-backend.js +186 -0
  180. package/dist/core/sandbox/linux-backend.js.map +1 -0
  181. package/dist/core/sandbox/macos-backend.d.ts +18 -0
  182. package/dist/core/sandbox/macos-backend.d.ts.map +1 -0
  183. package/dist/core/sandbox/macos-backend.js +195 -0
  184. package/dist/core/sandbox/macos-backend.js.map +1 -0
  185. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  186. package/dist/core/sandbox/network-proxy.d.ts.map +1 -0
  187. package/dist/core/sandbox/network-proxy.js +79 -0
  188. package/dist/core/sandbox/network-proxy.js.map +1 -0
  189. package/dist/core/sandbox/policy.d.ts +32 -0
  190. package/dist/core/sandbox/policy.d.ts.map +1 -0
  191. package/dist/core/sandbox/policy.js +38 -0
  192. package/dist/core/sandbox/policy.js.map +1 -0
  193. package/dist/core/sandbox/supervisor.d.ts +31 -0
  194. package/dist/core/sandbox/supervisor.d.ts.map +1 -0
  195. package/dist/core/sandbox/supervisor.js +18 -0
  196. package/dist/core/sandbox/supervisor.js.map +1 -0
  197. package/dist/core/sandbox/violations.d.ts +23 -0
  198. package/dist/core/sandbox/violations.d.ts.map +1 -0
  199. package/dist/core/sandbox/violations.js +30 -0
  200. package/dist/core/sandbox/violations.js.map +1 -0
  201. package/dist/core/sdk.d.ts +20 -36
  202. package/dist/core/sdk.d.ts.map +1 -1
  203. package/dist/core/sdk.js +129 -2
  204. package/dist/core/sdk.js.map +1 -1
  205. package/dist/core/session-manager.d.ts +41 -2
  206. package/dist/core/session-manager.d.ts.map +1 -1
  207. package/dist/core/session-manager.js +57 -0
  208. package/dist/core/session-manager.js.map +1 -1
  209. package/dist/core/session-share.d.ts +16 -0
  210. package/dist/core/session-share.d.ts.map +1 -0
  211. package/dist/core/session-share.js +22 -0
  212. package/dist/core/session-share.js.map +1 -0
  213. package/dist/core/settings-manager.d.ts +38 -9
  214. package/dist/core/settings-manager.d.ts.map +1 -1
  215. package/dist/core/settings-manager.js +39 -24
  216. package/dist/core/settings-manager.js.map +1 -1
  217. package/dist/core/system-prompt.d.ts.map +1 -1
  218. package/dist/core/system-prompt.js +5 -5
  219. package/dist/core/system-prompt.js.map +1 -1
  220. package/dist/core/timings.d.ts +1 -1
  221. package/dist/core/timings.d.ts.map +1 -1
  222. package/dist/core/timings.js +3 -2
  223. package/dist/core/timings.js.map +1 -1
  224. package/dist/core/tools/ask-user.d.ts +25 -0
  225. package/dist/core/tools/ask-user.d.ts.map +1 -0
  226. package/dist/core/tools/ask-user.js +49 -0
  227. package/dist/core/tools/ask-user.js.map +1 -0
  228. package/dist/core/tools/bash-command-segments.d.ts +22 -0
  229. package/dist/core/tools/bash-command-segments.d.ts.map +1 -0
  230. package/dist/core/tools/bash-command-segments.js +128 -0
  231. package/dist/core/tools/bash-command-segments.js.map +1 -0
  232. package/dist/core/tools/bash.d.ts +26 -3
  233. package/dist/core/tools/bash.d.ts.map +1 -1
  234. package/dist/core/tools/bash.js +105 -16
  235. package/dist/core/tools/bash.js.map +1 -1
  236. package/dist/core/tools/contract.d.ts +156 -0
  237. package/dist/core/tools/contract.d.ts.map +1 -0
  238. package/dist/core/tools/contract.js +48 -0
  239. package/dist/core/tools/contract.js.map +1 -0
  240. package/dist/core/tools/delegate.d.ts +38 -0
  241. package/dist/core/tools/delegate.d.ts.map +1 -0
  242. package/dist/core/tools/delegate.js +66 -0
  243. package/dist/core/tools/delegate.js.map +1 -0
  244. package/dist/core/tools/edit.d.ts +2 -2
  245. package/dist/core/tools/edit.d.ts.map +1 -1
  246. package/dist/core/tools/edit.js +24 -0
  247. package/dist/core/tools/edit.js.map +1 -1
  248. package/dist/core/tools/find.d.ts +2 -2
  249. package/dist/core/tools/find.d.ts.map +1 -1
  250. package/dist/core/tools/find.js +12 -0
  251. package/dist/core/tools/find.js.map +1 -1
  252. package/dist/core/tools/grep.d.ts +2 -2
  253. package/dist/core/tools/grep.d.ts.map +1 -1
  254. package/dist/core/tools/grep.js +12 -0
  255. package/dist/core/tools/grep.js.map +1 -1
  256. package/dist/core/tools/index.d.ts +29 -4
  257. package/dist/core/tools/index.d.ts.map +1 -1
  258. package/dist/core/tools/index.js +118 -2
  259. package/dist/core/tools/index.js.map +1 -1
  260. package/dist/core/tools/ls.d.ts +2 -2
  261. package/dist/core/tools/ls.d.ts.map +1 -1
  262. package/dist/core/tools/ls.js +12 -0
  263. package/dist/core/tools/ls.js.map +1 -1
  264. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  265. package/dist/core/tools/output-accumulator.js +1 -1
  266. package/dist/core/tools/output-accumulator.js.map +1 -1
  267. package/dist/core/tools/path-permission.d.ts +18 -0
  268. package/dist/core/tools/path-permission.d.ts.map +1 -0
  269. package/dist/core/tools/path-permission.js +28 -0
  270. package/dist/core/tools/path-permission.js.map +1 -0
  271. package/dist/core/tools/plan-present.d.ts +27 -0
  272. package/dist/core/tools/plan-present.d.ts.map +1 -0
  273. package/dist/core/tools/plan-present.js +55 -0
  274. package/dist/core/tools/plan-present.js.map +1 -0
  275. package/dist/core/tools/read.d.ts +2 -2
  276. package/dist/core/tools/read.d.ts.map +1 -1
  277. package/dist/core/tools/read.js +12 -0
  278. package/dist/core/tools/read.js.map +1 -1
  279. package/dist/core/tools/test.d.ts +34 -0
  280. package/dist/core/tools/test.d.ts.map +1 -0
  281. package/dist/core/tools/test.js +75 -0
  282. package/dist/core/tools/test.js.map +1 -0
  283. package/dist/core/tools/todo-write.d.ts +31 -0
  284. package/dist/core/tools/todo-write.d.ts.map +1 -0
  285. package/dist/core/tools/todo-write.js +51 -0
  286. package/dist/core/tools/todo-write.js.map +1 -0
  287. package/dist/core/tools/tool-definition-wrapper.d.ts +13 -2
  288. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  289. package/dist/core/tools/tool-definition-wrapper.js +11 -1
  290. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  291. package/dist/core/tools/tool-schema.d.ts +22 -0
  292. package/dist/core/tools/tool-schema.d.ts.map +1 -0
  293. package/dist/core/tools/tool-schema.js +45 -0
  294. package/dist/core/tools/tool-schema.js.map +1 -0
  295. package/dist/core/tools/web-fetch.d.ts +26 -0
  296. package/dist/core/tools/web-fetch.d.ts.map +1 -0
  297. package/dist/core/tools/web-fetch.js +62 -0
  298. package/dist/core/tools/web-fetch.js.map +1 -0
  299. package/dist/core/tools/web-search.d.ts +26 -0
  300. package/dist/core/tools/web-search.d.ts.map +1 -0
  301. package/dist/core/tools/web-search.js +53 -0
  302. package/dist/core/tools/web-search.js.map +1 -0
  303. package/dist/core/tools/write.d.ts +6 -2
  304. package/dist/core/tools/write.d.ts.map +1 -1
  305. package/dist/core/tools/write.js +32 -1
  306. package/dist/core/tools/write.js.map +1 -1
  307. package/dist/core/usage-performance-store.d.ts +56 -0
  308. package/dist/core/usage-performance-store.d.ts.map +1 -0
  309. package/dist/core/usage-performance-store.js +85 -0
  310. package/dist/core/usage-performance-store.js.map +1 -0
  311. package/dist/index.d.ts +1 -0
  312. package/dist/index.d.ts.map +1 -1
  313. package/dist/index.js +1 -0
  314. package/dist/index.js.map +1 -1
  315. package/dist/main.d.ts.map +1 -1
  316. package/dist/main.js +48 -6
  317. package/dist/main.js.map +1 -1
  318. package/dist/migrations.d.ts.map +1 -1
  319. package/dist/migrations.js +2 -2
  320. package/dist/migrations.js.map +1 -1
  321. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  322. package/dist/modes/interactive/components/config-selector.js +9 -6
  323. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  324. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -4
  325. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  326. package/dist/modes/interactive/components/first-time-setup.js +11 -39
  327. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  328. package/dist/modes/interactive/components/footer.d.ts +8 -1
  329. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  330. package/dist/modes/interactive/components/footer.js +49 -20
  331. package/dist/modes/interactive/components/footer.js.map +1 -1
  332. package/dist/modes/interactive/components/settings-selector.d.ts +2 -2
  333. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  334. package/dist/modes/interactive/components/settings-selector.js +6 -6
  335. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  336. package/dist/modes/interactive/components/tool-execution.d.ts +2 -1
  337. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  338. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  339. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  340. package/dist/modes/interactive/external-editor.js +58 -9
  341. package/dist/modes/interactive/external-editor.js.map +1 -1
  342. package/dist/modes/interactive/interactive-mode.d.ts +2 -3
  343. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  344. package/dist/modes/interactive/interactive-mode.js +92 -104
  345. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  346. package/dist/modes/interactive/theme/theme-schema.json +2 -2
  347. package/dist/package-manager-cli.d.ts.map +1 -1
  348. package/dist/package-manager-cli.js +15 -10
  349. package/dist/package-manager-cli.js.map +1 -1
  350. package/dist/rpc-entry.d.ts.map +1 -1
  351. package/dist/rpc-entry.js +2 -1
  352. package/dist/rpc-entry.js.map +1 -1
  353. package/dist/server/create-harness.d.ts +1 -1
  354. package/dist/server/create-harness.d.ts.map +1 -1
  355. package/dist/server/create-harness.js +6 -5
  356. package/dist/server/create-harness.js.map +1 -1
  357. package/dist/testing/replay/metrics.d.ts +24 -0
  358. package/dist/testing/replay/metrics.d.ts.map +1 -0
  359. package/dist/testing/replay/metrics.js +35 -0
  360. package/dist/testing/replay/metrics.js.map +1 -0
  361. package/dist/testing/replay/recorded-provider.d.ts +10 -0
  362. package/dist/testing/replay/recorded-provider.d.ts.map +1 -0
  363. package/dist/testing/replay/recorded-provider.js +108 -0
  364. package/dist/testing/replay/recorded-provider.js.map +1 -0
  365. package/dist/testing/replay/runner.d.ts +56 -0
  366. package/dist/testing/replay/runner.d.ts.map +1 -0
  367. package/dist/testing/replay/runner.js +406 -0
  368. package/dist/testing/replay/runner.js.map +1 -0
  369. package/dist/utils/apex-code-user-agent.d.ts +2 -0
  370. package/dist/utils/apex-code-user-agent.d.ts.map +1 -0
  371. package/dist/utils/apex-code-user-agent.js +5 -0
  372. package/dist/utils/apex-code-user-agent.js.map +1 -0
  373. package/dist/utils/clipboard-image.d.ts.map +1 -1
  374. package/dist/utils/clipboard-image.js +1 -1
  375. package/dist/utils/clipboard-image.js.map +1 -1
  376. package/dist/utils/tools-manager.d.ts +32 -0
  377. package/dist/utils/tools-manager.d.ts.map +1 -1
  378. package/dist/utils/tools-manager.js +198 -100
  379. package/dist/utils/tools-manager.js.map +1 -1
  380. package/dist/utils/version-check.d.ts +5 -5
  381. package/dist/utils/version-check.d.ts.map +1 -1
  382. package/dist/utils/version-check.js +23 -14
  383. package/dist/utils/version-check.js.map +1 -1
  384. package/dist/utils/windows-self-update.d.ts.map +1 -1
  385. package/dist/utils/windows-self-update.js +1 -1
  386. package/dist/utils/windows-self-update.js.map +1 -1
  387. package/docs/compaction.md +21 -21
  388. package/docs/containerization.md +24 -21
  389. package/docs/custom-provider.md +9 -9
  390. package/docs/development.md +11 -11
  391. package/docs/environment-variables.md +36 -77
  392. package/docs/extensions.md +47 -47
  393. package/docs/index.md +12 -24
  394. package/docs/json.md +6 -6
  395. package/docs/keybindings.md +3 -3
  396. package/docs/llama-cpp.md +7 -7
  397. package/docs/models.md +11 -11
  398. package/docs/packages.md +46 -42
  399. package/docs/prompt-templates.md +3 -3
  400. package/docs/providers.md +10 -10
  401. package/docs/quickstart.md +49 -32
  402. package/docs/rpc.md +11 -11
  403. package/docs/sdk.md +4 -4
  404. package/docs/security.md +18 -18
  405. package/docs/session-format.md +9 -9
  406. package/docs/sessions.md +13 -13
  407. package/docs/settings.md +10 -14
  408. package/docs/shell-aliases.md +1 -1
  409. package/docs/skills.md +8 -8
  410. package/docs/terminal-setup.md +12 -12
  411. package/docs/termux.md +4 -4
  412. package/docs/themes.md +8 -8
  413. package/docs/tmux.md +4 -4
  414. package/docs/tui.md +3 -3
  415. package/docs/usage.md +54 -51
  416. package/docs/windows.md +1 -1
  417. package/npm-shrinkwrap.json +5 -5
  418. package/package.json +2 -3
  419. package/dist/core/telemetry.d.ts +0 -3
  420. package/dist/core/telemetry.d.ts.map +0 -1
  421. package/dist/core/telemetry.js +0 -9
  422. package/dist/core/telemetry.js.map +0 -1
  423. package/dist/utils/pi-user-agent.d.ts +0 -2
  424. package/dist/utils/pi-user-agent.d.ts.map +0 -1
  425. package/dist/utils/pi-user-agent.js +0 -5
  426. package/dist/utils/pi-user-agent.js.map +0 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Tool-result eviction — Phase 3 task 3.1.
3
+ *
4
+ * Implements the eviction algorithm settled in `docs/architecture/contracts.md` § 2
5
+ * ("Context pipeline order — settled"): a pure, prefix-oldest, contiguous-run
6
+ * transform over the message list. It runs *before* compaction in the pipeline
7
+ * order (deferred-schema resolution → eviction → compaction), but this module knows
8
+ * nothing about that ordering or about `transformContext` — wiring is task 3.3.
9
+ *
10
+ * Pure by construction: no clock, no randomness, no I/O, no reads of the live tool
11
+ * registry. Every fact this function needs about a tool comes through the injected
12
+ * `contractLookup`, and every fact about "how big is this" comes from the message
13
+ * content already in hand.
14
+ */
15
+ import type { AgentMessage } from "apex-code-agent-core";
16
+ import type { ToolContract } from "../tools/contract.ts";
17
+ /**
18
+ * What `evictToolResults` needs to know about a tool's contract to decide whether
19
+ * (and how) to evict one of its results. Only the `context` axis of `ToolContract`
20
+ * matters here — callers may pass a full contract, or (as tests do) a hand-built
21
+ * stand-in with just `context` filled in.
22
+ */
23
+ export type ContractLookup = (toolName: string) => Pick<ToolContract, "context"> | undefined;
24
+ /** Marker substituted for an evicted result when its contract declares none. */
25
+ export declare const DEFAULT_EVICTION_MARKER = "[Tool result evicted to save context. Re-run the tool to see it again.]";
26
+ /**
27
+ * Replace the oldest contiguous run of recoverable tool results with markers, until
28
+ * the estimated total token cost of *all* tool-result messages in the list drops to
29
+ * or below `budget` (chars/4, the same heuristic `estimateTokens` uses elsewhere in
30
+ * this codebase — see `core/compaction/compaction.ts`). "Contiguous run from the
31
+ * oldest end" is enforced by walking messages in order and stopping the walk for
32
+ * good — not just skipping — the moment a tool result is encountered whose contract
33
+ * says it is not recoverable (or has no contract at all, matching the `UNCLASSIFIED`
34
+ * default of `resultRecoverable: false`): later eligible results are never reached,
35
+ * so no hole ever opens in the middle of the transcript. Non-tool-result messages
36
+ * (assistant/user turns) are passed through untouched and do not interrupt or count
37
+ * toward the walk.
38
+ *
39
+ * Deterministic and idempotent: a message whose content already exactly equals its
40
+ * tool's marker is recognized as already evicted and left alone, so re-running this
41
+ * function over its own output (or over a budget that is already satisfied) is a
42
+ * no-op. Never mutates `messages` or any message object — always returns a new
43
+ * array; unevicted messages keep their original object identity (unmutated sharing
44
+ * is not mutation), evicted ones are new objects with only `content` replaced.
45
+ */
46
+ export declare function evictToolResults(messages: readonly AgentMessage[], contractLookup: ContractLookup, budget: number): AgentMessage[];
47
+ //# sourceMappingURL=eviction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eviction.d.ts","sourceRoot":"","sources":["../../../src/core/context/eviction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC;AAE7F,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,4EAA4E,CAAC;AAiBjH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,MAAM,GACZ,YAAY,EAAE,CAsChB","sourcesContent":["/**\n * Tool-result eviction — Phase 3 task 3.1.\n *\n * Implements the eviction algorithm settled in `docs/architecture/contracts.md` § 2\n * (\"Context pipeline order — settled\"): a pure, prefix-oldest, contiguous-run\n * transform over the message list. It runs *before* compaction in the pipeline\n * order (deferred-schema resolution → eviction → compaction), but this module knows\n * nothing about that ordering or about `transformContext` — wiring is task 3.3.\n *\n * Pure by construction: no clock, no randomness, no I/O, no reads of the live tool\n * registry. Every fact this function needs about a tool comes through the injected\n * `contractLookup`, and every fact about \"how big is this\" comes from the message\n * content already in hand.\n */\n\nimport type { TextContent } from \"@earendil-works/pi-ai\";\nimport type { AgentMessage } from \"apex-code-agent-core\";\nimport { estimateTokens } from \"../compaction/compaction.ts\";\nimport type { ToolContract } from \"../tools/contract.ts\";\n\n/**\n * What `evictToolResults` needs to know about a tool's contract to decide whether\n * (and how) to evict one of its results. Only the `context` axis of `ToolContract`\n * matters here — callers may pass a full contract, or (as tests do) a hand-built\n * stand-in with just `context` filled in.\n */\nexport type ContractLookup = (toolName: string) => Pick<ToolContract, \"context\"> | undefined;\n\n/** Marker substituted for an evicted result when its contract declares none. */\nexport const DEFAULT_EVICTION_MARKER = \"[Tool result evicted to save context. Re-run the tool to see it again.]\";\n\nfunction isToolResultMessage(message: AgentMessage): message is Extract<AgentMessage, { role: \"toolResult\" }> {\n\treturn message.role === \"toolResult\";\n}\n\n/** True when `content` is already exactly the single-text-block marker form. */\nfunction isAlreadyEvicted(content: readonly unknown[], marker: string): boolean {\n\treturn (\n\t\tcontent.length === 1 &&\n\t\ttypeof content[0] === \"object\" &&\n\t\tcontent[0] !== null &&\n\t\t(content[0] as { type?: unknown }).type === \"text\" &&\n\t\t(content[0] as { text?: unknown }).text === marker\n\t);\n}\n\n/**\n * Replace the oldest contiguous run of recoverable tool results with markers, until\n * the estimated total token cost of *all* tool-result messages in the list drops to\n * or below `budget` (chars/4, the same heuristic `estimateTokens` uses elsewhere in\n * this codebase — see `core/compaction/compaction.ts`). \"Contiguous run from the\n * oldest end\" is enforced by walking messages in order and stopping the walk for\n * good — not just skipping — the moment a tool result is encountered whose contract\n * says it is not recoverable (or has no contract at all, matching the `UNCLASSIFIED`\n * default of `resultRecoverable: false`): later eligible results are never reached,\n * so no hole ever opens in the middle of the transcript. Non-tool-result messages\n * (assistant/user turns) are passed through untouched and do not interrupt or count\n * toward the walk.\n *\n * Deterministic and idempotent: a message whose content already exactly equals its\n * tool's marker is recognized as already evicted and left alone, so re-running this\n * function over its own output (or over a budget that is already satisfied) is a\n * no-op. Never mutates `messages` or any message object — always returns a new\n * array; unevicted messages keep their original object identity (unmutated sharing\n * is not mutation), evicted ones are new objects with only `content` replaced.\n */\nexport function evictToolResults(\n\tmessages: readonly AgentMessage[],\n\tcontractLookup: ContractLookup,\n\tbudget: number,\n): AgentMessage[] {\n\tlet remaining = 0;\n\tfor (const message of messages) {\n\t\tif (isToolResultMessage(message)) remaining += estimateTokens(message);\n\t}\n\n\tconst result: AgentMessage[] = [];\n\tlet stopped = false;\n\n\tfor (const message of messages) {\n\t\tif (stopped || !isToolResultMessage(message) || remaining <= budget) {\n\t\t\tresult.push(message);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst contract = contractLookup(message.toolName);\n\t\tconst recoverable = contract?.context.resultRecoverable === true;\n\t\tif (!recoverable) {\n\t\t\tstopped = true;\n\t\t\tresult.push(message);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst marker = contract.context.evictionMarker ?? DEFAULT_EVICTION_MARKER;\n\t\tif (isAlreadyEvicted(message.content, marker)) {\n\t\t\tresult.push(message);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst before = estimateTokens(message);\n\t\tconst markerContent: TextContent[] = [{ type: \"text\", text: marker }];\n\t\tconst evicted = { ...message, content: markerContent };\n\t\tconst after = estimateTokens(evicted);\n\t\tremaining = remaining - before + after;\n\t\tresult.push(evicted);\n\t}\n\n\treturn result;\n}\n"]}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Tool-result eviction — Phase 3 task 3.1.
3
+ *
4
+ * Implements the eviction algorithm settled in `docs/architecture/contracts.md` § 2
5
+ * ("Context pipeline order — settled"): a pure, prefix-oldest, contiguous-run
6
+ * transform over the message list. It runs *before* compaction in the pipeline
7
+ * order (deferred-schema resolution → eviction → compaction), but this module knows
8
+ * nothing about that ordering or about `transformContext` — wiring is task 3.3.
9
+ *
10
+ * Pure by construction: no clock, no randomness, no I/O, no reads of the live tool
11
+ * registry. Every fact this function needs about a tool comes through the injected
12
+ * `contractLookup`, and every fact about "how big is this" comes from the message
13
+ * content already in hand.
14
+ */
15
+ import { estimateTokens } from "../compaction/compaction.js";
16
+ /** Marker substituted for an evicted result when its contract declares none. */
17
+ export const DEFAULT_EVICTION_MARKER = "[Tool result evicted to save context. Re-run the tool to see it again.]";
18
+ function isToolResultMessage(message) {
19
+ return message.role === "toolResult";
20
+ }
21
+ /** True when `content` is already exactly the single-text-block marker form. */
22
+ function isAlreadyEvicted(content, marker) {
23
+ return (content.length === 1 &&
24
+ typeof content[0] === "object" &&
25
+ content[0] !== null &&
26
+ content[0].type === "text" &&
27
+ content[0].text === marker);
28
+ }
29
+ /**
30
+ * Replace the oldest contiguous run of recoverable tool results with markers, until
31
+ * the estimated total token cost of *all* tool-result messages in the list drops to
32
+ * or below `budget` (chars/4, the same heuristic `estimateTokens` uses elsewhere in
33
+ * this codebase — see `core/compaction/compaction.ts`). "Contiguous run from the
34
+ * oldest end" is enforced by walking messages in order and stopping the walk for
35
+ * good — not just skipping — the moment a tool result is encountered whose contract
36
+ * says it is not recoverable (or has no contract at all, matching the `UNCLASSIFIED`
37
+ * default of `resultRecoverable: false`): later eligible results are never reached,
38
+ * so no hole ever opens in the middle of the transcript. Non-tool-result messages
39
+ * (assistant/user turns) are passed through untouched and do not interrupt or count
40
+ * toward the walk.
41
+ *
42
+ * Deterministic and idempotent: a message whose content already exactly equals its
43
+ * tool's marker is recognized as already evicted and left alone, so re-running this
44
+ * function over its own output (or over a budget that is already satisfied) is a
45
+ * no-op. Never mutates `messages` or any message object — always returns a new
46
+ * array; unevicted messages keep their original object identity (unmutated sharing
47
+ * is not mutation), evicted ones are new objects with only `content` replaced.
48
+ */
49
+ export function evictToolResults(messages, contractLookup, budget) {
50
+ let remaining = 0;
51
+ for (const message of messages) {
52
+ if (isToolResultMessage(message))
53
+ remaining += estimateTokens(message);
54
+ }
55
+ const result = [];
56
+ let stopped = false;
57
+ for (const message of messages) {
58
+ if (stopped || !isToolResultMessage(message) || remaining <= budget) {
59
+ result.push(message);
60
+ continue;
61
+ }
62
+ const contract = contractLookup(message.toolName);
63
+ const recoverable = contract?.context.resultRecoverable === true;
64
+ if (!recoverable) {
65
+ stopped = true;
66
+ result.push(message);
67
+ continue;
68
+ }
69
+ const marker = contract.context.evictionMarker ?? DEFAULT_EVICTION_MARKER;
70
+ if (isAlreadyEvicted(message.content, marker)) {
71
+ result.push(message);
72
+ continue;
73
+ }
74
+ const before = estimateTokens(message);
75
+ const markerContent = [{ type: "text", text: marker }];
76
+ const evicted = { ...message, content: markerContent };
77
+ const after = estimateTokens(evicted);
78
+ remaining = remaining - before + after;
79
+ result.push(evicted);
80
+ }
81
+ return result;
82
+ }
83
+ //# sourceMappingURL=eviction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eviction.js","sourceRoot":"","sources":["../../../src/core/context/eviction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAW7D,gFAAgF;AAChF,MAAM,CAAC,MAAM,uBAAuB,GAAG,yEAAyE,CAAC;AAEjH,SAAS,mBAAmB,CAAC,OAAqB,EAA4D;IAC7G,OAAO,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC;AAAA,CACrC;AAED,gFAAgF;AAChF,SAAS,gBAAgB,CAAC,OAA2B,EAAE,MAAc,EAAW;IAC/E,OAAO,CACN,OAAO,CAAC,MAAM,KAAK,CAAC;QACpB,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ;QAC9B,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI;QAClB,OAAO,CAAC,CAAC,CAAwB,CAAC,IAAI,KAAK,MAAM;QACjD,OAAO,CAAC,CAAC,CAAwB,CAAC,IAAI,KAAK,MAAM,CAClD,CAAC;AAAA,CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAC/B,QAAiC,EACjC,cAA8B,EAC9B,MAAc,EACG;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAChC,IAAI,mBAAmB,CAAC,OAAO,CAAC;YAAE,SAAS,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAChC,IAAI,OAAO,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC;YACrE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,SAAS;QACV,CAAC;QAED,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,WAAW,GAAG,QAAQ,EAAE,OAAO,CAAC,iBAAiB,KAAK,IAAI,CAAC;QACjE,IAAI,CAAC,WAAW,EAAE,CAAC;YAClB,OAAO,GAAG,IAAI,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,SAAS;QACV,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,IAAI,uBAAuB,CAAC;QAC1E,IAAI,gBAAgB,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,SAAS;QACV,CAAC;QAED,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,aAAa,GAAkB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACtC,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * Tool-result eviction — Phase 3 task 3.1.\n *\n * Implements the eviction algorithm settled in `docs/architecture/contracts.md` § 2\n * (\"Context pipeline order — settled\"): a pure, prefix-oldest, contiguous-run\n * transform over the message list. It runs *before* compaction in the pipeline\n * order (deferred-schema resolution → eviction → compaction), but this module knows\n * nothing about that ordering or about `transformContext` — wiring is task 3.3.\n *\n * Pure by construction: no clock, no randomness, no I/O, no reads of the live tool\n * registry. Every fact this function needs about a tool comes through the injected\n * `contractLookup`, and every fact about \"how big is this\" comes from the message\n * content already in hand.\n */\n\nimport type { TextContent } from \"@earendil-works/pi-ai\";\nimport type { AgentMessage } from \"apex-code-agent-core\";\nimport { estimateTokens } from \"../compaction/compaction.ts\";\nimport type { ToolContract } from \"../tools/contract.ts\";\n\n/**\n * What `evictToolResults` needs to know about a tool's contract to decide whether\n * (and how) to evict one of its results. Only the `context` axis of `ToolContract`\n * matters here — callers may pass a full contract, or (as tests do) a hand-built\n * stand-in with just `context` filled in.\n */\nexport type ContractLookup = (toolName: string) => Pick<ToolContract, \"context\"> | undefined;\n\n/** Marker substituted for an evicted result when its contract declares none. */\nexport const DEFAULT_EVICTION_MARKER = \"[Tool result evicted to save context. Re-run the tool to see it again.]\";\n\nfunction isToolResultMessage(message: AgentMessage): message is Extract<AgentMessage, { role: \"toolResult\" }> {\n\treturn message.role === \"toolResult\";\n}\n\n/** True when `content` is already exactly the single-text-block marker form. */\nfunction isAlreadyEvicted(content: readonly unknown[], marker: string): boolean {\n\treturn (\n\t\tcontent.length === 1 &&\n\t\ttypeof content[0] === \"object\" &&\n\t\tcontent[0] !== null &&\n\t\t(content[0] as { type?: unknown }).type === \"text\" &&\n\t\t(content[0] as { text?: unknown }).text === marker\n\t);\n}\n\n/**\n * Replace the oldest contiguous run of recoverable tool results with markers, until\n * the estimated total token cost of *all* tool-result messages in the list drops to\n * or below `budget` (chars/4, the same heuristic `estimateTokens` uses elsewhere in\n * this codebase — see `core/compaction/compaction.ts`). \"Contiguous run from the\n * oldest end\" is enforced by walking messages in order and stopping the walk for\n * good — not just skipping — the moment a tool result is encountered whose contract\n * says it is not recoverable (or has no contract at all, matching the `UNCLASSIFIED`\n * default of `resultRecoverable: false`): later eligible results are never reached,\n * so no hole ever opens in the middle of the transcript. Non-tool-result messages\n * (assistant/user turns) are passed through untouched and do not interrupt or count\n * toward the walk.\n *\n * Deterministic and idempotent: a message whose content already exactly equals its\n * tool's marker is recognized as already evicted and left alone, so re-running this\n * function over its own output (or over a budget that is already satisfied) is a\n * no-op. Never mutates `messages` or any message object — always returns a new\n * array; unevicted messages keep their original object identity (unmutated sharing\n * is not mutation), evicted ones are new objects with only `content` replaced.\n */\nexport function evictToolResults(\n\tmessages: readonly AgentMessage[],\n\tcontractLookup: ContractLookup,\n\tbudget: number,\n): AgentMessage[] {\n\tlet remaining = 0;\n\tfor (const message of messages) {\n\t\tif (isToolResultMessage(message)) remaining += estimateTokens(message);\n\t}\n\n\tconst result: AgentMessage[] = [];\n\tlet stopped = false;\n\n\tfor (const message of messages) {\n\t\tif (stopped || !isToolResultMessage(message) || remaining <= budget) {\n\t\t\tresult.push(message);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst contract = contractLookup(message.toolName);\n\t\tconst recoverable = contract?.context.resultRecoverable === true;\n\t\tif (!recoverable) {\n\t\t\tstopped = true;\n\t\t\tresult.push(message);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst marker = contract.context.evictionMarker ?? DEFAULT_EVICTION_MARKER;\n\t\tif (isAlreadyEvicted(message.content, marker)) {\n\t\t\tresult.push(message);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst before = estimateTokens(message);\n\t\tconst markerContent: TextContent[] = [{ type: \"text\", text: marker }];\n\t\tconst evicted = { ...message, content: markerContent };\n\t\tconst after = estimateTokens(evicted);\n\t\tremaining = remaining - before + after;\n\t\tresult.push(evicted);\n\t}\n\n\treturn result;\n}\n"]}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Context pipeline wiring — shared by `AgentSession` (production) and the offline
3
+ * replay harness (`src/testing/replay/runner.ts`), so there is exactly one
4
+ * implementation of "how deferred-schema resolution and eviction attach to an
5
+ * `Agent`" rather than two independently maintained copies.
6
+ *
7
+ * Implements the ordering settled in `docs/architecture/contracts.md` § 2:
8
+ * deferred-schema resolution → tool-result eviction → compaction. Compaction runs
9
+ * reactively from its own existing seam (`_checkCompaction` in `agent-session.ts`)
10
+ * and is untouched here; this module only adds the two stages that run *ahead of*
11
+ * every LLM request, so that reactive check is reached less often because the
12
+ * request it is reacting to is smaller.
13
+ *
14
+ * The two stages land in different seams because they operate on different halves
15
+ * of the outbound request. Eviction is a pure function of `AgentMessage[]`, which is
16
+ * exactly what `transformContext` sees, immediately before `convertToLlm` — so it is
17
+ * wired there. Deferred-schema resolution operates on the *tool list*, not messages;
18
+ * `transformContext`'s signature has no tools parameter, and `AgentContext.tools` is
19
+ * a snapshot taken once per prompt rather than rebuilt per request. The seam that
20
+ * actually sees the assembled outbound tool list on every request is
21
+ * `streamFunction` — it receives the full `Context`, tools included, immediately
22
+ * before the provider call — so the projection is applied there instead. Because the
23
+ * two stages act on disjoint fields of the request with no data dependency between
24
+ * them, this does not change the observable pipeline order.
25
+ */
26
+ import type { Agent, StreamFn } from "apex-code-agent-core";
27
+ import { type ContractLookup } from "./eviction.ts";
28
+ /**
29
+ * Eviction-budget formula shared by every caller of `installContextPipeline`.
30
+ * Compaction fires once total context tokens exceed `contextWindow - reserveTokens`
31
+ * (`shouldCompact` in `core/compaction/compaction.ts`). Budgeting eviction — counted
32
+ * purely against tool-result tokens, per `evictToolResults`'s own contract — at half
33
+ * of that headroom leaves the other half free for system prompt and conversational
34
+ * growth, so eviction (cheap, structure-preserving) gets a chance to shrink the
35
+ * transcript before the expensive compaction stage is ever reached, matching
36
+ * contracts.md § 2's "why this order" (eviction before compaction, so compaction is
37
+ * reached later and less often).
38
+ *
39
+ * `Math.max(0, ...)` mirrors `shouldCompact`'s own behavior when `contextWindow` is
40
+ * unknown (0): evict aggressively rather than not at all, rather than inventing a
41
+ * different, unproven fallback.
42
+ *
43
+ * Production (`AgentSession`) calls this with the real model's `contextWindow` and
44
+ * the user's configured `reserveTokens`. The offline replay harness deliberately
45
+ * does *not* call this with the fixture model's `contextWindow` — see
46
+ * `REPLAY_EVICTION_BUDGET` in `src/testing/replay/runner.ts` for why a dedicated
47
+ * constant is used there instead.
48
+ */
49
+ export declare function evictionBudget(contextWindow: number, reserveTokens: number): number;
50
+ /**
51
+ * Adapter between a caller's tool registry and `announceToolsByName`, which
52
+ * operates on a minimal `DeferrableTool` shape that has no notion of a tool
53
+ * registry. Only `parameters` is ever replaced; every other field of the outbound
54
+ * tool (name, description, and on `AgentTool` specifically, `execute`, `label`,
55
+ * etc.) passes through unchanged via the object spread.
56
+ *
57
+ * A tool with no entry in `contractLookup` (a foreign/unclassified tool) is treated
58
+ * as `deferSchema: false` here — the safe default on this axis is to keep
59
+ * describing it fully, the mirror image of eviction's "no contract => don't evict"
60
+ * default: when unsure, don't withhold information the model needs.
61
+ */
62
+ export declare function projectToolSchemas<T extends {
63
+ name: string;
64
+ description: string;
65
+ parameters: unknown;
66
+ }>(tools: readonly T[], contractLookup: ContractLookup, loadedSchemaNames?: ReadonlySet<string>): T[];
67
+ /** Returns true for the built-in streamSimple function, including its context-pipeline wrapper. */
68
+ export declare function isDefaultStreamFunction(streamFunction: StreamFn): boolean;
69
+ export interface ContextPipelineOptions {
70
+ /** Resolves a tool's contract; shared by both eviction and schema projection. */
71
+ contractLookup: ContractLookup;
72
+ /** Names whose real schemas have been loaded by the model in this session. */
73
+ loadedSchemaNames?: ReadonlySet<string>;
74
+ /**
75
+ * Eviction budget as a thunk, not a plain number: `AgentSession`'s budget
76
+ * depends on `this.model` and `this.settingsManager`, both of which can change
77
+ * over the session's lifetime (model switch, settings edit), and is never
78
+ * cached. Evaluating it fresh on every request is what keeps eviction correct
79
+ * after either changes mid-session.
80
+ */
81
+ evictionBudget: () => number;
82
+ }
83
+ /**
84
+ * Installs the settled context pipeline (deferred-schema resolution → eviction)
85
+ * onto `agent`, chaining onto whatever `transformContext` / `streamFunction` the
86
+ * caller already installed rather than replacing it — the same pattern
87
+ * `AgentSession._installAgentNextTurnRefresh` uses for `prepareNextTurnWithContext`.
88
+ */
89
+ export declare function installContextPipeline(agent: Agent, options: ContextPipelineOptions): void;
90
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../src/core/context/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAG5D,OAAO,EAAE,KAAK,cAAc,EAAoB,MAAM,eAAe,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,EACtG,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,cAAc,EAAE,cAAc,EAC9B,iBAAiB,GAAE,WAAW,CAAC,MAAM,CAAa,GAChD,CAAC,EAAE,CAcL;AAID,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,QAAQ,GAAG,OAAO,CAEzE;AAED,MAAM,WAAW,sBAAsB;IACtC,iFAAiF;IACjF,cAAc,EAAE,cAAc,CAAC;IAC/B,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;OAMG;IACH,cAAc,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,sBAAsB,GAAG,IAAI,CA0B1F","sourcesContent":["/**\n * Context pipeline wiring — shared by `AgentSession` (production) and the offline\n * replay harness (`src/testing/replay/runner.ts`), so there is exactly one\n * implementation of \"how deferred-schema resolution and eviction attach to an\n * `Agent`\" rather than two independently maintained copies.\n *\n * Implements the ordering settled in `docs/architecture/contracts.md` § 2:\n * deferred-schema resolution → tool-result eviction → compaction. Compaction runs\n * reactively from its own existing seam (`_checkCompaction` in `agent-session.ts`)\n * and is untouched here; this module only adds the two stages that run *ahead of*\n * every LLM request, so that reactive check is reached less often because the\n * request it is reacting to is smaller.\n *\n * The two stages land in different seams because they operate on different halves\n * of the outbound request. Eviction is a pure function of `AgentMessage[]`, which is\n * exactly what `transformContext` sees, immediately before `convertToLlm` — so it is\n * wired there. Deferred-schema resolution operates on the *tool list*, not messages;\n * `transformContext`'s signature has no tools parameter, and `AgentContext.tools` is\n * a snapshot taken once per prompt rather than rebuilt per request. The seam that\n * actually sees the assembled outbound tool list on every request is\n * `streamFunction` — it receives the full `Context`, tools included, immediately\n * before the provider call — so the projection is applied there instead. Because the\n * two stages act on disjoint fields of the request with no data dependency between\n * them, this does not change the observable pipeline order.\n */\n\nimport { streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport type { Agent, StreamFn } from \"apex-code-agent-core\";\nimport { resolveToolContext } from \"../tools/contract.ts\";\nimport { announceToolsByName, type DeferrableTool } from \"./deferred-schemas.ts\";\nimport { type ContractLookup, evictToolResults } from \"./eviction.ts\";\n\n/**\n * Eviction-budget formula shared by every caller of `installContextPipeline`.\n * Compaction fires once total context tokens exceed `contextWindow - reserveTokens`\n * (`shouldCompact` in `core/compaction/compaction.ts`). Budgeting eviction — counted\n * purely against tool-result tokens, per `evictToolResults`'s own contract — at half\n * of that headroom leaves the other half free for system prompt and conversational\n * growth, so eviction (cheap, structure-preserving) gets a chance to shrink the\n * transcript before the expensive compaction stage is ever reached, matching\n * contracts.md § 2's \"why this order\" (eviction before compaction, so compaction is\n * reached later and less often).\n *\n * `Math.max(0, ...)` mirrors `shouldCompact`'s own behavior when `contextWindow` is\n * unknown (0): evict aggressively rather than not at all, rather than inventing a\n * different, unproven fallback.\n *\n * Production (`AgentSession`) calls this with the real model's `contextWindow` and\n * the user's configured `reserveTokens`. The offline replay harness deliberately\n * does *not* call this with the fixture model's `contextWindow` — see\n * `REPLAY_EVICTION_BUDGET` in `src/testing/replay/runner.ts` for why a dedicated\n * constant is used there instead.\n */\nexport function evictionBudget(contextWindow: number, reserveTokens: number): number {\n\treturn Math.max(0, Math.floor((contextWindow - reserveTokens) / 2));\n}\n\n/**\n * Adapter between a caller's tool registry and `announceToolsByName`, which\n * operates on a minimal `DeferrableTool` shape that has no notion of a tool\n * registry. Only `parameters` is ever replaced; every other field of the outbound\n * tool (name, description, and on `AgentTool` specifically, `execute`, `label`,\n * etc.) passes through unchanged via the object spread.\n *\n * A tool with no entry in `contractLookup` (a foreign/unclassified tool) is treated\n * as `deferSchema: false` here — the safe default on this axis is to keep\n * describing it fully, the mirror image of eviction's \"no contract => don't evict\"\n * default: when unsure, don't withhold information the model needs.\n */\nexport function projectToolSchemas<T extends { name: string; description: string; parameters: unknown }>(\n\ttools: readonly T[],\n\tcontractLookup: ContractLookup,\n\tloadedSchemaNames: ReadonlySet<string> = new Set(),\n): T[] {\n\tconst deferrable: DeferrableTool[] = tools.map((tool) => ({\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tparameters: tool.parameters,\n\t\tcontract: {\n\t\t\tcontext: {\n\t\t\t\tdeferSchema:\n\t\t\t\t\t!loadedSchemaNames.has(tool.name) && resolveToolContext(contractLookup, tool.name).context.deferSchema,\n\t\t\t},\n\t\t},\n\t}));\n\tconst announced = announceToolsByName(deferrable);\n\treturn tools.map((tool, index) => ({ ...tool, parameters: announced[index].parameters }));\n}\n\nconst defaultStreamFunctions = new WeakSet<StreamFn>();\n\n/** Returns true for the built-in streamSimple function, including its context-pipeline wrapper. */\nexport function isDefaultStreamFunction(streamFunction: StreamFn): boolean {\n\treturn streamFunction === streamSimple || defaultStreamFunctions.has(streamFunction);\n}\n\nexport interface ContextPipelineOptions {\n\t/** Resolves a tool's contract; shared by both eviction and schema projection. */\n\tcontractLookup: ContractLookup;\n\t/** Names whose real schemas have been loaded by the model in this session. */\n\tloadedSchemaNames?: ReadonlySet<string>;\n\t/**\n\t * Eviction budget as a thunk, not a plain number: `AgentSession`'s budget\n\t * depends on `this.model` and `this.settingsManager`, both of which can change\n\t * over the session's lifetime (model switch, settings edit), and is never\n\t * cached. Evaluating it fresh on every request is what keeps eviction correct\n\t * after either changes mid-session.\n\t */\n\tevictionBudget: () => number;\n}\n\n/**\n * Installs the settled context pipeline (deferred-schema resolution → eviction)\n * onto `agent`, chaining onto whatever `transformContext` / `streamFunction` the\n * caller already installed rather than replacing it — the same pattern\n * `AgentSession._installAgentNextTurnRefresh` uses for `prepareNextTurnWithContext`.\n */\nexport function installContextPipeline(agent: Agent, options: ContextPipelineOptions): void {\n\tconst { contractLookup, evictionBudget: getBudget, loadedSchemaNames = new Set<string>() } = options;\n\n\tconst previousTransformContext = agent.transformContext;\n\tagent.transformContext = async (messages, signal) => {\n\t\tconst afterPrevious = previousTransformContext ? await previousTransformContext(messages, signal) : messages;\n\t\treturn evictToolResults(afterPrevious, contractLookup, getBudget());\n\t};\n\n\tconst previousStreamFunction = agent.streamFunction;\n\tconst wrappedStreamFunction = (\n\t\tmodel: Parameters<typeof previousStreamFunction>[0],\n\t\tcontext: Parameters<typeof previousStreamFunction>[1],\n\t\tstreamOptions: Parameters<typeof previousStreamFunction>[2],\n\t) => {\n\t\tif (!context.tools || context.tools.length === 0) {\n\t\t\treturn previousStreamFunction(model, context, streamOptions);\n\t\t}\n\t\treturn previousStreamFunction(\n\t\t\tmodel,\n\t\t\t{ ...context, tools: projectToolSchemas(context.tools, contractLookup, loadedSchemaNames) },\n\t\t\tstreamOptions,\n\t\t);\n\t};\n\tif (isDefaultStreamFunction(previousStreamFunction)) defaultStreamFunctions.add(wrappedStreamFunction);\n\tagent.streamFunction = wrappedStreamFunction;\n}\n"]}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Context pipeline wiring — shared by `AgentSession` (production) and the offline
3
+ * replay harness (`src/testing/replay/runner.ts`), so there is exactly one
4
+ * implementation of "how deferred-schema resolution and eviction attach to an
5
+ * `Agent`" rather than two independently maintained copies.
6
+ *
7
+ * Implements the ordering settled in `docs/architecture/contracts.md` § 2:
8
+ * deferred-schema resolution → tool-result eviction → compaction. Compaction runs
9
+ * reactively from its own existing seam (`_checkCompaction` in `agent-session.ts`)
10
+ * and is untouched here; this module only adds the two stages that run *ahead of*
11
+ * every LLM request, so that reactive check is reached less often because the
12
+ * request it is reacting to is smaller.
13
+ *
14
+ * The two stages land in different seams because they operate on different halves
15
+ * of the outbound request. Eviction is a pure function of `AgentMessage[]`, which is
16
+ * exactly what `transformContext` sees, immediately before `convertToLlm` — so it is
17
+ * wired there. Deferred-schema resolution operates on the *tool list*, not messages;
18
+ * `transformContext`'s signature has no tools parameter, and `AgentContext.tools` is
19
+ * a snapshot taken once per prompt rather than rebuilt per request. The seam that
20
+ * actually sees the assembled outbound tool list on every request is
21
+ * `streamFunction` — it receives the full `Context`, tools included, immediately
22
+ * before the provider call — so the projection is applied there instead. Because the
23
+ * two stages act on disjoint fields of the request with no data dependency between
24
+ * them, this does not change the observable pipeline order.
25
+ */
26
+ import { streamSimple } from "@earendil-works/pi-ai/compat";
27
+ import { resolveToolContext } from "../tools/contract.js";
28
+ import { announceToolsByName } from "./deferred-schemas.js";
29
+ import { evictToolResults } from "./eviction.js";
30
+ /**
31
+ * Eviction-budget formula shared by every caller of `installContextPipeline`.
32
+ * Compaction fires once total context tokens exceed `contextWindow - reserveTokens`
33
+ * (`shouldCompact` in `core/compaction/compaction.ts`). Budgeting eviction — counted
34
+ * purely against tool-result tokens, per `evictToolResults`'s own contract — at half
35
+ * of that headroom leaves the other half free for system prompt and conversational
36
+ * growth, so eviction (cheap, structure-preserving) gets a chance to shrink the
37
+ * transcript before the expensive compaction stage is ever reached, matching
38
+ * contracts.md § 2's "why this order" (eviction before compaction, so compaction is
39
+ * reached later and less often).
40
+ *
41
+ * `Math.max(0, ...)` mirrors `shouldCompact`'s own behavior when `contextWindow` is
42
+ * unknown (0): evict aggressively rather than not at all, rather than inventing a
43
+ * different, unproven fallback.
44
+ *
45
+ * Production (`AgentSession`) calls this with the real model's `contextWindow` and
46
+ * the user's configured `reserveTokens`. The offline replay harness deliberately
47
+ * does *not* call this with the fixture model's `contextWindow` — see
48
+ * `REPLAY_EVICTION_BUDGET` in `src/testing/replay/runner.ts` for why a dedicated
49
+ * constant is used there instead.
50
+ */
51
+ export function evictionBudget(contextWindow, reserveTokens) {
52
+ return Math.max(0, Math.floor((contextWindow - reserveTokens) / 2));
53
+ }
54
+ /**
55
+ * Adapter between a caller's tool registry and `announceToolsByName`, which
56
+ * operates on a minimal `DeferrableTool` shape that has no notion of a tool
57
+ * registry. Only `parameters` is ever replaced; every other field of the outbound
58
+ * tool (name, description, and on `AgentTool` specifically, `execute`, `label`,
59
+ * etc.) passes through unchanged via the object spread.
60
+ *
61
+ * A tool with no entry in `contractLookup` (a foreign/unclassified tool) is treated
62
+ * as `deferSchema: false` here — the safe default on this axis is to keep
63
+ * describing it fully, the mirror image of eviction's "no contract => don't evict"
64
+ * default: when unsure, don't withhold information the model needs.
65
+ */
66
+ export function projectToolSchemas(tools, contractLookup, loadedSchemaNames = new Set()) {
67
+ const deferrable = tools.map((tool) => ({
68
+ name: tool.name,
69
+ description: tool.description,
70
+ parameters: tool.parameters,
71
+ contract: {
72
+ context: {
73
+ deferSchema: !loadedSchemaNames.has(tool.name) && resolveToolContext(contractLookup, tool.name).context.deferSchema,
74
+ },
75
+ },
76
+ }));
77
+ const announced = announceToolsByName(deferrable);
78
+ return tools.map((tool, index) => ({ ...tool, parameters: announced[index].parameters }));
79
+ }
80
+ const defaultStreamFunctions = new WeakSet();
81
+ /** Returns true for the built-in streamSimple function, including its context-pipeline wrapper. */
82
+ export function isDefaultStreamFunction(streamFunction) {
83
+ return streamFunction === streamSimple || defaultStreamFunctions.has(streamFunction);
84
+ }
85
+ /**
86
+ * Installs the settled context pipeline (deferred-schema resolution → eviction)
87
+ * onto `agent`, chaining onto whatever `transformContext` / `streamFunction` the
88
+ * caller already installed rather than replacing it — the same pattern
89
+ * `AgentSession._installAgentNextTurnRefresh` uses for `prepareNextTurnWithContext`.
90
+ */
91
+ export function installContextPipeline(agent, options) {
92
+ const { contractLookup, evictionBudget: getBudget, loadedSchemaNames = new Set() } = options;
93
+ const previousTransformContext = agent.transformContext;
94
+ agent.transformContext = async (messages, signal) => {
95
+ const afterPrevious = previousTransformContext ? await previousTransformContext(messages, signal) : messages;
96
+ return evictToolResults(afterPrevious, contractLookup, getBudget());
97
+ };
98
+ const previousStreamFunction = agent.streamFunction;
99
+ const wrappedStreamFunction = (model, context, streamOptions) => {
100
+ if (!context.tools || context.tools.length === 0) {
101
+ return previousStreamFunction(model, context, streamOptions);
102
+ }
103
+ return previousStreamFunction(model, { ...context, tools: projectToolSchemas(context.tools, contractLookup, loadedSchemaNames) }, streamOptions);
104
+ };
105
+ if (isDefaultStreamFunction(previousStreamFunction))
106
+ defaultStreamFunctions.add(wrappedStreamFunction);
107
+ agent.streamFunction = wrappedStreamFunction;
108
+ }
109
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../../src/core/context/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAuB,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAuB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,cAAc,CAAC,aAAqB,EAAE,aAAqB,EAAU;IACpF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAAA,CACpE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CACjC,KAAmB,EACnB,cAA8B,EAC9B,iBAAiB,GAAwB,IAAI,GAAG,EAAE,EAC5C;IACN,MAAM,UAAU,GAAqB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzD,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,QAAQ,EAAE;YACT,OAAO,EAAE;gBACR,WAAW,EACV,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW;aACvG;SACD;KACD,CAAC,CAAC,CAAC;IACJ,MAAM,SAAS,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAAA,CAC1F;AAED,MAAM,sBAAsB,GAAG,IAAI,OAAO,EAAY,CAAC;AAEvD,mGAAmG;AACnG,MAAM,UAAU,uBAAuB,CAAC,cAAwB,EAAW;IAC1E,OAAO,cAAc,KAAK,YAAY,IAAI,sBAAsB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAAA,CACrF;AAiBD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAY,EAAE,OAA+B,EAAQ;IAC3F,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,GAAG,IAAI,GAAG,EAAU,EAAE,GAAG,OAAO,CAAC;IAErG,MAAM,wBAAwB,GAAG,KAAK,CAAC,gBAAgB,CAAC;IACxD,KAAK,CAAC,gBAAgB,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC;QACpD,MAAM,aAAa,GAAG,wBAAwB,CAAC,CAAC,CAAC,MAAM,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC7G,OAAO,gBAAgB,CAAC,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC,CAAC;IAAA,CACpE,CAAC;IAEF,MAAM,sBAAsB,GAAG,KAAK,CAAC,cAAc,CAAC;IACpD,MAAM,qBAAqB,GAAG,CAC7B,KAAmD,EACnD,OAAqD,EACrD,aAA2D,EAC1D,EAAE,CAAC;QACJ,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClD,OAAO,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,sBAAsB,CAC5B,KAAK,EACL,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,kBAAkB,CAAC,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,iBAAiB,CAAC,EAAE,EAC3F,aAAa,CACb,CAAC;IAAA,CACF,CAAC;IACF,IAAI,uBAAuB,CAAC,sBAAsB,CAAC;QAAE,sBAAsB,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACvG,KAAK,CAAC,cAAc,GAAG,qBAAqB,CAAC;AAAA,CAC7C","sourcesContent":["/**\n * Context pipeline wiring — shared by `AgentSession` (production) and the offline\n * replay harness (`src/testing/replay/runner.ts`), so there is exactly one\n * implementation of \"how deferred-schema resolution and eviction attach to an\n * `Agent`\" rather than two independently maintained copies.\n *\n * Implements the ordering settled in `docs/architecture/contracts.md` § 2:\n * deferred-schema resolution → tool-result eviction → compaction. Compaction runs\n * reactively from its own existing seam (`_checkCompaction` in `agent-session.ts`)\n * and is untouched here; this module only adds the two stages that run *ahead of*\n * every LLM request, so that reactive check is reached less often because the\n * request it is reacting to is smaller.\n *\n * The two stages land in different seams because they operate on different halves\n * of the outbound request. Eviction is a pure function of `AgentMessage[]`, which is\n * exactly what `transformContext` sees, immediately before `convertToLlm` — so it is\n * wired there. Deferred-schema resolution operates on the *tool list*, not messages;\n * `transformContext`'s signature has no tools parameter, and `AgentContext.tools` is\n * a snapshot taken once per prompt rather than rebuilt per request. The seam that\n * actually sees the assembled outbound tool list on every request is\n * `streamFunction` — it receives the full `Context`, tools included, immediately\n * before the provider call — so the projection is applied there instead. Because the\n * two stages act on disjoint fields of the request with no data dependency between\n * them, this does not change the observable pipeline order.\n */\n\nimport { streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport type { Agent, StreamFn } from \"apex-code-agent-core\";\nimport { resolveToolContext } from \"../tools/contract.ts\";\nimport { announceToolsByName, type DeferrableTool } from \"./deferred-schemas.ts\";\nimport { type ContractLookup, evictToolResults } from \"./eviction.ts\";\n\n/**\n * Eviction-budget formula shared by every caller of `installContextPipeline`.\n * Compaction fires once total context tokens exceed `contextWindow - reserveTokens`\n * (`shouldCompact` in `core/compaction/compaction.ts`). Budgeting eviction — counted\n * purely against tool-result tokens, per `evictToolResults`'s own contract — at half\n * of that headroom leaves the other half free for system prompt and conversational\n * growth, so eviction (cheap, structure-preserving) gets a chance to shrink the\n * transcript before the expensive compaction stage is ever reached, matching\n * contracts.md § 2's \"why this order\" (eviction before compaction, so compaction is\n * reached later and less often).\n *\n * `Math.max(0, ...)` mirrors `shouldCompact`'s own behavior when `contextWindow` is\n * unknown (0): evict aggressively rather than not at all, rather than inventing a\n * different, unproven fallback.\n *\n * Production (`AgentSession`) calls this with the real model's `contextWindow` and\n * the user's configured `reserveTokens`. The offline replay harness deliberately\n * does *not* call this with the fixture model's `contextWindow` — see\n * `REPLAY_EVICTION_BUDGET` in `src/testing/replay/runner.ts` for why a dedicated\n * constant is used there instead.\n */\nexport function evictionBudget(contextWindow: number, reserveTokens: number): number {\n\treturn Math.max(0, Math.floor((contextWindow - reserveTokens) / 2));\n}\n\n/**\n * Adapter between a caller's tool registry and `announceToolsByName`, which\n * operates on a minimal `DeferrableTool` shape that has no notion of a tool\n * registry. Only `parameters` is ever replaced; every other field of the outbound\n * tool (name, description, and on `AgentTool` specifically, `execute`, `label`,\n * etc.) passes through unchanged via the object spread.\n *\n * A tool with no entry in `contractLookup` (a foreign/unclassified tool) is treated\n * as `deferSchema: false` here — the safe default on this axis is to keep\n * describing it fully, the mirror image of eviction's \"no contract => don't evict\"\n * default: when unsure, don't withhold information the model needs.\n */\nexport function projectToolSchemas<T extends { name: string; description: string; parameters: unknown }>(\n\ttools: readonly T[],\n\tcontractLookup: ContractLookup,\n\tloadedSchemaNames: ReadonlySet<string> = new Set(),\n): T[] {\n\tconst deferrable: DeferrableTool[] = tools.map((tool) => ({\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tparameters: tool.parameters,\n\t\tcontract: {\n\t\t\tcontext: {\n\t\t\t\tdeferSchema:\n\t\t\t\t\t!loadedSchemaNames.has(tool.name) && resolveToolContext(contractLookup, tool.name).context.deferSchema,\n\t\t\t},\n\t\t},\n\t}));\n\tconst announced = announceToolsByName(deferrable);\n\treturn tools.map((tool, index) => ({ ...tool, parameters: announced[index].parameters }));\n}\n\nconst defaultStreamFunctions = new WeakSet<StreamFn>();\n\n/** Returns true for the built-in streamSimple function, including its context-pipeline wrapper. */\nexport function isDefaultStreamFunction(streamFunction: StreamFn): boolean {\n\treturn streamFunction === streamSimple || defaultStreamFunctions.has(streamFunction);\n}\n\nexport interface ContextPipelineOptions {\n\t/** Resolves a tool's contract; shared by both eviction and schema projection. */\n\tcontractLookup: ContractLookup;\n\t/** Names whose real schemas have been loaded by the model in this session. */\n\tloadedSchemaNames?: ReadonlySet<string>;\n\t/**\n\t * Eviction budget as a thunk, not a plain number: `AgentSession`'s budget\n\t * depends on `this.model` and `this.settingsManager`, both of which can change\n\t * over the session's lifetime (model switch, settings edit), and is never\n\t * cached. Evaluating it fresh on every request is what keeps eviction correct\n\t * after either changes mid-session.\n\t */\n\tevictionBudget: () => number;\n}\n\n/**\n * Installs the settled context pipeline (deferred-schema resolution → eviction)\n * onto `agent`, chaining onto whatever `transformContext` / `streamFunction` the\n * caller already installed rather than replacing it — the same pattern\n * `AgentSession._installAgentNextTurnRefresh` uses for `prepareNextTurnWithContext`.\n */\nexport function installContextPipeline(agent: Agent, options: ContextPipelineOptions): void {\n\tconst { contractLookup, evictionBudget: getBudget, loadedSchemaNames = new Set<string>() } = options;\n\n\tconst previousTransformContext = agent.transformContext;\n\tagent.transformContext = async (messages, signal) => {\n\t\tconst afterPrevious = previousTransformContext ? await previousTransformContext(messages, signal) : messages;\n\t\treturn evictToolResults(afterPrevious, contractLookup, getBudget());\n\t};\n\n\tconst previousStreamFunction = agent.streamFunction;\n\tconst wrappedStreamFunction = (\n\t\tmodel: Parameters<typeof previousStreamFunction>[0],\n\t\tcontext: Parameters<typeof previousStreamFunction>[1],\n\t\tstreamOptions: Parameters<typeof previousStreamFunction>[2],\n\t) => {\n\t\tif (!context.tools || context.tools.length === 0) {\n\t\t\treturn previousStreamFunction(model, context, streamOptions);\n\t\t}\n\t\treturn previousStreamFunction(\n\t\t\tmodel,\n\t\t\t{ ...context, tools: projectToolSchemas(context.tools, contractLookup, loadedSchemaNames) },\n\t\t\tstreamOptions,\n\t\t);\n\t};\n\tif (isDefaultStreamFunction(previousStreamFunction)) defaultStreamFunctions.add(wrappedStreamFunction);\n\tagent.streamFunction = wrappedStreamFunction;\n}\n"]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pure helpers for bounded, pre-completion credential failover around
3
+ * ModelRuntime.streamSimple(). Classification and stream buffering/replay only;
4
+ * pool selection state lives in CredentialPool, request orchestration in ModelRuntime.
5
+ */
6
+ import type { AssistantMessage, AssistantMessageEvent } from "@earendil-works/pi-ai";
7
+ import type { CredentialFailureKind } from "./credential-pool.ts";
8
+ /**
9
+ * Classifies a failed AssistantMessage into a rotation-eligible CredentialFailureKind,
10
+ * or undefined when the failure should not trigger credential rotation (success,
11
+ * abort, or a non-retryable provider error such as quota exhaustion).
12
+ */
13
+ export declare function classifyCredentialFailure(message: AssistantMessage): CredentialFailureKind | undefined;
14
+ /** An AsyncIterable<AssistantMessageEvent> paired with its eventual AssistantMessage, matching the shape `lazyStream` forwards. */
15
+ export interface ResultStream extends AsyncIterable<AssistantMessageEvent> {
16
+ result(): Promise<AssistantMessage>;
17
+ }
18
+ export interface DrainedAttempt {
19
+ events: readonly AssistantMessageEvent[];
20
+ message: AssistantMessage;
21
+ }
22
+ /**
23
+ * Fully consumes one attempt's stream before any event reaches the caller. This is
24
+ * what makes a failed attempt invisible to the caller: nothing is forwarded until the
25
+ * attempt's outcome (and therefore its retry eligibility) is known.
26
+ */
27
+ export declare function drainAttempt(stream: ResultStream): Promise<DrainedAttempt>;
28
+ /** Replays a fully-drained attempt's events as a fresh stream terminating in its known message. */
29
+ export declare function replayAttempt({ events, message }: DrainedAttempt): ResultStream;
30
+ //# sourceMappingURL=credential-failover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-failover.d.ts","sourceRoot":"","sources":["../../src/core/credential-failover.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAErF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAMlE;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,qBAAqB,GAAG,SAAS,CAOtG;AAED,mIAAmI;AACnI,MAAM,WAAW,YAAa,SAAQ,aAAa,CAAC,qBAAqB,CAAC;IACzE,MAAM,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACzC,OAAO,EAAE,gBAAgB,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAIhF;AAED,mGAAmG;AACnG,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,cAAc,GAAG,YAAY,CAO/E","sourcesContent":["/**\n * Pure helpers for bounded, pre-completion credential failover around\n * ModelRuntime.streamSimple(). Classification and stream buffering/replay only;\n * pool selection state lives in CredentialPool, request orchestration in ModelRuntime.\n */\n\nimport type { AssistantMessage, AssistantMessageEvent } from \"@earendil-works/pi-ai\";\nimport { isRetryableAssistantError } from \"@earendil-works/pi-ai\";\nimport type { CredentialFailureKind } from \"./credential-pool.ts\";\n\nconst RATE_LIMIT_PATTERN = /\\b429\\b|rate.?limit|too many requests/i;\nconst BLOCKED_CREDENTIAL_PATTERN =\n\t/\\b401\\b|\\b403\\b|unauthorized|forbidden|invalid.?api.?key|permission denied|expired/i;\n\n/**\n * Classifies a failed AssistantMessage into a rotation-eligible CredentialFailureKind,\n * or undefined when the failure should not trigger credential rotation (success,\n * abort, or a non-retryable provider error such as quota exhaustion).\n */\nexport function classifyCredentialFailure(message: AssistantMessage): CredentialFailureKind | undefined {\n\tif (message.stopReason !== \"error\" || !message.errorMessage) return undefined;\n\tconst text = message.errorMessage;\n\tif (RATE_LIMIT_PATTERN.test(text)) return \"rate_limited\";\n\tif (BLOCKED_CREDENTIAL_PATTERN.test(text)) return \"blocked\";\n\tif (isRetryableAssistantError(message)) return \"temporary\";\n\treturn undefined;\n}\n\n/** An AsyncIterable<AssistantMessageEvent> paired with its eventual AssistantMessage, matching the shape `lazyStream` forwards. */\nexport interface ResultStream extends AsyncIterable<AssistantMessageEvent> {\n\tresult(): Promise<AssistantMessage>;\n}\n\nexport interface DrainedAttempt {\n\tevents: readonly AssistantMessageEvent[];\n\tmessage: AssistantMessage;\n}\n\n/**\n * Fully consumes one attempt's stream before any event reaches the caller. This is\n * what makes a failed attempt invisible to the caller: nothing is forwarded until the\n * attempt's outcome (and therefore its retry eligibility) is known.\n */\nexport async function drainAttempt(stream: ResultStream): Promise<DrainedAttempt> {\n\tconst events: AssistantMessageEvent[] = [];\n\tfor await (const event of stream) events.push(event);\n\treturn { events, message: await stream.result() };\n}\n\n/** Replays a fully-drained attempt's events as a fresh stream terminating in its known message. */\nexport function replayAttempt({ events, message }: DrainedAttempt): ResultStream {\n\treturn {\n\t\tasync *[Symbol.asyncIterator]() {\n\t\t\tyield* events;\n\t\t},\n\t\tresult: () => Promise.resolve(message),\n\t};\n}\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Pure helpers for bounded, pre-completion credential failover around
3
+ * ModelRuntime.streamSimple(). Classification and stream buffering/replay only;
4
+ * pool selection state lives in CredentialPool, request orchestration in ModelRuntime.
5
+ */
6
+ import { isRetryableAssistantError } from "@earendil-works/pi-ai";
7
+ const RATE_LIMIT_PATTERN = /\b429\b|rate.?limit|too many requests/i;
8
+ const BLOCKED_CREDENTIAL_PATTERN = /\b401\b|\b403\b|unauthorized|forbidden|invalid.?api.?key|permission denied|expired/i;
9
+ /**
10
+ * Classifies a failed AssistantMessage into a rotation-eligible CredentialFailureKind,
11
+ * or undefined when the failure should not trigger credential rotation (success,
12
+ * abort, or a non-retryable provider error such as quota exhaustion).
13
+ */
14
+ export function classifyCredentialFailure(message) {
15
+ if (message.stopReason !== "error" || !message.errorMessage)
16
+ return undefined;
17
+ const text = message.errorMessage;
18
+ if (RATE_LIMIT_PATTERN.test(text))
19
+ return "rate_limited";
20
+ if (BLOCKED_CREDENTIAL_PATTERN.test(text))
21
+ return "blocked";
22
+ if (isRetryableAssistantError(message))
23
+ return "temporary";
24
+ return undefined;
25
+ }
26
+ /**
27
+ * Fully consumes one attempt's stream before any event reaches the caller. This is
28
+ * what makes a failed attempt invisible to the caller: nothing is forwarded until the
29
+ * attempt's outcome (and therefore its retry eligibility) is known.
30
+ */
31
+ export async function drainAttempt(stream) {
32
+ const events = [];
33
+ for await (const event of stream)
34
+ events.push(event);
35
+ return { events, message: await stream.result() };
36
+ }
37
+ /** Replays a fully-drained attempt's events as a fresh stream terminating in its known message. */
38
+ export function replayAttempt({ events, message }) {
39
+ return {
40
+ async *[Symbol.asyncIterator]() {
41
+ yield* events;
42
+ },
43
+ result: () => Promise.resolve(message),
44
+ };
45
+ }
46
+ //# sourceMappingURL=credential-failover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-failover.js","sourceRoot":"","sources":["../../src/core/credential-failover.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAGlE,MAAM,kBAAkB,GAAG,wCAAwC,CAAC;AACpE,MAAM,0BAA0B,GAC/B,qFAAqF,CAAC;AAEvF;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAyB,EAAqC;IACvG,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY;QAAE,OAAO,SAAS,CAAC;IAC9E,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC;IAClC,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,cAAc,CAAC;IACzD,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,yBAAyB,CAAC,OAAO,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3D,OAAO,SAAS,CAAC;AAAA,CACjB;AAYD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAoB,EAA2B;IACjF,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM;QAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;AAAA,CAClD;AAED,mGAAmG;AACnG,MAAM,UAAU,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAkB,EAAgB;IAChF,OAAO;QACN,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG;YAC/B,KAAK,CAAC,CAAC,MAAM,CAAC;QAAA,CACd;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;KACtC,CAAC;AAAA,CACF","sourcesContent":["/**\n * Pure helpers for bounded, pre-completion credential failover around\n * ModelRuntime.streamSimple(). Classification and stream buffering/replay only;\n * pool selection state lives in CredentialPool, request orchestration in ModelRuntime.\n */\n\nimport type { AssistantMessage, AssistantMessageEvent } from \"@earendil-works/pi-ai\";\nimport { isRetryableAssistantError } from \"@earendil-works/pi-ai\";\nimport type { CredentialFailureKind } from \"./credential-pool.ts\";\n\nconst RATE_LIMIT_PATTERN = /\\b429\\b|rate.?limit|too many requests/i;\nconst BLOCKED_CREDENTIAL_PATTERN =\n\t/\\b401\\b|\\b403\\b|unauthorized|forbidden|invalid.?api.?key|permission denied|expired/i;\n\n/**\n * Classifies a failed AssistantMessage into a rotation-eligible CredentialFailureKind,\n * or undefined when the failure should not trigger credential rotation (success,\n * abort, or a non-retryable provider error such as quota exhaustion).\n */\nexport function classifyCredentialFailure(message: AssistantMessage): CredentialFailureKind | undefined {\n\tif (message.stopReason !== \"error\" || !message.errorMessage) return undefined;\n\tconst text = message.errorMessage;\n\tif (RATE_LIMIT_PATTERN.test(text)) return \"rate_limited\";\n\tif (BLOCKED_CREDENTIAL_PATTERN.test(text)) return \"blocked\";\n\tif (isRetryableAssistantError(message)) return \"temporary\";\n\treturn undefined;\n}\n\n/** An AsyncIterable<AssistantMessageEvent> paired with its eventual AssistantMessage, matching the shape `lazyStream` forwards. */\nexport interface ResultStream extends AsyncIterable<AssistantMessageEvent> {\n\tresult(): Promise<AssistantMessage>;\n}\n\nexport interface DrainedAttempt {\n\tevents: readonly AssistantMessageEvent[];\n\tmessage: AssistantMessage;\n}\n\n/**\n * Fully consumes one attempt's stream before any event reaches the caller. This is\n * what makes a failed attempt invisible to the caller: nothing is forwarded until the\n * attempt's outcome (and therefore its retry eligibility) is known.\n */\nexport async function drainAttempt(stream: ResultStream): Promise<DrainedAttempt> {\n\tconst events: AssistantMessageEvent[] = [];\n\tfor await (const event of stream) events.push(event);\n\treturn { events, message: await stream.result() };\n}\n\n/** Replays a fully-drained attempt's events as a fresh stream terminating in its known message. */\nexport function replayAttempt({ events, message }: DrainedAttempt): ResultStream {\n\treturn {\n\t\tasync *[Symbol.asyncIterator]() {\n\t\t\tyield* events;\n\t\t},\n\t\tresult: () => Promise.resolve(message),\n\t};\n}\n"]}
@@ -0,0 +1,60 @@
1
+ /** Non-secret credential selection state for a single process. */
2
+ export type CredentialIdentity = string & {
3
+ readonly __brand: "CredentialIdentity";
4
+ };
5
+ export declare function createCredentialIdentity(value: string): CredentialIdentity;
6
+ export interface CredentialPoolEntry {
7
+ identity: CredentialIdentity;
8
+ providerId: string;
9
+ }
10
+ export type CredentialFailureKind = "rate_limited" | "blocked" | "temporary";
11
+ export interface CredentialPoolOptions {
12
+ entries: readonly CredentialPoolEntry[];
13
+ now?: () => number;
14
+ cooldownMs?: number;
15
+ }
16
+ export interface CredentialPoolSelection {
17
+ identity: CredentialIdentity;
18
+ providerId: string;
19
+ }
20
+ export interface CredentialPoolSnapshotEntry {
21
+ identity: CredentialIdentity;
22
+ providerId: string;
23
+ blockedUntil: number | undefined;
24
+ }
25
+ export interface CredentialRefreshLease {
26
+ identity: CredentialIdentity;
27
+ owner: string;
28
+ expiresAt: number;
29
+ }
30
+ export declare class CredentialPool {
31
+ private readonly entries;
32
+ private readonly now;
33
+ private readonly cooldownMs;
34
+ private readonly blockedUntilByIdentity;
35
+ private readonly refreshLeaseByIdentity;
36
+ private nextIndex;
37
+ constructor({ entries, now, cooldownMs }: CredentialPoolOptions);
38
+ select({ providerId, attempted }: {
39
+ providerId: string;
40
+ attempted: ReadonlySet<CredentialIdentity>;
41
+ }): CredentialPoolSelection | undefined;
42
+ recordFailure({ identity, kind }: {
43
+ identity: CredentialIdentity;
44
+ kind: CredentialFailureKind;
45
+ }): void;
46
+ snapshot(): CredentialPoolSnapshotEntry[];
47
+ private activeLease;
48
+ /** Grant a refresh lease for an identity, unless another owner already holds an unexpired one. */
49
+ acquireRefreshLease({ identity, owner, durationMs }: {
50
+ identity: CredentialIdentity;
51
+ owner: string;
52
+ durationMs: number;
53
+ }): CredentialRefreshLease | undefined;
54
+ /** Release a held refresh lease. Returns false if the caller is not the current owner. */
55
+ releaseRefreshLease({ identity, owner }: {
56
+ identity: CredentialIdentity;
57
+ owner: string;
58
+ }): boolean;
59
+ }
60
+ //# sourceMappingURL=credential-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-pool.d.ts","sourceRoot":"","sources":["../../src/core/credential-pool.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAElE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAErF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAG1E;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,qBAAqB,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,CAAC;AAE7E,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACvC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyC;IAChF,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyD;IAChG,OAAO,CAAC,SAAS,CAAK;IAEtB,YAAY,EAAE,OAAO,EAAE,GAAc,EAAE,UAAmB,EAAE,EAAE,qBAAqB,EAWlF;IAED,MAAM,CAAC,EACN,UAAU,EACV,SAAS,EACT,EAAE;QACF,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,WAAW,CAAC,kBAAkB,CAAC,CAAC;KAC3C,GAAG,uBAAuB,GAAG,SAAS,CAkBtC;IAED,aAAa,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAAG,IAAI,CAIrG;IAED,QAAQ,IAAI,2BAA2B,EAAE,CAMxC;IAED,OAAO,CAAC,WAAW;IAUnB,kGAAkG;IAClG,mBAAmB,CAAC,EACnB,QAAQ,EACR,KAAK,EACL,UAAU,EACV,EAAE;QACF,QAAQ,EAAE,kBAAkB,CAAC;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACnB,GAAG,sBAAsB,GAAG,SAAS,CAMrC;IAED,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAKjG;CACD","sourcesContent":["/** Non-secret credential selection state for a single process. */\n\nexport type CredentialIdentity = string & { readonly __brand: \"CredentialIdentity\" };\n\nexport function createCredentialIdentity(value: string): CredentialIdentity {\n\tif (!value.trim()) throw new Error(\"Credential identity must not be empty\");\n\treturn value as CredentialIdentity;\n}\n\nexport interface CredentialPoolEntry {\n\tidentity: CredentialIdentity;\n\tproviderId: string;\n}\n\nexport type CredentialFailureKind = \"rate_limited\" | \"blocked\" | \"temporary\";\n\nexport interface CredentialPoolOptions {\n\tentries: readonly CredentialPoolEntry[];\n\tnow?: () => number;\n\tcooldownMs?: number;\n}\n\nexport interface CredentialPoolSelection {\n\tidentity: CredentialIdentity;\n\tproviderId: string;\n}\n\nexport interface CredentialPoolSnapshotEntry {\n\tidentity: CredentialIdentity;\n\tproviderId: string;\n\tblockedUntil: number | undefined;\n}\n\nexport interface CredentialRefreshLease {\n\tidentity: CredentialIdentity;\n\towner: string;\n\texpiresAt: number;\n}\n\nexport class CredentialPool {\n\tprivate readonly entries: readonly CredentialPoolEntry[];\n\tprivate readonly now: () => number;\n\tprivate readonly cooldownMs: number;\n\tprivate readonly blockedUntilByIdentity = new Map<CredentialIdentity, number>();\n\tprivate readonly refreshLeaseByIdentity = new Map<CredentialIdentity, CredentialRefreshLease>();\n\tprivate nextIndex = 0;\n\n\tconstructor({ entries, now = Date.now, cooldownMs = 60_000 }: CredentialPoolOptions) {\n\t\tconst seen = new Set<CredentialIdentity>();\n\t\tfor (const entry of entries) {\n\t\t\tif (seen.has(entry.identity)) {\n\t\t\t\tthrow new Error(`Duplicate credential identity \"${entry.identity}\"`);\n\t\t\t}\n\t\t\tseen.add(entry.identity);\n\t\t}\n\t\tthis.entries = entries;\n\t\tthis.now = now;\n\t\tthis.cooldownMs = cooldownMs;\n\t}\n\n\tselect({\n\t\tproviderId,\n\t\tattempted,\n\t}: {\n\t\tproviderId: string;\n\t\tattempted: ReadonlySet<CredentialIdentity>;\n\t}): CredentialPoolSelection | undefined {\n\t\tif (this.entries.length === 0) return undefined;\n\t\tconst now = this.now();\n\t\tfor (let offset = 0; offset < this.entries.length; offset++) {\n\t\t\tconst index = (this.nextIndex + offset) % this.entries.length;\n\t\t\tconst entry = this.entries[index];\n\t\t\tconst blockedUntil = this.blockedUntilByIdentity.get(entry.identity);\n\t\t\tif (\n\t\t\t\tentry.providerId !== providerId ||\n\t\t\t\tattempted.has(entry.identity) ||\n\t\t\t\t(blockedUntil !== undefined && blockedUntil > now)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.nextIndex = (index + 1) % this.entries.length;\n\t\t\treturn entry;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\trecordFailure({ identity, kind }: { identity: CredentialIdentity; kind: CredentialFailureKind }): void {\n\t\tif (kind === \"rate_limited\" || kind === \"blocked\") {\n\t\t\tthis.blockedUntilByIdentity.set(identity, this.now() + this.cooldownMs);\n\t\t}\n\t}\n\n\tsnapshot(): CredentialPoolSnapshotEntry[] {\n\t\treturn this.entries.map((entry) => ({\n\t\t\tidentity: entry.identity,\n\t\t\tproviderId: entry.providerId,\n\t\t\tblockedUntil: this.blockedUntilByIdentity.get(entry.identity),\n\t\t}));\n\t}\n\n\tprivate activeLease(identity: CredentialIdentity): CredentialRefreshLease | undefined {\n\t\tconst lease = this.refreshLeaseByIdentity.get(identity);\n\t\tif (!lease) return undefined;\n\t\tif (lease.expiresAt <= this.now()) {\n\t\t\tthis.refreshLeaseByIdentity.delete(identity);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn lease;\n\t}\n\n\t/** Grant a refresh lease for an identity, unless another owner already holds an unexpired one. */\n\tacquireRefreshLease({\n\t\tidentity,\n\t\towner,\n\t\tdurationMs,\n\t}: {\n\t\tidentity: CredentialIdentity;\n\t\towner: string;\n\t\tdurationMs: number;\n\t}): CredentialRefreshLease | undefined {\n\t\tconst existing = this.activeLease(identity);\n\t\tif (existing && existing.owner !== owner) return undefined;\n\t\tconst lease: CredentialRefreshLease = { identity, owner, expiresAt: this.now() + durationMs };\n\t\tthis.refreshLeaseByIdentity.set(identity, lease);\n\t\treturn lease;\n\t}\n\n\t/** Release a held refresh lease. Returns false if the caller is not the current owner. */\n\treleaseRefreshLease({ identity, owner }: { identity: CredentialIdentity; owner: string }): boolean {\n\t\tconst existing = this.activeLease(identity);\n\t\tif (!existing || existing.owner !== owner) return false;\n\t\tthis.refreshLeaseByIdentity.delete(identity);\n\t\treturn true;\n\t}\n}\n"]}