nanocodex 0.2.0 → 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 +72 -13
- package/actions/events.mjs +105 -22
- package/actions/index.mjs +2 -0
- package/actions/session.d.mts +7 -1
- package/actions/session.mjs +10 -0
- package/actions/turn.d.mts +18 -5
- package/actions/turn.mjs +7 -2
- package/browser/Agent.d.mts +0 -3
- package/browser/Agent.mjs +12 -2
- package/browser/host.mjs +11 -7
- package/browser/index.d.mts +4 -0
- package/index.d.mts +4 -0
- package/internal.mjs +225 -24
- package/node/Agent.d.mts +0 -5
- package/node/Agent.mjs +9 -2
- package/node/host.mjs +33 -12
- package/node/index.d.mts +4 -0
- package/package.json +3 -3
- package/pkg-node/nanocodex.d.ts +69 -38
- package/pkg-node/nanocodex.js +193 -89
- package/pkg-node/nanocodex_bg.wasm +0 -0
- package/pkg-node/nanocodex_bg.wasm.d.ts +7 -2
- package/pkg-web/nanocodex.d.ts +76 -40
- package/pkg-web/nanocodex.js +191 -88
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +7 -2
- package/runtime/code-runtime.mjs +29 -12
- package/types.d.mts +38 -3
- package/scripts/check-package.mjs +0 -38
package/internal.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const agentStates = new WeakMap();
|
|
2
|
+
const turnStates = new WeakMap();
|
|
3
|
+
const resultStates = new WeakMap();
|
|
3
4
|
const hostSessions = new Map();
|
|
4
5
|
const hostConnections = new Map();
|
|
5
6
|
let activeHost;
|
|
@@ -41,12 +42,18 @@ export function prompt(agent, options) {
|
|
|
41
42
|
|
|
42
43
|
export function getTurnResult(turn) {
|
|
43
44
|
const state = turnState(turn);
|
|
44
|
-
state.result ||= Promise.resolve()
|
|
45
|
+
state.result ||= Promise.resolve()
|
|
46
|
+
.then(() => state.raw.result())
|
|
47
|
+
.then(createTurnResult);
|
|
45
48
|
return state.result;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
export function getTurnSnapshot(
|
|
49
|
-
return
|
|
51
|
+
export function getTurnSnapshot(result) {
|
|
52
|
+
return resultState(result).snapshot();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getTurnUsage(result) {
|
|
56
|
+
return resultState(result).usage();
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export function steer(turn, options) {
|
|
@@ -66,7 +73,7 @@ export async function fork(agent, options) {
|
|
|
66
73
|
const at = options?.at;
|
|
67
74
|
const raw = at === undefined
|
|
68
75
|
? await state.raw.fork()
|
|
69
|
-
: await state.raw.forkFrom(
|
|
76
|
+
: await state.raw.forkFrom(resultState(at).raw);
|
|
70
77
|
return createAgent(raw, state.runtime);
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -83,7 +90,23 @@ export function setFastMode(agent, enabled) {
|
|
|
83
90
|
return agentState(agent).raw.setFastMode(enabled);
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
export function
|
|
93
|
+
export function compact(agent) {
|
|
94
|
+
return agentState(agent).raw.compact();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function shutdown(agent) {
|
|
98
|
+
const state = knownAgentState(agent);
|
|
99
|
+
if (state.shutdownPromise) return state.shutdownPromise;
|
|
100
|
+
if (state.disposed) throw new Error("the Nanocodex agent has been disposed");
|
|
101
|
+
if (typeof state.raw.shutdown !== "function") {
|
|
102
|
+
throw new Error("this Nanocodex runtime does not expose graceful shutdown");
|
|
103
|
+
}
|
|
104
|
+
state.disposed = true;
|
|
105
|
+
state.shutdownPromise = joinAgentShutdown(state);
|
|
106
|
+
return state.shutdownPromise;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function subscribeAgentEvents(agent, listener, options = {}, onRelease) {
|
|
87
110
|
const state = agentState(agent);
|
|
88
111
|
if (typeof state.runtime.subscribe !== "function") {
|
|
89
112
|
throw new Error("this Nanocodex runtime does not expose agent events");
|
|
@@ -91,11 +114,25 @@ export function subscribeAgentEvents(agent, listener, options = {}) {
|
|
|
91
114
|
if (typeof listener !== "function") {
|
|
92
115
|
throw new TypeError("watchAgentEvents requires a listener");
|
|
93
116
|
}
|
|
94
|
-
|
|
117
|
+
const unsubscribe = state.runtime.subscribe((event, encodedLength) => {
|
|
95
118
|
if (options.includeAllSessions || !event?.request_id || event.request_id === agent.sessionId) {
|
|
96
|
-
listener(event);
|
|
119
|
+
listener(event, encodedLength);
|
|
97
120
|
}
|
|
98
121
|
});
|
|
122
|
+
let active = true;
|
|
123
|
+
const subscription = {
|
|
124
|
+
close(notify) {
|
|
125
|
+
if (!active) return;
|
|
126
|
+
active = false;
|
|
127
|
+
state.subscriptions.delete(subscription);
|
|
128
|
+
const errors = [];
|
|
129
|
+
runCleanup(errors, () => unsubscribe?.());
|
|
130
|
+
if (notify) runCleanup(errors, () => onRelease?.());
|
|
131
|
+
throwCleanupErrors(errors);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
state.subscriptions.add(subscription);
|
|
135
|
+
return () => subscription.close(false);
|
|
99
136
|
}
|
|
100
137
|
|
|
101
138
|
export function toWasmConfig(options = {}) {
|
|
@@ -111,6 +148,7 @@ export function toWasmConfig(options = {}) {
|
|
|
111
148
|
copy(config, "api_base_url", options.apiBaseUrl);
|
|
112
149
|
copy(config, "instructions", options.instructions);
|
|
113
150
|
copy(config, "session_id", options.sessionId);
|
|
151
|
+
copy(config, "workspace", options.workspace);
|
|
114
152
|
copy(config, "resume", options.resume);
|
|
115
153
|
return config;
|
|
116
154
|
}
|
|
@@ -119,8 +157,10 @@ export function createEventChannel() {
|
|
|
119
157
|
const listeners = new Set();
|
|
120
158
|
return Object.freeze({
|
|
121
159
|
emit(eventJson) {
|
|
160
|
+
if (!listeners.size) return;
|
|
122
161
|
const event = typeof eventJson === "string" ? JSON.parse(eventJson) : eventJson;
|
|
123
|
-
|
|
162
|
+
const encodedLength = typeof eventJson === "string" ? eventJson.length : undefined;
|
|
163
|
+
for (const listener of listeners) listener(event, encodedLength);
|
|
124
164
|
},
|
|
125
165
|
subscribe(listener) {
|
|
126
166
|
listeners.add(listener);
|
|
@@ -150,9 +190,18 @@ export function releaseHostSession(host, sessionId) {
|
|
|
150
190
|
}
|
|
151
191
|
|
|
152
192
|
const hostBridge = Object.freeze({
|
|
153
|
-
async connect(endpoint, apiKey, sessionId) {
|
|
193
|
+
async connect(endpoint, apiKey, accountId, fedramp, sessionId, turnState) {
|
|
154
194
|
const host = requiredSessionHost(sessionId);
|
|
155
|
-
|
|
195
|
+
let result;
|
|
196
|
+
try {
|
|
197
|
+
result = JSON.parse(await host.connect(endpoint, apiKey, sessionId, {
|
|
198
|
+
accountId: accountId ?? undefined,
|
|
199
|
+
fedramp,
|
|
200
|
+
turnState: turnState ?? undefined,
|
|
201
|
+
}));
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw JSON.stringify(connectFailure(error));
|
|
204
|
+
}
|
|
156
205
|
const handle = nextHostConnection++;
|
|
157
206
|
hostConnections.set(handle, { host, handle: result.handle });
|
|
158
207
|
hostSessions.set(sessionId, host);
|
|
@@ -176,8 +225,12 @@ const hostBridge = Object.freeze({
|
|
|
176
225
|
hostConnections.delete(handle);
|
|
177
226
|
connection.host.close(connection.handle);
|
|
178
227
|
},
|
|
179
|
-
sleep(milliseconds) {
|
|
180
|
-
|
|
228
|
+
sleep(sessionId, milliseconds) {
|
|
229
|
+
const host = requiredSessionHost(sessionId);
|
|
230
|
+
if (typeof host.sleep !== "function") {
|
|
231
|
+
throw new TypeError("the selected Nanocodex host must define sleep(milliseconds)");
|
|
232
|
+
}
|
|
233
|
+
return host.sleep(milliseconds);
|
|
181
234
|
},
|
|
182
235
|
executeCode(source, sessionId, callId) {
|
|
183
236
|
return requiredSessionHost(sessionId).executeCode(source, sessionId, callId);
|
|
@@ -202,6 +255,9 @@ function createAgent(raw, runtime) {
|
|
|
202
255
|
raw,
|
|
203
256
|
runtime,
|
|
204
257
|
disposed: false,
|
|
258
|
+
released: false,
|
|
259
|
+
shutdownPromise: undefined,
|
|
260
|
+
subscriptions: new Set(),
|
|
205
261
|
sessionId: raw.sessionId,
|
|
206
262
|
uid: `agent-${nextAgentUid++}`,
|
|
207
263
|
};
|
|
@@ -234,14 +290,12 @@ function agentView(state, extensions) {
|
|
|
234
290
|
return agentView(state, deepMerge(extensions, extension));
|
|
235
291
|
},
|
|
236
292
|
dispose() {
|
|
237
|
-
if (state.
|
|
238
|
-
state
|
|
239
|
-
state.runtime.release?.(state.raw);
|
|
240
|
-
state.runtime.dispose(state.raw);
|
|
293
|
+
if (state.shutdownPromise) return;
|
|
294
|
+
releaseAgentState(state);
|
|
241
295
|
},
|
|
242
296
|
};
|
|
243
297
|
agent = Object.assign(base, extensions);
|
|
244
|
-
|
|
298
|
+
agentStates.set(agent, state);
|
|
245
299
|
return agent;
|
|
246
300
|
}
|
|
247
301
|
|
|
@@ -251,11 +305,87 @@ function requiredSessionHost(sessionId) {
|
|
|
251
305
|
return host;
|
|
252
306
|
}
|
|
253
307
|
|
|
308
|
+
function releaseAgentState(state) {
|
|
309
|
+
if (state.released) return;
|
|
310
|
+
state.disposed = true;
|
|
311
|
+
state.released = true;
|
|
312
|
+
const errors = [];
|
|
313
|
+
for (const subscription of [...state.subscriptions]) {
|
|
314
|
+
runCleanup(errors, () => subscription.close(true));
|
|
315
|
+
}
|
|
316
|
+
runCleanup(errors, () => state.runtime.release?.(state.raw));
|
|
317
|
+
runCleanup(errors, () => state.runtime.dispose(state.raw));
|
|
318
|
+
throwCleanupErrors(errors);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function joinAgentShutdown(state) {
|
|
322
|
+
await Promise.resolve();
|
|
323
|
+
let shutdownFailed = false;
|
|
324
|
+
let shutdownError;
|
|
325
|
+
try {
|
|
326
|
+
await state.raw.shutdown();
|
|
327
|
+
} catch (error) {
|
|
328
|
+
shutdownFailed = true;
|
|
329
|
+
shutdownError = error;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let cleanupFailed = false;
|
|
333
|
+
let cleanupError;
|
|
334
|
+
try {
|
|
335
|
+
releaseAgentState(state);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
cleanupFailed = true;
|
|
338
|
+
cleanupError = error;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (shutdownFailed && cleanupFailed) {
|
|
342
|
+
const cleanupErrors = cleanupError instanceof AggregateError
|
|
343
|
+
? cleanupError.errors
|
|
344
|
+
: [cleanupError];
|
|
345
|
+
throw new AggregateError(
|
|
346
|
+
[shutdownError, ...cleanupErrors],
|
|
347
|
+
"Nanocodex driver shutdown and resource release both failed",
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (shutdownFailed) throw shutdownError;
|
|
351
|
+
if (cleanupFailed) throw cleanupError;
|
|
352
|
+
}
|
|
353
|
+
|
|
254
354
|
function requiredActiveHost() {
|
|
255
355
|
if (!activeHost) throw new Error("no Nanocodex host is active");
|
|
256
356
|
return activeHost;
|
|
257
357
|
}
|
|
258
358
|
|
|
359
|
+
function connectFailure(error) {
|
|
360
|
+
if (typeof error === "string") {
|
|
361
|
+
try {
|
|
362
|
+
const encoded = JSON.parse(error);
|
|
363
|
+
if (encoded?.kind === "handshake_rejected" || encoded?.kind === "transport") {
|
|
364
|
+
return encoded;
|
|
365
|
+
}
|
|
366
|
+
} catch {}
|
|
367
|
+
}
|
|
368
|
+
const status = Number(error?.status);
|
|
369
|
+
if (Number.isInteger(status) && status >= 100 && status <= 599) {
|
|
370
|
+
const retryAfter = Number(error?.retryAfter);
|
|
371
|
+
return {
|
|
372
|
+
kind: "handshake_rejected",
|
|
373
|
+
status,
|
|
374
|
+
body: typeof error?.body === "string" ? error.body : errorDetail(error),
|
|
375
|
+
...(Number.isFinite(retryAfter) && retryAfter >= 0 ? { retry_after: retryAfter } : {}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
kind: "transport",
|
|
380
|
+
detail: errorDetail(error),
|
|
381
|
+
reconnectable: true,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function errorDetail(error) {
|
|
386
|
+
return error && (error.stack || error.message) || String(error);
|
|
387
|
+
}
|
|
388
|
+
|
|
259
389
|
function createTurn(raw, agent) {
|
|
260
390
|
if (!raw || typeof raw.result !== "function") {
|
|
261
391
|
throw new TypeError("the runtime returned an invalid Nanocodex turn handle");
|
|
@@ -264,7 +394,6 @@ function createTurn(raw, agent) {
|
|
|
264
394
|
const turn = {
|
|
265
395
|
get agent() { return state.agent; },
|
|
266
396
|
result: () => getTurnResult(turn),
|
|
267
|
-
snapshot: () => getTurnSnapshot(turn),
|
|
268
397
|
steer: (input) => steer(turn, input),
|
|
269
398
|
cancel: () => cancel(turn),
|
|
270
399
|
dispose() {
|
|
@@ -273,24 +402,67 @@ function createTurn(raw, agent) {
|
|
|
273
402
|
state.raw.free();
|
|
274
403
|
},
|
|
275
404
|
};
|
|
276
|
-
|
|
405
|
+
turnStates.set(turn, state);
|
|
277
406
|
return Object.freeze(turn);
|
|
278
407
|
}
|
|
279
408
|
|
|
409
|
+
function createTurnResult(raw) {
|
|
410
|
+
if (
|
|
411
|
+
!raw
|
|
412
|
+
|| typeof raw.finalMessage !== "string"
|
|
413
|
+
|| typeof raw.snapshot !== "function"
|
|
414
|
+
|| typeof raw.usage !== "function"
|
|
415
|
+
) {
|
|
416
|
+
raw?.free?.();
|
|
417
|
+
throw new TypeError("the runtime returned an invalid Nanocodex turn result");
|
|
418
|
+
}
|
|
419
|
+
const state = {
|
|
420
|
+
raw,
|
|
421
|
+
snapshotValue: undefined,
|
|
422
|
+
usageValue: undefined,
|
|
423
|
+
snapshot() {
|
|
424
|
+
state.snapshotValue ||= freezeJson(JSON.parse(raw.snapshot()));
|
|
425
|
+
return state.snapshotValue;
|
|
426
|
+
},
|
|
427
|
+
usage() {
|
|
428
|
+
state.usageValue ||= freezeJson(JSON.parse(raw.usage()));
|
|
429
|
+
return state.usageValue;
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
const result = {
|
|
433
|
+
finalMessage: raw.finalMessage,
|
|
434
|
+
get snapshot() { return state.snapshot(); },
|
|
435
|
+
get usage() { return state.usage(); },
|
|
436
|
+
};
|
|
437
|
+
resultStates.set(result, state);
|
|
438
|
+
return Object.freeze(result);
|
|
439
|
+
}
|
|
440
|
+
|
|
280
441
|
function agentState(agent) {
|
|
281
|
-
const state = agent
|
|
282
|
-
if (!state) throw new TypeError("expected a Nanocodex agent");
|
|
442
|
+
const state = knownAgentState(agent);
|
|
283
443
|
if (state.disposed) throw new Error("the Nanocodex agent has been disposed");
|
|
284
444
|
return state;
|
|
285
445
|
}
|
|
286
446
|
|
|
447
|
+
function knownAgentState(agent) {
|
|
448
|
+
const state = agentStates.get(agent);
|
|
449
|
+
if (!state) throw new TypeError("expected a Nanocodex agent");
|
|
450
|
+
return state;
|
|
451
|
+
}
|
|
452
|
+
|
|
287
453
|
function turnState(turn) {
|
|
288
|
-
const state = turn
|
|
454
|
+
const state = turnStates.get(turn);
|
|
289
455
|
if (!state) throw new TypeError("expected a Nanocodex turn");
|
|
290
456
|
if (state.disposed) throw new Error("the Nanocodex turn has been disposed");
|
|
291
457
|
return state;
|
|
292
458
|
}
|
|
293
459
|
|
|
460
|
+
function resultState(result) {
|
|
461
|
+
const state = resultStates.get(result);
|
|
462
|
+
if (!state) throw new TypeError("expected a completed Nanocodex turn result");
|
|
463
|
+
return state;
|
|
464
|
+
}
|
|
465
|
+
|
|
294
466
|
function actionInput(options) {
|
|
295
467
|
const input = options?.input;
|
|
296
468
|
if (typeof input !== "string" && !Array.isArray(input)) {
|
|
@@ -316,3 +488,32 @@ function isObject(value) {
|
|
|
316
488
|
function copy(target, key, value) {
|
|
317
489
|
if (value !== undefined) target[key] = value;
|
|
318
490
|
}
|
|
491
|
+
|
|
492
|
+
function runCleanup(errors, cleanup) {
|
|
493
|
+
try {
|
|
494
|
+
cleanup();
|
|
495
|
+
} catch (error) {
|
|
496
|
+
errors.push(error);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function throwCleanupErrors(errors) {
|
|
501
|
+
if (errors.length === 1) throw errors[0];
|
|
502
|
+
if (errors.length > 1) {
|
|
503
|
+
throw new AggregateError(errors, "multiple Nanocodex resources failed to release");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function freezeJson(value) {
|
|
508
|
+
if (!value || typeof value !== "object") return value;
|
|
509
|
+
const pending = [value];
|
|
510
|
+
while (pending.length) {
|
|
511
|
+
const current = pending.pop();
|
|
512
|
+
if (Object.isFrozen(current)) continue;
|
|
513
|
+
for (const child of Object.values(current)) {
|
|
514
|
+
if (child && typeof child === "object" && !Object.isFrozen(child)) pending.push(child);
|
|
515
|
+
}
|
|
516
|
+
Object.freeze(current);
|
|
517
|
+
}
|
|
518
|
+
return value;
|
|
519
|
+
}
|
package/node/Agent.d.mts
CHANGED
|
@@ -12,12 +12,7 @@ export function create(options: create.Options): Promise<create.ReturnType>;
|
|
|
12
12
|
export declare namespace create {
|
|
13
13
|
type Options = AgentOptions & ({ apiKey: string; mpp?: never } | { apiKey?: never; mpp: MppSession }) & {
|
|
14
14
|
apiBaseUrl?: string | undefined;
|
|
15
|
-
connectTimeoutMs?: number | undefined;
|
|
16
|
-
maxFrameBytes?: number | undefined;
|
|
17
|
-
maxQueuedBytes?: number | undefined;
|
|
18
|
-
maxQueuedMessages?: number | undefined;
|
|
19
15
|
module?: unknown;
|
|
20
|
-
sendTimeoutMs?: number | undefined;
|
|
21
16
|
tools?: ToolMap | undefined;
|
|
22
17
|
websocketUrl?: string | undefined;
|
|
23
18
|
};
|
package/node/Agent.mjs
CHANGED
|
@@ -23,19 +23,25 @@ export function create(options = {}) {
|
|
|
23
23
|
fastMode,
|
|
24
24
|
instructions,
|
|
25
25
|
sessionId,
|
|
26
|
+
workspace,
|
|
26
27
|
resume,
|
|
27
28
|
apiKey,
|
|
28
29
|
mpp,
|
|
29
30
|
websocketUrl,
|
|
30
31
|
apiBaseUrl,
|
|
31
32
|
module,
|
|
32
|
-
|
|
33
|
+
tools,
|
|
33
34
|
} = options;
|
|
34
35
|
const events = createEventChannel();
|
|
35
36
|
if (mpp !== undefined && apiKey !== undefined) {
|
|
36
37
|
throw new TypeError("apiKey and mpp are mutually exclusive");
|
|
37
38
|
}
|
|
38
|
-
const host = createNodeHost({
|
|
39
|
+
const host = createNodeHost({
|
|
40
|
+
mpp,
|
|
41
|
+
onEvent: events.emit,
|
|
42
|
+
tools,
|
|
43
|
+
workspace: workspace ?? resume?.workspace,
|
|
44
|
+
});
|
|
39
45
|
activateHost(host);
|
|
40
46
|
const runtime = defineRuntime({
|
|
41
47
|
key: "node-wasm",
|
|
@@ -67,6 +73,7 @@ export function create(options = {}) {
|
|
|
67
73
|
fastMode,
|
|
68
74
|
instructions,
|
|
69
75
|
sessionId,
|
|
76
|
+
workspace,
|
|
70
77
|
resume,
|
|
71
78
|
});
|
|
72
79
|
}
|
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,29 +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 = {}) {
|
|
27
30
|
if (options.mpp) return connectMpp(endpoint);
|
|
28
31
|
return new Promise((resolve, reject) => {
|
|
29
32
|
let settled = false;
|
|
30
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;
|
|
31
47
|
const socket = new WebSocket(endpoint, {
|
|
32
48
|
handshakeTimeout: connectTimeoutMs,
|
|
33
49
|
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
|
-
},
|
|
50
|
+
headers,
|
|
44
51
|
});
|
|
45
52
|
const handle = nextHandle++;
|
|
46
53
|
const connection = queueState(socket);
|
|
47
54
|
|
|
48
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
|
+
});
|
|
49
70
|
socket.on("open", () => {
|
|
50
71
|
settled = true;
|
|
51
72
|
connections.set(handle, connection);
|
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.
|
|
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": {
|