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/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 || globalThis.WebSocket;
9
- if (!WebSocketImpl) throw new Error("WebSocket is unavailable in this runtime");
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, _apiKey, sessionId) {
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
- socket.addEventListener("open", () => {
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({ handle, status: 101, reasoning_included: false }));
38
- }, { once: true });
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 !== WebSocketImpl.OPEN) {
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 !== WebSocketImpl.OPEN,
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
+ }
@@ -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 AGENT_STATE = Symbol("nanocodex.agent");
2
- const TURN_STATE = Symbol("nanocodex.turn");
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().then(() => state.raw.result());
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(turn) {
49
- return JSON.parse(turnState(turn).raw.snapshot());
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(turnState(at).raw);
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 subscribeAgentEvents(agent, listener, options = {}) {
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
- return state.runtime.subscribe((event) => {
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
- for (const listener of listeners) listener(event);
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
- const result = JSON.parse(await host.connect(endpoint, apiKey, sessionId));
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
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
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.disposed) return;
238
- state.disposed = true;
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
- Object.defineProperty(agent, AGENT_STATE, { value: state });
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
- Object.defineProperty(turn, TURN_STATE, { value: state });
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?.[AGENT_STATE];
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?.[TURN_STATE];
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
- ...hostOptions
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({ ...hostOptions, mpp, onEvent: events.emit });
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
  }