@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,362 @@
1
+ /**
2
+ * Obtaining and managing credentials, in nax's vocabulary.
3
+ *
4
+ * This file and its siblings are the only place in src/ permitted to import
5
+ * nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts). Nothing it exports carries a
6
+ * nax-ai type, so src/cli/auth.ts can consume it without breaching that gate.
7
+ */
8
+ import { readFile } from "node:fs/promises";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { ambientAuthAvailable, defaultProviders, login, } from "@nathapp/nax-ai";
12
+ import { NaxError } from "#src/infra/index";
13
+ import { naxCredentialStore, readStoredEntries } from "./credentials/index.js";
14
+ /**
15
+ * Deliberately dumb: the two vocabularies are one-for-one by design, so this
16
+ * is a rename boundary rather than a translation with opinions. Both sides use
17
+ * kebab-case, so the names pass straight through.
18
+ */
19
+ export function toLoginInteraction(interaction) {
20
+ return {
21
+ prompt: async (prompt) => interaction.prompt(prompt),
22
+ notify: (event) => interaction.notify(event),
23
+ };
24
+ }
25
+ /**
26
+ * Cancellation is not a failure, and the CLI needs to tell them apart to exit
27
+ * 130 silently. A marker class rather than a code match: an exit status should
28
+ * not depend on message text.
29
+ */
30
+ export class AuthCancelledError extends Error {
31
+ providerId;
32
+ constructor(providerId) {
33
+ super(`Login for "${providerId}" was cancelled`);
34
+ this.providerId = providerId;
35
+ this.name = "AuthCancelledError";
36
+ }
37
+ }
38
+ /** Test seam, following the _clientDeps precedent. */
39
+ export const _authDeps = {
40
+ login,
41
+ ambientAuthAvailable,
42
+ providerIds: async () => (await defaultProviders()).map((provider) => provider.id),
43
+ /**
44
+ * Injectable timer pair — lets tests drive `anyAmbientCredential`'s
45
+ * expiry branch off a virtual clock instead of waiting the full 2s
46
+ * production timeout. Mirrors `_heartbeatDeps` / `_idleWatchdogDeps`.
47
+ *
48
+ * @internal
49
+ */
50
+ setTimeout: ((fn, ms) => setTimeout(fn, ms)),
51
+ clearTimeout: ((id) => clearTimeout(id)),
52
+ };
53
+ /**
54
+ * nax-ai's errors are mapped here rather than in the CLI, so its type names
55
+ * stay behind this directory's boundary.
56
+ *
57
+ * A prohibited flow keeps its message: the recorded reason is the whole point
58
+ * of the policy file, and a generic failure would discard it.
59
+ */
60
+ function toNaxError(error, providerId) {
61
+ if (error instanceof Error) {
62
+ // Nax's own prompts reject with PromptCancelledError from inside the
63
+ // interaction callback, so it surfaces here rather than being converted
64
+ // by nax-ai's login() — that conversion only fires on an aborted signal,
65
+ // and runLogin never threads one.
66
+ if (error.name === "LoginCancelledError" || error.name === "PromptCancelledError") {
67
+ return new AuthCancelledError(providerId);
68
+ }
69
+ if (error.name === "OAuthFlowProhibitedError") {
70
+ return new NaxError(error.message, "AUTH_OAUTH_PROHIBITED", { providerId });
71
+ }
72
+ if (error.name === "AuthMethodUnavailableError") {
73
+ // "requested" separates "this provider offers nothing we can run" from
74
+ // "it offers something, just not the --method you asked for". Collapsing
75
+ // them tells a user whose provider supports oauth that nothing is
76
+ // available, and sends them to a config problem that does not exist.
77
+ const requested = error.requested;
78
+ return new NaxError(requested === undefined
79
+ ? `No login method is available for "${providerId}".`
80
+ : `"${providerId}" does not offer "${requested}" login. Omit --method to see what it does offer.`, "AUTH_METHOD_UNAVAILABLE", { providerId, ...(requested !== undefined ? { requested } : {}) });
81
+ }
82
+ return new NaxError(`Login for "${providerId}" failed: ${error.message}`, "AUTH_LOGIN_FAILED", { providerId });
83
+ }
84
+ return new NaxError(`Login for "${providerId}" failed.`, "AUTH_LOGIN_FAILED", { providerId });
85
+ }
86
+ /**
87
+ * Obtain a credential and write it to the store.
88
+ *
89
+ * `method` is forwarded, not interpreted: nax-ai owns the table of what each
90
+ * provider offers and rejects an unavailable choice itself. Omitted, it runs
91
+ * its own selection prompt. Deciding here is how the two drift apart.
92
+ *
93
+ * The result is reported exactly as returned. kind is never derived from
94
+ * method — M5's design predicted openrouter would report kind "api-key" and
95
+ * its live run reported "oauth".
96
+ */
97
+ export async function runLogin(providerId, interaction, method) {
98
+ try {
99
+ const result = await _authDeps.login({
100
+ providerId,
101
+ credentials: naxCredentialStore(),
102
+ interaction: toLoginInteraction(interaction),
103
+ ...(method !== undefined ? { method } : {}),
104
+ });
105
+ return { providerId: result.providerId, method: result.method, kind: result.kind };
106
+ }
107
+ catch (error) {
108
+ throw toNaxError(error, providerId);
109
+ }
110
+ }
111
+ export const DEFAULT_PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
112
+ /**
113
+ * pi's on-disk shape is flat and snake-cased; the store's is versioned and
114
+ * kebab-cased. accountId is deliberately dropped: pi derives it from the
115
+ * access-token JWT at request time rather than trusting what is stored, and
116
+ * nax-ai's own credential round-trip already drops it.
117
+ */
118
+ function fromPiEntry(entry) {
119
+ if (entry.type === "api_key" && typeof entry.key === "string") {
120
+ return { kind: "api-key", key: entry.key };
121
+ }
122
+ if (entry.type === "oauth" &&
123
+ typeof entry.access === "string" &&
124
+ typeof entry.refresh === "string" &&
125
+ typeof entry.expires === "number") {
126
+ return { kind: "oauth", access: entry.access, refresh: entry.refresh, expires: entry.expires };
127
+ }
128
+ return undefined;
129
+ }
130
+ /**
131
+ * Bring pi's credentials across.
132
+ *
133
+ * Existing entries are skipped rather than overwritten: import plausibly runs
134
+ * after a fresh login, and silently replacing a credential just obtained would
135
+ * be the worst kind of quiet data loss.
136
+ *
137
+ * The presence check happens inside modify()'s callback, not before it: that
138
+ * is what holds the store's cross-process lock across the whole
139
+ * read-modify-write. Deciding "already present" from a separate, unlocked
140
+ * read would race a concurrent login completing between the two calls.
141
+ */
142
+ export async function importPiCredentials(options) {
143
+ const path = options?.from ?? DEFAULT_PI_AUTH_PATH;
144
+ let raw;
145
+ try {
146
+ raw = await readFile(path, "utf8");
147
+ }
148
+ catch (error) {
149
+ if (error.code === "ENOENT") {
150
+ throw new NaxError(`No credential file to import at ${path}.`, "AUTH_IMPORT_SOURCE_MISSING", {
151
+ path,
152
+ cause: error,
153
+ });
154
+ }
155
+ throw new NaxError(`The file at ${path} could not be read.`, "AUTH_IMPORT_SOURCE_UNREADABLE", {
156
+ path,
157
+ cause: error,
158
+ });
159
+ }
160
+ let parsed;
161
+ try {
162
+ parsed = JSON.parse(raw);
163
+ }
164
+ catch (error) {
165
+ throw new NaxError(`The file at ${path} is not valid JSON.`, "AUTH_IMPORT_SOURCE_UNREADABLE", {
166
+ path,
167
+ cause: error,
168
+ });
169
+ }
170
+ const store = naxCredentialStore();
171
+ const outcomes = [];
172
+ for (const providerId of Object.keys(parsed).sort()) {
173
+ const entry = parsed[providerId];
174
+ const credential = entry === undefined ? undefined : fromPiEntry(entry);
175
+ if (credential === undefined) {
176
+ outcomes.push({ providerId, status: "unsupported" });
177
+ continue;
178
+ }
179
+ let status = "imported";
180
+ await store.modify(providerId, async (existing) => {
181
+ if (existing !== undefined && options?.force !== true) {
182
+ status = "skipped";
183
+ return existing;
184
+ }
185
+ return credential;
186
+ });
187
+ outcomes.push({ providerId, status });
188
+ }
189
+ return outcomes;
190
+ }
191
+ export async function listStoredProviders() {
192
+ return readStoredEntries();
193
+ }
194
+ /**
195
+ * Removal, not revocation. pi has no revocation anywhere — its own types
196
+ * define logout as deletion — so the provider-side token stays live until it
197
+ * expires. Callers must not describe this as logging out.
198
+ */
199
+ export async function removeStoredProvider(providerId) {
200
+ await naxCredentialStore().delete(providerId);
201
+ }
202
+ export function authImportOutcomeLabel(status) {
203
+ if (status === "imported")
204
+ return "imported";
205
+ if (status === "skipped")
206
+ return "skipped, already present";
207
+ return "unsupported credential type";
208
+ }
209
+ /**
210
+ * Of these providers, which would ambient auth satisfy on its own?
211
+ *
212
+ * A stored credential owns its provider in pi's resolution order, so any
213
+ * provider named here has a working environment variable that the stored
214
+ * credential is shadowing. This only ever decorates a diagnostic, so a failing
215
+ * probe reports nothing rather than breaking the command around it.
216
+ */
217
+ export async function ambientShadows(providerIds) {
218
+ const checked = await Promise.all(providerIds.map(async (providerId) => {
219
+ try {
220
+ return (await _authDeps.ambientAuthAvailable(providerId)) ? providerId : undefined;
221
+ }
222
+ catch {
223
+ return undefined;
224
+ }
225
+ }));
226
+ return checked.filter((id) => id !== undefined);
227
+ }
228
+ /**
229
+ * Of these providers, which have NO credential at all — neither stored nor
230
+ * ambient?
231
+ *
232
+ * Asked for the providers the default native tier map names, so an install
233
+ * whose only credential belongs to another provider fails before a billed call
234
+ * rather than at the first request.
235
+ *
236
+ * What counts as "has a credential": a credential the assembled store serves
237
+ * for that provider, or nax-ai's ambient probe answering true. That probe
238
+ * swallows its own check/resolve errors and answers false, so a provider whose
239
+ * resolution fails IS reported missing — the caller must exclude providers it
240
+ * knows authenticate some other way (catalog overrides). The two cases here
241
+ * that do not guess "no" are the ones this function can see: an unreadable
242
+ * store, and a sweep that outlives AMBIENT_PROBE_TIMEOUT_MS (pi's resolve()
243
+ * "may execute commands") — both report nothing missing.
244
+ *
245
+ * US-004: "has a stored credential" is a read through `naxCredentialStore()`,
246
+ * per provider, rather than a listing of the file. With an exec helper
247
+ * configured only the store knows which source serves a provider, and a helper
248
+ * failure (CREDENTIAL_HELPER_FAILED, CREDENTIAL_HELPER_INVALID) or a refused
249
+ * change (CREDENTIAL_CHANGED) is a run-level fault, not a missing credential —
250
+ * it propagates and refuses the run. The reads also record each provider's
251
+ * baseline (`credential.resolved`) before any story starts, which is what the
252
+ * guard later compares against. They happen before, and outside, the ambient
253
+ * sweep's race below, so a helper slower than the sweep's timeout is still
254
+ * asked — and they are issued as one batch, so N helper-served providers cost
255
+ * the slowest helper rather than the sum of all of them.
256
+ */
257
+ export async function providersWithoutCredentials(providerIds) {
258
+ const unique = [...new Set(providerIds)];
259
+ const store = naxCredentialStore();
260
+ const stored = new Set();
261
+ // `allSettled`, not `all`: a rejection here is fatal for the run, and `all`
262
+ // would return the moment the first one failed while every sibling read kept
263
+ // going — leaving up to N-1 helper subprocesses alive for their own full
264
+ // timeout, still writing `credential.resolved` entries and guard baselines for
265
+ // a caller that has already thrown. Settling first costs nothing on the happy
266
+ // path (it is one batch either way) and makes the failure path clean.
267
+ // (These reads sit outside the ambient race below on purpose — a helper slower
268
+ // than AMBIENT_PROBE_TIMEOUT_MS is still asked.)
269
+ const served = await Promise.allSettled(unique.map(async (providerId) => ({ providerId, credential: await store.read(providerId) })));
270
+ for (const outcome of served) {
271
+ if (outcome.status === "rejected") {
272
+ // A damaged credential file stays "unknown", which is the behaviour this
273
+ // check has always had: reporting the provider missing would look exactly
274
+ // like "you have no credentials" for a file that is merely unreadable.
275
+ if (outcome.reason instanceof NaxError && outcome.reason.code === "CREDENTIAL_FILE_UNREADABLE")
276
+ return [];
277
+ throw outcome.reason;
278
+ }
279
+ if (outcome.value.credential !== undefined)
280
+ stored.add(outcome.value.providerId);
281
+ }
282
+ const sweep = Promise.all(unique
283
+ .filter((providerId) => !stored.has(providerId))
284
+ .map(async (providerId) => {
285
+ try {
286
+ return (await _authDeps.ambientAuthAvailable(providerId)) ? undefined : providerId;
287
+ }
288
+ catch {
289
+ return undefined;
290
+ }
291
+ })).then((missing) => missing.filter((id) => id !== undefined));
292
+ let timer;
293
+ const expiry = new Promise((resolve) => {
294
+ timer = setTimeout(() => resolve([]), AMBIENT_PROBE_TIMEOUT_MS);
295
+ });
296
+ try {
297
+ return await Promise.race([sweep, expiry]);
298
+ }
299
+ finally {
300
+ if (timer !== undefined)
301
+ clearTimeout(timer);
302
+ }
303
+ }
304
+ /**
305
+ * How long the whole ambient sweep may take before it gives up and reports
306
+ * "credentialed".
307
+ *
308
+ * Measured today the sweep is ~17ms: the catalog is memoised upstream after a
309
+ * ~15ms first load, and 39 ambient probes take ~2ms because no bundled pi
310
+ * provider defines check() and every resolve() reads environment variables and
311
+ * credential files only. That is a snapshot, not a guarantee — pi's own
312
+ * contract warns resolve() "may execute commands", and sweeping the catalog
313
+ * amplifies that across every provider.
314
+ */
315
+ const AMBIENT_PROBE_TIMEOUT_MS = 2_000;
316
+ /**
317
+ * Is ANY provider satisfied by ambient auth alone?
318
+ *
319
+ * Deliberately not "is provider X satisfied": the caller
320
+ * (NativeAgentAdapter.hasCredentials) has no provider to ask about, because
321
+ * the model — and so the provider — is chosen per request.
322
+ *
323
+ * Expiry resolves TRUE, not false. Pruning an agent that would have worked
324
+ * kills a run; reporting one that cannot authenticate costs a single
325
+ * request-time auth error that is already mapped and handled. Where this
326
+ * cannot answer, it must not guess "no".
327
+ */
328
+ export async function anyAmbientCredential() {
329
+ let timer;
330
+ const sweep = (async () => {
331
+ const ids = await _authDeps.providerIds();
332
+ if (ids.length === 0)
333
+ return false;
334
+ // Resolve on the first success rather than awaiting every probe, so one
335
+ // satisfied provider does not wait behind a slow one.
336
+ return new Promise((resolve) => {
337
+ let outstanding = ids.length;
338
+ const settleOne = (satisfied) => {
339
+ if (satisfied)
340
+ resolve(true);
341
+ else if (--outstanding === 0)
342
+ resolve(false);
343
+ };
344
+ for (const id of ids) {
345
+ _authDeps.ambientAuthAvailable(id).then((ok) => settleOne(ok),
346
+ // A probe that throws is not a satisfied provider. It is also not a
347
+ // reason to fail the sweep: the other providers still count.
348
+ () => settleOne(false));
349
+ }
350
+ });
351
+ })();
352
+ const expiry = new Promise((resolve) => {
353
+ timer = _authDeps.setTimeout(() => resolve(true), AMBIENT_PROBE_TIMEOUT_MS);
354
+ });
355
+ try {
356
+ return await Promise.race([sweep, expiry]);
357
+ }
358
+ finally {
359
+ if (timer !== undefined)
360
+ _authDeps.clearTimeout(timer);
361
+ }
362
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The nax-ai client, built once per process.
3
+ *
4
+ * defaultProviders() loads nax-ai's bundled catalog (~1290 models), so building per
5
+ * call would pay that cost on every completion. The build is memoised, but a
6
+ * FAILED build is not: a transient failure must not poison the process.
7
+ *
8
+ * This file and its siblings are the only place in src/ permitted to import
9
+ * nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts).
10
+ */
11
+ import { type Client, defaultProtocols } from "@nathapp/nax-ai";
12
+ import type { ProviderCatalogOverride } from "#src/config/catalog-overrides";
13
+ /** The catalog overrides a native client is built with (`agent.native.catalogOverrides`). */
14
+ export type NativeCatalogOverrides = readonly ProviderCatalogOverride[];
15
+ /**
16
+ * Options for `buildNativeClient`. Production callers leave this empty —
17
+ * every field has a sensible nax-ai default. The seam exists so tests can
18
+ * opt out of paths the production retry policy was never meant to exercise.
19
+ */
20
+ export interface BuildNativeClientOptions {
21
+ /**
22
+ * Transport-fault retry budget (nax-ai's `ClientOptions.transportRetries`,
23
+ * default 2). Tests that want a single attempt pass `0`; the production
24
+ * 2-retry schedule sleeps through 250ms + 500ms of backoff on the first
25
+ * thrown "Provider is not configured" / 5xx, which is most of the wall-clock
26
+ * in tests that intentionally fail at the protocol boundary.
27
+ */
28
+ readonly transportRetries?: number;
29
+ }
30
+ /**
31
+ * The real builder. Exported on its own — not just as `_clientDeps.build` —
32
+ * because test/preload.ts overwrites `_clientDeps.build` with a sentinel
33
+ * before any test file loads (to stop a real client leaking into the
34
+ * module-level cache across files), which would otherwise make this
35
+ * synchronous, no-network construction path uncoverable by any test.
36
+ */
37
+ export declare function buildNativeClient(catalogOverrides?: readonly ProviderCatalogOverride[], options?: BuildNativeClientOptions): Promise<Client>;
38
+ /**
39
+ * Test seam. `build` is replaced in tests so no catalog is loaded and no
40
+ * network is reached. `defaultProtocols` is separately injectable so a test can
41
+ * observe what buildNativeClient passes it (the credentials wiring above)
42
+ * without loading the real catalog — see client.test.ts.
43
+ */
44
+ export declare const _clientDeps: {
45
+ build: typeof buildNativeClient;
46
+ defaultProtocols: typeof defaultProtocols;
47
+ };
48
+ export declare function getNativeClient(catalogOverrides?: NativeCatalogOverrides): Promise<Client>;
49
+ /** Clears the memo. Tests only. */
50
+ export declare function _resetNativeClient(): void;
@@ -0,0 +1,174 @@
1
+ /**
2
+ * The nax-ai client, built once per process.
3
+ *
4
+ * defaultProviders() loads nax-ai's bundled catalog (~1290 models), so building per
5
+ * call would pay that cost on every completion. The build is memoised, but a
6
+ * FAILED build is not: a transient failure must not poison the process.
7
+ *
8
+ * This file and its siblings are the only place in src/ permitted to import
9
+ * nax-ai (packages/repo-tooling/scripts/check-nax-ai-imports.ts).
10
+ */
11
+ import { createHash } from "node:crypto";
12
+ import { createClient, defaultProtocols, defaultProviders } from "@nathapp/nax-ai";
13
+ import { NaxError } from "#src/infra/index";
14
+ import { byCodePoint } from "#src/internal/sort";
15
+ import { naxCredentialStore } from "./credentials/index.js";
16
+ import { toProviderOverrides } from "./models.js";
17
+ /**
18
+ * How nax names itself to a provider that reports on the calling application.
19
+ *
20
+ * nax-ai owns which vendor spells this in which header — OpenRouter reads
21
+ * `HTTP-Referer` and `X-Title` into the `app_id`, `origin` and `http_referer`
22
+ * fields of every generation record — and nax owns only the identity. Without
23
+ * it, the sole identifying header on the wire is pi-ai's hardcoded
24
+ * `User-Agent: pi (<platform> <release>; <arch>)`, which every pi-ai consumer
25
+ * sends, so nax traffic cannot be told apart from anything else in the provider
26
+ * account paying for it.
27
+ *
28
+ * A literal rather than a read of package.json: the bundle is built with
29
+ * `bun build --target bun` and does not carry a package.json to read at
30
+ * runtime. No version in the name — a display name that changed every release
31
+ * would split one application into many rows on a provider's dashboard.
32
+ */
33
+ const NAX_CLIENT_APP = { name: "nax", url: "https://github.com/nathapp-io/nax" };
34
+ /**
35
+ * The real builder. Exported on its own — not just as `_clientDeps.build` —
36
+ * because test/preload.ts overwrites `_clientDeps.build` with a sentinel
37
+ * before any test file loads (to stop a real client leaking into the
38
+ * module-level cache across files), which would otherwise make this
39
+ * synchronous, no-network construction path uncoverable by any test.
40
+ */
41
+ export async function buildNativeClient(catalogOverrides = [], options = {}) {
42
+ return createClient({
43
+ providers: await defaultProviders(),
44
+ // The factory form, not the entries form: nax-ai has TWO catalogs — the
45
+ // client's, which `model()` and `pricing()` read, and the protocol layer's,
46
+ // which request-time resolution reads — and an override has to reach both.
47
+ // Entries built without it reproduce #1982 exactly: the model resolves and
48
+ // prices happily, then throws `Unknown model ... in the pi-ai catalog` on
49
+ // the first real request. createClient hands the factory the array it was
50
+ // constructed with, so the two sides cannot disagree — and nax-ai 0.1.11
51
+ // rejects at construction a client whose entries never heard about an
52
+ // override the client itself declared (nax-ai#36).
53
+ protocols: ({ providerOverrides }) => _clientDeps.defaultProtocols({
54
+ // The credential seam: pi resolves the store first, then ambient sources
55
+ // (env vars, AWS profiles, ADC), so a stored credential owns its provider
56
+ // and CI with only an environment variable keeps working. Passing it here
57
+ // is what makes `nax auth login` reach a run. This is the only inlet:
58
+ // ClientOptions once carried a `credentials` field that createClient
59
+ // never read, and nax-ai 0.1.4 removed it for exactly that reason.
60
+ credentials: naxCredentialStore(),
61
+ // Construction-time, like `credentials`: the identity is a constant of
62
+ // the process, so nax-ai takes it here rather than on every request.
63
+ clientApp: NAX_CLIENT_APP,
64
+ providerOverrides,
65
+ }),
66
+ // nax-ai applies these last (`normaliseCatalog`), replacing any bundled
67
+ // entry with the same id and lazily creating the provider bucket when the
68
+ // id is unknown to pi-ai — that is what makes a model newer than the
69
+ // snapshot resolvable (#1982). Omitted entirely when empty so the
70
+ // no-override path stays byte-identical to before.
71
+ ...(catalogOverrides.length > 0 ? { providerOverrides: toProviderOverrides(catalogOverrides) } : {}),
72
+ // nax-ai defaults `transportRetries` to 2 (250ms + 500ms backoff).
73
+ // Passed through verbatim when the caller asks, omitted when it doesn't,
74
+ // so the production path stays byte-identical to before this seam.
75
+ ...(options.transportRetries !== undefined ? { transportRetries: options.transportRetries } : {}),
76
+ });
77
+ }
78
+ /**
79
+ * Test seam. `build` is replaced in tests so no catalog is loaded and no
80
+ * network is reached. `defaultProtocols` is separately injectable so a test can
81
+ * observe what buildNativeClient passes it (the credentials wiring above)
82
+ * without loading the real catalog — see client.test.ts.
83
+ */
84
+ export const _clientDeps = {
85
+ build: buildNativeClient,
86
+ defaultProtocols,
87
+ };
88
+ /**
89
+ * Recursively rebuild plain objects with keys sorted by code point. Array
90
+ * order is preserved — model lists keep their declared order because nax-ai
91
+ * replaces entries by id and a duplicate id is last-wins. Built with
92
+ * `Object.fromEntries` (not key assignment) so a header literally named
93
+ * `__proto__` survives as an own property instead of being swallowed by the
94
+ * prototype setter.
95
+ */
96
+ function canonicalise(value) {
97
+ if (Array.isArray(value))
98
+ return value.map((item) => canonicalise(item));
99
+ if (value !== null && typeof value === "object") {
100
+ return Object.fromEntries(Object.entries(value)
101
+ .sort(([a], [b]) => byCodePoint(a, b))
102
+ .map(([key, val]) => [key, canonicalise(val)]));
103
+ }
104
+ return value;
105
+ }
106
+ /**
107
+ * Deterministic serialisation of an override set (nax#2025): overrides sorted
108
+ * by provider and object keys sorted by code point (`byCodePoint`, per CTX-5 —
109
+ * the ordering feeds both the cache key and the digest, so it must be stable
110
+ * across locales and ICU versions), so two sets that differ only in listing
111
+ * order or header key order — semantically identical on the nax-ai side —
112
+ * serialise identically. Two overrides with the SAME provider keep their
113
+ * declared order: duplicate providers are last-wins by design, so their
114
+ * relative order is part of the semantics.
115
+ */
116
+ function canonicalOverrideKey(overrides) {
117
+ const sorted = [...overrides].sort((a, b) => byCodePoint(a.provider, b.provider));
118
+ return JSON.stringify(canonicalise(sorted));
119
+ }
120
+ /** Hex prefix length of the set digest used in error summaries. */
121
+ const DIGEST_LENGTH = 12;
122
+ /**
123
+ * Secret-free summary of an override set for error context: provider names,
124
+ * header key NAMES (never values — an `Authorization` header is a credential,
125
+ * nax#2025), and a digest of the canonical key so sets that differ only in
126
+ * redacted material stay distinguishable.
127
+ *
128
+ * The digest is an IDENTIFIER, not a security boundary: it hashes the raw
129
+ * canonical key, so it must only ever appear next to the sanitised summary —
130
+ * never logged beside the redacted-but-inferable override set.
131
+ */
132
+ function summariseOverrides(overrides) {
133
+ return {
134
+ providers: overrides.map((o) => o.provider).sort(),
135
+ headerKeys: [...new Set(overrides.flatMap((o) => Object.keys(o.headers ?? {})))].sort(),
136
+ digest: createHash("sha256").update(canonicalOverrideKey(overrides)).digest("hex").slice(0, DIGEST_LENGTH),
137
+ };
138
+ }
139
+ let cached;
140
+ /** Override set the cached build was created for — kept for secret-free summarising. */
141
+ let cachedOverrides;
142
+ /** Serialised override set the cached build was created for. */
143
+ let cachedOverridesKey;
144
+ export async function getNativeClient(catalogOverrides = []) {
145
+ const overridesKey = canonicalOverrideKey(catalogOverrides);
146
+ if (cached !== undefined && overridesKey !== cachedOverridesKey) {
147
+ // The client is a constant of the process (catalog load is ~50ms / ~650KB),
148
+ // so overrides must be collected into ONE set before the first build. A
149
+ // silent second build would swap the client under in-flight sessions.
150
+ throw new NaxError("The native client was already built for a different catalog-override set. " +
151
+ "Collect every override into one agent.native.catalogOverrides list instead of varying them per call.", "NATIVE_CLIENT_OVERRIDES_MISMATCH", { builtFor: summariseOverrides(cachedOverrides ?? []), requested: summariseOverrides(catalogOverrides) });
152
+ }
153
+ if (cached === undefined) {
154
+ // Shallow copy: the caller's array may later be mutated (e.g. a push), which
155
+ // would mislabel `builtFor` — the summary must describe the set that was built.
156
+ cachedOverrides = [...catalogOverrides];
157
+ cachedOverridesKey = overridesKey;
158
+ // Cache the promise, not the value, so concurrent callers share one build.
159
+ // Drop it on rejection: a failed catalog load should not be permanent.
160
+ cached = _clientDeps.build(catalogOverrides).catch((err) => {
161
+ cached = undefined;
162
+ cachedOverrides = undefined;
163
+ cachedOverridesKey = undefined;
164
+ throw err;
165
+ });
166
+ }
167
+ return cached;
168
+ }
169
+ /** Clears the memo. Tests only. */
170
+ export function _resetNativeClient() {
171
+ cached = undefined;
172
+ cachedOverrides = undefined;
173
+ cachedOverridesKey = undefined;
174
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * One-shot completion over nax-ai, with no session (S1 port 2). Package-owned
3
+ * options: the caller passes a `SessionModel` whose pricing is already in the
4
+ * standard vocabulary, and the per-adapter one-shot session key.
5
+ */
6
+ import type { AdapterFailure } from "#src/session/adapter-failure";
7
+ import type { AuthStamp, SessionModel } from "#src/session/session-types";
8
+ import type { PricingRates, TokenUsage } from "../cost/standard-types.ts";
9
+ import { type NativeCatalogOverrides } from "./client.ts";
10
+ export interface NativeCompleteOptions {
11
+ readonly model: SessionModel;
12
+ readonly maxTokens?: number;
13
+ readonly timeoutMs?: number;
14
+ }
15
+ export interface NativeCompleteContext {
16
+ readonly catalogOverrides: NativeCatalogOverrides;
17
+ /**
18
+ * The caller's one-shot session key (`newSessionKey()`), held per adapter
19
+ * instance so a run's one-shots share a backend and keep a cache warm.
20
+ */
21
+ readonly sessionKey: string;
22
+ }
23
+ export interface NativeCompleteResult {
24
+ output: string;
25
+ tokenUsage: TokenUsage;
26
+ estimatedCostUsd: number;
27
+ sessionId?: string;
28
+ pricingSource?: "catalog-rates" | "config-override";
29
+ rates?: PricingRates;
30
+ auth?: AuthStamp;
31
+ adapterFailure?: AdapterFailure;
32
+ }
33
+ export declare function nativeComplete(prompt: string, options: NativeCompleteOptions, context: NativeCompleteContext): Promise<NativeCompleteResult>;