@alexkroman1/aai-ui 5.0.1 → 5.1.1

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 (30) hide show
  1. package/dist/{_colors-DYX7XRTr.js → _colors-Bfh3-BVE.js} +3 -1
  2. package/dist/{_utils-CN1yVgYS.js → _utils-CsqbIVGI.js} +10 -1
  3. package/dist/_utils.d.ts +6 -0
  4. package/dist/{chat-view-CW-tZCMm.js → chat-view-C6N2oXjo.js} +4 -4
  5. package/dist/client-config.d.ts +3 -2
  6. package/dist/components/_colors.d.ts +2 -0
  7. package/dist/components/chat-view.js +1 -1
  8. package/dist/components/controls.js +1 -1
  9. package/dist/components/message-list.js +1 -1
  10. package/dist/components/start-screen.js +1 -1
  11. package/dist/components/tool-call-block.js +1 -1
  12. package/dist/components/url-chips.d.ts +2 -1
  13. package/dist/{controls-DV368uhb.js → controls-Cqg7T4FN.js} +5 -8
  14. package/dist/default-client/assets/{audio-BdEKgLwT.js → audio-C1U_foBG.js} +1 -1
  15. package/dist/default-client/assets/{capture-processor-_QJe3nzY.js → capture-processor-dyyJArQs.js} +1 -1
  16. package/dist/default-client/assets/index-CGZajs1V.js +192 -0
  17. package/dist/default-client/assets/{playback-processor-C_F19eUJ.js → playback-processor-BO2TdYrv.js} +1 -1
  18. package/dist/default-client/index.html +1 -1
  19. package/dist/define-client.d.ts +6 -8
  20. package/dist/define-client.js +5 -4
  21. package/dist/hooks.js +1 -1
  22. package/dist/index.js +5 -5
  23. package/dist/{message-list-q2lTiFhF.js → message-list-CD9GxrlH.js} +106 -104
  24. package/dist/{session-core-ByBLnLfW.js → session-core-Bvga6tUS.js} +31 -25
  25. package/dist/session-core-audio-setup.d.ts +10 -1
  26. package/dist/session-core-types.d.ts +8 -4
  27. package/dist/session-core.js +1 -1
  28. package/dist/{tool-call-block-DIxpG8GM.js → tool-call-block-O9vLqoyU.js} +2 -2
  29. package/package.json +2 -2
  30. package/dist/default-client/assets/index-RQnhfgSS.js +0 -192
