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 CHANGED
@@ -9,22 +9,37 @@ import { Actions, Agent } from "nanocodex/node";
9
9
 
10
10
  const agent = await Agent.create({
11
11
  apiKey: process.env.OPENAI_API_KEY,
12
+ model: "gpt-5.6-luna",
13
+ instructions: "You are a Rust coding agent. Preserve unrelated work and run relevant tests.",
12
14
  reasoningMode: "pro",
13
15
  thinking: "high",
14
16
  tools,
17
+ workspace: process.cwd(),
15
18
  });
16
19
 
17
20
  const turn = agent.turn.prompt({ input: "Build the thing." });
18
- console.log(await turn.result());
21
+ const result = await turn.result();
22
+ turn.dispose();
23
+ console.log(result.finalMessage);
24
+ console.log(result.usage);
25
+ console.log(result.usage.estimated_cost?.usd);
26
+ console.log(result.usage.cost_status);
19
27
 
20
28
  await agent.session.setThinking("high");
21
29
  await agent.session.setFastMode(true);
30
+ await agent.session.compact();
22
31
 
23
- const branch = await agent.session.fork({ at: turn });
24
- console.log(await branch.turn.prompt({ input: "Try another approach." }).result());
32
+ const branch = await agent.session.fork({ at: result });
33
+ const branchTurn = branch.turn.prompt({ input: "Try another approach." });
34
+ const branchResult = await branchTurn.result();
35
+ branchTurn.dispose();
36
+ console.log(branchResult.finalMessage);
25
37
 
26
38
  const followOn = Actions.turn.prompt(agent, { input: "Now explain it." });
27
- console.log(await Actions.turn.getResult(followOn));
39
+ console.log((await Actions.turn.getResult(followOn)).finalMessage);
40
+ followOn.dispose();
41
+ await branch.session.shutdown();
42
+ await agent.session.shutdown();
28
43
  ```
29
44
 
30
45
  Node and browser applications can instead pay through MPP without an OpenAI
@@ -77,13 +92,30 @@ const events = agent.events.watch();
77
92
  const unwatch = events.onEvent((event) => {
78
93
  process.stdout.write(`${JSON.stringify(event)}\n`);
79
94
  });
95
+ let turn;
80
96
  try {
81
- console.error(await agent.turn.prompt({ input: "Build the thing." }).result());
97
+ turn = agent.turn.prompt({ input: "Build the thing." });
98
+ const result = await turn.result();
99
+ console.error(result.finalMessage);
82
100
  } finally {
83
- await mpp.close();
101
+ turn?.dispose();
84
102
  unwatch();
85
103
  events.off();
86
- agent.dispose();
104
+ const cleanupErrors = [];
105
+ try {
106
+ await agent.session.shutdown();
107
+ } catch (error) {
108
+ cleanupErrors.push(error);
109
+ }
110
+ try {
111
+ await mpp.close();
112
+ } catch (error) {
113
+ cleanupErrors.push(error);
114
+ }
115
+ if (cleanupErrors.length === 1) throw cleanupErrors[0];
116
+ if (cleanupErrors.length > 1) {
117
+ throw new AggregateError(cleanupErrors, "agent shutdown and MPP settlement both failed");
118
+ }
87
119
  }
88
120
  ```
89
121
 
@@ -93,17 +125,19 @@ across agents, and supply mppx `channelStore` for reuse after a process or page
93
125
  restart. Nanocodex never closes a caller-owned MPP session. `apiKey` and `mpp`
94
126
  are mutually exclusive.
95
127
 
96
- Completed turns can be persisted and resumed by a fresh Node or browser agent:
128
+ Completed results can be persisted and resumed by a fresh Node or browser
129
+ agent:
97
130
 
98
131
  ```js
99
- const snapshot = turn.snapshot();
100
- agent.dispose();
132
+ const snapshot = result.snapshot;
133
+ await agent.session.shutdown();
101
134
 
102
135
  const resumed = await Agent.create({
103
136
  apiKey: process.env.OPENAI_API_KEY,
104
137
  resume: snapshot,
105
138
  tools,
106
139
  });
140
+ await resumed.session.shutdown();
107
141
  ```
