@nathapp/nax-agent 0.1.0

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 (398) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
  5. package/dist/coding-tools/coding-tool-bash.js +44 -0
  6. package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
  7. package/dist/coding-tools/coding-tool-extras.js +56 -0
  8. package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
  9. package/dist/coding-tools/coding-tool-sandbox.js +215 -0
  10. package/dist/coding-tools/coding-tool-support.d.ts +132 -0
  11. package/dist/coding-tools/coding-tool-support.js +159 -0
  12. package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
  13. package/dist/coding-tools/universal-coding-tools.js +5 -0
  14. package/dist/command-interceptor/index.d.ts +55 -0
  15. package/dist/command-interceptor/index.js +60 -0
  16. package/dist/command-interceptor/shell.d.ts +36 -0
  17. package/dist/command-interceptor/shell.js +137 -0
  18. package/dist/command-safety/build.d.ts +19 -0
  19. package/dist/command-safety/build.js +47 -0
  20. package/dist/command-safety/guard.d.ts +32 -0
  21. package/dist/command-safety/guard.js +179 -0
  22. package/dist/command-safety/identifiers.d.ts +24 -0
  23. package/dist/command-safety/identifiers.js +20 -0
  24. package/dist/command-safety/index.d.ts +17 -0
  25. package/dist/command-safety/index.js +11 -0
  26. package/dist/command-safety/questions.d.ts +19 -0
  27. package/dist/command-safety/questions.js +60 -0
  28. package/dist/command-safety/row.d.ts +2 -0
  29. package/dist/command-safety/row.js +14 -0
  30. package/dist/command-safety/rule-scorer.d.ts +9 -0
  31. package/dist/command-safety/rule-scorer.js +391 -0
  32. package/dist/command-safety/shadow.d.ts +33 -0
  33. package/dist/command-safety/shadow.js +188 -0
  34. package/dist/command-safety/systemone-client.d.ts +16 -0
  35. package/dist/command-safety/systemone-client.js +92 -0
  36. package/dist/command-safety/tap.d.ts +37 -0
  37. package/dist/command-safety/tap.js +77 -0
  38. package/dist/command-safety/temp-only.d.ts +6 -0
  39. package/dist/command-safety/temp-only.js +215 -0
  40. package/dist/command-safety/tmp-write.d.ts +2 -0
  41. package/dist/command-safety/tmp-write.js +112 -0
  42. package/dist/command-safety/types.d.ts +185 -0
  43. package/dist/command-safety/types.js +21 -0
  44. package/dist/config/bash-approval.d.ts +33 -0
  45. package/dist/config/bash-approval.js +30 -0
  46. package/dist/config/catalog-overrides.d.ts +94 -0
  47. package/dist/config/catalog-overrides.js +7 -0
  48. package/dist/config/native-agent/index.d.ts +8 -0
  49. package/dist/config/native-agent/index.js +8 -0
  50. package/dist/config/schemas-sandbox.d.ts +31 -0
  51. package/dist/config/schemas-sandbox.js +45 -0
  52. package/dist/cost/core/index.d.ts +8 -0
  53. package/dist/cost/core/index.js +7 -0
  54. package/dist/cost/estimate.d.ts +34 -0
  55. package/dist/cost/estimate.js +65 -0
  56. package/dist/cost/model-spec.d.ts +28 -0
  57. package/dist/cost/model-spec.js +29 -0
  58. package/dist/cost/standard-types.d.ts +9 -0
  59. package/dist/cost/standard-types.js +1 -0
  60. package/dist/cost/usage-math.d.ts +18 -0
  61. package/dist/cost/usage-math.js +46 -0
  62. package/dist/index.d.ts +26 -0
  63. package/dist/index.js +21 -0
  64. package/dist/infra/agent-logger.d.ts +22 -0
  65. package/dist/infra/agent-logger.js +19 -0
  66. package/dist/infra/credentials-config.d.ts +20 -0
  67. package/dist/infra/credentials-config.js +15 -0
  68. package/dist/infra/errors.d.ts +9 -0
  69. package/dist/infra/errors.js +11 -0
  70. package/dist/infra/index.d.ts +7 -0
  71. package/dist/infra/index.js +7 -0
  72. package/dist/infra/nax-error.d.ts +10 -0
  73. package/dist/infra/nax-error.js +16 -0
  74. package/dist/infra/spin-breaker/hash.d.ts +2 -0
  75. package/dist/infra/spin-breaker/hash.js +5 -0
  76. package/dist/infra/spin-breaker/index.d.ts +120 -0
  77. package/dist/infra/spin-breaker/index.js +340 -0
  78. package/dist/internal/agent-output-env.d.ts +44 -0
  79. package/dist/internal/agent-output-env.js +57 -0
  80. package/dist/internal/argv-exec.d.ts +58 -0
  81. package/dist/internal/argv-exec.js +224 -0
  82. package/dist/internal/bounded-io.d.ts +24 -0
  83. package/dist/internal/bounded-io.js +51 -0
  84. package/dist/internal/command-spec/index.d.ts +29 -0
  85. package/dist/internal/command-spec/index.js +36 -0
  86. package/dist/internal/describe-value-type.d.ts +8 -0
  87. package/dist/internal/describe-value-type.js +14 -0
  88. package/dist/internal/exec-framing.d.ts +18 -0
  89. package/dist/internal/exec-framing.js +22 -0
  90. package/dist/internal/file-lock.d.ts +91 -0
  91. package/dist/internal/file-lock.js +212 -0
  92. package/dist/internal/file-size.d.ts +2 -0
  93. package/dist/internal/file-size.js +12 -0
  94. package/dist/internal/git-add.d.ts +43 -0
  95. package/dist/internal/git-add.js +59 -0
  96. package/dist/internal/git-env.d.ts +59 -0
  97. package/dist/internal/git-env.js +93 -0
  98. package/dist/internal/git-exec.d.ts +58 -0
  99. package/dist/internal/git-exec.js +115 -0
  100. package/dist/internal/path-file-lock.d.ts +23 -0
  101. package/dist/internal/path-file-lock.js +25 -0
  102. package/dist/internal/process-alive.d.ts +42 -0
  103. package/dist/internal/process-alive.js +56 -0
  104. package/dist/internal/process-kill.d.ts +27 -0
  105. package/dist/internal/process-kill.js +54 -0
  106. package/dist/internal/realpath.d.ts +29 -0
  107. package/dist/internal/realpath.js +52 -0
  108. package/dist/internal/redact.d.ts +40 -0
  109. package/dist/internal/redact.js +161 -0
  110. package/dist/internal/shell-quote.d.ts +2 -0
  111. package/dist/internal/shell-quote.js +4 -0
  112. package/dist/internal/sort.d.ts +20 -0
  113. package/dist/internal/sort.js +20 -0
  114. package/dist/internal/strip-control-chars.d.ts +19 -0
  115. package/dist/internal/strip-control-chars.js +38 -0
  116. package/dist/internal/thenable.d.ts +10 -0
  117. package/dist/internal/thenable.js +15 -0
  118. package/dist/internal.d.ts +91 -0
  119. package/dist/internal.js +91 -0
  120. package/dist/native/adapter-deps.d.ts +46 -0
  121. package/dist/native/adapter-deps.js +43 -0
  122. package/dist/native/auth-types.d.ts +70 -0
  123. package/dist/native/auth-types.js +13 -0
  124. package/dist/native/auth.d.ts +133 -0
  125. package/dist/native/auth.js +362 -0
  126. package/dist/native/client.d.ts +50 -0
  127. package/dist/native/client.js +174 -0
  128. package/dist/native/complete.d.ts +33 -0
  129. package/dist/native/complete.js +88 -0
  130. package/dist/native/credentials/chained-store.d.ts +30 -0
  131. package/dist/native/credentials/chained-store.js +68 -0
  132. package/dist/native/credentials/change-guard.d.ts +51 -0
  133. package/dist/native/credentials/change-guard.js +136 -0
  134. package/dist/native/credentials/exec-source.d.ts +50 -0
  135. package/dist/native/credentials/exec-source.js +222 -0
  136. package/dist/native/credentials/fingerprint.d.ts +26 -0
  137. package/dist/native/credentials/fingerprint.js +174 -0
  138. package/dist/native/credentials/helper-process.d.ts +79 -0
  139. package/dist/native/credentials/helper-process.js +259 -0
  140. package/dist/native/credentials/helper-protocol.d.ts +36 -0
  141. package/dist/native/credentials/helper-protocol.js +79 -0
  142. package/dist/native/credentials/index.d.ts +60 -0
  143. package/dist/native/credentials/index.js +184 -0
  144. package/dist/native/errors.d.ts +34 -0
  145. package/dist/native/errors.js +109 -0
  146. package/dist/native/index.d.ts +25 -0
  147. package/dist/native/index.js +23 -0
  148. package/dist/native/model-resolver.d.ts +57 -0
  149. package/dist/native/model-resolver.js +73 -0
  150. package/dist/native/models.d.ts +116 -0
  151. package/dist/native/models.js +183 -0
  152. package/dist/native/session/ask-human.d.ts +15 -0
  153. package/dist/native/session/ask-human.js +22 -0
  154. package/dist/native/session/compaction.d.ts +94 -0
  155. package/dist/native/session/compaction.js +170 -0
  156. package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
  157. package/dist/native/session/handle-invalid-tool-call.js +159 -0
  158. package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
  159. package/dist/native/session/loop-events/cache-boundary.js +29 -0
  160. package/dist/native/session/loop-events/external-handler.d.ts +38 -0
  161. package/dist/native/session/loop-events/external-handler.js +193 -0
  162. package/dist/native/session/loop-events/index.d.ts +16 -0
  163. package/dist/native/session/loop-events/index.js +15 -0
  164. package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
  165. package/dist/native/session/loop-events/payload-guard.js +31 -0
  166. package/dist/native/session/loop-events/registry.d.ts +84 -0
  167. package/dist/native/session/loop-events/registry.js +305 -0
  168. package/dist/native/session/loop-events/types.d.ts +251 -0
  169. package/dist/native/session/loop-events/types.js +14 -0
  170. package/dist/native/session/loop-handlers.d.ts +73 -0
  171. package/dist/native/session/loop-handlers.js +137 -0
  172. package/dist/native/session/nudge.d.ts +29 -0
  173. package/dist/native/session/nudge.js +35 -0
  174. package/dist/native/session/rate-provenance.d.ts +17 -0
  175. package/dist/native/session/rate-provenance.js +39 -0
  176. package/dist/native/session/session.d.ts +139 -0
  177. package/dist/native/session/session.js +208 -0
  178. package/dist/native/session/tool-input-exemplar.d.ts +2 -0
  179. package/dist/native/session/tool-input-exemplar.js +66 -0
  180. package/dist/native/session/tool-input-validation.d.ts +15 -0
  181. package/dist/native/session/tool-input-validation.js +146 -0
  182. package/dist/native/session/tool-mapping.d.ts +18 -0
  183. package/dist/native/session/tool-mapping.js +27 -0
  184. package/dist/native/session/tool-result.d.ts +37 -0
  185. package/dist/native/session/tool-result.js +25 -0
  186. package/dist/native/session/transcript-store.d.ts +77 -0
  187. package/dist/native/session/transcript-store.js +200 -0
  188. package/dist/native/session/truncation-handler.d.ts +47 -0
  189. package/dist/native/session/truncation-handler.js +83 -0
  190. package/dist/native/session/turn-accumulator.d.ts +45 -0
  191. package/dist/native/session/turn-accumulator.js +67 -0
  192. package/dist/native/session/turn-ask-human.d.ts +23 -0
  193. package/dist/native/session/turn-ask-human.js +41 -0
  194. package/dist/native/session/turn-compaction-step.d.ts +74 -0
  195. package/dist/native/session/turn-compaction-step.js +128 -0
  196. package/dist/native/session/turn-complete-step.d.ts +77 -0
  197. package/dist/native/session/turn-complete-step.js +159 -0
  198. package/dist/native/session/turn-end-event.d.ts +3 -0
  199. package/dist/native/session/turn-end-event.js +17 -0
  200. package/dist/native/session/turn-events.d.ts +63 -0
  201. package/dist/native/session/turn-events.js +48 -0
  202. package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
  203. package/dist/native/session/turn-loop-round-trip.js +318 -0
  204. package/dist/native/session/turn-loop.d.ts +23 -0
  205. package/dist/native/session/turn-loop.js +250 -0
  206. package/dist/native/session/turn-result.d.ts +39 -0
  207. package/dist/native/session/turn-result.js +64 -0
  208. package/dist/native/session/turn-retry.d.ts +120 -0
  209. package/dist/native/session/turn-retry.js +182 -0
  210. package/dist/native/session/turn-tool-batch.d.ts +61 -0
  211. package/dist/native/session/turn-tool-batch.js +259 -0
  212. package/dist/native/session/turn-types.d.ts +164 -0
  213. package/dist/native/session/turn-types.js +36 -0
  214. package/dist/native/session-adapter.d.ts +56 -0
  215. package/dist/native/session-adapter.js +369 -0
  216. package/dist/native/session-affinity.d.ts +36 -0
  217. package/dist/native/session-affinity.js +43 -0
  218. package/dist/native/tier-providers.d.ts +18 -0
  219. package/dist/native/tier-providers.js +20 -0
  220. package/dist/permissions/approval-audit.d.ts +10 -0
  221. package/dist/permissions/approval-audit.js +22 -0
  222. package/dist/permissions/approvals-link.d.ts +20 -0
  223. package/dist/permissions/approvals-link.js +62 -0
  224. package/dist/permissions/approvals-store.d.ts +168 -0
  225. package/dist/permissions/approvals-store.js +259 -0
  226. package/dist/permissions/approvals-taint.d.ts +33 -0
  227. package/dist/permissions/approvals-taint.js +138 -0
  228. package/dist/permissions/ask-chain.d.ts +69 -0
  229. package/dist/permissions/ask-chain.js +32 -0
  230. package/dist/permissions/ask.d.ts +23 -0
  231. package/dist/permissions/ask.js +25 -0
  232. package/dist/permissions/bash-lex.d.ts +59 -0
  233. package/dist/permissions/bash-lex.js +279 -0
  234. package/dist/permissions/grammar.d.ts +12 -0
  235. package/dist/permissions/grammar.js +24 -0
  236. package/dist/permissions/index.d.ts +11 -0
  237. package/dist/permissions/index.js +9 -0
  238. package/dist/permissions/secret-spans.d.ts +19 -0
  239. package/dist/permissions/secret-spans.js +88 -0
  240. package/dist/permissions/types.d.ts +85 -0
  241. package/dist/permissions/types.js +1 -0
  242. package/dist/runtime/index.d.ts +3 -0
  243. package/dist/runtime/index.js +2 -0
  244. package/dist/runtime/node-glob.d.ts +3 -0
  245. package/dist/runtime/node-glob.js +83 -0
  246. package/dist/runtime/node-runtime.d.ts +3 -0
  247. package/dist/runtime/node-runtime.js +126 -0
  248. package/dist/runtime/slot.d.ts +11 -0
  249. package/dist/runtime/slot.js +17 -0
  250. package/dist/runtime/types.d.ts +51 -0
  251. package/dist/runtime/types.js +1 -0
  252. package/dist/runtime/which.d.ts +8 -0
  253. package/dist/runtime/which.js +32 -0
  254. package/dist/sandbox/argv-quote.d.ts +11 -0
  255. package/dist/sandbox/argv-quote.js +13 -0
  256. package/dist/sandbox/defaults.d.ts +27 -0
  257. package/dist/sandbox/defaults.js +47 -0
  258. package/dist/sandbox/git-guards.d.ts +64 -0
  259. package/dist/sandbox/git-guards.js +161 -0
  260. package/dist/sandbox/index.d.ts +11 -0
  261. package/dist/sandbox/index.js +10 -0
  262. package/dist/sandbox/launcher.d.ts +37 -0
  263. package/dist/sandbox/launcher.js +160 -0
  264. package/dist/sandbox/messages.d.ts +17 -0
  265. package/dist/sandbox/messages.js +46 -0
  266. package/dist/sandbox/policy-builder.d.ts +44 -0
  267. package/dist/sandbox/policy-builder.js +149 -0
  268. package/dist/sandbox/policy-inputs.d.ts +53 -0
  269. package/dist/sandbox/policy-inputs.js +76 -0
  270. package/dist/sandbox/probe.d.ts +8 -0
  271. package/dist/sandbox/probe.js +66 -0
  272. package/dist/sandbox/registry.d.ts +17 -0
  273. package/dist/sandbox/registry.js +57 -0
  274. package/dist/sandbox/session-tmp.d.ts +20 -0
  275. package/dist/sandbox/session-tmp.js +95 -0
  276. package/dist/sandbox/srt-backend.d.ts +21 -0
  277. package/dist/sandbox/srt-backend.js +99 -0
  278. package/dist/sandbox/types.d.ts +102 -0
  279. package/dist/sandbox/types.js +1 -0
  280. package/dist/session/adapter-failure.d.ts +60 -0
  281. package/dist/session/adapter-failure.js +11 -0
  282. package/dist/session/agent-stream-events.d.ts +103 -0
  283. package/dist/session/agent-stream-events.js +6 -0
  284. package/dist/session/interaction-handler.d.ts +53 -0
  285. package/dist/session/interaction-handler.js +2 -0
  286. package/dist/session/no-op-interaction-handler.d.ts +9 -0
  287. package/dist/session/no-op-interaction-handler.js +11 -0
  288. package/dist/session/protocol-types.d.ts +12 -0
  289. package/dist/session/protocol-types.js +1 -0
  290. package/dist/session/session-types.d.ts +456 -0
  291. package/dist/session/session-types.js +64 -0
  292. package/dist/session/tool-descriptor.d.ts +23 -0
  293. package/dist/session/tool-descriptor.js +5 -0
  294. package/dist/session/turn-deadline.d.ts +17 -0
  295. package/dist/session/turn-deadline.js +23 -0
  296. package/dist/tools/ask-request.d.ts +35 -0
  297. package/dist/tools/ask-request.js +57 -0
  298. package/dist/tools/bash-cwd.d.ts +82 -0
  299. package/dist/tools/bash-cwd.js +55 -0
  300. package/dist/tools/bash.d.ts +69 -0
  301. package/dist/tools/bash.js +283 -0
  302. package/dist/tools/delete.d.ts +57 -0
  303. package/dist/tools/delete.js +154 -0
  304. package/dist/tools/denial-redirect.d.ts +42 -0
  305. package/dist/tools/denial-redirect.js +266 -0
  306. package/dist/tools/deny-paths.d.ts +35 -0
  307. package/dist/tools/deny-paths.js +76 -0
  308. package/dist/tools/edit-region.d.ts +50 -0
  309. package/dist/tools/edit-region.js +90 -0
  310. package/dist/tools/edit.d.ts +25 -0
  311. package/dist/tools/edit.js +114 -0
  312. package/dist/tools/exec-allowlist-text.d.ts +18 -0
  313. package/dist/tools/exec-allowlist-text.js +24 -0
  314. package/dist/tools/exec-guard.d.ts +69 -0
  315. package/dist/tools/exec-guard.js +151 -0
  316. package/dist/tools/git-commit.d.ts +59 -0
  317. package/dist/tools/git-commit.js +220 -0
  318. package/dist/tools/git-flags/index.d.ts +10 -0
  319. package/dist/tools/git-flags/index.js +10 -0
  320. package/dist/tools/git.d.ts +88 -0
  321. package/dist/tools/git.js +397 -0
  322. package/dist/tools/glob.d.ts +29 -0
  323. package/dist/tools/glob.js +153 -0
  324. package/dist/tools/grep.d.ts +21 -0
  325. package/dist/tools/grep.js +162 -0
  326. package/dist/tools/index.d.ts +36 -0
  327. package/dist/tools/index.js +28 -0
  328. package/dist/tools/narrow-grants.d.ts +19 -0
  329. package/dist/tools/narrow-grants.js +54 -0
  330. package/dist/tools/nax-owned-writes.d.ts +135 -0
  331. package/dist/tools/nax-owned-writes.js +259 -0
  332. package/dist/tools/package-managers-table.d.ts +146 -0
  333. package/dist/tools/package-managers-table.js +325 -0
  334. package/dist/tools/package-managers-types.d.ts +36 -0
  335. package/dist/tools/package-managers-types.js +6 -0
  336. package/dist/tools/package-managers.d.ts +85 -0
  337. package/dist/tools/package-managers.js +391 -0
  338. package/dist/tools/path-list.d.ts +38 -0
  339. package/dist/tools/path-list.js +62 -0
  340. package/dist/tools/policy-bash-raw.d.ts +19 -0
  341. package/dist/tools/policy-bash-raw.js +210 -0
  342. package/dist/tools/policy-bash.d.ts +64 -0
  343. package/dist/tools/policy-bash.js +232 -0
  344. package/dist/tools/policy-command-branch.d.ts +32 -0
  345. package/dist/tools/policy-command-branch.js +63 -0
  346. package/dist/tools/policy-input.d.ts +2 -0
  347. package/dist/tools/policy-input.js +10 -0
  348. package/dist/tools/policy-match.d.ts +75 -0
  349. package/dist/tools/policy-match.js +117 -0
  350. package/dist/tools/policy-paths-branch.d.ts +97 -0
  351. package/dist/tools/policy-paths-branch.js +314 -0
  352. package/dist/tools/policy.d.ts +65 -0
  353. package/dist/tools/policy.js +232 -0
  354. package/dist/tools/protected-paths.d.ts +26 -0
  355. package/dist/tools/protected-paths.js +8 -0
  356. package/dist/tools/provider-adapt.d.ts +4 -0
  357. package/dist/tools/provider-adapt.js +32 -0
  358. package/dist/tools/provider-advertise.d.ts +39 -0
  359. package/dist/tools/provider-advertise.js +50 -0
  360. package/dist/tools/provider-grants.d.ts +36 -0
  361. package/dist/tools/provider-grants.js +72 -0
  362. package/dist/tools/provider-sanitize.d.ts +19 -0
  363. package/dist/tools/provider-sanitize.js +53 -0
  364. package/dist/tools/provider-types.d.ts +41 -0
  365. package/dist/tools/provider-types.js +26 -0
  366. package/dist/tools/read-continuation.d.ts +147 -0
  367. package/dist/tools/read-continuation.js +176 -0
  368. package/dist/tools/read-file.d.ts +32 -0
  369. package/dist/tools/read-file.js +85 -0
  370. package/dist/tools/read.d.ts +25 -0
  371. package/dist/tools/read.js +182 -0
  372. package/dist/tools/registry.d.ts +125 -0
  373. package/dist/tools/registry.js +67 -0
  374. package/dist/tools/request-capability.d.ts +13 -0
  375. package/dist/tools/request-capability.js +23 -0
  376. package/dist/tools/run-command-exec.d.ts +14 -0
  377. package/dist/tools/run-command-exec.js +161 -0
  378. package/dist/tools/run-command.d.ts +107 -0
  379. package/dist/tools/run-command.js +398 -0
  380. package/dist/tools/runtime-calltool.d.ts +146 -0
  381. package/dist/tools/runtime-calltool.js +223 -0
  382. package/dist/tools/runtime.d.ts +149 -0
  383. package/dist/tools/runtime.js +345 -0
  384. package/dist/tools/scratchpad.d.ts +20 -0
  385. package/dist/tools/scratchpad.js +197 -0
  386. package/dist/tools/spill.d.ts +81 -0
  387. package/dist/tools/spill.js +251 -0
  388. package/dist/tools/tool-audit.d.ts +171 -0
  389. package/dist/tools/tool-audit.js +183 -0
  390. package/dist/tools/truncate.d.ts +98 -0
  391. package/dist/tools/truncate.js +297 -0
  392. package/dist/tools/types.d.ts +145 -0
  393. package/dist/tools/types.js +24 -0
  394. package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
  395. package/dist/tools/verb-denial-argv-hint.js +40 -0
  396. package/dist/tools/write.d.ts +9 -0
  397. package/dist/tools/write.js +45 -0
  398. package/package.json +54 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Model reference, usage and cost for the native path.