@@ -1,4 +1,4 @@
1
- import{i as e}from"./index-RQnhfgSS.js";import{t}from"./_module-url-BX0RuRU2.js";var n=t(`
1
+ import{i as e}from"./index-CGZajs1V.js";import{t}from"./_module-url-BX0RuRU2.js";var n=t(`
2
2
  class PlaybackProcessor extends AudioWorkletProcessor {
3
3
  constructor(options) {
4
4
  super();
@@ -6,7 +6,7 @@
6
6
  <title>aai</title>
7
7
  <link rel="icon" href="./favicon.ico" />
8
8
  <style>html, body { background: #FBF8F2; margin: 0; }</style>
9
- <script type="module" crossorigin src="./assets/index-RQnhfgSS.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-CGZajs1V.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-CRfdZPH1.css">
11
11
  </head>
12
12
  <body>
@@ -2,25 +2,23 @@
2
2
  import { type ComponentType } from "react";
3
3
  import { type ToolDisplayConfig } from "./components/tool-config-context.ts";
4
4
  import type { SessionCore } from "./session-core-types.ts";
5
- import type { ClientTheme, WebSocketConstructor } from "./types.ts";
5
+ import type { ClientTheme, VoiceSessionOptions } from "./types.ts";
6
6
  /**
7
7
  * Base options shared by both client tiers.
8
8
  *
9
+ * The session-forwarded fields are picked from {@link VoiceSessionOptions}
10
+ * (one source of truth for types and docs) rather than re-declared — a
11
+ * re-declared copy is exactly how doc comments drift.
12
+ *
9
13
  * @public
10
14
  */
11
- type BaseOptions = {
15
+ type BaseOptions = Pick<VoiceSessionOptions, "onSessionId" | "resumeSessionId" | "WebSocket"> & {
12
16
  /** CSS selector or DOM element to render into. Defaults to `"#app"`. */
13
17
  target?: string | HTMLElement;
14
18
  /** Base URL of the AAI platform server. Derived from `location.href` by default. */
15
19
  platformUrl?: string;
16
20
  /** Theme color overrides. */
17
21
  theme?: ClientTheme;
18
- /** Called when the server sends a session ID. Store it for reconnection. */
19
- onSessionId?: (sessionId: string) => void;
20
- /** Session ID from a previous connection for resuming persisted state. */
21
- resumeSessionId?: string;
22
- /** WebSocket constructor override. Passed through to session options. */
23
- WebSocket?: WebSocketConstructor;
24
22
  };
25
23
  /**
26
24
  * Tier 1: Config-only options. Renders the default shell (StartScreen + ChatView).
@@ -1,7 +1,8 @@
1
- import { r as fetchClientConfig, t as createSessionCore } from "./session-core-ByBLnLfW.js";
1
+ import { r as fetchClientConfig, t as createSessionCore } from "./session-core-Bvga6tUS.js";
2
2
  import { SessionProvider, ThemeProvider } from "./context.js";
3
- import { t as ChatView } from "./chat-view-CW-tZCMm.js";
4
- import { n as ToolConfigContext } from "./tool-call-block-DIxpG8GM.js";
3
+ import { t as ChatView } from "./chat-view-C6N2oXjo.js";
4
+ import { t as pageBaseUrl } from "./_utils-CsqbIVGI.js";
5
+ import { n as ToolConfigContext } from "./tool-call-block-O9vLqoyU.js";
5
6
  import { SidebarLayout } from "./components/sidebar-layout.js";
6
7
  import { StartScreen } from "./components/start-screen.js";
7
8
  import { createElement, useEffect, useState } from "react";
@@ -86,7 +87,7 @@ function DefaultRoot({ platformUrl, name, Sidebar, sidebarWidth }) {
86
87
  */
87
88
  function client(config) {
88
89
  const container = resolveContainer(config.target);
89
- const platformUrl = config.platformUrl ?? globalThis.location.origin + globalThis.location.pathname;
90
+ const platformUrl = config.platformUrl ?? pageBaseUrl();
90
91
  const session = createSessionCore({
91
92
  platformUrl,
92
93
  onSessionId: config.onSessionId,
package/dist/hooks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { useSessionSelector } from "./context.js";
2
- import { n as tryParseJSON } from "./_utils-CN1yVgYS.js";
2
+ import { r as tryParseJSON } from "./_utils-CsqbIVGI.js";
3
3
  import { useEffect, useRef } from "react";
4
4
  //#region hooks.ts
5
5
  /** Index of the first item whose sequence number is above the watermark (tail scan from the end). */
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { n as buildAgentUrl, r as fetchClientConfig, t as createSessionCore } from "./session-core-ByBLnLfW.js";
1
+ import { n as buildAgentUrl, r as fetchClientConfig, t as createSessionCore } from "./session-core-Bvga6tUS.js";
2
2
  import { SessionProvider, ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
3
3
  import { Button } from "./components/button.js";
4
- import { t as ChatView } from "./chat-view-CW-tZCMm.js";
5
- import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-DV368uhb.js";
6
- import { n as Markdown, t as MessageList } from "./message-list-q2lTiFhF.js";
7
- import { n as ToolConfigContext } from "./tool-call-block-DIxpG8GM.js";
4
+ import { t as ChatView } from "./chat-view-C6N2oXjo.js";
5
+ import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-Cqg7T4FN.js";
6
+ import { n as Markdown, t as MessageList } from "./message-list-CD9GxrlH.js";
7
+ import { n as ToolConfigContext } from "./tool-call-block-O9vLqoyU.js";
8
8
  import { SidebarLayout } from "./components/sidebar-layout.js";
9
9
  import { StartScreen } from "./components/start-screen.js";
10
10
  import { VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
@@ -1,6 +1,6 @@
1
1
  import { useSessionSelector, useTheme } from "./context.js";
2
- import { a as primaryTint, i as TEXT_MUTED, n as SURFACE_TINT, r as TEXT_FAINT } from "./_colors-DYX7XRTr.js";
3
- import { t as ToolCallBlock } from "./tool-call-block-DIxpG8GM.js";
2
+ import { i as TEXT_MUTED, n as SURFACE_TINT, o as primaryTint, r as TEXT_FAINT } from "./_colors-Bfh3-BVE.js";
3
+ import { t as ToolCallBlock } from "./tool-call-block-O9vLqoyU.js";
4
4
  import clsx from "clsx";
5
5
  import { memo, useCallback, useEffect, useMemo, useRef } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -52,110 +52,111 @@ function escapeBareListMarkers(text) {
52
52
  */
53
53
  const Markdown = memo(function Markdown({ text }) {
54
54
  const theme = useTheme();
55
- return /* @__PURE__ */ jsx(ReactMarkdown, {
56
- remarkPlugins: [remarkGfm],
57
- components: {
58
- p: (props) => /* @__PURE__ */ jsx("p", {
59
- className: "my-1.5 first:mt-0 last:mb-0",
60
- ...props
61
- }),
62
- ul: (props) => /* @__PURE__ */ jsx("ul", {
63
- className: "my-1.5 list-disc pl-5",
64
- ...props
65
- }),
66
- ol: (props) => /* @__PURE__ */ jsx("ol", {
67
- className: "my-1.5 list-decimal pl-5",
68
- ...props
69
- }),
70
- li: (props) => /* @__PURE__ */ jsx("li", {
71
- className: "my-0.5",
72
- ...props
73
- }),
74
- h1: (props) => /* @__PURE__ */ jsx("h1", {
75
- className: "mt-3 mb-1.5 text-[17px] font-semibold",
76
- ...props
77
- }),
78
- h2: (props) => /* @__PURE__ */ jsx("h2", {
79
- className: "mt-3 mb-1.5 text-[16px] font-semibold",
80
- ...props
81
- }),
82
- h3: (props) => /* @__PURE__ */ jsx("h3", {
83
- className: "mt-3 mb-1.5 text-[15px] font-semibold",
84
- ...props
85
- }),
86
- a: ({ style, ...props }) => /* @__PURE__ */ jsx("a", {
87
- className: "underline underline-offset-2",
88
- style: {
89
- color: theme.primary,
90
- ...style
91
- },
92
- target: "_blank",
93
- rel: "noreferrer noopener",
94
- ...props
95
- }),
96
- code: ({ className, style, children, ...rest }) => {
97
- return /language-/.test(className ?? "") ? /* @__PURE__ */ jsx("code", {
98
- className: "font-aai-mono text-[12.5px]",
99
- ...rest,
100
- children
101
- }) : /* @__PURE__ */ jsx("code", {
102
- className: "rounded-sm border px-1 py-0.5 font-aai-mono text-[12.5px]",
103
- style: {
104
- borderColor: theme.border,
105
- background: SURFACE_TINT,
106
- ...style
107
- },
108
- ...rest,
109
- children
110
- });
55
+ const components = useMemo(() => ({
56
+ p: (props) => /* @__PURE__ */ jsx("p", {
57
+ className: "my-1.5 first:mt-0 last:mb-0",
58
+ ...props
59
+ }),
60
+ ul: (props) => /* @__PURE__ */ jsx("ul", {
61
+ className: "my-1.5 list-disc pl-5",
62
+ ...props
63
+ }),
64
+ ol: (props) => /* @__PURE__ */ jsx("ol", {
65
+ className: "my-1.5 list-decimal pl-5",
66
+ ...props
67
+ }),
68
+ li: (props) => /* @__PURE__ */ jsx("li", {
69
+ className: "my-0.5",
70
+ ...props
71
+ }),
72
+ h1: (props) => /* @__PURE__ */ jsx("h1", {
73
+ className: "mt-3 mb-1.5 text-[17px] font-semibold",
74
+ ...props
75
+ }),
76
+ h2: (props) => /* @__PURE__ */ jsx("h2", {
77
+ className: "mt-3 mb-1.5 text-[16px] font-semibold",
78
+ ...props
79
+ }),
80
+ h3: (props) => /* @__PURE__ */ jsx("h3", {
81
+ className: "mt-3 mb-1.5 text-[15px] font-semibold",
82
+ ...props
83
+ }),
84
+ a: ({ style, ...props }) => /* @__PURE__ */ jsx("a", {
85
+ className: "underline underline-offset-2",
86
+ style: {
87
+ color: theme.primary,
88
+ ...style
111
89
  },
112
- pre: ({ style, ...props }) => /* @__PURE__ */ jsx("pre", {
113
- className: "my-1.5 overflow-x-auto rounded-md border p-2.5 whitespace-pre-wrap wrap-break-word",
90
+ target: "_blank",
91
+ rel: "noreferrer noopener",
92
+ ...props
93
+ }),
94
+ code: ({ className, style, children, ...rest }) => {
95
+ return /language-/.test(className ?? "") ? /* @__PURE__ */ jsx("code", {
96
+ className: "font-aai-mono text-[12.5px]",
97
+ ...rest,
98
+ children
99
+ }) : /* @__PURE__ */ jsx("code", {
100
+ className: "rounded-sm border px-1 py-0.5 font-aai-mono text-[12.5px]",
114
101
  style: {
115
102
  borderColor: theme.border,
116
103
  background: SURFACE_TINT,
117
104
  ...style
118
105
  },
119
- ...props
120
- }),
121
- blockquote: ({ style, ...props }) => /* @__PURE__ */ jsx("blockquote", {
122
- className: "my-1.5 border-l-2 pl-3",
123
- style: {
124
- borderColor: theme.border,
125
- color: TEXT_MUTED,
126
- ...style
127
- },
128
- ...props
129
- }),
130
- table: (props) => /* @__PURE__ */ jsx("table", {
131
- className: "my-1.5 w-full border-collapse text-sm",
132
- ...props
133
- }),
134
- th: ({ style, ...props }) => /* @__PURE__ */ jsx("th", {
135
- className: "border px-2 py-1 text-left font-medium",
136
- style: {
137
- borderColor: theme.border,
138
- ...style
139
- },
140
- ...props
141
- }),
142
- td: ({ style, ...props }) => /* @__PURE__ */ jsx("td", {
143
- className: "border px-2 py-1",
144
- style: {
145
- borderColor: theme.border,
146
- ...style
147
- },
148
- ...props
149
- }),
150
- hr: ({ style, ...props }) => /* @__PURE__ */ jsx("hr", {
151
- className: "my-2.5 border-t",
152
- style: {
153
- borderColor: theme.border,
154
- ...style
155
- },
156
- ...props
157
- })
106
+ ...rest,
107
+ children
108
+ });
158
109
  },
110
+ pre: ({ style, ...props }) => /* @__PURE__ */ jsx("pre", {
111
+ className: "my-1.5 overflow-x-auto rounded-md border p-2.5 whitespace-pre-wrap wrap-break-word",
112
+ style: {
113
+ borderColor: theme.border,
114
+ background: SURFACE_TINT,
115
+ ...style
116
+ },
117
+ ...props
118
+ }),
119
+ blockquote: ({ style, ...props }) => /* @__PURE__ */ jsx("blockquote", {
120
+ className: "my-1.5 border-l-2 pl-3",
121
+ style: {
122
+ borderColor: theme.border,
123
+ color: TEXT_MUTED,
124
+ ...style
125
+ },
126
+ ...props
127
+ }),
128
+ table: (props) => /* @__PURE__ */ jsx("table", {
129
+ className: "my-1.5 w-full border-collapse text-sm",
130
+ ...props
131
+ }),
132
+ th: ({ style, ...props }) => /* @__PURE__ */ jsx("th", {
133
+ className: "border px-2 py-1 text-left font-medium",
134
+ style: {
135
+ borderColor: theme.border,
136
+ ...style
137
+ },
138
+ ...props
139
+ }),
140
+ td: ({ style, ...props }) => /* @__PURE__ */ jsx("td", {
141
+ className: "border px-2 py-1",
142
+ style: {
143
+ borderColor: theme.border,
144
+ ...style
145
+ },
146
+ ...props
147
+ }),
148
+ hr: ({ style, ...props }) => /* @__PURE__ */ jsx("hr", {
149
+ className: "my-2.5 border-t",
150
+ style: {
151
+ borderColor: theme.border,
152
+ ...style
153
+ },
154
+ ...props
155
+ })
156
+ }), [theme]);
157
+ return /* @__PURE__ */ jsx(ReactMarkdown, {
158
+ remarkPlugins: [remarkGfm],
159
+ components,
159
160
  children: escapeBareListMarkers(text)
160
161
  });
161
162
  });
@@ -342,6 +343,10 @@ const MessageList = memo(function MessageList({ className }) {
342
343
  messages
343
344
  ]);
344
345
  const { anchorRef, onScroll } = useAutoScroll(contentVersion, userTranscript !== null);
346
+ const streamingMessage = useMemo(() => agentTranscript ? {
347
+ role: "assistant",
348
+ content: agentTranscript
349
+ } : null, [agentTranscript]);
345
350
  const items = useMemo(() => interleave(messages, toolCalls, (msg) => /* @__PURE__ */ jsx(MessageBubble, {
346
351
  message: msg,
347
352
  theme
@@ -359,11 +364,8 @@ const MessageList = memo(function MessageList({ className }) {
359
364
  className: "flex flex-col gap-4 p-7",
360
365
  children: [
361
366
  items,
362
- agentTranscript && /* @__PURE__ */ jsx(MessageBubble, {
363
- message: {
364
- role: "assistant",
365
- content: agentTranscript
366
- },
367
+ streamingMessage && /* @__PURE__ */ jsx(MessageBubble, {
368
+ message: streamingMessage,
367
369
  theme
368
370
  }),
369
371
  userTranscript !== null && /* @__PURE__ */ jsx(UserBubble, {
@@ -1,6 +1,6 @@
1
1
  import { MIC_SEND_MAX_BUFFERED_BYTES } from "./types.js";
2
2
  import { CLIENT_CONFIG_PATH, ClientConfigResponseSchema, ServerMessageSchema, lenientParse } from "@alexkroman1/aai/protocol";
3
- import { DEFAULT_MAX_HISTORY, WS_OPEN, createEpoch, errorMessage, safeJsonParse } from "@alexkroman1/aai";
3
+ import { DEFAULT_MAX_HISTORY, WS_OPEN, createEpoch, errorMessage, safeJsonParse, toArgsRecord } from "@alexkroman1/aai";
4
4
  import ReconnectingWebSocket from "partysocket/ws";
5
5
  //#region client-config.ts
6
6
  /**
@@ -32,8 +32,9 @@ const AGENT_DEFAULT = {};
32
32
  * server) — a durable fact worth latching. A lookup that FAILED means
33
33
  * nothing about the server, and treating the two alike is how a single 503
34
34
  * (a sandbox mid-boot, or one that failed to start) pinned a session to the
35
- * platform's `/:slug/websocket` — answered `410 Gone` on every retry, with
36
- * no re-brokering even after the agent recovered.
35
+ * platform's `/:slug/websocket` — a WebSocket redirect browsers don't
36
+ * follow, so every retry failed with no re-brokering even after the agent
37
+ * recovered.
37
38
  */
38
39
  async function loadClientConfig(platformUrl, fetchFn) {
39
40
  const doFetch = fetchFn ?? ((input, init) => globalThis.fetch(input, init));
@@ -60,6 +61,24 @@ async function fetchClientConfig(platformUrl, fetchFn) {
60
61
  * staleness/failure handling), while `session-core.ts` owns the state store
61
62
  * and connection lifecycle.
62
63
  */
64
+ let audioModulesPromise = null;
65
+ /**
66
+ * Load the audio module and worklet sources, memoized so a prefetch at
67
+ * `connect()` time overlaps the chunk fetch with the WebSocket handshake
68
+ * instead of serializing it behind the server's `config` frame. A failed
69
+ * load clears the memo so the next attempt retries the import.
70
+ */
71
+ function loadAudioModules() {
72
+ audioModulesPromise ??= Promise.all([
73
+ import("./audio.js"),
74
+ import("./worklets/capture-processor.js").then((m) => m.default),
75
+ import("./worklets/playback-processor.js").then((m) => m.default)
76
+ ]).catch((err) => {
77
+ audioModulesPromise = null;
78
+ throw err;
79
+ });
80
+ return audioModulesPromise;
81
+ }
63
82
  /**
64
83
  * Initialize audio capture and playback after the server sends a ready config.
65
84
  *
@@ -93,11 +112,7 @@ async function initAudioCapture(conn, msg, deps) {
93
112
  });
94
113
  };
95
114
  try {
96
- const [{ createVoiceIO }, captureWorklet, playbackWorklet] = await Promise.all([
97
- import("./audio.js"),
98
- import("./worklets/capture-processor.js").then((m) => m.default),
99
- import("./worklets/playback-processor.js").then((m) => m.default)
100
- ]);
115
+ const [{ createVoiceIO }, captureWorklet, playbackWorklet] = await loadAudioModules();
101
116
  const io = await createVoiceIO({
102
117
  sttSampleRate: msg.sampleRate,
103
118
  ttsSampleRate: msg.ttsSampleRate,
@@ -105,17 +120,11 @@ async function initAudioCapture(conn, msg, deps) {
105
120
  playbackWorkletSrc: playbackWorklet,
106
121
  onMicData: (pcm16) => {
107
122
  try {
108
- deps.sendAudio(new Uint8Array(pcm16));
123
+ deps.sendAudio(pcm16);
109
124
  } catch {
110
125
  console.debug("[aai-ui] sendAudio dropped: connection closed");
111
126
  }
112
127
  },
113
- onPlaybackStats: (stats) => {
114
- console.warn("[aai-ui] playback concealed a gap in this turn", stats);
115
- },
116
- onMicSilent: () => {
117
- console.warn("[aai-ui] microphone is delivering only silence — check the selected input device");
118
- },
119
128
  onError: (err) => {
120
129
  if (!conn.generation.isCurrent(gen)) return;
121
130
  reportAudioFailure(err.message);
@@ -128,7 +137,7 @@ async function initAudioCapture(conn, msg, deps) {
128
137
  conn.voiceIO?.close().catch(() => {});
129
138
  conn.voiceIO = io;
130
139
  if (conn.preInitAudio.length > 0) {
131
- for (const chunk of conn.preInitAudio) io.enqueue(chunk.buffer);
140
+ for (const chunk of conn.preInitAudio) io.enqueue(chunk);
132
141
  conn.preInitAudio = [];
133
142
  }
134
143
  deps.sendJson({ type: "audio_ready" });
@@ -304,7 +313,7 @@ function createMessageHandlers(deps) {
304
313
  updateState({ toolCalls: appendCapped(getSnapshot().toolCalls, {
305
314
  callId: e.toolCallId,
306
315
  name: e.toolName,
307
- args: e.args ?? {},
316
+ args: toArgsRecord(e.args),
308
317
  status: "pending",
309
318
  seq: ++toolCallSeq,
310
319
  afterMessageId: getSnapshot().messages.at(-1)?.id ?? -1
@@ -363,7 +372,7 @@ function createMessageHandlers(deps) {
363
372
  const snap = getSnapshot();
364
373
  if (snap.state === "error" || snap.state === "disconnected" && snap.error !== null) return;
365
374
  if (snap.state !== "speaking") updateState({ state: "speaking" });
366
- if (conn.voiceIO) conn.voiceIO.enqueue(chunk.buffer);
375
+ if (conn.voiceIO) conn.voiceIO.enqueue(chunk);
367
376
  else if (conn.preInitAudio.length < MAX_PREINIT_AUDIO_CHUNKS) conn.preInitAudio.push(chunk);
368
377
  }
369
378
  /** See {@link MessageHandlers.settleWhenAudioDrained}. Captures
@@ -393,7 +402,7 @@ function createMessageHandlers(deps) {
393
402
  }
394
403
  function handleMessage(data) {
395
404
  if (data instanceof ArrayBuffer) {
396
- playAudioChunk(new Uint8Array(data));
405
+ playAudioChunk(data);
397
406
  return;
398
407
  }
399
408
  if (typeof data !== "string") {
@@ -468,7 +477,7 @@ function reconnectPending(socket) {
468
477
  //#region session-core-url.ts
469
478
  /** Build the session WebSocket URL from the platform URL and resume state. */
470
479
  function buildWsUrl(platformUrl, resume, sessionId) {
471
- return applyResumeParams(new URL("websocket", platformUrl.endsWith("/") ? platformUrl : `${platformUrl}/`), resume, sessionId);
480
+ return applyResumeParams(buildAgentUrl(platformUrl, "websocket"), resume, sessionId);
472
481
  }
473
482
  /**
474
483
  * Turn a broker-provided session URL (`sessionUrl` from `GET client-config`
@@ -658,11 +667,7 @@ function createSessionCore(options) {
658
667
  async function currentWsUrl() {
659
668
  const cfg = serverIsBroker === false ? null : await loadClientConfig(options.platformUrl);
660
669
  if (cfg) serverIsBroker = cfg.sessionUrl !== void 0;
661
- const url = cfg?.sessionUrl ? buildBrokeredWsUrl(cfg.sessionUrl, hasConnected, sessionId) : buildWsUrl(options.platformUrl, hasConnected, sessionId);
662
- const display = new URL(url);
663
- display.search = "";
664
- if (display.toString() !== currentSnapshot.apiUrl) updateState({ apiUrl: display.toString() });
665
- return url.toString();
670
+ return (cfg?.sessionUrl ? buildBrokeredWsUrl(cfg.sessionUrl, hasConnected, sessionId) : buildWsUrl(options.platformUrl, hasConnected, sessionId)).toString();
666
671
  }
667
672
  /** Open a socket: an injected constructor as-is (tests — connects to the
668
673
  * same-origin path, no brokering), or partysocket's reconnecting
@@ -680,6 +685,7 @@ function createSessionCore(options) {
680
685
  state: "connecting",
681
686
  error: null
682
687
  });
688
+ loadAudioModules().catch(() => {});
683
689
  teardownConnection();
684
690
  conn.generation.bump();
685
691
  const controller = new AbortController();
@@ -4,7 +4,7 @@ import type { ConnState, SessionSnapshot } from "./session-core-types.ts";
4
4
  /** Dependencies `initAudioCapture` needs from the owning session core. */
5
5
  export type AudioSetupDeps = {
6
6
  sendJson: (msg: ClientMessage) => void;
7
- sendAudio: (bytes: Uint8Array) => void;
7
+ sendAudio: (bytes: ArrayBuffer) => void;
8
8
  updateState: (partial: Partial<SessionSnapshot>) => void;
9
9
  /** Turn-boundary-guarded drain from the message handlers — replays a
10
10
  * buffered `audio_done` without stomping a barge-in's state. */
@@ -12,6 +12,14 @@ export type AudioSetupDeps = {
12
12
  /** Release the mic/VoiceIO (used when the audio path dies non-fatally). */
13
13
  cleanupAudio: () => void;
14
14
  };
15
+ type AudioModules = [typeof import("./audio.ts"), string, string];
16
+ /**
17
+ * Load the audio module and worklet sources, memoized so a prefetch at
18
+ * `connect()` time overlaps the chunk fetch with the WebSocket handshake
19
+ * instead of serializing it behind the server's `config` frame. A failed
20
+ * load clears the memo so the next attempt retries the import.
21
+ */
22
+ export declare function loadAudioModules(): Promise<AudioModules>;
15
23
  /**
16
24
  * Initialize audio capture and playback after the server sends a ready config.
17
25
  *
@@ -31,3 +39,4 @@ export declare function initAudioCapture(conn: ConnState, msg: {
31
39
  sampleRate: number;
32
40
  ttsSampleRate: number;
33
41
  }, deps: AudioSetupDeps): Promise<void>;
42
+ export {};
@@ -29,9 +29,13 @@ export type SessionSnapshot = {
29
29
  /** True while the microphone is live and streaming to the server. */
30
30
  readonly recording: boolean;
31
31
  /**
32
- * The WebSocket URL a program can connect to directly (the same endpoint
33
- * this session uses), e.g. `wss://host/my-agent/websocket`. Derived from
34
- * `platformUrl` at construction — available before connecting.
32
+ * The WebSocket URL a program can connect to directly — the long-living
33
+ * platform endpoint, e.g. `wss://host/my-agent/websocket`. Derived from
34
+ * `platformUrl` at construction — available before connecting — and never
35
+ * replaced by the brokered sandbox tunnel URL the session may actually be
36
+ * connected to: that URL is ephemeral (it dies when the sandbox is
37
+ * replaced), while the platform endpoint is stable and upgrades
38
+ * programmatic clients to the current sandbox endpoint itself.
35
39
  */
36
40
  readonly apiUrl: string;
37
41
  /**
@@ -112,7 +116,7 @@ export type ConnState = {
112
116
  * the playback worklet once init completes. Closes the race between the
113
117
  * server starting greeting audio (immediately on S2S connect) and the
114
118
  * client awaiting mic permission + worklet registration. */
115
- preInitAudio: Uint8Array[];
119
+ preInitAudio: ArrayBuffer[];
116
120
  /** True if `audio_done` arrived before `voiceIO` was initialized. The done
117
121
  * signal must be replayed after draining preInitAudio, or a short greeting
118
122
  * buffered during mic-permission never finishes playing. */
@@ -1,2 +1,2 @@
1
- import { t as createSessionCore } from "./session-core-ByBLnLfW.js";
1
+ import { t as createSessionCore } from "./session-core-Bvga6tUS.js";
2
2
  export { createSessionCore };
@@ -1,7 +1,7 @@
1
1
  import { useTheme } from "./context.js";
2
- import { r as TEXT_FAINT } from "./_colors-DYX7XRTr.js";
2
+ import { r as TEXT_FAINT } from "./_colors-Bfh3-BVE.js";
3
3
  import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
4
- import { n as tryParseJSON, t as truncate } from "./_utils-CN1yVgYS.js";
4
+ import { n as truncate, r as tryParseJSON } from "./_utils-CsqbIVGI.js";
5
5
  import clsx from "clsx";
6
6
  import { createContext, memo, useContext, useMemo, useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-ui",
3
- "version": "5.0.1",
3
+ "version": "5.1.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -22,7 +22,7 @@
22
22
  "react-markdown": "^10.1.0",
23
23
  "remark-gfm": "^4.0.1",
24
24
  "use-sync-external-store": "^1.6.0",
25
- "@alexkroman1/aai": "5.0.1"
25
+ "@alexkroman1/aai": "5.1.1"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": "^19.0.0",