108
142
 
109
143
  The snapshot contains authoritative typed history but no provider response ID,
@@ -130,13 +164,28 @@ context, and typed history.
130
164
  an owned client decorated with matching domain actions:
131
165
 
132
166
  - `agent.turn.prompt(...)` / `Actions.turn.prompt(agent, ...)`
133
- - `turn.snapshot()` / `Actions.turn.getSnapshot(turn)`
167
+ - `turn.result()` / `Actions.turn.getResult(turn)`
168
+ - `result.snapshot` / `Actions.turn.getSnapshot(result)`
169
+ - `result.usage` / `Actions.turn.getUsage(result)`
134
170
  - `agent.session.fork(...)` / `Actions.session.fork(agent, ...)`
171
+ - `agent.session.compact()` / `Actions.session.compact(agent)`
135
172
  - `agent.session.setThinking(...)` / `Actions.session.setThinking(agent, ...)`
136
173
  - `agent.session.setFastMode(...)` / `Actions.session.setFastMode(agent, ...)`
174
+ - `agent.session.shutdown()` / `Actions.session.shutdown(agent)`
137
175
  - `agent.session.spawn()` / `Actions.session.spawn(agent)`
138
176
  - `agent.events.watch(...)` / `Actions.events.watch(agent, ...)`
139
177
 
178
+ `turn.result()` resolves to a frozen completed `TurnResult`. Its
179
+ `finalMessage` is eager; `usage` and `snapshot` cross the WASM boundary lazily
180
+ once and are then cached. Historical `fork({ at })` accepts this completed
181
+ result, never an unfinished turn or a provider response ID.
182
+
183
+ `turn.dispose()` only releases the JavaScript/WASM handle; like dropping the
184
+ Rust `Turn`, it does not cancel accepted work. Await `turn.cancel()` before
185
+ disposing unfinished work. At an application or session boundary,
186
+ `agent.session.shutdown()` cancels unfinished turns and joins driver, model,
187
+ tool, and transport cleanup.
188
+
140
189
  Every action owns its types, for example `Actions.turn.prompt.Options`,
141
190
  `Actions.turn.prompt.ReturnType`, and `Actions.events.watch.Watcher`.
142
191
 
@@ -150,6 +199,10 @@ unlisten();
150
199
  watch.off();
151
200
  ```
152
201
 
202
+ A throwing callback is reported through the host's `reportError` hook (or
203
+ `console.error` when that hook is unavailable) without interrupting later
204
+ listeners or the owned agent lifecycle.
205
+
153
206
  The same watcher can instead be consumed as an ordered async iterable; breaking
154
207
  the loop releases that iterator, while `watch.off()` terminates the whole watch.
155
208
 
@@ -190,14 +243,59 @@ const agent = await Agent.create({
190
243
  });
191
244
  ```
192
245
 
