@runtypelabs/persona 4.26.0 → 4.26.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.
- package/dist/approval-ui.d.cts +6 -0
- package/dist/approval-ui.d.ts +6 -0
- package/dist/artifacts-ui.d.cts +6 -0
- package/dist/artifacts-ui.d.ts +6 -0
- package/dist/chunk-GKPCIQPB.js +1 -0
- package/dist/{client-history-YPEG45Q4.js → client-history-GOZWBDA5.js} +1 -1
- package/dist/client-history.js +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +6 -0
- package/dist/context-mentions.d.ts +6 -0
- package/dist/event-stream-view.d.cts +6 -0
- package/dist/event-stream-view.d.ts +6 -0
- package/dist/forms-ui.d.cts +6 -0
- package/dist/forms-ui.d.ts +6 -0
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/session-reconnect.d.cts +6 -0
- package/dist/session-reconnect.d.ts +6 -0
- package/dist/smart-dom-reader.d.cts +6 -0
- package/dist/smart-dom-reader.d.ts +6 -0
- package/dist/theme-editor-preview.cjs +14 -14
- package/dist/theme-editor-preview.d.cts +6 -0
- package/dist/theme-editor-preview.d.ts +6 -0
- package/dist/theme-editor-preview.js +5 -5
- package/dist/theme-editor.d.cts +6 -0
- package/dist/theme-editor.d.ts +6 -0
- package/dist/voice-runtime-XLU6YK2N.js +17 -0
- package/dist/voice-runtime.cjs +3 -3
- package/dist/voice-runtime.d.cts +6 -0
- package/dist/voice-runtime.d.ts +6 -0
- package/dist/voice-runtime.js +3 -3
- package/package.json +1 -1
- package/src/approval-paused-tool-settle.test.ts +108 -4
- package/src/session.ts +51 -10
- package/src/session.voice.test.ts +1 -0
- package/src/types.ts +6 -0
- package/src/voice/runtype-voice-provider.ts +19 -9
- package/src/voice/voice-delegation.ts +1 -1
- package/src/voice/voice.test.ts +81 -2
- package/dist/chunk-QPPJUSZH.js +0 -1
- package/dist/voice-runtime-UOQOFAFY.js +0 -17
|
@@ -3,9 +3,10 @@ import { AgentWidgetClient } from "./client";
|
|
|
3
3
|
import { AgentWidgetSession } from "./session";
|
|
4
4
|
import type { AgentWidgetMessage } from "./types";
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
// NEW toolCallId, so no terminal
|
|
8
|
-
//
|
|
6
|
+
// Older servers resume an approval by re-running the agent, which re-issues the
|
|
7
|
+
// gated call under a NEW toolCallId, so no terminal frame arrives for the paused
|
|
8
|
+
// one; the session settles it when the stream ends. Current servers run the
|
|
9
|
+
// paused call itself under its original id, and that real result wins.
|
|
9
10
|
|
|
10
11
|
const originalFetch = global.fetch;
|
|
11
12
|
afterEach(() => {
|
|
@@ -60,10 +61,14 @@ const pendingApproval = (toolCallId?: string): AgentWidgetMessage => ({
|
|
|
60
61
|
|
|
61
62
|
const setup = (initialMessages: AgentWidgetMessage[]) => {
|
|
62
63
|
let messages: AgentWidgetMessage[] = [];
|
|
64
|
+
const toolStates: Array<AgentWidgetMessage["toolCall"]> = [];
|
|
63
65
|
const session = new AgentWidgetSession(
|
|
64
66
|
{ clientToken: "ct_live_demo", apiUrl: "https://api.runtype.com", initialMessages },
|
|
65
67
|
{
|
|
66
|
-
onMessagesChanged: (m) => {
|
|
68
|
+
onMessagesChanged: (m) => {
|
|
69
|
+
messages = m;
|
|
70
|
+
toolStates.push(m.find((x) => x.toolCall?.id === "toolu_paused")?.toolCall);
|
|
71
|
+
},
|
|
67
72
|
onStatusChanged: () => {},
|
|
68
73
|
onStreamingChanged: () => {},
|
|
69
74
|
onError: () => {},
|
|
@@ -74,6 +79,7 @@ const setup = (initialMessages: AgentWidgetMessage[]) => {
|
|
|
74
79
|
return {
|
|
75
80
|
resolve: (decision: "approved" | "denied") => session.resolveApproval(approval, decision),
|
|
76
81
|
tool: (id = "toolu_paused") => messages.find((m) => m.toolCall?.id === id),
|
|
82
|
+
toolStates,
|
|
77
83
|
};
|
|
78
84
|
};
|
|
79
85
|
|
|
@@ -189,6 +195,50 @@ describe("AgentWidgetSession settles the approval-paused tool bubble", () => {
|
|
|
189
195
|
});
|
|
190
196
|
});
|
|
191
197
|
|
|
198
|
+
it("lets the server's own result for the paused id complete an approved call", async () => {
|
|
199
|
+
global.fetch = vi.fn().mockResolvedValue(
|
|
200
|
+
sse([
|
|
201
|
+
{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "approved" },
|
|
202
|
+
{ type: "tool_start", toolCallId: "toolu_paused", toolName: "place_pickup_order", toolType: "custom" },
|
|
203
|
+
{
|
|
204
|
+
type: "tool_complete",
|
|
205
|
+
toolCallId: "toolu_paused",
|
|
206
|
+
toolName: "place_pickup_order",
|
|
207
|
+
success: true,
|
|
208
|
+
result: { orderId: "ord_1" },
|
|
209
|
+
},
|
|
210
|
+
{ type: "execution_complete", kind: "agent", success: true },
|
|
211
|
+
])
|
|
212
|
+
);
|
|
213
|
+
const { resolve, tool, toolStates } = setup([pausedTool(), pendingApproval("toolu_paused")]);
|
|
214
|
+
await resolve("approved");
|
|
215
|
+
|
|
216
|
+
expect(tool()?.toolCall).toMatchObject({ status: "complete", result: { orderId: "ord_1" } });
|
|
217
|
+
expect(toolStates.some((state) => state?.superseded)).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("lets the server's own failed result for the paused id complete a denied call", async () => {
|
|
221
|
+
global.fetch = vi.fn().mockResolvedValue(
|
|
222
|
+
sse([
|
|
223
|
+
{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "denied" },
|
|
224
|
+
{ type: "tool_start", toolCallId: "toolu_paused", toolName: "place_pickup_order", toolType: "custom" },
|
|
225
|
+
{
|
|
226
|
+
type: "tool_complete",
|
|
227
|
+
toolCallId: "toolu_paused",
|
|
228
|
+
toolName: "place_pickup_order",
|
|
229
|
+
success: false,
|
|
230
|
+
error: 'Tool "place_pickup_order" was denied by the user.',
|
|
231
|
+
},
|
|
232
|
+
{ type: "execution_complete", kind: "agent", success: true },
|
|
233
|
+
])
|
|
234
|
+
);
|
|
235
|
+
const { resolve, tool } = setup([pausedTool(), pendingApproval("toolu_paused")]);
|
|
236
|
+
await resolve("denied");
|
|
237
|
+
|
|
238
|
+
expect(tool()?.toolCall).toMatchObject({ status: "complete", success: false });
|
|
239
|
+
expect(tool()?.toolCall?.error).toContain("denied by the user");
|
|
240
|
+
});
|
|
241
|
+
|
|
192
242
|
it("does not touch a tool call that already completed", async () => {
|
|
193
243
|
global.fetch = vi.fn().mockResolvedValue(resumeStream("denied", false));
|
|
194
244
|
const { resolve, tool } = setup([pausedTool("complete"), pendingApproval("toolu_paused")]);
|
|
@@ -219,3 +269,57 @@ describe("AgentWidgetSession settles the approval-paused tool bubble", () => {
|
|
|
219
269
|
});
|
|
220
270
|
});
|
|
221
271
|
});
|
|
272
|
+
|
|
273
|
+
describe("approval settles stay with the stream that decided them", () => {
|
|
274
|
+
const tool = (id: string): AgentWidgetMessage => ({
|
|
275
|
+
...pausedTool(),
|
|
276
|
+
id: `tool-${id}`,
|
|
277
|
+
toolCall: { ...pausedTool().toolCall!, id },
|
|
278
|
+
});
|
|
279
|
+
const approval = (approvalId: string, toolCallId: string): AgentWidgetMessage => ({
|
|
280
|
+
...pendingApproval(toolCallId),
|
|
281
|
+
id: `approval-${approvalId}`,
|
|
282
|
+
approval: { ...pendingApproval(toolCallId).approval!, id: approvalId, toolCallId },
|
|
283
|
+
});
|
|
284
|
+
const frame = (event: Record<string, unknown>) =>
|
|
285
|
+
new TextEncoder().encode(`data: ${JSON.stringify(event)}\n\n`);
|
|
286
|
+
|
|
287
|
+
it("does not settle another stream's paused call when one stream ends", async () => {
|
|
288
|
+
let messages: AgentWidgetMessage[] = [];
|
|
289
|
+
const session = new AgentWidgetSession(
|
|
290
|
+
{
|
|
291
|
+
clientToken: "ct_live_demo",
|
|
292
|
+
apiUrl: "https://api.runtype.com",
|
|
293
|
+
initialMessages: [tool("toolu_a"), approval("appr_a", "toolu_a"), tool("toolu_b"), approval("appr_b", "toolu_b")],
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
onMessagesChanged: (m) => { messages = m; },
|
|
297
|
+
onStatusChanged: () => {},
|
|
298
|
+
onStreamingChanged: () => {},
|
|
299
|
+
onError: () => {},
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
let controlB!: ReadableStreamDefaultController<Uint8Array>;
|
|
303
|
+
const streamB = new ReadableStream<Uint8Array>({ start: (c) => { controlB = c; } });
|
|
304
|
+
const connectedB = session.connectStream(streamB, { allowReentry: true });
|
|
305
|
+
controlB.enqueue(frame({ type: "approval_complete", executionId: "exec_b", approvalId: "appr_b", decision: "approved" }));
|
|
306
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
307
|
+
|
|
308
|
+
await session.connectStream(
|
|
309
|
+
sse([
|
|
310
|
+
{ type: "approval_complete", executionId: "exec_a", approvalId: "appr_a", decision: "approved" },
|
|
311
|
+
{ type: "execution_complete", kind: "agent", success: true },
|
|
312
|
+
]).body!,
|
|
313
|
+
{ allowReentry: true }
|
|
314
|
+
);
|
|
315
|
+
const b = () => messages.find((m) => m.toolCall?.id === "toolu_b")?.toolCall;
|
|
316
|
+
expect(b()?.superseded).toBeUndefined();
|
|
317
|
+
|
|
318
|
+
controlB.enqueue(frame({ type: "tool_start", toolCallId: "toolu_b", toolName: "place_pickup_order", toolType: "custom" }));
|
|
319
|
+
controlB.enqueue(frame({ type: "tool_complete", toolCallId: "toolu_b", toolName: "place_pickup_order", success: true, result: { orderId: "ord_b" } }));
|
|
320
|
+
controlB.close();
|
|
321
|
+
await connectedB;
|
|
322
|
+
expect(b()).toMatchObject({ status: "complete", result: { orderId: "ord_b" } });
|
|
323
|
+
expect(b()?.superseded).toBeUndefined();
|
|
324
|
+
});
|
|
325
|
+
});
|
package/src/session.ts
CHANGED
|
@@ -285,6 +285,10 @@ export class AgentWidgetSession {
|
|
|
285
285
|
private messages: AgentWidgetMessage[];
|
|
286
286
|
private status: AgentWidgetSessionStatus = "idle";
|
|
287
287
|
private streaming = false;
|
|
288
|
+
/** Approval messages decided on a stream, keyed to that stream so it settles only its own. */
|
|
289
|
+
#approvalSettlesAtStreamEnd = new Map<string, object | undefined>();
|
|
290
|
+
/** The `connectStream` call whose event is being handled, if any. */
|
|
291
|
+
#activeStreamToken: object | undefined;
|
|
288
292
|
private abortController: AbortController | null = null;
|
|
289
293
|
#sequenceCounter = Date.now();
|
|
290
294
|
|
|
@@ -2988,18 +2992,33 @@ export class AgentWidgetSession {
|
|
|
2988
2992
|
|
|
2989
2993
|
this.#setStreaming(true);
|
|
2990
2994
|
|
|
2995
|
+
const streamToken = {};
|
|
2991
2996
|
try {
|
|
2992
2997
|
await this.client.processStream(
|
|
2993
2998
|
stream,
|
|
2994
|
-
|
|
2999
|
+
(event) => {
|
|
3000
|
+
const previous = this.#activeStreamToken;
|
|
3001
|
+
this.#activeStreamToken = streamToken;
|
|
3002
|
+
try {
|
|
3003
|
+
this.handleEvent(event);
|
|
3004
|
+
} finally {
|
|
3005
|
+
this.#activeStreamToken = previous;
|
|
3006
|
+
}
|
|
3007
|
+
},
|
|
2995
3008
|
options?.assistantMessageId,
|
|
2996
3009
|
options?.seedContent
|
|
2997
3010
|
);
|
|
3011
|
+
this.#flushApprovalSettles(streamToken);
|
|
2998
3012
|
} catch (error) {
|
|
2999
3013
|
// During a durable reconnect a thrown resume stream is just another drop:
|
|
3000
3014
|
// the reconnect loop owns the retry/backoff. Don't paint an error or tear
|
|
3001
|
-
// down, stay in `resuming`.
|
|
3002
|
-
|
|
3015
|
+
// down, stay in `resuming`. The reconnect may still deliver the paused
|
|
3016
|
+
// call's result, so its approval settles wait for streaming to end.
|
|
3017
|
+
if (this.status === "resuming" || this.#reconnecting) {
|
|
3018
|
+
this.#releaseApprovalSettles(streamToken);
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
this.#flushApprovalSettles(streamToken);
|
|
3003
3022
|
this.#setStatus("error");
|
|
3004
3023
|
// Mirror the idle/error handlers: a failed resume stream must not tear
|
|
3005
3024
|
// down streaming/abortController while another WebMCP resolve is still
|
|
@@ -3670,13 +3689,14 @@ export class AgentWidgetSession {
|
|
|
3670
3689
|
|
|
3671
3690
|
// A resolved approval reaches this handler only from the server's
|
|
3672
3691
|
// `approval_complete` (resolveApproval's optimistic update upserts
|
|
3673
|
-
// directly)
|
|
3692
|
+
// directly). The server may now finish the paused call itself on this
|
|
3693
|
+
// stream, so the bubble settles at stream end only if it is still open.
|
|
3674
3694
|
if (
|
|
3675
3695
|
event.message.variant === "approval" &&
|
|
3676
3696
|
event.message.approval &&
|
|
3677
3697
|
event.message.approval.status !== "pending"
|
|
3678
3698
|
) {
|
|
3679
|
-
this.#
|
|
3699
|
+
this.#approvalSettlesAtStreamEnd.set(event.message.id, this.#activeStreamToken);
|
|
3680
3700
|
}
|
|
3681
3701
|
|
|
3682
3702
|
// Track the open assistant text bubble's REAL streamed id so a durable
|
|
@@ -4134,6 +4154,7 @@ export class AgentWidgetSession {
|
|
|
4134
4154
|
|
|
4135
4155
|
// Speak the latest assistant message when streaming completes
|
|
4136
4156
|
if (wasStreaming && !streaming) {
|
|
4157
|
+
this.#flushApprovalSettles(undefined);
|
|
4137
4158
|
this.speakLatestAssistantMessage();
|
|
4138
4159
|
}
|
|
4139
4160
|
}
|
|
@@ -4234,12 +4255,32 @@ export class AgentWidgetSession {
|
|
|
4234
4255
|
}
|
|
4235
4256
|
|
|
4236
4257
|
/**
|
|
4237
|
-
* Settle the
|
|
4238
|
-
*
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4258
|
+
* Settle the approval-paused bubbles one stream decided but did not
|
|
4259
|
+
* complete. `undefined` settles those no live `connectStream` owns.
|
|
4260
|
+
*/
|
|
4261
|
+
#flushApprovalSettles(streamToken: object | undefined) {
|
|
4262
|
+
for (const [id, token] of [...this.#approvalSettlesAtStreamEnd]) {
|
|
4263
|
+
if (token !== streamToken) continue;
|
|
4264
|
+
this.#approvalSettlesAtStreamEnd.delete(id);
|
|
4265
|
+
this.#settleApprovalPausedToolCall(id);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
|
|
4269
|
+
/** Hand a dropped stream's pending settles to the end of all streaming. */
|
|
4270
|
+
#releaseApprovalSettles(streamToken: object) {
|
|
4271
|
+
for (const [id, token] of this.#approvalSettlesAtStreamEnd) {
|
|
4272
|
+
if (token === streamToken) this.#approvalSettlesAtStreamEnd.set(id, undefined);
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
/**
|
|
4277
|
+
* Settle the tool bubble an approval paused when no terminal tool frame
|
|
4278
|
+
* arrived for it. Current servers run the paused call itself under its
|
|
4279
|
+
* original id; older ones re-issue it under a new id. Denied and timed-out
|
|
4280
|
+
* calls settle as failed; an approved call settles as superseded. A call
|
|
4281
|
+
* already complete is left alone.
|
|
4242
4282
|
*/
|
|
4283
|
+
|
|
4243
4284
|
#settleApprovalPausedToolCall(approvalMessageId: string) {
|
|
4244
4285
|
const approval = this.messages.find((m) => m.id === approvalMessageId)?.approval;
|
|
4245
4286
|
const id = approval?.toolCallId;
|
|
@@ -790,6 +790,7 @@ describe('AgentWidgetSession - voice client delegation bridge', () => {
|
|
|
790
790
|
expect(decline).toEqual({
|
|
791
791
|
status: 'denied',
|
|
792
792
|
text: 'Okay, I cancelled the place pickup order request. Nothing was done.',
|
|
793
|
+
inChat: false, // only spoken: its read-back stays as a caption
|
|
793
794
|
});
|
|
794
795
|
expect(dispatch).toHaveBeenCalledTimes(1); // no chat turn for the decline
|
|
795
796
|
expect(approvalOf().status).toBe('denied');
|
package/src/types.ts
CHANGED
|
@@ -5294,6 +5294,12 @@ export type VoiceDelegationResult = {
|
|
|
5294
5294
|
status: VoiceDelegationStatus;
|
|
5295
5295
|
/** The assistant text (Markdown allowed). */
|
|
5296
5296
|
text: string;
|
|
5297
|
+
/**
|
|
5298
|
+
* `false` when no chat message carries this answer (it is only spoken), so
|
|
5299
|
+
* the voice model's read-back of it stays visible as a caption.
|
|
5300
|
+
* @default true
|
|
5301
|
+
*/
|
|
5302
|
+
inChat?: boolean;
|
|
5297
5303
|
/**
|
|
5298
5304
|
* Set when the turn parked on approvals (`text` then asks for the decision).
|
|
5299
5305
|
* Resolves with the terminal result once the visitor decides (or the
|
|
@@ -56,10 +56,11 @@
|
|
|
56
56
|
// replaced). A second gated tool in the same turn sends another update.
|
|
57
57
|
// `delegation_cancelled{delegationId}` (or a `warning` refusing a frame for
|
|
58
58
|
// it) stops all frames for that id; the chat turn still renders, and since
|
|
59
|
-
// nobody speaks it now, browser TTS may read it. The voice model's spoken read-back
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
59
|
+
// nobody speaks it now, browser TTS may read it. The voice model's spoken read-back of
|
|
60
|
+
// an answered delegation is folded: the chat already shows it. With
|
|
61
|
+
// `delegation_read_back` negotiated it is the utterance the server tags with that
|
|
62
|
+
// `delegationId`; otherwise it is the first new assistant utterance after
|
|
63
|
+
// `delegation_completed`, unless the visitor speaks first. Transcripts in such a call are display-only captions, never sent to the
|
|
63
64
|
// agent as conversation; a user bubble becomes conversation only when it is
|
|
64
65
|
// submitted. With `context` negotiated, one `context{text}` frame (chat history
|
|
65
66
|
// as of call start plus the host's `callContext`) is held until the visitor's
|
|
@@ -450,7 +451,8 @@ export class RuntypeVoiceProvider implements VoiceProvider {
|
|
|
450
451
|
*/
|
|
451
452
|
#voiceSocketUrl(host: string, agentId: string, attach?: URLSearchParams): string {
|
|
452
453
|
const capabilities = ["partial_transcript", "context"];
|
|
453
|
-
if (this.bridge && this.config?.clientDelegation !== false)
|
|
454
|
+
if (this.bridge && this.config?.clientDelegation !== false)
|
|
455
|
+
capabilities.push("client_delegation", "delegation_update", "delegation_read_back");
|
|
454
456
|
if (attach) capabilities.unshift("attach");
|
|
455
457
|
const params = new URLSearchParams({
|
|
456
458
|
voiceProtocol: VOICE_PROTOCOL,
|
|
@@ -746,9 +748,17 @@ export class RuntypeVoiceProvider implements VoiceProvider {
|
|
|
746
748
|
if (msg.final === true) this.#flushCallContext(generation);
|
|
747
749
|
this.#foldReadback = false;
|
|
748
750
|
} else {
|
|
749
|
-
// Read-back of a delegated result
|
|
750
|
-
//
|
|
751
|
-
|
|
751
|
+
// Read-back of a delegated result the chat already renders. A server
|
|
752
|
+
// that negotiated `delegation_read_back` tags it with its delegation;
|
|
753
|
+
// an older one rotates the assistant id at completion, so it is the
|
|
754
|
+
// first new id after it (a guess a filler finishing late defeats).
|
|
755
|
+
if (this.#capabilities.has("delegation_read_back")) {
|
|
756
|
+
// Fold only from the first frame: hiding an utterance already shown would strand a partial caption.
|
|
757
|
+
const tag = msg.delegationId;
|
|
758
|
+
if (typeof tag === "string" && this.#answered.has(tag) && !this.#assistantTurns.has(turnId)) {
|
|
759
|
+
this.#foldedTurns.add(turnId);
|
|
760
|
+
}
|
|
761
|
+
} else if (this.#foldReadback && !this.#assistantTurns.has(turnId)) {
|
|
752
762
|
this.#foldedTurns.add(turnId);
|
|
753
763
|
this.#foldReadback = false;
|
|
754
764
|
}
|
|
@@ -946,7 +956,7 @@ export class RuntypeVoiceProvider implements VoiceProvider {
|
|
|
946
956
|
result.text,
|
|
947
957
|
);
|
|
948
958
|
if (!sent) return;
|
|
949
|
-
if (result.status !== "failed") this.#answered.add(delegationId);
|
|
959
|
+
if (result.status !== "failed" && result.inChat !== false) this.#answered.add(delegationId);
|
|
950
960
|
if (!result.followUp) return;
|
|
951
961
|
// The approval bookkeeping (expiry, supersede) runs either way.
|
|
952
962
|
const abort = new AbortController();
|
|
@@ -201,7 +201,7 @@ export function createVoiceSessionBridge(host: VoiceDelegationHost): VoiceSessio
|
|
|
201
201
|
const tool = toolOf(pendingIds[0]);
|
|
202
202
|
// The parked delegation still gets its one terminal result, silently.
|
|
203
203
|
settle(live[0], "denied", "");
|
|
204
|
-
return { status: "denied", text: `Okay, I cancelled the ${tool} request. Nothing was done
|
|
204
|
+
return { status: "denied", text: `Okay, I cancelled the ${tool} request. Nothing was done.`, inChat: false };
|
|
205
205
|
}
|
|
206
206
|
// A voice-originated WebMCP approval holds the chat turn open: this turn
|
|
207
207
|
// replaces it (a new send declines it), so don't wait on it. Anything
|
package/src/voice/voice.test.ts
CHANGED
|
@@ -896,7 +896,7 @@ describe('RuntypeVoiceProvider (realtime streaming)', () => {
|
|
|
896
896
|
|
|
897
897
|
it('declares client_delegation (never the kebab form) only with a bridge and the switch on', async () => {
|
|
898
898
|
const { ws } = await startDelegatedCall();
|
|
899
|
-
expect(ws.url).toBe(voiceUrl('wss://api.example.com', 'a1', [...BASE_CAPS, 'client_delegation', 'delegation_update']));
|
|
899
|
+
expect(ws.url).toBe(voiceUrl('wss://api.example.com', 'a1', [...BASE_CAPS, 'client_delegation', 'delegation_update', 'delegation_read_back']));
|
|
900
900
|
expect(ws.url).not.toContain('client-delegation');
|
|
901
901
|
await startDelegatedCall({ clientDelegation: false });
|
|
902
902
|
expect(lastWs().url).toBe(voiceUrl('wss://api.example.com', 'a1', BASE_CAPS));
|
|
@@ -1144,6 +1144,85 @@ describe('RuntypeVoiceProvider (realtime streaming)', () => {
|
|
|
1144
1144
|
expect(calls).toEqual([]);
|
|
1145
1145
|
});
|
|
1146
1146
|
|
|
1147
|
+
describe('with delegation_read_back negotiated', () => {
|
|
1148
|
+
const TAG_CAPS = [...ALL_CAPS, 'delegation_read_back'];
|
|
1149
|
+
const tagged = (role: string, text: string, turnId: string, delegationId?: string) =>
|
|
1150
|
+
JSON.stringify({ type: 'transcript_update', role, text, utteranceId: turnId, final: true, delegationId });
|
|
1151
|
+
async function answered(text = 'Three are overdue.') {
|
|
1152
|
+
const call = await startDelegatedCall({}, { capabilities: TAG_CAPS });
|
|
1153
|
+
delegate(call.ws, 'd1', 'q');
|
|
1154
|
+
await flush();
|
|
1155
|
+
call.pending[0]({ status: 'completed', text });
|
|
1156
|
+
await flush();
|
|
1157
|
+
completed(call.ws, 'd1');
|
|
1158
|
+
return call;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
it('folds the tagged read-back, not a cut filler resuming first (production trace)', async () => {
|
|
1162
|
+
const { ws, transcripts } = await answered();
|
|
1163
|
+
ws.triggerMessage(tagged('assistant', "I'm pulling up the support queue.", 'f2'));
|
|
1164
|
+
ws.triggerMessage(tagged('assistant', 'Three are overdue.', 'r1', 'd1'));
|
|
1165
|
+
expect(transcripts.map((t) => t[1])).toEqual(["I'm pulling up the support queue."]);
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
it('keeps folding the tagged read-back after a late user transcript', async () => {
|
|
1169
|
+
const { ws, transcripts } = await answered();
|
|
1170
|
+
ws.triggerMessage(tagged('user', 'Who is overdue?', 'u1'));
|
|
1171
|
+
ws.triggerMessage(tagged('assistant', 'Three are overdue.', 'r1', 'd1'));
|
|
1172
|
+
expect(transcripts.map((t) => t[1])).toEqual(['Who is overdue?']);
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
it('renders untagged speech and a read-back tagged for a delegation the chat does not show', async () => {
|
|
1176
|
+
const { ws, transcripts } = await answered();
|
|
1177
|
+
ws.triggerMessage(tagged('assistant', 'Anything else?', 'a2'));
|
|
1178
|
+
ws.triggerMessage(tagged('assistant', 'I could not do that.', 'r9', 'd9'));
|
|
1179
|
+
expect(transcripts.map((t) => t[1])).toEqual(['Anything else?', 'I could not do that.']);
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
it('keeps the tagged read-back of an answer that is not in the chat (a spoken decline)', async () => {
|
|
1183
|
+
const { ws, transcripts, pending } = await startDelegatedCall({}, { capabilities: TAG_CAPS });
|
|
1184
|
+
delegate(ws, 'd1', 'cancel that');
|
|
1185
|
+
await flush();
|
|
1186
|
+
pending[0]({ status: 'denied', text: 'Okay, I cancelled the request.', inChat: false });
|
|
1187
|
+
await flush();
|
|
1188
|
+
completed(ws, 'd1');
|
|
1189
|
+
ws.triggerMessage(tagged('assistant', 'Okay, I cancelled the request.', 'r1', 'd1'));
|
|
1190
|
+
expect(transcripts.map((t) => t[1])).toEqual(['Okay, I cancelled the request.']);
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
it('folds every frame of a read-back tagged from its first partial', async () => {
|
|
1194
|
+
const { ws, transcripts } = await answered();
|
|
1195
|
+
const frame = (text: string, final: boolean, delegationId?: string) =>
|
|
1196
|
+
JSON.stringify({ type: 'transcript_update', role: 'assistant', text, utteranceId: 'r1', final, delegationId });
|
|
1197
|
+
ws.triggerMessage(frame('Three are', false, 'd1'));
|
|
1198
|
+
ws.triggerMessage(frame('Three are overdue.', true, 'd1'));
|
|
1199
|
+
expect(transcripts).toEqual([]);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
it('keeps showing an utterance whose tag arrives after an untagged partial, so no caption is left half-done', async () => {
|
|
1203
|
+
const { ws, transcripts } = await answered();
|
|
1204
|
+
const frame = (text: string, final: boolean, delegationId?: string) =>
|
|
1205
|
+
JSON.stringify({ type: 'transcript_update', role: 'assistant', text, utteranceId: 'r1', final, delegationId });
|
|
1206
|
+
ws.triggerMessage(frame('Three are', false));
|
|
1207
|
+
ws.triggerMessage(frame('Three are overdue.', true, 'd1'));
|
|
1208
|
+
expect(transcripts.map((t) => [t[1], t[2]])).toEqual([
|
|
1209
|
+
['Three are', false],
|
|
1210
|
+
['Three are overdue.', true],
|
|
1211
|
+
]);
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
it('does not fold the tagged read-back of a failed delegation', async () => {
|
|
1215
|
+
const { ws, transcripts, pending } = await startDelegatedCall({}, { capabilities: TAG_CAPS });
|
|
1216
|
+
delegate(ws, 'd1', 'q');
|
|
1217
|
+
await flush();
|
|
1218
|
+
pending[0]({ status: 'failed', text: '' });
|
|
1219
|
+
await flush();
|
|
1220
|
+
completed(ws, 'd1');
|
|
1221
|
+
ws.triggerMessage(tagged('assistant', "Sorry, I couldn't complete that.", 'r1', 'd1'));
|
|
1222
|
+
expect(transcripts).toHaveLength(1);
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1147
1226
|
it('does not fold after a failed delegation', async () => {
|
|
1148
1227
|
const { ws, transcripts, pending } = await startDelegatedCall();
|
|
1149
1228
|
delegate(ws, 'd1', 'q');
|
|
@@ -1583,7 +1662,7 @@ describe('RuntypeVoiceProvider prewarm', () => {
|
|
|
1583
1662
|
provider.setSessionBridge({ getHistory: () => [], runDelegatedTurn: async () => ({ status: 'completed', text: '' }) });
|
|
1584
1663
|
provider.prewarm();
|
|
1585
1664
|
expect(lastWs().url).toBe(
|
|
1586
|
-
voiceUrl('wss://api.example.com', 'agent%2F1', ['attach', ...BASE_CAPS, 'client_delegation', 'delegation_update']),
|
|
1665
|
+
voiceUrl('wss://api.example.com', 'agent%2F1', ['attach', ...BASE_CAPS, 'client_delegation', 'delegation_update', 'delegation_read_back']),
|
|
1587
1666
|
);
|
|
1588
1667
|
});
|
|
1589
1668
|
|
package/dist/chunk-QPPJUSZH.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var t="4.26.0";var n=t;export{n as a};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import{a as Y}from"./chunk-6V3MNEKB.js";import{a as K}from"./chunk-QPPJUSZH.js";var x=16e3,ue=24e3,ge=4096,he=x*2*8,pe=4,fe=1380533830,me=3e4,ve=5e3,ye=3e4,be=3e4,Se=6e5,we="full-duplex-v1",Ae=300,ke=2e3,Me=2e3,Ce="runtype-browser-v1",Te=54e4,Ee="You're talking to an AI assistant. Voice is processed by OpenAI.",Ie=8e3,Pe=4e3,Ve=12,J=2e3;function _e(i,e){let t="Conversation so far:",s=e.slice(0,Pe).trim(),n=Ie-(s?s.length+2:0)-t.length,r=[];for(let l of i.slice(-Ve).reverse()){let a=l.content.replace(/\s+/g," ").trim();if(!a)continue;a.length>J&&(a=`${a.slice(0,J-1)}\u2026`);let o=`${l.role==="user"?"User":"Assistant"}: ${a}`;if(o.length+1>n)break;n-=o.length+1,r.unshift(o)}let d=r.length>0?[`${t}
|
|
2
|
-
${r.join(`
|
|
3
|
-
`)}`]:[];return s&&d.push(s),d.join(`
|
|
4
|
-
|
|
5
|
-
`)}function Re(i){return i.byteLength>=44&&new DataView(i).getUint32(0,!1)===fe?new Uint8Array(i,44):new Uint8Array(i)}function Z(i){let e=i.replace(/\/+$/,"");return/^wss?:\/\//i.test(e)?e:/^https?:\/\//i.test(e)?e.replace(/^http/i,"ws"):`${typeof window<"u"&&window.location?.protocol==="https:"?"wss:":"ws:"}//${e}`}function Le(i){return Z(i).replace(/^ws/i,"http")}function De(i){return typeof i!="number"||!Number.isFinite(i)?ye:Math.min(Se,Math.max(be,Math.round(i)))}var P=class{constructor(e){this.config=e;this.type="runtype";this.#e=null;this.#d=null;this.#u=[];this.#i=null;this.#r=null;this.#a=null;this.#f=[];this.#m=0;this.#n=null;this.#g=!1;this.#c=!1;this.#s=0;this.#w=!1;this.#F=Number.NEGATIVE_INFINITY;this.#o=null;this.#l=!1;this.#A=!1;this.#k=0;this.#h=0;this.#v=!1;this.bridge=null;this.#C=!1;this.#y=new Set;this.#b=new Map;this.#T=new Set;this.#_=!1;this.#E=null;this.#R=null;this.#L=Promise.resolve();this.#D=new Set;this.#S=!1;this.#O=new Set;this.#N=new Set;this.#z=[];this.#B=[];this.#W=[];this.#U=[];this.#x=[]}#e;#d;#u;#i;#r;#a;#f;#m;#n;#g;#c;#s;#w;#F;#o;#l;#A;#k;#h;#M;#v;#C;#y;#b;#T;#V;#_;#E;#R;#L;#D;#S;#O;#N;#z;#B;#W;#U;#x;async connect(){}setSessionBridge(e){this.bridge=e}prewarm(){if(this.#g||this.#o)return;let e=this.config?.agentId,t=this.config?.clientToken,s=this.config?.host;if(!e||!t||!s)return;let n=Date.now();if(!(n-this.#F<me)){this.#F=n;try{this.config?.prewarmMode==="attach"?this.#te(s,e,t):this.#ee(s,e,t)}catch{}}}#ee(e,t,s){if(typeof fetch!="function")return;let n=`${Le(e)}/v1/client/agents/${encodeURIComponent(t)}/voice/prewarm`;fetch(n,{method:"POST",headers:{Authorization:`Bearer ${s}`},keepalive:!0}).catch(()=>{})}#te(e,t,s){let n=new URLSearchParams,r=this.config?.attachIdleMs,d=De(r);r!==void 0&&n.set("attachIdleMs",String(d));let l=new WebSocket(this.#G(e,t,n),["runtype.bearer","runtype.attach",s]);l.binaryType="arraybuffer";let a=()=>{},o=new Promise(u=>{a=u}),c={ws:l,state:"connecting",ready:o,settle:a,openedAt:0,idleTimer:void 0};this.#o=c,l.onopen=()=>{this.#o===c&&(c.state=l.protocol==="runtype.attach"?"attached":"live",c.openedAt=Date.now(),this.#X(c,d),c.state==="attached"&&l.send('{"type":"ping"}'),c.settle())},l.onmessage=u=>{if(this.#o!==c||typeof u.data!="string")return;let p;try{p=JSON.parse(u.data)}catch{return}p.type==="attached"&&typeof p.idleMs=="number"&&this.#X(c,p.idleMs-(Date.now()-c.openedAt))},l.onerror=()=>{},l.onclose=()=>{this.#o===c&&this.#I()}}#X(e,t){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{this.#o===e&&this.#I()},Math.max(0,t))}#I(){let e=this.#o;if(!e)return;this.#o=null,clearTimeout(e.idleTimer);let{ws:t}=e;if(t.onopen=null,t.onmessage=null,t.onerror=null,t.onclose=null,e.settle(),t.readyState===WebSocket.CONNECTING||t.readyState===WebSocket.OPEN)try{t.close(1e3,"prewarm released")}catch{}}async#se(){let e=this.#o;if(!e)return null;if(e.state==="connecting"){let t;await Promise.race([e.ready,new Promise(s=>{t=setTimeout(s,ve)})]),clearTimeout(t)}return this.#o!==e?null:e.state==="connecting"||e.ws.readyState!==WebSocket.OPEN?(this.#I(),null):(this.#o=null,clearTimeout(e.idleTimer),{ws:e.ws,live:e.state==="live"})}#G(e,t,s){let n=["partial_transcript","context"];this.bridge&&this.config?.clientDelegation!==!1&&n.push("client_delegation","delegation_update"),s&&n.unshift("attach");let r=new URLSearchParams({voiceProtocol:Ce,clientVersion:`persona/${K}`,voiceCapabilities:we,clientCapabilities:n.join(",")});return s?.forEach((d,l)=>r.set(l,d)),`${Z(e)}/ws/agents/${encodeURIComponent(t)}/voice?${r}`}async startListening(){if(this.#g)return;let e=this.config?.agentId,t=this.config?.clientToken,s=this.config?.host;if(!e)throw new Error("Runtype voice requires an agentId");if(!t)throw new Error("Runtype voice requires a clientToken");if(!s)throw new Error("Runtype voice requires a host (or widget apiUrl)");let n=++this.#s;this.#w=!1,this.#g=!0;try{let r=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:x,channelCount:1,echoCancellation:!0}});if(n!==this.#s){r.getTracks().forEach(u=>u.stop());return}this.#i=r;let d=window.AudioContext||window.webkitAudioContext,l=new d({sampleRate:x});l.state==="suspended"&&await l.resume().catch(()=>{}),this.#d=l;let a=this.config?.createPlaybackEngine?await this.config.createPlaybackEngine():new Y(ue);if(n!==this.#s){a.destroy(),r.getTracks().forEach(u=>u.stop()),l.close().catch(()=>{});return}this.#n=a,this.#j(),this.#ie(l,r,n);let o=await this.#se();if(n!==this.#s){o?.ws.close(1e3,"client ended call");return}if(o){let{ws:u}=o;this.#e=u,this.#K(u,n),o.live||u.send('{"type":"start"}'),this.#$(u),this.#t("listening");return}let c=new WebSocket(this.#G(s,e),["runtype.bearer",t]);c.binaryType="arraybuffer",this.#e=c,c.onopen=()=>{n===this.#s&&(this.#$(c),this.#t("listening"))},this.#K(c,n)}catch(r){throw this.#p(),this.#P(r),this.#t("error"),r}}#K(e,t){e.onmessage=s=>this.#ne(s,t),e.onerror=()=>{t===this.#s&&(this.#P(new Error("Voice connection failed")),this.#t("error"),this.#p())},e.onclose=s=>{if(this.#w){this.#w=!1;return}if(t===this.#s){if(s.code!==1e3){let n=s.code?` (code ${s.code})`:"";this.#P(new Error(`Voice connection closed${n}`)),this.#t("error")}else this.#t("idle");this.#p()}}}getDisclosure(){let e=this.config?.disclosureText;return this.#g&&this.#l&&e!==!1?e||Ee:null}async stopListening(){this.#p(),this.#t("idle")}async disconnect(){this.#p(),this.#t("disconnected"),this.#z=[],this.#B=[],this.#W=[],this.#U=[],this.#x=[]}stopPlayback(){this.#Q();let e=this.#e;e&&e.readyState===WebSocket.OPEN&&(this.#l&&(this.#k=Date.now()+ke,e.send('{"type":"cancel"}')),this.#t("listening"))}getInterruptionMode(){return"barge-in"}isBargeInActive(){return this.#g}onLevel(e){this.#u.push(e)}async deactivateBargeIn(){this.#p(),this.#t("idle")}#ie(e,t,s){let n=e.createMediaStreamSource(t);this.#r=n;let r=e.createScriptProcessor(ge,1,1);this.#a=r,r.onaudioprocess=d=>{if(s!==this.#s)return;let l=this.#e,a=l?.readyState===WebSocket.OPEN;if(l&&!a&&l.readyState!==WebSocket.CONNECTING)return;let o=d.inputBuffer.getChannelData(0),c=new Int16Array(o.length),u=0;for(let p=0;p<o.length;p++){let f=Math.max(-1,Math.min(1,o[p]));u+=f*f,c[p]=f<0?f*32768:f*32767}if(l&&a)this.#$(l),l.send(c.buffer);else for(this.#f.push(c.buffer),this.#m+=c.byteLength;this.#m>he;)this.#m-=this.#f.shift().byteLength;if(this.#u.length>0){let p=Math.sqrt(u/o.length),f=Math.max(0,Math.min(1,p*pe));for(let g of this.#u)g(f)}},n.connect(r),r.connect(e.destination)}#$(e){for(let t of this.#f)e.send(t);this.#f=[],this.#m=0}#ne(e,t){if(t!==this.#s)return;if(e.data instanceof ArrayBuffer){this.#ce(e.data,t);return}let s;try{s=JSON.parse(e.data)}catch{return}switch(s.type){case"session_config":s.speechMode&&(this.#l=s.speechMode==="speech_to_speech",this.#n?.setContinuousMode?.(this.#l)),Array.isArray(s.capabilities)&&(this.#y=new Set(s.capabilities)),typeof s.callId=="string"&&(this.#V=s.callId),this.#C=this.#l&&this.#y.has("client_delegation")&&!!this.bridge,this.#l&&!this.#c&&this.#t("listening"),this.#y.has("context")&&!this.#_&&(this.#_=!0,this.#E=this.#oe());break;case"transcript_update":{let n=s.role==="assistant"?"assistant":"user",r=s.utteranceId??s.turnId;if(!r||n==="assistant"&&this.#q())break;let d=String(r);if(n==="user")s.final===!0&&this.#Y(t),this.#S=!1;else if(this.#S&&!this.#O.has(d)&&(this.#N.add(d),this.#S=!1),this.#O.add(d),this.#N.has(d))break;this.#H(n,s.text??"",s.final===!0,{turnId:d,...typeof s.startMs=="number"&&{startMs:s.startMs},...typeof s.endMs=="number"&&{endMs:s.endMs},...this.#C&&{caption:!0}});break}case"delegation_started":case"delegation_completed":{let n=String(s.delegationId??s.turnId);if(s.type==="delegation_started"){this.#Y(t);let r=s.input;this.#C&&r&&this.#ae(n,{delegationId:n,userText:String(r.text??""),userUtteranceIds:Array.isArray(r.userUtteranceIds)?r.userUtteranceIds.filter(d=>typeof d=="string"):[],messages:Array.isArray(r.messages)?r.messages:[]},t)}else s.speak!==!1&&this.#D.has(n)&&(this.#S=!0);this.#A=s.type==="delegation_started",!this.#c&&!this.#q()&&this.#t(this.#A?"processing":"listening");break}case"delegation_cancelled":this.#J(String(s.delegationId),s.reason!=="session_ending"&&s.reason!=="provider_cancelled");break;case"warning":console.warn(`[Persona voice] ${s.code}: ${s.message??""}`,this.#V??""),s.delegationId&&(s.code==="UNKNOWN_DELEGATION"||s.code==="LATE_RESULT_LIMIT")&&this.#J(String(s.delegationId));break;case"audio_clear":this.#Q(),this.#k=0,this.#t("listening");break;case"transcript_interim":this.#t("listening"),this.#H("user",s.text??"",!1);break;case"transcript_final":{let n=s.role==="assistant"?"assistant":"user";this.#t(n==="user"?"processing":"speaking"),this.#H(n,s.text??"",!0);break}case"audio_end":this.#n?this.#n.markStreamEnd():(this.#c=!1,this.#t("listening"));break;case"metrics":this.#de({llmMs:s.llm_ms,ttsMs:s.tts_ms,firstAudioMs:s.first_audio_ms,totalMs:s.total_ms});break;case"error":this.#P(new Error(s.error||"Voice error")),this.#t("error");break}}async#oe(){let e=this.bridge?.getHistory()??[],t="";try{let s=this.config?.callContext;t=String((typeof s=="function"?await s():s)??"")}catch{}return _e(e,t)}#Y(e){let t=this.#E;if(!t)return;this.#E=null;let s=!1,n=t.then(r=>{let d=this.#e;s||!r||e!==this.#s||d?.readyState!==WebSocket.OPEN||d.send(JSON.stringify({type:"context",text:r}))});this.#R={done:n,drop:()=>s=!0}}async#re(){let e=this.#R;if(!e)return;let t,s=await Promise.race([e.done.then(()=>!1),new Promise(n=>{t=setTimeout(()=>n(!0),Me)})]);clearTimeout(t),s&&e.drop()}#J(e,t=!1){this.#T.add(e),this.bridge?.dropDelegation?.(e,t),this.#b.get(e)?.abort()}#ae(e,t,s){let n=this.bridge,r=(d,l,a)=>{let o=this.#e;return s!==this.#s||o?.readyState!==WebSocket.OPEN||this.#T.has(e)?!1:(o.send(JSON.stringify({type:d,delegationId:e,status:l,text:a})),!0)};this.#L=this.#L.then(async()=>{if(s!==this.#s||this.#T.has(e))return;let d=await n.runDelegatedTurn(t).catch(()=>({status:"failed",text:""}));await this.#re();let l=!!d.followUp,a=l&&this.#y.has("delegation_update");if(!r(a?"delegation_update":"delegation_result",l&&!a?"completed":d.status,d.text)||(d.status!=="failed"&&this.#D.add(e),!d.followUp))return;let c=new AbortController;this.#b.set(e,c),d.followUp({signal:c.signal,approvalTimeoutMs:this.config?.approvalTimeoutMs&&Math.min(this.config.approvalTimeoutMs,Te),readBack:a,onUpdate:u=>r("delegation_update","pending_approval",u)}).then(u=>{this.#b.delete(e),a&&u&&!c.signal.aborted&&r("delegation_result",u.status,u.text)})})}#ce(e,t){if(t!==this.#s||!this.#n||this.#q())return;let s=Re(e);s.length!==0&&(this.#c||(this.#c=!0,this.#t("speaking")),this.#j(),this.#n.enqueue(s),this.#l&&this.#le(s.length))}#q(){return Date.now()<this.#k}#j(){if(this.#v||!this.#n)return;this.#v=!0;let e=this.#s;this.#n.onFinished(()=>{e===this.#s&&(this.#v=!1,this.#Z())})}#le(e){let t=Date.now();this.#h=Math.max(t,this.#h)+e/48,clearTimeout(this.#M),this.#M=setTimeout(()=>this.#Z(),this.#h-t+Ae)}#Z(){this.#c=!1,this.#h=0,this.#e&&this.#e.readyState===WebSocket.OPEN&&this.#t(this.#A?"processing":"listening")}#Q(){this.#n?.flush(),this.#v=!1,this.#j(),this.#c=!1,this.#h=0,clearTimeout(this.#M)}#p(){this.#s+=1,this.#g=!1,this.#c=!1,this.#I(),this.#l=!1,this.#A=!1,this.#C=!1,this.#y=new Set;for(let e of this.#b.values())e.abort();if(this.#b.clear(),this.#T.clear(),this.#V=void 0,this.#_=!1,this.#E=null,this.#R=null,this.#L=Promise.resolve(),this.#D.clear(),this.#S=!1,this.#O.clear(),this.#N.clear(),this.#v=!1,this.#k=0,this.#h=0,clearTimeout(this.#M),this.#f=[],this.#m=0,this.#a&&(this.#a.onaudioprocess=null,this.#a.disconnect(),this.#a=null),this.#r&&(this.#r.disconnect(),this.#r=null),this.#i&&(this.#i.getTracks().forEach(e=>e.stop()),this.#i=null),this.#d&&(this.#d.close().catch(()=>{}),this.#d=null),this.#n&&(this.#n.destroy(),this.#n=null),this.#e){this.#w=!0;try{this.#e.close(1e3,"client ended call")}catch{}this.#e=null}}onResult(e){this.#z.push(e)}onError(e){this.#B.push(e)}onStatusChange(e){this.#W.push(e)}onTranscript(e){this.#U.push(e)}onMetrics(e){this.#x.push(e)}#t(e){this.#W.forEach(t=>t(e))}#P(e){this.#B.forEach(t=>t(e))}#H(e,t,s,n){this.#U.forEach(r=>r(e,t,s,n))}#de(e){this.#x.forEach(t=>t(e))}};var C=class{constructor(e={}){this.config=e;this.type="browser";this.#e=null;this.#d=[];this.#u=[];this.#i=[];this.#r=!1;this.#a=typeof window<"u"?window:void 0}#e;#d;#u;#i;#r;#a;async connect(){this.#i.forEach(e=>e("connected"))}async startListening(){try{if(this.#r)throw new Error("Already listening");if(!this.#a)throw new Error("Window object not available");let e=this.#a.SpeechRecognition||this.#a.webkitSpeechRecognition;if(!e)throw new Error("Browser speech recognition not supported");this.#e=new e,this.#e.lang=this.config?.language||"en-US",this.#e.continuous=this.config?.continuous||!1,this.#e.interimResults=!0,this.#e.onresult=t=>{let s=Array.from(t.results).map(r=>r[0]).map(r=>r.transcript).join(""),n=t.results[t.results.length-1].isFinal;this.#d.forEach(r=>r({text:s,confidence:n?.8:.5,provider:"browser"})),n&&!this.config?.continuous&&this.stopListening()},this.#e.onerror=t=>{this.#u.forEach(s=>s(new Error(t.error))),this.#i.forEach(s=>s("error"))},this.#e.onstart=()=>{this.#r=!0,this.#i.forEach(t=>t("listening"))},this.#e.onend=()=>{this.#r=!1,this.#i.forEach(t=>t("idle"))},this.#e.start()}catch(e){throw this.#u.forEach(t=>t(e)),this.#i.forEach(t=>t("error")),e}}async stopListening(){this.#e&&(this.#e.stop(),this.#e=null),this.#r=!1,this.#i.forEach(e=>e("idle"))}onResult(e){this.#d.push(e)}onError(e){this.#u.push(e)}onStatusChange(e){this.#i.push(e)}async disconnect(){await this.stopListening(),this.#i.forEach(e=>e("disconnected"))}static isSupported(){return"SpeechRecognition"in window||"webkitSpeechRecognition"in window}};function _(i){switch(i.type){case"runtype":if(!i.runtype)throw new Error("Runtype voice provider requires configuration");return new P(i.runtype);case"browser":if(!C.isSupported())throw new Error("Browser speech recognition not supported");return new C(i.browser||{});case"custom":{let e=i.custom;if(!e)throw new Error("Custom voice provider requires a `custom` provider instance or factory");let t=typeof e=="function"?e():e;if(!t||typeof t.startListening!="function")throw new Error("Custom voice provider `custom` must be a VoiceProvider (or a factory returning one)");return t}default:throw new Error(`Unknown voice provider type: ${i.type}`)}}function Q(i){if(i?.type==="custom"&&i.custom)return _({type:"custom",custom:i.custom});if(i?.type==="runtype"&&i.runtype)return _({type:"runtype",runtype:i.runtype});if(C.isSupported())return _({type:"browser",browser:i?.browser||{language:"en-US"}});throw new Error("No supported voice providers available")}function Oe(i){try{return Q(i),!0}catch{return!1}}var ee=i=>i.toLowerCase().replace(/[^\p{L}\p{N}]+/gu,""),$=class{constructor(e){this.host=e;this.turns=new Map;this.latestId=null;this.cancelled=new Set}apply(e,t,s,n,r,d){let l=this.host;t=t.trim();let a=this.turns.get(n);if(a||(a={},this.turns.set(n,a),this.latestId=n),e==="user"){a.userFinal=s;let o=a.userId?l.find(a.userId):void 0;if(o){let c=a.userStartMs===void 0&&r!==void 0;c&&(a.userStartMs=r),l.upsert({...o,content:t,voiceProcessing:!s,...c&&this.placeBefore(this.startedAfter(r))})}else{a.userStartMs=r;let c=a.assistantId?l.find(a.assistantId):void 0;a.userId=l.inject({role:"user",content:t,streaming:!1,voiceProcessing:!s,voiceCaption:d,...this.placeBefore(this.startedAfter(r)??c)}).id}}else if(!this.cancelled.has(n)){a.assistantFinal=s;let o=a.assistantId?l.find(a.assistantId):void 0;if(o){let c=a.assistantStartMs===void 0&&r!==void 0;c&&(a.assistantStartMs=r),l.upsert({...o,content:t,streaming:!s,voiceProcessing:!s,...c&&this.placeBefore(this.startedAfter(r))})}else t&&(a.assistantStartMs=r,a.assistantId=l.inject({role:"assistant",content:t,streaming:!s,voiceProcessing:!s,voiceCaption:d,...this.placeBefore(this.startedAfter(r))}).id);s&&a.assistantId&&l.markSpoken(a.assistantId)}this.sync()}claimUserTurn(e,t=[]){let s=t[t.length-1];for(let r of t)r!==s&&(this.turns.has(r)||this.turns.set(r,{}),this.turns.get(r).claimed=!0);let n;if(s){if(n=this.turns.get(s),n||(n={},this.turns.set(s,n)),n.claimed)return null;(!n.userId||!this.host.find(n.userId))&&(n.userFinal=!0,n.userId=this.host.inject({role:"user",content:e.trim()}).id)}else{let r=ee(e),d=[...this.turns.values()].reverse().flatMap(l=>{let a=l.userId&&!l.claimed?this.host.find(l.userId):void 0;return a?[{turn:l,text:ee(a.content)}]:[]}).filter(({text:l})=>l&&r);n=(d.find(({text:l})=>l===r)??d.find(({text:l})=>l.startsWith(r)||r.startsWith(l)))?.turn}return n?(n.claimed=!0,n.userId):null}cancel(){let e=this.inFlight();if(e.length===0){let t=this.awaiting();if(!t)return;e=[t]}for(let t of e)this.cancelled.add(t);this.closeReplies(e),this.sync()}fail(e){let t=this.awaiting(),s=this.inFlight();if(!t&&s.length===0)return;for(let d of s)this.turns.get(d).assistantFinal=!0;this.closeReplies(s);let n=this.host.inject({role:"assistant",content:e,streaming:!1,voiceProcessing:!1}),r=t?this.turns.get(t):void 0;r&&(r.assistantId=n.id,r.assistantFinal=!0),this.sync()}settle(){if(this.turns.size===0)return;let e=this.pending(),t=new Set;for(let s of this.turns.values())s.userId&&t.add(s.userId),s.assistantId&&(t.add(s.assistantId),this.host.markSpoken(s.assistantId));this.turns.clear(),this.latestId=null,this.host.settle(t),e&&this.host.setStreaming(!1)}startedAfter(e){if(e===void 0)return;let t;for(let s of this.turns.values())for(let[n,r]of[[s.userId,s.userStartMs],[s.assistantId,s.assistantStartMs]])n&&r!==void 0&&r>e&&(!t||r<t.start)&&(t={start:r,id:n});return t&&this.host.find(t.id)}placeBefore(e){if(!e)return;let t=e.sequence??0,s=t-1;for(let n of this.turns.values())for(let r of[n.userId,n.assistantId]){let d=r?this.host.find(r):void 0,l=d?.sequence;d?.createdAt===e.createdAt&&l!==void 0&&l>s&&l<t&&(s=l)}return{createdAt:e.createdAt,sequence:(s+t)/2}}closeReplies(e){let t=new Set;for(let s of e){let n=this.turns.get(s)?.assistantId;n&&(t.add(n),this.host.markSpoken(n))}t.size>0&&this.host.settle(t)}inFlight(){let e=[];for(let[t,s]of this.turns)s.assistantId&&!s.assistantFinal&&!this.cancelled.has(t)&&e.push(t);return e}awaiting(){let e=this.latestId;if(!e)return null;let t=this.turns.get(e);return t?.userFinal&&!t.assistantId&&!t.claimed&&!this.cancelled.has(e)?e:null}pending(){return this.awaiting()!==null||this.inFlight().length>0}sync(){this.host.setStreaming(this.pending())}};var Ne="I need your answer in the chat before I can continue.";var te="no|nope|never ?mind|decline(?: it| that)?|cancel(?: it| that| this)?(?: (?:the|my|that|this) (?:[a-z]+ )?(?:order|request))?|don'?t (?:do|place) (?:it|that|the (?:[a-z]+ )?order)",Be=new RegExp(`^(?:${te})(?: (?:${te}))?(?: thanks| thank you)?$`),We=i=>Be.test(i.toLowerCase().replace(/[^a-z' ]+/g," ").replace(/\s+/g," ").trim()),se="Briefly tell the user what you're about to do and ask them to approve or decline it in the chat. Don't claim it's done.",ie=()=>new Promise(i=>setTimeout(i,50)),q=(i,e)=>i.length>e?`${i.slice(0,e-1).trimEnd()}\u2026`:i,E=i=>i.replace(/^webmcp:/,"").replace(/([a-z\d])([A-Z])/g,"$1 $2").replace(/[_\-.]+/g," ").trim().toLowerCase();function j(i,e=0){if(i==null||i==="")return"";if(typeof i!="object")return String(i);if(e>2)return"";if(Array.isArray(i))return i.map(r=>j(r,e+1)).filter(Boolean).join(", ");let t=i,s=t.quantity??t.qty??t.count,n=t.name??t.item??t.product??t.title;return s!=null&&n!=null&&typeof n!="object"?`${s} ${n}`:Object.entries(t).filter(([r])=>!r.startsWith("_")).map(([r,d])=>{let l=j(d,e+1);return l&&(e?`${E(r)} ${l}`:`${E(r)}: ${l}`)}).filter(Boolean).join(e?", ":"; ")}function ne(i){let e=i.length>1?"These actions need the user's approval in the chat before they happen:":"This action needs the user's approval in the chat before it happens:",t=1e3-e.length-se.length-2,s=i.map(n=>{let r=[E(n.toolName)];n.description&&r.push(`(${q(n.description.trim(),160)})`);let d=j(n.parameters);return d&&r.push(`with ${d}`),n.reason&&r.push(`because: ${q(n.reason.trim(),160)}`),q(`- ${r.join(" ")}`,Math.floor(t/i.length)-1)});return`${e}
|
|
6
|
-
${s.join(`
|
|
7
|
-
`)}
|
|
8
|
-
|
|
9
|
-
${se}`}var R=i=>i.variant==="approval"&&i.approval?.status==="pending";function Ue(i){let e=null,t=null,s=()=>i.track(e??t),n=[],r=new Map,d=new Map,l=(g,m)=>{r.set(g,[...r.get(g)??[],m]),r.size>32&&r.delete(r.keys().next().value)},a=g=>i.messages().find(m=>m.id===g),o=g=>g.approvals.filter(m=>a(m)?.approval?.status==="pending"),c=g=>E(a(g)?.approval?.toolName??""),u=(g,m,b)=>{g.outcome={status:m,text:b};for(let S of o(g))i.decide(S)},p=g=>g.flatMap(m=>i.messages().find(b=>b.id===m)??[]).filter(m=>!m.variant&&m.content.trim()).map(m=>m.content.trim()).join(`
|
|
10
|
-
|
|
11
|
-
`),f=async g=>{do await ie();while(i.busy()&&!(g&&(i.parked()||g.failed)))};return{getHistory:()=>i.messages().flatMap(g=>(g.role==="user"||g.role==="assistant")&&!g.variant&&!g.voiceProcessing&&!g.voiceCaption&&g.content?[{role:g.role,content:g.content}]:[]),dropDelegation(g,m){d.set(g,!!m),d.size>32&&d.delete(d.keys().next().value);let b=m&&n.find(S=>S.delegationId===g);b&&!b.outcome&&u(b,"timeout","");for(let S of r.get(g)??[])S.dropped=!0,S.ids.length&&i.unspoken(S.ids)},async runDelegatedTurn({delegationId:g,userText:m,userUtteranceIds:b}){let S=n.filter(h=>o(h).length),I=S.flatMap(o);if(I.length===1&&We(m)){let h=c(I[0]);return u(S[0],"denied",""),{status:"denied",text:`Okay, I cancelled the ${h} request. Nothing was done.`}}let H=i.parked()&&i.messages().every(h=>!R(h)||I.includes(h.id));i.busy()&&!H&&await f();let oe=new Set(i.messages().map(h=>h.id)),L=()=>i.messages().filter(h=>!oe.has(h.id)&&h.variant==="approval"),A={ids:[],failed:!1};l(g,A),e=A,s();try{await i.send(m,i.claim(m,b)??void 0),await f(A)}catch{A.failed=!0}finally{e=null,s()}let re=A.ids.flatMap(h=>i.messages().find(y=>y.id===h)??[]),T=p(A.ids),V=A.failed?[]:L().filter(R),ae=new Set(V.map(h=>E(h.approval.toolName)));for(let h of S){if(h.outcome)continue;for(let w of o(h).filter(D=>ae.has(c(D))))h.replaced.add(w),i.decide(w);let y=c(h.approvals[0]);h.approvals.every(w=>h.replaced.has(w))?h.outcome={status:"cancelled",text:`The earlier ${y} request was replaced by the new one; it was not done.`}:H&&h.approvals.every(w=>a(w)?.approval?.status==="denied")&&(h.outcome={status:"cancelled",text:`The earlier ${y} request was replaced by the new one; it was not done.`})}if(V.length){let h=new Set(V.map(w=>w.id)),y={delegationId:g,approvals:[...h],at:Date.now(),replaced:new Set};return n.push(y),d.get(g)&&u(y,"timeout",""),T=`${T}
|
|
12
|
-
|
|
13
|
-
${ne(V.map(w=>w.approval))}`.trim(),{status:"pending_approval",text:T,followUp:async({signal:w,approvalTimeoutMs:D=3e5,readBack:F=!0,onUpdate:ce})=>{let k={ids:[],failed:!1};r.delete(g),l(g,k);let z=()=>L().filter(v=>h.has(v.id)||k.ids.includes(v.id)),le=()=>L().filter(v=>R(v)&&!h.has(v.id)&&!n.some(M=>M.approvals.includes(v.id))&&(k.ids.includes(v.id)||v.approval?.toolType==="webmcp"&&t===k)),X=()=>{t=k,s()};F&&X();try{for(;!w.aborted&&!y.outcome&&(e||i.busy()||z().some(R));){F&&t!==k&&!o(y).length&&X();let v=le();if(v.length){for(let M of v)h.add(M.id),y.approvals.push(M.id);y.at=Date.now(),ce?.(ne(v.map(M=>M.approval)))}if(Date.now()-y.at>=D&&o(y).length&&(!i.busy()||i.parked())){u(y,"timeout",`That ${c(y.approvals[0])} request expired, so nothing was done.`);break}await ie()}}finally{t===k&&(t=null,s()),n.splice(n.indexOf(y),1)}if(w.aborted)return null;if(y.outcome)return y.outcome;if(k.failed)return{status:"failed",text:`The ${c(y.approvals[0])} request didn't complete.`};let G=z(),O=v=>G.filter(v).map(M=>E(M.approval.toolName)).join(", "),N=G.some(v=>v.approval?.status==="approved"),B=O(v=>v.approval?.status==="denied"&&!y.replaced.has(v.id)),W=O(v=>y.replaced.has(v.id)),U=O(v=>v.approval?.status==="timeout"),de=[N?p(k.ids):"",B&&`The user declined the ${B} request in the chat, so ${N?"that part was not done":"nothing was done"}.`,W&&`The earlier ${W} request was replaced by the new one; it was not done.`,U&&`The ${U} request timed out, so it was not done.`].filter(Boolean).join(`
|
|
14
|
-
|
|
15
|
-
`);return{status:N?"completed":B?"denied":W?"cancelled":U?"timeout":"completed",text:de}}}}return!A.failed&&i.parked()||re.some(h=>h.agentMetadata?.awaitingLocalTool&&!h.agentMetadata.askUserQuestionAnswered&&!h.toolCall?.name?.startsWith("webmcp:"))?{status:A.failed?"failed":"completed",text:`${T}
|
|
16
|
-
|
|
17
|
-
${Ne}`.trim()}:{status:!A.failed&&T?"completed":"failed",text:T}}}}var xe=(i,e,t,s)=>{let n=i.session;try{i.keyed=t?new t({find:o=>n.messages.find(c=>c.id===o),inject:o=>n.injectMessage(o),upsert:o=>n.upsertMessage(o),settle:o=>{n.messages=n.messages.map(c=>o.has(c.id)?{...c,streaming:!1,voiceProcessing:!1}:c),n.callbacks.onMessagesChanged([...n.messages])},setStreaming:o=>{(o||!i.busy())&&i.setStreaming(o)},markSpoken:o=>{n.ttsSpokenMessageIds.add(o)}}):null,s&&e.setSessionBridge&&e.setSessionBridge(s({messages:()=>n.messages,busy:()=>i.busy(),parked:()=>n.webMcpApprovalResolvers.size>0,claim:(o,c)=>i.keyed?.claimUserTurn(o,c)??null,send:(o,c)=>n.sendMessage(o,{viaVoice:!0,voiceTurn:{userMessageId:c}}),track:o=>{i.setDelegation(o)},unspoken:o=>{for(let c of o)n.ttsSpokenMessageIds.delete(c);n.streaming||n.speakLatestAssistantMessage(o)},decide:o=>{if(n.webMcpApprovalResolvers.has(o))return n.resolveWebMcpApproval(o,"denied");let c=n.messages.find(u=>u.id===o)?.approval;c?.status==="pending"&&n.resolveApproval(c,"denied")}}));let r=i.generation(),d=()=>i.provider()===e&&r===i.generation(),a=(n.config.voiceRecognition??{}).processingErrorText??"Voice processing failed. Please try again.";e.onResult(o=>{d()&&o.provider!=="runtype"&&o.text&&o.text.trim()&&n.sendMessage(o.text,{viaVoice:!0})}),e.onTranscript&&e.onTranscript((o,c,u,p)=>{if(d()){if(p?.turnId){i.keyed?.apply(o,c,u,p.turnId,p.startMs,p.caption);return}if(o==="user"){if(i.pendingUser)n.upsertMessage({id:i.pendingUser,role:"user",content:c,createdAt:new Date().toISOString(),streaming:!1,voiceProcessing:!u});else{let f=n.injectMessage({role:"user",content:c,streaming:!1,voiceProcessing:!u});i.pendingUser=f.id}if(u){i.turns().start(p?.turnId),i.pendingUser=null;let f=n.injectMessage({role:"assistant",content:"",streaming:!0,voiceProcessing:!0});i.pendingAssistant=f.id,i.setStreaming(!0)}}else{if(!i.turns().accepts(p?.turnId))return;if(i.pendingAssistant)n.upsertMessage({id:i.pendingAssistant,role:"assistant",content:c,createdAt:new Date().toISOString(),streaming:!u,voiceProcessing:!u});else{let f=n.injectMessage({role:"assistant",content:c,streaming:!u,voiceProcessing:!u});i.pendingAssistant=f.id}u&&(i.pendingAssistant&&n.ttsSpokenMessageIds.add(i.pendingAssistant),i.setStreaming(!1),i.pendingAssistant=null)}}}),e.onLevel&&e.onLevel(o=>{d()&&i.setLevel(Number.isFinite(o)?Math.max(0,Math.min(1,o)):0)}),e.onMetrics&&e.onMetrics(o=>{d()&&n.config.voiceRecognition?.onMetrics?.(o)}),e.onError(o=>{d()&&(console.error("Voice error:",o),i.pendingAssistant&&(n.upsertMessage({id:i.pendingAssistant,role:"assistant",content:a,createdAt:new Date().toISOString(),streaming:!1,voiceProcessing:!1}),i.setStreaming(!1),i.pendingUser=null,i.pendingAssistant=null),i.keyed?.fail(a))}),e.onStatusChange(o=>{d()&&(i.setStatus(o),(o==="listening"||o==="idle"||o==="disconnected")&&i.settlePending(o!=="listening"),(o==="idle"||o==="disconnected")&&i.keyed?.settle(),n.callbacks.onVoiceStatusChanged?.(o))}),e.connect()}catch(r){console.error("Failed to setup voice:",r)}};var $e=i=>{let e=a=>{if(i.original)return;let o=i.config().voiceRecognition??{};i.original={backgroundColor:a.style.backgroundColor,color:a.style.color,borderColor:a.style.borderColor,iconName:o.iconName??"mic",iconSize:parseFloat(o.iconSize??"")||i.iconFallbackPx}},t=(a,o,c)=>{let u=a.querySelector("svg");u&&u.remove();let p=i.original?.iconSize??(parseFloat(i.config().voiceRecognition?.iconSize??"")||i.iconFallbackPx),f=i.renderIcon(o,p,c,1.5);f&&a.appendChild(f)},s=a=>{a.classList.remove("persona-voice-recording","persona-voice-processing","persona-voice-speaking")};return{recording:()=>{let a=i.mic();if(!a)return;e(a);let o=i.config().voiceRecognition??{},c=o.recordingBackgroundColor,u=o.recordingIconColor,p=o.recordingBorderColor;if(s(a),a.classList.add("persona-voice-recording"),i.setMicState("recording"),a.style.backgroundColor=c??"var(--persona-voice-recording-bg, #ef4444)",a.style.color=u??"var(--persona-voice-recording-indicator, #ffffff)",u){let f=a.querySelector("svg");f&&f.setAttribute("stroke",u)}p&&(a.style.borderColor=p),a.setAttribute("aria-label","Stop voice recognition")},processing:()=>{let a=i.mic();if(!a)return;e(a);let o=i.config().voiceRecognition??{},c=i.session().getVoiceInterruptionMode(),u=i.original,p=o.processingIconName??"loader",f=o.processingIconColor??u?.color??"",g=o.processingBackgroundColor??u?.backgroundColor??"",m=o.processingBorderColor??u?.borderColor??"";s(a),a.classList.add("persona-voice-processing"),i.setMicState("processing"),a.style.backgroundColor=g,a.style.borderColor=m;let b=f||"currentColor";a.style.color=b,t(a,p,b),a.setAttribute("aria-label","Processing voice input"),c==="none"&&(a.style.cursor="default")},speaking:()=>{let a=i.mic();if(!a)return;e(a);let o=i.config().voiceRecognition??{},c=i.session().getVoiceInterruptionMode(),u=i.original,p=c==="cancel"?"square":c==="barge-in"?"mic":"volume-2",f=o.speakingIconName??p,g=o.speakingIconColor??(c==="barge-in"?o.recordingIconColor??u?.color??"":u?.color??""),m=o.speakingBackgroundColor??(c==="barge-in"?o.recordingBackgroundColor??"var(--persona-voice-recording-bg, #ef4444)":u?.backgroundColor??""),b=o.speakingBorderColor??(c==="barge-in"?o.recordingBorderColor??"":u?.borderColor??"");s(a),a.classList.add("persona-voice-speaking"),i.setMicState("speaking"),a.style.backgroundColor=m,a.style.borderColor=b;let S=g||"currentColor";a.style.color=S,t(a,f,S);let I=c==="cancel"?"Stop playback and re-record":c==="barge-in"?"Speak to interrupt":"Agent is speaking";a.setAttribute("aria-label",I),c==="none"&&(a.style.cursor="default"),c==="barge-in"&&a.classList.add("persona-voice-recording")},reset:()=>{let a=i.mic();if(!a)return;s(a),i.setMicState("idle");let o=i.original;o&&(a.style.backgroundColor=o.backgroundColor??"",a.style.color=o.color??"",a.style.borderColor=o.borderColor??"",t(a,o.iconName,o.color||"currentColor"),i.original=null),a.style.cursor="",a.setAttribute("aria-label","Start voice recognition")}}};export{C as BrowserVoiceProvider,$ as KeyedVoiceTranscript,P as RuntypeVoiceProvider,Q as createBestAvailableVoiceProvider,$e as createMicStateStyles,_ as createVoiceProvider,Ue as createVoiceSessionBridge,Oe as isVoiceSupported,xe as wireSessionVoice};
|