@runtypelabs/persona 4.26.1 → 4.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/approval-ui.d.cts +5 -0
  2. package/dist/approval-ui.d.ts +5 -0
  3. package/dist/artifacts-ui.d.cts +5 -0
  4. package/dist/artifacts-ui.d.ts +5 -0
  5. package/dist/chunk-3255TNY7.js +1 -0
  6. package/dist/{client-history-GOZWBDA5.js → client-history-HTRKHBAF.js} +1 -1
  7. package/dist/client-history.js +1 -1
  8. package/dist/client-stream-N22JHTLE.js +4 -0
  9. package/dist/client-stream.js +3 -3
  10. package/dist/codegen.cjs +1 -1
  11. package/dist/codegen.js +1 -1
  12. package/dist/context-mentions.d.cts +5 -0
  13. package/dist/context-mentions.d.ts +5 -0
  14. package/dist/event-stream-view.d.cts +5 -0
  15. package/dist/event-stream-view.d.ts +5 -0
  16. package/dist/forms-ui.d.cts +7 -0
  17. package/dist/forms-ui.d.ts +7 -0
  18. package/dist/index.cjs +57 -57
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +7 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.global.js +1 -1
  23. package/dist/index.global.js.map +1 -1
  24. package/dist/index.js +58 -58
  25. package/dist/index.js.map +1 -1
  26. package/dist/session-reconnect.d.cts +5 -0
  27. package/dist/session-reconnect.d.ts +5 -0
  28. package/dist/smart-dom-reader.d.cts +5 -0
  29. package/dist/smart-dom-reader.d.ts +5 -0
  30. package/dist/theme-editor-preview.cjs +59 -59
  31. package/dist/theme-editor-preview.d.cts +5 -0
  32. package/dist/theme-editor-preview.d.ts +5 -0
  33. package/dist/theme-editor-preview.js +31 -31
  34. package/dist/theme-editor.d.cts +5 -0
  35. package/dist/theme-editor.d.ts +5 -0
  36. package/dist/voice-runtime-RHRX4F3T.js +17 -0
  37. package/dist/voice-runtime.cjs +8 -8
  38. package/dist/voice-runtime.d.cts +9 -0
  39. package/dist/voice-runtime.d.ts +9 -0
  40. package/dist/voice-runtime.js +8 -8
  41. package/package.json +1 -1
  42. package/src/client-stream.ts +10 -3
  43. package/src/client.test.ts +20 -0
  44. package/src/session.ts +5 -0
  45. package/src/session.voice.test.ts +59 -3
  46. package/src/types.ts +5 -0
  47. package/src/ui.composer-defer-one.test.ts +17 -0
  48. package/src/ui.ts +49 -12
  49. package/src/voice/__fixtures__/voice-wire/server/session_end-provider-error.json +6 -0
  50. package/src/voice/__fixtures__/voice-wire/server/session_end-unknown-reason.json +4 -0
  51. package/src/voice/__fixtures__/voice-wire/server/session_end.json +5 -0
  52. package/src/voice/runtype-voice-provider.ts +112 -10
  53. package/src/voice/voice-delegation.ts +26 -10
  54. package/src/voice/voice.test.ts +176 -0
  55. package/dist/chunk-GKPCIQPB.js +0 -1
  56. package/dist/client-stream-42FOI3EC.js +0 -4
  57. package/dist/voice-runtime-XLU6YK2N.js +0 -17
@@ -99,6 +99,8 @@ const LEVEL_RMS_SCALE = 4;
99
99
  const RIFF_MAGIC = 0x52494646; // "RIFF"
100
100
  /** `prewarm()` warms at most once per this window per provider instance. */
101
101
  const PREWARM_THROTTLE_MS = 30_000;
102
+ /** How long a call start waits for a suspended capture context to run. */
103
+ const AUDIO_RESUME_TIMEOUT_MS = 1_000;
102
104
  /** How long `startListening()` waits on an attach still handshaking. */
103
105
  const ATTACH_HANDSHAKE_TIMEOUT_MS = 5_000;
104
106
  /** Attached-socket idle window: server default and clamp range. */
@@ -142,6 +144,17 @@ const VOICE_PROTOCOL = "runtype-browser-v1";
142
144
  /** `approvalTimeoutMs` cap: below core's delegation deadline (600 s, restarted by each update). */
143
145
  const APPROVAL_TIMEOUT_MAX_MS = 540_000;
144
146
  const DISCLOSURE_TEXT = "You're talking to an AI assistant. Voice is processed by OpenAI.";
147
+ /** Status-line text per `session_end.reason` (Amendment 6); unlisted reasons show nothing. */
148
+ const SESSION_END_TEXT = new Map([
149
+ ["idle_timeout", "Voice call ended after a quiet period."],
150
+ ["max_duration", "This voice call reached its time limit."],
151
+ ["provider_ended", "The voice session ended."],
152
+ ["quota", "Voice is unavailable right now."],
153
+ ["auth_expired", "Voice session expired. Tap the mic to reconnect."],
154
+ ]);
155
+ /** Reasons that get one automatic reconnect. */
156
+ const RECONNECT_REASONS = ["provider_error", "server_restart"];
157
+ const CONNECTION_LOST = "Voice connection lost.";
145
158
  /** Call-start context frame: total cap, host share, history window, per-message cap. */
146
159
  const CONTEXT_MAX_CHARS = 8000;
147
160
  const CONTEXT_HOST_MAX_CHARS = 4000;
