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/browser/host.mjs
CHANGED
|
@@ -3,25 +3,30 @@ 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);
|
|
12
17
|
const onEvent = options.onEvent || (() => {});
|
|
13
|
-
const createWebSocket = options.createWebSocket || ((endpoint) => new WebSocketImpl(endpoint));
|
|
14
18
|
const maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
|
|
15
19
|
const maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
|
|
16
20
|
const maxBufferedSendBytes = options.maxBufferedSendBytes ?? DEFAULT_MAX_BUFFERED_SEND_BYTES;
|
|
17
21
|
const encoder = new TextEncoder();
|
|
18
22
|
let nextHandle = 1;
|
|
19
23
|
|
|
20
|
-
function connect(endpoint, _apiKey, sessionId) {
|
|
24
|
+
function connect(endpoint, _apiKey, sessionId, metadata = {}) {
|
|
25
|
+
if (options.mpp) return connectMpp(endpoint);
|
|
21
26
|
return new Promise((resolve, reject) => {
|
|
22
27
|
let settled = false;
|
|
23
28
|
const handle = nextHandle++;
|
|
24
|
-
const socket = createWebSocket(endpoint, sessionId);
|
|
29
|
+
const socket = createWebSocket(endpoint, sessionId, metadata);
|
|
25
30
|
const connection = {
|
|
26
31
|
socket,
|
|
27
32
|
queue: [],
|
|
@@ -59,9 +64,44 @@ export function createBrowserHost(options = {}) {
|
|
|
59
64
|
});
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
async function connectMpp(endpoint) {
|
|
68
|
+
if (typeof options.mpp.ws !== "function") {
|
|
69
|
+
throw new TypeError("mpp must provide ws(endpoint)");
|
|
70
|
+
}
|
|
71
|
+
const socket = await options.mpp.ws(endpoint);
|
|
72
|
+
if (!socket || typeof socket.addEventListener !== "function") {
|
|
73
|
+
throw new TypeError("mpp.ws(endpoint) must return a WebSocket");
|
|
74
|
+
}
|
|
75
|
+
const handle = nextHandle++;
|
|
76
|
+
const connection = {
|
|
77
|
+
socket,
|
|
78
|
+
queue: [],
|
|
79
|
+
queuedBytes: 0,
|
|
80
|
+
waiter: undefined,
|
|
81
|
+
intentionallyClosed: false,
|
|
82
|
+
overflowed: false,
|
|
83
|
+
managed: true,
|
|
84
|
+
};
|
|
85
|
+
connections.set(handle, connection);
|
|
86
|
+
socket.addEventListener("message", (event) => {
|
|
87
|
+
enqueue(connection, typeof event.data === "string"
|
|
88
|
+
? { kind: "text", text: event.data }
|
|
89
|
+
: { kind: "binary" });
|
|
90
|
+
});
|
|
91
|
+
socket.addEventListener("close", (event) => {
|
|
92
|
+
if (!connection.intentionallyClosed && !connection.overflowed) {
|
|
93
|
+
enqueue(connection, { kind: "closed", detail: `with code ${event.code ?? 1000}` });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
socket.addEventListener("error", () => {
|
|
97
|
+
enqueue(connection, { kind: "error", detail: "MPP WebSocket connection failed" });
|
|
98
|
+
});
|
|
99
|
+
return JSON.stringify({ handle, status: 101, reasoning_included: false });
|
|
100
|
+
}
|
|
101
|
+
|
|
62
102
|
function send(handle, message) {
|
|
63
103
|
const connection = connections.get(handle);
|
|
64
|
-
if (!connection || connection.socket.readyState !==
|
|
104
|
+
if (!connection || connection.socket.readyState !== WEBSOCKET_OPEN) {
|
|
65
105
|
return Promise.resolve(JSON.stringify({
|
|
66
106
|
ok: false,
|
|
67
107
|
reconnectable: true,
|
|
@@ -69,6 +109,10 @@ export function createBrowserHost(options = {}) {
|
|
|
69
109
|
}));
|
|
70
110
|
}
|
|
71
111
|
try {
|
|
112
|
+
if (connection.managed) {
|
|
113
|
+
connection.socket.send(JSON.stringify({ mpp: "message", data: message }));
|
|
114
|
+
return Promise.resolve(JSON.stringify({ ok: true }));
|
|
115
|
+
}
|
|
72
116
|
const frameBytes = encoder.encode(message).byteLength;
|
|
73
117
|
if (frameBytes > maxBufferedSendBytes
|
|
74
118
|
|| connection.socket.bufferedAmount + frameBytes > maxBufferedSendBytes) {
|
|
@@ -83,7 +127,7 @@ export function createBrowserHost(options = {}) {
|
|
|
83
127
|
} catch (error) {
|
|
84
128
|
return Promise.resolve(JSON.stringify({
|
|
85
129
|
ok: false,
|
|
86
|
-
reconnectable: connection.socket.readyState !==
|
|
130
|
+
reconnectable: connection.socket.readyState !== WEBSOCKET_OPEN,
|
|
87
131
|
error: error instanceof Error ? error.message : String(error),
|
|
88
132
|
}));
|
|
89
133
|
}
|
package/browser/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/index.d.mts
CHANGED
|
@@ -4,16 +4,21 @@ 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,
|
|
12
14
|
ReasoningMode,
|
|
15
|
+
SessionSnapshot,
|
|
13
16
|
Thinking,
|
|
14
17
|
Tool,
|
|
15
18
|
ToolContext,
|
|
16
19
|
ToolMap,
|
|
17
20
|
Turn,
|
|
21
|
+
TurnResult,
|
|
22
|
+
TurnUsage,
|
|
18
23
|
WatchEventsOptions,
|
|
19
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,10 +42,20 @@ 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
|
|
|
51
|
+
export function getTurnSnapshot(result) {
|
|
52
|
+
return resultState(result).snapshot();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getTurnUsage(result) {
|
|
56
|
+
return resultState(result).usage();
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
export function steer(turn, options) {
|
|
49
60
|
const state = turnState(turn);
|
|
50
61
|
const input = actionInput(options);
|
|
@@ -62,7 +73,7 @@ export async function fork(agent, options) {
|
|
|
62
73
|
const at = options?.at;
|
|
63
74
|
const raw = at === undefined
|
|
64
75
|
? await state.raw.fork()
|
|
65
|
-
: await state.raw.forkFrom(
|
|
76
|
+
: await state.raw.forkFrom(resultState(at).raw);
|
|
66
77
|
return createAgent(raw, state.runtime);
|
|
67
78
|
}
|
|
68
79
|
|
|
@@ -71,7 +82,31 @@ export async function spawn(agent) {
|
|
|
71
82
|
return createAgent(await state.raw.spawn(), state.runtime);
|
|
72
83
|
}
|
|
73
84
|
|
|
74
|
-
export function
|
|
85
|
+
export function setThinking(agent, thinking) {
|
|
86
|
+
return agentState(agent).raw.setThinking(thinking);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function setFastMode(agent, enabled) {
|
|
90
|
+
return agentState(agent).raw.setFastMode(enabled);
|
|
91
|
+
}
|
|
92
|
+
|
|
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) {
|
|
75
110
|
const state = agentState(agent);
|
|
76
111
|
if (typeof state.runtime.subscribe !== "function") {
|
|
77
112
|
throw new Error("this Nanocodex runtime does not expose agent events");
|
|
@@ -79,11 +114,25 @@ export function subscribeAgentEvents(agent, listener, options = {}) {
|
|
|
79
114
|
if (typeof listener !== "function") {
|
|
80
115
|
throw new TypeError("watchAgentEvents requires a listener");
|
|
81
116
|
}
|
|
82
|
-
|
|
117
|
+
const unsubscribe = state.runtime.subscribe((event, encodedLength) => {
|
|
83
118
|
if (options.includeAllSessions || !event?.request_id || event.request_id === agent.sessionId) {
|
|
84
|
-
listener(event);
|
|
119
|
+
listener(event, encodedLength);
|
|
85
120
|
}
|
|
86
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);
|
|
87
136
|
}
|
|
88
137
|
|
|
89
138
|
export function toWasmConfig(options = {}) {
|
|
@@ -94,10 +143,13 @@ export function toWasmConfig(options = {}) {
|
|
|
94
143
|
const config = { api_key: apiKey };
|
|
95
144
|
copy(config, "thinking", options.thinking);
|
|
96
145
|
copy(config, "reasoning_mode", options.reasoningMode);
|
|
146
|
+
copy(config, "fast_mode", options.fastMode);
|
|
97
147
|
copy(config, "websocket_url", options.websocketUrl);
|
|
98
148
|
copy(config, "api_base_url", options.apiBaseUrl);
|
|
99
149
|
copy(config, "instructions", options.instructions);
|
|
100
150
|
copy(config, "session_id", options.sessionId);
|
|
151
|
+
copy(config, "workspace", options.workspace);
|
|
152
|
+
copy(config, "resume", options.resume);
|
|
101
153
|
return config;
|
|
102
154
|
}
|
|
103
155
|
|
|
@@ -105,8 +157,10 @@ export function createEventChannel() {
|
|
|
105
157
|
const listeners = new Set();
|
|
106
158
|
return Object.freeze({
|
|
107
159
|
emit(eventJson) {
|
|
160
|
+
if (!listeners.size) return;
|
|
108
161
|
const event = typeof eventJson === "string" ? JSON.parse(eventJson) : eventJson;
|
|
109
|
-
|
|
162
|
+
const encodedLength = typeof eventJson === "string" ? eventJson.length : undefined;
|
|
163
|
+
for (const listener of listeners) listener(event, encodedLength);
|
|
110
164
|
},
|
|
111
165
|
subscribe(listener) {
|
|
112
166
|
listeners.add(listener);
|
|
@@ -136,9 +190,18 @@ export function releaseHostSession(host, sessionId) {
|
|
|
136
190
|
}
|
|
137
191
|
|
|
138
192
|
const hostBridge = Object.freeze({
|
|
139
|
-
async connect(endpoint, apiKey, sessionId) {
|
|
193
|
+
async connect(endpoint, apiKey, accountId, fedramp, sessionId, turnState) {
|
|
140
194
|
const host = requiredSessionHost(sessionId);
|
|
141
|
-
|
|
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
|
+
}
|
|
142
205
|
const handle = nextHostConnection++;
|
|
143
206
|
hostConnections.set(handle, { host, handle: result.handle });
|
|
144
207
|
hostSessions.set(sessionId, host);
|
|
@@ -162,8 +225,12 @@ const hostBridge = Object.freeze({
|
|
|
162
225
|
hostConnections.delete(handle);
|
|
163
226
|
connection.host.close(connection.handle);
|
|
164
227
|
},
|
|
165
|
-
sleep(milliseconds) {
|
|
166
|
-
|
|
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);
|
|
167
234
|
},
|
|
168
235
|
executeCode(source, sessionId, callId) {
|
|
169
236
|
return requiredSessionHost(sessionId).executeCode(source, sessionId, callId);
|
|
@@ -188,6 +255,9 @@ function createAgent(raw, runtime) {
|
|
|
188
255
|
raw,
|
|
189
256
|
runtime,
|
|
190
257
|
disposed: false,
|
|
258
|
+
released: false,
|
|
259
|
+
shutdownPromise: undefined,
|
|
260
|
+
subscriptions: new Set(),
|
|
191
261
|
sessionId: raw.sessionId,
|
|
192
262
|
uid: `agent-${nextAgentUid++}`,
|
|
193
263
|
};
|
|
@@ -220,14 +290,12 @@ function agentView(state, extensions) {
|
|
|
220
290
|
return agentView(state, deepMerge(extensions, extension));
|
|
221
291
|
},
|
|
222
292
|
dispose() {
|
|
223
|
-
if (state.
|
|
224
|
-
state
|
|
225
|
-
state.runtime.release?.(state.raw);
|
|
226
|
-
state.runtime.dispose(state.raw);
|
|
293
|
+
if (state.shutdownPromise) return;
|
|
294
|
+
releaseAgentState(state);
|
|
227
295
|
},
|
|
228
296
|
};
|
|
229
297
|
agent = Object.assign(base, extensions);
|
|
230
|
-
|
|
298
|
+
agentStates.set(agent, state);
|
|
231
299
|
return agent;
|
|
232
300
|
}
|
|
233
301
|
|
|
@@ -237,11 +305,87 @@ function requiredSessionHost(sessionId) {
|
|
|
237
305
|
return host;
|
|
238
306
|
}
|
|
239
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
|
+
|
|
240
354
|
function requiredActiveHost() {
|
|
241
355
|
if (!activeHost) throw new Error("no Nanocodex host is active");
|
|
242
356
|
return activeHost;
|
|
243
357
|
}
|
|
244
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
|
+
|
|
245
389
|
function createTurn(raw, agent) {
|
|
246
390
|
if (!raw || typeof raw.result !== "function") {
|
|
247
391
|
throw new TypeError("the runtime returned an invalid Nanocodex turn handle");
|
|
@@ -258,24 +402,67 @@ function createTurn(raw, agent) {
|
|
|
258
402
|
state.raw.free();
|
|
259
403
|
},
|
|
260
404
|
};
|
|
261
|
-
|
|
405
|
+
turnStates.set(turn, state);
|
|
262
406
|
return Object.freeze(turn);
|
|
263
407
|
}
|
|
264
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
|
+
|
|
265
441
|
function agentState(agent) {
|
|
266
|
-
const state = agent
|
|
267
|
-
if (!state) throw new TypeError("expected a Nanocodex agent");
|
|
442
|
+
const state = knownAgentState(agent);
|
|
268
443
|
if (state.disposed) throw new Error("the Nanocodex agent has been disposed");
|
|
269
444
|
return state;
|
|
270
445
|
}
|
|
271
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
|
+
|
|
272
453
|
function turnState(turn) {
|
|
273
|
-
const state = turn
|
|
454
|
+
const state = turnStates.get(turn);
|
|
274
455
|
if (!state) throw new TypeError("expected a Nanocodex turn");
|
|
275
456
|
if (state.disposed) throw new Error("the Nanocodex turn has been disposed");
|
|
276
457
|
return state;
|
|
277
458
|
}
|
|
278
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
|
+
|
|
279
466
|
function actionInput(options) {
|
|
280
467
|
const input = options?.input;
|
|
281
468
|
if (typeof input !== "string" && !Array.isArray(input)) {
|
|
@@ -301,3 +488,32 @@ function isObject(value) {
|
|
|
301
488
|
function copy(target, key, value) {
|
|
302
489
|
if (value !== undefined) target[key] = value;
|
|
303
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentOptions,
|
|
3
3
|
DefaultAgent,
|
|
4
|
+
MppSession,
|
|
4
5
|
ToolMap,
|
|
5
6
|
} from "../types.mjs";
|
|
6
7
|
|
|
@@ -9,14 +10,9 @@ export type Agent = DefaultAgent;
|
|
|
9
10
|
/** Creates a Node-hosted Rust/WASM Agent. */
|
|
10
11
|
export function create(options: create.Options): Promise<create.ReturnType>;
|
|
11
12
|
export declare namespace create {
|
|
12
|
-
type Options = AgentOptions & {
|
|
13
|
+
type Options = AgentOptions & ({ apiKey: string; mpp?: never } | { apiKey?: never; mpp: MppSession }) & {
|
|
13
14
|
apiBaseUrl?: string | undefined;
|
|
14
|
-
|
|
15
|
-
connectTimeoutMs?: number | undefined;
|
|
16
|
-
maxFrameBytes?: number | undefined;
|
|
17
|
-
maxQueuedBytes?: number | undefined;
|
|
18
|
-
maxQueuedMessages?: number | undefined;
|
|
19
|
-
sendTimeoutMs?: number | undefined;
|
|
15
|
+
module?: unknown;
|
|
20
16
|
tools?: ToolMap | undefined;
|
|
21
17
|
websocketUrl?: string | undefined;
|
|
22
18
|
};
|
package/node/Agent.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import initWeb, { Nanocodex as WebNanocodex } from "../pkg-web/nanocodex.js";
|
|
2
3
|
|
|
3
4
|
import { agentActions } from "../actions/index.mjs";
|
|
4
5
|
import {
|
|
@@ -12,32 +13,51 @@ import {
|
|
|
12
13
|
} from "../internal.mjs";
|
|
13
14
|
import { createNodeHost } from "./host.mjs";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
let initializedWeb;
|
|
17
|
+
let NodeNanocodex;
|
|
17
18
|
|
|
18
19
|
export function create(options = {}) {
|
|
19
20
|
const {
|
|
20
21
|
thinking,
|
|
21
22
|
reasoningMode,
|
|
23
|
+
fastMode,
|
|
22
24
|
instructions,
|
|
23
25
|
sessionId,
|
|
26
|
+
workspace,
|
|
27
|
+
resume,
|
|
24
28
|
apiKey,
|
|
29
|
+
mpp,
|
|
25
30
|
websocketUrl,
|
|
26
31
|
apiBaseUrl,
|
|
27
|
-
|
|
32
|
+
module,
|
|
33
|
+
tools,
|
|
28
34
|
} = options;
|
|
29
35
|
const events = createEventChannel();
|
|
30
|
-
|
|
36
|
+
if (mpp !== undefined && apiKey !== undefined) {
|
|
37
|
+
throw new TypeError("apiKey and mpp are mutually exclusive");
|
|
38
|
+
}
|
|
39
|
+
const host = createNodeHost({
|
|
40
|
+
mpp,
|
|
41
|
+
onEvent: events.emit,
|
|
42
|
+
tools,
|
|
43
|
+
workspace: workspace ?? resume?.workspace,
|
|
44
|
+
});
|
|
31
45
|
activateHost(host);
|
|
32
46
|
const runtime = defineRuntime({
|
|
33
47
|
key: "node-wasm",
|
|
34
48
|
name: "Nanocodex Node WASM",
|
|
35
49
|
type: "node",
|
|
36
|
-
create(config) {
|
|
50
|
+
async create(config) {
|
|
51
|
+
activateHost(host);
|
|
52
|
+
const Nanocodex = module === undefined
|
|
53
|
+
? loadNodeNanocodex()
|
|
54
|
+
: await loadWebNanocodex(module);
|
|
37
55
|
activateHost(host);
|
|
38
56
|
return new Nanocodex(JSON.stringify(toWasmConfig({
|
|
39
|
-
apiKey,
|
|
40
|
-
websocketUrl
|
|
57
|
+
apiKey: apiKey ?? (mpp === undefined ? undefined : "mpp-managed"),
|
|
58
|
+
websocketUrl: websocketUrl ?? (mpp === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: "wss://openai.mpp.tempo.xyz/v1/responses"),
|
|
41
61
|
apiBaseUrl,
|
|
42
62
|
...config,
|
|
43
63
|
})));
|
|
@@ -47,5 +67,25 @@ export function create(options = {}) {
|
|
|
47
67
|
release: (raw) => releaseHostSession(host, raw.sessionId),
|
|
48
68
|
decorate: (agent) => agent.extend(agentActions()),
|
|
49
69
|
});
|
|
50
|
-
return createAgentClient(runtime, {
|
|
70
|
+
return createAgentClient(runtime, {
|
|
71
|
+
thinking,
|
|
72
|
+
reasoningMode,
|
|
73
|
+
fastMode,
|
|
74
|
+
instructions,
|
|
75
|
+
sessionId,
|
|
76
|
+
workspace,
|
|
77
|
+
resume,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function loadNodeNanocodex() {
|
|
82
|
+
const require = createRequire(import.meta.url);
|
|
83
|
+
NodeNanocodex ||= require("../pkg-node/nanocodex.js").Nanocodex;
|
|
84
|
+
return NodeNanocodex;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function loadWebNanocodex(module) {
|
|
88
|
+
initializedWeb ||= initWeb({ module_or_path: module });
|
|
89
|
+
await initializedWeb;
|
|
90
|
+
return WebNanocodex;
|
|
51
91
|
}
|