nanocodex 0.2.0 → 0.4.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/node/host.mjs CHANGED
@@ -1,10 +1,13 @@
1
1
  import { Console } from "node:console";
2
2
  import { createRequire } from "node:module";
3
+ import { resolve } from "node:path";
3
4
  import WebSocket from "ws";
5
+ import packageMetadata from "../package.json" with { type: "json" };
4
6
 
5
7
  import { createCodeRuntime } from "../runtime/code-runtime.mjs";
6
8
 
7
9
  const RESPONSES_WEBSOCKETS_BETA = "responses_websockets=2026-02-06";
10
+ const USER_AGENT = `nanocodex-wasm/${packageMetadata.version}`;
8
11
  const DEFAULT_MAX_QUEUED_MESSAGES = 4_096;
9
12
  const DEFAULT_MAX_QUEUED_BYTES = 32 * 1024 * 1024;
10
13
  const DEFAULT_MAX_FRAME_BYTES = 16 * 1024 * 1024;
@@ -12,9 +15,13 @@ const DEFAULT_MAX_FRAME_BYTES = 16 * 1024 * 1024;
12
15
  export function createNodeHost(options = {}) {
13
16
  const connections = new Map();
14
17
  const code = createCodeRuntime(options.tools, {
15
- require: createRequire(`${process.cwd()}/`),
18
+ require: createRequire(resolve(options.workspace ?? process.cwd(), ".nanocodex-code-mode.cjs")),
16
19
  console: new Console({ stdout: process.stderr, stderr: process.stderr }),
17
20
  });
21
+ const toolMode = options.toolMode ?? "code";
22
+ if (toolMode !== "code" && toolMode !== "direct") {
23
+ throw new TypeError("toolMode must be code or direct");
24
+ }
18
25
  const onEvent = options.onEvent || (() => {});
19
26
  const connectTimeoutMs = options.connectTimeoutMs ?? 30_000;
20
27
  const sendTimeoutMs = options.sendTimeoutMs ?? 30_000;
@@ -23,29 +30,47 @@ export function createNodeHost(options = {}) {
23
30
  const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
24
31
  let nextHandle = 1;
25
32
 
26
- function connect(endpoint, apiKey, sessionId) {
33
+ function connect(endpoint, apiKey, sessionId, metadata = {}) {
27
34
  if (options.mpp) return connectMpp(endpoint);
28
35
  return new Promise((resolve, reject) => {
29
36
  let settled = false;
30
37
  let upgradeResponse;
38
+ const headers = {
39
+ Authorization: `Bearer ${apiKey}`,
40
+ "OpenAI-Beta": RESPONSES_WEBSOCKETS_BETA,
41
+ "x-openai-internal-codex-responses-lite": "true",
42
+ "session-id": sessionId,
43
+ "thread-id": sessionId,
44
+ "x-client-request-id": sessionId,
45
+ "x-responsesapi-include-timing-metrics": "true",
46
+ "User-Agent": USER_AGENT,
47
+ };
48
+ if (metadata.accountId) headers["ChatGPT-Account-ID"] = metadata.accountId;
49
+ if (metadata.fedramp) headers["X-OpenAI-Fedramp"] = "true";
50
+ if (metadata.turnState) headers["x-codex-turn-state"] = metadata.turnState;
31
51
  const socket = new WebSocket(endpoint, {
32
52
  handshakeTimeout: connectTimeoutMs,
33
53
  maxPayload: maxFrameBytes,
34
- headers: {
35
- Authorization: `Bearer ${apiKey}`,
36
- "OpenAI-Beta": RESPONSES_WEBSOCKETS_BETA,
37
- "x-openai-internal-codex-responses-lite": "true",
38
- "session-id": sessionId,
39
- "thread-id": sessionId,
40
- "x-client-request-id": sessionId,
41
- "x-responsesapi-include-timing-metrics": "true",
42
- "User-Agent": "nanocodex-wasm/0.1.0",
43
- },
54
+ headers,
44
55
  });
45
56
  const handle = nextHandle++;
46
57
  const connection = queueState(socket);
47
58
 
48
59
  socket.on("upgrade", (response) => { upgradeResponse = response; });
60
+ socket.on("unexpected-response", (_request, response) => {
61
+ if (settled) return;
62
+ settled = true;
63
+ const chunks = [];
64
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
65
+ response.on("end", () => {
66
+ const error = new Error(`WebSocket handshake was rejected with HTTP ${response.statusCode}`);
67
+ error.status = response.statusCode;
68
+ error.body = chunks.length ? Buffer.concat(chunks).toString("utf8") : "empty response body";
69
+ const retryAfter = Number(header(response.headers, "retry-after"));
70
+ if (Number.isFinite(retryAfter) && retryAfter >= 0) error.retryAfter = retryAfter;
71
+ reject(error);
72
+ });
73
+ });
49
74
  socket.on("open", () => {
50
75
  settled = true;
51
76
  connections.set(handle, connection);
@@ -216,6 +241,8 @@ export function createNodeHost(options = {}) {
216
241
  close,
217
242
  sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
218
243
  executeCode: code.executeCode,
244
+ executeTool: code.executeTool,
245
+ toolMode: () => toolMode,
219
246
  toolDefinitions: code.toolDefinitions,
220
247
  emitEvent: onEvent,
221
248
  reset: code.reset,
package/node/index.d.mts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { Actions } from "../index.mjs";
2
2
  export type {
3
3
  AgentEvent,
4
+ CostStatus,
5
+ EstimatedUsdCost,
4
6
  PromptInput,
5
7
  PromptItem,
6
8
  ReasoningMode,
@@ -10,5 +12,7 @@ export type {
10
12
  ToolContext,
11
13
  ToolMap,
12
14
  Turn,
15
+ TurnResult,
16
+ TurnUsage,
13
17
  } from "../types.mjs";
14
18
  export * as Agent from "./Agent.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nanocodex",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Viem-style Node and browser bindings for the Nanocodex WASM agent",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -16,6 +16,10 @@
16
16
  "./browser": {
17
17
  "types": "./browser/index.d.mts",
18
18
  "import": "./browser/index.mjs"
19
+ },
20
+ "./wasm": {
21
+ "types": "./wasm.d.mts",
22
+ "import": "./pkg-web/nanocodex_bg.wasm"
19
23
  }
20
24
  },
21
25
  "files": [
@@ -25,10 +29,10 @@
25
29
  "pkg-node",
26
30
  "pkg-web",
27
31
  "runtime",
28
- "scripts/check-package.mjs",
29
32
  "internal.mjs",
30
33
  "index.d.mts",
31
34
  "index.mjs",
35
+ "wasm.d.mts",
32
36
  "types.d.mts",
33
37
  "README.md"
34
38
  ],
@@ -39,7 +43,8 @@
39
43
  "scripts": {
40
44
  "check:package": "node scripts/check-package.mjs",
41
45
  "prepack": "npm run check:package",
42
- "test": "node --test test/*.test.mjs && npm run test:typecheck && npm run check:package",
46
+ "test": "node --test test/*.test.mjs && npm run test:performance && npm run test:typecheck && npm run check:package",
47
+ "test:performance": "node --test test/performance.bench.mjs",
43
48
  "test:typecheck": "tsc -p test/tsconfig.json"
44
49
  },
45
50
  "dependencies": {
@@ -2,13 +2,21 @@
2
2
  /* eslint-disable */
3
3
 
4
4
  /**
5
- * Persistent WASM agent handle hosted by Node.js or a browser Worker.
5
+ * JavaScript binding over the shared Rust agent lifecycle.
6
6
  */
7
7
  export class Nanocodex {
8
8
  free(): void;
9
9
  [Symbol.dispose](): void;
10
10
  /**
11
- * Fork the latest safe committed model boundary.
11
+ * Compacts retained history immediately without fabricating a user prompt.
12
+ *
13
+ * # Errors
14
+ *
15
+ * Throws when compaction or the agent driver fails.
16
+ */
17
+ compact(): Promise<void>;
18
+ /**
19
+ * Forks the latest safe committed model boundary.
12
20
  *
13
21
  * # Errors
14
22
  *
@@ -16,57 +24,63 @@ export class Nanocodex {
16
24
  */
17
25
  fork(): Promise<Nanocodex>;
18
26
  /**
19
- * Fork from the exact checkpoint retained by a completed historical turn.
27
+ * Forks from an exact completed historical turn.
20
28
  *
21
29
  * # Errors
22
30
  *
23
- * Rejects if the turn is incomplete, belongs to another lineage, or the
24
- * driver stops.
31
+ * Rejects if the result belongs to another agent or the driver stopped.
25
32
  */
26
- forkFrom(turn: Turn): Promise<Nanocodex>;
33
+ forkFrom(result: TurnResult): Promise<Nanocodex>;
27
34
  /**
28
- * Build a persistent agent from a JSON configuration object.
35
+ * Builds an agent from its JavaScript JSON configuration.
29
36
  *
30
37
  * # Errors
31
38
  *
32
- * Throws when the JSON or required configuration is invalid.
39
+ * Throws when the JSON or agent policy is invalid.
33
40
  */
34
41
  constructor(config_json: string);
35
42
  /**
36
- * Accept a prompt immediately and return its independently awaitable turn.
43
+ * Accepts a text prompt and returns its independently awaitable turn.
37
44
  *
38
45
  * # Errors
39
46
  *
40
- * Throws when the prompt is empty or the agent driver has stopped.
47
+ * Throws when the prompt is empty.
41
48
  */
42
49
  prompt(instruction: string): Turn;
43
50
  /**
44
- * Accept ordered browser-safe text, image, or audio input encoded as JSON.
51
+ * Accepts browser-safe multimodal input encoded as JSON.
45
52
  *
46
53
  * # Errors
47
54
  *
48
- * Throws for malformed or empty content, local filesystem inputs, or a
49
- * stopped driver.
55
+ * Throws for malformed, empty, or local-filesystem input.
50
56
  */
51
57
  promptContent(content_json: string): Turn;
52
58
  /**
53
- * Enable or disable priority processing for subsequently accepted turns.
59
+ * Enables or disables priority processing for subsequently accepted turns.
54
60
  *
55
61
  * # Errors
56
62
  *
57
- * Rejects after the agent driver stops.
63
+ * Rejects after the driver stops.
58
64
  */
59
65
  setFastMode(enabled: boolean): Promise<void>;
60
66
  /**
61
- * Change the reasoning effort for subsequently accepted turns.
67
+ * Changes the reasoning effort for subsequently accepted turns.
62
68
  *
63
69
  * # Errors
64
70
  *
65
- * Rejects an unsupported effort or a stopped agent driver.
71
+ * Rejects an invalid effort or a stopped driver.
66
72
  */
67
73
  setThinking(thinking: string): Promise<void>;
68
74
  /**
69
- * Start a clean sibling with the same configuration and tools.
75
+ * Gracefully stops the driver and joins every resource owned by this agent.
76
+ *
77
+ * # Errors
78
+ *
79
+ * Rejects when the driver had already stopped or cleanup fails.
80
+ */
81
+ shutdown(): Promise<void>;
82
+ /**
83
+ * Starts a clean sibling with the same private agent policy.
70
84
  *
71
85
  * # Errors
72
86
  *
@@ -74,60 +88,77 @@ export class Nanocodex {
74
88
  */
75
89
  spawn(): Promise<Nanocodex>;
76
90
  /**
77
- * Stable session identifier used to route this agent's event stream.
91
+ * Returns the stable `UUIDv7` session identity.
78
92
  */
79
93
  readonly sessionId: string;
80
94
  }
81
95
 
82
96
  /**
83
- * Completion and control handle for one accepted WASM turn.
97
+ * JavaScript binding over one shared Rust turn.
84
98
  */
85
99
  export class Turn {
86
100
  private constructor();
87
101
  free(): void;
88
102
  [Symbol.dispose](): void;
89
103
  /**
90
- * Cancel this exact active or queued turn.
104
+ * Cancels this exact active or queued turn.
91
105
  *
92
106
  * # Errors
93
107
  *
94
- * Rejects if the turn is already terminal or the driver stops.
108
+ * Rejects if the turn is already terminal or its driver stopped.
95
109
  */
96
110
  cancel(): Promise<void>;
97
111
  /**
98
- * Wait for the turn and return its final assistant message.
112
+ * Waits for the final assistant message.
99
113
  *
100
114
  * # Errors
101
115
  *
102
- * Rejects when the model run fails, the driver stops, or two consumers
103
- * await the same pending turn concurrently.
116
+ * Rejects when the model run or driver fails.
104
117
  */
105
- result(): Promise<string>;
118
+ result(): Promise<TurnResult>;
106
119
  /**
107
- * Serialize this completed turn's resumable session snapshot.
120
+ * Injects text input at the active turn's next safe model boundary.
108
121
  *
109
122
  * # Errors
110
123
  *
111
- * Throws until the turn has completed or if serialization fails.
124
+ * Rejects if the turn is not active or its driver stopped.
112
125
  */
113
- snapshot(): string;
126
+ steer(instruction: string): Promise<void>;
114
127
  /**
115
- * Inject input at this active turn's next safe model boundary.
128
+ * Injects browser-safe multimodal input at the active turn's next boundary.
116
129
  *
117
130
  * # Errors
118
131
  *
119
- * Rejects for empty input, a queued or terminal turn, a full steer queue,
120
- * or a stopped driver.
132
+ * Rejects malformed input or a turn that is no longer active.
121
133
  */
122
- steer(instruction: string): Promise<void>;
134
+ steerContent(content_json: string): Promise<void>;
135
+ }
136
+
137
+ /**
138
+ * JavaScript binding over one completed Rust turn result.
139
+ */
140
+ export class TurnResult {
141
+ private constructor();
142
+ free(): void;
143
+ [Symbol.dispose](): void;
123
144
  /**
124
- * Inject ordered browser-safe text, image, or audio input at this active
125
- * turn's next safe model boundary.
145
+ * Serializes this completed boundary's resumable session snapshot.
126
146
  *
127
147
  * # Errors
128
148
  *
129
- * Rejects for malformed or empty content, local filesystem inputs, a
130
- * queued or terminal turn, a full steer queue, or a stopped driver.
149
+ * Throws when serialization fails.
131
150
  */
132
- steerContent(content_json: string): Promise<void>;
151
+ snapshot(): string;
152
+ /**
153
+ * Serializes exact aggregate usage for this completed logical turn.
154
+ *
155
+ * # Errors
156
+ *
157
+ * Throws when serialization fails.
158
+ */
159
+ usage(): string;
160
+ /**
161
+ * Returns the final assistant message.
162
+ */
163
+ readonly finalMessage: string;
133
164
  }