@@ -271,6 +284,14 @@ export class RuntypeVoiceProvider implements VoiceProvider {
271
284
  #cancelledDelegations = new Set<string>();
272
285
  // session_config.callId: the server's id for this call, for logs.
273
286
  #callId: string | undefined;
287
+ // Amendment 6: why the server ended this call (its last frame), if it said.
288
+ #sessionEnd: { reason: string; message?: string } | null = null;
289
+ // Status-line text for the UI, read once (`takeNotice`).
290
+ #notice: string | null = null;
291
+ // One automatic reconnect per call the visitor started: pending, and spent.
292
+ #reconnectTimer: ReturnType<typeof setTimeout> | undefined;
293
+ #reconnected = false;
294
+ #autoReconnect = false;
274
295
  // Call-start context, built at session_config and held until the visitor's
275
296
  // first final transcript (sent earlier, the voice model tends to answer it).
276
297
  #contextSent = false;
@@ -467,6 +488,13 @@ export class RuntypeVoiceProvider implements VoiceProvider {
467
488
  /** Start the call: acquire mic, open the WS, stream PCM until hang-up. */
468
489
  async startListening(): Promise<void> {
469
490
  if (this.#callLive) return; // idempotent: a call is already live
491
+ // A call the visitor starts gets a fresh reconnect; the automatic one doesn't.
492
+ const auto = this.#autoReconnect;
493
+ if (!auto) {
494
+ this.#reconnected = false;
495
+ this.#notice = null;
496
+ }
497
+ this.#autoReconnect = false;
470
498
 
471
499
  const agentId = this.config?.agentId;
472
500
  const token = this.config?.clientToken;
@@ -499,10 +527,22 @@ export class RuntypeVoiceProvider implements VoiceProvider {
499
527
  const captureContext: AudioContext = new AudioCtx({
500
528
  sampleRate: CAPTURE_SAMPLE_RATE,
501
529
  });
530
+ this.#captureContext = captureContext;
502
531
  if (captureContext.state === "suspended") {
503
- await captureContext.resume().catch(() => {});
532
+ // Outside a click (the automatic reconnect, or a call restored after a
533
+ // page navigation) the autoplay policy can leave audio suspended and
534
+ // `resume()` pending forever: fail fast rather than hang or open a dead call.
535
+ await Promise.race([
536
+ captureContext.resume().catch(() => {}),
537
+ new Promise((resolve) => setTimeout(resolve, auto ? 500 : AUDIO_RESUME_TIMEOUT_MS)),
538
+ ]);
539
+ if (generation !== this.#callGeneration) return; // hung up meanwhile
540
+ if ((captureContext.state as string) !== "running") {
541
+ if (auto) throw new Error((this.#notice = `${CONNECTION_LOST} Tap the mic to reconnect.`));
542
+ // A restored call: the widget asks for a tap to resume.
543
+ throw new Error("Audio blocked until a tap");
544
+ }
504
545
  }
505
- this.#captureContext = captureContext;
506
546
 
507
547
  const engine = this.config?.createPlaybackEngine
508
548
  ? await this.config.createPlaybackEngine()
@@ -560,7 +600,8 @@ export class RuntypeVoiceProvider implements VoiceProvider {
560
600
  ws.onmessage = (event) => this.#handleMessage(event, generation);
561
601
 
562
602
  ws.onerror = () => {
563
- if (generation !== this.#callGeneration) return;
603
+ // After `session_end`, the close that follows reports the server's reason.
604
+ if (generation !== this.#callGeneration || this.#sessionEnd) return;
564
605
  this.#emitError(new Error("Voice connection failed"));
565
606
  this.#emitStatus("error");
566
607
  this.#cleanup();
@@ -572,17 +613,68 @@ export class RuntypeVoiceProvider implements VoiceProvider {
572
613
  return;
573
614
  }
574
615
  if (generation !== this.#callGeneration) return;
575
- if (evt.code !== 1000) {
576
- const codeMsg = evt.code ? ` (code ${evt.code})` : "";
577
- this.#emitError(new Error(`Voice connection closed${codeMsg}`));
578
- this.#emitStatus("error");
579
- } else {
580
- this.#emitStatus("idle");
616
+ // Why the server ended it: its `session_end`, or the attach idle close.
617
+ const end = this.#sessionEnd ?? (evt.code === 4408 ? { reason: "idle_timeout" } : null);
618
+ if (!end) {
619
+ // No reason given (a deploy, a dropped connection): as before.
620
+ if (evt.code !== 1000) {
621
+ const codeMsg = evt.code ? ` (code ${evt.code})` : "";
622
+ this.#emitError(new Error(`Voice connection closed${codeMsg}`));
623
+ this.#emitStatus("error");
624
+ } else {
625
+ this.#emitStatus("idle");
626
+ }
627
+ this.#cleanup();
628
+ return;
581
629
  }
582
- this.#cleanup();
630
+ this.#endCall(end.reason, end.message);
583
631
  };
584
632
  }
585
633
 
634
+ /**
635
+ * The server ended the call with a reason (`session_end`, Amendment 6): show
636
+ * its text, and reconnect once after a provider error or a restart. Unknown
637
+ * reasons are an ordinary end (`ended_by_server`).
638
+ */
639
+ #endCall(reason: string, message?: string): void {
640
+ const retry = RECONNECT_REASONS.includes(reason) && !this.#reconnected;
641
+ this.#cleanup();
642
+ if (retry) {
643
+ // Keep the call "live" while it waits, so the mic button hangs up (and cancels).
644
+ this.#reconnected = true;
645
+ this.#callLive = true;
646
+ this.#notice = reason === "provider_error" ? `${CONNECTION_LOST} Reconnecting…` : "Reconnecting…";
647
+ // A hang-up meanwhile runs #cleanup, which clears this timer.
648
+ this.#reconnectTimer = setTimeout(
649
+ () => {
650
+ this.#callLive = false;
651
+ this.#autoReconnect = true;
652
+ void this.startListening().catch(() => {});
653
+ },
654
+ 1000 + Math.random() * 1000,
655
+ );
656
+ this.#emitStatus("idle");
657
+ return;
658
+ }
659
+ if (RECONNECT_REASONS.includes(reason)) {
660
+ // The reconnect already ran: this end is the second failure.
661
+ this.#notice = CONNECTION_LOST;
662
+ this.#emitError(new Error(CONNECTION_LOST));
663
+ return this.#emitStatus("error");
664
+ }
665
+ // `error`: the preceding `error` frame already reported it.
666
+ if (reason === "error") return this.#emitStatus("error");
667
+ this.#notice = (reason === "quota" && message) || SESSION_END_TEXT.get(reason) || null;
668
+ this.#emitStatus("idle");
669
+ }
670
+
671
+ /** Status-line text for the call (why it ended, or that it is reconnecting), once. */
672
+ takeNotice(): string | null {
673
+ const notice = this.#notice;
674
+ this.#notice = null;
675
+ return notice;
676
+ }
677
+
586
678
  /** The AI-disclosure notice for a live speech-to-speech call (`disclosureText`; `false` hides it). */
587
679
  getDisclosure(): string | null {
588
680
  const text = this.config?.disclosureText;
@@ -872,6 +964,14 @@ export class RuntypeVoiceProvider implements VoiceProvider {
872
964
  this.#emitError(new Error(msg.error || "Voice error"));
873
965
  this.#emitStatus("error");
874
966
  break;
967
+
968
+ case "session_end":
969
+ // The server's last frame before it closes: the close acts on it.
970
+ this.#sessionEnd = {
971
+ reason: String(msg.reason),
972
+ ...(typeof msg.message === "string" && { message: msg.message }),
973
+ };
974
+ break;
875
975
  }
876
976
  }
877
977
 
@@ -1063,6 +1163,8 @@ export class RuntypeVoiceProvider implements VoiceProvider {
1063
1163
  this.#followUps.clear();
1064
1164
  this.#cancelledDelegations.clear();
1065
1165
  this.#callId = undefined;
1166
+ this.#sessionEnd = null;
1167
+ clearTimeout(this.#reconnectTimer);
1066
1168
  this.#contextSent = false;
1067
1169
  this.#pendingContext = null;
1068
1170
  this.#contextSend = null;
@@ -164,16 +164,32 @@ export function createVoiceSessionBridge(host: VoiceDelegationHost): VoiceSessio
164
164
  };
165
165
 
166
166
  return {
167
- getHistory: () =>
168
- host.messages().flatMap((m) =>
169
- (m.role === "user" || m.role === "assistant") &&
170
- !m.variant &&
171
- !m.voiceProcessing &&
172
- !m.voiceCaption &&
173
- m.content
174
- ? [{ role: m.role, content: m.content }]
175
- : [],
176
- ),
167
+ // Read once at call start, before this call has any captions, so the
168
+ // captions here are earlier calls' (e.g. on the previous page of a
169
+ // multi-page site): spoken conversation the new voice session never heard.
170
+ // An assistant caption followed by a chat answer before the next user turn
171
+ // is a delegated turn's spoken filler ("Let me check."): it would only use
172
+ // up a slot of the context's recent-message window.
173
+ getHistory: () => {
174
+ const settled = host.messages().filter(
175
+ (m) =>
176
+ (m.role === "user" || m.role === "assistant") &&
177
+ !m.variant &&
178
+ !m.voiceProcessing &&
179
+ !m.streaming &&
180
+ m.content,
181
+ );
182
+ let answered = false;
183
+ const kept: Array<{ role: "user" | "assistant"; content: string }> = [];
184
+ for (let i = settled.length - 1; i >= 0; i -= 1) {
185
+ const m = settled[i];
186
+ if (m.role === "user") answered = false;
187
+ else if (!m.voiceCaption) answered = true;
188
+ else if (answered) continue;
189
+ kept.unshift({ role: m.role as "user" | "assistant", content: m.content });
190
+ }
191
+ return kept;
192
+ },
177
193
 
178
194
  dropDelegation(delegationId, expired) {
179
195
  // Remembered for a turn still running: when it parks, the same rule applies.
@@ -1537,6 +1537,182 @@ describe('RuntypeVoiceProvider (realtime streaming)', () => {
1537
1537
  });
1538
1538
  });
1539
1539
  });
1540
+
1541
+ describe('session_end (Amendment 6)', () => {
1542
+ const fixture = (name: string) =>
1543
+ JSON.stringify(wireFixtures('server').find((f) => JSON.stringify(f).includes(name)) ?? {});
1544
+ /** A live full-duplex call that collects errors and statuses from here on. */
1545
+ const endableCall = async () => {
1546
+ const call = await startFullDuplexCall();
1547
+ const errors: string[] = [];
1548
+ call.provider.onError((e) => errors.push(e.message));
1549
+ return { ...call, errors };
1550
+ };
1551
+ const end = (ws: MockWebSocket, frame: Record<string, unknown> | string, code: number) => {
1552
+ ws.triggerMessage(typeof frame === 'string' ? frame : JSON.stringify({ type: 'session_end', ...frame }));
1553
+ ws.triggerClose(code);
1554
+ };
1555
+
1556
+ it.each([
1557
+ ['idle_timeout', 1000, 'Voice call ended after a quiet period.'],
1558
+ ['max_duration', 1000, 'This voice call reached its time limit.'],
1559
+ ['provider_ended', 1000, 'The voice session ended.'],
1560
+ ['quota', 1008, 'Voice is unavailable right now.'],
1561
+ ['auth_expired', 1008, 'Voice session expired. Tap the mic to reconnect.'], // reserved: no auto-reconnect
1562
+ ['ended_by_server', 1000, null],
1563
+ ['maintenance_window', 1000, null], // unknown: an ordinary server end
1564
+ ])('ends on %s with its status text, without an error', async (reason, code, text) => {
1565
+ const { ws, provider, statuses, errors } = await endableCall();
1566
+ end(ws, { reason }, code);
1567
+ expect(provider.takeNotice()).toBe(text);
1568
+ expect(provider.takeNotice()).toBeNull(); // read once
1569
+ expect(errors).toEqual([]);
1570
+ expect(statuses.at(-1)).toBe('idle');
1571
+ expect(provider.isBargeInActive()).toBe(false);
1572
+ await vi.advanceTimersByTimeAsync(5_000);
1573
+ expect(MockWebSocket.instances.at(-1)).toBe(ws); // no reconnect
1574
+ });
1575
+
1576
+ it('shows the quota message the server sent', async () => {
1577
+ const { ws, provider } = await endableCall();
1578
+ end(ws, { reason: 'quota', message: 'Your voice minutes are used up.' }, 1008);
1579
+ expect(provider.takeNotice()).toBe('Your voice minutes are used up.');
1580
+ });
1581
+
1582
+ it('takes the doc fixtures: max_duration, and an unknown reason as ended_by_server', async () => {
1583
+ const first = await endableCall();
1584
+ end(first.ws, fixture('max_duration'), 1000);
1585
+ expect(first.provider.takeNotice()).toBe('This voice call reached its time limit.');
1586
+ const second = await endableCall();
1587
+ end(second.ws, fixture('maintenance_window'), 1000);
1588
+ expect(second.provider.takeNotice()).toBeNull();
1589
+ expect(second.errors).toEqual([]);
1590
+ });
1591
+
1592
+ it('reports a fatal error once: the error frame, not the session_end after it', async () => {
1593
+ const { ws, provider, statuses, errors } = await endableCall();
1594
+ ws.triggerMessage(JSON.stringify({ type: 'error', error: 'Voice engine failed', fatal: true }));
1595
+ end(ws, { reason: 'error' }, 1011);
1596
+ expect(errors).toEqual(['Voice engine failed']);
1597
+ expect(statuses.at(-1)).toBe('error');
1598
+ expect(provider.takeNotice()).toBeNull();
1599
+ });
1600
+
1601
+ it('reconnects once after a provider error, 1-2 s later, and says so', async () => {
1602
+ const { ws, provider, statuses, errors } = await endableCall();
1603
+ end(ws, fixture('provider_error'), 1011);
1604
+ expect(provider.takeNotice()).toBe('Voice connection lost. Reconnecting…');
1605
+ expect(errors).toEqual([]);
1606
+ expect(statuses.at(-1)).toBe('idle');
1607
+ expect(provider.isBargeInActive()).toBe(true); // the mic button hangs up meanwhile
1608
+ await vi.advanceTimersByTimeAsync(999);
1609
+ expect(MockWebSocket.instances.at(-1)).toBe(ws);
1610
+ await vi.advanceTimersByTimeAsync(1_001);
1611
+ const second = lastWs();
1612
+ expect(second).not.toBe(ws);
1613
+ expect(second.protocols).toEqual(['runtype.bearer', 'ct_secret']);
1614
+
1615
+ // A second failure doesn't reconnect again.
1616
+ second.triggerOpen();
1617
+ end(second, { reason: 'provider_error' }, 1011);
1618
+ expect(provider.takeNotice()).toBe('Voice connection lost.');
1619
+ expect(errors).toEqual(['Voice connection lost.']);
1620
+ expect(statuses.at(-1)).toBe('error');
1621
+ await vi.advanceTimersByTimeAsync(5_000);
1622
+ expect(lastWs()).toBe(second);
1623
+ });
1624
+
1625
+ it('reconnects once on server_restart, saying "Reconnecting…"', async () => {
1626
+ const { ws, provider } = await endableCall();
1627
+ end(ws, { reason: 'server_restart' }, 1012);
1628
+ expect(provider.takeNotice()).toBe('Reconnecting…');
1629
+ await vi.advanceTimersByTimeAsync(2_000);
1630
+ expect(lastWs()).not.toBe(ws);
1631
+ });
1632
+
1633
+ it('never reconnects once the visitor hangs up, during the wait or before the close', async () => {
1634
+ const waiting = await endableCall();
1635
+ end(waiting.ws, { reason: 'provider_error' }, 1011);
1636
+ await waiting.provider.stopListening();
1637
+ await vi.advanceTimersByTimeAsync(5_000);
1638
+ expect(lastWs()).toBe(waiting.ws);
1639
+
1640
+ const hungUp = await endableCall();
1641
+ hungUp.ws.triggerMessage(JSON.stringify({ type: 'session_end', reason: 'provider_error' }));
1642
+ await hungUp.provider.stopListening(); // closes the socket itself
1643
+ await vi.advanceTimersByTimeAsync(5_000);
1644
+ expect(lastWs()).toBe(hungUp.ws);
1645
+ expect(hungUp.errors).toEqual([]);
1646
+ });
1647
+
1648
+ it('reconnects on the real failure order: error, audio_end, session_end, close 1011', async () => {
1649
+ const { ws, provider, statuses, errors } = await endableCall();
1650
+ ws.triggerMessage(JSON.stringify({ type: 'error', error: 'Voice engine failed', fatal: true }));
1651
+ ws.triggerMessage(JSON.stringify({ type: 'audio_end' }));
1652
+ end(ws, { reason: 'provider_error', retryable: true }, 1011);
1653
+ expect(errors).toEqual(['Voice engine failed']); // reported once, by the error frame
1654
+ expect(statuses.at(-1)).toBe('idle');
1655
+ expect(provider.takeNotice()).toBe('Voice connection lost. Reconnecting…');
1656
+ expect(provider.isBargeInActive()).toBe(true);
1657
+ await vi.advanceTimersByTimeAsync(2_000);
1658
+ expect(lastWs()).not.toBe(ws);
1659
+ });
1660
+
1661
+ it('still reconnects when a socket error comes between session_end and the close', async () => {
1662
+ const { ws, provider, errors } = await endableCall();
1663
+ ws.triggerMessage(JSON.stringify({ type: 'session_end', reason: 'provider_error' }));
1664
+ ws.triggerError();
1665
+ ws.triggerClose(1011);
1666
+ expect(errors).toEqual([]);
1667
+ expect(provider.takeNotice()).toBe('Voice connection lost. Reconnecting…');
1668
+ await vi.advanceTimersByTimeAsync(2_000);
1669
+ expect(lastWs()).not.toBe(ws);
1670
+ });
1671
+
1672
+ it('ends the automatic reconnect when audio stays suspended outside a click (iOS)', async () => {
1673
+ const { ws, provider, statuses, errors } = await endableCall();
1674
+ end(ws, { reason: 'server_restart' }, 1012);
1675
+ provider.takeNotice();
1676
+ class SuspendedAudioContext extends MockAudioContext {
1677
+ state = 'suspended';
1678
+ resume() {
1679
+ return new Promise<void>(() => {}); // iOS: pending until a gesture
1680
+ }
1681
+ }
1682
+ (globalThis as any).window.AudioContext = SuspendedAudioContext;
1683
+ try {
1684
+ await vi.advanceTimersByTimeAsync(2_500);
1685
+ } finally {
1686
+ (globalThis as any).window.AudioContext = MockAudioContext;
1687
+ }
1688
+ expect(lastWs()).toBe(ws); // no dead call opened
1689
+ expect(errors).toEqual(['Voice connection lost. Tap the mic to reconnect.']);
1690
+ expect(statuses.at(-1)).toBe('error');
1691
+ expect(provider.takeNotice()).toBe('Voice connection lost. Tap the mic to reconnect.');
1692
+ expect(provider.isBargeInActive()).toBe(false);
1693
+ });
1694
+
1695
+ it('treats the attach idle close (4408) as a quiet end, not an error', async () => {
1696
+ for (const withFrame of [false, true]) {
1697
+ const { ws, provider, statuses, errors } = await endableCall();
1698
+ if (withFrame) ws.triggerMessage(JSON.stringify({ type: 'session_end', reason: 'idle_timeout' }));
1699
+ ws.triggerClose(4408);
1700
+ expect(errors).toEqual([]);
1701
+ expect(statuses.at(-1)).toBe('idle');
1702
+ expect(provider.takeNotice()).toBe('Voice call ended after a quiet period.');
1703
+ }
1704
+ });
1705
+
1706
+ it('keeps an abnormal close without session_end exactly as before', async () => {
1707
+ const { ws, provider, statuses, errors } = await endableCall();
1708
+ ws.triggerClose(1006);
1709
+ expect(errors).toEqual(['Voice connection closed (code 1006)']);
1710
+ expect(statuses.at(-1)).toBe('error');
1711
+ expect(provider.takeNotice()).toBeNull();
1712
+ await vi.advanceTimersByTimeAsync(5_000);
1713
+ expect(lastWs()).toBe(ws);
1714
+ });
1715
+ });
1540
1716
  });
1541
1717
  });
1542
1718
 
@@ -1 +0,0 @@
1
- var t="4.26.1";var n=t;export{n as a};
@@ -1,4 +0,0 @@
1
- import{c as me,d as ye,e as Ie}from"./chunk-K6HKIP5K.js";import"./chunk-K7NA7G7J.js";import{d as we}from"./chunk-2ANTRKTV.js";import"./chunk-TR2H475E.js";import{e as de,p as z}from"./chunk-NQLDCJAP.js";import"./chunk-76IELU7F.js";async function Te(c,h,m){let u={"Content-Type":"application/json",...c.headers};c.getHeaders&&Object.assign(u,await c.getHeaders());let M={executionId:h.executionId,approvalId:h.approvalId,decision:m,streamResponse:!0},x=(k,p)=>fetch(`${c.clientApiBase()}${k}`,{method:"POST",headers:u,body:JSON.stringify({...p,...M})}),A=`/v1/agents/${h.agentId}/approve`;if(!c.isClientTokenMode())return x(A);let{sessionId:y}=await c.initSession(),S=await x("/v1/client/approve",{sessionId:y});if(S.status===404){let k=await S.clone().json().catch(()=>null);if(!k||k.error==="Not Found")return x(A)}return S}async function Se(c,h,m,u){let M=c.isClientTokenMode(),x=M?c.getClientApiUrl("resume"):`${c.config.apiUrl?.replace(/\/+$/,"")||we}/resume`,A,y=null;if(M){let p=await c.initSession();A=p.sessionId,p.durableRecovery?.enabled===!0&&(y=await c.readVisitorToken())}let S={"Content-Type":"application/json",...y?{"X-Visitor-Token":y}:{},...c.headers};c.getHeaders&&Object.assign(S,await c.getHeaders());let k={executionId:h,toolOutputs:m,streamResponse:u?.streamResponse??!0,...u?.after?{after:u.after}:{}};if(A&&(k.sessionId=A),M&&A){let p=[...Ie(c.config),...await(await c.getWebMcpBridge())?.snapshotForDispatch()??[]],{response:D,commit:ee}=await c.sendWithClientToolsDiff(A,p,d=>{let f={...k,...d};return c.debug&&console.debug("[AgentWidgetClient] client token resume",f),fetch(x,{method:"POST",headers:S,body:JSON.stringify(f),signal:u?.signal})},{emptyMeansReplace:!0});return D.ok&&ee(),D}return fetch(x,{method:"POST",headers:S,body:JSON.stringify(k),signal:u?.signal})}function ke(c){let h=c.toLowerCase(),u={"application/pdf":"pdf","application/json":"json","application/zip":"zip","text/plain":"txt","text/csv":"csv","text/markdown":"md"}[h];if(u)return`attachment.${u}`;let M=h.indexOf("/");if(M>0){let x=h.slice(M+1).split(";")[0]?.trim()??"";if(x&&x!=="octet-stream"&&/^[a-z0-9.+-]+$/i.test(x))return`attachment.${x}`}return"attachment"}var he=c=>c.startsWith("{")||c.startsWith("[")||c.startsWith("<");function We(c,h){if(!c)return h;let m=c.trim(),u=h.trim();if(m.length===0)return h;if(u.length===0)return c;let M=he(m);if(!he(u))return c;if(!M||u===m||u.startsWith(m))return h;let A=z(c);return z(h)!==null&&A===null?h:c}async function be(c,h,m,u,M,x,A){if(!c.parseSSEEvent)return!1;try{let y=await c.parseSSEEvent(h);if(y===null)return!1;let S=p=>{let D={id:`assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!0,variant:"assistant",sequence:x(),...p!==void 0&&{partId:p}};return u.current=D,M(D),D},k=p=>u.current?u.current:S(p);if(y.text!==void 0){y.partId!==void 0&&A.current!==null&&y.partId!==A.current&&(u.current&&(u.current.streaming=!1,M(u.current)),S(y.partId)),y.partId!==void 0&&(A.current=y.partId);let p=k(y.partId);y.partId!==void 0&&!p.partId&&(p.partId=y.partId),p.content+=y.text,M(p)}return y.done&&(u.current&&(u.current.streaming=!1,M(u.current)),A.current=null,m({type:"status",status:"idle"})),y.error&&(A.current=null,m({type:"error",error:new Error(y.error)})),!0}catch(y){return typeof console<"u"&&console.error("[AgentWidget] parseSSEEvent error:",y),!1}}async function Ee(c,h,m,u,M){let x=h.getReader(),A=new TextDecoder,y="",S=Date.now(),k=0,p=()=>S+k++,D=n=>{let e=n.reasoning?{...n.reasoning,chunks:[...n.reasoning.chunks]}:void 0,t=n.toolCall?{...n.toolCall,chunks:n.toolCall.chunks?[...n.toolCall.chunks]:void 0}:void 0,o=n.tools?n.tools.map(s=>({...s,chunks:s.chunks?[...s.chunks]:void 0})):void 0;return{...n,reasoning:e,toolCall:t,tools:o}},ee=n=>{if(n.role!=="assistant"||n.variant)return!0;let e=Array.isArray(n.contentParts)&&n.contentParts.length>0,t=typeof n.rawContent=="string"&&n.rawContent.trim()!=="";return typeof n.content=="string"&&n.content.trim()!==""||e||t||!!n.stopReason},d=n=>{ee(n)&&m({type:"message",message:D(n)})},f=null,U=null,O={current:null},Ce={current:null},F=null,E="",$=new Map,v=new Map,te=new Map,Q=new Map,ne=new Map,X=u,ce=!1,oe=()=>{if(f)return f;let n,e="",t=F;return!ce&&X?(n=X,ce=!0,e=M??""):X&&t?n=`${X}_${t}`:n=`assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`,f={id:n,role:"assistant",content:e,createdAt:new Date().toISOString(),streaming:!0,sequence:p()},d(f),f},se=n=>{let e=Q.get(n);if(e)return e;let t={id:`reason-${n}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!0,variant:"reasoning",sequence:p(),reasoning:{id:n,status:"streaming",chunks:[]}};return Q.set(n,t),d(t),t},H=new Set,re=new Map,ge=new Set,J=new Map,Ae=n=>{if(!n)return!1;let e=n.replace(/_+/g,"_").replace(/^_|_$/g,"");return e==="emit_artifact_markdown"||e==="emit_artifact_component"},L=n=>{let e=ne.get(n);if(e)return e;let t={id:`tool-${n}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!0,variant:"tool",sequence:p(),toolCall:{id:n,status:"pending"}};return ne.set(n,t),d(t),t},ue=n=>{if(typeof n=="number"&&Number.isFinite(n))return n;if(typeof n=="string"){let e=Number(n);if(!Number.isNaN(e)&&Number.isFinite(e))return e;let t=Date.parse(n);if(!Number.isNaN(t))return t}return Date.now()},ie=n=>{if(typeof n=="string")return n;if(n==null)return"";try{return JSON.stringify(n)}catch{return String(n)}},T=new Map,W=new Map,Me=(n,e)=>{let t=ie(e),o=W.get(n.id),s=We(o,t);n.rawContent=s;let r=T.get(n.id),i=I=>{let g=n.content??"";I.trim()!==""&&(g.trim().length===0||I.startsWith(g)||I.trimStart().startsWith(g.trim()))&&(n.content=I)},l=()=>{if(r){let I=r.close?.();I instanceof Promise&&I.catch(()=>{})}T.delete(n.id),W.delete(n.id),n.streaming=!1,d(n)};if(!r){i(t),l();return}let a=z(s);if(a!==null&&a.trim()!==""){i(a),l();return}let w=I=>{let g=typeof I=="string"?I:I?.text??null;if(g!==null&&g.trim()!=="")return g;let N=r.getExtractedText();return N!==null&&N.trim()!==""?N:t},C;try{C=r.processChunk(s)}catch{i(t),l();return}if(C instanceof Promise){C.then(I=>{i(w(I)),l()}).catch(()=>{i(t),l()});return}i(w(C)),l()},K=null,ae=(n,e,t,o)=>{n.rawContent=e,T.has(n.id)||T.set(n.id,c.createStreamParser());let s=T.get(n.id),r=e.trim().startsWith("{")||e.trim().startsWith("[");if(r&&W.set(n.id,e),s.__isPlainTextParser===!0){n.content=o!==void 0?e:n.content+t,W.delete(n.id),T.delete(n.id),n.rawContent=void 0,d(n);return}let l=s.processChunk(e);if(l instanceof Promise)l.then(a=>{let w=typeof a=="string"?a:a?.text??null;w!==null&&w.trim()!==""?(n.content=w,d(n)):!r&&!e.trim().startsWith("<")&&(n.content=o!==void 0?e:n.content+t,W.delete(n.id),T.delete(n.id),n.rawContent=void 0,d(n))}).catch(()=>{n.content=o!==void 0?e:n.content+t,W.delete(n.id),T.delete(n.id),n.rawContent=void 0,d(n)});else{let a=typeof l=="string"?l:l?.text??null;a!==null&&a.trim()!==""?(n.content=a,d(n)):!r&&!e.trim().startsWith("<")&&(n.content=o!==void 0?e:n.content+t,W.delete(n.id),T.delete(n.id),n.rawContent=void 0,d(n))}},V=(n,e)=>{let t=e??n.content;if(t==null||t===""){n.streaming=!1,d(n);return}let s=W.get(n.id)??ie(t);n.rawContent=s;let r=T.get(n.id),i=null,l=!1;if(r&&(i=r.getExtractedText(),i===null&&(i=z(s)),i===null)){let a=r.processChunk(s);a instanceof Promise?(l=!0,a.then(w=>{let C=typeof w=="string"?w:w?.text??null;C!==null&&(n.content=C,n.streaming=!1,T.delete(n.id),W.delete(n.id),d(n))}).catch(()=>{})):i=typeof a=="string"?a:a?.text??null}if(!l){i!==null&&i.trim()!==""?n.content=i:W.has(n.id)||(n.content=ie(t));let a=T.get(n.id);if(a){let w=a.close?.();w instanceof Promise&&w.catch(()=>{}),T.delete(n.id)}W.delete(n.id),n.streaming=!1,d(n)}},pe=(n,e,t)=>{let o=v.get(n);if(o)return o;let s={id:`nested-${e}-${n}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!0,sequence:p(),...t?{variant:t}:{},...t==="reasoning"?{reasoning:{id:n,status:"streaming",chunks:[]}}:{},agentMetadata:{parentToolId:e}};return v.set(n,s),d(s),s},G=[],le,Y=new Map,_=0,B="agent",fe=!1,Z=null,j=null,xe=c.config().iterationDisplay??"separate";for(le=()=>{for(let n=0;n<G.length;n++){let e=G[n];if(!fe&&B!=="flow"&&typeof e.stepType=="string"&&(B="flow"),e.type==="reasoning_start"){let t=typeof e.id=="string"?e.id:null,o=typeof e.parentToolCallId=="string"&&e.parentToolCallId?e.parentToolCallId:null;if(t&&o){$.set(t,o),pe(t,o,"reasoning");continue}let s=e.id,r=se(s);r.reasoning=r.reasoning??{id:s,status:"streaming",chunks:[]},r.reasoning.startedAt=r.reasoning.startedAt??Date.now(),r.reasoning.completedAt=void 0,r.reasoning.durationMs=void 0,(e.scope==="loop"||e.scope==="turn")&&(r.reasoning.scope=e.scope),r.streaming=!0,r.reasoning.status="streaming",d(r)}else if(e.type==="reasoning_delta"){let t=typeof e.id=="string"?e.id:null;if(t&&$.has(t)&&v.has(t)){let r=v.get(t),i=e.delta;i&&r.reasoning&&(r.reasoning.chunks.push(String(i)),d(r));continue}let o=e.id,s=se(o);s.reasoning=s.reasoning??{id:o,status:"streaming",chunks:[]},s.reasoning.startedAt=s.reasoning.startedAt??Date.now(),e.delta&&s.reasoning.chunks.push(e.delta),s.reasoning.status="streaming",s.streaming=!0,d(s)}else if(e.type==="reasoning_complete"){let t=typeof e.id=="string"?e.id:null;if(t&&$.has(t)&&v.has(t)){let i=v.get(t);if(i.reasoning){let l=typeof e.text=="string"?e.text:"";l&&i.reasoning.chunks.length===0&&i.reasoning.chunks.push(l),i.reasoning.status="complete",i.streaming=!1,d(i)}$.delete(t),v.delete(t);continue}let o=e.id,s=typeof e.text=="string"?e.text:"";!Q.get(o)&&(s||e.scope==="loop")&&se(o);let r=Q.get(o);if(r?.reasoning){(e.scope==="loop"||e.scope==="turn")&&(r.reasoning.scope=e.scope),s&&r.reasoning.chunks.length===0&&r.reasoning.chunks.push(s),r.reasoning.status="complete",r.reasoning.completedAt=Date.now();let i=r.reasoning.startedAt??Date.now();r.reasoning.durationMs=Math.max(0,(r.reasoning.completedAt??Date.now())-i),r.streaming=!1,d(r)}}else if(e.type==="tool_start"){f&&(f.streaming=!1,d(f),f=null),typeof e.iteration=="number"&&(_=e.iteration);let t=e.toolCallId,o=e.toolName;if(Ae(o)){H.add(t);continue}let s=L(t),r=s.toolCall??{id:t,status:"pending"};r.name=o??r.name,r.status="running",r.success=void 0,r.error=void 0,r.duration=void 0,e.parameters!==void 0&&(r.args=e.parameters),r.startedAt=r.startedAt??ue(e.startedAt),r.completedAt=void 0,r.durationMs=void 0,s.toolCall=r,s.streaming=!0,e.executionId&&(s.agentMetadata={executionId:e.executionId,iteration:e.iteration}),d(s)}else if(e.type==="tool_output_delta"){let t=e.toolCallId;if(H.has(t))continue;let o=L(t),s=o.toolCall??{id:t,status:"running"};s.startedAt=s.startedAt??Date.now();let r=e.delta;r&&(s.chunks=s.chunks??[],s.chunks.push(String(r))),s.status="running",o.toolCall=s,o.streaming=!0,e.executionId&&(o.agentMetadata=o.agentMetadata??{executionId:e.executionId,iteration:_}),d(o)}else if(e.type==="tool_complete"){let t=e.toolCallId;if(H.has(t)){H.delete(t);continue}let o=L(t),s=o.toolCall??{id:t,status:"running"};s.status="complete",e.result!==void 0&&(s.result=e.result),s.name=e.toolName??s.name,s.success=e.success,s.error=e.error,s.completedAt=Date.now();let r=e.executionTime;if(typeof r=="number")s.durationMs=r,s.duration=r;else{let i=s.startedAt??Date.now();s.durationMs=Math.max(0,(s.completedAt??Date.now())-i)}o.toolCall=s,o.streaming=!1,e.executionId&&(o.agentMetadata=o.agentMetadata??{executionId:e.executionId,iteration:e.iteration??_}),d(o)}else if(e.type==="await"&&e.toolName){let t=typeof e.toolCallId=="string"&&e.toolCallId.length>0?e.toolCallId:void 0,o=t??e.toolId??`local-${p()}`,s=L(o),r=e.toolName,i=e.origin==="webmcp"&&!de(r)?`webmcp:${r}`:r,l=de(i),a=s.toolCall??{id:o,status:"pending"};a.name=i,a.args=e.parameters,a.status=l?"running":"complete",a.chunks=a.chunks??[],a.startedAt=a.startedAt??ue(e.awaitedAt),l?(a.completedAt=void 0,a.duration=void 0,a.durationMs=void 0):a.completedAt=a.completedAt??a.startedAt,s.toolCall=a,s.streaming=!1,s.agentMetadata={...s.agentMetadata,executionId:e.executionId??s.agentMetadata?.executionId,awaitingLocalTool:!0,...t?{webMcpToolCallId:t}:{}},d(s)}else if(e.type==="text_start"){let t=typeof e.id=="string"?e.id:null,o=typeof e.parentToolCallId=="string"&&e.parentToolCallId?e.parentToolCallId:null;if(t&&o){$.set(t,o);continue}let s=f;s&&(B==="flow"?(V(s),K=s):(s.streaming=!1,d(s)),f=null),F=typeof e.id=="string"?e.id:F,E=""}else if(e.type==="text_delta"){let t=typeof e.id=="string"?e.id:null,o=t?$.get(t):void 0;if(t&&o){let i=typeof e.delta=="string"?e.delta:"",l=(te.get(t)??"")+i;if(te.set(t,l),l.trim()==="")continue;let a=pe(t,o);a.agentMetadata={...a.agentMetadata,executionId:e.executionId,parentToolId:o},ae(a,l,i,void 0);continue}if(F=typeof e.id=="string"?e.id:F,B==="flow"){let i=typeof e.delta=="string"?e.delta:"";if(E+=i,E.trim()==="")continue;let l=oe();l.agentMetadata={executionId:e.executionId,iteration:_},ae(l,E,i,void 0),U=l;continue}let s=typeof e.delta=="string"?e.delta:"";E+=s;let r=oe();r.agentMetadata={executionId:e.executionId,iteration:_,turnId:Z??void 0,agentName:j?.agentName},ae(r,E,s,void 0),U=r}else if(e.type==="text_complete"){let t=typeof e.id=="string"?e.id:null;if(t&&$.has(t)){let s=v.get(t);s&&V(s),$.delete(t),te.delete(t),v.delete(t);continue}let o=f;o&&(B==="flow"?(V(o),K=o):((o.content??"")===""&&typeof e.text=="string"&&(o.content=e.text),o.streaming=!1,d(o)),f=null),F=null,E=""}else if(e.type==="step_complete"){if(e.stepType==="tool")continue;if(e.success===!1){let t=e.error||"Step failed";m({type:"error",error:new Error(t)});let o=f;o&&o.streaming&&(o.streaming=!1,d(o)),m({type:"status",status:"idle"});continue}{let t=K;K=null;let o=e.stopReason,r=(e.result&&typeof e.result=="object"?e.result:null)?.response;if(t)o&&(t.stopReason=o),r!=null?Me(t,r):t.streaming!==!1&&(T.delete(t.id),W.delete(t.id),t.streaming=!1,d(t));else{let i=r!=null&&r!=="";if(i||o){let l=oe();o&&(l.stopReason=o),i?V(l,r):(l.streaming=!1,d(l))}}continue}}else if(e.type==="execution_start")B=e.kind==="flow"?"flow":"agent",fe=!0,B==="agent"&&(j={executionId:e.executionId,agentId:e.agentId??"virtual",agentName:e.agentName??""});else if(e.type==="turn_start"){let t=typeof e.iteration=="number"?e.iteration:_;if(t!==_){if(xe==="separate"&&t>1){let o=f;o&&(o.streaming=!1,d(o),f=null)}_=t}Z=typeof e.id=="string"?e.id:null,U=null}else if(e.type==="tool_input_delta"){let t=e.toolCallId;if(t){let o=ne.get(t);o?.toolCall&&(o.toolCall.chunks=o.toolCall.chunks??[],o.toolCall.chunks.push(e.delta??""),d(o))}}else if(e.type==="tool_input_complete"){if(H.has(e.toolCallId))continue;let t=L(e.toolCallId),o=t.toolCall;o.args=e.parameters,o.name=e.toolName??o.name,d(t)}else if(e.type==="turn_complete"){let t=e.stopReason,o=f??U;if(t&&o!==null){let s=e.id;(!s||o.agentMetadata?.turnId===s)&&(o.stopReason=t,d(o))}Z===e.id&&(Z=null)}else if(e.type==="media_start"){let t=String(e.id);Y.set(t,{mediaType:typeof e.mediaType=="string"?e.mediaType:void 0,toolCallId:e.toolCallId,parts:[]})}else if(e.type==="media_delta"){let t=Y.get(String(e.id));t&&typeof e.delta=="string"&&t.parts.push(e.delta)}else if(e.type==="media_complete"){let t=String(e.id),o=Y.get(t);Y.delete(t);let s=(typeof e.mediaType=="string"?e.mediaType:void 0)??o?.mediaType??"application/octet-stream",r=typeof e.data=="string"?e.data:void 0,i=typeof e.url=="string"?e.url:o&&o.parts.length>0?o.parts.join(""):void 0,l=null;if(r)l={type:"media",data:r,mediaType:s};else if(i){let I=s.toLowerCase();l={type:I==="image"||I.startsWith("image/")?"image-url":"file-url",url:i,mediaType:s}}let a=e.toolCallId??o?.toolCallId,w=l?[l]:[],C=[];for(let I of w){if(!I||typeof I!="object")continue;let g=I,N=typeof g.type=="string"?g.type:void 0,q=typeof g.mediaType=="string"?g.mediaType.toLowerCase():"",b=null,P="";if(N==="media"){let R=typeof g.data=="string"?g.data:void 0;if(!R)continue;P=q.length>0?q:"application/octet-stream",b=`data:${P};base64,${R}`}else if(N==="image-url"){let R=typeof g.url=="string"?g.url:void 0;if(!R)continue;P=q,b=R}else if(N==="file-url"){let R=typeof g.url=="string"?g.url:void 0;if(!R)continue;P=q,b=R}else continue;if(b)if(N==="image-url"||P.startsWith("image/"))C.push({type:"image",image:b,...P.includes("/")?{mimeType:P}:{}});else if(P.startsWith("audio/"))C.push({type:"audio",audio:b,mimeType:P});else if(P.startsWith("video/"))C.push({type:"video",video:b,mimeType:P});else{let R=P||"application/octet-stream";C.push({type:"file",data:b,mimeType:R,filename:ke(R)})}}if(C.length>0){let I=p(),g=a,q={id:`agent-media-${typeof g=="string"&&g.length>0?`${g}-${I}`:String(I)}`,role:"assistant",content:"",contentParts:C,createdAt:new Date().toISOString(),streaming:!1,sequence:I,agentMetadata:{executionId:e.executionId,iteration:_}};d(q);let b=f;b&&(b.streaming=!1,d(b)),f=null,O.current=null}}else if(e.type==="execution_complete"){let t=f;t&&(e.kind==="flow"&&t.streaming!==!1?V(t):(t.streaming=!1,d(t)),f=null),F=null,E="",K=null,m({type:"status",status:"idle",terminal:!0})}else if(e.type==="execution_error"){let t=typeof e.error=="string"?e.error:e.error?.message??"Execution error";m({type:"error",error:new Error(t)})}else if(e.type!=="ping"){if(e.type==="approval_start"){let t=e.approvalId??`approval-${p()}`,o={id:`approval-${t}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!1,variant:"approval",sequence:p(),approval:{id:t,status:"pending",agentId:j?.agentId??"virtual",executionId:e.executionId??j?.executionId??"",toolName:e.toolName??"",toolType:e.toolType,description:e.description??`Execute ${e.toolName??"tool"}`,...typeof e.reason=="string"&&e.reason?{reason:e.reason}:{},parameters:e.parameters,...typeof e.toolCallId=="string"&&e.toolCallId?{toolCallId:e.toolCallId}:{}}};d(o)}else if(e.type==="approval_complete"){let t=e.approvalId;if(t){let s={id:`approval-${t}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!1,variant:"approval",sequence:p(),approval:{id:t,status:e.decision??"approved",agentId:j?.agentId??"virtual",executionId:e.executionId??j?.executionId??"",toolName:"",description:"",resolvedAt:Date.now()}};d(s)}}else if(e.type==="artifact_start"||e.type==="artifact_delta"||e.type==="artifact_update"||e.type==="artifact_complete"){if(e.type==="artifact_start"){let t=e.artifactType,o=String(e.id),s=typeof e.title=="string"?e.title:void 0,r=e.file,i;if(r&&typeof r=="object"&&!Array.isArray(r)&&typeof r.path=="string"&&typeof r.mimeType=="string"&&(i={path:r.path,mimeType:r.mimeType,...typeof r.language=="string"?{language:r.language}:{}}),m({type:"artifact_start",id:o,artifactType:t,title:s,component:typeof e.component=="string"?e.component:void 0,...i?{file:i}:{}}),J.set(o,{markdown:"",title:s,file:i}),!ge.has(o)){ge.add(o);let l=me(c.config().features?.artifacts,{artifactType:t,...i?{file:i}:{}}),a=typeof e.component=="string"?e.component:void 0,w={id:`artifact-ref-${o}`,role:"assistant",content:"",createdAt:new Date().toISOString(),streaming:!0,sequence:p(),rawContent:ye(l,{artifactId:o,title:s,artifactType:t,status:"streaming",...i?{file:i}:{},...a?{component:a}:{}})};re.set(o,w),d(w)}}else if(e.type==="artifact_delta"){let t=String(e.id),o=typeof e.delta=="string"?e.delta:String(e.delta??"");m({type:"artifact_delta",id:t,artDelta:o});let s=J.get(t);s&&(s.markdown+=o)}else if(e.type==="artifact_update"){let t=e.props&&typeof e.props=="object"&&!Array.isArray(e.props)?e.props:{};m({type:"artifact_update",id:String(e.id),props:t,component:typeof e.component=="string"?e.component:void 0});let o=J.get(String(e.id));o&&(o.props={...o.props??{},...t})}else if(e.type==="artifact_complete"){let t=String(e.id);m({type:"artifact_complete",id:t});let o=re.get(t);if(o){o.streaming=!1;try{let s=JSON.parse(o.rawContent??"{}");if(s.props){s.props.status="complete";let r=J.get(t);r?.markdown&&(s.props.markdown=r.markdown),r?.file&&(s.props.file=r.file),s.component==="PersonaArtifactInline"&&r?.props&&Object.keys(r.props).length>0&&(s.props.componentProps=r.props)}o.rawContent=JSON.stringify(s)}catch{}J.delete(t),d(o),re.delete(t)}}}else if(e.type==="error"&&e.recoverable===!1&&e.error!=null&&e.error!==""){let t=typeof e.error=="string"?e.error:e.error?.message!=null?String(e.error.message):"Execution error";m({type:"error",error:new Error(t)});let o=f;o&&o.streaming&&(o.streaming=!1,d(o)),m({type:"status",status:"idle"})}}}G.length=0};;){let{done:n,value:e}=await x.read();if(n)break;y+=A.decode(e,{stream:!0});let t=y.split(`
2
-
3
- `);y=t.pop()??"";for(let o of t){let s=o.split(`
4
- `),r="message",i="",l=null;for(let g of s)g.startsWith("event:")?r=g.replace("event:","").trim():g.startsWith("data:")?i+=g.replace("data:","").trim():g.startsWith("id:")&&(l=g.slice(3).trim());let a=()=>{l!==null&&l!==""&&m({type:"cursor",id:l})};if(!i){a();continue}let w;try{w=JSON.parse(i)}catch(g){m({type:"error",error:g instanceof Error?g:new Error("Failed to parse chat stream payload")});continue}let C=w!==null&&typeof w=="object"&&!Array.isArray(w)?w:null,I=r!=="message"?r:typeof C?.type=="string"?C.type:"message";if(c.onSSEEvent?.(I,w),c.parseSSEEvent){O.current=f;let g=await be(c,w,m,O,d,p,Ce);if(O.current&&O.current!==f&&(f=O.current),g){a();continue}}C&&(G.push({...C,type:I}),le()),a()}}le()}export{We as preferFinalStructuredContent,Te as resolveApproval,Se as resumeFlow,Ee as streamResponse};
@@ -1,17 +0,0 @@
1
- import{a as Y}from"./chunk-6V3MNEKB.js";import{a as K}from"./chunk-GKPCIQPB.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",ke=300,Ae=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.#m=[];this.#v=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.#k=!1;this.#A=0;this.#h=0;this.#y=!1;this.bridge=null;this.#C=!1;this.#p=new Set;this.#b=new Map;this.#T=new Set;this.#D=!1;this.#E=null;this.#O=null;this.#N=Promise.resolve();this.#I=new Set;this.#S=!1;this.#P=new Set;this.#V=new Set;this.#z=[];this.#B=[];this.#W=[];this.#U=[];this.#x=[]}#e;#d;#u;#i;#r;#a;#m;#v;#n;#g;#c;#s;#w;#F;#o;#l;#k;#A;#h;#M;#y;#C;#p;#b;#T;#L;#D;#E;#O;#N;#I;#S;#P;#V;#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.#_()}}#X(e,t){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>{this.#o===e&&this.#_()},Math.max(0,t))}#_(){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.#_(),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","delegation_read_back"),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.#f(),this.#R(r),this.#t("error"),r}}#K(e,t){e.onmessage=s=>this.#ne(s,t),e.onerror=()=>{t===this.#s&&(this.#R(new Error("Voice connection failed")),this.#t("error"),this.#f())},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.#R(new Error(`Voice connection closed${n}`)),this.#t("error")}else this.#t("idle");this.#f()}}}getDisclosure(){let e=this.config?.disclosureText;return this.#g&&this.#l&&e!==!1?e||Ee:null}async stopListening(){this.#f(),this.#t("idle")}async disconnect(){this.#f(),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.#A=Date.now()+Ae,e.send('{"type":"cancel"}')),this.#t("listening"))}getInterruptionMode(){return"barge-in"}isBargeInActive(){return this.#g}onLevel(e){this.#u.push(e)}async deactivateBargeIn(){this.#f(),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.#m.push(c.buffer),this.#v+=c.byteLength;this.#v>he;)this.#v-=this.#m.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.#m)e.send(t);this.#m=[],this.#v=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.#p=new Set(s.capabilities)),typeof s.callId=="string"&&(this.#L=s.callId),this.#C=this.#l&&this.#p.has("client_delegation")&&!!this.bridge,this.#l&&!this.#c&&this.#t("listening"),this.#p.has("context")&&!this.#D&&(this.#D=!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.#p.has("delegation_read_back")){let l=s.delegationId;typeof l=="string"&&this.#I.has(l)&&!this.#P.has(d)&&this.#V.add(d)}else this.#S&&!this.#P.has(d)&&(this.#V.add(d),this.#S=!1);if(this.#P.add(d),this.#V.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.#I.has(n)&&(this.#S=!0);this.#k=s.type==="delegation_started",!this.#c&&!this.#q()&&this.#t(this.#k?"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.#L??""),s.delegationId&&(s.code==="UNKNOWN_DELEGATION"||s.code==="LATE_RESULT_LIMIT")&&this.#J(String(s.delegationId));break;case"audio_clear":this.#Q(),this.#A=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.#R(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.#O={done:n,drop:()=>s=!0}}async#re(){let e=this.#O;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.#N=this.#N.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.#p.has("delegation_update");if(!r(a?"delegation_update":"delegation_result",l&&!a?"completed":d.status,d.text)||(d.status!=="failed"&&d.inChat!==!1&&this.#I.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.#A}#j(){if(this.#y||!this.#n)return;this.#y=!0;let e=this.#s;this.#n.onFinished(()=>{e===this.#s&&(this.#y=!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+ke)}#Z(){this.#c=!1,this.#h=0,this.#e&&this.#e.readyState===WebSocket.OPEN&&this.#t(this.#k?"processing":"listening")}#Q(){this.#n?.flush(),this.#y=!1,this.#j(),this.#c=!1,this.#h=0,clearTimeout(this.#M)}#f(){this.#s+=1,this.#g=!1,this.#c=!1,this.#_(),this.#l=!1,this.#k=!1,this.#C=!1,this.#p=new Set;for(let e of this.#b.values())e.abort();if(this.#b.clear(),this.#T.clear(),this.#L=void 0,this.#D=!1,this.#E=null,this.#O=null,this.#N=Promise.resolve(),this.#I.clear(),this.#S=!1,this.#P.clear(),this.#V.clear(),this.#y=!1,this.#A=0,this.#h=0,clearTimeout(this.#M),this.#m=[],this.#v=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))}#R(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.`,inChat:!1}}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"),k={ids:[],failed:!1};l(g,k),e=k,s();try{await i.send(m,i.claim(m,b)??void 0),await f(k)}catch{k.failed=!0}finally{e=null,s()}let re=k.ids.flatMap(h=>i.messages().find(y=>y.id===h)??[]),T=p(k.ids),V=k.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 A={ids:[],failed:!1};r.delete(g),l(g,A);let z=()=>L().filter(v=>h.has(v.id)||A.ids.includes(v.id)),le=()=>L().filter(v=>R(v)&&!h.has(v.id)&&!n.some(M=>M.approvals.includes(v.id))&&(A.ids.includes(v.id)||v.approval?.toolType==="webmcp"&&t===A)),X=()=>{t=A,s()};F&&X();try{for(;!w.aborted&&!y.outcome&&(e||i.busy()||z().some(R));){F&&t!==A&&!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===A&&(t=null,s()),n.splice(n.indexOf(y),1)}if(w.aborted)return null;if(y.outcome)return y.outcome;if(A.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(A.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!k.failed&&i.parked()||re.some(h=>h.agentMetadata?.awaitingLocalTool&&!h.agentMetadata.askUserQuestionAnswered&&!h.toolCall?.name?.startsWith("webmcp:"))?{status:k.failed?"failed":"completed",text:`${T}
16
-
17
- ${Ne}`.trim()}:{status:!k.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};