246
+ Server-side Worker runtimes can await a `fetch()`-based WebSocket upgrade. The
247
+ third callback argument is a discriminated authorization request plus connection
248
+ metadata. With `apiKey`, `authorization` is `"bearer"` and `bearerToken` is
249
+ present. With `hostAuth: true`, it is `"host_managed"`; the host must resolve
250
+ credentials without exposing them to WASM. Do not retain or log bearer tokens.
251
+ Return the socket alone or a descriptor containing response metadata:
252
+
253
+ ```js
254
+ import { Agent } from "nanocodex/browser";
255
+ import module from "nanocodex/wasm";
256
+
257
+ const agent = await Agent.create({
258
+ apiKey,
259
+ module,
260
+ async createWebSocket(endpoint, sessionId, request) {
261
+ if (request.authorization !== "bearer") {
262
+ throw new Error("this host requires Nanocodex bearer authorization");
263
+ }
264
+ const response = await fetch(endpoint.replace("wss:", "https:"), {
265
+ headers: {
266
+ Authorization: `Bearer ${request.bearerToken}`,
267
+ Upgrade: "websocket",
268
+ "session-id": sessionId,
269
+ },
270
+ });
271
+ if (!response.webSocket) throw new Error(`upgrade failed: ${response.status}`);
272
+ response.webSocket.accept();
273
+ return { socket: response.webSocket, status: response.status };
274
+ },
275
+ });
276
+ ```
277
+
278
+ `hostAuth` is useful when the embedding runtime owns rotating credentials. The
279
+ callback can acquire a fresh token, attempt the upgrade, and refresh-and-retry
280
+ on 401. Bound and reject upgrade work in the callback: until it returns a
281
+ socket, there is no connection handle for Nanocodex to close. `apiKey`,
282
+ `hostAuth`, and `mpp` are mutually exclusive.
283
+
193
284
  After publication, a browser can load the same entrypoint without a package
194
285
  manager or build step:
195
286
 
196
287
  ```html
197
288
  <script type="module">
198
- import { Agent } from "https://cdn.jsdelivr.net/npm/nanocodex@0.1.0/browser/index.mjs";
289
+ import { Agent } from "https://cdn.jsdelivr.net/npm/nanocodex@0.4.0/browser/index.mjs";
199
290
  const agent = await Agent.create({ websocketUrl: "/api/responses" });
200
- console.log(await agent.turn.prompt({ input: "Hello." }).result());
291
+ const turn = agent.turn.prompt({ input: "Hello." });
292
+ try {
293
+ const result = await turn.result();
294
+ console.log(result.finalMessage);
295
+ } finally {
296
+ turn.dispose();
297
+ await agent.session.shutdown();
298
+ }
201
299
  </script>
202
300
  ```
203
301
 
@@ -1,19 +1,34 @@
1
1
  import { subscribeAgentEvents } from "../internal.mjs";
2
2
 
