@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.
- package/LICENSE +65 -0
- package/NOTICE +2 -0
- package/README.md +182 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/cordis.patch.yml +52 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +3405 -0
- package/dist/standard-preset.d.ts +7 -0
- package/dist/standard-preset.js +25 -0
- package/engine-release.json +14 -0
- package/package.json +119 -0
- package/presets/standard/agent.cordis.yml +6 -0
- package/presets/standard/preset.yml +2 -0
- package/vendor/nanocodex/README.md +1344 -0
- package/vendor/nanocodex/actions/events.d.mts +10 -0
- package/vendor/nanocodex/actions/events.mjs +163 -0
- package/vendor/nanocodex/actions/index.d.mts +13 -0
- package/vendor/nanocodex/actions/index.mjs +35 -0
- package/vendor/nanocodex/actions/session.d.mts +66 -0
- package/vendor/nanocodex/actions/session.mjs +59 -0
- package/vendor/nanocodex/actions/turn.d.mts +59 -0
- package/vendor/nanocodex/actions/turn.mjs +37 -0
- package/vendor/nanocodex/actions/voice.d.mts +22 -0
- package/vendor/nanocodex/actions/voice.mjs +50 -0
- package/vendor/nanocodex/browser/Agent.d.mts +55 -0
- package/vendor/nanocodex/browser/Agent.mjs +11 -0
- package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
- package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
- package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
- package/vendor/nanocodex/browser/Transport.d.mts +74 -0
- package/vendor/nanocodex/browser/Transport.mjs +61 -0
- package/vendor/nanocodex/browser/Voice.d.mts +71 -0
- package/vendor/nanocodex/browser/Voice.mjs +291 -0
- package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
- package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
- package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
- package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
- package/vendor/nanocodex/browser/config.d.mts +36 -0
- package/vendor/nanocodex/browser/config.mjs +439 -0
- package/vendor/nanocodex/browser/engine.mjs +13 -0
- package/vendor/nanocodex/browser/harness.mjs +55 -0
- package/vendor/nanocodex/browser/host.d.mts +83 -0
- package/vendor/nanocodex/browser/host.mjs +555 -0
- package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
- package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
- package/vendor/nanocodex/browser/index.d.mts +92 -0
- package/vendor/nanocodex/browser/index.mjs +23 -0
- package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
- package/vendor/nanocodex/browser/workspace.d.mts +16 -0
- package/vendor/nanocodex/browser/workspace.mjs +107 -0
- package/vendor/nanocodex/cloud/Client.d.mts +83 -0
- package/vendor/nanocodex/cloud/Client.mjs +231 -0
- package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
- package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
- package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
- package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
- package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
- package/vendor/nanocodex/cloud/Errors.mjs +39 -0
- package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
- package/vendor/nanocodex/cloud/Principal.mjs +88 -0
- package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
- package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
- package/vendor/nanocodex/cloud/Transport.mjs +454 -0
- package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
- package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
- package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
- package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
- package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
- package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
- package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
- package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
- package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
- package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
- package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
- package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
- package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
- package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
- package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
- package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
- package/vendor/nanocodex/cloud/index.d.mts +30 -0
- package/vendor/nanocodex/cloud/index.mjs +9 -0
- package/vendor/nanocodex/cloud/internal.mjs +533 -0
- package/vendor/nanocodex/cloud/mercator.mjs +108 -0
- package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
- package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
- package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
- package/vendor/nanocodex/cloud/server/index.mjs +1 -0
- package/vendor/nanocodex/cloud/types.d.mts +211 -0
- package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
- package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
- package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
- package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
- package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
- package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
- package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
- package/vendor/nanocodex/cloudflare/index.mjs +6 -0
- package/vendor/nanocodex/host/Agent.d.mts +55 -0
- package/vendor/nanocodex/host/Agent.mjs +1 -0
- package/vendor/nanocodex/host/index.d.mts +82 -0
- package/vendor/nanocodex/host/index.mjs +10 -0
- package/vendor/nanocodex/index.d.mts +81 -0
- package/vendor/nanocodex/index.mjs +12 -0
- package/vendor/nanocodex/internal.mjs +1275 -0
- package/vendor/nanocodex/managed/Agent.d.mts +380 -0
- package/vendor/nanocodex/managed/Agent.mjs +1694 -0
- package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
- package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
- package/vendor/nanocodex/managed/README.md +241 -0
- package/vendor/nanocodex/managed/Voice.mjs +202 -0
- package/vendor/nanocodex/managed/index.d.mts +20 -0
- package/vendor/nanocodex/managed/index.mjs +2 -0
- package/vendor/nanocodex/managed/internal.mjs +119 -0
- package/vendor/nanocodex/node/Agent.d.mts +50 -0
- package/vendor/nanocodex/node/Agent.mjs +203 -0
- package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
- package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
- package/vendor/nanocodex/node/Transport.d.mts +44 -0
- package/vendor/nanocodex/node/Transport.mjs +46 -0
- package/vendor/nanocodex/node/host.mjs +731 -0
- package/vendor/nanocodex/node/index.d.mts +73 -0
- package/vendor/nanocodex/node/index.mjs +17 -0
- package/vendor/nanocodex/node/workspace.d.mts +10 -0
- package/vendor/nanocodex/node/workspace.mjs +198 -0
- package/vendor/nanocodex/package.json +200 -0
- package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
- package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
- package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
- package/vendor/nanocodex/pkg-node/package.json +1 -0
- package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
- package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
- package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
- package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
- package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
- package/vendor/nanocodex/pkg-web/package.json +1 -0
- package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
- package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
- package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
- package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
- package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
- package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
- package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
- package/vendor/nanocodex/runtime/durability.mjs +153 -0
- package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
- package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
- package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
- package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
- package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
- package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
- package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
- package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
- package/vendor/nanocodex/runtime/subagents.mjs +54 -0
- package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
- package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
- package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
- package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
- package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
- package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
- package/vendor/nanocodex/runtime/utf8.mjs +1 -0
- package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
- package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
- package/vendor/nanocodex/runtime/workspace.mjs +1 -0
- package/vendor/nanocodex/tools/Tools.d.mts +56 -0
- package/vendor/nanocodex/tools/Tools.mjs +136 -0
- package/vendor/nanocodex/tools/artifact.d.mts +1 -0
- package/vendor/nanocodex/tools/artifact.mjs +1 -0
- package/vendor/nanocodex/tools/attachment.mjs +1 -0
- package/vendor/nanocodex/tools/bash.d.mts +1 -0
- package/vendor/nanocodex/tools/bash.mjs +1 -0
- package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
- package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
- package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
- package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
- package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
- package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
- package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
- package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
- package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
- package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
- package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
- package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
- package/vendor/nanocodex/tools/browser/index.mjs +174 -0
- package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
- package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
- package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
- package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
- package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
- package/vendor/nanocodex/tools/dataset.d.mts +1 -0
- package/vendor/nanocodex/tools/dataset.mjs +1 -0
- package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
- package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
- package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
- package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
- package/vendor/nanocodex/tools/index.d.mts +49 -0
- package/vendor/nanocodex/tools/index.mjs +19 -0
- package/vendor/nanocodex/tools/namedTool.mjs +1 -0
- package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
- package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
- package/vendor/nanocodex/tools/ssh.d.mts +1 -0
- package/vendor/nanocodex/tools/ssh.mjs +1 -0
- package/vendor/nanocodex/tools/standard.mjs +1 -0
- package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
- package/vendor/nanocodex/types.d.mts +663 -0
- package/vendor/nanocodex/wasm.d.mts +4 -0
- package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
- package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
- package/vendor/nanocodex/worker/index.d.mts +18 -0
- package/vendor/nanocodex/worker/index.mjs +5 -0
- package/vendor/nanocodex-tools/README.md +65 -0
- package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
- package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
- package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
- package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
- package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
- package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
- package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
- package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
- package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
- package/vendor/nanocodex-tools/dist/index.js +14 -0
- package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
- package/vendor/nanocodex-tools/dist/memory.js +411 -0
- package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
- package/vendor/nanocodex-tools/dist/namespace.js +338 -0
- package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
- package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
- package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
- package/vendor/nanocodex-tools/dist/runtime.js +37 -0
- package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
- package/vendor/nanocodex-tools/dist/session.js +286 -0
- package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
- package/vendor/nanocodex-tools/dist/shell.js +553 -0
- package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
- package/vendor/nanocodex-tools/dist/ssh.js +66 -0
- package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
- package/vendor/nanocodex-tools/dist/workspace.js +237 -0
- package/vendor/nanocodex-tools/package.json +122 -0
- package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
- package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
- package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
- package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
- package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
- package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
- package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
- package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
- package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
- package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
- package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
- package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
- package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
- package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
- package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
- package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
- package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
- package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
- package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
- package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
- package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
- package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
- package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
- package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
- package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
- package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
- package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
- package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
- package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
- package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
- package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
- package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
- package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
- package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
- package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
- package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
- package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
- 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
|
+
```
|