@lamplitisles/dsh-nanocodex 0.1.0-beta.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 (276) hide show
  1. package/LICENSE +65 -0
  2. package/NOTICE +2 -0
  3. package/README.md +182 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/cordis.patch.yml +52 -0
  6. package/dist/index.d.ts +435 -0
  7. package/dist/index.js +3405 -0
  8. package/dist/standard-preset.d.ts +7 -0
  9. package/dist/standard-preset.js +25 -0
  10. package/engine-release.json +14 -0
  11. package/package.json +119 -0
  12. package/presets/standard/agent.cordis.yml +6 -0
  13. package/presets/standard/preset.yml +2 -0
  14. package/vendor/nanocodex/README.md +1344 -0
  15. package/vendor/nanocodex/actions/events.d.mts +10 -0
  16. package/vendor/nanocodex/actions/events.mjs +163 -0
  17. package/vendor/nanocodex/actions/index.d.mts +13 -0
  18. package/vendor/nanocodex/actions/index.mjs +35 -0
  19. package/vendor/nanocodex/actions/session.d.mts +66 -0
  20. package/vendor/nanocodex/actions/session.mjs +59 -0
  21. package/vendor/nanocodex/actions/turn.d.mts +59 -0
  22. package/vendor/nanocodex/actions/turn.mjs +37 -0
  23. package/vendor/nanocodex/actions/voice.d.mts +22 -0
  24. package/vendor/nanocodex/actions/voice.mjs +50 -0
  25. package/vendor/nanocodex/browser/Agent.d.mts +55 -0
  26. package/vendor/nanocodex/browser/Agent.mjs +11 -0
  27. package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
  28. package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
  29. package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
  30. package/vendor/nanocodex/browser/Transport.d.mts +74 -0
  31. package/vendor/nanocodex/browser/Transport.mjs +61 -0
  32. package/vendor/nanocodex/browser/Voice.d.mts +71 -0
  33. package/vendor/nanocodex/browser/Voice.mjs +291 -0
  34. package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
  35. package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
  36. package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
  37. package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
  38. package/vendor/nanocodex/browser/config.d.mts +36 -0
  39. package/vendor/nanocodex/browser/config.mjs +439 -0
  40. package/vendor/nanocodex/browser/engine.mjs +13 -0
  41. package/vendor/nanocodex/browser/harness.mjs +55 -0
  42. package/vendor/nanocodex/browser/host.d.mts +83 -0
  43. package/vendor/nanocodex/browser/host.mjs +555 -0
  44. package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
  45. package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
  46. package/vendor/nanocodex/browser/index.d.mts +92 -0
  47. package/vendor/nanocodex/browser/index.mjs +23 -0
  48. package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
  49. package/vendor/nanocodex/browser/workspace.d.mts +16 -0
  50. package/vendor/nanocodex/browser/workspace.mjs +107 -0
  51. package/vendor/nanocodex/cloud/Client.d.mts +83 -0
  52. package/vendor/nanocodex/cloud/Client.mjs +231 -0
  53. package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
  54. package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
  55. package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
  56. package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
  57. package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
  58. package/vendor/nanocodex/cloud/Errors.mjs +39 -0
  59. package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
  60. package/vendor/nanocodex/cloud/Principal.mjs +88 -0
  61. package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
  62. package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
  63. package/vendor/nanocodex/cloud/Transport.mjs +454 -0
  64. package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
  65. package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
  66. package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
  67. package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
  68. package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
  69. package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
  70. package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
  71. package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
  72. package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
  73. package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
  74. package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
  75. package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
  76. package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
  77. package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
  78. package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
  79. package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
  80. package/vendor/nanocodex/cloud/index.d.mts +30 -0
  81. package/vendor/nanocodex/cloud/index.mjs +9 -0
  82. package/vendor/nanocodex/cloud/internal.mjs +533 -0
  83. package/vendor/nanocodex/cloud/mercator.mjs +108 -0
  84. package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
  85. package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
  86. package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
  87. package/vendor/nanocodex/cloud/server/index.mjs +1 -0
  88. package/vendor/nanocodex/cloud/types.d.mts +211 -0
  89. package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
  90. package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
  91. package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
  92. package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
  93. package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
  94. package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
  95. package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
  96. package/vendor/nanocodex/cloudflare/index.mjs +6 -0
  97. package/vendor/nanocodex/host/Agent.d.mts +55 -0
  98. package/vendor/nanocodex/host/Agent.mjs +1 -0
  99. package/vendor/nanocodex/host/index.d.mts +82 -0
  100. package/vendor/nanocodex/host/index.mjs +10 -0
  101. package/vendor/nanocodex/index.d.mts +81 -0
  102. package/vendor/nanocodex/index.mjs +12 -0
  103. package/vendor/nanocodex/internal.mjs +1275 -0
  104. package/vendor/nanocodex/managed/Agent.d.mts +380 -0
  105. package/vendor/nanocodex/managed/Agent.mjs +1694 -0
  106. package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
  107. package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
  108. package/vendor/nanocodex/managed/README.md +241 -0
  109. package/vendor/nanocodex/managed/Voice.mjs +202 -0
  110. package/vendor/nanocodex/managed/index.d.mts +20 -0
  111. package/vendor/nanocodex/managed/index.mjs +2 -0
  112. package/vendor/nanocodex/managed/internal.mjs +119 -0
  113. package/vendor/nanocodex/node/Agent.d.mts +50 -0
  114. package/vendor/nanocodex/node/Agent.mjs +203 -0
  115. package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
  116. package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
  117. package/vendor/nanocodex/node/Transport.d.mts +44 -0
  118. package/vendor/nanocodex/node/Transport.mjs +46 -0
  119. package/vendor/nanocodex/node/host.mjs +731 -0
  120. package/vendor/nanocodex/node/index.d.mts +73 -0
  121. package/vendor/nanocodex/node/index.mjs +17 -0
  122. package/vendor/nanocodex/node/workspace.d.mts +10 -0
  123. package/vendor/nanocodex/node/workspace.mjs +198 -0
  124. package/vendor/nanocodex/package.json +200 -0
  125. package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
  126. package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
  127. package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
  128. package/vendor/nanocodex/pkg-node/package.json +1 -0
  129. package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
  130. package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
  131. package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
  132. package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
  133. package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
  134. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
  135. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
  136. package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
  137. package/vendor/nanocodex/pkg-web/package.json +1 -0
  138. package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
  139. package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
  140. package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
  141. package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
  142. package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
  143. package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
  144. package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
  145. package/vendor/nanocodex/runtime/durability.mjs +153 -0
  146. package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
  147. package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
  148. package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
  149. package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
  150. package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
  151. package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
  152. package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
  153. package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
  154. package/vendor/nanocodex/runtime/subagents.mjs +54 -0
  155. package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
  156. package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
  157. package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
  158. package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
  159. package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
  160. package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
  161. package/vendor/nanocodex/runtime/utf8.mjs +1 -0
  162. package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
  163. package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
  164. package/vendor/nanocodex/runtime/workspace.mjs +1 -0
  165. package/vendor/nanocodex/tools/Tools.d.mts +56 -0
  166. package/vendor/nanocodex/tools/Tools.mjs +136 -0
  167. package/vendor/nanocodex/tools/artifact.d.mts +1 -0
  168. package/vendor/nanocodex/tools/artifact.mjs +1 -0
  169. package/vendor/nanocodex/tools/attachment.mjs +1 -0
  170. package/vendor/nanocodex/tools/bash.d.mts +1 -0
  171. package/vendor/nanocodex/tools/bash.mjs +1 -0
  172. package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
  173. package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
  174. package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
  175. package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
  176. package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
  177. package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
  178. package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
  179. package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
  180. package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
  181. package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
  182. package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
  183. package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
  184. package/vendor/nanocodex/tools/browser/index.mjs +174 -0
  185. package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
  186. package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
  187. package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
  188. package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
  189. package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
  190. package/vendor/nanocodex/tools/dataset.d.mts +1 -0
  191. package/vendor/nanocodex/tools/dataset.mjs +1 -0
  192. package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
  193. package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
  194. package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
  195. package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
  196. package/vendor/nanocodex/tools/index.d.mts +49 -0
  197. package/vendor/nanocodex/tools/index.mjs +19 -0
  198. package/vendor/nanocodex/tools/namedTool.mjs +1 -0
  199. package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
  200. package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
  201. package/vendor/nanocodex/tools/ssh.d.mts +1 -0
  202. package/vendor/nanocodex/tools/ssh.mjs +1 -0
  203. package/vendor/nanocodex/tools/standard.mjs +1 -0
  204. package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
  205. package/vendor/nanocodex/types.d.mts +663 -0
  206. package/vendor/nanocodex/wasm.d.mts +4 -0
  207. package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
  208. package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
  209. package/vendor/nanocodex/worker/index.d.mts +18 -0
  210. package/vendor/nanocodex/worker/index.mjs +5 -0
  211. package/vendor/nanocodex-tools/README.md +65 -0
  212. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
  213. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
  214. package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
  215. package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
  216. package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
  217. package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
  218. package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
  219. package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
  220. package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
  221. package/vendor/nanocodex-tools/dist/index.js +14 -0
  222. package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
  223. package/vendor/nanocodex-tools/dist/memory.js +411 -0
  224. package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
  225. package/vendor/nanocodex-tools/dist/namespace.js +338 -0
  226. package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
  227. package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
  228. package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
  229. package/vendor/nanocodex-tools/dist/runtime.js +37 -0
  230. package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
  231. package/vendor/nanocodex-tools/dist/session.js +286 -0
  232. package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
  233. package/vendor/nanocodex-tools/dist/shell.js +553 -0
  234. package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
  235. package/vendor/nanocodex-tools/dist/ssh.js +66 -0
  236. package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
  237. package/vendor/nanocodex-tools/dist/workspace.js +237 -0
  238. package/vendor/nanocodex-tools/package.json +122 -0
  239. package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
  240. package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
  241. package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
  242. package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
  243. package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
  244. package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
  245. package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
  246. package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
  247. package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
  248. package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
  249. package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
  250. package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
  251. package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
  252. package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
  253. package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
  254. package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
  255. package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
  256. package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
  257. package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
  258. package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
  259. package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
  260. package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
  261. package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
  262. package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
  263. package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
  264. package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
  265. package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
  266. package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
  267. package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
  268. package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
  269. package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
  270. package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
  271. package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
  272. package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
  273. package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
  274. package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
  275. package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
  276. package/vendor/nanocodex-tools/tools/x.mjs +91 -0
