@alexkroman1/aai-ui 0.16.0 → 0.17.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/dist/agent-state-labels.d.ts +8 -3
- package/dist/{chat-view-DIwPoAEG.js → chat-view-W6KcdjJY.js} +40 -30
- package/dist/{client-config-C7fQrioM.js → client-config-CzvWmw4X.js} +7 -12
- package/dist/client-config.d.ts +29 -10
- package/dist/components/_form-readiness.d.ts +2 -1
- package/dist/components/audio-result.d.ts +2 -0
- package/dist/components/bullet-list.d.ts +1 -0
- package/dist/components/button.d.ts +2 -2
- package/dist/components/chat-view.js +1 -1
- package/dist/components/console-shell.d.ts +8 -5
- package/dist/components/controls.d.ts +1 -0
- package/dist/components/conversation-view.d.ts +1 -0
- package/dist/components/facts.d.ts +1 -0
- package/dist/components/form-fields.d.ts +8 -8
- package/dist/components/form-types.d.ts +16 -2
- package/dist/components/form.d.ts +9 -4
- package/dist/components/markdown.d.ts +1 -0
- package/dist/components/message-list.d.ts +1 -0
- package/dist/components/message-list.js +1 -1
- package/dist/components/session-controls.d.ts +7 -2
- package/dist/components/session-error-banner.d.ts +1 -0
- package/dist/components/session-state-dot.d.ts +20 -7
- package/dist/components/sidebar-layout.d.ts +2 -2
- package/dist/components/tool-call-block.d.ts +3 -3
- package/dist/components/tool-call-row.d.ts +1 -0
- package/dist/components/tool-config-context.d.ts +2 -1
- package/dist/components/url-chips.d.ts +4 -3
- package/dist/components/workflow-fields.d.ts +2 -1
- package/dist/components/workflow-pending-note.d.ts +1 -0
- package/dist/components/workflow-run-error.d.ts +1 -0
- package/dist/components/workflow-run-panel.d.ts +1 -0
- package/dist/context.d.ts +8 -7
- package/dist/context.js +2 -2
- package/dist/default-client/assets/{audio-DzwGxdv4.js → audio-Ct7JpSNT.js} +1 -1
- package/dist/default-client/assets/{capture-processor-CXCWxCO8.js → capture-processor-Cx1AdMHL.js} +1 -1
- package/dist/default-client/assets/index-BlUYCTB5.js +126 -0
- package/dist/default-client/assets/{playback-processor-DXHfyQD9.js → playback-processor-CCwTknSu.js} +1 -1
- package/dist/default-client/index.html +1 -1
- package/dist/define-client.d.ts +2 -0
- package/dist/define-client.js +4 -4
- package/dist/inbox-protocol.d.ts +14 -4
- package/dist/inbox.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +61 -19
- package/dist/internal.js +1 -1
- package/dist/linked-client.d.ts +2 -0
- package/dist/{message-list-DHZcLhRA.js → message-list-BsaBkBgo.js} +21 -10
- package/dist/page.d.ts +2 -0
- package/dist/phone.d.ts +1 -0
- package/dist/route-fetch.d.ts +1 -0
- package/dist/session/audio-setup.d.ts +1 -1
- package/dist/session/audio-state.d.ts +6 -6
- package/dist/session/index.js +1 -1
- package/dist/session/messages.d.ts +1 -9
- package/dist/session/preconnect.d.ts +1 -1
- package/dist/session/types.d.ts +4 -0
- package/dist/{session-CYdjRvbl.js → session-3lgPYNNO.js} +25 -14
- package/dist/stored-value.d.ts +2 -0
- package/dist/types.d.ts +48 -4
- package/dist/use-client-runs.d.ts +2 -0
- package/dist/use-conversation-log.d.ts +2 -0
- package/dist/use-conversation.d.ts +24 -9
- package/dist/use-copy.d.ts +1 -0
- package/dist/use-download-url.d.ts +6 -1
- package/dist/use-flash.d.ts +1 -0
- package/dist/use-inbox.d.ts +2 -0
- package/dist/use-push-to-talk.d.ts +6 -1
- package/dist/use-route-mutation.d.ts +3 -0
- package/dist/use-route.d.ts +2 -0
- package/dist/use-session-controls.d.ts +1 -0
- package/dist/use-tap-to-talk.d.ts +5 -2
- package/dist/use-user-transcript.d.ts +5 -1
- package/dist/use-workflow-form.d.ts +8 -1
- package/dist/use-workflow-progress.d.ts +5 -0
- package/dist/use-workflow-run.d.ts +5 -0
- package/dist/use-workflow-runs.d.ts +10 -2
- package/dist/use-workflow-stream.d.ts +25 -3
- package/dist/use-workflows.d.ts +10 -2
- package/dist/workflow-client.d.ts +5 -0
- package/dist/workflow-field-kind.d.ts +8 -4
- package/dist/worklets/capture-processor.d.ts +3 -3
- package/dist/worklets/capture-processor.js +2 -2
- package/dist/worklets/playback-processor.d.ts +3 -3
- package/dist/worklets/playback-processor.js +2 -2
- package/package.json +2 -2
- package/dist/default-client/assets/index-BOTLwsNO.js +0 -126
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
|
-
/**
|
|
80
|
-
|
|
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
|
-
|
|
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 = {
|
|
@@ -323,6 +342,7 @@ export type WebSocketConstructor = {
|
|
|
323
342
|
/**
|
|
324
343
|
* Theme color overrides for the AAI UI components.
|
|
325
344
|
*
|
|
345
|
+
* @input
|
|
326
346
|
* @public
|
|
327
347
|
*/
|
|
328
348
|
export type ClientTheme = {
|
|
@@ -337,3 +357,27 @@ export type ClientTheme = {
|
|
|
337
357
|
/** Border color. Default: `#E4E4E7`. */
|
|
338
358
|
border?: string;
|
|
339
359
|
};
|
|
360
|
+
/**
|
|
361
|
+
* The theme a component reads back from `useTheme()`: every
|
|
362
|
+
* {@link ClientTheme} field, filled with its default where the client set
|
|
363
|
+
* none.
|
|
364
|
+
*
|
|
365
|
+
* Its own type rather than `Required<ClientTheme>`, because the two travel in
|
|
366
|
+
* opposite directions — an author BUILDS a `ClientTheme` and only READS this —
|
|
367
|
+
* so a token added later can be optional on one and present on the other.
|
|
368
|
+
*
|
|
369
|
+
* @sealed
|
|
370
|
+
* @public
|
|
371
|
+
*/
|
|
372
|
+
export type ResolvedClientTheme = {
|
|
373
|
+
/** Background color, also painted on `html`/`body`. */
|
|
374
|
+
readonly bg: string;
|
|
375
|
+
/** Primary accent color. */
|
|
376
|
+
readonly primary: string;
|
|
377
|
+
/** Main text color. */
|
|
378
|
+
readonly text: string;
|
|
379
|
+
/** Surface/card color. */
|
|
380
|
+
readonly surface: string;
|
|
381
|
+
/** Border color. */
|
|
382
|
+
readonly border: string;
|
|
383
|
+
};
|
|
@@ -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 = {
|
|
@@ -33,6 +33,12 @@ import { type UseUserTranscriptResult } from "./use-user-transcript.ts";
|
|
|
33
33
|
* is the thing this hook computes — handing back two lists would hand back the
|
|
34
34
|
* problem. `kind` is what a `switch` in a custom renderer narrows on.
|
|
35
35
|
*
|
|
36
|
+
* `"other"` is the row this version cannot describe — today, an item a newer
|
|
37
|
+
* page wrote into a stored conversation log (`type` is the `kind` it was
|
|
38
|
+
* stored under). It is a member from the start so that a row kind added
|
|
39
|
+
* later is a value an existing `switch` already has a branch for, not a
|
|
40
|
+
* compile error: render nothing for it, or a placeholder.
|
|
41
|
+
*
|
|
36
42
|
* @public
|
|
37
43
|
*/
|
|
38
44
|
export type ConversationItem = {
|
|
@@ -41,10 +47,14 @@ export type ConversationItem = {
|
|
|
41
47
|
} | {
|
|
42
48
|
kind: "tool";
|
|
43
49
|
toolCall: ToolCallInfo;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "other";
|
|
52
|
+
type: string;
|
|
44
53
|
};
|
|
45
54
|
/**
|
|
46
55
|
* What {@link useConversation} returns.
|
|
47
56
|
*
|
|
57
|
+
* @sealed
|
|
48
58
|
* @public
|
|
49
59
|
*/
|
|
50
60
|
export type UseConversationResult = {
|
|
@@ -107,15 +117,20 @@ export declare function interleave(messages: readonly ChatMessage[], toolCalls:
|
|
|
107
117
|
* const { items, streaming, transcript, thinking } = useConversation();
|
|
108
118
|
* return (
|
|
109
119
|
* <div>
|
|
110
|
-
* {items.map((item) =>
|
|
111
|
-
* item.kind
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
120
|
+
* {items.map((item) => {
|
|
121
|
+
* switch (item.kind) {
|
|
122
|
+
* case "message":
|
|
123
|
+
* return (
|
|
124
|
+
* <p key={item.message.id} data-role={item.message.role}>
|
|
125
|
+
* {item.message.content}
|
|
126
|
+
* </p>
|
|
127
|
+
* );
|
|
128
|
+
* case "tool":
|
|
129
|
+
* return <code key={item.toolCall.callId}>{item.toolCall.name}</code>;
|
|
130
|
+
* default:
|
|
131
|
+
* return null;
|
|
132
|
+
* }
|
|
133
|
+
* })}
|
|
119
134
|
* {streaming !== null && <p data-role="assistant">{streaming}</p>}
|
|
120
135
|
* {transcript.speaking && <p data-role="user">{transcript.text}</p>}
|
|
121
136
|
* {thinking && <p>…</p>}
|
package/dist/use-copy.d.ts
CHANGED
|
@@ -23,7 +23,11 @@
|
|
|
23
23
|
* "downloading" as the same thing — so this reports it.
|
|
24
24
|
*/
|
|
25
25
|
import type { WorkflowApi } from "./workflow-client.ts";
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Options for {@link useDownloadUrl}.
|
|
28
|
+
*
|
|
29
|
+
* @input
|
|
30
|
+
*/
|
|
27
31
|
export type UseDownloadUrlOptions = {
|
|
28
32
|
/** The client to read the bytes with. Defaults to one for the page's own agent. */
|
|
29
33
|
api?: WorkflowApi;
|
|
@@ -31,6 +35,7 @@ export type UseDownloadUrlOptions = {
|
|
|
31
35
|
/**
|
|
32
36
|
* What {@link useDownloadUrl} reports.
|
|
33
37
|
*
|
|
38
|
+
* @sealed
|
|
34
39
|
* @public
|
|
35
40
|
*/
|
|
36
41
|
export type UseDownloadUrlResult = {
|
package/dist/use-flash.d.ts
CHANGED
package/dist/use-inbox.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ import type { InboxEvent, InboxNotice } from "./inbox-protocol.ts";
|
|
|
33
33
|
/**
|
|
34
34
|
* Options for {@link useInbox}.
|
|
35
35
|
*
|
|
36
|
+
* @input
|
|
36
37
|
* @public
|
|
37
38
|
*/
|
|
38
39
|
export type UseInboxOptions = {
|
|
@@ -63,6 +64,7 @@ export type UseInboxOptions = {
|
|
|
63
64
|
/**
|
|
64
65
|
* What {@link useInbox} returns.
|
|
65
66
|
*
|
|
67
|
+
* @sealed
|
|
66
68
|
* @public
|
|
67
69
|
*/
|
|
68
70
|
export type UseInboxResult = {
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* And one that is a nuisance rather than a stuck turn: the hold key is ignored
|
|
22
22
|
* while focus is in a text field, where Space is a character.
|
|
23
23
|
*/
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Options for {@link usePushToTalk}.
|
|
26
|
+
*
|
|
27
|
+
* @input
|
|
28
|
+
*/
|
|
25
29
|
export type UsePushToTalkOptions = {
|
|
26
30
|
/**
|
|
27
31
|
* The keyboard key that holds the turn open, as a `KeyboardEvent.code` —
|
|
@@ -34,6 +38,7 @@ export type UsePushToTalkOptions = {
|
|
|
34
38
|
/**
|
|
35
39
|
* What {@link usePushToTalk} returns.
|
|
36
40
|
*
|
|
41
|
+
* @sealed
|
|
37
42
|
* @public
|
|
38
43
|
*/
|
|
39
44
|
export type UsePushToTalkResult = {
|
|
@@ -26,6 +26,7 @@ import { type RouteMethod } from "./route-fetch.ts";
|
|
|
26
26
|
/**
|
|
27
27
|
* Options for {@link useRouteMutation}.
|
|
28
28
|
*
|
|
29
|
+
* @input
|
|
29
30
|
* @public
|
|
30
31
|
*/
|
|
31
32
|
export type UseRouteMutationOptions = {
|
|
@@ -45,6 +46,7 @@ export type UseRouteMutationOptions = {
|
|
|
45
46
|
/**
|
|
46
47
|
* Options for one {@link UseRouteMutationResult.run} call.
|
|
47
48
|
*
|
|
49
|
+
* @input
|
|
48
50
|
* @public
|
|
49
51
|
*/
|
|
50
52
|
export type RouteMutationRunOptions = {
|
|
@@ -58,6 +60,7 @@ export type RouteMutationRunOptions = {
|
|
|
58
60
|
/**
|
|
59
61
|
* What {@link useRouteMutation} returns.
|
|
60
62
|
*
|
|
63
|
+
* @sealed
|
|
61
64
|
* @public
|
|
62
65
|
*/
|
|
63
66
|
export type UseRouteMutationResult = {
|
package/dist/use-route.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
/**
|
|
14
14
|
* Options for {@link useRoute}.
|
|
15
15
|
*
|
|
16
|
+
* @input
|
|
16
17
|
* @public
|
|
17
18
|
*/
|
|
18
19
|
export type UseRouteOptions = {
|
|
@@ -28,6 +29,7 @@ export type UseRouteOptions = {
|
|
|
28
29
|
/**
|
|
29
30
|
* What {@link useRoute} returns.
|
|
30
31
|
*
|
|
32
|
+
* @sealed
|
|
31
33
|
* @public
|
|
32
34
|
*/
|
|
33
35
|
export type UseRouteResult<T> = {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
/**
|
|
32
32
|
* Options for {@link useTapToTalk}.
|
|
33
33
|
*
|
|
34
|
+
* @input
|
|
34
35
|
* @public
|
|
35
36
|
*/
|
|
36
37
|
export type UseTapToTalkOptions = {
|
|
@@ -56,14 +57,16 @@ export type UseTapToTalkOptions = {
|
|
|
56
57
|
/**
|
|
57
58
|
* What {@link useTapToTalk} returns.
|
|
58
59
|
*
|
|
60
|
+
* @sealed
|
|
59
61
|
* @public
|
|
60
62
|
*/
|
|
61
63
|
export type UseTapToTalkResult = {
|
|
62
64
|
/**
|
|
63
65
|
* The call: `"idle"` (not meant to be running), `"connecting"`, or
|
|
64
|
-
* `"active"` — up, whatever the agent is doing.
|
|
66
|
+
* `"active"` — up, whatever the agent is doing. Open (`| (string & {})`) so
|
|
67
|
+
* a phase added later is not a break.
|
|
65
68
|
*/
|
|
66
|
-
phase: "idle" | "connecting" | "active";
|
|
69
|
+
phase: "idle" | "connecting" | "active" | (string & {});
|
|
67
70
|
/** Whether the caller is live — mic open, in a realtime conversation. The button is ON. */
|
|
68
71
|
live: boolean;
|
|
69
72
|
/**
|
|
@@ -33,7 +33,11 @@
|
|
|
33
33
|
* @internal
|
|
34
34
|
*/
|
|
35
35
|
export declare const TRANSCRIBING_PLACEHOLDER = "\u2026";
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* What {@link useUserTranscript} returns.
|
|
38
|
+
*
|
|
39
|
+
* @sealed
|
|
40
|
+
*/
|
|
37
41
|
export interface UseUserTranscriptResult {
|
|
38
42
|
/**
|
|
39
43
|
* True while the caller holds the turn — from speech detection to the final
|
|
@@ -42,6 +42,7 @@ import type { SubmitInputOf } from "./workflow-def-types.ts";
|
|
|
42
42
|
* because a form is allowed more than one and a bar over "the upload" would
|
|
43
43
|
* restart at zero partway through with nothing to say why.
|
|
44
44
|
*
|
|
45
|
+
* @sealed
|
|
45
46
|
* @public
|
|
46
47
|
*/
|
|
47
48
|
export type UploadStatus = UploadProgress & {
|
|
@@ -72,6 +73,8 @@ export type UploadStatus = UploadProgress & {
|
|
|
72
73
|
* the run is accepted, and `run` is non-`undefined` from before the bytes are
|
|
73
74
|
* in, so a page can render `<WorkflowProgress>` beside the upload bar instead
|
|
74
75
|
* of after it. Here the run does not exist until the last byte lands.
|
|
76
|
+
*
|
|
77
|
+
* @sealed
|
|
75
78
|
*/
|
|
76
79
|
export type WorkflowSubmission<R = unknown, I = unknown> = {
|
|
77
80
|
/**
|
|
@@ -190,7 +193,11 @@ export type WorkflowSubmission<R = unknown, I = unknown> = {
|
|
|
190
193
|
/** The submit's own failure (a rejected input), or the watch's. */
|
|
191
194
|
error: string | undefined;
|
|
192
195
|
};
|
|
193
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Options for {@link useWorkflowSubmit}.
|
|
198
|
+
*
|
|
199
|
+
* @input
|
|
200
|
+
*/
|
|
194
201
|
export type UseWorkflowSubmitOptions = {
|
|
195
202
|
/** The client to start runs with. Defaults to one for the page's own agent. */
|
|
196
203
|
api?: WorkflowApi;
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
import type { WorkflowApi } from "./workflow-client.ts";
|
|
54
54
|
/** The slice of the client this needs: one method. */
|
|
55
55
|
export type RunProgressReader = Pick<WorkflowApi, "streamOutput">;
|
|
56
|
+
/**
|
|
57
|
+
* What {@link useWorkflowProgress} reports.
|
|
58
|
+
*
|
|
59
|
+
* @public @sealed
|
|
60
|
+
*/
|
|
56
61
|
export type UseWorkflowProgressResult<T = string> = {
|
|
57
62
|
/** Every chunk the run has written, oldest first. */
|
|
58
63
|
progress: T[];
|
|
@@ -23,6 +23,11 @@ export declare const DEFAULT_WORKFLOW_POLL_MS = 2000;
|
|
|
23
23
|
* tab is open.
|
|
24
24
|
*/
|
|
25
25
|
export declare const MAX_MISSING_READS = 3;
|
|
26
|
+
/**
|
|
27
|
+
* What {@link useWorkflowRun} reports.
|
|
28
|
+
*
|
|
29
|
+
* @public @sealed
|
|
30
|
+
*/
|
|
26
31
|
export type UseWorkflowRunResult<R = unknown> = {
|
|
27
32
|
/** Latest snapshot, or undefined before the first read lands. */
|
|
28
33
|
run: WorkflowRun<R> | undefined;
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* stale.
|
|
22
22
|
*/
|
|
23
23
|
import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Options for {@link useWorkflowRuns}.
|
|
26
|
+
*
|
|
27
|
+
* @input
|
|
28
|
+
*/
|
|
25
29
|
export type UseWorkflowRunsOptions = {
|
|
26
30
|
/** The client to read with. Defaults to one for the page's own agent. */
|
|
27
31
|
api?: WorkflowApi;
|
|
@@ -39,7 +43,11 @@ export type UseWorkflowRunsOptions = {
|
|
|
39
43
|
/** Skip the read entirely — for a page that does not know its workflow yet. */
|
|
40
44
|
skip?: boolean;
|
|
41
45
|
};
|
|
42
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* What {@link useWorkflowRuns} reports.
|
|
48
|
+
*
|
|
49
|
+
* @sealed
|
|
50
|
+
*/
|
|
43
51
|
export type UseWorkflowRunsResult<R = unknown> = {
|
|
44
52
|
/** The runs, newest first. Empty until the first read lands. */
|
|
45
53
|
runs: WorkflowRun<R>[];
|
|
@@ -76,8 +76,9 @@
|
|
|
76
76
|
* `stream.ts`'s idle bound (five minutes of no new bytes) is what decides that a
|
|
77
77
|
* pause has become an abandonment.
|
|
78
78
|
*/
|
|
79
|
-
import type { AnyWorkflowDef, WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
80
|
-
import type {
|
|
79
|
+
import type { AnyWorkflowDef, UploadParallelOption, WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
80
|
+
import type { WorkflowSubmission } from "./use-workflow-form.ts";
|
|
81
|
+
import type { WorkflowApi } from "./workflow-client.ts";
|
|
81
82
|
import type { SubmitInputOf } from "./workflow-def-types.ts";
|
|
82
83
|
/**
|
|
83
84
|
* Options for {@link useWorkflowStream}.
|
|
@@ -103,8 +104,29 @@ import type { SubmitInputOf } from "./workflow-def-types.ts";
|
|
|
103
104
|
* the file is readable — that number is the CONTIGUOUS prefix, so a run reading
|
|
104
105
|
* ahead of the uplink sees the same growing file whether one connection or four
|
|
105
106
|
* are filling it. What changes is only how fast it grows.
|
|
107
|
+
*
|
|
108
|
+
* Declared on its own rather than `Omit`ted from the submit options, so an
|
|
109
|
+
* option added to one hook is a decision about the other, not a side effect.
|
|
110
|
+
*
|
|
111
|
+
* @input
|
|
106
112
|
*/
|
|
107
|
-
export type UseWorkflowStreamOptions =
|
|
113
|
+
export type UseWorkflowStreamOptions = {
|
|
114
|
+
/** The client to start runs with. Defaults to one for the page's own agent. */
|
|
115
|
+
api?: WorkflowApi;
|
|
116
|
+
/**
|
|
117
|
+
* Correlation key recorded with the run, for finding it again without the
|
|
118
|
+
* id. Not defaulted here — see above.
|
|
119
|
+
*/
|
|
120
|
+
key?: string;
|
|
121
|
+
/** How often the fallback poll re-reads a live run. */
|
|
122
|
+
intervalMs?: number;
|
|
123
|
+
/**
|
|
124
|
+
* Send the file as concurrent parts instead of in one request — on by
|
|
125
|
+
* default, `false` opts out, `{ partBytes, concurrency }` tunes it. See
|
|
126
|
+
* `UploadOptions.parallel`.
|
|
127
|
+
*/
|
|
128
|
+
parallel?: UploadParallelOption;
|
|
129
|
+
};
|
|
108
130
|
/**
|
|
109
131
|
* What {@link useWorkflowStream} returns: a {@link WorkflowSubmission}, exactly.
|
|
110
132
|
*
|
package/dist/use-workflows.d.ts
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { WorkflowSummary } from "@alexkroman1/aai/workflow-api";
|
|
11
11
|
import type { WorkflowApi } from "./workflow-client.ts";
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Options for {@link useWorkflows}.
|
|
14
|
+
*
|
|
15
|
+
* @input
|
|
16
|
+
*/
|
|
13
17
|
export type UseWorkflowsOptions = {
|
|
14
18
|
/** The client to read the listing with. Defaults to one for the page's own agent. */
|
|
15
19
|
api?: WorkflowApi;
|
|
@@ -23,7 +27,11 @@ export type UseWorkflowsOptions = {
|
|
|
23
27
|
*/
|
|
24
28
|
skip?: boolean;
|
|
25
29
|
};
|
|
26
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* What {@link useWorkflows} reports.
|
|
32
|
+
*
|
|
33
|
+
* @sealed
|
|
34
|
+
*/
|
|
27
35
|
export type UseWorkflowsResult = {
|
|
28
36
|
/** The agent's declared workflows, each with the JSON Schema of its input. */
|
|
29
37
|
workflows: WorkflowSummary[];
|
|
@@ -68,6 +68,11 @@ export type { AgentClient, WorkflowApi, WorkflowInputOf, WorkflowOutputOf, Workf
|
|
|
68
68
|
* status predicate beside the status union cannot have.
|
|
69
69
|
*/
|
|
70
70
|
export { isTerminal } from "@alexkroman1/aai/workflow-api";
|
|
71
|
+
/**
|
|
72
|
+
* Options for {@link createWorkflowApi}.
|
|
73
|
+
*
|
|
74
|
+
* @public @input
|
|
75
|
+
*/
|
|
71
76
|
export type WorkflowApiOptions = {
|
|
72
77
|
/**
|
|
73
78
|
* Base URL of the agent. Defaults to the page's own origin + path, which is
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
* studio's SDK-subpath list: mirroring "would just move the drift somewhere
|
|
19
19
|
* new".
|
|
20
20
|
*
|
|
21
|
-
* So a NEW control added to `<WorkflowFields>` is a new
|
|
22
|
-
* {@link WorkflowFieldKind}
|
|
23
|
-
*
|
|
21
|
+
* So a NEW control added to `<WorkflowFields>` is a new value of
|
|
22
|
+
* {@link WorkflowFieldKind}. The set is open, so that addition is not a break:
|
|
23
|
+
* a reader's `switch` keeps a `default` and treats a kind it does not know the
|
|
24
|
+
* way it treats `"none"`.
|
|
24
25
|
*/
|
|
25
26
|
/**
|
|
26
27
|
* The control a schema property renders as inside `<WorkflowFields>`.
|
|
@@ -37,9 +38,12 @@
|
|
|
37
38
|
* text field is — the same value over the wire — so it is a hand-written swap
|
|
38
39
|
* rather than a shape the schema can ask for.
|
|
39
40
|
*
|
|
41
|
+
* Open (`| (string & {})`): a control added later is a new value, so read an
|
|
42
|
+
* unknown kind as `"none"`.
|
|
43
|
+
*
|
|
40
44
|
* @public
|
|
41
45
|
*/
|
|
42
|
-
export type WorkflowFieldKind = "text" | "number" | "select" | "checkbox" | "file" | "none";
|
|
46
|
+
export type WorkflowFieldKind = "text" | "number" | "select" | "checkbox" | "file" | "none" | (string & {});
|
|
43
47
|
/**
|
|
44
48
|
* A property's declared type, taking the first non-null member of a union.
|
|
45
49
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Raw worklet source — exported so tests can evaluate the processor directly. */
|
|
2
|
-
export declare const captureProcessorSource
|
|
3
|
-
declare const
|
|
4
|
-
export default
|
|
2
|
+
export declare const captureProcessorSource: string;
|
|
3
|
+
declare const captureProcessorUrl: string;
|
|
4
|
+
export default captureProcessorUrl;
|
|
@@ -92,6 +92,6 @@ class CaptureProcessor extends AudioWorkletProcessor {
|
|
|
92
92
|
|
|
93
93
|
registerProcessor('capture-processor', CaptureProcessor);
|
|
94
94
|
`;
|
|
95
|
-
|
|
95
|
+
const captureProcessorUrl = workletModuleUrl(captureProcessorSource);
|
|
96
96
|
//#endregion
|
|
97
|
-
export { captureProcessorSource,
|
|
97
|
+
export { captureProcessorSource, captureProcessorUrl as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Raw worklet source — exported so tests can evaluate the processor directly. */
|
|
2
|
-
export declare const playbackProcessorSource = "\nclass PlaybackProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n const opts = options.processorOptions || {};\n // The node's context IS the playback context (audio/voice-io.ts asserts its rate),\n // so the worklet-global sampleRate is authoritative; the option exists\n // for the node-less test harness.\n const rate = opts.sampleRate ?? sampleRate;\n // Kept, because every derived quantity below reads it and `bufferedMs` in\n // process() is the one that used to read the worklet global instead. Those\n // agree in production (the option is unset, so `rate` IS `sampleRate`) and\n // diverge only in the node-less harness \u2014 which is the one place a spec\n // could ever assert on the reported backlog, so the divergence made the\n // number untestable rather than wrong.\n this.rate = rate;\n // The one fill target \u2014 for the start of a turn and for recovery after an\n // underrun alike. If 'done' arrives first (short utterance), start\n // immediately instead of waiting for audio that is never coming.\n this.fillSamples = Math.floor((rate * (opts.fillMs ?? 200)) / 1000);\n // Concealment source: a ring of the most recently played samples, looped\n // under a decaying gain to cover a gap. Sized to the fade window, with a\n // per-sample decay that reaches the floor exactly at its end.\n this.concealCapacity = Math.max(1, Math.floor((rate * 40) / 1000));\n this.concealBuf = new Float32Array(this.concealCapacity);\n this.concealDecay = Math.exp(Math.log(0.001) / this.concealCapacity);\n // Float32 ring buffer \u2014 PLAYBACK_BUFFER_SECONDS at the context sample\n // rate. Allocated once for the node's lifetime; per-turn state resets via\n // resetTurn(). writePos and readPos are absolute (monotonic) sample\n // counts; the buffer is indexed modulo capacity so a longer reply keeps\n // playing instead of writing past the end and going silent.\n this.capacity = rate * 60;\n this.samples = new Float32Array(this.capacity);\n // Cadence for the 'progress' report in process(), counted in samples so\n // the hot path never reads a clock. Survives resetTurn(): the host clamps\n // upward only, so a stale count costs at most one extra report.\n this.reportIntervalSamples = Math.floor((rate * 500) / 1000);\n this.sinceReportSamples = 0;\n // Platform endianness probe: the wire format is PCM16 little-endian, so\n // the Int16Array fast path in ingestBytes is only valid on LE hosts\n // (every shipping browser target; the DataView path is the fallback).\n this.littleEndian = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;\n this.resetTurn();\n\n this.port.onmessage = (e) => {\n const d = e.data;\n if (d.event === 'write') {\n this.ingestBytes(d.buffer);\n } else if (d.event === 'interrupt') {\n // Applied eagerly, not deferred to the next process(): onmessage and\n // process() never interleave (one audio thread), and a deferred\n // interrupt would let 'write'/'done' frames for the NEXT turn\n // coalesce in ahead of it and be wiped by the reset along with the\n // cancelled turn's audio.\n this.stopTurn('interrupt');\n } else if (d.event === 'done') {\n this.isDone = true;\n // Echoed back on this turn's 'stop' so the host can tell WHICH turn\n // drained: a stop already in flight when a barge-in lands would\n // otherwise settle the next turn's done() the moment it arrives.\n this.doneTurn = d.turn ?? null;\n }\n };\n }\n\n // Reset per-turn state so the node is reusable across replies without\n // reallocating the sample buffer or re-instantiating the worklet.\n resetTurn() {\n this.isDone = false;\n // Host turn id carried by this turn's 'done' (null until one arrives).\n this.doneTurn = null;\n this.playing = false;\n // Whether any real audio has been rendered this turn. Separates a turn's\n // pre-roll (nothing to extrapolate from, and not a defect) from a\n // mid-turn underrun.\n this.hasPlayed = false;\n // Carry-over byte for split samples across chunks\n this.carry = null;\n this.writePos = 0;\n this.readPos = 0;\n // Concealment ring state and the current fade position.\n this.concealLen = 0;\n this.concealWrite = 0;\n this.concealPos = 0;\n this.concealGain = 1;\n // Episode flags, so a multi-quantum gap counts as one event.\n this.concealing = false;\n this.concealedSilence = false;\n // Reported to the host on 'stop'. A fresh object per turn: the one just\n // posted must not be mutated by the next turn.\n this.stats = {\n concealedSamples: 0,\n silentConcealedSamples: 0,\n concealmentEvents: 0,\n silentConcealmentEvents: 0,\n };\n }\n\n // End the current turn: notify the host and rearm for the next reply.\n // Must NOT return false from process() \u2014 a processor that stops is dead\n // for good, forcing a new node (and buffer) per reply.\n // `reason` ('interrupt' | 'done') tells the host which turn boundary this\n // stop belongs to \u2014 interrupt-stops are dropped host-side, flush() having\n // already settled that turn \u2014 and `turn` names WHICH turn drained (the id\n // this turn's 'done' carried), so a drain-stop still in flight when a\n // barge-in lands cannot settle the next turn's done() early.\n stopTurn(reason) {\n this.port.postMessage({ event: 'stop', reason, turn: this.doneTurn, stats: this.stats });\n this.resetTurn();\n }\n\n // Cover a quantum (from `start`) where real audio should have been.\n //\n // Before the turn's first samples there is nothing to extrapolate from, so\n // the gap is plain silence and counted as nothing \u2014 WebRTC likewise only\n // counts concealment once playout has begun. After that, loop the retained\n // tail under a decaying gain: a hard zero-fill is a discontinuity mid-word,\n // which is the click that makes a brief stall sound like breakage.\n coverGap(out, start) {\n if (!this.hasPlayed) {\n out.fill(0, start);\n return;\n }\n if (!this.concealing) {\n this.concealing = true;\n this.concealedSilence = false;\n this.stats.concealmentEvents++;\n }\n const total = out.length - start;\n const len = this.concealLen;\n let silent = 0;\n // Second condition: once the fade has decayed to the floor, every sample\n // the loop would emit is 0 anyway \u2014 bulk-fill instead of running 128\n // branchy iterations per quantum for the whole tail of a long stall.\n if (len === 0 || this.concealGain < 0.001) {\n out.fill(0, start);\n silent = total;\n } else {\n let g = this.concealGain;\n for (let i = start; i < out.length; i++) {\n if (g < 0.001) {\n // The fade has run out: keep counting the gap, but stop looping a\n // fragment that is now inaudible anyway.\n out[i] = 0;\n silent++;\n continue;\n }\n out[i] = this.concealBuf[this.concealPos] * g;\n this.concealPos = this.concealPos + 1 === len ? 0 : this.concealPos + 1;\n g *= this.concealDecay;\n }\n this.concealGain = g;\n }\n this.stats.concealedSamples += total;\n if (silent > 0) {\n this.stats.silentConcealedSamples += silent;\n if (!this.concealedSilence) {\n this.concealedSilence = true;\n this.stats.silentConcealmentEvents++;\n }\n }\n }\n\n // Retain the tail of a rendered quantum as the next gap's concealment\n // source, and close any episode the real audio just ended.\n rememberTail(out, n) {\n const cap = this.concealCapacity;\n const take = Math.min(n, cap);\n // Bulk copies (this runs on every cleanly rendered quantum): the tail is\n // one contiguous source run, landing in at most two ring runs.\n const tail = out.subarray(n - take, n);\n const first = Math.min(take, cap - this.concealWrite);\n this.concealBuf.set(tail.subarray(0, first), this.concealWrite);\n if (take > first) this.concealBuf.set(tail.subarray(first), 0);\n this.concealWrite = (this.concealWrite + take) % cap;\n this.concealLen = Math.min(cap, this.concealLen + take);\n // Read the loop oldest-first; once the ring is full the write cursor is\n // the oldest retained sample.\n this.concealPos = this.concealLen === cap ? this.concealWrite : 0;\n this.concealing = false;\n this.concealGain = 1;\n }\n\n ingestBytes(uint8) {\n let bytes = uint8;\n\n if (this.carry !== null) {\n const merged = new Uint8Array(1 + bytes.length);\n merged[0] = this.carry;\n merged.set(bytes, 1);\n bytes = merged;\n this.carry = null;\n }\n\n if (bytes.length % 2 !== 0) {\n this.carry = bytes[bytes.length - 1];\n bytes = bytes.subarray(0, bytes.length - 1);\n }\n\n if (bytes.length === 0) return;\n const numSamples = bytes.length / 2;\n const cap = this.capacity;\n const samples = this.samples;\n if (this.littleEndian && (bytes.byteOffset & 1) === 0) {\n // Fast path: 2-byte-aligned LE bytes wrap directly as an Int16Array;\n // copy wrap-aware in at most two runs with no per-sample DataView call\n // or modulo. This runs on the realtime audio thread.\n const int16 = new Int16Array(bytes.buffer, bytes.byteOffset, numSamples);\n let src = 0;\n let dst = this.writePos % cap;\n while (src < numSamples) {\n const run = Math.min(numSamples - src, cap - dst);\n for (let j = 0; j < run; j++) {\n samples[dst + j] = int16[src + j] / 0x8000;\n }\n src += run;\n dst = 0;\n }\n } else {\n // Odd byte offset (or big-endian host): fall back to per-sample reads.\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.length);\n let dst = this.writePos % cap;\n for (let i = 0; i < numSamples; i++) {\n samples[dst] = view.getInt16(i * 2, true) / 0x8000;\n dst++;\n if (dst === cap) dst = 0;\n }\n }\n this.writePos += numSamples;\n // If the producer outran the consumer by more than the buffer holds, drop\n // the oldest unplayed audio rather than reading samples we've overwritten.\n if (this.writePos - this.readPos > this.capacity) {\n this.readPos = this.writePos - this.capacity;\n }\n }\n\n process(inputs, outputs) {\n // No output wired up yet \u2014 nothing to render this quantum. Throwing here\n // would permanently kill the processor (the node is persistent per\n // session), so guard like the capture processor does.\n if (!outputs[0] || !outputs[0][0]) return true;\n const out = outputs[0][0];\n const avail = this.writePos - this.readPos;\n\n // Report the unplayed backlog to the host (`playback_progress`). This IS\n // the closed-loop signal: the host otherwise models playback open-loop \u2014\n // every forwarded chunk assumed to start playing on arrival at exactly\n // 1.0x \u2014 and cannot see a buffer that has run ahead of the wall clock.\n // Measured against a client draining at 0.6x, the host declared the line\n // silent while it still held seconds of the reply, and then opened the\n // speaking-edge gate over speech the caller had not heard.\n //\n // Sent from process() rather than a timer because this is the only place\n // that sees the buffer on the audio thread; the counter is in QUANTA, so\n // it costs an integer compare per render and no clock read. Silence is not\n // reported: an empty buffer is what the host already assumes.\n this.sinceReportSamples += out.length;\n if (this.sinceReportSamples >= this.reportIntervalSamples) {\n this.sinceReportSamples = 0;\n if (avail > 0) {\n this.port.postMessage({ event: 'progress', bufferedMs: (avail / this.rate) * 1000 });\n }\n }\n\n // Filling: wait for the target. 'done' short-circuits it \u2014 what is\n // buffered is all there will be, so there is nothing left to wait for.\n if (!this.playing) {\n if (avail >= this.fillSamples || this.isDone) {\n this.playing = true;\n } else {\n this.coverGap(out, 0);\n return true;\n }\n }\n\n // Underrun: this quantum cannot be filled and more audio is still coming.\n // Go back to filling and cover the gap, leaving\n // readPos untouched \u2014 the fragment stays buffered and plays intact once\n // the buffer recovers, instead of being dribbled out a few samples at a\n // time for the rest of the turn.\n if (avail < out.length && !this.isDone) {\n this.playing = false;\n this.coverGap(out, 0);\n return true;\n }\n\n if (avail > 0) {\n const n = Math.min(avail, out.length);\n // Copy from the ring buffer, splitting across the wrap boundary.\n const start = this.readPos % this.capacity;\n const first = Math.min(n, this.capacity - start);\n out.set(this.samples.subarray(start, start + first), 0);\n if (n > first) out.set(this.samples.subarray(0, n - first), first);\n this.readPos += n;\n // Only reachable with n < out.length on the turn's final partial\n // quantum (the underrun branch above catches every other case).\n out.fill(0, n);\n this.hasPlayed = true;\n this.rememberTail(out, n);\n return true;\n }\n\n // Drained and done: end the turn. Not reachable mid-turn \u2014 an empty\n // buffer with audio still coming is the underrun branch above.\n out.fill(0);\n if (this.isDone) {\n this.stopTurn('done');\n }\n return true;\n }\n}\n\nregisterProcessor('playback-processor', PlaybackProcessor);\n";
|
|
3
|
-
declare const
|
|
4
|
-
export default
|
|
2
|
+
export declare const playbackProcessorSource: string;
|
|
3
|
+
declare const playbackProcessorUrl: string;
|
|
4
|
+
export default playbackProcessorUrl;
|
|
@@ -316,6 +316,6 @@ class PlaybackProcessor extends AudioWorkletProcessor {
|
|
|
316
316
|
|
|
317
317
|
registerProcessor('playback-processor', PlaybackProcessor);
|
|
318
318
|
`;
|
|
319
|
-
|
|
319
|
+
const playbackProcessorUrl = workletModuleUrl(playbackProcessorSource);
|
|
320
320
|
//#endregion
|
|
321
|
-
export {
|
|
321
|
+
export { playbackProcessorUrl as default, playbackProcessorSource };
|