@alexkroman1/aai-ui 0.16.0 → 0.18.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 (123) hide show
  1. package/README.md +6 -7
  2. package/dist/_submission-timing.d.ts +40 -0
  3. package/dist/_submission.d.ts +56 -0
  4. package/dist/_theme.d.ts +17 -0
  5. package/dist/_theme.test-d.d.ts +14 -0
  6. package/dist/{_utils-Mi-SCFA1.js → _utils-BlZBJPdj.js} +4 -5
  7. package/dist/_utils.d.ts +4 -5
  8. package/dist/agent-state-labels.d.ts +8 -3
  9. package/dist/agent-state.d.ts +4 -2
  10. package/dist/append-dictation.d.ts +23 -0
  11. package/dist/audio/dictation.d.ts +36 -0
  12. package/dist/audio/index.d.ts +4 -2
  13. package/dist/audio/index.js +44 -1
  14. package/dist/{chat-view-DIwPoAEG.js → chat-view-BwKpF0MN.js} +65 -45
  15. package/dist/{client-config-C7fQrioM.js → client-config-DcMCWmkO.js} +8 -13
  16. package/dist/client-config.d.ts +29 -10
  17. package/dist/components/_form-readiness.d.ts +2 -1
  18. package/dist/components/audio-result.d.ts +2 -0
  19. package/dist/components/bullet-list.d.ts +1 -0
  20. package/dist/components/button.d.ts +2 -2
  21. package/dist/components/button.js +1 -1
  22. package/dist/components/chat-view.js +1 -2
  23. package/dist/components/console-shell.d.ts +8 -5
  24. package/dist/components/controls.d.ts +1 -0
  25. package/dist/components/controls.js +2 -2
  26. package/dist/components/conversation-view.d.ts +1 -0
  27. package/dist/components/facts.d.ts +1 -0
  28. package/dist/components/form-fields.d.ts +31 -12
  29. package/dist/components/form-types.d.ts +48 -2
  30. package/dist/components/form.d.ts +10 -5
  31. package/dist/components/markdown.d.ts +1 -0
  32. package/dist/components/message-list.d.ts +1 -0
  33. package/dist/components/message-list.js +1 -2
  34. package/dist/components/session-controls.d.ts +7 -2
  35. package/dist/components/session-error-banner.d.ts +1 -0
  36. package/dist/components/session-state-dot.d.ts +45 -20
  37. package/dist/components/sidebar-layout.d.ts +2 -2
  38. package/dist/components/sidebar-layout.js +1 -1
  39. package/dist/components/start-screen.js +2 -2
  40. package/dist/components/tool-call-block.d.ts +3 -3
  41. package/dist/components/tool-call-block.js +1 -2
  42. package/dist/components/tool-call-row.d.ts +1 -0
  43. package/dist/components/tool-config-context.d.ts +23 -5
  44. package/dist/components/url-chips.d.ts +4 -3
  45. package/dist/components/workflow-fields.d.ts +36 -11
  46. package/dist/components/workflow-pending-note.d.ts +31 -12
  47. package/dist/components/workflow-run-error.d.ts +1 -0
  48. package/dist/components/workflow-run-panel.d.ts +1 -0
  49. package/dist/context-BF0KsI9X.js +473 -0
  50. package/dist/context.d.ts +8 -7
  51. package/dist/context.js +1 -381
  52. package/dist/default-client/assets/audio-CtBvSS3L.js +1 -0
  53. package/dist/default-client/assets/{capture-processor-CXCWxCO8.js → capture-processor-BZXisGjZ.js} +1 -1
  54. package/dist/default-client/assets/index-BkjCv4MX.css +2 -0
  55. package/dist/default-client/assets/index-DUQoaxdq.js +127 -0
  56. package/dist/default-client/assets/{playback-processor-DXHfyQD9.js → playback-processor-BHk-iXEU.js} +1 -1
  57. package/dist/default-client/index.html +2 -2
  58. package/dist/default-client.d.ts +1 -3
  59. package/dist/define-client.d.ts +32 -28
  60. package/dist/define-client.js +85 -44
  61. package/dist/dictation-transcriber.d.ts +49 -0
  62. package/dist/{eyebrow-UfmSz9yy.js → eyebrow-Bp4y1rZ9.js} +1 -1
  63. package/dist/{hooks-DN4gHYsj.js → hooks-1CoQY_Fc.js} +2 -2
  64. package/dist/hooks.js +1 -2
  65. package/dist/inbox-protocol.d.ts +14 -4
  66. package/dist/inbox.d.ts +1 -0
  67. package/dist/index.d.ts +12 -9
  68. package/dist/index.js +905 -277
  69. package/dist/internal.d.ts +1 -1
  70. package/dist/internal.js +6 -6
  71. package/dist/linked-client.d.ts +2 -0
  72. package/dist/{message-list-DHZcLhRA.js → message-list-BBHLK05X.js} +24 -13
  73. package/dist/page.d.ts +2 -0
  74. package/dist/phone.d.ts +1 -0
  75. package/dist/route-fetch.d.ts +8 -1
  76. package/dist/session/audio-setup.d.ts +1 -1
  77. package/dist/session/audio-state.d.ts +6 -6
  78. package/dist/session/index.js +1 -1
  79. package/dist/session/messages.d.ts +1 -9
  80. package/dist/session/preconnect.d.ts +1 -1
  81. package/dist/session/types.d.ts +4 -0
  82. package/dist/{session-CYdjRvbl.js → session-Rud8mmU-.js} +26 -15
  83. package/dist/stored-value.d.ts +2 -0
  84. package/dist/{tool-call-block-uXtMOUWN.js → tool-call-block-C8s6AhjS.js} +4 -4
  85. package/dist/tool-config-context-Bd-7xrfP.js +28 -0
  86. package/dist/types.d.ts +95 -6
  87. package/dist/{url-chips-oyy_Eiqh.js → url-chips-C_SHU6fq.js} +2 -2
  88. package/dist/use-client-runs.d.ts +2 -0
  89. package/dist/use-conversation-log.d.ts +2 -0
  90. package/dist/use-conversation.d.ts +24 -9
  91. package/dist/use-copy.d.ts +1 -0
  92. package/dist/use-dictation.d.ts +145 -0
  93. package/dist/use-download-url.d.ts +6 -1
  94. package/dist/use-flash.d.ts +1 -0
  95. package/dist/use-inbox.d.ts +2 -0
  96. package/dist/use-push-to-talk.d.ts +6 -1
  97. package/dist/use-route-mutation.d.ts +3 -0
  98. package/dist/use-route.d.ts +51 -7
  99. package/dist/use-run-key.d.ts +6 -1
  100. package/dist/use-session-controls.d.ts +1 -0
  101. package/dist/use-tap-to-talk.d.ts +5 -2
  102. package/dist/{use-user-transcript-uyHhzy4d.js → use-user-transcript-lOu_uMnf.js} +1 -1
  103. package/dist/use-user-transcript.d.ts +5 -1
  104. package/dist/use-workflow-form.d.ts +76 -167
  105. package/dist/use-workflow-progress.d.ts +5 -0
  106. package/dist/{use-workflow-run-DdhWmbx-.js → use-workflow-run-BTEemod0.js} +2 -2
  107. package/dist/use-workflow-run.d.ts +5 -0
  108. package/dist/use-workflow-runs.d.ts +18 -4
  109. package/dist/use-workflow-stream.d.ts +33 -7
  110. package/dist/use-workflows.d.ts +10 -2
  111. package/dist/workflow-client.d.ts +5 -0
  112. package/dist/workflow-def-types.d.ts +18 -2
  113. package/dist/workflow-field-kind.d.ts +66 -10
  114. package/dist/workflow-submission.d.ts +265 -0
  115. package/dist/worklets/capture-processor.d.ts +3 -3
  116. package/dist/worklets/capture-processor.js +2 -2
  117. package/dist/worklets/playback-processor.d.ts +3 -3
  118. package/dist/worklets/playback-processor.js +2 -2
  119. package/package.json +2 -2
  120. package/dist/default-client/assets/audio-DzwGxdv4.js +0 -1
  121. package/dist/default-client/assets/index-BOTLwsNO.js +0 -126
  122. package/dist/default-client/assets/index-_-x5JLLa.css +0 -2
  123. package/dist/tool-config-context-Es4YUzV2.js +0 -19
