nanocodex 0.4.0 → 0.6.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.
Files changed (196) hide show
  1. package/README.md +944 -78
  2. package/actions/events.mjs +14 -25
  3. package/actions/index.d.mts +1 -0
  4. package/actions/index.mjs +11 -1
  5. package/actions/session.d.mts +29 -1
  6. package/actions/session.mjs +22 -0
  7. package/actions/turn.d.mts +15 -6
  8. package/actions/turn.mjs +10 -0
  9. package/actions/voice.d.mts +26 -0
  10. package/actions/voice.mjs +54 -0
  11. package/browser/Agent.d.mts +41 -24
  12. package/browser/Agent.mjs +7 -84
  13. package/browser/ChatGptSubscription.d.mts +7 -0
  14. package/browser/ChatGptSubscription.mjs +19 -0
  15. package/browser/InlineAgent.mjs +267 -0
  16. package/browser/Transport.d.mts +80 -0
  17. package/browser/Transport.mjs +63 -0
  18. package/browser/Voice.d.mts +86 -0
  19. package/browser/Voice.mjs +316 -0
  20. package/browser/VoiceSession.mjs +763 -0
  21. package/browser/WorkerAgent.d.mts +65 -0
  22. package/browser/WorkerAgent.mjs +1467 -0
  23. package/browser/agent.worker.mjs +3 -0
  24. package/browser/config.d.mts +36 -0
  25. package/browser/config.mjs +439 -0
  26. package/browser/engine.mjs +13 -0
  27. package/browser/harness.mjs +55 -0
  28. package/browser/host.d.mts +33 -5
  29. package/browser/host.mjs +320 -26
  30. package/browser/hostManagedWebSocket.d.mts +14 -0
  31. package/browser/hostManagedWebSocket.mjs +110 -0
  32. package/browser/index.d.mts +64 -1
  33. package/browser/index.mjs +22 -1
  34. package/browser/indexeddb-durability-store.mjs +210 -0
  35. package/browser/workspace.d.mts +16 -0
  36. package/browser/workspace.mjs +107 -0
  37. package/cloud/Client.d.mts +83 -0
  38. package/cloud/Client.mjs +231 -0
  39. package/cloud/Decorator.d.mts +31 -0
  40. package/cloud/Decorator.mjs +37 -0
  41. package/cloud/Dialog.d.mts +113 -0
  42. package/cloud/Dialog.mjs +366 -0
  43. package/cloud/Errors.d.mts +22 -0
  44. package/cloud/Errors.mjs +39 -0
  45. package/cloud/Principal.d.mts +26 -0
  46. package/cloud/Principal.mjs +88 -0
  47. package/cloud/RemoteProvider.mjs +88 -0
  48. package/cloud/Transport.d.mts +47 -0
  49. package/cloud/Transport.mjs +454 -0
  50. package/cloud/actions/account.d.mts +9 -0
  51. package/cloud/actions/account.mjs +19 -0
  52. package/cloud/actions/agent.d.mts +18 -0
  53. package/cloud/actions/agent.mjs +429 -0
  54. package/cloud/actions/connection.d.mts +133 -0
  55. package/cloud/actions/connection.mjs +580 -0
  56. package/cloud/actions/grant.d.mts +10 -0
  57. package/cloud/actions/grant.mjs +12 -0
  58. package/cloud/actions/index.d.mts +7 -0
  59. package/cloud/actions/index.mjs +7 -0
  60. package/cloud/actions/machineUsd.d.mts +23 -0
  61. package/cloud/actions/machineUsd.mjs +37 -0
  62. package/cloud/actions/model.d.mts +12 -0
  63. package/cloud/actions/model.mjs +50 -0
  64. package/cloud/actions/mpp.d.mts +28 -0
  65. package/cloud/actions/mpp.mjs +39 -0
  66. package/cloud/index.d.mts +30 -0
  67. package/cloud/index.mjs +9 -0
  68. package/cloud/internal.mjs +533 -0
  69. package/cloud/mercator.mjs +108 -0
  70. package/cloud/server/HostPrincipal.d.mts +34 -0
  71. package/cloud/server/HostPrincipal.mjs +202 -0
  72. package/cloud/server/index.d.mts +1 -0
  73. package/cloud/server/index.mjs +1 -0
  74. package/cloud/types.d.mts +212 -0
  75. package/cloudflare/Agent.d.mts +136 -0
  76. package/cloudflare/Agent.mjs +784 -0
  77. package/cloudflare/egress-subject.mjs +19 -0
  78. package/cloudflare/egress.d.mts +41 -0
  79. package/cloudflare/egress.mjs +119 -0
  80. package/cloudflare/event-socket.mjs +316 -0
  81. package/cloudflare/index.d.mts +7 -0
  82. package/cloudflare/index.mjs +6 -0
  83. package/host/Agent.d.mts +55 -0
  84. package/host/Agent.mjs +1 -0
  85. package/host/index.d.mts +71 -0
  86. package/host/index.mjs +10 -0
  87. package/index.d.mts +46 -0
  88. package/index.mjs +11 -0
  89. package/internal.mjs +673 -61
  90. package/managed/Agent.d.mts +459 -0
  91. package/managed/Agent.mjs +1824 -0
  92. package/managed/ManagedError.d.mts +9 -0
  93. package/managed/ManagedError.mjs +8 -0
  94. package/managed/README.md +244 -0
  95. package/managed/Voice.mjs +209 -0
  96. package/managed/index.d.mts +22 -0
  97. package/managed/index.mjs +2 -0
  98. package/managed/internal.mjs +119 -0
  99. package/node/Agent.d.mts +35 -8
  100. package/node/Agent.mjs +116 -25
  101. package/node/ChatGptSubscription.d.mts +9 -0
  102. package/node/ChatGptSubscription.mjs +17 -0
  103. package/node/Transport.d.mts +41 -0
  104. package/node/Transport.mjs +46 -0
  105. package/node/host.mjs +99 -17
  106. package/node/index.d.mts +45 -1
  107. package/node/index.mjs +16 -1
  108. package/node/workspace.d.mts +10 -0
  109. package/node/workspace.mjs +198 -0
  110. package/package.json +145 -11
  111. package/pkg-node/nanocodex.d.ts +523 -11
  112. package/pkg-node/nanocodex.js +2038 -179
  113. package/pkg-node/nanocodex_bg.wasm.d.ts +100 -29
  114. package/pkg-web/.nanocodex-bindgen-stamp +6 -0
  115. package/pkg-web/nanocodex-build.json +1 -0
  116. package/pkg-web/nanocodex.d.ts +623 -40
  117. package/pkg-web/nanocodex.js +2031 -178
  118. package/pkg-web/nanocodex_bg.js +2667 -0
  119. package/pkg-web/nanocodex_bg.wasm +0 -0
  120. package/pkg-web/nanocodex_bg.wasm.d.ts +100 -29
  121. package/pkg-web/nanocodex_worker.js +9 -0
  122. package/runtime/chatgpt-subscription.mjs +212 -0
  123. package/runtime/cloudflare-durability-store.d.mts +26 -0
  124. package/runtime/cloudflare-durability-store.mjs +14 -0
  125. package/runtime/code-evaluator.worker.mjs +137 -0
  126. package/runtime/code-runtime.mjs +1 -243
  127. package/runtime/durability-store.d.mts +85 -0
  128. package/runtime/durability-store.mjs +752 -0
  129. package/runtime/durability.mjs +174 -0
  130. package/runtime/managed-transport.mjs +461 -0
  131. package/runtime/mcp-runtime.mjs +534 -0
  132. package/runtime/postgres-durability-store.d.mts +64 -0
  133. package/runtime/postgres-durability-store.mjs +581 -0
  134. package/runtime/quickjs-evaluator.d.mts +17 -0
  135. package/runtime/quickjs-evaluator.mjs +236 -0
  136. package/runtime/response-controls.mjs +42 -0
  137. package/runtime/response-lanes.mjs +72 -0
  138. package/runtime/responses-transport.mjs +22 -0
  139. package/runtime/subagents.d.mts +107 -0
  140. package/runtime/subagents.mjs +54 -0
  141. package/runtime/subscription-store.d.mts +8 -0
  142. package/runtime/subscription-store.mjs +42 -0
  143. package/runtime/tempo-provider.d.mts +125 -0
  144. package/runtime/tempo-provider.mjs +264 -0
  145. package/runtime/tool-configuration.mjs +1 -0
  146. package/runtime/tool-router.mjs +1 -0
  147. package/runtime/utf8.mjs +1 -0
  148. package/runtime/worker-evaluator.mjs +162 -0
  149. package/runtime/workspace.d.mts +1 -0
  150. package/runtime/workspace.mjs +1 -0
  151. package/tools/Tools.d.mts +56 -0
  152. package/tools/Tools.mjs +136 -0
  153. package/tools/artifact.d.mts +1 -0
  154. package/tools/artifact.mjs +1 -0
  155. package/tools/attachment.mjs +1 -0
  156. package/tools/bash.d.mts +1 -0
  157. package/tools/bash.mjs +1 -0
  158. package/tools/browser/accountInfo.mjs +795 -0
  159. package/tools/browser/browserBuffer.mjs +18 -0
  160. package/tools/browser/browserCompiler.mjs +155 -0
  161. package/tools/browser/browserEgress.mjs +161 -0
  162. package/tools/browser/browserPython.mjs +133 -0
  163. package/tools/browser/browserShell.mjs +1075 -0
  164. package/tools/browser/browserSprintf.mjs +4 -0
  165. package/tools/browser/browserSsh.mjs +201 -0
  166. package/tools/browser/browserZlib.mjs +136 -0
  167. package/tools/browser/compiler.worker.mjs +160 -0
  168. package/tools/browser/devTunnelsSshBrowser.mjs +13 -0
  169. package/tools/browser/index.d.mts +209 -0
  170. package/tools/browser/index.mjs +174 -0
  171. package/tools/browser/opfsGit.mjs +248 -0
  172. package/tools/browser/python.worker.mjs +114 -0
  173. package/tools/browser/threadGit.mjs +197 -0
  174. package/tools/browser/unsupportedNodeRsa.mjs +5 -0
  175. package/tools/browser/workspace.mjs +83 -0
  176. package/tools/dataset.d.mts +1 -0
  177. package/tools/dataset.mjs +1 -0
  178. package/tools/datasetContract.mjs +1 -0
  179. package/tools/datasetEngine.mjs +1 -0
  180. package/tools/hostedCatalog.d.mts +1 -0
  181. package/tools/hostedCatalog.mjs +1 -0
  182. package/tools/index.d.mts +49 -0
  183. package/tools/index.mjs +19 -0
  184. package/tools/namedTool.mjs +1 -0
  185. package/tools/repository-workspace.d.mts +1 -0
  186. package/tools/repository-workspace.mjs +1 -0
  187. package/tools/ssh.d.mts +1 -0
  188. package/tools/ssh.mjs +1 -0
  189. package/tools/standard.mjs +1 -0
  190. package/tools/standardDescriptions.mjs +1 -0
  191. package/types.d.mts +431 -16
  192. package/worker/ChatGptSubscription.d.mts +9 -0
  193. package/worker/ChatGptSubscription.mjs +29 -0
  194. package/worker/index.d.mts +18 -0
  195. package/worker/index.mjs +5 -0
  196. package/pkg-node/nanocodex_bg.wasm +0 -0