@@ -0,0 +1,1344 @@
1
+ # Nanocodex for JavaScript
2
+
3
+ The Node, browser, and Web API host entrypoints expose the same viem-v3-style
4
+ API. A `Transport` owns authentication, placement, and socket setup;
5
+ `Agent.create(...)` owns tools and the common Agent/Turn lifecycle. Generated
6
+ WASM handles, managed control-plane handles, and host routing remain private.
7
+
8
+ ```js
9
+ import { Actions, Agent, Transport } from "nanocodex/node";
10
+
11
+ const agent = await Agent.create({
12
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
13
+ model: "gpt-5.6-luna",
14
+ instructions: "You are a Rust coding agent. Preserve unrelated work and run relevant tests.",
15
+ reasoningMode: "pro",
16
+ thinking: "high",
17
+ tools,
18
+ workspace: process.cwd(),
19
+ });
20
+
21
+ const turn = agent.turn.prompt({ input: "Build the thing." });
22
+ const result = await turn.result();
23
+ turn.dispose();
24
+ console.log(result.finalMessage);
25
+ const usage = await result.usage();
26
+ console.log(usage);
27
+ console.log(usage.estimated_cost?.usd);
28
+ console.log(usage.cost_status);
29
+
30
+ await agent.session.setThinking("high");
31
+ await agent.session.setFastMode(true);
32
+ const compaction = await agent.session.compact();
33
+ if (compaction) console.log(compaction.summary, compaction.retained_tail);
34
+
35
+ const branch = await agent.session.fork({ at: result });
36
+ const branchTurn = branch.turn.prompt({ input: "Try another approach." });
37
+ const branchResult = await branchTurn.result();
38
+ branchTurn.dispose();
39
+ console.log(branchResult.finalMessage);
40
+ branchResult.dispose();
41
+
42
+ const followOn = Actions.turn.prompt(agent, { input: "Now explain it." });
43
+ const followResult = await Actions.turn.getResult(followOn);
44
+ console.log(followResult.finalMessage);
45
+ followOn.dispose();
46
+ followResult.dispose();
47
+ result.dispose();
48
+ await branch.session.shutdown();
49
+ await agent.session.shutdown();
50
+ ```
51
+
52
+ ### Host-owned compaction
53
+
54
+ The Node and current-isolate Web API WASM hosts can supply a host-owned compaction instruction
55
+ while Nanocodex continues to own the model loop, tools, active context,
56
+ compaction, and engine checkpoints. The existing `instructions` option sets
57
+ the model persona; `companionCompactionInstruction` remains the fixed
58
+ host-supplied instruction for client-owned compaction:
59
+
60
+ ```js
61
+ const agent = await Agent.create({
62
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
63
+ instructions: hostPersona,
64
+ companionCompactionInstruction: "Preserve relationship facts and recent work.",
65
+ historySeed: {
66
+ history: [
67
+ {
68
+ type: "message",
69
+ role: "user",
70
+ content: [{ type: "input_text", text: "Remember the blue room." }],
71
+ },
72
+ {
73
+ type: "message",
74
+ role: "assistant",
75
+ content: [{ type: "output_text", text: "I will remember it." }],
76
+ status: "completed",
77
+ },
78
+ ],
79
+ continuitySummary: "The conversation concerns the blue room.",
80
+ },
81
+ });
82
+
83
+ const turn = agent.turn.prompt({
84
+ input: "What room did I ask you to remember?",
85
+ supplementaryContext: "<recall>Selected host-owned evidence.</recall>",
86
+ });
87
+ const result = await turn.result();
88
+ ```
89
+
90
+ `supplementaryContext` is host-resolved evidence appended to the same final
91
+ user message as `input`. It neither replaces persona instructions nor starts a
92
+ turn by itself, and it remains associated with its prompt while independently
93
+ queued inputs wait behind an active turn. The host owns recall, timeouts, and
94
+ its application transcript; Nanocodex only validates and carries the supplied
95
+ string.
96
+
97
+ `historySeed` is a validated active-history entry point, not a DSH event-log
98
+ schema. Its public `HistoryItem` union accepts message text and supported
99
+ image/audio content, function and Code Mode tool call/result pairs, and
100
+ provider compaction items. `continuitySummary` is inserted as private model
101
+ context. Unknown or malformed items, an empty history without a user message,
102
+ an empty summary, and simultaneous `historySeed` plus `resume` are rejected
103
+ before provider work. Historical tool calls are replayed as history and are
104
+ never executed. The engine creates the lineage, cache key, and snapshot
105
+ metadata; callers do not fabricate provider continuation IDs.
106
+
107
+ When `companionCompactionInstruction` is configured, summaries are normal
108
+ generations using the complete active request prefix: instructions, tools,
109
+ model settings, prompt-cache key, and the current typed history are retained.
110
+ The final instruction is appended at the end of that request. Eligible
111
+ Responses Lite continuation and the normal full-replay fallback remain in use;
112
+ the next request after installation intentionally performs a fresh replay, so
113
+ these client-side tests do not claim a provider cache hit.
114
+
115
+ For per-operation policy, provide `resolveCompactionInstruction` instead. It
116
+ is awaited before every manual, pressure, mid-tool, or context-overflow
117
+ summary request and receives a small context plus an `AbortSignal`:
118
+
119
+ ```js
120
+ const agent = await Agent.create({
121
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
122
+ resolveCompactionInstruction: async (context, signal) => {
123
+ const policy = context.trigger === "manual"
124
+ ? "Keep the user's selected facts and the latest work."
125
+ : "Keep the facts required to resume the active operation.";
126
+ signal.throwIfAborted?.();
127
+ return policy;
128
+ },
129
+ });
130
+ ```
131
+
132
+ The resolver result must be a non-empty string. Resolver failure or
133
+ cancellation does not fall back to `companionCompactionInstruction` and does
134
+ not start a provider request. Shutting down an Agent aborts a pending resolver;
135
+ an active summary is also canceled without installing a partial result.
136
+
137
+ A successful summary replaces the superseded history with private summary
138
+ context and the latest complete real-user-led tail. It never emits the
139
+ summary as assistant output, and historical or summary tool calls are never
140
+ dispatched. Custom host compaction returns a `CompactionOutcome` with the
141
+ ordered `revision`, `trigger`, private `summary`, `replaced_history` range,
142
+ `retained_tail` item identities, and complete post-replacement `context`.
143
+ Automatic custom replacements additionally emit an ordered
144
+ `model.compaction.replaced` event at the installation boundary; its payload
145
+ has the same outcome fields plus `phase` and `after_model_call_index`. Manual
146
+ custom compaction emits the same event after installation. Call explicit
147
+ compaction from idle maintenance because it may cancel an active turn.
148
+ When neither host-owned option is configured, provider-default compaction is
149
+ unchanged and `agent.session.compact()` returns `null`; no custom replacement
150
+ event is emitted.
151
+
152
+ After a successful manual replacement, call `agent.session.snapshot()` before
153
+ disposing the Agent when the host needs to persist that exact boundary. The
154
+ returned `SessionSnapshot` is produced by the engine's committed checkpoint,
155
+ so it includes the installed summary and complete retained tool tail, along
156
+ with the lineage and cache metadata required for a cold `resume`. It rejects
157
+ before the first committed boundary and after the Agent has stopped; callers
158
+ must treat its history and tool payloads as sensitive model state.
159
+
160
+ `resolveCompactionInstruction` is supported in `nanocodex/node` and in the
161
+ current-isolate `nanocodex/host` API. The package-owned `nanocodex/browser`
162
+ module Worker accepts only structured-clone-safe options, so its types omit
163
+ the resolver and runtime rejects a function-valued option. No browser Worker
164
+ RPC resolver is provided.
165
+
166
+ Host-owned checkpoints use the existing `durability`/`durabilityId` options.
167
+ After a completed boundary is stored, a fresh Agent instance can reopen it and
168
+ replay the summary, retained history, and completed tool pairs before accepting
169
+ the next prompt. This is a checkpoint contract, not a claim of arbitrary-crash
170
+ exactly-once semantics. DSH-specific translation, UI lifecycle mapping,
171
+ recall policy, transcript persistence, and the official DSH 0.1.2-rc.1
172
+ recovery-baseline verification remain the responsibility of the dependent DSH
173
+ adapter.
174
+
175
+ Transports are explicit, immutable configurations, like viem v3 transports:
176
+
177
+ ```js
178
+ Transport.openAi({ apiKey, websocketUrl });
179
+ Transport.chatGpt({ subscription });
180
+ Transport.mpp({ session: paymentSession });
181
+ Transport.managed({ agent: { create: true } });
182
+ Transport.managed({ agent: { id: retainedAgentId } });
183
+ ```
184
+
185
+ For `nanocodex/node`, `Transport.openAi` and `Transport.chatGpt` prefer the
186
+ Responses WebSocket. Set `apiBaseUrl` to the matching HTTPS `/v1` base when
187
+ the WebSocket endpoint is not guaranteed to be reachable. A failed upgrade or
188
+ other eligible transport failure before response output begins is replayed
189
+ once over that endpoint as an incremental `text/event-stream` request. The
190
+ Node host caps WebSocket establishment and HTTP response headers at 15 seconds,
191
+ owns the fetch reader and bearer headers, and keeps the selected HTTPS
192
+ transport for the rest of that live Agent session. The request body, session
193
+ ID, thread ID, tool results, and authoritative history remain engine-owned;
194
+ the host never needs to resubmit a turn after output has started.
195
+
196
+ Fallback is deliberately not used for authentication, model/request
197
+ validation, caller cancellation, or a failure after an assistant delta,
198
+ response item, or tool execution has begun. The browser/current-isolate and
199
+ MPP hosts do not advertise this Node-only host capability. When fallback is
200
+ selected, `agent.events.watch()` receives one sanitized
201
+ `model.attempt.retrying` event whose payload includes:
202
+ `previous_transport: "responses_websocket_v2"`,
203
+ `next_transport: "responses_https_sse"`, and a low-cardinality `reason` such
204
+ as `"upgrade_required"` or `"transport_unavailable"`; it contains no bearer
205
+ token, headers, prompt, or request body.
206
+
207
+ Managed identity is always explicit. `{ create: true }` provisions one new
208
+ account-owned durable Agent; `{ id }` eagerly verifies and opens that existing
209
+ Agent. Omitting `agent` never creates a durable resource. Both return the same
210
+ `sessionId`, `events.watch()`, `turn.prompt()` / Turn, `dispose()`, and
211
+ `session.shutdown()` lifecycle used by local transports. Managed shutdown
212
+ closes this client and any reverse tool attachment; it does not delete the
213
+ durable Agent.
214
+
215
+ Choose the entrypoint by execution owner:
216
+
217
+ - `nanocodex/browser` creates and owns a package module Worker. Its options are
218
+ structured-clone-safe and its default harness includes the browser workspace.
219
+ - `nanocodex/host` runs in the current Web API isolate. Use it inside a
220
+ caller-owned browser Worker, Cloudflare Worker, Vercel Function, or similar
221
+ host when transports, tools, filesystems, or durability contain functions.
222
+ - `nanocodex/node` runs in the current Node process with Node host adapters.
223
+
224
+ The browser transports additionally expose `Transport.hostManaged(...)` for a
225
+ Worker, Durable Object, or application proxy that owns rotating credentials.
226
+ Authentication modes are constructors rather than a union of mutually
227
+ exclusive fields on `Agent.create`.
228
+
229
+ ### Compose and place tools
230
+
231
+ `createTools` owns one deterministic tool recipe. Custom functions, a portable
232
+ workspace, and MCP are composed once; placement is selected afterward. Pass the
233
+ recipe to an in-process Node or Web API host, or reverse-attach it to a managed
234
+ agent target:
235
+
236
+ For a reverse machine attachment, `attachmentId` is its stable safe-ASCII source
237
+ identity (at most 123 bytes), and must equal the `id` of its sole non-secret
238
+ `machines` entry. Multiple machines may stay attached through independent
239
+ `Tools` runtimes; reconnect one runtime to replace that machine route while the
240
+ durable managed agent stays alive. Generic attachments may omit machine metadata.
241
+
242
+ ```js
243
+ import { createTools } from "nanocodex";
244
+ import { Agent, Transport, Workspace } from "nanocodex/node";
245
+ import WebSocket from "ws";
246
+
247
+ const workspace = await Workspace.open({ path: process.cwd() });
248
+ const tools = await createTools({
249
+ attachmentId: "laptop",
250
+ machines: [{
251
+ id: "laptop",
252
+ name: "My laptop",
253
+ workspace: process.cwd(),
254
+ capabilities: ["filesystem", "native-shell"],
255
+ }],
256
+ workspace,
257
+ tools: {
258
+ lookup_issue: {
259
+ description: "Read one issue from the application database.",
260
+ parameters: {
261
+ type: "object",
262
+ properties: { id: { type: "string" } },
263
+ required: ["id"],
264
+ additionalProperties: false,
265
+ },
266
+ handler: ({ id }) => issues.get(id),
267
+ },
268
+ },
269
+ mcp: {
270
+ docs: { url: "https://mcp.example.test" },
271
+ },
272
+ });
273
+
274
+ const agent = await Agent.create({
275
+ transport: Transport.managed({
276
+ agent: { id: agentId },
277
+ baseUrl: managedOrigin,
278
+ apiKey,
279
+ toolsTransport: (target, options) => new WebSocket(target, {
280
+ headers: options.headers,
281
+ }),
282
+ }),
283
+ tools,
284
+ });
285
+
286
+ // On shutdown:
287
+ await agent.session.shutdown();
288
+ ```
289
+
290
+ The managed target retains credentials in a private transport closure; the API
291
+ key is not embedded in the endpoint or serializable target data. While the
292
+ attachment is live, an exact same-name attached tool wins over the cloud tool.
293
+ After detach, the cloud definition is immediately eligible again. Definition
294
+ parity is validated before the attached catalog becomes active, and calls
295
+ already admitted retain their pinned placement.
296
+
297
+ `Tools` has one Agent owner and owns the lifecycle of its MCP runtime and
298
+ reverse attachments. Local transports host the recipe in process; a managed
299
+ transport starts a bounded reverse-attachment supervisor while the durable
300
+ Agent remains available through its cloud tools. A successful catalog
301
+ acknowledgement upgrades later admissions to the attached placement. A second
302
+ Agent host rejects the same value. Do not also supply legacy top-level
303
+ workspace or MCP configuration to an Agent that already receives them through
304
+ `Tools`.
305
+
306
+ Browser consumers can attach Codex's ChatGPT Realtime voice lifecycle to the
307
+ same retained Agent. The resource owns microphone, speaker, WebRTC, sideband,
308
+ and delegation cleanup; stopping voice does not cancel an active coding turn.
309
+ Snapshots update each speaker's transcript row as speech arrives, using a stable
310
+ `id` and `isPartial` flag. Completion replaces that row. `transcript.delta` events
311
+ carry the current partial text; `transcript` events retain completed-turn semantics.
312
+ Internal Realtime envelopes are projected into spoken text before publication.
313
+ Transcript updates continue while a delegation waits for durable admission.
314
+
315
+ The one-operation-at-a-time action surface is the canonical imperative API:
316
+
317
+ ```js
318
+ import { Actions } from "nanocodex/browser";
319
+
320
+ const voice = Actions.voice.create(agent);
321
+
322
+ await Actions.voice.start(voice); // defaults to Codex's `cove` voice
323
+ await Actions.voice.stop(voice);
324
+ await Actions.voice.destroy(voice);
325
+ ```
326
+
327
+ Subscription voice preferences use the same Rust policy in browsers and native
328
+ apps. `start` and `create` accept `voice`, `instructions`, `pace` (`slow`,
329
+ `natural`, `fast`), `updates` (`auto`, `results`, `silent`), and optional
330
+ `acknowledgements`. Pace and style are speaking instructions. Update preferences
331
+ also select how coding-agent commentary and results reach the voice model.
332
+ Advanced consumers can set `handoffMode` to `thinking`, `commentary`, or
333
+ `bem_tags`; an explicit `updates` preference takes precedence. Apply changed
334
+ settings by stopping and starting a call. The shared terminal provides a saved
335
+ Voice settings panel with an Apply and reconnect action.
336
+
337
+ During an active call, `Actions.voice.speak(voice, text)` queues explicit speech,
338
+ `appendText(voice, text, { role: "developer" })` adds text using Codex's
339
+ subscription adapter (which treats all roles as context), and
340
+ `appendContext(voice, text)` adds background commentary without
341
+ requesting speech. Context and speech are split into provider-sized messages.
342
+ These commands retain frames until sent and preserve them
343
+ across a sideband reconnect. They are also methods on the resource and on
344
+ `useVoice` from `nanocodex-react`. These settings use ChatGPT subscription voice;
345
+ custom voices and Platform audio configuration are not accepted.
346
+
347
+ `Voice.create(...)` remains the equivalent namespaced resource constructor, and
348
+ `Voice.voices` is the exact ChatGPT V3 voice catalog. The constructor accepts a
349
+ normal browser Agent, an account-owned managed Agent, or a grant-scoped
350
+ `ConnectAgent`. Authentication stays in the owning host routes; Connect uses a
351
+ fresh one-use sideband ticket, and the browser binding never receives ChatGPT
352
+ credentials or places its reusable grant bearer in a WebSocket URL.
353
+
354
+ ### Durable Cloudflare Agent
355
+
356
+ `nanocodex/cloudflare` is the standard Durable Object consumer. It keeps the
357
+ host transport, SQLite durable state, private runtime identity, event persistence,
358
+ hibernatable socket fan-out, and cursor replay inside the adapter:
359
+
360
+ ```js
361
+ import { DurableObject } from "cloudflare:workers";
362
+ import { Agent } from "nanocodex/cloudflare";
363
+
364
+ export class CodingAgent extends DurableObject {
365
+ #ready;
366
+
367
+ constructor(context, env) {
368
+ super(context, env);
369
+ this.#ready = Agent.create(this, {
370
+ instructions: "You are a focused coding agent.",
371
+ });
372
+ }
373
+
374
+ async prompt(input) {
375
+ const agent = await this.#ready;
376
+ const turn = agent.turn.prompt({ input });
377
+ let result;
378
+ try {
379
+ result = await turn.result();
380
+ return result.finalMessage;
381
+ } finally {
382
+ try {
383
+ result?.dispose();
384
+ } finally {
385
+ turn.dispose();
386
+ }
387
+ }
388
+ }
389
+
390
+ async fetch(request) {
391
+ return (await this.#ready).events.connect(request);
392
+ }
393
+ }
394
+ ```
395
+
396
+ The returned value is the normal typed Agent: follow-on prompts reuse its owned
397
+ history, and results remain independently awaitable. `events.connect(request)`
398
+ is only a read-only AgentEvent WebSocket surface; it does not define prompt,
399
+ membership, room, quota, or application routing policy. Event frames are
400
+ `{ cursor, event }`. Replay is bounded; a far-behind client can receive
401
+ `{ type: "replay_paused", cursor, latest_cursor }` followed by close code
402
+ `1013`, then continues by reconnecting with that pause cursor as
403
+ `?cursor=<decimal>`.
404
+
405
+ Cloudflare Agents default to direct tool mode because Workers prohibit dynamic
406
+ `eval`/`new Function`. Caller-defined tools therefore work without a code
407
+ evaluator. Select `toolMode: "code"` only when also supplying an evaluator that
408
+ is explicitly compatible with the deployed Worker runtime. Runtime-owned
409
+ Subagents are installed by default, including on a durable root. Clean children
410
+ persist independent execution state under their own agent session IDs. The
411
+ Rust task-tree registry remains in memory and is closed with the live root, so
412
+ tree-local IDs and topology are not reconstructed from those agent states. Use
413
+ `Subagents.create({ maxConcurrency })` in `tools` to set an explicit finite
414
+ concurrency limit. Active subagent turns are unlimited by default.
415
+
416
+ Each Durable Object persists a private runtime identity in its own SQLite
417
+ storage and derives its state identity from it, so multiple objects in one
418
+ isolate remain independent and eviction reuses the same identity. Before
419
+ replacing an Agent inside a still-live object, await `agent.session.shutdown()`;
420
+ deleting the Durable Object and its retained event/state rows remains an
421
+ application-owned lifecycle operation.
422
+
423
+ Internally this constructor uses `Transport.hostManaged` and an exact brokered
424
+ Responses WebSocket. `authMode` is required and accepts only `"api_key"` or
425
+ `"chatgpt"`; URLs and non-secret placeholders are fixed. `Agent.create` awaits
426
+ the private binding's WebSocket upgrade, so a missing binding or a broker whose
427
+ single policy does not match the selected mode rejects startup. The managed
428
+ Worker API deliberately has no provider-key, token, transport, or durability
429
+ option.
430
+
431
+ The managed Worker needs only the Durable Object and private broker bindings;
432
+ the broker's separate Wrangler configuration owns the real provider secret:
433
+
434
+ ```jsonc
435
+ {
436
+ "services": [{ "binding": "EGRESS", "service": "my-private-egress-broker" }],
437
+ "durable_objects": {
438
+ "bindings": [{ "name": "AGENTS", "class_name": "CodingAgent" }]
439
+ },
440
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["CodingAgent"] }],
441
+ "vars": { "NANOCODEX_AUTH_MODE": "chatgpt" }
442
+ }
443
+ ```
444
+
445
+ Do not put `OPENAI_API_KEY`, OAuth material, account IDs, or relay capabilities
446
+ in this managed Worker configuration. A private Service Binding is a
447
+ controlled-code boundary, so the separately deployed broker must still enforce
448
+ one exact destination, one matching credential policy, placeholder replacement,
449
+ header allowlisting, and no public route.
450
+
451
+ Task-tree orchestration is an optional extension over the core agent. Both
452
+ native and WASM consumers run the same Rust implementation and receive the
453
+ same seven tools: `spawn_agent`, `submit_result`, `send_agent_message`,
454
+ `list_agents`, `wait_agent`, `interrupt_agent`, and `close_agent`.
455
+
456
+ Inside a caller-owned Worker or server isolate, host capabilities stay as
457
+ ordinary functions without crossing another compatibility protocol:
458
+
459
+ ```js
460
+ import { Agent, Transport } from "nanocodex/host";
461
+ import nanocodexWasm from "./nanocodex.wasm";
462
+
463
+ const myApplicationTool = {
464
+ name: "lookup_order",
465
+ description: "Look up one order.",
466
+ parameters: {
467
+ type: "object",
468
+ properties: { id: { type: "string" } },
469
+ required: ["id"],
470
+ additionalProperties: false,
471
+ },
472
+ handler: ({ id }) => orders.get(id),
473
+ };
474
+
475
+ const agent = await Agent.create({
476
+ module: nanocodexWasm,
477
+ transport: Transport.hostManaged({
478
+ websocketUrl: "/api/responses",
479
+ createWebSocket: (endpoint) => new WebSocket(endpoint),
480
+ }),
481
+ tools: [myApplicationTool],
482
+ });
483
+ ```
484
+
485
+ `parameters` is optional and defaults to an open object. TypeScript types are
486
+ erased at runtime, so provide JSON Schema only when the model needs a precise
487
+ argument contract, as `lookup_order` does above.
488
+
489
+ For provider-native free-form input, set `definition` on the same application
490
+ tool. The router replaces `definition.name` with the containing map key (or
491
+ the `NamedTool.name`), so the host does not need a second tool-registration
492
+ path. A custom definition receives the exact model string in its handler and
493
+ the normal `ToolContext` identity and cancellation signal:
494
+
495
+ ```js
496
+ const applyPatch = {
497
+ description: "Apply one patch through the host-owned workspace.",
498
+ definition: {
499
+ type: "custom",
500
+ description: "Apply one patch through the host-owned workspace.",
501
+ format: {
502
+ type: "grammar",
503
+ syntax: "lark",
504
+ definition: 'start: "patch"', // replace with the host's complete grammar
505
+ },
506
+ },
507
+ async handler(input, { callId, sessionId, signal }) {
508
+ if (typeof input !== "string") throw new TypeError("raw patch input required");
509
+ return applyPatchInHost(input, { callId, sessionId, signal });
510
+ },
511
+ };
512
+
513
+ const agent = await Agent.create({
514
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
515
+ tools: { apply_patch: applyPatch },
516
+ subagents: false,
517
+ });
518
+ ```
519
+
520
+ `type: "custom"` is a direct Responses custom tool, so its handler input is a
521
+ string rather than parsed JSON. Completed custom calls and outputs remain
522
+ engine-owned history: snapshots and cold `resume` replay them as model context
523
+ without invoking the handler again. Node agents enable built-in subagents by
524
+ default; `subagents: false` removes those built-in tools while retaining the
525
+ application tools above. Passing `Subagents.create()` together with `false`
526
+ is rejected.
527
+
528
+ ## Standard web and browser tools
529
+
530
+ `nanocodex/tools` contains composable named tools rather than another agent or
531
+ runtime. Each factory returns an entry that can sit beside application tools
532
+ and Rust/WASM extensions in the same array:
533
+
534
+ ```js
535
+ import { Agent, Transport } from "nanocodex/host";
536
+ import {
537
+ dataset,
538
+ imageGeneration,
539
+ updatePlan,
540
+ web,
541
+ } from "nanocodex/tools";
542
+
543
+ const agent = await Agent.create({
544
+ transport: Transport.hostManaged({
545
+ websocketUrl: "/api/responses",
546
+ createWebSocket: (endpoint) => new WebSocket(endpoint),
547
+ }),
548
+ tools: [
549
+ web(),
550
+ dataset(),
551
+ imageGeneration({
552
+ recentImages: (sessionId, count) => images.get(sessionId).slice(-count),
553
+ rememberImage: (sessionId, imageUrl) => images.get(sessionId).push(imageUrl),
554
+ }),
555
+ updatePlan(),
556
+ myApplicationTool,
557
+ ],
558
+ });
559
+ ```
560
+
561
+ The web and image factories use the canonical OpenAI/Codex tool names, argument
562
+ schemas, bounds, and image-edit modes, and normalize common malformed model
563
+ arguments before dispatch. In a browser, they default to the same-origin
564
+ `/api/tools/web-search` and `/api/tools/image-generation` routes. The host owns
565
+ only a bounded JSON endpoint, credentials, authorization, and persistence.
566
+ `web(...)` posts `{ commands, session_id, model }`, where `model` is the
567
+ effective model of the invoking root or subagent; `imageGeneration(...)` posts
568
+ `{ images, prompt }`. The host owns model authorization and may ignore or
569
+ override this value. Pass `url` when the host route lives elsewhere.
570
+
571
+ `dataset()` runs entirely in the caller and inspects public HTTPS Parquet,
572
+ uncompressed JSONL, and Hugging Face datasets. It opens a session-scoped handle,
573
+ returns schema metadata, and supports projection and filtering queries without
574
+ hard row or offset ceilings. Input and output bytes remain bounded; partial
575
+ results return an opaque `nextCursor` that retains the query and resumes from a
576
+ physical Parquet row batch or JSONL byte position. Parquet uses HTTP range reads
577
+ and predicate pushdown where possible; JSONL scans incrementally and requires
578
+ byte-range support for cursor continuation. The implementation, Parquet reader,
579
+ and non-Snappy codecs load only after the model first calls the tool. Direct URLs
580
+ must allow browser CORS, and Parquet servers must support byte ranges.
581
+ Consumers that only need this capability can import `dataset` from the smaller
582
+ `nanocodex/tools/dataset` leaf entry.
583
+
584
+ ```js
585
+ const datasets = dataset();
586
+ const opened = await datasets.handler({
587
+ operation: "open",
588
+ source: {
589
+ kind: "huggingface",
590
+ dataset: "openai/gsm8k",
591
+ config: "main",
592
+ split: "train",
593
+ },
594
+ }, { sessionId: "thread-1" });
595
+
596
+ const page = await datasets.handler({
597
+ operation: "query",
598
+ dataset_id: opened.datasetId,
599
+ columns: ["question", "answer"],
600
+ filters: [{ column: "question", op: "contains", value: "how many" }],
601
+ limit: 5,
602
+ }, { sessionId: "thread-1" });
603
+
604
+ if (page.nextCursor) {
605
+ await datasets.handler({
606
+ operation: "query",
607
+ dataset_id: opened.datasetId,
608
+ cursor: page.nextCursor,
609
+ limit: 5,
610
+ }, { sessionId: "thread-1" });
611
+ }
612
+ ```
613
+
614
+ This same adapter works inside a Cloudflare Worker or Durable Object:
615
+
616
+ ```js
617
+ import { Agent, Transport } from "nanocodex/host";
618
+ import { web } from "nanocodex/tools";
619
+
620
+ const agent = await Agent.create({
621
+ module: env.NANOCODEX_WASM,
622
+ transport: Transport.hostManaged({
623
+ websocketUrl: env.RESPONSES_WEBSOCKET_URL,
624
+ createWebSocket: (endpoint) => new WebSocket(endpoint),
625
+ }),
626
+ toolMode: "direct",
627
+ tools: [
628
+ web({
629
+ url: env.WEB_TOOL_URL,
630
+ headers: { authorization: `Bearer ${env.WEB_TOOL_TOKEN}` },
631
+ }),
632
+ ],
633
+ });
634
+ ```
635
+
636
+ For a caller-owned browser Worker, `browser(...)` composes the same tools with
637
+ one persistent OPFS workspace and a lazy WASM-backed shell (Python through
638
+ Pyodide, C/C++ through wasm-clang, plus browser Git and bounded commands):
639
+
640
+ ```js
641
+ import { Agent } from "nanocodex/host";
642
+ import { browser } from "nanocodex/tools/browser";
643
+
644
+ const runtime = await browser({
645
+ threadId,
646
+ recentImages,
647
+ rememberImage,
648
+ });
649
+
650
+ const agent = await Agent.create({
651
+ transport,
652
+ filesystem: runtime.filesystem,
653
+ instructions: runtime.instructions,
654
+ executionEnvironment: {
655
+ currentDate,
656
+ timezone,
657
+ projectInstructions: runtime.projectInstructions,
658
+ },
659
+ tools: runtime.tools,
660
+ });
661
+ ```
662
+
663
+ `browser(...)` runs in a browser Worker because OPFS is a browser capability;
664
+ use the individual factories in server-side Cloudflare Workers. Vite integration
665
+ is provided separately by `nanocodex-vite`.
666
+
667
+ The browser composition includes native `browseX` public X browsing, advertised
668
+ by `accountInfo().apis` without an X connector. The embedding app serves
669
+ `/api/tools/x/browse` and `/api/tools/x/convert`; Nanocodex's account app forwards
670
+ these requests to the private X Worker.
671
+
672
+ The browser composition includes `render_artifact` as a normal typed tool. For
673
+ other hosts, compose the same factory with any workspace implementing the
674
+ Nanocodex workspace contract:
675
+
676
+ ```js
677
+ import { artifact, web } from "nanocodex/tools";
678
+
679
+ const tools = [
680
+ web({ url: env.WEB_TOOL_URL }),
681
+ artifact({ workspace }),
682
+ ];
683
+ ```
684
+
685
+ The artifact factory performs no dynamic evaluation and is safe to load in a
686
+ Cloudflare Worker. Browser hosts additionally install the exact iframe syntax
687
+ validator. The model calls `tools.render_artifact({ id, title, source })` from
688
+ Code Mode, or `render_artifact` directly when the host selects direct mode; no
689
+ artifact CLI is installed. Artifact capacity is host-owned: the binding adds no
690
+ byte, source-length, ID-length, or document-count policy limits.
691
+
692
+ Application tools may provide `outputSchema` alongside `parameters`. The
693
+ binding serializes it to Rust's `output_schema`, so Code Mode receives the same
694
+ generated TypeScript return declaration as native Codex tools instead of
695
+ guessing result fields:
696
+
697
+ ```js
698
+ const execCommand = {
699
+ name: "exec_command",
700
+ description: "Run a command.",
701
+ parameters: { type: "object", properties: { cmd: { type: "string" } }, required: ["cmd"] },
702
+ outputSchema: {
703
+ type: "object",
704
+ properties: { output: { type: "string" }, wall_time_seconds: { type: "number" } },
705
+ required: ["output", "wall_time_seconds"],
706
+ additionalProperties: false,
707
+ },
708
+ handler: runCommand,
709
+ };
710
+ ```
711
+
712
+ This is what loading a Rust-written tool from JavaScript looks like here.
713
+ `nanocodex-subagents` is statically linked into `nanocodex.wasm`; every JS
714
+ `Agent.create(...)` installs it by default. Spreading `Subagents.create()` into
715
+ `tools` overrides its maximum concurrency and contributes one opaque extension
716
+ entry, not seven JavaScript handlers. Inside the binding, Rust creates one
717
+ shared registry and installs fresh tools for every root, spawn, and fork:
718
+
719
+ ```rust,ignore
720
+ let (registry, control, updates) = nanocodex_subagents::channel(max_concurrency);
721
+ let tools = Tools::builder().without_defaults().build()?;
722
+ let tools = nanocodex_tools::embedded::bind_host(tools, javascript_host);
723
+ let (agent, events) = Nanocodex::builder(openai)
724
+ .tools_factory(move |handle| {
725
+ nanocodex_subagents::install_tools(tools.clone(), handle, registry.clone())
726
+ })
727
+ .build()?;
728
+ ```
729
+
730
+ This is deliberately static composition, not a generic runtime loader for an
731
+ arbitrary second `.wasm` plugin. A custom Rust extension is linked into the
732
+ binding crate at build time and exposed by a small branded JS configuration;
733
+ adding a dynamic component ABI would be a separate feature with a much larger
734
+ contract and runtime cost.
735
+
736
+ The root owns the task tree. `agent.session.shutdown()` closes every child
737
+ before stopping the root driver; applications do not maintain a parallel JS
738
+ scheduler or reimplement the communication tools.
739
+
740
+ ## Persistent workspaces
741
+
742
+ Runtime-specific `Workspace` adapters give an embedding application one file
743
+ contract for both local browser kernels and Node kernels. The browser adapter
744
+ uses the origin-private file system (OPFS), so reopening the same stable name
745
+ after a Worker, page, or agent-session restart reuses its files. The Node
746
+ adapter roots the same operations in an ordinary directory and refuses path
747
+ traversal and symbolic-link escapes.
748
+
749
+ ```js
750
+ import { Workspace } from "nanocodex/browser/workspace";
751
+ import { Agent, Transport } from "nanocodex/host";
752
+
753
+ const workspace = await Workspace.open({ name: "my-notebook" });
754
+ const agent = await Agent.create({
755
+ transport: Transport.hostManaged({
756
+ websocketUrl: "/api/responses",
757
+ createWebSocket: (endpoint) => new WebSocket(endpoint),
758
+ }),
759
+ filesystem: workspace,
760
+ });
761
+
762
+ await workspace.writeFile("README.md", "# Durable browser workspace\n");
763
+ console.log(await workspace.list(".", { recursive: true }));
764
+ ```
765
+
766
+ The returned handle is application-owned and remains usable by a file browser,
767
+ editor, upload/download surface, or another agent session. `Workspace.tools`
768
+ exposes bounded `list_files`, `read_file`, `write_file`, `make_directory`, and
769
+ `delete_file` operations through the normal caller-defined tool boundary. It
770
+ does not add a fake browser shell.
771
+
772
+ Node uses the same shape with a real directory:
773
+
774
+ ```js
775
+ import { Agent, Transport, Workspace } from "nanocodex/node";
776
+
777
+ const workspace = await Workspace.open({ path: process.cwd() });
778
+ const agent = await Agent.create({
779
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
780
+ filesystem: workspace,
781
+ });
782
+ ```
783
+
784
+ Node and browser applications can instead pay through MPP without an OpenAI
785
+ API key. Pass an MPP session with a `ws(endpoint)` method; an `mppx` Tempo
786
+ session manager has this shape. Nanocodex defaults the socket to
787
+ `wss://openai.mpp.tempo.xyz/v1/responses` when `mpp` is present.
788
+
789
+ ```js
790
+ import { Agent, createTempoProviderFromAccounts, Transport } from "nanocodex/node";
791
+ import { Expiry } from "accounts";
792
+ import { Provider } from "accounts/cli";
793
+ import { parseUnits } from "viem";
794
+ import { connect } from "viem/experimental/erc7846";
795
+ import WebSocket from "ws";
796
+
797
+ const pathUsd = "0x20c0000000000000000000000000000000000000";
798
+ const provider = Provider.create({ mpp: false });
799
+ if (!provider.store.persist.hasHydrated()) {
800
+ await new Promise((resolve) => provider.store.persist.onFinishHydration(resolve));
801
+ }
802
+ const status = await provider.getAccessKeyStatus();
803
+ if (status === "missing" || status === "expired") {
804
+ await connect(provider.getClient(), {
805
+ capabilities: { authorizeAccessKey: {
806
+ expiry: Expiry.days(1),
807
+ limits: [{ token: pathUsd, limit: parseUnits("25", 6) }],
808
+ } },
809
+ });
810
+ }
811
+ const root = provider.getAccount();
812
+ const account = await provider.store.accessKeys.select({
813
+ account: root.address,
814
+ chainId: provider.getClient().chain.id,
815
+ });
816
+ if (!account) throw new Error("Tempo account has no usable access key");
817
+ console.error(`Tempo access-key signer: ${account.accessKeyAddress}`);
818
+ const tempoProvider = await createTempoProviderFromAccounts({
819
+ wallet: provider,
820
+ accessKey: account.accessKeyAddress,
821
+ policy: {
822
+ autoSwap: { tokenIn: [pathUsd], slippage: 1 },
823
+ maxDeposit: "0.05",
824
+ topUpAmount: "0.05",
825
+ },
826
+ session: { bootstrap: true, webSocket: WebSocket },
827
+ });
828
+ const mpp = tempoProvider.session;
829
+
830
+ const agent = await Agent.create({
831
+ transport: Transport.mpp({ session: tempoProvider }),
832
+ thinking: "none",
833
+ fastMode: true,
834
+ tools,
835
+ });
836
+ const events = agent.events.watch();
837
+ const unwatch = events.onEvent((event) => {
838
+ process.stdout.write(`${JSON.stringify(event)}\n`);
839
+ });
840
+ let turn;
841
+ let result;
842
+ try {
843
+ turn = agent.turn.prompt({ input: "Build the thing." });
844
+ result = await turn.result();
845
+ console.error(result.finalMessage);
846
+ } finally {
847
+ try {
848
+ result?.dispose();
849
+ } finally {
850
+ turn?.dispose();
851
+ }
852
+ unwatch();
853
+ events.off();
854
+ const cleanupErrors = [];
855
+ try {
856
+ await agent.session.shutdown();
857
+ } catch (error) {
858
+ cleanupErrors.push(error);
859
+ }
860
+ try {
861
+ await mpp.close();
862
+ } catch (error) {
863
+ cleanupErrors.push(error);
864
+ }
865
+ if (cleanupErrors.length === 1) throw cleanupErrors[0];
866
+ if (cleanupErrors.length > 1) {
867
+ throw new AggregateError(cleanupErrors, "agent shutdown and MPP settlement both failed");
868
+ }
869
+ }
870
+ ```
871
+
872
+ The application still owns its wallet, deposit policy, persisted payment
873
+ channel store, and final settlement. Keep the manager alive to reuse its channel
874
+ across agents, and supply mppx `channelStore` for reuse after a process or page
875
+ restart. Nanocodex never closes a caller-owned MPP session.
876
+ `createTempoProviderFromAccounts({ wallet, ... })`
877
+ accepts any provider returned by Accounts SDK `Provider.create(...)`, regardless
878
+ of its wallet adapter, and constructs both payment paths from that provider's
879
+ adapter-neutral `getMppxParameters()` contract. The lower-level
880
+ `createTempoProvider({ session, payment })` remains available when the
881
+ application constructs MPPx itself. Both explicitly select Tempo provider mode.
882
+ In that mode Nanocodex automatically adds its built-in Mercator MCP and wraps it
883
+ with the same wallet and payment policy. The provider also exposes an MPP-aware
884
+ `fetch`; Mercator's paid REST handoffs use that same method rather than a second
885
+ wallet or payment configuration. Its MCP transport remains wrapped at the MCP
886
+ protocol layer, so browser requests do not need an `Accept-Payment` CORS header.
887
+ Browser Connect consumers send paid REST handoffs through the Connect API's
888
+ fixed Mercator relay because Mercator's job endpoint is not itself CORS-enabled;
889
+ the relay preserves MPP challenges, credentials, and receipts but never signs.
890
+ Passing a generic `MppSession`, an OpenAI key, or ChatGPT host auth does not
891
+ initialize Mercator. Pass `mcp: false` to opt out explicitly.
892
+
893
+ Remote Streamable HTTP MCP servers are configured directly on the agent. The
894
+ JavaScript binding uses the official MCP SDK transport, keeps remote tools
895
+ deferred, and mirrors native Nanocodex exposure: the initial Responses request
896
+ contains provider-native `tool_search`, while canonical `mcp__<server>__<tool>`
897
+ functions are callable only below Code Mode. Code Mode also exposes
898
+ `tools.tool_search`, so one cell can discover a deferred tool and invoke the
899
+ returned canonical name. Search results return loadable namespaces for the next
900
+ model request; remote tools never become a flat set of top-level model-visible
901
+ calls.
902
+
903
+ MPP-enabled MCP uses MPPx's in-place `McpClient.wrap`. Ordinary paid HTTP uses
904
+ `Mppx.create(...).fetch`. The public `tempo()` method is installed in both and
905
+ supports Tempo charge and session challenges, so paid services composed behind
906
+ Mercator use the same signer and spending policy as the model:
907
+
908
+ ```js
909
+ const mcpMethod = tempo({
910
+ account,
911
+ channelStore,
912
+ getClient: () => provider.getClient(),
913
+ maxDeposit: "0.05",
914
+ topUpAmount: "0.05",
915
+ });
916
+
917
+ const agent = await Agent.create({
918
+ transport: Transport.mpp({
919
+ session: createTempoProvider({
920
+ session: mpp,
921
+ payment: { methods: [mcpMethod] },
922
+ }),
923
+ }),
924
+ });
925
+ ```
926
+
927
+ Explicit `mcp` entries are merged over the Tempo defaults, so an application
928
+ can replace `mercator` or add other servers without rebuilding the provider.
929
+
930
+ Each server also accepts `headers`, `fetch`, allow/deny tool lists, a timeout,
931
+ or an already initialized MCP SDK-compatible `client`. Nanocodex closes clients
932
+ it creates and leaves caller-owned clients open. Connection failures are
933
+ reported by `tool_search` so one unavailable server does not prevent the agent
934
+ from starting.
935
+
936
+ Code Mode is the default. Model-facing `exec` cells can yield with a first-line
937
+ `// @exec: {"yield_time_ms": 1000, "max_output_tokens": 1000}` directive or
938
+ `yield_control()`. The model resumes the returned cell ID through `wait`, which
939
+ returns only new output and can terminate the cell. Cells belong to their agent
940
+ session and are invalidated when the host shuts down; a persisted `wait` never
941
+ restarts missing work. Embedded cells retain ownership of all nested tool calls
942
+ until they finish or are cancelled.
943
+
944
+ Custom evaluators receive `audio`, `notify`, `yield_control`, `setTimeout`, and
945
+ `clearTimeout` alongside the existing globals in `CodeEvaluatorEnvironment`.
946
+ Forward those helpers into the guest environment to preserve the model-visible
947
+ contract. `image` accepts individual MCP image blocks and honors explicit detail
948
+ before MCP metadata; `audio` accepts MCP audio blocks. Both accept data URLs.
949
+
950
+ Runtimes whose content-security policy rejects `eval`/`new Function` can supply
951
+ a Code Mode evaluator. `createQuickJsEvaluator` accepts an asyncified
952
+ `quickjs-emscripten-core` module, serializes Asyncify execution, and exposes only
953
+ the standard Nanocodex Code Mode globals across the interpreter boundary. This
954
+ keeps deferred MCP plus Code Mode functional in Cloudflare Workers:
955
+
956
+ ```js
957
+ import asyncVariant from "@jitl/quickjs-wasmfile-release-asyncify";
958
+ import { Agent, createQuickJsEvaluator, createTempoProvider, Transport } from "nanocodex/host";
959
+ import { newQuickJSAsyncWASMModuleFromVariant } from "quickjs-emscripten-core";
960
+
961
+ const quickJs = await newQuickJSAsyncWASMModuleFromVariant(asyncVariant);
962
+ const agent = await Agent.create({
963
+ transport: Transport.mpp({ session: tempoProvider }),
964
+ // module and mcp omitted here
965
+ codeEvaluator: createQuickJsEvaluator(quickJs),
966
+ });
967
+ ```
968
+
969
+ Cloudflare requires the QuickJS `.wasm` file to be statically imported and
970
+ passed with `newVariant(..., { wasmModule })`; the complete deployment is in
971
+ `examples/cloudflare-fetch-mcp`.
972
+
973
+ Completed results can be persisted and resumed by a fresh Node or browser
974
+ agent:
975
+
976
+ ```js
977
+ const snapshot = await result.snapshot();
978
+ result.dispose();
979
+ await agent.session.shutdown();
980
+
981
+ const resumed = await Agent.create({
982
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
983
+ resume: snapshot,
984
+ tools,
985
+ });
986
+ await resumed.session.shutdown();
987
+ ```
988
+
989
+ The snapshot contains authoritative typed history but no provider response ID,
990
+ so the first resumed request safely replays the committed conversation. Resume
991
+ with the same instructions and tool definitions, and release the original
992
+ agent before handing its snapshot to another writer.
993
+
994
+ For crash recovery inside a turn, provide the generic durability host instead
995
+ of manually persisting snapshots. The host stores one opaque Rust state value;
996
+ model replay, tool ambiguity, operation deduplication, and checkpoint recovery
997
+ remain in Rust/WASM:
998
+
999
+ ```js
1000
+ import { Agent, Transport } from "nanocodex/host";
1001
+
1002
+ const agent = await Agent.create({
1003
+ transport: Transport.openAi({ apiKey: process.env.OPENAI_API_KEY }),
1004
+ durability: {
1005
+ async load(stateId) {
1006
+ return database.loadState(stateId);
1007
+ },
1008
+ async acquire(stateId, { ownerId }) {
1009
+ return database.acquireState(stateId, ownerId);
1010
+ },
1011
+ async replace(stateId, { ownerId, fence, expectedRevision, payload }) {
1012
+ return database.compareAndReplace(
1013
+ stateId,
1014
+ ownerId,
1015
+ fence,
1016
+ expectedRevision,
1017
+ payload,
1018
+ );
1019
+ // { status: "replaced", revision: "8" }
1020
+ // or { status: "conflict", actualRevision: "8" }
1021
+ // or { status: "not_committed", message: "transaction rolled back" }
1022
+ },
1023
+ },
1024
+ durabilityId: "customer-agent-123",
1025
+ });
1026
+
1027
+ // Every prompt is durable because the state store is configured. Supply `id`
1028
+ // only when an external retry must identify the same logical operation.
1029
+ const turn = agent.turn.prompt({ input: "Build the thing." });
1030
+ // const turn = agent.turn.prompt({ id: "request-7", input: "Build the thing." });
1031
+ let result;
1032
+ try {
1033
+ result = await turn.result();
1034
+ console.log(result.finalMessage);
1035
+ } finally {
1036
+ try {
1037
+ result?.dispose();
1038
+ } finally {
1039
+ turn.dispose();
1040
+ await agent.session.shutdown();
1041
+ }
1042
+ }
1043
+ ```
1044
+
1045
+ Revisions are unsigned decimal strings so JavaScript preserves Rust's full
1046
+ `u64` range. Import `durabilityRevision`, `createMemoryDurabilityStore`,
1047
+ `createSqliteDurabilityStore`, and `sqliteDurabilitySchema` from the small
1048
+ `nanocodex/durability` leaf. Durable step hosts can carry the memory store's
1049
+ `snapshot()` into the next step. SQLite hosts provide one transaction query
1050
+ adapter and execute the canonical schema; the platform never interprets the
1051
+ opaque Rust state. See `js/managed`,
1052
+ `examples/vercel-workflows`, and `examples/rivet-actors` for all three host
1053
+ shapes.
1054
+
1055
+ Cloudflare Durable Objects can bind their colocated SQLite and initialize the
1056
+ canonical schema in one call. The adapter is structural and adds no Workers
1057
+ runtime dependency:
1058
+
1059
+ ```js
1060
+ import { createCloudflareDurabilityStore } from "nanocodex/durability/cloudflare";
1061
+
1062
+ const durability = createCloudflareDurabilityStore(this.ctx.storage);
1063
+ const agent = await Agent.create({
1064
+ module: env.NANOCODEX_WASM,
1065
+ transport,
1066
+ durability,
1067
+ durabilityId: sessionId,
1068
+ });
1069
+ ```
1070
+
1071
+ Vercel and other PostgreSQL hosts use `createPostgresDurabilityStore(pool)`
1072
+ from `nanocodex/durability/postgres`; connection ownership and secret policy
1073
+ remain in the application.
1074
+
1075
+ The built-in stores can move one stopped agent across providers without
1076
+ decoding or rebasing its Rust state. Cloudflare owners should use the adapter's
1077
+ lifecycle-safe export instead of reconstructing its private state ID:
1078
+
1079
+ ```js
1080
+ import { Agent as CloudflareAgent } from "nanocodex/cloudflare";
1081
+ import { importDurabilityStatePages } from "nanocodex/durability";
1082
+ import { createPostgresDurabilityStore } from "nanocodex/durability/postgres";
1083
+
1084
+ await cloudflareAgent.session.shutdown();
1085
+ const pages = [];
1086
+ let cursor;
1087
+ let to;
1088
+ do {
1089
+ const page = await CloudflareAgent.exportDurabilityState(durableObjectOwner, {
1090
+ from: "0", // exclusive destination revision
1091
+ to, // omit once, then repeat the selected inclusive source revision
1092
+ cursor,
1093
+ });
1094
+ pages.push(page);
1095
+ to = page.to;
1096
+ cursor = page.nextCursor ?? undefined;
1097
+ } while (cursor !== undefined);
1098
+
1099
+ // Send the pages through an authenticated, encrypted operator path.
1100
+ const destination = createPostgresDurabilityStore(vercelPostgresPool);
1101
+ await importDurabilityStatePages(destination, JSON.parse(JSON.stringify(pages)));
1102
+
1103
+ const vercelAgent = await Agent.create({
1104
+ module: wasmModule,
1105
+ transport,
1106
+ durability: destination,
1107
+ durabilityId: pages[0].stateId,
1108
+ });
1109
+ ```
1110
+
1111
+ `from` is exclusive and `to` is inclusive. For a nonzero `from`, load the
1112
+ destination once, hash that exact state with `durabilityStateDigest`, and repeat
1113
+ the short `fromDigest` on every page request; revision zero's null-state digest
1114
+ is implied. Each page carries that SHA-256 lineage digest, so import atomically
1115
+ succeeds only if the destination still has the exact revision and payload
1116
+ selected at `from`.
1117
+ Because `to` is one complete Rust state, no intermediate revision log is
1118
+ needed. Export fences the old source owner, and PostgreSQL reconciles lost
1119
+ COMMIT responses internally by retrying the identical idempotent request, so
1120
+ the API never reports an ambiguous write outcome. Stop source admission before
1121
+ the first page and never resume it after cutover begins. Pages can contain
1122
+ conversation and tool state, so handle them as secrets. The Vercel example
1123
+ includes a WASM integration test that executes the
1124
+ same agent Cloudflare → PostgreSQL → Cloudflare, replays committed turn IDs
1125
+ without model calls, rebuilds the first new provider request from committed
1126
+ history without a previous-response handle, and then continues with new turns
1127
+ on each destination.
1128
+
1129
+ The managed Cloudflare service exposes the same offline cutover at `POST
1130
+ /v1/agents/<agent-id>/durability`; the call permanently closes source admission.
1131
+ Create a destination with `POST /v1/agents`, an `Idempotency-Key` header, and
1132
+ `{ "durability": <archive> }`. The stable key owns resumable receipt adoption.
1133
+ The Vercel example accepts that same body at `POST /api/sessions` and exports a
1134
+ stopped PostgreSQL state through `POST /api/durability/export` with
1135
+ `{ "state_id": <durability-id>, "from": <revision>,
1136
+ "fromDigest": <required-for-nonzero-from>, "to": <optional-revision>,
1137
+ "cursor": <optional-cursor> }`.
1138
+
1139
+ Node embedders whose bundler relocates package assets may compile and pass the
1140
+ web-target artifact explicitly. The runtime still uses the Node host for
1141
+ WebSockets and Code Mode:
1142
+
1143
+ ```js
1144
+ const module = await WebAssembly.compile(await readFile(wasmAssetPath));
1145
+ const agent = await Agent.create({ transport: Transport.openAi({ apiKey }), module });
1146
+ ```
1147
+
1148
+ A Codex-compatible rollout can also be resumed by materializing its committed
1149
+ `response_item` history into a snapshot with no `request_prefix`. Nanocodex
1150
+ rebuilds the current prefix from the supplied instructions and JavaScript tools
1151
+ while preserving the rollout's workspace, lineage, cache key, canonical user
1152
+ context, and typed history.
1153
+
1154
+ `Agent` and `Actions` are module namespaces, not classes. `Agent.create` returns
1155
+ an owned client decorated with matching domain actions:
1156
+
1157
+ - `agent.turn.prompt(...)` / `Actions.turn.prompt(agent, ...)`
1158
+ - `turn.accepted()` / `Actions.turn.accepted(turn)`
1159
+ - `turn.result()` / `Actions.turn.getResult(turn)`
1160
+ - `result.snapshot()` / `Actions.turn.getSnapshot(result)`
1161
+ - `result.usage()` / `Actions.turn.getUsage(result)`
1162
+ - `agent.session.fork(...)` / `Actions.session.fork(agent, ...)`
1163
+ - `agent.session.compact()` / `Actions.session.compact(agent)`
1164
+ - `agent.session.snapshot()` / `Actions.session.snapshot(agent)`
1165
+ - `agent.session.setThinking(...)` / `Actions.session.setThinking(agent, ...)`
1166
+ - `agent.session.setFastMode(...)` / `Actions.session.setFastMode(agent, ...)`
1167
+ - `agent.session.shutdown()` / `Actions.session.shutdown(agent)`
1168
+ - `agent.session.spawn()` / `Actions.session.spawn(agent)`
1169
+ - `agent.events.watch(...)` / `Actions.events.watch(agent, ...)`
1170
+
1171
+ `turn.accepted()` resolves when Rust has admitted the prompt. A durable agent
1172
+ returns its stable request ID; a custom runtime without durable admission
1173
+ returns `undefined`. Managed HTTP hosts can await this narrow boundary before
1174
+ acknowledging a request without waiting for model execution or materializing a
1175
+ result.
1176
+
1177
+ `turn.result()` resolves to a frozen, opaque completed `TurnResult` handle. Its
1178
+ `finalMessage` is eager. The async `usage()` and `snapshot()` actions materialize
1179
+ immutable values once and cache their promises. A package Worker completes a
1180
+ turn with only the message and hidden result identity; Rust-produced snapshot
1181
+ JSON crosses the Worker boundary only on first demand and is parsed once in the
1182
+ calling isolate. Historical `fork({ at })` consumes the hidden identity directly,
1183
+ never an unfinished turn, clone, snapshot, or provider response ID.
1184
+
1185
+ The completed result owns its identity independently from the `Turn`, so
1186
+ `turn.dispose()` does not invalidate a successful result. Call `result.dispose()`
1187
+ after its last fork/materialization; this releases the retained Worker/native
1188
+ checkpoint and invalidates future `snapshot()`, `usage()`, and historical forks.
1189
+ An undisposed result intentionally keeps its package Worker alive after the last
1190
+ Agent shuts down so its lazy values remain available. Garbage collection is only
1191
+ a fallback for forgotten handles, not deterministic cleanup.
1192
+
1193
+ `turn.dispose()` only releases the JavaScript/WASM handle; like dropping the
1194
+ Rust `Turn`, it does not cancel accepted work. Await `turn.cancel()` before
1195
+ disposing unfinished work. At an application or session boundary,
1196
+ `agent.session.shutdown()` cancels unfinished turns and joins driver, model,
1197
+ tool, and transport cleanup.
1198
+
1199
+ Every action owns its types, for example `Actions.turn.prompt.Options`,
1200
+ `Actions.turn.prompt.ReturnType`, and `Actions.events.watch.Watcher`.
1201
+
1202
+ Event watches are lazy, terminal handles:
1203
+
1204
+ ```js
1205
+ const watch = agent.events.watch();
1206
+ const unlisten = watch.onEvent(console.log);
1207
+
1208
+ unlisten();
1209
+ watch.off();
1210
+ ```
1211
+
1212
+ A throwing callback is reported through the host's `reportError` hook (or
1213
+ `console.error` when that hook is unavailable) without interrupting later
1214
+ listeners or the owned agent lifecycle.
1215
+
1216
+ The same watcher can instead be consumed as an ordered async iterable; breaking
1217
+ the loop releases that iterator, while `watch.off()` terminates the whole watch.
1218
+
1219
+ ```js
1220
+ const watch = agent.events.watch();
1221
+ for await (const event of watch) {
1222
+ console.log(event);
1223
+ if (done) break;
1224
+ }
1225
+ watch.off();
1226
+ ```
1227
+
1228
+ Applications add typed action domains with decorators:
1229
+
1230
+ ```js
1231
+ const extended = agent.extend((client) => ({
1232
+ inspect: {
1233
+ session: () => client.sessionId,
1234
+ },
1235
+ }));
1236
+
1237
+ extended.inspect.session();
1238
+ ```
1239
+
1240
+ The package-owned browser Worker accepts the same transport policy without
1241
+ function-valued callbacks:
1242
+
1243
+ ```js
1244
+ import { Agent, Transport } from "nanocodex/browser";
1245
+
1246
+ const agent = await Agent.create({
1247
+ transport: Transport.hostManaged({
1248
+ websocketUrl: signedOrCookieAuthorizedEndpoint,
1249
+ }),
1250
+ threadId,
1251
+ });
1252
+ ```
1253
+
1254
+ Caller-owned browser Workers and server isolates import `nanocodex/host` when
1255
+ they need function-valued tools or socket construction. Server-side runtimes
1256
+ can await a `fetch()`-based WebSocket upgrade. The third callback argument is a
1257
+ discriminated authorization request plus connection metadata, including the
1258
+ eager `preconnect` request. With `Transport.openAi`, `authorization` is
1259
+ `"bearer"` and `bearerToken` is present. With `Transport.hostManaged`, it is
1260
+ `"host_managed"`; the host must resolve credentials without exposing them to
1261
+ WASM. Do not retain or log bearer tokens. Return the socket alone or a
1262
+ descriptor containing response metadata:
1263
+
1264
+ ```js
1265
+ import { Agent, Transport } from "nanocodex/host";
1266
+ import module from "nanocodex/wasm";
1267
+
1268
+ const agent = await Agent.create({
1269
+ transport: Transport.openAi({
1270
+ apiKey,
1271
+ async createWebSocket(endpoint, sessionId, request) {
1272
+ if (request.authorization !== "bearer") {
1273
+ throw new Error("this host requires Nanocodex bearer authorization");
1274
+ }
1275
+ const response = await fetch(endpoint.replace("wss:", "https:"), {
1276
+ headers: {
1277
+ Authorization: `Bearer ${request.bearerToken}`,
1278
+ Upgrade: "websocket",
1279
+ "session-id": sessionId,
1280
+ },
1281
+ });
1282
+ if (!response.webSocket) throw new Error(`upgrade failed: ${response.status}`);
1283
+ response.webSocket.accept();
1284
+ return { socket: response.webSocket, status: response.status };
1285
+ },
1286
+ }),
1287
+ module,
1288
+ });
1289
+ ```
1290
+
1291
+ `Transport.hostManaged` is useful when the embedding runtime owns rotating credentials. The
1292
+ callback can acquire a fresh token, attempt the upgrade, and refresh-and-retry
1293
+ on 401. Bound and reject upgrade work in the callback: until it returns a
1294
+ socket, there is no connection handle for Nanocodex to close. Selecting one
1295
+ transport makes authentication modes mutually exclusive by construction.
1296
+
1297
+ After publication, a browser can load the current-isolate host without a
1298
+ package manager or build step:
1299
+
1300
+ ```html
1301
+ <script type="module">
1302
+ import { Agent, Transport } from "https://cdn.jsdelivr.net/npm/nanocodex@0.5.0/host/index.mjs";
1303
+ const agent = await Agent.create({
1304
+ transport: Transport.hostManaged({
1305
+ websocketUrl: "/api/responses",
1306
+ createWebSocket: (endpoint) => new WebSocket(endpoint),
1307
+ }),
1308
+ });
1309
+ const turn = agent.turn.prompt({ input: "Hello." });
1310
+ let result;
1311
+ try {
1312
+ result = await turn.result();
1313
+ console.log(result.finalMessage);
1314
+ } finally {
1315
+ try {
1316
+ result?.dispose();
1317
+ } finally {
1318
+ turn.dispose();
1319
+ await agent.session.shutdown();
1320
+ }
1321
+ }
1322
+ </script>
1323
+ ```
1324
+
1325
+ Pin the package version in production. The adjacent WASM file is part of the
1326
+ npm package and is resolved relative to the host module. This no-build path
1327
+ runs in the current page isolate; bundled applications should prefer the
1328
+ package-owned Worker from `nanocodex/browser`. The endpoint must be authorized
1329
+ by the embedding application because browser WebSockets cannot attach OpenAI's
1330
+ upgrade authorization header.
1331
+
1332
+ The owned Rust session retains follow-on history, response state, tool output,
1333
+ its WebSocket, and stable prompt-cache identity. Typed browser content accepts
1334
+ ordered text, remote/data-URL image, and audio items. JavaScript tools are
1335
+ ordinary async handlers described by JSON Schema and appear in the same ordered
1336
+ agent event stream as built-in code mode.
1337
+
1338
+ Run the standalone Node proof with:
1339
+
1340
+ ```sh
1341
+ cd examples/node
1342
+ npm install
1343
+ OPENAI_API_KEY=... npm start
1344
+ ```