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/README.md +111 -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 +14 -6
- package/browser/Agent.mjs +21 -2
- package/browser/host.d.mts +33 -1
- package/browser/host.mjs +53 -11
- package/browser/index.d.mts +4 -0
- package/index.d.mts +4 -0
- package/internal.mjs +233 -24
- package/node/Agent.d.mts +1 -5
- package/node/Agent.mjs +13 -2
- package/node/host.mjs +39 -12
- package/node/index.d.mts +4 -0
- package/package.json +8 -3
- package/pkg-node/nanocodex.d.ts +69 -38
- package/pkg-node/nanocodex.js +203 -88
- 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 +201 -87
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +7 -2
- package/runtime/code-runtime.mjs +58 -12
- package/types.d.mts +40 -3
- package/wasm.d.mts +4 -0
- package/scripts/check-package.mjs +0 -38
package/browser/host.mjs
CHANGED
|
@@ -3,26 +3,41 @@ import { createCodeRuntime } from "../runtime/code-runtime.mjs";
|
|
|
3
3
|
const DEFAULT_MAX_QUEUED_MESSAGES = 4_096;
|
|
4
4
|
const DEFAULT_MAX_QUEUED_BYTES = 32 * 1024 * 1024;
|
|
5
5
|
const DEFAULT_MAX_BUFFERED_SEND_BYTES = 16 * 1024 * 1024;
|
|
6
|
+
const WEBSOCKET_OPEN = 1;
|
|
6
7
|
|
|
7
8
|
export function createBrowserHost(options = {}) {
|
|
8
|
-
const WebSocketImpl = options.WebSocketImpl
|
|
9
|
-
|
|
9
|
+
const WebSocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
|
|
10
|
+
const createWebSocket = options.createWebSocket
|
|
11
|
+
?? (WebSocketImpl && ((endpoint) => new WebSocketImpl(endpoint)));
|
|
12
|
+
if (!options.mpp && !createWebSocket) {
|
|
13
|
+
throw new Error("WebSocket is unavailable in this runtime");
|
|
14
|
+
}
|
|
10
15
|
const connections = new Map();
|
|
11
16
|
const code = createCodeRuntime(options.tools);
|
|
17
|
+
const toolMode = options.toolMode ?? "code";
|
|
18
|
+
if (toolMode !== "code" && toolMode !== "direct") {
|
|
19
|
+
throw new TypeError("toolMode must be code or direct");
|
|
20
|
+
}
|
|
12
21
|
const onEvent = options.onEvent || (() => {});
|
|
13
|
-
const createWebSocket = options.createWebSocket || ((endpoint) => new WebSocketImpl(endpoint));
|
|
14
22
|
const maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
|
|
15
23
|
const maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
|
|
16
24
|
const maxBufferedSendBytes = options.maxBufferedSendBytes ?? DEFAULT_MAX_BUFFERED_SEND_BYTES;
|
|
17
25
|
const encoder = new TextEncoder();
|
|
18
26
|
let nextHandle = 1;
|
|
19
27
|
|
|
20
|
-
function connect(endpoint,
|
|
28
|
+
async function connect(endpoint, apiKey, sessionId, metadata = {}) {
|
|
21
29
|
if (options.mpp) return connectMpp(endpoint);
|
|
30
|
+
const authorization = options.hostAuth
|
|
31
|
+
? { authorization: "host_managed" }
|
|
32
|
+
: { authorization: "bearer", bearerToken: apiKey };
|
|
33
|
+
const request = { ...metadata };
|
|
34
|
+
delete request.authorization;
|
|
35
|
+
delete request.bearerToken;
|
|
36
|
+
Object.assign(request, authorization);
|
|
37
|
+
const opened = await createWebSocket(endpoint, sessionId, request);
|
|
38
|
+
const { socket, ...handshake } = normalizeWebSocketConnection(opened);
|
|
22
39
|
return new Promise((resolve, reject) => {
|
|
23
40
|
let settled = false;
|
|
24
|
-
const handle = nextHandle++;
|
|
25
|
-
const socket = createWebSocket(endpoint, sessionId);
|
|
26
41
|
const connection = {
|
|
27
42
|
socket,
|
|
28
43
|
queue: [],
|
|
@@ -31,11 +46,21 @@ export function createBrowserHost(options = {}) {
|
|
|
31
46
|
intentionallyClosed: false,
|
|
32
47
|
overflowed: false,
|
|
33
48
|
};
|
|
34
|
-
|
|
49
|
+
const resolveOpen = () => {
|
|
50
|
+
if (settled) return;
|
|
35
51
|
settled = true;
|
|
52
|
+
const handle = nextHandle++;
|
|
36
53
|
connections.set(handle, connection);
|
|
37
|
-
resolve(JSON.stringify({
|
|
38
|
-
|
|
54
|
+
resolve(JSON.stringify({
|
|
55
|
+
handle,
|
|
56
|
+
status: handshake.status ?? 101,
|
|
57
|
+
request_id: handshake.requestId,
|
|
58
|
+
server_model: handshake.serverModel,
|
|
59
|
+
reasoning_included: handshake.reasoningIncluded ?? false,
|
|
60
|
+
turn_state: handshake.turnState,
|
|
61
|
+
}));
|
|
62
|
+
};
|
|
63
|
+
socket.addEventListener("open", resolveOpen, { once: true });
|
|
39
64
|
socket.addEventListener("message", (event) => {
|
|
40
65
|
enqueue(connection, typeof event.data === "string"
|
|
41
66
|
? { kind: "text", text: event.data }
|
|
@@ -57,6 +82,11 @@ export function createBrowserHost(options = {}) {
|
|
|
57
82
|
enqueue(connection, { kind: "error", detail: "WebSocket connection failed" });
|
|
58
83
|
}
|
|
59
84
|
});
|
|
85
|
+
if (socket.readyState === WEBSOCKET_OPEN) resolveOpen();
|
|
86
|
+
else if (socket.readyState > WEBSOCKET_OPEN) {
|
|
87
|
+
settled = true;
|
|
88
|
+
reject(new Error("WebSocket closed during connection"));
|
|
89
|
+
}
|
|
60
90
|
});
|
|
61
91
|
}
|
|
62
92
|
|
|
@@ -97,7 +127,7 @@ export function createBrowserHost(options = {}) {
|
|
|
97
127
|
|
|
98
128
|
function send(handle, message) {
|
|
99
129
|
const connection = connections.get(handle);
|
|
100
|
-
if (!connection || connection.socket.readyState !==
|
|
130
|
+
if (!connection || connection.socket.readyState !== WEBSOCKET_OPEN) {
|
|
101
131
|
return Promise.resolve(JSON.stringify({
|
|
102
132
|
ok: false,
|
|
103
133
|
reconnectable: true,
|
|
@@ -123,7 +153,7 @@ export function createBrowserHost(options = {}) {
|
|
|
123
153
|
} catch (error) {
|
|
124
154
|
return Promise.resolve(JSON.stringify({
|
|
125
155
|
ok: false,
|
|
126
|
-
reconnectable: connection.socket.readyState !==
|
|
156
|
+
reconnectable: connection.socket.readyState !== WEBSOCKET_OPEN,
|
|
127
157
|
error: error instanceof Error ? error.message : String(error),
|
|
128
158
|
}));
|
|
129
159
|
}
|
|
@@ -194,8 +224,20 @@ export function createBrowserHost(options = {}) {
|
|
|
194
224
|
close,
|
|
195
225
|
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
196
226
|
executeCode: code.executeCode,
|
|
227
|
+
executeTool: code.executeTool,
|
|
228
|
+
toolMode: () => toolMode,
|
|
197
229
|
toolDefinitions: code.toolDefinitions,
|
|
198
230
|
emitEvent: onEvent,
|
|
199
231
|
reset: code.reset,
|
|
200
232
|
});
|
|
201
233
|
}
|
|
234
|
+
|
|
235
|
+
function normalizeWebSocketConnection(opened) {
|
|
236
|
+
if (opened?.socket && typeof opened.socket.addEventListener === "function") {
|
|
237
|
+
return opened;
|
|
238
|
+
}
|
|
239
|
+
if (!opened || typeof opened.addEventListener !== "function") {
|
|
240
|
+
throw new TypeError("createWebSocket must return a WebSocket or a connection descriptor");
|
|
241
|
+
}
|
|
242
|
+
return { socket: opened };
|
|
243
|
+
}
|
package/browser/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/index.d.mts
CHANGED
|
@@ -4,8 +4,10 @@ export type {
|
|
|
4
4
|
AgentActions,
|
|
5
5
|
AgentEvent,
|
|
6
6
|
AgentOptions,
|
|
7
|
+
CostStatus,
|
|
7
8
|
DefaultAgent,
|
|
8
9
|
EventWatcher,
|
|
10
|
+
EstimatedUsdCost,
|
|
9
11
|
ForkOptions,
|
|
10
12
|
PromptInput,
|
|
11
13
|
PromptItem,
|
|
@@ -16,5 +18,7 @@ export type {
|
|
|
16
18
|
ToolContext,
|
|
17
19
|
ToolMap,
|
|
18
20
|
Turn,
|
|
21
|
+
TurnResult,
|
|
22
|
+
TurnUsage,
|
|
19
23
|
WatchEventsOptions,
|
|
20
24
|
} from "./types.mjs";
|
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 = {}) {
|
|
@@ -104,6 +141,7 @@ export function toWasmConfig(options = {}) {
|
|
|
104
141
|
throw new TypeError("apiKey must be a non-empty string");
|
|
105
142
|
}
|
|
106
143
|
const config = { api_key: apiKey };
|
|
144
|
+
copy(config, "model", options.model);
|
|
107
145
|
copy(config, "thinking", options.thinking);
|
|
108
146
|
copy(config, "reasoning_mode", options.reasoningMode);
|
|
109
147
|
copy(config, "fast_mode", options.fastMode);
|
|
@@ -111,6 +149,7 @@ export function toWasmConfig(options = {}) {
|
|
|
111
149
|
copy(config, "api_base_url", options.apiBaseUrl);
|
|
112
150
|
copy(config, "instructions", options.instructions);
|
|
113
151
|
copy(config, "session_id", options.sessionId);
|
|
152
|
+
copy(config, "workspace", options.workspace);
|
|
114
153
|
copy(config, "resume", options.resume);
|
|
115
154
|
return config;
|
|
116
155
|
}
|
|
@@ -119,8 +158,10 @@ export function createEventChannel() {
|
|
|
119
158
|
const listeners = new Set();
|
|
120
159
|
return Object.freeze({
|
|
121
160
|
emit(eventJson) {
|
|
161
|
+
if (!listeners.size) return;
|
|
122
162
|
const event = typeof eventJson === "string" ? JSON.parse(eventJson) : eventJson;
|
|
123
|
-
|
|
163
|
+
const encodedLength = typeof eventJson === "string" ? eventJson.length : undefined;
|
|
164
|
+
for (const listener of listeners) listener(event, encodedLength);
|
|
124
165
|
},
|
|
125
166
|
subscribe(listener) {
|
|
126
167
|
listeners.add(listener);
|
|
@@ -150,9 +191,18 @@ export function releaseHostSession(host, sessionId) {
|
|
|
150
191
|
}
|
|
151
192
|
|
|
152
193
|
const hostBridge = Object.freeze({
|
|
153
|
-
async connect(endpoint, apiKey, sessionId) {
|
|
194
|
+
async connect(endpoint, apiKey, accountId, fedramp, sessionId, turnState) {
|
|
154
195
|
const host = requiredSessionHost(sessionId);
|
|
155
|
-
|
|
196
|
+
let result;
|
|
197
|
+
try {
|
|
198
|
+
result = JSON.parse(await host.connect(endpoint, apiKey, sessionId, {
|
|
199
|
+
accountId: accountId ?? undefined,
|
|
200
|
+
fedramp,
|
|
201
|
+
turnState: turnState ?? undefined,
|
|
202
|
+
}));
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw JSON.stringify(connectFailure(error));
|
|
205
|
+
}
|
|
156
206
|
const handle = nextHostConnection++;
|
|
157
207
|
hostConnections.set(handle, { host, handle: result.handle });
|
|
158
208
|
hostSessions.set(sessionId, host);
|
|
@@ -176,12 +226,23 @@ const hostBridge = Object.freeze({
|
|
|
176
226
|
hostConnections.delete(handle);
|
|
177
227
|
connection.host.close(connection.handle);
|
|
178
228
|
},
|
|
179
|
-
sleep(milliseconds) {
|
|
180
|
-
|
|
229
|
+
sleep(sessionId, milliseconds) {
|
|
230
|
+
const host = requiredSessionHost(sessionId);
|
|
231
|
+
if (typeof host.sleep !== "function") {
|
|
232
|
+
throw new TypeError("the selected Nanocodex host must define sleep(milliseconds)");
|
|
233
|
+
}
|
|
234
|
+
return host.sleep(milliseconds);
|
|
181
235
|
},
|
|
182
236
|
executeCode(source, sessionId, callId) {
|
|
183
237
|
return requiredSessionHost(sessionId).executeCode(source, sessionId, callId);
|
|
184
238
|
},
|
|
239
|
+
executeTool(name, input, sessionId, callId) {
|
|
240
|
+
return requiredSessionHost(sessionId).executeTool(name, input, sessionId, callId);
|
|
241
|
+
},
|
|
242
|
+
toolMode(sessionId) {
|
|
243
|
+
// The WASM constructor asks before its session is adopted.
|
|
244
|
+
return (hostSessions.get(sessionId) ?? requiredActiveHost()).toolMode();
|
|
245
|
+
},
|
|
185
246
|
toolDefinitions(sessionId) {
|
|
186
247
|
// ModelRun builds its stable tool prefix inside the WASM constructor,
|
|
187
248
|
// immediately before the returned session can be adopted. Runtime
|
|
@@ -202,6 +263,9 @@ function createAgent(raw, runtime) {
|
|
|
202
263
|
raw,
|
|
203
264
|
runtime,
|
|
204
265
|
disposed: false,
|
|
266
|
+
released: false,
|
|
267
|
+
shutdownPromise: undefined,
|
|
268
|
+
subscriptions: new Set(),
|
|
205
269
|
sessionId: raw.sessionId,
|
|
206
270
|
uid: `agent-${nextAgentUid++}`,
|
|
207
271
|
};
|
|
@@ -234,14 +298,12 @@ function agentView(state, extensions) {
|
|
|
234
298
|
return agentView(state, deepMerge(extensions, extension));
|
|
235
299
|
},
|
|
236
300
|
dispose() {
|
|
237
|
-
if (state.
|
|
238
|
-
state
|
|
239
|
-
state.runtime.release?.(state.raw);
|
|
240
|
-
state.runtime.dispose(state.raw);
|
|
301
|
+
if (state.shutdownPromise) return;
|
|
302
|
+
releaseAgentState(state);
|
|
241
303
|
},
|
|
242
304
|
};
|
|
243
305
|
agent = Object.assign(base, extensions);
|
|
244
|
-
|
|
306
|
+
agentStates.set(agent, state);
|
|
245
307
|
return agent;
|
|
246
308
|
}
|
|
247
309
|
|
|
@@ -251,11 +313,87 @@ function requiredSessionHost(sessionId) {
|
|
|
251
313
|
return host;
|
|
252
314
|
}
|
|
253
315
|
|
|
316
|
+
function releaseAgentState(state) {
|
|
317
|
+
if (state.released) return;
|
|
318
|
+
state.disposed = true;
|
|
319
|
+
state.released = true;
|
|
320
|
+
const errors = [];
|
|
321
|
+
for (const subscription of [...state.subscriptions]) {
|
|
322
|
+
runCleanup(errors, () => subscription.close(true));
|
|
323
|
+
}
|
|
324
|
+
runCleanup(errors, () => state.runtime.release?.(state.raw));
|
|
325
|
+
runCleanup(errors, () => state.runtime.dispose(state.raw));
|
|
326
|
+
throwCleanupErrors(errors);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function joinAgentShutdown(state) {
|
|
330
|
+
await Promise.resolve();
|
|
331
|
+
let shutdownFailed = false;
|
|
332
|
+
let shutdownError;
|
|
333
|
+
try {
|
|
334
|
+
await state.raw.shutdown();
|
|
335
|
+
} catch (error) {
|
|
336
|
+
shutdownFailed = true;
|
|
337
|
+
shutdownError = error;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let cleanupFailed = false;
|
|
341
|
+
let cleanupError;
|
|
342
|
+
try {
|
|
343
|
+
releaseAgentState(state);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
cleanupFailed = true;
|
|
346
|
+
cleanupError = error;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (shutdownFailed && cleanupFailed) {
|
|
350
|
+
const cleanupErrors = cleanupError instanceof AggregateError
|
|
351
|
+
? cleanupError.errors
|
|
352
|
+
: [cleanupError];
|
|
353
|
+
throw new AggregateError(
|
|
354
|
+
[shutdownError, ...cleanupErrors],
|
|
355
|
+
"Nanocodex driver shutdown and resource release both failed",
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (shutdownFailed) throw shutdownError;
|
|
359
|
+
if (cleanupFailed) throw cleanupError;
|
|
360
|
+
}
|
|
361
|
+
|
|
254
362
|
function requiredActiveHost() {
|
|
255
363
|
if (!activeHost) throw new Error("no Nanocodex host is active");
|
|
256
364
|
return activeHost;
|
|
257
365
|
}
|
|
258
366
|
|
|
367
|
+
function connectFailure(error) {
|
|
368
|
+
if (typeof error === "string") {
|
|
369
|
+
try {
|
|
370
|
+
const encoded = JSON.parse(error);
|
|
371
|
+
if (encoded?.kind === "handshake_rejected" || encoded?.kind === "transport") {
|
|
372
|
+
return encoded;
|
|
373
|
+
}
|
|
374
|
+
} catch {}
|
|
375
|
+
}
|
|
376
|
+
const status = Number(error?.status);
|
|
377
|
+
if (Number.isInteger(status) && status >= 100 && status <= 599) {
|
|
378
|
+
const retryAfter = Number(error?.retryAfter);
|
|
379
|
+
return {
|
|
380
|
+
kind: "handshake_rejected",
|
|
381
|
+
status,
|
|
382
|
+
body: typeof error?.body === "string" ? error.body : errorDetail(error),
|
|
383
|
+
...(Number.isFinite(retryAfter) && retryAfter >= 0 ? { retry_after: retryAfter } : {}),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
kind: "transport",
|
|
388
|
+
detail: errorDetail(error),
|
|
389
|
+
reconnectable: true,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function errorDetail(error) {
|
|
394
|
+
return error && (error.stack || error.message) || String(error);
|
|
395
|
+
}
|
|
396
|
+
|
|
259
397
|
function createTurn(raw, agent) {
|
|
260
398
|
if (!raw || typeof raw.result !== "function") {
|
|
261
399
|
throw new TypeError("the runtime returned an invalid Nanocodex turn handle");
|
|
@@ -264,7 +402,6 @@ function createTurn(raw, agent) {
|
|
|
264
402
|
const turn = {
|
|
265
403
|
get agent() { return state.agent; },
|
|
266
404
|
result: () => getTurnResult(turn),
|
|
267
|
-
snapshot: () => getTurnSnapshot(turn),
|
|
268
405
|
steer: (input) => steer(turn, input),
|
|
269
406
|
cancel: () => cancel(turn),
|
|
270
407
|
dispose() {
|
|
@@ -273,24 +410,67 @@ function createTurn(raw, agent) {
|
|
|
273
410
|
state.raw.free();
|
|
274
411
|
},
|
|
275
412
|
};
|
|
276
|
-
|
|
413
|
+
turnStates.set(turn, state);
|
|
277
414
|
return Object.freeze(turn);
|
|
278
415
|
}
|
|
279
416
|
|
|
417
|
+
function createTurnResult(raw) {
|
|
418
|
+
if (
|
|
419
|
+
!raw
|
|
420
|
+
|| typeof raw.finalMessage !== "string"
|
|
421
|
+
|| typeof raw.snapshot !== "function"
|
|
422
|
+
|| typeof raw.usage !== "function"
|
|
423
|
+
) {
|
|
424
|
+
raw?.free?.();
|
|
425
|
+
throw new TypeError("the runtime returned an invalid Nanocodex turn result");
|
|
426
|
+
}
|
|
427
|
+
const state = {
|
|
428
|
+
raw,
|
|
429
|
+
snapshotValue: undefined,
|
|
430
|
+
usageValue: undefined,
|
|
431
|
+
snapshot() {
|
|
432
|
+
state.snapshotValue ||= freezeJson(JSON.parse(raw.snapshot()));
|
|
433
|
+
return state.snapshotValue;
|
|
434
|
+
},
|
|
435
|
+
usage() {
|
|
436
|
+
state.usageValue ||= freezeJson(JSON.parse(raw.usage()));
|
|
437
|
+
return state.usageValue;
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
const result = {
|
|
441
|
+
finalMessage: raw.finalMessage,
|
|
442
|
+
get snapshot() { return state.snapshot(); },
|
|
443
|
+
get usage() { return state.usage(); },
|
|
444
|
+
};
|
|
445
|
+
resultStates.set(result, state);
|
|
446
|
+
return Object.freeze(result);
|
|
447
|
+
}
|
|
448
|
+
|
|
280
449
|
function agentState(agent) {
|
|
281
|
-
const state = agent
|
|
282
|
-
if (!state) throw new TypeError("expected a Nanocodex agent");
|
|
450
|
+
const state = knownAgentState(agent);
|
|
283
451
|
if (state.disposed) throw new Error("the Nanocodex agent has been disposed");
|
|
284
452
|
return state;
|
|
285
453
|
}
|
|
286
454
|
|
|
455
|
+
function knownAgentState(agent) {
|
|
456
|
+
const state = agentStates.get(agent);
|
|
457
|
+
if (!state) throw new TypeError("expected a Nanocodex agent");
|
|
458
|
+
return state;
|
|
459
|
+
}
|
|
460
|
+
|
|
287
461
|
function turnState(turn) {
|
|
288
|
-
const state = turn
|
|
462
|
+
const state = turnStates.get(turn);
|
|
289
463
|
if (!state) throw new TypeError("expected a Nanocodex turn");
|
|
290
464
|
if (state.disposed) throw new Error("the Nanocodex turn has been disposed");
|
|
291
465
|
return state;
|
|
292
466
|
}
|
|
293
467
|
|
|
468
|
+
function resultState(result) {
|
|
469
|
+
const state = resultStates.get(result);
|
|
470
|
+
if (!state) throw new TypeError("expected a completed Nanocodex turn result");
|
|
471
|
+
return state;
|
|
472
|
+
}
|
|
473
|
+
|
|
294
474
|
function actionInput(options) {
|
|
295
475
|
const input = options?.input;
|
|
296
476
|
if (typeof input !== "string" && !Array.isArray(input)) {
|
|
@@ -316,3 +496,32 @@ function isObject(value) {
|
|
|
316
496
|
function copy(target, key, value) {
|
|
317
497
|
if (value !== undefined) target[key] = value;
|
|
318
498
|
}
|
|
499
|
+
|
|
500
|
+
function runCleanup(errors, cleanup) {
|
|
501
|
+
try {
|
|
502
|
+
cleanup();
|
|
503
|
+
} catch (error) {
|
|
504
|
+
errors.push(error);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function throwCleanupErrors(errors) {
|
|
509
|
+
if (errors.length === 1) throw errors[0];
|
|
510
|
+
if (errors.length > 1) {
|
|
511
|
+
throw new AggregateError(errors, "multiple Nanocodex resources failed to release");
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function freezeJson(value) {
|
|
516
|
+
if (!value || typeof value !== "object") return value;
|
|
517
|
+
const pending = [value];
|
|
518
|
+
while (pending.length) {
|
|
519
|
+
const current = pending.pop();
|
|
520
|
+
if (Object.isFrozen(current)) continue;
|
|
521
|
+
for (const child of Object.values(current)) {
|
|
522
|
+
if (child && typeof child === "object" && !Object.isFrozen(child)) pending.push(child);
|
|
523
|
+
}
|
|
524
|
+
Object.freeze(current);
|
|
525
|
+
}
|
|
526
|
+
return value;
|
|
527
|
+
}
|
package/node/Agent.d.mts
CHANGED
|
@@ -12,13 +12,9 @@ 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;
|
|
17
|
+
toolMode?: "code" | "direct" | undefined;
|
|
22
18
|
websocketUrl?: string | undefined;
|
|
23
19
|
};
|
|
24
20
|
type ReturnType = Agent;
|
package/node/Agent.mjs
CHANGED
|
@@ -18,24 +18,33 @@ let NodeNanocodex;
|
|
|
18
18
|
|
|
19
19
|
export function create(options = {}) {
|
|
20
20
|
const {
|
|
21
|
+
model,
|
|
21
22
|
thinking,
|
|
22
23
|
reasoningMode,
|
|
23
24
|
fastMode,
|
|
24
25
|
instructions,
|
|
25
26
|
sessionId,
|
|
27
|
+
workspace,
|
|
26
28
|
resume,
|
|
27
29
|
apiKey,
|
|
28
30
|
mpp,
|
|
29
31
|
websocketUrl,
|
|
30
32
|
apiBaseUrl,
|
|
31
33
|
module,
|
|
32
|
-
|
|
34
|
+
tools,
|
|
35
|
+
toolMode,
|
|
33
36
|
} = options;
|
|
34
37
|
const events = createEventChannel();
|
|
35
38
|
if (mpp !== undefined && apiKey !== undefined) {
|
|
36
39
|
throw new TypeError("apiKey and mpp are mutually exclusive");
|
|
37
40
|
}
|
|
38
|
-
const host = createNodeHost({
|
|
41
|
+
const host = createNodeHost({
|
|
42
|
+
mpp,
|
|
43
|
+
onEvent: events.emit,
|
|
44
|
+
tools,
|
|
45
|
+
toolMode,
|
|
46
|
+
workspace: workspace ?? resume?.workspace,
|
|
47
|
+
});
|
|
39
48
|
activateHost(host);
|
|
40
49
|
const runtime = defineRuntime({
|
|
41
50
|
key: "node-wasm",
|
|
@@ -62,11 +71,13 @@ export function create(options = {}) {
|
|
|
62
71
|
decorate: (agent) => agent.extend(agentActions()),
|
|
63
72
|
});
|
|
64
73
|
return createAgentClient(runtime, {
|
|
74
|
+
model,
|
|
65
75
|
thinking,
|
|
66
76
|
reasoningMode,
|
|
67
77
|
fastMode,
|
|
68
78
|
instructions,
|
|
69
79
|
sessionId,
|
|
80
|
+
workspace,
|
|
70
81
|
resume,
|
|
71
82
|
});
|
|
72
83
|
}
|