@@ -0,0 +1,316 @@
1
+ import { createBrowserVoice } from "../internal.mjs";
2
+ import {
3
+ managedBrowserVoiceTransport,
4
+ observeManagedAgentEvents,
5
+ } from "../managed/internal.mjs";
6
+ import { createManagedBrowserVoice } from "../managed/Voice.mjs";
7
+ import { BrowserVoiceSession } from "./VoiceSession.mjs";
8
+
9
+ export { VoiceError } from "./VoiceSession.mjs";
10
+
11
+ export const voices = Object.freeze([
12
+ "juniper", "maple", "spruce", "ember", "vale", "breeze", "arbor", "sol", "cove",
13
+ ]);
14
+ export const defaultVoice = "cove";
15
+
16
+ const IDLE_SNAPSHOT = Object.freeze({
17
+ error: undefined,
18
+ muted: false,
19
+ microphoneLevel: 0,
20
+ speakerLevel: 0,
21
+ status: "idle",
22
+ statusText: undefined,
23
+ transcripts: Object.freeze([]),
24
+ voice: undefined,
25
+ });
26
+ const activeResources = new WeakMap();
27
+
28
+ /** Creates a thin browser binding over the Rust-owned Codex voice controller. */
29
+ export function create(agent, options = {}) {
30
+ validateAgent(agent);
31
+ validateOptions(options);
32
+ const listeners = new Set();
33
+ const eventListeners = new Set();
34
+ const managed = agent.type === "managed" || agent.type === "connect";
35
+ const managedTransport = managed ? managedBrowserVoiceTransport(agent) : undefined;
36
+ if (agent.type === "managed" && managedTransport?.sameOrigin) {
37
+ const browserOrigin = browserLocationOrigin();
38
+ if (browserOrigin !== undefined && browserOrigin !== managedTransport.origin) {
39
+ throw new TypeError("Voice.create requires a same-origin managed Agent host; use Connect for cross-origin agents");
40
+ }
41
+ }
42
+ const sessionId = managed ? agent.id : agent.sessionId;
43
+ const target = Object.freeze({ pane: "main", branchId: sessionId });
44
+ let snapshot = IDLE_SNAPSHOT;
45
+ let session;
46
+ let startPromise;
47
+ let stopPromise;
48
+ let watcher;
49
+ let releaseEvents;
50
+ let destroyed = false;
51
+ let generation = 0;
52
+ let resource;
53
+
54
+ function publish(next) {
55
+ snapshot = Object.freeze({
56
+ error: next.error,
57
+ muted: next.muted ?? snapshot.muted,
58
+ microphoneLevel: next.microphoneLevel ?? snapshot.microphoneLevel,
59
+ speakerLevel: next.speakerLevel ?? snapshot.speakerLevel,
60
+ status: next.status,
61
+ statusText: next.statusText,
62
+ transcripts: next.transcripts ?? snapshot.transcripts,
63
+ voice: next.voice,
64
+ });
65
+ for (const listener of listeners) listener();
66
+ }
67
+
68
+ function emit(event) {
69
+ for (const listener of eventListeners) listener(event);
70
+ }
71
+
72
+ function cleanupWatcher() {
73
+ releaseEvents?.();
74
+ releaseEvents = undefined;
75
+ watcher?.off();
76
+ watcher = undefined;
77
+ }
78
+
79
+ function observeAgentEvents(active) {
80
+ if (managed) {
81
+ releaseEvents = observeManagedAgentEvents(agent, ({ event, turnId, cursor }) => (
82
+ active.observe({ type: "event", target, event, turnId, cursor })
83
+ ));
84
+ return;
85
+ }
86
+ watcher = agent.events.watch({ includeAllSessions: false });
87
+ releaseEvents = watcher.onEvent((event) => active.observe({ type: "event", target, event }));
88
+ }
89
+
90
+ async function start(parameters = {}, attempt = 0) {
91
+ if (destroyed) throw new Error("voice resource is destroyed");
92
+ const selectedVoice = parameters.voice ?? options.voice ?? defaultVoice;
93
+ if (!voices.includes(selectedVoice)) throw new TypeError(`unsupported ChatGPT voice: ${selectedVoice}`);
94
+ if (session) return startPromise;
95
+ if (stopPromise) await stopPromise.catch(() => {});
96
+ if (destroyed) throw new Error("voice resource is destroyed");
97
+ if (session) return startPromise;
98
+ const previous = activeResources.get(agent);
99
+ if (previous && previous !== resource) await previous.stop().catch(() => {});
100
+ activeResources.set(agent, resource);
101
+ const current = ++generation;
102
+ publish({
103
+ error: undefined,
104
+ muted: attempt > 0 && snapshot.muted, microphoneLevel: 0, speakerLevel: 0,
105
+ status: "connecting",
106
+ statusText: undefined,
107
+ transcripts: snapshot.transcripts,
108
+ voice: selectedVoice,
109
+ });
110
+ emit(Object.freeze({ type: "connecting", voice: selectedVoice }));
111
+
112
+ const core = Promise.resolve().then(() => managed
113
+ ? createManagedBrowserVoice(agent, selectedVoice)
114
+ : createBrowserVoice(agent, selectedVoice));
115
+ const transport = managedTransport;
116
+ let transcriptSequence = 0;
117
+ const next = new BrowserVoiceSession({
118
+ core,
119
+ sessionId,
120
+ voice: selectedVoice,
121
+ settings: voiceSettings({ ...options, ...parameters, voice: selectedVoice }),
122
+ ...(transport?.call === undefined ? {} : { call: transport.call }),
123
+ ...(transport?.sidebandUrl === undefined ? {} : { sidebandUrl: transport.sidebandUrl }),
124
+ ...(options.callUrl === undefined ? {} : { callUrl: options.callUrl }),
125
+ ...(options.sidebandUrl === undefined ? {} : { sidebandUrl: options.sidebandUrl }),
126
+ ...(options.captureMicrophone === undefined ? {} : { captureMicrophone: options.captureMicrophone }),
127
+ ...(options.beforeAgentTurn === undefined ? {} : { beforeAgentTurn: options.beforeAgentTurn }),
128
+ onStatus(text) {
129
+ if (session === next && generation === current && snapshot.status !== "idle" && snapshot.status !== "error") {
130
+ publish({ ...snapshot, statusText: text });
131
+ }
132
+ },
133
+ onTranscript(speaker, text, metadata = {}) {
134
+ if (session !== next || generation !== current) return;
135
+ const id = `${current}:${speaker}:${metadata.id ?? `entry-${++transcriptSequence}`}`;
136
+ const index = snapshot.transcripts.findIndex((entry) => entry.id === id);
137
+ if (!text.trim() && index < 0) return;
138
+ const entry = Object.freeze({ speaker, text, id, isPartial: metadata.is_partial === true });
139
+ const transcripts = [...snapshot.transcripts];
140
+ if (index < 0) transcripts.push(entry); else transcripts[index] = entry;
141
+ publish({ ...snapshot, transcripts: Object.freeze(transcripts.slice(-200)) });
142
+ emit(Object.freeze({ type: metadata.is_partial ? "transcript.delta" : "transcript", ...entry }));
143
+ },
144
+ onLevels({ microphone, speaker, muted }) {
145
+ if (session === next && generation === current) publish({ ...snapshot, muted, microphoneLevel: microphone, speakerLevel: speaker });
146
+ },
147
+ onUndeliveredAnswer(text) {
148
+ if (destroyed || !text.trim()) return;
149
+ const transcripts = [...snapshot.transcripts];
150
+ const normalized = text.trim().replace(/\s+/g, " ");
151
+ const index = transcripts.findLastIndex((entry) => entry.speaker === "assistant"
152
+ && !entry.recovered && entry.id?.startsWith(`${current}:`)
153
+ && entry.text.trim().replace(/\s+/g, " ") === normalized);
154
+ const id = index < 0 ? `${current}:recovered:${++transcriptSequence}` : transcripts[index].id;
155
+ const entry = Object.freeze({ speaker: "assistant", text, id, isPartial: false, recovered: true });
156
+ if (index < 0) transcripts.push(entry); else transcripts[index] = entry;
157
+ publish({ ...snapshot, transcripts: Object.freeze(transcripts.slice(-200)) });
158
+ emit(Object.freeze({ type: "answer.recovered", ...entry }));
159
+ },
160
+ onTerminated(message) {
161
+ if (session !== next || destroyed || generation !== current) return;
162
+ generation += 1;
163
+ session = undefined;
164
+ cleanupWatcher();
165
+ const closing = next.close().catch(() => next.abort()).finally(() => {
166
+ if (stopPromise === closing) stopPromise = undefined;
167
+ });
168
+ stopPromise = closing;
169
+ if (activeResources.get(agent) === resource) activeResources.delete(agent);
170
+ const error = new Error(message);
171
+ publish({ ...snapshot, error, microphoneLevel: 0, speakerLevel: 0, status: "error", statusText: message });
172
+ emit(Object.freeze({ type: "error", error }));
173
+ },
174
+ });
175
+ session = next;
176
+ next.setMuted(snapshot.muted);
177
+ observeAgentEvents(next);
178
+ startPromise = next.start().then(() => {
179
+ if (destroyed || session !== next || generation !== current) return;
180
+ publish({ ...snapshot, error: undefined, status: "active", statusText: `Voice active (${selectedVoice})` });
181
+ emit(Object.freeze({ type: "started", voice: selectedVoice }));
182
+ }).catch(async (cause) => {
183
+ if (session === next) session = undefined;
184
+ if (activeResources.get(agent) === resource) activeResources.delete(agent);
185
+ cleanupWatcher();
186
+ await next.close().catch(() => next.abort());
187
+ if (destroyed || generation !== current) return;
188
+ if (attempt === 0 && cause?.code === "peer_connection_timeout") return start(parameters, 1);
189
+ const error = cause instanceof Error ? cause : new Error(String(cause));
190
+ publish({ ...snapshot, error, status: "error", statusText: error.message });
191
+ emit(Object.freeze({ type: "error", error }));
192
+ throw error;
193
+ }).finally(() => {
194
+ if (generation === current) startPromise = undefined;
195
+ });
196
+ return startPromise;
197
+ }
198
+
199
+ async function stop() {
200
+ generation += 1;
201
+ if (stopPromise) return stopPromise;
202
+ const active = session;
203
+ session = undefined;
204
+ cleanupWatcher();
205
+ if (activeResources.get(agent) === resource) activeResources.delete(agent);
206
+ if (!active) {
207
+ if (snapshot.status !== "idle") publish({ ...IDLE_SNAPSHOT, transcripts: snapshot.transcripts });
208
+ return;
209
+ }
210
+ if (!destroyed) {
211
+ publish({ ...IDLE_SNAPSHOT, transcripts: Object.freeze(snapshot.transcripts.map((entry) => Object.freeze({ ...entry, isPartial: false }))) });
212
+ emit(Object.freeze({ type: "stopped" }));
213
+ }
214
+ stopPromise = active.close().catch((cause) => {
215
+ active.abort();
216
+ if (!destroyed) {
217
+ const error = cause instanceof Error ? cause : new Error(String(cause));
218
+ publish({ ...IDLE_SNAPSHOT, transcripts: snapshot.transcripts, error, status: "error", statusText: error.message });
219
+ emit(Object.freeze({ type: "error", error }));
220
+ }
221
+ throw cause;
222
+ }).finally(() => { stopPromise = undefined; });
223
+ return stopPromise;
224
+ }
225
+
226
+ async function destroy() {
227
+ if (destroyed) return;
228
+ destroyed = true;
229
+ generation += 1;
230
+ const active = session;
231
+ session = undefined;
232
+ cleanupWatcher();
233
+ if (activeResources.get(agent) === resource) activeResources.delete(agent);
234
+ if (active) await active.close().catch(() => active.abort());
235
+ else if (stopPromise) await stopPromise.catch(() => {});
236
+ listeners.clear();
237
+ eventListeners.clear();
238
+ }
239
+
240
+ function command(method, ...args) {
241
+ if (!session || snapshot.status !== "active") return Promise.reject(new Error("voice is not active"));
242
+ return session.command(method, ...args);
243
+ }
244
+
245
+ resource = Object.freeze({
246
+ cancel: async () => {
247
+ if (!session) return false;
248
+ if (snapshot.status === "connecting") {
249
+ await stop();
250
+ return true;
251
+ }
252
+ return session.cancel();
253
+ },
254
+ destroy,
255
+ setMuted(muted) {
256
+ if (typeof muted !== "boolean") throw new TypeError("voice muted must be a boolean");
257
+ if (!session) throw new Error("voice is not active");
258
+ session.setMuted(muted);
259
+ },
260
+ toggleMuted() { resource.setMuted(!snapshot.muted); },
261
+ noteTypedInput: () => session?.noteTypedInput() ?? Promise.resolve(),
262
+ speak: (text) => command("appendSpeech", text),
263
+ appendText: (text, { role = "user" } = {}) => command("appendText", role, text),
264
+ appendContext: (text) => command("appendContext", text),
265
+ getSnapshot: () => snapshot,
266
+ onEvent(listener) {
267
+ if (typeof listener !== "function") throw new TypeError("voice event listener must be a function");
268
+ eventListeners.add(listener);
269
+ return () => eventListeners.delete(listener);
270
+ },
271
+ start,
272
+ stop,
273
+ subscribe(listener) {
274
+ if (typeof listener !== "function") throw new TypeError("voice listener must be a function");
275
+ listeners.add(listener);
276
+ return () => listeners.delete(listener);
277
+ },
278
+ toggle: (parameters) => session ? stop() : start(parameters),
279
+ });
280
+ return resource;
281
+ }
282
+
283
+ function validateAgent(agent) {
284
+ const local = typeof agent?.sessionId === "string";
285
+ const managed = (agent?.type === "managed" || agent?.type === "connect") && typeof agent.id === "string"
286
+ && typeof agent.turn?.prompt === "function";
287
+ if (!agent || typeof agent !== "object" || (!local && !managed) || typeof agent.events?.watch !== "function") {
288
+ throw new TypeError("Voice.create requires a Nanocodex Agent");
289
+ }
290
+ }
291
+
292
+ function validateOptions(options) {
293
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
294
+ throw new TypeError("Voice.create options must be an object");
295
+ }
296
+ if (options.captureMicrophone !== undefined && typeof options.captureMicrophone !== "function") {
297
+ throw new TypeError("voice captureMicrophone must be a function");
298
+ }
299
+ if (options.beforeAgentTurn !== undefined && typeof options.beforeAgentTurn !== "function") {
300
+ throw new TypeError("voice beforeAgentTurn must be a function");
301
+ }
302
+ }
303
+
304
+ function browserLocationOrigin() {
305
+ try {
306
+ const origin = globalThis.location?.origin;
307
+ return typeof origin === "string" && origin !== "null" ? origin : undefined;
308
+ } catch {
309
+ return undefined;
310
+ }
311
+ }
312
+
313
+ function voiceSettings(options) {
314
+ return Object.fromEntries(["voice", "instructions", "pace", "updates", "handoffMode", "acknowledgements"]
315
+ .filter((key) => options[key] !== undefined).map((key) => [key, options[key]]));
316
+ }