nanocodex 0.1.1 → 0.3.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/README.md +170 -6
- package/actions/events.mjs +105 -22
- package/actions/index.mjs +4 -0
- package/actions/session.d.mts +14 -2
- package/actions/session.mjs +24 -1
- package/actions/turn.d.mts +22 -3
- package/actions/turn.mjs +10 -0
- package/browser/Agent.d.mts +5 -5
- package/browser/Agent.mjs +31 -6
- package/browser/host.mjs +51 -7
- package/browser/index.d.mts +5 -0
- package/index.d.mts +5 -0
- package/internal.mjs +237 -21
- package/node/Agent.d.mts +3 -7
- package/node/Agent.mjs +48 -8
- package/node/host.mjs +78 -13
- package/node/index.d.mts +5 -0
- package/package.json +3 -3
- package/pkg-node/nanocodex.d.ts +84 -29
- package/pkg-node/nanocodex.js +221 -63
- package/pkg-node/nanocodex_bg.wasm +0 -0
- package/pkg-node/nanocodex_bg.wasm.d.ts +10 -2
- package/pkg-web/nanocodex.d.ts +94 -31
- package/pkg-web/nanocodex.js +220 -63
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +10 -2
- package/runtime/code-runtime.mjs +35 -12
- package/types.d.mts +67 -2
- package/scripts/check-package.mjs +0 -38
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,7 +15,7 @@ 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(
|
|
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
|
});
|
|
18
21
|
const onEvent = options.onEvent || (() => {});
|
|
@@ -23,28 +26,47 @@ export function createNodeHost(options = {}) {
|
|
|
23
26
|
const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
|
|
24
27
|
let nextHandle = 1;
|
|
25
28
|
|
|
26
|
-
function connect(endpoint, apiKey, sessionId) {
|
|
29
|
+
function connect(endpoint, apiKey, sessionId, metadata = {}) {
|
|
30
|
+
if (options.mpp) return connectMpp(endpoint);
|
|
27
31
|
return new Promise((resolve, reject) => {
|
|
28
32
|
let settled = false;
|
|
29
33
|
let upgradeResponse;
|
|
34
|
+
const 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": USER_AGENT,
|
|
43
|
+
};
|
|
44
|
+
if (metadata.accountId) headers["ChatGPT-Account-ID"] = metadata.accountId;
|
|
45
|
+
if (metadata.fedramp) headers["X-OpenAI-Fedramp"] = "true";
|
|
46
|
+
if (metadata.turnState) headers["x-codex-turn-state"] = metadata.turnState;
|
|
30
47
|
const socket = new WebSocket(endpoint, {
|
|
31
48
|
handshakeTimeout: connectTimeoutMs,
|
|
32
49
|
maxPayload: maxFrameBytes,
|
|
33
|
-
headers
|
|
34
|
-
Authorization: `Bearer ${apiKey}`,
|
|
35
|
-
"OpenAI-Beta": RESPONSES_WEBSOCKETS_BETA,
|
|
36
|
-
"x-openai-internal-codex-responses-lite": "true",
|
|
37
|
-
"session-id": sessionId,
|
|
38
|
-
"thread-id": sessionId,
|
|
39
|
-
"x-client-request-id": sessionId,
|
|
40
|
-
"x-responsesapi-include-timing-metrics": "true",
|
|
41
|
-
"User-Agent": "nanocodex-wasm/0.1.0",
|
|
42
|
-
},
|
|
50
|
+
headers,
|
|
43
51
|
});
|
|
44
52
|
const handle = nextHandle++;
|
|
45
53
|
const connection = queueState(socket);
|
|
46
54
|
|
|
47
55
|
socket.on("upgrade", (response) => { upgradeResponse = response; });
|
|
56
|
+
socket.on("unexpected-response", (_request, response) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
settled = true;
|
|
59
|
+
const chunks = [];
|
|
60
|
+
response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
61
|
+
response.on("end", () => {
|
|
62
|
+
const error = new Error(`WebSocket handshake was rejected with HTTP ${response.statusCode}`);
|
|
63
|
+
error.status = response.statusCode;
|
|
64
|
+
error.body = chunks.length ? Buffer.concat(chunks).toString("utf8") : "empty response body";
|
|
65
|
+
const retryAfter = Number(header(response.headers, "retry-after"));
|
|
66
|
+
if (Number.isFinite(retryAfter) && retryAfter >= 0) error.retryAfter = retryAfter;
|
|
67
|
+
reject(error);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
48
70
|
socket.on("open", () => {
|
|
49
71
|
settled = true;
|
|
50
72
|
connections.set(handle, connection);
|
|
@@ -80,6 +102,35 @@ export function createNodeHost(options = {}) {
|
|
|
80
102
|
});
|
|
81
103
|
}
|
|
82
104
|
|
|
105
|
+
async function connectMpp(endpoint) {
|
|
106
|
+
if (typeof options.mpp.ws !== "function") {
|
|
107
|
+
throw new TypeError("mpp must provide ws(endpoint)");
|
|
108
|
+
}
|
|
109
|
+
const socket = await options.mpp.ws(endpoint);
|
|
110
|
+
if (!socket || typeof socket.addEventListener !== "function") {
|
|
111
|
+
throw new TypeError("mpp.ws(endpoint) must return a WebSocket");
|
|
112
|
+
}
|
|
113
|
+
const handle = nextHandle++;
|
|
114
|
+
const connection = queueState(socket);
|
|
115
|
+
connection.managed = true;
|
|
116
|
+
connections.set(handle, connection);
|
|
117
|
+
socket.addEventListener("message", (event) => {
|
|
118
|
+
enqueue(connection, typeof event.data === "string"
|
|
119
|
+
? { kind: "text", text: event.data }
|
|
120
|
+
: { kind: "binary" });
|
|
121
|
+
});
|
|
122
|
+
socket.addEventListener("close", (event) => {
|
|
123
|
+
if (!connection.intentionallyClosed && !connection.overflowed) {
|
|
124
|
+
const suffix = event.reason ? `: ${event.reason}` : "";
|
|
125
|
+
enqueue(connection, { kind: "closed", detail: `with code ${event.code ?? 1000}${suffix}` });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
socket.addEventListener("error", () => {
|
|
129
|
+
enqueue(connection, { kind: "error", detail: "MPP WebSocket connection failed" });
|
|
130
|
+
});
|
|
131
|
+
return JSON.stringify({ handle, status: 101, reasoning_included: false });
|
|
132
|
+
}
|
|
133
|
+
|
|
83
134
|
function send(handle, message) {
|
|
84
135
|
const connection = connections.get(handle);
|
|
85
136
|
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
|
|
@@ -89,6 +140,18 @@ export function createNodeHost(options = {}) {
|
|
|
89
140
|
error: "WebSocket is no longer open",
|
|
90
141
|
}));
|
|
91
142
|
}
|
|
143
|
+
if (connection.managed) {
|
|
144
|
+
try {
|
|
145
|
+
connection.socket.send(JSON.stringify({ mpp: "message", data: message }));
|
|
146
|
+
return Promise.resolve(JSON.stringify({ ok: true }));
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return Promise.resolve(JSON.stringify({
|
|
149
|
+
ok: false,
|
|
150
|
+
reconnectable: connection.socket.readyState !== WebSocket.OPEN,
|
|
151
|
+
error: errorMessage(error),
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
92
155
|
return new Promise((resolve) => {
|
|
93
156
|
let completed = false;
|
|
94
157
|
const timer = setTimeout(() => finish({
|
|
@@ -159,7 +222,8 @@ export function createNodeHost(options = {}) {
|
|
|
159
222
|
detail: `receive queue exceeded ${maxQueuedMessages} messages or ${maxQueuedBytes} bytes`,
|
|
160
223
|
};
|
|
161
224
|
connection.queue.push({ message: error, bytes: messageBytes(error) });
|
|
162
|
-
connection.socket.terminate();
|
|
225
|
+
if (typeof connection.socket.terminate === "function") connection.socket.terminate();
|
|
226
|
+
else connection.socket.close(1009, "receive queue exceeded configured bounds");
|
|
163
227
|
return;
|
|
164
228
|
}
|
|
165
229
|
connection.queue.push({ message, bytes });
|
|
@@ -187,6 +251,7 @@ function queueState(socket) {
|
|
|
187
251
|
waiter: undefined,
|
|
188
252
|
intentionallyClosed: false,
|
|
189
253
|
overflowed: false,
|
|
254
|
+
managed: false,
|
|
190
255
|
};
|
|
191
256
|
}
|
|
192
257
|
|
package/node/index.d.mts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
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,
|
|
9
|
+
SessionSnapshot,
|
|
7
10
|
Thinking,
|
|
8
11
|
Tool,
|
|
9
12
|
ToolContext,
|
|
10
13
|
ToolMap,
|
|
11
14
|
Turn,
|
|
15
|
+
TurnResult,
|
|
16
|
+
TurnUsage,
|
|
12
17
|
} from "../types.mjs";
|
|
13
18
|
export * as Agent from "./Agent.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nanocodex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Viem-style Node and browser bindings for the Nanocodex WASM agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"pkg-node",
|
|
26
26
|
"pkg-web",
|
|
27
27
|
"runtime",
|
|
28
|
-
"scripts/check-package.mjs",
|
|
29
28
|
"internal.mjs",
|
|
30
29
|
"index.d.mts",
|
|
31
30
|
"index.mjs",
|
|
@@ -39,7 +38,8 @@
|
|
|
39
38
|
"scripts": {
|
|
40
39
|
"check:package": "node scripts/check-package.mjs",
|
|
41
40
|
"prepack": "npm run check:package",
|
|
42
|
-
"test": "node --test test/*.test.mjs && npm run test:typecheck && npm run check:package",
|
|
41
|
+
"test": "node --test test/*.test.mjs && npm run test:performance && npm run test:typecheck && npm run check:package",
|
|
42
|
+
"test:performance": "node --test test/performance.bench.mjs",
|
|
43
43
|
"test:typecheck": "tsc -p test/tsconfig.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
package/pkg-node/nanocodex.d.ts
CHANGED
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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,41 +24,63 @@ export class Nanocodex {
|
|
|
16
24
|
*/
|
|
17
25
|
fork(): Promise<Nanocodex>;
|
|
18
26
|
/**
|
|
19
|
-
*
|
|
27
|
+
* Forks from an exact completed historical turn.
|
|
20
28
|
*
|
|
21
29
|
* # Errors
|
|
22
30
|
*
|
|
23
|
-
* Rejects if the
|
|
24
|
-
* driver stops.
|
|
31
|
+
* Rejects if the result belongs to another agent or the driver stopped.
|
|
25
32
|
*/
|
|
26
|
-
forkFrom(
|
|
33
|
+
forkFrom(result: TurnResult): Promise<Nanocodex>;
|
|
27
34
|
/**
|
|
28
|
-
*
|
|
35
|
+
* Builds an agent from its JavaScript JSON configuration.
|
|
29
36
|
*
|
|
30
37
|
* # Errors
|
|
31
38
|
*
|
|
32
|
-
* Throws when the JSON or
|
|
39
|
+
* Throws when the JSON or agent policy is invalid.
|
|
33
40
|
*/
|
|
34
41
|
constructor(config_json: string);
|
|
35
42
|
/**
|
|
36
|
-
*
|
|
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
|
|
47
|
+
* Throws when the prompt is empty.
|
|
41
48
|
*/
|
|
42
49
|
prompt(instruction: string): Turn;
|
|
43
50
|
/**
|
|
44
|
-
*
|
|
51
|
+
* Accepts browser-safe multimodal input encoded as JSON.
|
|
45
52
|
*
|
|
46
53
|
* # Errors
|
|
47
54
|
*
|
|
48
|
-
* Throws for malformed
|
|
49
|
-
* stopped driver.
|
|
55
|
+
* Throws for malformed, empty, or local-filesystem input.
|
|
50
56
|
*/
|
|
51
57
|
promptContent(content_json: string): Turn;
|
|
52
58
|
/**
|
|
53
|
-
*
|
|
59
|
+
* Enables or disables priority processing for subsequently accepted turns.
|
|
60
|
+
*
|
|
61
|
+
* # Errors
|
|
62
|
+
*
|
|
63
|
+
* Rejects after the driver stops.
|
|
64
|
+
*/
|
|
65
|
+
setFastMode(enabled: boolean): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Changes the reasoning effort for subsequently accepted turns.
|
|
68
|
+
*
|
|
69
|
+
* # Errors
|
|
70
|
+
*
|
|
71
|
+
* Rejects an invalid effort or a stopped driver.
|
|
72
|
+
*/
|
|
73
|
+
setThinking(thinking: string): Promise<void>;
|
|
74
|
+
/**
|
|
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.
|
|
54
84
|
*
|
|
55
85
|
* # Errors
|
|
56
86
|
*
|
|
@@ -58,52 +88,77 @@ export class Nanocodex {
|
|
|
58
88
|
*/
|
|
59
89
|
spawn(): Promise<Nanocodex>;
|
|
60
90
|
/**
|
|
61
|
-
*
|
|
91
|
+
* Returns the stable `UUIDv7` session identity.
|
|
62
92
|
*/
|
|
63
93
|
readonly sessionId: string;
|
|
64
94
|
}
|
|
65
95
|
|
|
66
96
|
/**
|
|
67
|
-
*
|
|
97
|
+
* JavaScript binding over one shared Rust turn.
|
|
68
98
|
*/
|
|
69
99
|
export class Turn {
|
|
70
100
|
private constructor();
|
|
71
101
|
free(): void;
|
|
72
102
|
[Symbol.dispose](): void;
|
|
73
103
|
/**
|
|
74
|
-
*
|
|
104
|
+
* Cancels this exact active or queued turn.
|
|
75
105
|
*
|
|
76
106
|
* # Errors
|
|
77
107
|
*
|
|
78
|
-
* Rejects if the turn is already terminal or
|
|
108
|
+
* Rejects if the turn is already terminal or its driver stopped.
|
|
79
109
|
*/
|
|
80
110
|
cancel(): Promise<void>;
|
|
81
111
|
/**
|
|
82
|
-
*
|
|
112
|
+
* Waits for the final assistant message.
|
|
83
113
|
*
|
|
84
114
|
* # Errors
|
|
85
115
|
*
|
|
86
|
-
* Rejects when the model run
|
|
87
|
-
* await the same pending turn concurrently.
|
|
116
|
+
* Rejects when the model run or driver fails.
|
|
88
117
|
*/
|
|
89
|
-
result(): Promise<
|
|
118
|
+
result(): Promise<TurnResult>;
|
|
90
119
|
/**
|
|
91
|
-
*
|
|
120
|
+
* Injects text input at the active turn's next safe model boundary.
|
|
92
121
|
*
|
|
93
122
|
* # Errors
|
|
94
123
|
*
|
|
95
|
-
* Rejects
|
|
96
|
-
* or a stopped driver.
|
|
124
|
+
* Rejects if the turn is not active or its driver stopped.
|
|
97
125
|
*/
|
|
98
126
|
steer(instruction: string): Promise<void>;
|
|
99
127
|
/**
|
|
100
|
-
*
|
|
101
|
-
* turn's next safe model boundary.
|
|
128
|
+
* Injects browser-safe multimodal input at the active turn's next boundary.
|
|
102
129
|
*
|
|
103
130
|
* # Errors
|
|
104
131
|
*
|
|
105
|
-
* Rejects
|
|
106
|
-
* queued or terminal turn, a full steer queue, or a stopped driver.
|
|
132
|
+
* Rejects malformed input or a turn that is no longer active.
|
|
107
133
|
*/
|
|
108
134
|
steerContent(content_json: string): Promise<void>;
|
|
109
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;
|
|
144
|
+
/**
|
|
145
|
+
* Serializes this completed boundary's resumable session snapshot.
|
|
146
|
+
*
|
|
147
|
+
* # Errors
|
|
148
|
+
*
|
|
149
|
+
* Throws when serialization fails.
|
|
150
|
+
*/
|
|
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;
|
|
164
|
+
}
|