3
+ *
4
+ * The provider travels inside the model string, not beside it: a multi-provider
5
+ * agent needs it there, and opencode's entries already encode it that way
6
+ * (ADR-027 section 1). Under acpx the same string stays opaque.
7
+ */
8
+ import type { Pricing, ProviderOverride, ThinkingLevel } from "@nathapp/nax-ai";
9
+ import type { ProviderCatalogOverride } from "#src/config/catalog-overrides";
10
+ import { parseModelSpec } from "#src/cost/model-spec";
11
+ /** The one agent name that routes to this transport. Defined once in the
12
+ * `@/config/native-agent` leaf (config validates against it); re-exported
13
+ * here, not in the barrel, so session-adapter.ts can import it without an
14
+ * index -> session-adapter -> index cycle. */
15
+ export { NATIVE_AGENT_NAME as NATIVE_AGENT } from "#src/config/native-agent/index";
16
+ export { parseModelSpec };
17
+ export interface NativeModelRef {
18
+ readonly provider: string;
19
+ readonly model: string;
20
+ /** Reasoning-effort suffix, when the config string carried one. Free-form —
21
+ * validate with `toThinkingLevel` before sending it on the wire. */
22
+ readonly effort?: string;
23
+ }
24
+ /**
25
+ * Strip the nax-level `[effort]` suffix FIRST, then split on the FIRST slash: a
26
+ * provider id never contains one, a model id often does
27
+ * (`huggingface/MiniMaxAI/MiniMax-M2.7`). Order matters — the suffix is
28
+ * trailing, so splitting on the slash before stripping it would leave the
29
+ * suffix glued onto the model id (`"claude-opus-5[high]"`) and
30
+ * `client.model()` would throw its own unknown-model error instead of this
31
+ * function's clearer one.
32
+ *
33
+ * A suffix with no slash (`"claude-opus-5[high]"`) still fails malformed-model
34
+ * validation: `parseModelSpec` only removes the suffix, it does not supply a
35
+ * missing provider.
36
+ */
37
+ export declare function parseNativeModel(raw: string): NativeModelRef;
38
+ /**
39
+ * Every level nax-ai's `ThinkingLevel` union admits, as an exhaustive mapped
40
+ * type rather than a hand-rolled string array. `Record<ThinkingLevel, true>`
41
+ * only type-checks when every union member has an entry, so if nax-ai adds a
42
+ * level upstream, this fails to COMPILE — a real gate, not a comment that can
43
+ * silently drift out of sync with the union it mirrors.
44
+ */
45
+ export declare const THINKING_LEVELS: Record<ThinkingLevel, true>;
46
+ /**
47
+ * Translate nax's config-side "effort" suffix into nax-ai's request-side
48
+ * "thinking" field. Kept in this one place, named for the seam it crosses: nax
49
+ * calls the concept "effort", nax-ai calls the identical value "thinking".
50
+ *
51
+ * The suffix is a free-form string (see model-spec.ts), so a value outside
52
+ * nax-ai's ThinkingLevel union is possible from a typo'd profile. This
53
+ * mirrors applyReasoningEffort's stance in
54
+ * src/agents/acp/reasoning-effort.ts: "Best-effort: a failure leaves the
55
+ * session at the adapter default rather than failing the whole run. The
56
+ * warning is what keeps that downgrade visible." nax does not clamp against
57
+ * per-model capabilities here — that is nax-ai's clampThinkingLevel's job,
58
+ * run inside client.complete against the resolved model's own
59
+ * thinkingLevels.
60
+ */
61
+ export declare function toThinkingLevel(effort: string | undefined): ThinkingLevel | undefined;
62
+ /**
63
+ * Translate nax's config-side catalog overrides (agent.native.catalogOverrides,
64
+ * nax#1982) into nax-ai's declaration-data `ProviderOverride` records.
65
+ *
66
+ * The override is a COMPLETE entry, not a patch: nax-ai's `normaliseCatalog`
67
+ * applies it last through `setModel`, which replaces any same-id entry and
68
+ * lazily creates the provider bucket — that is what makes an id absent from
69
+ * the bundled pi-ai snapshot resolvable (verified against nax-ai 0.1.10).
70
+ * `provider` is stamped from the outer record because nax-ai's `ResolvedModel`
71
+ * carries it per model.
72
+ */
73
+ export declare function toProviderOverrides(overrides: readonly ProviderCatalogOverride[]): ProviderOverride[];
74
+ /**
75
+ * `estimateCostUsd` (and the tier-selection helper it relies on) was
76
+ * relocated to `src/agents/cost/` (US-001) so the ACP and native paths share
77
+ * one implementation. Native callers now import it from `@/agents/cost`.
78
+ */
79
+ /**
80
+ * Turn nax-ai's catalog `Pricing` into the rate object `priceCall` prices from
81
+ * (nax#1843, nax#1847). Both call sites -- `nativeComplete()` (complete.ts)
82
+ * and `NativeSessionAdapter.sendTurn()` (session-adapter.ts) -- build the rate
83
+ * object this way, and both receive the override already converted by
84
+ * `toSessionModel`, so the fix cannot drift between them.
85
+ *
86
+ * An explicit `modelDef.pricing` override wins WHOLESALE: the caller converts
87
+ * the config override with `toPricing` (via `toSessionModel`), which fills
88
+ * the override's own missing cache rates from its input rate, but catalog
89
+ * values are never merged into an override -- that would silently rewrite
90
+ * rates the user configured on purpose.
91
+ *
92
+ * US-003 (#1817): reports which branch it took alongside the card, so the
93
+ * adapter can stamp `pricingSource` on the result without re-deriving the
94
+ * rate-card branch (US-003 retired the table-backed `MODEL_PRICING` lookup
95
+ * this resolver was the successor to). The card is the rate object; the
96
+ * source is the answer to "which one did we use".
97
+ */
98
+ export declare function buildRateCard(catalog: Pricing, override: Pricing | undefined): {
99
+ rates: Pricing;
100
+ source: "config-override" | "catalog-rates";
101
+ };
102
+ /**
103
+ * Resolve the context window `runNativeTurn` compacts against: an explicit
104
+ * `ModelDef.contextWindow` override, falling back to nax-ai's
105
+ * `ResolvedModel.contextWindow` (nax#1848). Same override-then-fallback shape
106
+ * as `buildRateCard`, but with a direction guard that has no pricing
107
+ * equivalent: the window never reaches the provider, so it feeds only
108
+ * `shouldCompact` / `keepBudget` (`session/turn-loop.ts`). Lowering it is
109
+ * therefore safe -- compaction just fires earlier against an otherwise real
110
+ * request. Raising it above the real window would defeat compaction and
111
+ * reintroduce the overflow it exists to prevent, so that direction is
112
+ * rejected outright rather than clamped: this is a deliberate testing / cost
113
+ * lever, not a value set by accident, and a wrong value should be loud. An
114
+ * override equal to the real window is accepted -- it changes nothing.
115
+ */
116
+ export declare function resolveContextWindow(override: number | undefined, realWindow: number): number;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Model reference, usage and cost for the native path.
3
+ *
4
+ * The provider travels inside the model string, not beside it: a multi-provider
5
+ * agent needs it there, and opencode's entries already encode it that way
6
+ * (ADR-027 section 1). Under acpx the same string stays opaque.
7
+ */
8
+ import { parseModelSpec } from "#src/cost/model-spec";
9
+ import { getSafeLogger, NaxError } from "#src/infra/index";
10
+ /** The one agent name that routes to this transport. Defined once in the
11
+ * `@/config/native-agent` leaf (config validates against it); re-exported
12
+ * here, not in the barrel, so session-adapter.ts can import it without an
13
+ * index -> session-adapter -> index cycle. */
14
+ export { NATIVE_AGENT_NAME as NATIVE_AGENT } from "#src/config/native-agent/index";
15
+ export { parseModelSpec };
16
+ /**
17
+ * Strip the nax-level `[effort]` suffix FIRST, then split on the FIRST slash: a
18
+ * provider id never contains one, a model id often does
19
+ * (`huggingface/MiniMaxAI/MiniMax-M2.7`). Order matters — the suffix is
20
+ * trailing, so splitting on the slash before stripping it would leave the
21
+ * suffix glued onto the model id (`"claude-opus-5[high]"`) and
22
+ * `client.model()` would throw its own unknown-model error instead of this
23
+ * function's clearer one.
24
+ *
25
+ * A suffix with no slash (`"claude-opus-5[high]"`) still fails malformed-model
26
+ * validation: `parseModelSpec` only removes the suffix, it does not supply a
27
+ * missing provider.
28
+ */
29
+ export function parseNativeModel(raw) {
30
+ const { model: withoutSuffix, effort } = parseModelSpec(raw);
31
+ const slash = withoutSuffix.indexOf("/");
32
+ const provider = slash === -1 ? "" : withoutSuffix.slice(0, slash);
33
+ const model = slash === -1 ? "" : withoutSuffix.slice(slash + 1);
34
+ if (provider === "" || model === "") {
35
+ throw new NaxError(
36
+ // Naming the sibling field is the point (nax#1851): a reader looking at
37
+ // `{ provider: "anthropic", model: "claude-sonnet-5" }` assumes the field
38
+ // right there is the one being used, and the old message said nothing to
39
+ // correct that. No suggested id is composed from it — on this path the
40
+ // value reaching us may be `resolveModel`'s inference rather than
41
+ // anything configured, and a guessed suggestion is worse than none.
42
+ `Native model "${raw}" must be written "provider/model" (e.g. "openai/gpt-5.4-mini"). There is no default provider. A "provider" field beside "model" in config is NOT used on the native path — the provider belongs in the model id itself.`, "NATIVE_MODEL_MALFORMED", { stage: "complete", model: raw });
43
+ }
44
+ return { provider, model, ...(effort !== undefined ? { effort } : {}) };
45
+ }
46
+ /**
47
+ * Every level nax-ai's `ThinkingLevel` union admits, as an exhaustive mapped
48
+ * type rather than a hand-rolled string array. `Record<ThinkingLevel, true>`
49
+ * only type-checks when every union member has an entry, so if nax-ai adds a
50
+ * level upstream, this fails to COMPILE — a real gate, not a comment that can
51
+ * silently drift out of sync with the union it mirrors.
52
+ */
53
+ export const THINKING_LEVELS = {
54
+ off: true,
55
+ minimal: true,
56
+ low: true,
57
+ medium: true,
58
+ high: true,
59
+ xhigh: true,
60
+ max: true,
61
+ };
62
+ function isThinkingLevel(value) {
63
+ return Object.hasOwn(THINKING_LEVELS, value);
64
+ }
65
+ /**
66
+ * Translate nax's config-side "effort" suffix into nax-ai's request-side
67
+ * "thinking" field. Kept in this one place, named for the seam it crosses: nax
68
+ * calls the concept "effort", nax-ai calls the identical value "thinking".
69
+ *
70
+ * The suffix is a free-form string (see model-spec.ts), so a value outside
71
+ * nax-ai's ThinkingLevel union is possible from a typo'd profile. This
72
+ * mirrors applyReasoningEffort's stance in
73
+ * src/agents/acp/reasoning-effort.ts: "Best-effort: a failure leaves the
74
+ * session at the adapter default rather than failing the whole run. The
75
+ * warning is what keeps that downgrade visible." nax does not clamp against
76
+ * per-model capabilities here — that is nax-ai's clampThinkingLevel's job,
77
+ * run inside client.complete against the resolved model's own
78
+ * thinkingLevels.
79
+ */
80
+ export function toThinkingLevel(effort) {
81
+ if (effort === undefined)
82
+ return undefined;
83
+ if (isThinkingLevel(effort))
84
+ return effort;
85
+ getSafeLogger()?.warn("native-adapter", `Unknown effort "${effort}"; continuing at provider default`, { effort });
86
+ return undefined;
87
+ }
88
+ /**
89
+ * Translate nax's config-side catalog overrides (agent.native.catalogOverrides,
90
+ * nax#1982) into nax-ai's declaration-data `ProviderOverride` records.
91
+ *
92
+ * The override is a COMPLETE entry, not a patch: nax-ai's `normaliseCatalog`
93
+ * applies it last through `setModel`, which replaces any same-id entry and
94
+ * lazily creates the provider bucket — that is what makes an id absent from
95
+ * the bundled pi-ai snapshot resolvable (verified against nax-ai 0.1.10).
96
+ * `provider` is stamped from the outer record because nax-ai's `ResolvedModel`
97
+ * carries it per model.
98
+ */
99
+ export function toProviderOverrides(overrides) {
100
+ return overrides.map((override) => ({
101
+ provider: override.provider,
102
+ // Omitted when undeclared, never passed as `undefined`: nax-ai reads
103
+ // `!== undefined` as a declaration and checks it against the protocol
104
+ // entries (nax#2019).
105
+ ...(override.baseUrl !== undefined ? { baseUrl: override.baseUrl } : {}),
106
+ // Copied, not aliased: nax-ai's client catalog assigns this reference
107
+ // straight onto ResolvedProvider.headers (providers/catalog.ts) without a
108
+ // copy of its own, so forwarding the live config object would let a later
109
+ // mutation reach an already-built client.
110
+ ...(override.headers !== undefined ? { headers: { ...override.headers } } : {}),
111
+ models: override.models.map((model) => ({
112
+ id: model.id,
113
+ provider: override.provider,
114
+ protocol: model.protocol,
115
+ pricing: model.pricing,
116
+ contextWindow: model.contextWindow,
117
+ // Omitted when the override declares none: nax-ai then falls back to the
118
+ // template sibling's ceiling, which is the only honest value available.
119
+ ...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}),
120
+ supportsTools: model.supportsTools,
121
+ thinkingLevels: model.thinkingLevels,
122
+ // nax#2191: forwarded verbatim. nax-ai's `assertOverrideModelRouting`
123
+ // rejects an empty `{}` (pi's check is truthiness, not emptiness — issue
124
+ // #43), so omission is the only safe absent shape. The protocol-side
125
+ // rejection on a non-`openai-completions` model is nax-ai's; nax's
126
+ // schema refuses an empty declaration at config load so the user gets
127
+ // the error there rather than on first dispatch.
128
+ ...(model.openRouterRouting !== undefined ? { openRouterRouting: model.openRouterRouting } : {}),
129
+ })),
130
+ }));
131
+ }
132
+ /**
133
+ * `estimateCostUsd` (and the tier-selection helper it relies on) was
134
+ * relocated to `src/agents/cost/` (US-001) so the ACP and native paths share
135
+ * one implementation. Native callers now import it from `@/agents/cost`.
136
+ */
137
+ /**
138
+ * Turn nax-ai's catalog `Pricing` into the rate object `priceCall` prices from
139
+ * (nax#1843, nax#1847). Both call sites -- `nativeComplete()` (complete.ts)
140
+ * and `NativeSessionAdapter.sendTurn()` (session-adapter.ts) -- build the rate
141
+ * object this way, and both receive the override already converted by
142
+ * `toSessionModel`, so the fix cannot drift between them.
143
+ *
144
+ * An explicit `modelDef.pricing` override wins WHOLESALE: the caller converts
145
+ * the config override with `toPricing` (via `toSessionModel`), which fills
146
+ * the override's own missing cache rates from its input rate, but catalog
147
+ * values are never merged into an override -- that would silently rewrite
148
+ * rates the user configured on purpose.
149
+ *
150
+ * US-003 (#1817): reports which branch it took alongside the card, so the
151
+ * adapter can stamp `pricingSource` on the result without re-deriving the
152
+ * rate-card branch (US-003 retired the table-backed `MODEL_PRICING` lookup
153
+ * this resolver was the successor to). The card is the rate object; the
154
+ * source is the answer to "which one did we use".
155
+ */
156
+ export function buildRateCard(catalog, override) {
157
+ if (override !== undefined)
158
+ return { rates: override, source: "config-override" };
159
+ return { rates: catalog, source: "catalog-rates" };
160
+ }
161
+ /**
162
+ * Resolve the context window `runNativeTurn` compacts against: an explicit
163
+ * `ModelDef.contextWindow` override, falling back to nax-ai's
164
+ * `ResolvedModel.contextWindow` (nax#1848). Same override-then-fallback shape
165
+ * as `buildRateCard`, but with a direction guard that has no pricing
166
+ * equivalent: the window never reaches the provider, so it feeds only
167
+ * `shouldCompact` / `keepBudget` (`session/turn-loop.ts`). Lowering it is
168
+ * therefore safe -- compaction just fires earlier against an otherwise real
169
+ * request. Raising it above the real window would defeat compaction and
170
+ * reintroduce the overflow it exists to prevent, so that direction is
171
+ * rejected outright rather than clamped: this is a deliberate testing / cost
172
+ * lever, not a value set by accident, and a wrong value should be loud. An
173
+ * override equal to the real window is accepted -- it changes nothing.
174
+ */
175
+ export function resolveContextWindow(override, realWindow) {
176
+ if (override === undefined)
177
+ return realWindow;
178
+ if (override > realWindow) {
179
+ throw new NaxError(`configured contextWindow (${override}) exceeds the model's real context window (${realWindow}); ` +
180
+ "raising it above the real window would defeat compaction and risk a provider overflow", "CONTEXT_WINDOW_OVERRIDE_EXCEEDS_REAL_WINDOW", { configuredContextWindow: override, realContextWindow: realWindow });
181
+ }
182
+ return override;
183
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The native human-Q&A channel.
3
+ *
4
+ * `AdapterInteraction` has always declared `{ kind: "question" }`, but the
5
+ * native loop routed only context-tools and coding-tools, so on native the
6
+ * operator could never be asked anything — while `agent.maxInteractionTurns`,
7
+ * the budget that names exactly this, was being spent as a round-trip cap.
8
+ *
9
+ * A declared tool rather than acpx's output parsing (acp/adapter.ts): the
10
+ * native protocol has a structured tool channel, and parsing prose for a
11
+ * question when a structured call is available is the weaker mechanism.
12
+ */
13
+ import type { ToolDefinition } from "@nathapp/nax-ai";
14
+ export declare const ASK_HUMAN_TOOL_NAME = "ask_human";
15
+ export declare const askHumanToolDefinition: ToolDefinition;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The native human-Q&A channel.
3
+ *
4
+ * `AdapterInteraction` has always declared `{ kind: "question" }`, but the
5
+ * native loop routed only context-tools and coding-tools, so on native the
6
+ * operator could never be asked anything — while `agent.maxInteractionTurns`,
7
+ * the budget that names exactly this, was being spent as a round-trip cap.
8
+ *
9
+ * A declared tool rather than acpx's output parsing (acp/adapter.ts): the
10
+ * native protocol has a structured tool channel, and parsing prose for a
11
+ * question when a structured call is available is the weaker mechanism.
12
+ */
13
+ export const ASK_HUMAN_TOOL_NAME = "ask_human";
14
+ export const askHumanToolDefinition = {
15
+ name: ASK_HUMAN_TOOL_NAME,
16
+ description: "Ask the human operator a question and wait for their reply. Use only when genuinely blocked: the budget is small and each call costs a round trip.",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: { text: { type: "string", description: "The question to put to the operator." } },
20
+ required: ["text"],
21
+ },
22
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Context compaction for the native turn loop.
3
+ *
4
+ * Pure functions only: no file I/O, no model calls, no clock. turn-loop.ts owns
5
+ * the orchestration and the one model call. Keeping this half pure is what makes
6
+ * the cut-point rules testable directly rather than only end to end.
7
+ *
8
+ * See docs/superpowers/specs/2026-09-04-native-context-compaction-design.md.
9
+ */
10
+ import type { ConversationMessage } from "@nathapp/nax-ai";
11
+ import type { AdapterInteractionResponse } from "#src/session/interaction-handler";
12
+ /**
13
+ * The transcript message nax stores: nax-ai's ConversationMessage widened with
14
+ * the coding-tool denial marker (ADR-029 s5). The marker is structural data the
15
+ * model must be able to act on — dropping it because the wire type does not
16
+ * know it yet is exactly the defect this widening exists to prevent.
17
+ *
18
+ * Lives here rather than in turn-loop.ts because both modules need it and this
19
+ * is the one without a dependency on the other.
20
+ */
21
+ export type TranscriptMessage = ConversationMessage | {
22
+ readonly role: "tool-result";
23
+ readonly toolCallId: string;
24
+ readonly content: string;
25
+ readonly isError?: boolean;
26
+ readonly denied?: AdapterInteractionResponse["denied"];
27
+ };
28
+ /** Compaction settings after config resolution. Reaches the adapter as a primitive. */
29
+ export interface ResolvedCompaction {
30
+ readonly enabled: boolean;
31
+ readonly compactAtPercent: number;
32
+ readonly keepRecentPercent: number;
33
+ }
34
+ /**
35
+ * Characters over four. Deliberately crude and deliberately high: over-estimating
36
+ * compacts slightly early, under-estimating overflows.
37
+ */
38
+ export declare function estimateTokens(message: TranscriptMessage): number;
39
+ /**
40
+ * Anchor on truth, guess only the tail.
41
+ *
42
+ * `lastUsage.promptTokens` is every token the provider counted for the prompt
43
+ * up to and including `anchorIndex` — uncached input plus cache reads plus
44
+ * cache writes (see `inputClassTokens`). It is deliberately not just the
45
+ * uncached input: under prompt caching the cached prefix is reported
46
+ * separately, and anchoring on that one field reads a 71k-token context as
47
+ * ~16 and silently disables compaction (nax#1852).
48
+ *
49
+ * With no anchor every message is estimated, which is the case the reactive
50
+ * backstop exists to cover.
51
+ */
52
+ export declare function estimateContextTokens(messages: readonly TranscriptMessage[], lastUsage?: {
53
+ readonly promptTokens: number;
54
+ }, anchorIndex?: number): number;
55
+ /**
56
+ * The percentage governs; the floor only takes over when a percentage of the
57
+ * window would leave too little room for a reply. Both constants are a safety
58
+ * rail against a window smaller than the defaults assume, not tuning knobs —
59
+ * which is why they are not config.
60
+ */
61
+ export declare function compactionThreshold(window: number, cfg: ResolvedCompaction): number;
62
+ export declare function shouldCompact(tokens: number, window: number, cfg: ResolvedCompaction): boolean;
63
+ /**
64
+ * How much recent conversation survives verbatim, in tokens.
65
+ * `aggressive` halves it — the reactive backstop's only difference.
66
+ */
67
+ export declare function keepBudget(window: number, cfg: ResolvedCompaction, aggressive?: boolean): number;
68
+ /**
69
+ * Marks the summary message. The summary carries no marker FIELD: transcript
70
+ * messages reach nax-ai structurally, so an extra property would travel to the
71
+ * wire (the existing `denied` marker already does). Position plus this prefix
72
+ * is how a later compaction finds the summary it must replace.
73
+ */
74
+ export declare const COMPACTION_SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
75
+ export declare const COMPACTION_SUMMARY_SUFFIX = "\n</summary>\n";
76
+ /**
77
+ * Walk backwards accumulating estimated size; once `keepTokens` is reached, take
78
+ * the nearest valid cut at or after that point.
79
+ *
80
+ * Two passes on purpose. A single backwards pass that remembers the last valid
81
+ * index it saw returns `messages.length` whenever trailing tool-results consume
82
+ * the whole budget before any valid cut appears — an out-of-range index the
83
+ * caller then dereferences. Collecting the valid cuts first makes "the nearest
84
+ * cut at or after i" answerable without that hole, and `cuts[0]` is the honest
85
+ * default: keep everything, which `prepareCompaction` reads as "no plan".
86
+ */
87
+ export declare function findCutPoint(messages: readonly TranscriptMessage[], startIndex: number, keepTokens: number): number;
88
+ export interface CompactionPlan {
89
+ readonly cutIndex: number;
90
+ readonly toSummarize: readonly TranscriptMessage[];
91
+ readonly previousSummary?: string;
92
+ }
93
+ export declare function prepareCompaction(messages: readonly TranscriptMessage[], keepTokens: number): CompactionPlan | undefined;
94
+ export declare function applyCompaction(messages: readonly TranscriptMessage[], plan: CompactionPlan, summary: string): TranscriptMessage[];
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Context compaction for the native turn loop.
3
+ *
4
+ * Pure functions only: no file I/O, no model calls, no clock. turn-loop.ts owns
5
+ * the orchestration and the one model call. Keeping this half pure is what makes
6
+ * the cut-point rules testable directly rather than only end to end.
7
+ *
8
+ * See docs/superpowers/specs/2026-09-04-native-context-compaction-design.md.
9
+ */
10
+ /** A reply needs room whatever the window is. */
11
+ const MIN_HEADROOM_TOKENS = 4096;
12
+ /** ...but never take a quarter of a small window for headroom. */
13
+ const MAX_HEADROOM_FRACTION = 0.25;
14
+ const CHARS_PER_TOKEN = 4;
15
+ /**
16
+ * Characters over four. Deliberately crude and deliberately high: over-estimating
17
+ * compacts slightly early, under-estimating overflows.
18
+ */
19
+ export function estimateTokens(message) {
20
+ let chars = 0;
21
+ switch (message.role) {
22
+ case "user":
23
+ chars = message.content.length;
24
+ break;
25
+ case "assistant":
26
+ chars = message.content.length;
27
+ for (const block of message.thinking ?? [])
28
+ chars += block.text.length;
29
+ for (const call of message.toolCalls ?? [])
30
+ chars += call.name.length + JSON.stringify(call.input).length;
31
+ break;
32
+ case "tool-result":
33
+ chars = message.content.length;
34
+ break;
35
+ }
36
+ return Math.ceil(chars / CHARS_PER_TOKEN);
37
+ }
38
+ /**
39
+ * Anchor on truth, guess only the tail.
40
+ *
41
+ * `lastUsage.promptTokens` is every token the provider counted for the prompt
42
+ * up to and including `anchorIndex` — uncached input plus cache reads plus
43
+ * cache writes (see `inputClassTokens`). It is deliberately not just the
44
+ * uncached input: under prompt caching the cached prefix is reported
45
+ * separately, and anchoring on that one field reads a 71k-token context as
46
+ * ~16 and silently disables compaction (nax#1852).
47
+ *
48
+ * With no anchor every message is estimated, which is the case the reactive
49
+ * backstop exists to cover.
50
+ */
51
+ export function estimateContextTokens(messages, lastUsage, anchorIndex) {
52
+ if (lastUsage === undefined || anchorIndex === undefined) {
53
+ return messages.reduce((sum, m) => sum + estimateTokens(m), 0);
54
+ }
55
+ let trailing = 0;
56
+ for (let i = anchorIndex + 1; i < messages.length; i++)
57
+ trailing += estimateTokens(messages[i]);
58
+ return lastUsage.promptTokens + trailing;
59
+ }
60
+ /**
61
+ * The percentage governs; the floor only takes over when a percentage of the
62
+ * window would leave too little room for a reply. Both constants are a safety
63
+ * rail against a window smaller than the defaults assume, not tuning knobs —
64
+ * which is why they are not config.
65
+ */
66
+ export function compactionThreshold(window, cfg) {
67
+ const headroom = Math.min(MIN_HEADROOM_TOKENS, Math.floor(window * MAX_HEADROOM_FRACTION));
68
+ return Math.min(Math.floor(window * (cfg.compactAtPercent / 100)), window - headroom);
69
+ }
70
+ export function shouldCompact(tokens, window, cfg) {
71
+ return cfg.enabled && tokens > compactionThreshold(window, cfg);
72
+ }
73
+ /**
74
+ * How much recent conversation survives verbatim, in tokens.
75
+ * `aggressive` halves it — the reactive backstop's only difference.
76
+ */
77
+ export function keepBudget(window, cfg, aggressive = false) {
78
+ const budget = Math.floor(window * (cfg.keepRecentPercent / 100));
79
+ return aggressive ? Math.floor(budget / 2) : budget;
80
+ }
81
+ /**
82
+ * Marks the summary message. The summary carries no marker FIELD: transcript
83
+ * messages reach nax-ai structurally, so an extra property would travel to the
84
+ * wire (the existing `denied` marker already does). Position plus this prefix
85
+ * is how a later compaction finds the summary it must replace.
86
+ */
87
+ export const COMPACTION_SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
88
+ export const COMPACTION_SUMMARY_SUFFIX = "\n</summary>\n";
89
+ /** Index of the pinned first message; never summarized, never dropped. */
90
+ const PIN_INDEX = 0;
91
+ /** Where the summary sits once written. */
92
+ const SUMMARY_INDEX = 1;
93
+ /**
94
+ * Valid cut points are user or assistant messages, NEVER a tool-result.
95
+ *
96
+ * That one rule carries both hard constraints: a tool-result can never become
97
+ * the first kept message (orphaning it from its tool_use, which the provider
98
+ * rejects), and cuts land between messages rather than inside an assistant
99
+ * message, so a kept thinking block keeps its exact text and signature.
100
+ */
101
+ function isValidCut(message) {
102
+ return message.role === "user" || message.role === "assistant";
103
+ }
104
+ /**
105
+ * Walk backwards accumulating estimated size; once `keepTokens` is reached, take
106
+ * the nearest valid cut at or after that point.
107
+ *
108
+ * Two passes on purpose. A single backwards pass that remembers the last valid
109
+ * index it saw returns `messages.length` whenever trailing tool-results consume
110
+ * the whole budget before any valid cut appears — an out-of-range index the
111
+ * caller then dereferences. Collecting the valid cuts first makes "the nearest
112
+ * cut at or after i" answerable without that hole, and `cuts[0]` is the honest
113
+ * default: keep everything, which `prepareCompaction` reads as "no plan".
114
+ */
115
+ export function findCutPoint(messages, startIndex, keepTokens) {
116
+ const cuts = [];
117
+ for (let i = startIndex; i < messages.length; i++) {
118
+ if (isValidCut(messages[i]))
119
+ cuts.push(i);
120
+ }
121
+ if (cuts.length === 0)
122
+ return messages.length;
123
+ let candidate = cuts[0];
124
+ let accumulated = 0;
125
+ for (let i = messages.length - 1; i >= startIndex; i--) {
126
+ accumulated += estimateTokens(messages[i]);
127
+ if (accumulated >= keepTokens) {
128
+ const at = cuts.find((c) => c >= i);
129
+ if (at !== undefined)
130
+ candidate = at;
131
+ break;
132
+ }
133
+ }
134
+ return candidate;
135
+ }
136
+ /** Reads a previous summary out of the message at SUMMARY_INDEX, if one is there. */
137
+ function readPreviousSummary(messages) {
138
+ const candidate = messages[SUMMARY_INDEX];
139
+ if (candidate === undefined || candidate.role !== "user")
140
+ return undefined;
141
+ if (!candidate.content.startsWith(COMPACTION_SUMMARY_PREFIX))
142
+ return undefined;
143
+ return candidate.content.slice(COMPACTION_SUMMARY_PREFIX.length, -COMPACTION_SUMMARY_SUFFIX.length);
144
+ }
145
+ export function prepareCompaction(messages, keepTokens) {
146
+ const previousSummary = readPreviousSummary(messages);
147
+ // Everything from here is fair game; the pin, and any existing summary, are not.
148
+ const spanStart = previousSummary === undefined ? PIN_INDEX + 1 : SUMMARY_INDEX + 1;
149
+ const cutIndex = findCutPoint(messages, spanStart, keepTokens);
150
+ // An empty span is "nothing to do" whether or not a summary already exists
151
+ // (nax#1842). A merge plan is still returnable — that needs `previousSummary`
152
+ // AND new content, which is exactly `cutIndex > spanStart`. With an empty span
153
+ // there is nothing to fold in: summarize() would be paid to re-summarize the
154
+ // previous summary alone and applyCompaction would rebuild an array no smaller
155
+ // than the one it replaced, once per round trip for the rest of the turn.
156
+ if (cutIndex <= spanStart)
157
+ return undefined;
158
+ return {
159
+ cutIndex,
160
+ toSummarize: messages.slice(spanStart, cutIndex),
161
+ ...(previousSummary !== undefined ? { previousSummary } : {}),
162
+ };
163
+ }
164
+ export function applyCompaction(messages, plan, summary) {
165
+ return [
166
+ messages[PIN_INDEX],
167
+ { role: "user", content: COMPACTION_SUMMARY_PREFIX + summary + COMPACTION_SUMMARY_SUFFIX },
168
+ ...messages.slice(plan.cutIndex),
169
+ ];
170
+ }