@@ -34,7 +34,7 @@
34
34
  * @module internal
35
35
  */
36
36
  export { buildAgentUrl, loadClientConfig } from "./client-config.ts";
37
- export { ToolConfigContext } from "./components/tool-config-context.ts";
37
+ export { ToolConfigContext, type ToolDisplayConfig } from "./components/tool-config-context.ts";
38
38
  export { ApiUrlChip, SessionUrlChips, UiUrlChip } from "./components/url-chips.tsx";
39
39
  export { SessionProvider, ThemeProvider } from "./context.ts";
40
40
  export { VOICE_CAPTURE_CONSTRAINTS } from "./types.ts";
package/dist/internal.js CHANGED
@@ -1,8 +1,8 @@
1
- import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-C7fQrioM.js";
2
- import { SessionProvider, ThemeProvider } from "./context.js";
3
- import { n as SessionUrlChips, r as UiUrlChip, t as ApiUrlChip } from "./url-chips-oyy_Eiqh.js";
4
- import { t as TRANSCRIBING_PLACEHOLDER } from "./use-user-transcript-uyHhzy4d.js";
5
- import { t as ToolConfigContext } from "./tool-config-context-Es4YUzV2.js";
6
- import { i as DEFAULT_PROGRESS_POLL_MS, n as MAX_MISSING_READS, t as DEFAULT_WORKFLOW_POLL_MS } from "./use-workflow-run-DdhWmbx-.js";
1
+ import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-DcMCWmkO.js";
2
+ import { n as ThemeProvider, t as SessionProvider } from "./context-BF0KsI9X.js";
3
+ import { n as SessionUrlChips, r as UiUrlChip, t as ApiUrlChip } from "./url-chips-C_SHU6fq.js";
4
+ import { t as TRANSCRIBING_PLACEHOLDER } from "./use-user-transcript-lOu_uMnf.js";
5
+ import { n as ToolConfigContext } from "./tool-config-context-Bd-7xrfP.js";
6
+ import { i as DEFAULT_PROGRESS_POLL_MS, n as MAX_MISSING_READS, t as DEFAULT_WORKFLOW_POLL_MS } from "./use-workflow-run-BTEemod0.js";
7
7
  import { VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
8
8
  export { ApiUrlChip, DEFAULT_PROGRESS_POLL_MS, DEFAULT_WORKFLOW_POLL_MS, MAX_MISSING_READS, SessionProvider, SessionUrlChips, TRANSCRIBING_PLACEHOLDER, ThemeProvider, ToolConfigContext, UiUrlChip, VOICE_CAPTURE_CONSTRAINTS, buildAgentUrl, loadClientConfig };
@@ -17,6 +17,7 @@
17
17
  /**
18
18
  * Options for {@link createLinkedClient}.
19
19
  *
20
+ * @input
20
21
  * @public
21
22
  */
22
23
  export type LinkedClientOptions = {
@@ -31,6 +32,7 @@ export type LinkedClientOptions = {
31
32
  /**
32
33
  * Which client a page is — see {@link createLinkedClient}.
33
34
  *
35
+ * @sealed
34
36
  * @public
35
37
  */
36
38
  export type LinkedClient = {
@@ -1,7 +1,7 @@
1
- import { useSessionSelector, useTheme } from "./context.js";
1
+ import { c as useSessionSelector, u as useTheme } from "./context-BF0KsI9X.js";
2
2
  import { a as inkTint, o as primaryTint } from "./_colors-CpZO-88A.js";
3
- import { n as useUserTranscript } from "./use-user-transcript-uyHhzy4d.js";
4
- import { n as ToolCallRow, t as ToolCallBlock } from "./tool-call-block-uXtMOUWN.js";
3
+ import { n as useUserTranscript } from "./use-user-transcript-lOu_uMnf.js";
4
+ import { n as ToolCallRow, t as ToolCallBlock } from "./tool-call-block-C8s6AhjS.js";
5
5
  import { memo, useCallback, useMemo } from "react";
6
6
  import clsx from "clsx";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -152,15 +152,20 @@ function isThinking(state, messages, toolCalls) {
152
152
  * const { items, streaming, transcript, thinking } = useConversation();
153
153
  * return (
154
154
  * <div>
155
- * {items.map((item) =>
156
- * item.kind === "message" ? (
157
- * <p key={item.message.id} data-role={item.message.role}>
158
- * {item.message.content}
159
- * </p>
160
- * ) : (
161
- * <code key={item.toolCall.callId}>{item.toolCall.name}</code>
162
- * ),
163
- * )}
155
+ * {items.map((item) => {
156
+ * switch (item.kind) {
157
+ * case "message":
158
+ * return (
159
+ * <p key={item.message.id} data-role={item.message.role}>
160
+ * {item.message.content}
161
+ * </p>
162
+ * );
163
+ * case "tool":
164
+ * return <code key={item.toolCall.callId}>{item.toolCall.name}</code>;
165
+ * default:
166
+ * return null;
167
+ * }
168
+ * })}
164
169
  * {streaming !== null && <p data-role="assistant">{streaming}</p>}
165
170
  * {transcript.speaking && <p data-role="user">{transcript.text}</p>}
166
171
  * {thinking && <p>…</p>}
@@ -280,7 +285,13 @@ function ConversationView({ renderMessage, renderTool = defaultTool, renderStrea
280
285
  const { items, streaming, transcript, thinking } = useConversation();
281
286
  const liveItems = log ? void 0 : items;
282
287
  const rows = useMemo(() => {
283
- const renderItem = (item, key) => item.kind === "message" ? renderKeyed(`${key}m${item.message.id}`, renderMessage(item.message)) : renderKeyed(`${key}t${item.toolCall.callId}`, renderTool(item.toolCall));
288
+ const renderItem = (item, key) => {
289
+ switch (item.kind) {
290
+ case "message": return renderKeyed(`${key}m${item.message.id}`, renderMessage(item.message));
291
+ case "tool": return renderKeyed(`${key}t${item.toolCall.callId}`, renderTool(item.toolCall));
292
+ default: return null;
293
+ }
294
+ };
284
295
  if (!log) return (liveItems ?? []).map((item) => renderItem(item, ""));
285
296
  return logRows(log, renderItem, renderMessage, renderNote, renderSessionHeader);
286
297
  }, [
package/dist/page.d.ts CHANGED
@@ -27,6 +27,7 @@ import type { ClientTheme } from "./types.ts";
27
27
  /**
28
28
  * Configuration for {@link mountPage}.
29
29
  *
30
+ * @input
30
31
  * @public
31
32
  */
32
33
  export type PageConfig = {
@@ -55,6 +56,7 @@ export type PageConfig = {
55
56
  /**
56
57
  * Handle returned by {@link mountPage}. `Disposable`, so `using` works.
57
58
  *
59
+ * @sealed
58
60
  * @public
59
61
  */
60
62
  export type PageHandle = {
package/dist/phone.d.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  /**
13
13
  * Options for {@link phoneE164}.
14
14
  *
15
+ * @input
15
16
  * @public
16
17
  */
17
18
  export type PhoneE164Options = {
@@ -29,6 +29,7 @@ export type RouteMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
29
29
  /**
30
30
  * Options for {@link routeFetch}.
31
31
  *
32
+ * @input
32
33
  * @public
33
34
  */
34
35
  export type RouteFetchOptions = {
@@ -40,6 +41,12 @@ export type RouteFetchOptions = {
40
41
  client?: string | undefined;
41
42
  /** The agent's base URL. Default: the page's own directory. */
42
43
  baseUrl?: string | undefined;
44
+ /**
45
+ * Extra request headers — an `authorization` for a route behind
46
+ * `apiKeyRoute`, say. Names are case-insensitive, so a `content-type` (or
47
+ * `Content-Type`) here beats the JSON one a body would set.
48
+ */
49
+ headers?: Readonly<Record<string, string>> | undefined;
43
50
  /** Abort the request. */
44
51
  signal?: AbortSignal | undefined;
45
52
  };
@@ -71,7 +78,7 @@ export type RouteFetchOptions = {
71
78
  * @param method - The route's method.
72
79
  * @param path - The route's path as declared, starting with `/`; `?query` allowed.
73
80
  * @param body - Sent as JSON when given.
74
- * @param options - `client`, `baseUrl` and `signal`; see {@link RouteFetchOptions}.
81
+ * @param options - `client`, `baseUrl`, `headers` and `signal`; see {@link RouteFetchOptions}.
75
82
  * @returns The parsed JSON body.
76
83
  *
77
84
  * @public
@@ -53,5 +53,5 @@ export type AudioPathCallbacks = {
53
53
  * up for a connection nobody wants any more is a live microphone, and no
54
54
  * amount of cancellation upstream releases the device.
55
55
  */
56
- export declare function openAudioPath(config: AudioPathConfig, callbacks: AudioPathCallbacks, preConnect?: Promise<PreConnectCapture | null> | null): Promise<VoiceIO>;
56
+ export declare function openAudioPath(config: AudioPathConfig, callbacks: AudioPathCallbacks, preConnect?: Promise<PreConnectCapture | null> | null, signal?: AbortSignal): Promise<VoiceIO>;
57
57
  export {};
@@ -32,14 +32,14 @@
32
32
  * anywhere; as context of `down`/`starting` they are cleared by entering
33
33
  * `down` rather than by three call sites remembering to.
34
34
  *
35
- * ## What cancellation does NOT do, and the one guard that had to survive
35
+ * ## Cancellation follows the microphone's ownership
36
36
  *
37
37
  * Stopping the `bringUp` actor hides its resolution; it does not close a
38
38
  * microphone the browser has already granted. So `bringUp` checks its own
39
- * `signal` after `openAudioPath` settles and releases what it built. That is
40
- * the single remaining "is this still wanted?" test, and it lives in the
41
- * producer — which owns the value nobody adopted — rather than at the four
42
- * consumer sites that used to re-ask.
39
+ * `signal` after `openAudioPath` settles and releases what it built. The same
40
+ * signal reaches the opening path and the transferred pre-connect capture,
41
+ * so cancellation before the lazy modules load prevents a permission request
42
+ * in the first place. Both checks live with the producer that owns the device.
43
43
  *
44
44
  * ## Effects stay in the session core
45
45
  *
@@ -63,7 +63,7 @@ export type AudioPathEffects = {
63
63
  * hand it a promise they resolve by hand, which is the only way to hold a
64
64
  * bring-up open across the events that abandon it.
65
65
  */
66
- open(config: AudioPathConfig, callbacks: AudioPathCallbacks): Promise<VoiceIO>;
66
+ open(config: AudioPathConfig, callbacks: AudioPathCallbacks, signal: AbortSignal): Promise<VoiceIO>;
67
67
  /** Relay captured microphone audio to the server. */
68
68
  sendMicAudio(pcm16: ArrayBuffer): void;
69
69
  /**
@@ -1,2 +1,2 @@
1
- import { a as ticketCarriage, i as resolveSessionTokenSync, n as CLEARED_SESSION_STATE, o as loadAudioModules, r as resolveSessionToken, t as createBrowserSession } from "../session-CYdjRvbl.js";
1
+ import { a as ticketCarriage, i as resolveSessionTokenSync, n as CLEARED_SESSION_STATE, o as loadAudioModules, r as resolveSessionToken, t as createBrowserSession } from "../session-Rud8mmU-.js";
2
2
  export { CLEARED_SESSION_STATE, createBrowserSession, loadAudioModules, resolveSessionToken, resolveSessionTokenSync, ticketCarriage };
@@ -7,15 +7,7 @@ import { type ConnState, type SessionSnapshot } from "./types.ts";
7
7
  * The empty arrays are safe to share: snapshot collections are never mutated
8
8
  * in place, only replaced.
9
9
  */
10
- export declare const CLEARED_SESSION_STATE: {
11
- messages: never[];
12
- toolCalls: never[];
13
- customEvents: never[];
14
- agentState: null;
15
- userTranscript: null;
16
- agentTranscript: null;
17
- error: null;
18
- };
10
+ export declare const CLEARED_SESSION_STATE: Readonly<Pick<SessionSnapshot, "messages" | "toolCalls" | "customEvents" | "agentState" | "userTranscript" | "agentTranscript" | "error">>;
19
11
  /** Config payload extracted from a `config` server message. */
20
12
  export type SessionConfigMessage = {
21
13
  sampleRate: number;
@@ -40,7 +40,7 @@ export type PreConnectAudio = {
40
40
  */
41
41
  begin(): void;
42
42
  /** Hand the held capture to a bring-up, or `null` when none is held. */
43
- take(): Promise<PreConnectCapture | null> | null;
43
+ take(signal?: AbortSignal): Promise<PreConnectCapture | null> | null;
44
44
  /** Release a held capture: the session is over or starting afresh. */
45
45
  release(): void;
46
46
  };
@@ -15,6 +15,7 @@ import type { AudioPath } from "./audio-state.ts";
15
15
  * Deliberately NOT the DOM `CustomEvent`: it shares nothing with that
16
16
  * interface, and the old name shadowed the global in `.tsx` files.
17
17
  *
18
+ * @sealed
18
19
  * @public
19
20
  */
20
21
  export type AgentCustomEvent = {
@@ -47,6 +48,7 @@ export type AgentStateFrame = Readonly<Record<string, unknown>>;
47
48
  * | `recording` | Is the microphone actually live right now? |
48
49
  * | `state` | What is the agent doing — see {@link AgentState}. |
49
50
  *
51
+ * @sealed
50
52
  * @public
51
53
  */
52
54
  export type SessionSnapshot = {
@@ -179,6 +181,7 @@ export declare const browserSessionBrand: unique symbol;
179
181
  * the session, so a feature one agent in many declares is not three members
180
182
  * every session carries in its autocomplete.
181
183
  *
184
+ * @sealed
182
185
  * @public
183
186
  */
184
187
  export type UserTurnControls = {
@@ -209,6 +212,7 @@ export type ToolCallOutcome = {
209
212
  /**
210
213
  * Options for {@link BrowserSession.sendText}.
211
214
  *
215
+ * @input
212
216
  * @public
213
217
  */
214
218
  export type SendTextOptions = {
@@ -1,8 +1,8 @@
1
- import { n as pageBaseUrl } from "./_utils-Mi-SCFA1.js";
2
- import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-C7fQrioM.js";
1
+ import { n as pageBaseUrl } from "./_utils-BlZBJPdj.js";
2
+ import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-DcMCWmkO.js";
3
3
  import { MIC_SEND_MAX_BUFFERED_BYTES } from "./types.js";
4
- import { SessionEventSchema, errorMessage, safeJsonParse } from "@alexkroman1/aai";
5
- import { SESSION_AUTH_PROTOCOL_PREFIX, SESSION_PROTOCOL, lenientParse } from "@alexkroman1/aai/protocol";
4
+ import { errorMessage, safeJsonParse } from "@alexkroman1/aai";
5
+ import { SESSION_AUTH_PROTOCOL_PREFIX, SESSION_PROTOCOL, SessionEventSchema, lenientParse } from "@alexkroman1/aai/protocol";
6
6
  import { CLIENT_ID_RE, MAX_CLIENT_MESSAGES, RESUME_ID_RE, WS_OPEN, createEpoch, toArgsRecord } from "@alexkroman1/aai/internal";
7
7
  import { omitUndefined } from "@alexkroman1/aai/utils";
8
8
  import { and, assign, createActor, fromPromise, not, setup, stateIn } from "xstate";
@@ -213,17 +213,19 @@ function loadAudioModules() {
213
213
  * up for a connection nobody wants any more is a live microphone, and no
214
214
  * amount of cancellation upstream releases the device.
215
215
  */
216
- async function openAudioPath(config, callbacks, preConnect) {
216
+ async function openAudioPath(config, callbacks, preConnect, signal) {
217
217
  const pre = await preConnect ?? void 0;
218
218
  try {
219
- return await openVoiceIO(config, callbacks, pre);
219
+ signal?.throwIfAborted();
220
+ return await openVoiceIO(config, callbacks, pre, signal);
220
221
  } catch (err) {
221
222
  pre?.close();
222
223
  throw err;
223
224
  }
224
225
  }
225
- async function openVoiceIO(config, callbacks, pre) {
226
+ async function openVoiceIO(config, callbacks, pre, signal) {
226
227
  const [{ createVoiceIO }, captureWorklet, playbackWorklet] = await loadAudioModules();
228
+ signal?.throwIfAborted();
227
229
  return createVoiceIO({
228
230
  preConnect: pre,
229
231
  onPreConnectAudio: callbacks.onPreConnectAudio,
@@ -297,7 +299,7 @@ function createAudioEffects(deps) {
297
299
  });
298
300
  }
299
301
  return {
300
- open: (config, callbacks) => (deps.openAudioPath ?? openAudioPath)(config, callbacks, preConnect.take()),
302
+ open: (config, callbacks, signal) => (deps.openAudioPath ?? openAudioPath)(config, callbacks, preConnect.take(signal), signal),
301
303
  sendMicAudio: (pcm16) => {
302
304
  try {
303
305
  mic.sendAudio(pcm16);
@@ -379,14 +381,14 @@ function createAudioEffects(deps) {
379
381
  * anywhere; as context of `down`/`starting` they are cleared by entering
380
382
  * `down` rather than by three call sites remembering to.
381
383
  *
382
- * ## What cancellation does NOT do, and the one guard that had to survive
384
+ * ## Cancellation follows the microphone's ownership
383
385
  *
384
386
  * Stopping the `bringUp` actor hides its resolution; it does not close a
385
387
  * microphone the browser has already granted. So `bringUp` checks its own
386
- * `signal` after `openAudioPath` settles and releases what it built. That is
387
- * the single remaining "is this still wanted?" test, and it lives in the
388
- * producer — which owns the value nobody adopted — rather than at the four
389
- * consumer sites that used to re-ask.
388
+ * `signal` after `openAudioPath` settles and releases what it built. The same
389
+ * signal reaches the opening path and the transferred pre-connect capture,
390
+ * so cancellation before the lazy modules load prevents a permission request
391
+ * in the first place. Both checks live with the producer that owns the device.
390
392
  *
391
393
  * ## Effects stay in the session core
392
394
  *
@@ -438,7 +440,7 @@ bringUp: fromPromise(async ({ input, signal }) => {
438
440
  message
439
441
  });
440
442
  }
441
- });
443
+ }, signal);
442
444
  if (signal.aborted) {
443
445
  input.effects.release(io);
444
446
  throw ABANDONED;
@@ -1651,10 +1653,19 @@ function createPreConnectAudio(enabled) {
1651
1653
  maxSeconds: 10
1652
1654
  })).catch(() => null);
1653
1655
  },
1654
- take() {
1656
+ take(signal) {
1655
1657
  const taken = held;
1658
+ const current = attempt;
1656
1659
  held = null;
1657
1660
  attempt = null;
1661
+ if (taken && current && signal) {
1662
+ const release = () => {
1663
+ current.released = true;
1664
+ };
1665
+ if (signal.aborted) release();
1666
+ else signal.addEventListener("abort", release, { once: true });
1667
+ return taken.finally(() => signal.removeEventListener("abort", release));
1668
+ }
1658
1669
  return taken;
1659
1670
  },
1660
1671
  release() {
@@ -19,6 +19,7 @@
19
19
  /**
20
20
  * Options for {@link createStoredValue}.
21
21
  *
22
+ * @input
22
23
  * @public
23
24
  */
24
25
  export type StoredValueOptions = {
@@ -30,6 +31,7 @@ export type StoredValueOptions = {
30
31
  /**
31
32
  * One remembered string — see {@link createStoredValue}.
32
33
  *
34
+ * @sealed
33
35
  * @public
34
36
  */
35
37
  export type StoredValue = {
@@ -1,8 +1,8 @@
1
- import { a as tryParseJSON, i as truncate } from "./_utils-Mi-SCFA1.js";
2
- import { useTheme } from "./context.js";
1
+ import { a as tryParseJSON, i as truncate } from "./_utils-BlZBJPdj.js";
2
+ import { u as useTheme } from "./context-BF0KsI9X.js";
3
3
  import { a as inkTint } from "./_colors-CpZO-88A.js";
4
- import { t as Eyebrow } from "./eyebrow-UfmSz9yy.js";
5
- import { n as useToolConfig } from "./tool-config-context-Es4YUzV2.js";
4
+ import { t as Eyebrow } from "./eyebrow-Bp4y1rZ9.js";
5
+ import { r as useToolConfig } from "./tool-config-context-Bd-7xrfP.js";
6
6
  import { memo, useMemo, useState } from "react";
7
7
  import clsx from "clsx";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -0,0 +1,28 @@
1
+ import { createContext, useContext } from "react";
2
+ //#region src/components/tool-config-context.ts
3
+ /**
4
+ * The value the context holds before the lookup lands (and forever, when the
5
+ * lookup fails): one frozen object, so the provider's value is stable across
6
+ * renders rather than a fresh `{}` per commit.
7
+ *
8
+ * @internal
9
+ */
10
+ const EMPTY_TOOL_DISPLAY = Object.freeze({});
11
+ /**
12
+ * Context for tool display configuration. Installed by `mountClient()` from
13
+ * the agent's `GET /client-config` `tools`; the built-in components read it
14
+ * via `useToolConfig`.
15
+ *
16
+ * @internal
17
+ */
18
+ const ToolConfigContext = createContext(EMPTY_TOOL_DISPLAY);
19
+ /**
20
+ * Read tool display configuration from the nearest `ToolConfigContext.Provider`.
21
+ *
22
+ * @internal
23
+ */
24
+ function useToolConfig() {
25
+ return useContext(ToolConfigContext);
26
+ }
27
+ //#endregion
28
+ export { ToolConfigContext as n, useToolConfig as r, EMPTY_TOOL_DISPLAY as t };
package/dist/types.d.ts CHANGED
@@ -56,9 +56,13 @@ export declare const VOICE_CAPTURE_CONSTRAINTS: MediaTrackConstraints;
56
56
  * until the next completed handshake, so a later frame cannot quietly paint
57
57
  * over the banner explaining a dead call.
58
58
  *
59
+ * The set is OPEN (`| (string & {})`): a later release may add a state, so a
60
+ * `switch` over it needs a `default`, and a lookup keyed by it needs a
61
+ * fallback for a state it has no entry for.
62
+ *
59
63
  * @public
60
64
  */
61
- export type AgentState = "disconnected" | "connecting" | "ready" | "listening" | "thinking" | "speaking" | "error";
65
+ export type AgentState = "disconnected" | "connecting" | "ready" | "listening" | "thinking" | "speaking" | "error" | (string & {});
62
66
  /**
63
67
  * A chat message exchanged between user and assistant.
64
68
  *
@@ -67,6 +71,7 @@ export type AgentState = "disconnected" | "connecting" | "ready" | "listening" |
67
71
  * surfaced via `SessionSnapshot.toolCalls` (or `useEvent` for `ctx.send`
68
72
  * events).
69
73
  *
74
+ * @sealed
70
75
  * @public
71
76
  */
72
77
  export type ChatMessage = {
@@ -76,14 +81,19 @@ export type ChatMessage = {
76
81
  * render key.
77
82
  */
78
83
  id: number;
79
- /** The sender of the message. */
80
- role: "user" | "assistant";
84
+ /**
85
+ * The sender of the message. Open (`| (string & {})`) so a later sender is
86
+ * not a break: render anything that is not `"user"` the way you render
87
+ * `"assistant"`, or skip it.
88
+ */
89
+ role: "user" | "assistant" | (string & {});
81
90
  /** The text content of the message. */
82
91
  content: string;
83
92
  };
84
93
  /**
85
94
  * Info about a tool call for display in the UI.
86
95
  *
96
+ * @sealed
87
97
  * @public
88
98
  */
89
99
  export type ToolCallInfo = {
@@ -105,7 +115,13 @@ export type ToolCallInfo = {
105
115
  * now actually compiles.
106
116
  */
107
117
  args: Record<string, DefaultToolResult>;
108
- status: "pending" | "done";
118
+ /**
119
+ * `"pending"` until the tool returns, then `"done"`. A tool that FAILED is
120
+ * `"done"` too: the wire reports a failure as a completion whose `result` is
121
+ * the error text the model read. Open (`| (string & {})`) so a later status
122
+ * is not a break — treat anything but `"pending"` as settled.
123
+ */
124
+ status: "pending" | "done" | (string & {});
109
125
  result?: string | undefined;
110
126
  /**
111
127
  * Monotonically increasing, session-unique insertion sequence number.
@@ -128,6 +144,7 @@ export type { SessionErrorCode } from "@alexkroman1/aai/protocol";
128
144
  /**
129
145
  * Error reported by the voice session.
130
146
  *
147
+ * @sealed
131
148
  * @public
132
149
  */
133
150
  export type SessionError = {
@@ -157,6 +174,7 @@ export type SessionError = {
157
174
  * defaults `platformUrl` from `location.href`, while `createBrowserSession`
158
175
  * requires it.
159
176
  *
177
+ * @input
160
178
  * @public
161
179
  */
162
180
  export type VoiceSessionOptions = {
@@ -314,6 +332,7 @@ export type VoiceSessionOptions = {
314
332
  /**
315
333
  * Minimal WebSocket constructor type accepted by {@link VoiceSessionOptions}.
316
334
  *
335
+ * @input
317
336
  * @public
318
337
  */
319
338
  export type WebSocketConstructor = {
@@ -321,11 +340,13 @@ export type WebSocketConstructor = {
321
340
  readonly OPEN: number;
322
341
  };
323
342
  /**
324
- * Theme color overrides for the AAI UI components.
343
+ * The five theme tokens, each optional — the long form of {@link ClientTheme},
344
+ * for a page that names its own ground and surfaces.
325
345
  *
346
+ * @input
326
347
  * @public
327
348
  */
328
- export type ClientTheme = {
349
+ export type ClientThemeTokens = {
329
350
  /** Background color, also painted on `html`/`body`. Default: `#FAFAFA`. */
330
351
  bg?: string;
331
352
  /** Primary accent color. Default: `#18181B`. */
@@ -337,3 +358,71 @@ export type ClientTheme = {
337
358
  /** Border color. Default: `#E4E4E7`. */
338
359
  border?: string;
339
360
  };
361
+ /**
362
+ * The short form of {@link ClientTheme}: one accent and a light or dark
363
+ * ground, from which `bg`, `surface`, `text` and `border` are DERIVED.
364
+ *
365
+ * Most pages that themed at all set `primary` and nothing else — and the few
366
+ * that wanted a dark page had to look up four neutrals that go together.
367
+ * `{ primary: "#e55", mode: "dark" }` is that lookup done once
368
+ * (`resolveClientTheme`, `_theme.ts`); `mode` defaults to `"light"`, which is
369
+ * the default palette, so `{ primary }` means the same thing in both forms.
370
+ *
371
+ * @input
372
+ * @public
373
+ */
374
+ export type ClientThemePreset = {
375
+ /** Primary accent color. */
376
+ primary: string;
377
+ /** The ground the neutrals are derived for. Default: `"light"`. */
378
+ mode?: "dark" | "light";
379
+ };
380
+ /**
381
+ * Theme color overrides for the AAI UI components: the five tokens
382
+ * ({@link ClientThemeTokens}), or an accent plus a light/dark mode
383
+ * ({@link ClientThemePreset}). Either resolves to a {@link ResolvedClientTheme}.
384
+ *
385
+ * The two forms are EXCLUSIVE: a theme naming `mode` may name no token but
386
+ * `primary`, and a theme naming a token may not name `mode`. Without the
387
+ * guards a mixed `{ primary, mode: "dark", bg }` compiled (an object literal is
388
+ * checked against the union as a whole, so a key from either member passed)
389
+ * and `bg` was silently dropped — the preset's derived neutrals won. The guard
390
+ * is `?: undefined` rather than `?: never` so an options bag that spells a hole
391
+ * as `undefined` still fits. `{ primary }` alone is both forms and resolves
392
+ * alike.
393
+ *
394
+ * @input
395
+ * @public
396
+ */
397
+ export type ClientTheme = (ClientThemeTokens & {
398
+ mode?: undefined;
399
+ }) | (ClientThemePreset & {
400
+ bg?: undefined;
401
+ text?: undefined;
402
+ surface?: undefined;
403
+ border?: undefined;
404
+ });
405
+ /**
406
+ * The theme a component reads back from `useTheme()`: every
407
+ * {@link ClientTheme} field, filled with its default where the client set
408
+ * none.
409
+ *
410
+ * Its own type rather than `Required<ClientTheme>`, because the two travel in
411
+ * opposite directions — an author BUILDS a `ClientTheme` and only READS this —
412
+ * so a token added later can be optional on one and present on the other.
413
+ *
414
+ * @sealed
415
+ * @public
416
+ */
417
+ export type ResolvedClientTheme = {
418
+ /** Background color, also painted on `html`/`body`. */
419
+ readonly bg: string;
420
+ /** Primary accent color. */
421
+ readonly primary: string;
422
+ /** Main text color. */
423
+ readonly text: string;
424
+ /** Surface/card color. */
425
+ readonly surface: string;
426
+ /** Border color. */
427
+ readonly border: string;
428
+ };
@@ -1,5 +1,5 @@
1
- import { n as pageBaseUrl } from "./_utils-Mi-SCFA1.js";
2
- import { useSessionSelector, useTheme } from "./context.js";
1
+ import { n as pageBaseUrl } from "./_utils-BlZBJPdj.js";
2
+ import { c as useSessionSelector, u as useTheme } from "./context-BF0KsI9X.js";
3
3
  import { a as inkTint, i as focusRingStyle, n as FOCUS_RING } from "./_colors-CpZO-88A.js";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import clsx from "clsx";
@@ -15,6 +15,7 @@ import type { ClientRun } from "@alexkroman1/aai";
15
15
  /**
16
16
  * Options for {@link useClientRuns}.
17
17
  *
18
+ * @input
18
19
  * @public
19
20
  */
20
21
  export type UseClientRunsOptions = {
@@ -29,6 +30,7 @@ export type UseClientRunsOptions = {
29
30
  /**
30
31
  * What {@link useClientRuns} returns.
31
32
  *
33
+ * @sealed
32
34
  * @public
33
35
  */
34
36
  export type UseClientRunsResult = {
@@ -23,6 +23,7 @@ import type { InboxEvent } from "./inbox-protocol.ts";
23
23
  /**
24
24
  * Options for {@link useConversationLog}.
25
25
  *
26
+ * @input
26
27
  * @public
27
28
  */
28
29
  export type UseConversationLogOptions = {
@@ -37,6 +38,7 @@ export type UseConversationLogOptions = {
37
38
  /**
38
39
  * What {@link useConversationLog} returns.
39
40
  *
41
+ * @sealed
40
42
  * @public
41
43
  */
42
44
  export type UseConversationLogResult = {