3
+ const MAX_BUFFERED_EVENTS = 4_096;
4
+ const MAX_BUFFERED_EVENT_CHARACTERS = 32 * 1024 * 1024;
5
+
3
6
  export function watch(agent, options = {}) {
4
7
  const listeners = new Set();
5
8
  const iterators = new Set();
6
9
  let unsubscribe;
7
10
  let closed = false;
8
11
 
9
- const emit = (event) => {
10
- for (const listener of listeners) listener(event);
11
- for (const iterator of iterators) iterator.push(event);
12
+ const emit = (event, encodedLength) => {
13
+ for (const listener of listeners) {
14
+ try {
15
+ listener(event);
16
+ } catch (error) {
17
+ reportListenerError(error);
18
+ }
19
+ }
20
+ for (const iterator of iterators) iterator.push(event, encodedLength);
12
21
  };
13
22
 
14
23
  const start = () => {
15
24
  if (closed || unsubscribe) return;
16
- unsubscribe = subscribeAgentEvents(agent, emit, options);
25
+ unsubscribe = subscribeAgentEvents(agent, emit, options, () => watcher.off());
26
+ };
27
+
28
+ const stopIfIdle = () => {
29
+ if (listeners.size || iterators.size) return;
30
+ unsubscribe?.();
31
+ unsubscribe = undefined;
17
32
  };
18
33
 
19
34
  const watcher = {
@@ -22,7 +37,10 @@ export function watch(agent, options = {}) {
22
37
  if (closed) return () => {};
23
38
  listeners.add(listener);
24
39
  start();
25
- return () => listeners.delete(listener);
40
+ return () => {
41
+ listeners.delete(listener);
42
+ stopIfIdle();
43
+ };
26
44
  },
27
45
  off() {
28
46
  if (closed) return;
@@ -35,7 +53,10 @@ export function watch(agent, options = {}) {
35
53
  },
36
54
  [Symbol.asyncIterator]() {
37
55
  if (closed) return emptyIterator();
38
- const iterator = eventIterator(() => iterators.delete(iterator));
56
+ const iterator = eventIterator(() => {
57
+ iterators.delete(iterator);
58
+ stopIfIdle();
59
+ });
39
60
  iterators.add(iterator);
40
61
  start();
41
62
  return iterator;
@@ -46,32 +67,82 @@ export function watch(agent, options = {}) {
46
67
 
47
68
  function eventIterator(onEnd) {
48
69
  const queue = [];
49
- let pending;
50
- let done = false;
70
+ let head = 0;
71
+ let bufferedCharacters = 0;
72
+ const pending = [];
73
+ let pendingHead = 0;
74
+ let ended = false;
75
+ let failure;
76
+ let failureReported = false;
77
+ let detached = false;
78
+
79
+ const detach = () => {
80
+ if (detached) return;
81
+ detached = true;
82
+ onEnd();
83
+ };
51
84
 
52
85
  const iterator = {
53
- push(event) {
54
- if (done) return;
55
- if (pending) {
56
- const resolve = pending;
57
- pending = undefined;
86
+ push(event, encodedLength) {
87
+ if (ended || failure) return;
88
+ if (pendingHead < pending.length) {
89
+ const resolve = pending[pendingHead++];
90
+ if (pendingHead === pending.length) {
91
+ pending.length = 0;
92
+ pendingHead = 0;
93
+ }
58
94
  resolve({ done: false, value: event });
59
95
  } else {
60
- queue.push(event);
96
+ const characters = encodedLength ?? JSON.stringify(event).length;
97
+ if (
98
+ queue.length - head >= MAX_BUFFERED_EVENTS
99
+ || bufferedCharacters + characters > MAX_BUFFERED_EVENT_CHARACTERS
100
+ ) {
101
+ failure = new RangeError(
102
+ `event iterator exceeded its private buffer of ${MAX_BUFFERED_EVENTS} events or `
103
+ + `${MAX_BUFFERED_EVENT_CHARACTERS} serialized characters`,
104
+ );
105
+ detach();
106
+ return;
107
+ }
108
+ queue.push({ characters, event });
109
+ bufferedCharacters += characters;
61
110
  }
62
111
  },
63
112
  end() {
64
- if (done) return;
65
- done = true;
66
- onEnd();
67
- pending?.({ done: true, value: undefined });
68
- pending = undefined;
113
+ if (ended) return;
114
+ ended = true;
115
+ detach();
116
+ while (pendingHead < pending.length) {
117
+ pending[pendingHead++]({ done: true, value: undefined });
118
+ }
119
+ pending.length = 0;
120
+ pendingHead = 0;
69
121
  queue.length = 0;
122
+ head = 0;
123
+ bufferedCharacters = 0;
70
124
  },
71
125
  next() {
72
- if (queue.length) return Promise.resolve({ done: false, value: queue.shift() });
73
- if (done) return Promise.resolve({ done: true, value: undefined });
74
- return new Promise((resolve) => { pending = resolve; });
126
+ if (head < queue.length) {
127
+ const entry = queue[head++];
128
+ bufferedCharacters -= entry.characters;
129
+ if (head === queue.length) {
130
+ queue.length = 0;
131
+ head = 0;
132
+ }
133
+ return Promise.resolve({ done: false, value: entry.event });
134
+ }
135
+ if (failure && !failureReported) {
136
+ failureReported = true;
137
+ return Promise.reject(failure);
138
+ }
139
+ if (ended || failure) return Promise.resolve({ done: true, value: undefined });
140
+ if (pending.length - pendingHead >= MAX_BUFFERED_EVENTS) {
141
+ return Promise.reject(new RangeError(
142
+ `event iterator exceeded its private buffer of ${MAX_BUFFERED_EVENTS} pending reads`,
143
+ ));
144
+ }
145
+ return new Promise((resolve) => { pending.push(resolve); });
75
146
  },
76
147
  return() {
77
148
  iterator.end();
@@ -89,3 +160,15 @@ function emptyIterator() {
89
160
  [Symbol.asyncIterator]() { return this; },
90
161
  };
91
162
  }
163
+
164
+ function reportListenerError(error) {
165
+ try {
166
+ if (typeof globalThis.reportError === "function") {
167
+ globalThis.reportError(error);
168
+ return;
169
+ }
170
+ } catch {}
171
+ try {
172
+ globalThis.console?.error?.("Nanocodex event listener failed", error);
173
+ } catch {}
174
+ }
package/actions/index.mjs CHANGED
@@ -10,9 +10,11 @@ export function agentActions() {
10
10
  watch: (options) => events.watch(agent, options),
11
11
  },
12
12
  session: {
13
+ compact: () => session.compact(agent),
13
14
  fork: (options) => session.fork(agent, options),
14
15
  setFastMode: (enabled) => session.setFastMode(agent, enabled),
15
16
  setThinking: (thinking) => session.setThinking(agent, thinking),
17
+ shutdown: () => session.shutdown(agent),
16
18
  spawn: () => session.spawn(agent),
17
19
  },
18
20
  turn: {
@@ -1,6 +1,9 @@
1
1
  import type { Agent, DefaultAgent, ForkOptions, Thinking } from "../types.mjs";
2
2
 
3
- /** Forks the latest checkpoint, or the exact completed Turn supplied in `options.at`. */
3
+ /** Compacts retained history immediately without fabricating a user prompt. */
4
+ export function compact(agent: Agent<object>): Promise<void>;
5
+
6
+ /** Forks the latest checkpoint, or the exact completed result supplied in `options.at`. */
4
7
  export function fork(agent: Agent<object>, options?: fork.Options): Promise<fork.ReturnType>;
5
8
  export declare namespace fork {
6
9
  type Options = ForkOptions;
@@ -18,3 +21,6 @@ export function setThinking(agent: Agent<object>, thinking: Thinking): Promise<v
18
21
 
19
22
  /** Enables or disables priority processing for subsequently accepted turns. */
20
23
  export function setFastMode(agent: Agent<object>, enabled: boolean): Promise<void>;
24
+
25
+ /** Stops the driver and joins every resource owned by this Agent. */
26
+ export function shutdown(agent: Agent<object>): Promise<void>;
@@ -1,10 +1,16 @@
1
1
  import {
2
+ compact as compactAgent,
2
3
  fork as forkAgent,
3
4
  setFastMode as setAgentFastMode,
4
5
  setThinking as setAgentThinking,
6
+ shutdown as shutdownAgent,
5
7
  spawn as spawnAgent,
6
8
  } from "../internal.mjs";
7
9
 
10
+ export function compact(agent) {
11
+ return compactAgent(agent);
12
+ }
13
+
8
14
  export function fork(agent, options = {}) {
9
15
  return forkAgent(agent, options);
10
16
  }
@@ -20,3 +26,7 @@ export function setThinking(agent, thinking) {
20
26
  export function setFastMode(agent, enabled) {
21
27
  return setAgentFastMode(agent, enabled);
22
28
  }
29
+
30
+ export function shutdown(agent) {
31
+ return shutdownAgent(agent);
32
+ }
@@ -1,4 +1,11 @@
1
- import type { Agent, PromptInput, SessionSnapshot, Turn } from "../types.mjs";
1
+ import type {
2
+ Agent,
3
+ PromptInput,
4
+ SessionSnapshot,
5
+ Turn,
6
+ TurnResult,
7
+ TurnUsage,
8
+ } from "../types.mjs";
2
9
 
3
10
  /** Accepts a prompt on an owned Agent and returns its independently awaitable Turn. */
4
11
  export function prompt<const agent extends Agent<object>>(
@@ -10,18 +17,24 @@ export declare namespace prompt {
10
17
  type ReturnType<agent extends Agent<object> = Agent<object>> = Turn<agent>;
11
18
  }
12
19
 
13
- /** Waits for a Turn's final assistant message. */
20
+ /** Waits for a Turn's typed completed result. */
14
21
  export function getResult(turn: Turn): Promise<getResult.ReturnType>;
15
22
  export declare namespace getResult {
16
- type ReturnType = string;
23
+ type ReturnType = TurnResult;
17
24
  }
18
25
 
19
- /** Copies a completed Turn into a serializable session snapshot. */
20
- export function getSnapshot(turn: Turn): getSnapshot.ReturnType;
26
+ /** Returns a completed result's serializable session snapshot. */
27
+ export function getSnapshot(result: TurnResult): getSnapshot.ReturnType;
21
28
  export declare namespace getSnapshot {
22
29
  type ReturnType = SessionSnapshot;
23
30
  }
24
31
 
32
+ /** Returns exact aggregate token usage from a completed result. */
33
+ export function getUsage(result: TurnResult): getUsage.ReturnType;
34
+ export declare namespace getUsage {
35
+ type ReturnType = TurnUsage;
36
+ }
37
+
25
38
  /** Adds input to an active Turn. */
26
39
  export function steer(turn: Turn, options: steer.Options): Promise<void>;
27
40
  export declare namespace steer {
package/actions/turn.mjs CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  cancel as cancelTurn,
3
3
  getTurnResult,
4
4
  getTurnSnapshot,
5
+ getTurnUsage,
5
6
  prompt as promptTurn,
6
7
  steer as steerTurn,
7
8
  } from "../internal.mjs";
@@ -14,8 +15,12 @@ export function getResult(turn) {
14
15
  return getTurnResult(turn);
15
16
  }
16
17
 
17
- export function getSnapshot(turn) {
18
- return getTurnSnapshot(turn);
18
+ export function getSnapshot(result) {
19
+ return getTurnSnapshot(result);
20
+ }
21
+
22
+ export function getUsage(result) {
23
+ return getTurnUsage(result);
19
24
  }
20
25
 
21
26
  export function steer(turn, options) {
@@ -4,6 +4,10 @@ import type {
4
4
  MppSession,
5
5
  ToolMap,
6
6
  } from "../types.mjs";
7
+ import type {
8
+ BrowserWebSocketConnection,
9
+ BrowserWebSocketRequest,
10
+ } from "./host.mjs";
7
11
 
8
12
  export type Agent = DefaultAgent;
9
13
 
@@ -11,17 +15,21 @@ export type Agent = DefaultAgent;
11
15
  export function create(options?: create.Options): Promise<create.ReturnType>;
12
16
  export declare namespace create {
13
17
  type Options = AgentOptions & (
14
- | { apiKey?: string | undefined; mpp?: never }
15
- | { apiKey?: never; mpp: MppSession }
18
+ | { apiKey?: string | undefined; hostAuth?: never; mpp?: never }
19
+ | { apiKey?: never; hostAuth?: true; mpp?: never }
20
+ | { apiKey?: never; hostAuth?: never; mpp: MppSession }
16
21
  ) & {
17
22
  WebSocketImpl?: typeof WebSocket | undefined;
18
23
  apiBaseUrl?: string | undefined;
19
- createWebSocket?(endpoint: string, sessionId: string): WebSocket;
20
- maxBufferedSendBytes?: number | undefined;
21
- maxQueuedBytes?: number | undefined;
22
- maxQueuedMessages?: number | undefined;
24
+ createWebSocket?(
25
+ endpoint: string,
26
+ sessionId: string,
27
+ request: BrowserWebSocketRequest,
28
+ ): WebSocket | BrowserWebSocketConnection | Promise<WebSocket | BrowserWebSocketConnection>;
23
29
  module?: unknown;
24
30
  tools?: ToolMap | undefined;
31
+ /** Direct dispatch is CSP-safe; Code Mode requires dynamic JavaScript evaluation. */
32
+ toolMode?: "code" | "direct" | undefined;
25
33
  websocketUrl?: string | undefined;
26
34
  };
27
35
  type ReturnType = Agent;
package/browser/Agent.mjs CHANGED
@@ -17,23 +17,40 @@ let initialized;
17
17
  export function create(options = {}) {
18
18
  const {
19
19
  apiKey,
20
+ hostAuth,
20
21
  mpp,
21
22
  websocketUrl,
22
23
  apiBaseUrl,
23
24
  module,
25
+ model,
24
26
  thinking,
25
27
  reasoningMode,
26
28
  fastMode,
27
29
  instructions,
28
30
  sessionId,
31
+ workspace,
29
32
  resume,
30
- ...hostOptions
33
+ WebSocketImpl,
34
+ createWebSocket,
35
+ tools,
36
+ toolMode,
31
37
  } = options;
32
38
  if (mpp !== undefined && apiKey !== undefined) {
33
39
  throw new TypeError("apiKey and mpp are mutually exclusive");
34
40
  }
41
+ if (hostAuth && (apiKey !== undefined || mpp !== undefined)) {
42
+ throw new TypeError("hostAuth is mutually exclusive with apiKey and mpp");
43
+ }
35
44
  const events = createEventChannel();
36
- const host = createBrowserHost({ ...hostOptions, mpp, onEvent: events.emit });
45
+ const host = createBrowserHost({
46
+ WebSocketImpl,
47
+ createWebSocket,
48
+ hostAuth: hostAuth === true || (apiKey === undefined && mpp === undefined),
49
+ mpp,
50
+ onEvent: events.emit,
51
+ tools,
52
+ toolMode,
53
+ });
37
54
  activateHost(host);
38
55
  const runtime = defineRuntime({
39
56
  key: "browser-wasm",
@@ -59,11 +76,13 @@ export function create(options = {}) {
59
76
  decorate: (agent) => agent.extend(agentActions()),
60
77
  });
61
78
  return createAgentClient(runtime, {
79
+ model,
62
80
  thinking,
63
81
  reasoningMode,
64
82
  fastMode,
65
83
  instructions,
66
84
  sessionId,
85
+ workspace,
67
86
  resume,
68
87
  });
69
88
  }
@@ -9,9 +9,41 @@ export type BrowserTool = {
9
9
 
10
10
  export type BrowserToolMap = Record<string, BrowserTool>;
11
11
 
12
+ type BrowserWebSocketMetadata = {
13
+ accountId?: string | undefined;
14
+ fedramp?: boolean | undefined;
15
+ turnState?: string | undefined;
16
+ };
17
+
18
+ export type BrowserWebSocketRequest = BrowserWebSocketMetadata & (
19
+ | {
20
+ authorization: "bearer";
21
+ /** Resolved credential for this handshake. Do not retain or log it. */
22
+ bearerToken: string;
23
+ }
24
+ | {
25
+ authorization: "host_managed";
26
+ bearerToken?: never;
27
+ }
28
+ );
29
+
30
+ export type BrowserWebSocketConnection = {
31
+ socket: WebSocket;
32
+ status?: number | undefined;
33
+ requestId?: string | undefined;
34
+ serverModel?: string | undefined;
35
+ reasoningIncluded?: boolean | undefined;
36
+ turnState?: string | undefined;
37
+ };
38
+
12
39
  export function createBrowserHost(options?: {
13
40
  WebSocketImpl?: typeof WebSocket;
14
- createWebSocket?: (endpoint: string, sessionId: string) => WebSocket;
41
+ hostAuth?: boolean;
42
+ createWebSocket?: (
43
+ endpoint: string,
44
+ sessionId: string,
45
+ request: BrowserWebSocketRequest,
46
+ ) => WebSocket | BrowserWebSocketConnection | Promise<WebSocket | BrowserWebSocketConnection>;
15
47
  onEvent?: (eventJson: string) => void;
16
48
  tools?: BrowserToolMap;
17
49
  maxQueuedMessages?: number;