@runtypelabs/persona 3.34.1 → 3.35.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 (186) hide show
  1. package/README.md +11 -11
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-CthJFfNx.d.cts → types-DgYsuwXL.d.cts} +15 -14
  5. package/dist/animations/{types-CthJFfNx.d.ts → types-DgYsuwXL.d.ts} +15 -14
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +11 -11
  9. package/dist/codegen.js +10 -10
  10. package/dist/index.cjs +46 -44
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +514 -158
  13. package/dist/index.d.ts +514 -158
  14. package/dist/index.global.js +41 -89
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +46 -44
  17. package/dist/index.js.map +1 -1
  18. package/dist/install.global.js +1 -1
  19. package/dist/install.global.js.map +1 -1
  20. package/dist/launcher.global.js +2 -2
  21. package/dist/launcher.global.js.map +1 -1
  22. package/dist/markdown-parsers.js +53 -0
  23. package/dist/plugin-kit.d.cts +4 -4
  24. package/dist/plugin-kit.d.ts +4 -4
  25. package/dist/runtype-tts.js +1 -0
  26. package/dist/smart-dom-reader.cjs +1 -1
  27. package/dist/smart-dom-reader.d.cts +352 -103
  28. package/dist/smart-dom-reader.d.ts +352 -103
  29. package/dist/smart-dom-reader.js +1 -1
  30. package/dist/theme-editor-preview.cjs +203 -0
  31. package/dist/theme-editor-preview.d.cts +5748 -0
  32. package/dist/theme-editor-preview.d.ts +5748 -0
  33. package/dist/theme-editor-preview.js +203 -0
  34. package/dist/theme-editor.cjs +14 -131
  35. package/dist/theme-editor.d.cts +362 -574
  36. package/dist/theme-editor.d.ts +362 -574
  37. package/dist/theme-editor.js +14 -131
  38. package/dist/theme-reference.cjs +1 -1
  39. package/dist/theme-reference.d.cts +1 -1
  40. package/dist/theme-reference.d.ts +1 -1
  41. package/dist/theme-reference.js +1 -1
  42. package/dist/voice-worklet-player.cjs +80 -0
  43. package/dist/voice-worklet-player.d.cts +215 -0
  44. package/dist/voice-worklet-player.d.ts +215 -0
  45. package/dist/voice-worklet-player.js +80 -0
  46. package/dist/widget.css +54 -36
  47. package/package.json +16 -2
  48. package/src/animations/glyph-cycle.ts +11 -14
  49. package/src/animations/wipe.ts +1 -1
  50. package/src/ask-user-question-tool.test.ts +1 -1
  51. package/src/ask-user-question-tool.ts +6 -8
  52. package/src/client.test.ts +12 -12
  53. package/src/client.ts +30 -31
  54. package/src/codegen.test.ts +1 -1
  55. package/src/codegen.ts +1 -1
  56. package/src/components/approval-bubble.test.ts +1 -1
  57. package/src/components/approval-bubble.ts +3 -3
  58. package/src/components/artifact-pane.ts +3 -3
  59. package/src/components/ask-user-question-bubble.test.ts +14 -14
  60. package/src/components/ask-user-question-bubble.ts +10 -10
  61. package/src/components/composer-builder.ts +17 -16
  62. package/src/components/composer-parts.test.ts +54 -0
  63. package/src/components/composer-parts.ts +95 -126
  64. package/src/components/demo-carousel.ts +1 -1
  65. package/src/components/event-stream-view.test.ts +7 -7
  66. package/src/components/event-stream-view.ts +5 -5
  67. package/src/components/header-builder.ts +33 -27
  68. package/src/components/header-layouts.ts +1 -1
  69. package/src/components/header-parts.test.ts +90 -0
  70. package/src/components/header-parts.ts +62 -93
  71. package/src/components/launcher.test.ts +11 -0
  72. package/src/components/launcher.ts +1 -1
  73. package/src/components/message-bubble.test.ts +74 -2
  74. package/src/components/message-bubble.ts +25 -11
  75. package/src/components/messages.ts +2 -2
  76. package/src/components/panel.test.ts +1 -1
  77. package/src/components/panel.ts +92 -97
  78. package/src/components/pill-composer-builder.ts +28 -24
  79. package/src/components/reasoning-bubble.ts +2 -2
  80. package/src/components/suggestions.ts +1 -1
  81. package/src/components/tool-bubble.ts +1 -1
  82. package/src/components/widget-view.test.ts +181 -0
  83. package/src/components/widget-view.ts +234 -0
  84. package/src/defaults.ts +54 -51
  85. package/src/generated/runtype-openapi-contract.ts +5 -0
  86. package/src/index-core.ts +30 -4
  87. package/src/index-global.ts +73 -6
  88. package/src/index.ts +7 -1
  89. package/src/install.test.ts +11 -11
  90. package/src/install.ts +28 -18
  91. package/src/launcher-global.ts +25 -14
  92. package/src/markdown-parsers-eager.ts +20 -0
  93. package/src/markdown-parsers-entry.ts +4 -0
  94. package/src/markdown-parsers-loader.ts +49 -0
  95. package/src/plugin-kit.test.ts +1 -1
  96. package/src/plugin-kit.ts +7 -7
  97. package/src/plugins/types.ts +5 -5
  98. package/src/postprocessors.ts +27 -14
  99. package/src/runtime/host-layout.test.ts +1 -1
  100. package/src/runtime/host-layout.ts +4 -5
  101. package/src/runtime/init.ts +2 -2
  102. package/src/session.test.ts +5 -6
  103. package/src/session.ts +333 -227
  104. package/src/session.voice.test.ts +146 -0
  105. package/src/session.webmcp.test.ts +24 -25
  106. package/src/smart-dom-reader.ts +3 -4
  107. package/src/styles/widget.css +54 -36
  108. package/src/suggest-replies-tool.test.ts +3 -3
  109. package/src/suggest-replies-tool.ts +5 -5
  110. package/src/testing/index.ts +1 -1
  111. package/src/theme-editor/color-utils.ts +2 -2
  112. package/src/theme-editor/index.ts +4 -13
  113. package/src/theme-editor/presets.ts +1 -1
  114. package/src/theme-editor/preview-utils.ts +22 -20
  115. package/src/theme-editor/preview.ts +4 -1
  116. package/src/theme-editor/role-mappings.ts +3 -3
  117. package/src/theme-editor/sections.ts +21 -20
  118. package/src/theme-editor/types.ts +1 -1
  119. package/src/theme-editor/webmcp/index.ts +1 -1
  120. package/src/theme-editor/webmcp/summary.ts +1 -2
  121. package/src/theme-editor/webmcp/tools.ts +30 -30
  122. package/src/theme-editor/webmcp/types.ts +2 -2
  123. package/src/theme-editor-preview.ts +10 -0
  124. package/src/theme-reference.ts +26 -26
  125. package/src/types.ts +368 -99
  126. package/src/ui.approval-plugin.test.ts +3 -3
  127. package/src/ui.ask-user-question-plugin.test.ts +9 -9
  128. package/src/ui.component-directive.test.ts +4 -4
  129. package/src/ui.composer-bar.test.ts +10 -11
  130. package/src/ui.composer-keyboard.test.ts +5 -5
  131. package/src/ui.header-icon-color.test.ts +59 -0
  132. package/src/ui.scroll.test.ts +5 -6
  133. package/src/ui.stop-button.test.ts +3 -4
  134. package/src/ui.suggest-replies.test.ts +2 -2
  135. package/src/ui.ts +292 -131
  136. package/src/utils/buttons.ts +1 -1
  137. package/src/utils/code-generators.ts +1 -1
  138. package/src/utils/composer-history.ts +3 -3
  139. package/src/utils/dom-context.test.ts +2 -2
  140. package/src/utils/dom-context.ts +7 -7
  141. package/src/utils/dom.test.ts +67 -0
  142. package/src/utils/dom.ts +75 -0
  143. package/src/utils/dropdown.ts +2 -2
  144. package/src/utils/event-stream-buffer.test.ts +2 -2
  145. package/src/utils/message-fingerprint.test.ts +9 -0
  146. package/src/utils/message-fingerprint.ts +5 -0
  147. package/src/utils/morph.ts +1 -1
  148. package/src/utils/sanitize.ts +32 -16
  149. package/src/utils/sequence-buffer.test.ts +8 -8
  150. package/src/utils/sequence-buffer.ts +4 -4
  151. package/src/utils/smart-dom-adapter.test.ts +1 -1
  152. package/src/utils/smart-dom-adapter.ts +4 -5
  153. package/src/utils/speech-text.test.ts +122 -0
  154. package/src/utils/speech-text.ts +101 -0
  155. package/src/utils/stream-animation.test.ts +3 -3
  156. package/src/utils/stream-animation.ts +6 -6
  157. package/src/utils/theme.test.ts +1 -1
  158. package/src/utils/throughput-tracker.test.ts +11 -11
  159. package/src/utils/throughput-tracker.ts +9 -9
  160. package/src/utils/tokens.ts +13 -13
  161. package/src/utils/virtual-scroller.ts +1 -1
  162. package/src/vendor/smart-dom-reader/README.md +4 -4
  163. package/src/vendor/smart-dom-reader/index.d.ts +1 -1
  164. package/src/vendor/smart-dom-reader/index.js +2 -2
  165. package/src/version.ts +1 -1
  166. package/src/voice/audio-playback-manager.test.ts +159 -0
  167. package/src/voice/audio-playback-manager.ts +145 -30
  168. package/src/voice/browser-speech-engine.ts +130 -0
  169. package/src/voice/fallback-speech-engine.ts +89 -0
  170. package/src/voice/index.ts +21 -0
  171. package/src/voice/read-aloud-controller.test.ts +143 -0
  172. package/src/voice/read-aloud-controller.ts +136 -0
  173. package/src/voice/runtype-speech-engine.test.ts +280 -0
  174. package/src/voice/runtype-speech-engine.ts +211 -0
  175. package/src/voice/runtype-tts-entry.ts +10 -0
  176. package/src/voice/runtype-tts-loader.test.ts +47 -0
  177. package/src/voice/runtype-tts-loader.ts +42 -0
  178. package/src/voice/runtype-voice-provider.ts +332 -537
  179. package/src/voice/voice-factory.ts +26 -4
  180. package/src/voice/voice.test.ts +321 -7
  181. package/src/voice/worklet-playback-engine.ts +272 -0
  182. package/src/voice-worklet-player.ts +39 -0
  183. package/src/webmcp-bridge.test.ts +6 -6
  184. package/src/webmcp-bridge.ts +24 -25
  185. package/src/webmcp-polyfill.ts +1 -1
  186. package/src/voice/voice-activity-detector.ts +0 -90
package/src/types.ts CHANGED
@@ -206,7 +206,7 @@ export type AgentConfig = {
206
206
  temperature?: number;
207
207
  /** Tool configuration for the agent */
208
208
  tools?: AgentToolsConfig;
209
- /** Persona artifacts sibling of tools (virtual agent / API parity) */
209
+ /** Persona artifacts: sibling of tools (virtual agent / API parity) */
210
210
  artifacts?: ArtifactConfigPayload;
211
211
  /** Loop configuration for multi-turn execution */
212
212
  loopConfig?: AgentLoopConfig;
@@ -236,7 +236,7 @@ export type AgentWidgetAgentRequestPayload = {
236
236
  context?: Record<string, unknown>;
237
237
  metadata?: Record<string, unknown>;
238
238
  /**
239
- * Per-turn page-discovered tools (WebMCP) same shape as
239
+ * Per-turn page-discovered tools (WebMCP): same shape as
240
240
  * `AgentWidgetRequestPayload.clientTools`.
241
241
  */
242
242
  clientTools?: ClientToolDefinition[];
@@ -250,7 +250,7 @@ export type AgentWidgetAgentRequestPayload = {
250
250
  * Wire shape for a single client-discovered tool sent on `dispatch.clientTools[]`.
251
251
  *
252
252
  * Mirrors the SDK's `ClientToolDefinition` in `@runtypelabs/sdk`. Only the
253
- * JSON-serializable surface of a WebMCP tool the `execute` function stays
253
+ * JSON-serializable surface of a WebMCP tool: the `execute` function stays
254
254
  * client-side; the server merges these into the agent's tool catalog under
255
255
  * the `webmcp:` namespace.
256
256
  */
@@ -258,16 +258,16 @@ export type ClientToolDefinition = {
258
258
  /** Bare tool name; the server prepends `webmcp:` on the wire. */
259
259
  name: string;
260
260
  description: string;
261
- /** JSON Schema (per WebMCP spec) passed through as-is. */
261
+ /** JSON Schema (per WebMCP spec): passed through as-is. */
262
262
  parametersSchema?: object;
263
263
  /**
264
264
  * `'webmcp'` for tools discovered via the polyfill (server prepends the
265
265
  * `webmcp:` wire prefix); `'sdk'` for widget/SDK-provided tools (name stays
266
- * bare on the wire). Matches the server's accepted enum any other value
266
+ * bare on the wire). Matches the server's accepted enum: any other value
267
267
  * fails dispatch validation.
268
268
  */
269
269
  origin?: 'webmcp' | 'sdk';
270
- /** Origin of the page that registered the tool for server-side audit. */
270
+ /** Origin of the page that registered the tool: for server-side audit. */
271
271
  pageOrigin?: string;
272
272
  /**
273
273
  * WebMCP `Tool.annotations` (spec). Not used for gating server-side; the
@@ -300,7 +300,7 @@ export type WebMcpConfirmInfo = {
300
300
  untrustedContentHint?: boolean;
301
301
  };
302
302
  /**
303
- * Why the confirm was requested. Currently always `'gate'` the default
303
+ * Why the confirm was requested. Currently always `'gate'`: the default
304
304
  * confirm-by-default gate that fires before every `webmcp:*` call. (The
305
305
  * `@mcp-b/webmcp-polyfill` owns the spec's `requestUserInteraction` callback
306
306
  * internally, so Persona no longer surfaces a nested in-tool confirm.)
@@ -333,7 +333,7 @@ export type WebMcpToolResult = {
333
333
  /**
334
334
  * Widget-level WebMCP configuration. Set `enabled: true` to opt in. The
335
335
  * surface's server-side `webmcp` policy is the source of truth for which
336
- * tools are accepted these client-side options are convenience filters.
336
+ * tools are accepted: these client-side options are convenience filters.
337
337
  */
338
338
  export type AgentWidgetWebMcpConfig = {
339
339
  /** Master switch. Default: `false` (widget never installs the polyfill). */
@@ -349,7 +349,7 @@ export type AgentWidgetWebMcpConfig = {
349
349
  * `webmcp:*` call; return `true` to approve immediately and skip the
350
350
  * confirmation UI entirely. Use this to auto-allow read-only tools (e.g.
351
351
  * a catalog search) while still gating mutating ones. Only consulted on
352
- * the default-UI path a custom `onConfirm` takes full control instead.
352
+ * the default-UI path: a custom `onConfirm` takes full control instead.
353
353
  */
354
354
  autoApprove?: (info: WebMcpConfirmInfo) => boolean;
355
355
  /**
@@ -408,7 +408,7 @@ export type AgentMessageMetadata = {
408
408
  * `flow_await` events for a LOCAL tool (core#3878). Present only when the
409
409
  * server emits it. Two PARALLEL calls to the same tool in one turn share a
410
410
  * `toolName` (and a collapsed `toolId`) but get DISTINCT `webMcpToolCallId`s,
411
- * so this is the key the widget batches a single `/resume` on preferred
411
+ * so this is the key the widget batches a single `/resume` on: preferred
412
412
  * over tool name, which collides for same-tool parallel calls. Absent →
413
413
  * fall back to the legacy name-keyed resume contract.
414
414
  */
@@ -522,11 +522,38 @@ export type AgentWidgetVoiceStateEvent = {
522
522
  timestamp: number;
523
523
  };
524
524
 
525
+ /**
526
+ * Fired on every voice `VoiceStatus` transition (listening / processing /
527
+ * speaking / idle / …). Unlike `voice:state` (a coarse active on/off), this
528
+ * exposes the granular status so consumers can render their own per-state UI
529
+ * (e.g. a listening/speaking status dock).
530
+ */
531
+ export type AgentWidgetVoiceStatusEvent = {
532
+ status: VoiceStatus;
533
+ timestamp: number;
534
+ };
535
+
525
536
  export type AgentWidgetActionEventPayload = {
526
537
  action: AgentWidgetParsedAction;
527
538
  message: AgentWidgetMessage;
528
539
  };
529
540
 
541
+ /**
542
+ * Fired on every "Read aloud" (text-to-speech) state transition for a message:
543
+ * `loading` (press) → `playing` → `paused`/`playing` → `idle` (finished or
544
+ * stopped). On the terminal `idle` transition, `messageId`/`message` still
545
+ * identify the message that just stopped.
546
+ */
547
+ export type AgentWidgetReadAloudEvent = {
548
+ /** The message being read aloud (or that just finished/stopped), if known. */
549
+ messageId: string | null;
550
+ /** The message object, when still present in the thread. */
551
+ message: AgentWidgetMessage | null;
552
+ /** The new playback state for this message. */
553
+ state: ReadAloudState;
554
+ timestamp: number;
555
+ };
556
+
530
557
  /**
531
558
  * Feedback event payload for upvote/downvote actions on messages
532
559
  */
@@ -579,6 +606,14 @@ export type AgentWidgetMessageActionsConfig = {
579
606
  * @default false
580
607
  */
581
608
  showDownvote?: boolean;
609
+ /**
610
+ * Show a "Read aloud" button that speaks the assistant message via
611
+ * text-to-speech (play / pause / resume). Uses the browser Web Speech API by
612
+ * default, or a hosted engine supplied via `textToSpeech.createEngine`.
613
+ * Voice, rate and pitch are read from the `textToSpeech` config.
614
+ * @default false
615
+ */
616
+ showReadAloud?: boolean;
582
617
  /**
583
618
  * Visibility mode: 'always' shows buttons always, 'hover' shows on hover only
584
619
  * @default 'hover'
@@ -632,6 +667,7 @@ export type AgentWidgetControllerEventMap = {
632
667
  "assistant:message": AgentWidgetMessage;
633
668
  "assistant:complete": AgentWidgetMessage;
634
669
  "voice:state": AgentWidgetVoiceStateEvent;
670
+ "voice:status": AgentWidgetVoiceStatusEvent;
635
671
  "action:detected": AgentWidgetActionEventPayload;
636
672
  "action:resubmit": AgentWidgetActionEventPayload;
637
673
  "widget:opened": AgentWidgetStateEvent;
@@ -639,6 +675,7 @@ export type AgentWidgetControllerEventMap = {
639
675
  "widget:state": AgentWidgetStateSnapshot;
640
676
  "message:feedback": AgentWidgetMessageFeedback;
641
677
  "message:copy": AgentWidgetMessage;
678
+ "message:read-aloud": AgentWidgetReadAloudEvent;
642
679
  "eventStream:opened": { timestamp: number };
643
680
  "eventStream:closed": { timestamp: number };
644
681
  "approval:requested": { approval: AgentWidgetApproval; message: AgentWidgetMessage };
@@ -685,8 +722,8 @@ export type AgentWidgetArtifactsLayoutConfig = {
685
722
  resizableMaxWidth?: string;
686
723
  /**
687
724
  * Visual treatment for the artifact column in split mode.
688
- * - `'panel'` bordered sidebar with left border, gap, and shadow (default).
689
- * - `'seamless'` flush with chat: no border or shadow, container background, zero gap.
725
+ * - `'panel'`: bordered sidebar with left border, gap, and shadow (default).
726
+ * - `'seamless'`: flush with chat: no border or shadow, container background, zero gap.
690
727
  * @default 'panel'
691
728
  */
692
729
  paneAppearance?: "panel" | "seamless";
@@ -700,7 +737,7 @@ export type AgentWidgetArtifactsLayoutConfig = {
700
737
  */
701
738
  paneBorder?: string;
702
739
  /**
703
- * `border-left` shorthand only typical for split view next to chat (with or without resizer).
740
+ * `border-left` shorthand only: typical for split view next to chat (with or without resizer).
704
741
  * Ignored if `paneBorder` is set. Example: `"1px solid #cccccc"`.
705
742
  */
706
743
  paneBorderLeft?: string;
@@ -727,8 +764,8 @@ export type AgentWidgetArtifactsLayoutConfig = {
727
764
  panePadding?: string;
728
765
  /**
729
766
  * Toolbar layout preset.
730
- * - `default` "Artifacts" title, horizontal tabs, text close.
731
- * - `document` view/source toggle, document title, copy / refresh / close; tab strip hidden when only one artifact.
767
+ * - `default`: "Artifacts" title, horizontal tabs, text close.
768
+ * - `document`: view/source toggle, document title, copy / refresh / close; tab strip hidden when only one artifact.
732
769
  * @default 'default'
733
770
  */
734
771
  toolbarPreset?: "default" | "document";
@@ -740,7 +777,7 @@ export type AgentWidgetArtifactsLayoutConfig = {
740
777
  * When `toolbarPreset` is `document`, show a small chevron after the copy control (e.g. menu affordance).
741
778
  */
742
779
  documentToolbarShowCopyChevron?: boolean;
743
- /** Document toolbar icon buttons (view, code, copy, refresh, close) CSS color. Sets `--persona-artifact-doc-toolbar-icon-color` on the widget root. */
780
+ /** Document toolbar icon buttons (view, code, copy, refresh, close): CSS color. Sets `--persona-artifact-doc-toolbar-icon-color` on the widget root. */
744
781
  documentToolbarIconColor?: string;
745
782
  /** Active view/source toggle background. Sets `--persona-artifact-doc-toggle-active-bg`. */
746
783
  documentToolbarToggleActiveBackground?: string;
@@ -896,11 +933,11 @@ export type AgentWidgetToolCallDisplayFeature = {
896
933
  expandable?: boolean;
897
934
  /**
898
935
  * Animation mode applied to the tool call header text while the tool is active.
899
- * - "none" static text, no animation
900
- * - "pulse" opacity pulse on the entire header text
901
- * - "shimmer" monochrome opacity sweep per character
902
- * - "shimmer-color" color gradient sweep per character
903
- * - "rainbow" rainbow color cycle per character
936
+ * - "none": static text, no animation
937
+ * - "pulse": opacity pulse on the entire header text
938
+ * - "shimmer": monochrome opacity sweep per character
939
+ * - "shimmer-color": color gradient sweep per character
940
+ * - "rainbow": rainbow color cycle per character
904
941
  * @default "none"
905
942
  */
906
943
  loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
@@ -930,11 +967,11 @@ export type AgentWidgetReasoningDisplayFeature = {
930
967
  /**
931
968
  * Animation mode applied to the reasoning header text while reasoning is active.
932
969
  * Reuses the same modes as tool call animations.
933
- * - "none" static text, no animation
934
- * - "pulse" opacity pulse on the entire header text
935
- * - "shimmer" monochrome opacity sweep per character
936
- * - "shimmer-color" color gradient sweep per character
937
- * - "rainbow" rainbow color cycle per character
970
+ * - "none": static text, no animation
971
+ * - "pulse": opacity pulse on the entire header text
972
+ * - "shimmer": monochrome opacity sweep per character
973
+ * - "shimmer-color": color gradient sweep per character
974
+ * - "rainbow": rainbow color cycle per character
938
975
  * @default "none"
939
976
  */
940
977
  loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
@@ -944,16 +981,16 @@ export type AgentWidgetReasoningDisplayFeature = {
944
981
  * Reveal animation applied to assistant message text while it is streaming.
945
982
  *
946
983
  * Built-in types always available:
947
- * - `none` text appears as tokens arrive (default).
948
- * - `typewriter` characters fade in with a blinking caret.
949
- * - `pop-bubble` the bubble scales in; text streams normally afterward.
950
- * - `letter-rise` per-char translateY + fade reveal.
951
- * - `word-fade` per-word blur + translateY fade-in.
984
+ * - `none`: text appears as tokens arrive (default).
985
+ * - `typewriter`: characters fade in with a blinking caret.
986
+ * - `pop-bubble`: the bubble scales in; text streams normally afterward.
987
+ * - `letter-rise`: per-char translateY + fade reveal.
988
+ * - `word-fade`: per-word blur + translateY fade-in.
952
989
  *
953
990
  * Subpath plugins (import from `@runtypelabs/persona/animations/*` to register):
954
991
  * - `wipe`, `glyph-cycle`.
955
992
  *
956
- * Custom types are allowed register a plugin with any string name and
993
+ * Custom types are allowed: register a plugin with any string name and
957
994
  * reference it by that name in `type`.
958
995
  */
959
996
  export type AgentWidgetStreamAnimationBuiltinType =
@@ -971,19 +1008,19 @@ export type AgentWidgetStreamAnimationType =
971
1008
 
972
1009
  /**
973
1010
  * Placeholder shown inside a streaming assistant bubble before the first token arrives.
974
- * - `none` use the default typing-dots indicator (existing behavior).
975
- * - `skeleton` shimmer bars, replaced by streaming content once it starts.
1011
+ * - `none`: use the default typing-dots indicator (existing behavior).
1012
+ * - `skeleton`: shimmer bars, replaced by streaming content once it starts.
976
1013
  */
977
1014
  export type AgentWidgetStreamAnimationPlaceholder = "none" | "skeleton";
978
1015
 
979
1016
  /**
980
1017
  * How much of the accumulated streaming content to display while tokens are
981
1018
  * still arriving. Trimming to a boundary means in-progress words or lines
982
- * stay hidden until they complete useful for animations that benefit from
1019
+ * stay hidden until they complete: useful for animations that benefit from
983
1020
  * unit-complete reveals (e.g. wipe, glyph-cycle).
984
- * - `none` show every character as it arrives (default).
985
- * - `word` trim to the last whitespace boundary.
986
- * - `line` trim to the last newline boundary.
1021
+ * - `none`: show every character as it arrives (default).
1022
+ * - `word`: trim to the last whitespace boundary.
1023
+ * - `line`: trim to the last newline boundary.
987
1024
  */
988
1025
  export type AgentWidgetStreamAnimationBuffer = "none" | "word" | "line";
989
1026
 
@@ -1016,7 +1053,7 @@ export type StreamAnimationContext = {
1016
1053
  * widget's style host.
1017
1054
  * - For each streaming assistant message whose `type` matches `name`, the
1018
1055
  * widget applies `containerClass` / `bubbleClass`, wraps text per `wrap`,
1019
- * and if `useCaret` is true appends a blinking caret.
1056
+ * and, if `useCaret` is true, appends a blinking caret.
1020
1057
  * - Hooks fire after the live DOM is morphed; plugins use stable element IDs
1021
1058
  * and `data-preserve-animation` to safely mutate per-char or per-word spans
1022
1059
  * without idiomorph clobbering in-flight work.
@@ -1032,7 +1069,7 @@ export type StreamAnimationPlugin = {
1032
1069
  wrap?: "none" | "char" | "word";
1033
1070
  /**
1034
1071
  * HTML tags whose descendant text is skipped during wrapping. Defaults to
1035
- * `["pre", "code", "a", "script", "style"]` useful for keeping code
1072
+ * `["pre", "code", "a", "script", "style"]`: useful for keeping code
1036
1073
  * blocks legible and link click-targets intact. Plugins that want to
1037
1074
  * animate characters inside inline code (e.g. `glyph-cycle`) can narrow
1038
1075
  * the list.
@@ -1061,8 +1098,7 @@ export type StreamAnimationPlugin = {
1061
1098
  /**
1062
1099
  * Report whether the plugin still has in-flight animation work for a
1063
1100
  * message. When `true`, the widget keeps rendering the message in its
1064
- * "streaming-animated" mode even after `message.streaming` flips false
1065
- * preventing the final non-animated render from yanking the rug out from
1101
+ * "streaming-animated" mode even after `message.streaming` flips false: * preventing the final non-animated render from yanking the rug out from
1066
1102
  * under unfinished per-char cycles or reveals.
1067
1103
  */
1068
1104
  isAnimating?: (message: AgentWidgetMessage) => boolean;
@@ -1076,7 +1112,7 @@ export type AgentWidgetStreamAnimationFeature = {
1076
1112
  /**
1077
1113
  * Per-unit animation duration (ms) for `typewriter`, `letter-rise`, `word-fade`,
1078
1114
  * and per-unit plugin animations. Each arriving character/word animates from
1079
- * invisible to visible over this duration, independent of its position the
1115
+ * invisible to visible over this duration, independent of its position: the
1080
1116
  * streaming cadence itself provides the visible stagger.
1081
1117
  * @default 120
1082
1118
  */
@@ -1137,7 +1173,7 @@ export type AgentWidgetFeatureFlags = {
1137
1173
  * Built-in `suggest_replies` quick-reply chips. When the assistant invokes
1138
1174
  * the tool, the widget shows the suggestions as tappable chips above the
1139
1175
  * composer (reusing the suggestion-chips surface) and immediately resumes
1140
- * the execution fire-and-forget, no user input awaited.
1176
+ * the execution: fire-and-forget, no user input awaited.
1141
1177
  */
1142
1178
  suggestReplies?: AgentWidgetSuggestRepliesFeature;
1143
1179
  };
@@ -1152,14 +1188,14 @@ export type AgentWidgetFeatureFlags = {
1152
1188
  export type AgentWidgetSuggestRepliesFeature = {
1153
1189
  /**
1154
1190
  * Enable the feature. Defaults to true. When false, `suggest_replies`
1155
- * renders as a regular tool bubble and is NOT auto-resumed only set this
1191
+ * renders as a regular tool bubble and is NOT auto-resumed: only set this
1156
1192
  * with no server-side `suggest_replies` declaration, or the execution
1157
1193
  * parks awaiting a resume that never comes.
1158
1194
  */
1159
1195
  enabled?: boolean;
1160
1196
  /**
1161
1197
  * Advertise the built-in `suggest_replies` tool to the agent on every
1162
- * dispatch via `clientTools[]` no server-side `runtimeTools` declaration
1198
+ * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
1163
1199
  * needed. Defaults to `false`: flows that already declare the tool via
1164
1200
  * `runtimeTools` would otherwise present it to the model twice. Ignored
1165
1201
  * when `enabled` is `false`.
@@ -1176,7 +1212,7 @@ export type AskUserQuestionOption = {
1176
1212
  label: string;
1177
1213
  /** Optional long-form description (shown as a subtitle on tap-hover). */
1178
1214
  description?: string;
1179
- /** Optional rich preview reserved for future rendering; ignored in v1. */
1215
+ /** Optional rich preview: reserved for future rendering; ignored in v1. */
1180
1216
  preview?: string;
1181
1217
  };
1182
1218
 
@@ -1232,14 +1268,14 @@ export type AgentWidgetAskUserQuestionFeature = {
1232
1268
  enabled?: boolean;
1233
1269
  /**
1234
1270
  * Advertise the built-in `ask_user_question` tool to the agent on every
1235
- * dispatch via `clientTools[]` no server-side `runtimeTools` declaration
1271
+ * dispatch via `clientTools[]`: no server-side `runtimeTools` declaration
1236
1272
  * needed. The tool ships with a model-facing description and JSON schema
1237
1273
  * matching {@link AskUserQuestionPayload}; when the model calls it, the
1238
1274
  * existing answer-pill sheet renders and the answer resumes the execution.
1239
1275
  *
1240
1276
  * Defaults to `false`: flows that already declare `ask_user_question` via
1241
1277
  * `runtimeTools` would otherwise present the tool to the model twice.
1242
- * Ignored when `enabled` is `false` never offer the agent a question
1278
+ * Ignored when `enabled` is `false`: never offer the agent a question
1243
1279
  * tool the widget can't render an answer UI for.
1244
1280
  */
1245
1281
  expose?: boolean;
@@ -1260,17 +1296,17 @@ export type AgentWidgetAskUserQuestionFeature = {
1260
1296
  /**
1261
1297
  * In grouped (multi-question) mode, auto-advance to the next page after a
1262
1298
  * single-select pill pick or free-text submit on intermediate pages.
1263
- * Defaults to `true`. The final page never auto-submits users always
1299
+ * Defaults to `true`. The final page never auto-submits: users always
1264
1300
  * confirm with an explicit "Submit all" click. Multi-select pages always
1265
1301
  * require an explicit Next regardless of this setting.
1266
1302
  */
1267
1303
  groupedAutoAdvance?: boolean;
1268
1304
  /**
1269
1305
  * Visual layout for the option list.
1270
- * - `"rows"` (default) full-width stacked rows with always-visible
1306
+ * - `"rows"` (default): full-width stacked rows with always-visible
1271
1307
  * descriptions, right-edge number badges (single-select) or checkboxes
1272
1308
  * (multi-select), and an always-visible inline "Other" input.
1273
- * - `"pills"` legacy compact pill list with horizontal wrap; description
1309
+ * - `"pills"`: legacy compact pill list with horizontal wrap; description
1274
1310
  * surfaces as a tooltip and the "Other…" pill expands on click.
1275
1311
  */
1276
1312
  layout?: "rows" | "pills";
@@ -1450,7 +1486,7 @@ export type AgentWidgetDockConfig = {
1450
1486
  *
1451
1487
  * - Set a CSS length (e.g. `"600px"`, `"80vh"`) to override the cap.
1452
1488
  * - Set `false` to disable the guard entirely (the panel then sizes purely
1453
- * from the surrounding layout make sure your page provides a definite
1489
+ * from the surrounding layout: make sure your page provides a definite
1454
1490
  * height all the way down to the dock target's parent).
1455
1491
  *
1456
1492
  * @default "100dvh"
@@ -1525,7 +1561,7 @@ export type AgentWidgetComposerBarConfig = {
1525
1561
  */
1526
1562
  modalMaxHeight?: string;
1527
1563
  /**
1528
- * Configuration for the "peek" banner the chrome-less row above the
1564
+ * Configuration for the "peek" banner: the chrome-less row above the
1529
1565
  * collapsed pill that previews the most recent assistant message.
1530
1566
  */
1531
1567
  peek?: AgentWidgetComposerBarPeekConfig;
@@ -1541,7 +1577,7 @@ export type AgentWidgetComposerBarConfig = {
1541
1577
  * - Otherwise the peek inherits from `features.streamAnimation`.
1542
1578
  *
1543
1579
  * Per-surface carve-outs:
1544
- * - `bubbleClass` from a plugin (used by `pop-bubble`) is ignored the peek
1580
+ * - `bubbleClass` from a plugin (used by `pop-bubble`) is ignored: the peek
1545
1581
  * has no bubble analog.
1546
1582
  * - `containerClass`, `wrap` ("char" | "word"), `useCaret`, `placeholder`
1547
1583
  * ("skeleton"), `buffer` ("word" | "line"), `speed`, `duration`, and
@@ -1794,7 +1830,13 @@ export type AgentWidgetVoiceRecognitionConfig = {
1794
1830
  /** Button border color while speaking. Inherits idle borderColor if not set */
1795
1831
  speakingBorderColor?: string;
1796
1832
  autoResume?: boolean | "assistant";
1797
-
1833
+
1834
+ /**
1835
+ * Called with per-turn latency metrics on the realtime (`runtype`) voice path.
1836
+ * Fires once per turn after the reply completes.
1837
+ */
1838
+ onMetrics?: (metrics: VoiceMetrics) => void;
1839
+
1798
1840
  // Voice provider configuration
1799
1841
  provider?: {
1800
1842
  type: 'browser' | 'runtype' | 'custom';
@@ -1804,15 +1846,31 @@ export type AgentWidgetVoiceRecognitionConfig = {
1804
1846
  };
1805
1847
  runtype?: {
1806
1848
  agentId: string;
1807
- clientToken: string;
1849
+ /** Defaults to the widget's `clientToken` when omitted. */
1850
+ clientToken?: string;
1851
+ /** Defaults to the widget's `apiUrl` when omitted. */
1808
1852
  host?: string;
1809
1853
  voiceId?: string;
1810
- /** Duration of silence (ms) before auto-stopping recording. Default: 2000 */
1854
+ /**
1855
+ * Optional custom streaming playback engine. Defaults to the built-in
1856
+ * `AudioPlaybackManager`. Import `createWorkletPlaybackEngine` from
1857
+ * `@runtypelabs/persona/voice-worklet-player` for a jitter-buffered engine.
1858
+ */
1859
+ createPlaybackEngine?: () => VoicePlaybackEngine | Promise<VoicePlaybackEngine>;
1860
+ /** @deprecated No-op on the realtime path: the server's STT owns turn-taking. */
1811
1861
  pauseDuration?: number;
1812
- /** RMS volume threshold below which counts as silence. Default: 0.01 */
1862
+ /** @deprecated No-op on the realtime path: the server's STT owns turn-taking. */
1813
1863
  silenceThreshold?: number;
1814
1864
  };
1815
- custom?: any;
1865
+ /**
1866
+ * Bring-your-own voice provider. Set `type: 'custom'` and pass either a
1867
+ * ready {@link VoiceProvider} instance or a `() => VoiceProvider` factory
1868
+ * (the factory defers construction until voice is set up). STT-style
1869
+ * providers deliver a final transcript via `onResult` (sent as a user
1870
+ * message); full-duplex providers can drive `onTranscript`/`onMetrics` like
1871
+ * the realtime `runtype` provider.
1872
+ */
1873
+ custom?: VoiceProvider | (() => VoiceProvider);
1816
1874
  };
1817
1875
  };
1818
1876
 
@@ -1836,18 +1894,68 @@ export type TextToSpeechConfig = {
1836
1894
  enabled: boolean;
1837
1895
  /**
1838
1896
  * TTS provider.
1839
- * - `'browser'` Use the Web Speech API for all assistant messages (default).
1840
- * - `'runtype'` Server handles TTS for voice interactions.
1841
- * Set `browserFallback: true` to also speak text-typed responses via the browser.
1897
+ * - `'browser'`: Use the Web Speech API for all assistant messages (default).
1898
+ * - `'runtype'`: Use Runtype-hosted TTS. The realtime voice path speaks
1899
+ * replies during a voice call, and the per-message "Read aloud" button (and
1900
+ * auto-speak of text-typed replies) stream from Runtype's
1901
+ * `POST {host}/v1/agents/:agentId/speak` endpoint via the built-in
1902
+ * {@link SpeechEngine}. `host`/`agentId`/`clientToken` are derived from the
1903
+ * widget config (see `agentId`/`host` below); unless `browserFallback` is
1904
+ * `false`, a missing endpoint or transient failure falls back to the browser
1905
+ * voice so the button is never broken.
1842
1906
  */
1843
1907
  provider?: 'browser' | 'runtype';
1844
1908
  /**
1845
- * When `provider` is `'runtype'`, fall back to browser TTS for assistant
1846
- * messages that the server didn't already speak (e.g. text-typed messages).
1909
+ * When `provider` is `'runtype'`:
1910
+ * - Read-aloud / auto-speak: acts as a safety net — if Runtype TTS is
1911
+ * unreachable (no endpoint yet, network/auth failure) or the Runtype config
1912
+ * is incomplete, speech falls back to the browser Web Speech API instead of
1913
+ * erroring. Set to `false` to surface Runtype errors instead. **Defaults to
1914
+ * on for this path.**
1915
+ * - Auto-speak of text-typed replies also requires this to be truthy (the
1916
+ * realtime voice path already speaks voice-call replies).
1917
+ *
1847
1918
  * Has no effect when provider is `'browser'` (browser TTS is always used).
1848
- * @default false
1849
1919
  */
1850
1920
  browserFallback?: boolean;
1921
+ /**
1922
+ * When `provider` is `'runtype'`, the agent whose configured voice synthesizes
1923
+ * read-aloud / auto-speak audio. Falls back to
1924
+ * `voiceRecognition.provider.runtype.agentId` when omitted. Required (here or
1925
+ * there) for Runtype TTS to activate; without it the browser voice is used.
1926
+ */
1927
+ agentId?: string;
1928
+ /**
1929
+ * When `provider` is `'runtype'`, the Runtype API host (e.g.
1930
+ * `https://api.runtype.com`). Defaults to the widget's `apiUrl`.
1931
+ */
1932
+ host?: string;
1933
+ /**
1934
+ * When `provider` is `'runtype'`, audio (ms) the streaming player buffers
1935
+ * before the first sample and after an underrun. Lower = snappier start;
1936
+ * higher = rides out delivery hiccups. Default 200. Applies to the default
1937
+ * in-bundle player; a custom `createPlaybackEngine` manages its own prebuffer.
1938
+ */
1939
+ prebufferMs?: number;
1940
+ /**
1941
+ * When `provider` is `'runtype'`, an optional factory for the streaming PCM
1942
+ * player that synthesizes read-aloud / auto-speak audio. Defaults to the
1943
+ * in-bundle, main-thread `AudioPlaybackManager` (smooth for steady Runtype
1944
+ * streams, no AudioWorklet — keeps the widget bundle lean).
1945
+ *
1946
+ * For the higher-quality, jitter-buffered AudioWorklet player, import
1947
+ * `createPcmStreamPlayer` from `@runtypelabs/persona/voice-worklet-player` and
1948
+ * pass it here — it then ships in your bundle, not Persona's:
1949
+ *
1950
+ * @example
1951
+ * import { createPcmStreamPlayer } from '@runtypelabs/persona/voice-worklet-player'
1952
+ * textToSpeech: {
1953
+ * enabled: true,
1954
+ * provider: 'runtype',
1955
+ * createPlaybackEngine: () => createPcmStreamPlayer({ prebufferMs: 400 }),
1956
+ * }
1957
+ */
1958
+ createPlaybackEngine?: () => PcmStreamPlayer | Promise<PcmStreamPlayer>;
1851
1959
  /** Voice name to use for browser TTS (e.g., 'Google US English'). If not found, uses auto-detect. */
1852
1960
  voice?: string;
1853
1961
  /**
@@ -1866,8 +1974,78 @@ export type TextToSpeechConfig = {
1866
1974
  rate?: number;
1867
1975
  /** Speech pitch (0 - 2). Default: 1 */
1868
1976
  pitch?: number;
1977
+ /**
1978
+ * Factory for a custom {@link SpeechEngine}, used by both the auto-speak path
1979
+ * and the per-message "Read aloud" action. When omitted, the browser Web
1980
+ * Speech API engine is used. Return a hosted engine here to use server-side
1981
+ * TTS (e.g. Runtype) — such an engine can stream audio through the realtime
1982
+ * voice {@link VoicePlaybackEngine}. May be async (it is resolved on first
1983
+ * playback, inside the user gesture).
1984
+ */
1985
+ createEngine?: () => SpeechEngine | Promise<SpeechEngine>;
1869
1986
  };
1870
1987
 
1988
+ /**
1989
+ * Playback state of the per-message "Read aloud" action.
1990
+ * - `idle`: not reading this message.
1991
+ * - `loading`: the engine is preparing audio (e.g. a hosted TTS fetch). Browser
1992
+ * engines pass through this state almost instantly.
1993
+ * - `playing`: audio is playing.
1994
+ * - `paused`: playback is paused and resumable.
1995
+ */
1996
+ export type ReadAloudState = "idle" | "loading" | "playing" | "paused";
1997
+
1998
+ /** A unit of speech handed to a {@link SpeechEngine}. */
1999
+ export interface SpeechRequest {
2000
+ /** Plain text to speak. The widget strips Markdown before calling the engine. */
2001
+ text: string;
2002
+ /** Preferred voice identifier (engine-specific; browser = voice name). */
2003
+ voice?: string;
2004
+ /** Speech rate (engine-specific range; browser: 0.1–10, default 1). */
2005
+ rate?: number;
2006
+ /** Speech pitch (engine-specific range; browser: 0–2, default 1). */
2007
+ pitch?: number;
2008
+ }
2009
+
2010
+ /** Lifecycle callbacks a {@link SpeechEngine} invokes during playback. */
2011
+ export interface SpeechCallbacks {
2012
+ /** Audio has started playing. */
2013
+ onStart?: () => void;
2014
+ /** Playback finished naturally (or was canceled). */
2015
+ onEnd?: () => void;
2016
+ /** Playback failed. */
2017
+ onError?: (error: Error) => void;
2018
+ }
2019
+
2020
+ /**
2021
+ * Pluggable text-to-speech engine behind the "Read aloud" message action.
2022
+ *
2023
+ * The widget ships a browser Web Speech API engine by default. Provide a hosted
2024
+ * engine (Runtype TTS, ElevenLabs, a server proxy, …) by implementing this
2025
+ * interface and returning it from {@link TextToSpeechConfig.createEngine}; a
2026
+ * server engine can stream audio into the realtime voice
2027
+ * {@link VoicePlaybackEngine}.
2028
+ */
2029
+ export interface SpeechEngine {
2030
+ /** Stable identifier, e.g. `"browser"`, `"runtype"`, or a custom id. */
2031
+ readonly id: string;
2032
+ /**
2033
+ * Whether {@link pause}/{@link resume} are supported. When `false`, the UI
2034
+ * offers play/stop only (tapping a playing message stops it).
2035
+ */
2036
+ readonly supportsPause: boolean;
2037
+ /** Begin speaking. Drives the lifecycle through `callbacks`. */
2038
+ speak(request: SpeechRequest, callbacks: SpeechCallbacks): void;
2039
+ /** Pause playback (no-op if unsupported). */
2040
+ pause(): void;
2041
+ /** Resume paused playback (no-op if unsupported). */
2042
+ resume(): void;
2043
+ /** Stop playback and discard any queued audio. */
2044
+ stop(): void;
2045
+ /** Optional teardown of engine resources. */
2046
+ destroy?(): void;
2047
+ }
2048
+
1871
2049
  // ============================================================================
1872
2050
  // Voice Provider Types
1873
2051
  // ============================================================================
@@ -1900,6 +2078,69 @@ export type VoiceStatus =
1900
2078
  | 'error'
1901
2079
  | 'idle';
1902
2080
 
2081
+ /**
2082
+ * Per-turn latency metrics emitted by the realtime voice path.
2083
+ * The wire frame is snake_case (`metrics`); decoded to camelCase at the
2084
+ * provider boundary before being surfaced through `onMetrics`.
2085
+ */
2086
+ export type VoiceMetrics = {
2087
+ /** LLM generation time (ms). */
2088
+ llmMs?: number;
2089
+ /** Text-to-speech synthesis time (ms). */
2090
+ ttsMs?: number;
2091
+ /** Time to first audio frame (ms). */
2092
+ firstAudioMs?: number;
2093
+ /** Total turn time (ms). */
2094
+ totalMs?: number;
2095
+ };
2096
+
2097
+ /**
2098
+ * Streaming PCM playback engine used by the realtime voice provider.
2099
+ *
2100
+ * The provider feeds raw PCM16 LE mono @ 24kHz (WAV header already stripped)
2101
+ * via `enqueue`, signals end-of-reply with `markStreamEnd`, and learns when
2102
+ * playback has fully drained via `onFinished`. The default implementation is
2103
+ * `AudioPlaybackManager`; an optional jitter-buffered AudioWorklet engine ships
2104
+ * from `@runtypelabs/persona/voice-worklet-player` and can be injected via the
2105
+ * `runtype.createPlaybackEngine` config hook.
2106
+ */
2107
+ export interface VoicePlaybackEngine {
2108
+ /** Enqueue a raw PCM16 LE mono @ 24kHz chunk (no WAV header). */
2109
+ enqueue(pcm: Uint8Array): void;
2110
+ /** Signal that no more chunks will arrive for the current reply. */
2111
+ markStreamEnd(): void;
2112
+ /** Immediately stop playback and discard queued audio. */
2113
+ flush(): void;
2114
+ /** Register a callback fired once all queued audio has finished playing. */
2115
+ onFinished(callback: () => void): void;
2116
+ /** Release all audio resources. */
2117
+ destroy(): Promise<void> | void;
2118
+ }
2119
+
2120
+ /**
2121
+ * A {@link VoicePlaybackEngine} that also supports pause/resume. Returned by
2122
+ * `createPcmStreamPlayer` (`@runtypelabs/persona/voice-worklet-player`): a
2123
+ * jitter-buffered AudioWorklet player for raw PCM16 / 24 kHz / mono streams.
2124
+ * Reuse it inside a hosted {@link SpeechEngine} to get gapless playback with a
2125
+ * configurable prebuffer and graceful underrun handling — feed each streamed
2126
+ * chunk to {@link VoicePlaybackEngine.enqueue} and the worklet does the rest.
2127
+ */
2128
+ export interface PcmStreamPlayer extends VoicePlaybackEngine {
2129
+ /** Pause playback; the audio clock suspends and {@link resume} continues in place. */
2130
+ pause(): void;
2131
+ /** Resume playback after {@link pause}. */
2132
+ resume(): void;
2133
+ /**
2134
+ * Register a callback fired once audible playback actually begins — i.e. the
2135
+ * prebuffer waterline filled and the first sample reached the output. Use this
2136
+ * (rather than "first chunk enqueued") to flip a UI from loading to playing, so
2137
+ * the spinner holds through the prebuffer. Fires once per playback session
2138
+ * (cleared by {@link VoicePlaybackEngine.flush}); a mid-reply underrun re-buffer
2139
+ * does not re-fire it.
2140
+ */
2141
+ onStarted(callback: () => void): void;
2142
+ }
2143
+
1903
2144
  /**
1904
2145
  * Voice provider configuration
1905
2146
  * Determines which voice provider to use and its specific settings
@@ -1912,15 +2153,29 @@ export type VoiceConfig = {
1912
2153
  };
1913
2154
  runtype?: {
1914
2155
  agentId: string;
1915
- clientToken: string;
2156
+ /** Defaults to the widget's `clientToken` when omitted. */
2157
+ clientToken?: string;
2158
+ /** Defaults to the widget's `apiUrl` (as a ws(s):// base) when omitted. */
1916
2159
  host?: string;
1917
2160
  voiceId?: string;
1918
- /** Duration of silence (ms) before auto-stopping recording. Default: 2000 */
2161
+ /**
2162
+ * Optional custom streaming playback engine. Defaults to the built-in
2163
+ * `AudioPlaybackManager`. Import `createWorkletPlaybackEngine` from
2164
+ * `@runtypelabs/persona/voice-worklet-player` for a jitter-buffered engine.
2165
+ */
2166
+ createPlaybackEngine?: () => VoicePlaybackEngine | Promise<VoicePlaybackEngine>;
2167
+ /** @deprecated No-op on the realtime path: the server's STT owns turn-taking. */
1919
2168
  pauseDuration?: number;
1920
- /** RMS volume threshold below which counts as silence. Default: 0.01 */
2169
+ /** @deprecated No-op on the realtime path: the server's STT owns turn-taking. */
1921
2170
  silenceThreshold?: number;
1922
2171
  };
1923
- custom?: any;
2172
+ /**
2173
+ * Bring-your-own voice provider for `type: 'custom'`: either a ready
2174
+ * {@link VoiceProvider} instance or a `() => VoiceProvider` factory. The
2175
+ * factory form defers construction (WebSocket/AudioContext/etc.) until
2176
+ * `createVoiceProvider` runs.
2177
+ */
2178
+ custom?: VoiceProvider | (() => VoiceProvider);
1924
2179
  };
1925
2180
 
1926
2181
  /**
@@ -1942,13 +2197,27 @@ export interface VoiceProvider {
1942
2197
  /** Register a callback fired when recording stops and audio is about to be sent */
1943
2198
  onProcessingStart?(callback: () => void): void;
1944
2199
 
2200
+ /**
2201
+ * Register a callback for incremental transcript updates during a voice turn.
2202
+ * `isFinal=false` is a live interim update (user partials, or assistant deltas
2203
+ * on providers that stream them); `isFinal=true` finalizes that role's text.
2204
+ * On the realtime `runtype` path, interim updates fire for the `user` only and
2205
+ * the `assistant` arrives as a single final.
2206
+ */
2207
+ onTranscript?(
2208
+ callback: (role: 'user' | 'assistant', text: string, isFinal: boolean) => void,
2209
+ ): void;
2210
+
2211
+ /** Register a callback for per-turn latency metrics (realtime path). */
2212
+ onMetrics?(callback: (metrics: VoiceMetrics) => void): void;
2213
+
1945
2214
  /** Returns the current interruption mode (only meaningful for Runtype provider) */
1946
2215
  getInterruptionMode?(): "none" | "cancel" | "barge-in";
1947
2216
 
1948
2217
  /** Returns true if the barge-in mic stream is alive (hot mic between turns) */
1949
2218
  isBargeInActive?(): boolean;
1950
2219
 
1951
- /** Tear down the barge-in mic pipeline "hang up" the always-on mic */
2220
+ /** Tear down the barge-in mic pipeline: "hang up" the always-on mic */
1952
2221
  deactivateBargeIn?(): Promise<void>;
1953
2222
 
1954
2223
  /** Stop playback / cancel in-flight request without starting recording */
@@ -1963,7 +2232,7 @@ export type AgentWidgetApprovalDecisionOptions = {
1963
2232
  /**
1964
2233
  * The user chose a "remember this" affordance (e.g. an "Always allow"
1965
2234
  * button) rather than a one-time decision. The widget resolves the *current*
1966
- * approval identically whether or not this is set an approval bubble is a
2235
+ * approval identically whether or not this is set: an approval bubble is a
1967
2236
  * single binary gate (`approved`/`denied`). Persisting a don't-ask-again
1968
2237
  * policy for *future* approvals (auto-resolving them, or not surfacing them)
1969
2238
  * is the integrator's responsibility, typically inside `onDecision`.
@@ -2018,9 +2287,9 @@ export type AgentWidgetApprovalConfig = {
2018
2287
  /**
2019
2288
  * How the technical details (the tool's agent-facing description and the
2020
2289
  * raw parameters JSON) are presented:
2021
- * - `"collapsed"` (default) hidden behind a "Show details" toggle
2022
- * - `"expanded"` visible, with the toggle available to hide them
2023
- * - `"hidden"` never rendered
2290
+ * - `"collapsed"` (default): hidden behind a "Show details" toggle
2291
+ * - `"expanded"`: visible, with the toggle available to hide them
2292
+ * - `"hidden"`: never rendered
2024
2293
  */
2025
2294
  detailsDisplay?: "collapsed" | "expanded" | "hidden";
2026
2295
  /** Label for the toggle that reveals the technical details */
@@ -2042,7 +2311,7 @@ export type AgentWidgetApprovalConfig = {
2042
2311
  displayTitle?: string;
2043
2312
  /**
2044
2313
  * Agent-authored justification for this specific call, when the agent
2045
- * provided one. It is the agent's own claim if you fold it into the
2314
+ * provided one. It is the agent's own claim: if you fold it into the
2046
2315
  * summary, keep it attributed to the agent.
2047
2316
  */
2048
2317
  reason?: string;
@@ -2148,7 +2417,7 @@ export type AgentWidgetToolCallConfig = {
2148
2417
  *
2149
2418
  * **Placeholders:** `{toolName}` (tool name), `{duration}` (live-updating elapsed time).
2150
2419
  *
2151
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` parsed at render time and
2420
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: parsed at render time and
2152
2421
  * applied as styled `<span>` elements. Works with all animation modes.
2153
2422
  *
2154
2423
  * When not set, falls back to the current `collapsedMode` behavior.
@@ -2161,7 +2430,7 @@ export type AgentWidgetToolCallConfig = {
2161
2430
  *
2162
2431
  * **Placeholders:** `{toolName}` (tool name), `{duration}` (final elapsed time).
2163
2432
  *
2164
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` same syntax as `activeTextTemplate`.
2433
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: same syntax as `activeTextTemplate`.
2165
2434
  *
2166
2435
  * When not set, falls back to the existing "Used tool for X seconds" text.
2167
2436
  * @example "Finished {toolName} ~{duration}~"
@@ -2223,7 +2492,7 @@ export type AgentWidgetReasoningConfig = {
2223
2492
  *
2224
2493
  * **Placeholders:** `{duration}` (live-updating elapsed time).
2225
2494
  *
2226
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` parsed at render time.
2495
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: parsed at render time.
2227
2496
  *
2228
2497
  * When not set, falls back to the default "Thinking..." text.
2229
2498
  * @example "Thinking... ~{duration}~"
@@ -2234,7 +2503,7 @@ export type AgentWidgetReasoningConfig = {
2234
2503
  *
2235
2504
  * **Placeholders:** `{duration}` (final elapsed time).
2236
2505
  *
2237
- * **Inline formatting:** `~dim~`, `*italic*`, `**bold**` same syntax as `activeTextTemplate`.
2506
+ * **Inline formatting:** `~dim~`, `*italic*`, `**bold**`: same syntax as `activeTextTemplate`.
2238
2507
  *
2239
2508
  * When not set, falls back to the default "Thought for X seconds" text.
2240
2509
  * @example "Thought for ~{duration}~"
@@ -2345,7 +2614,7 @@ export type AgentWidgetSSEEventResult = {
2345
2614
  done?: boolean;
2346
2615
  /** Error message if an error occurred */
2347
2616
  error?: string;
2348
- /** Text segment identity when this changes, a new assistant message bubble is created */
2617
+ /** Text segment identity: when this changes, a new assistant message bubble is created */
2349
2618
  partId?: string;
2350
2619
  } | null;
2351
2620
 
@@ -2433,7 +2702,7 @@ export type ClientChatRequest = {
2433
2702
  /** Per-turn inputs for Runtype prompt templates (e.g. {{page_url}}). */
2434
2703
  inputs?: Record<string, unknown>;
2435
2704
  context?: Record<string, unknown>;
2436
- /** WebMCP page-discovered tools same shape as `dispatch.clientTools[]`. */
2705
+ /** WebMCP page-discovered tools: same shape as `dispatch.clientTools[]`. */
2437
2706
  clientTools?: ClientToolDefinition[];
2438
2707
  /**
2439
2708
  * Diff-only / send-once: order-independent fingerprint of the client tool set.
@@ -3453,7 +3722,7 @@ export type AgentWidgetConfig = {
3453
3722
  * Per-stop-reason copy for the inline notice rendered on assistant
3454
3723
  * bubbles when the runtime reports a non-natural stop (e.g. the agent
3455
3724
  * loop hit `max_tool_calls` and was cut off mid-loop). Each key is
3456
- * optional keys you omit fall back to the built-in defaults. Set a
3725
+ * optional: keys you omit fall back to the built-in defaults. Set a
3457
3726
  * key to an empty string to suppress the notice for that reason.
3458
3727
  */
3459
3728
  stopReasonNotice?: Partial<Record<StopReasonKind, string>>;
@@ -3546,12 +3815,12 @@ export type AgentWidgetConfig = {
3546
3815
  */
3547
3816
  approval?: AgentWidgetApprovalConfig | false;
3548
3817
  /**
3549
- * WebMCP consume page-registered tools (`document.modelContext.registerTool`).
3818
+ * WebMCP: consume page-registered tools (`document.modelContext.registerTool`).
3550
3819
  * When `enabled`, the widget installs `@mcp-b/webmcp-polyfill`, snapshots the
3551
3820
  * registry on every dispatch, ships it as `clientTools[]`, and executes
3552
3821
  * returned `webmcp:*` tool calls with confirm-by-default gating.
3553
3822
  *
3554
- * Server-side policy on the chat surface is the source of truth these
3823
+ * Server-side policy on the chat surface is the source of truth: these
3555
3824
  * fields layer on top.
3556
3825
  *
3557
3826
  * @example
@@ -3585,7 +3854,7 @@ export type AgentWidgetConfig = {
3585
3854
  * This hook runs synchronously and must return the (potentially modified) state.
3586
3855
  *
3587
3856
  * Returning `{ state, open: true }` also signals that the widget panel should
3588
- * open after initialization useful when injecting a post-navigation message
3857
+ * open after initialization: useful when injecting a post-navigation message
3589
3858
  * that the user should immediately see.
3590
3859
  *
3591
3860
  * @example
@@ -3908,7 +4177,7 @@ export type AgentWidgetConfig = {
3908
4177
  * Setting this to `false` is the explicit kill-switch: it disables UI-state
3909
4178
  * persistence **and** message-history persistence. When `false`, any
3910
4179
  * `storageAdapter` you configure is ignored and the default localStorage
3911
- * adapter is not created no chat history is read or written. Pass `true`
4180
+ * adapter is not created: no chat history is read or written. Pass `true`
3912
4181
  * (or omit) to keep the default behavior of persisting messages via the
3913
4182
  * configured `storageAdapter` (or the built-in localStorage adapter).
3914
4183
  *
@@ -3937,7 +4206,7 @@ export type AgentWidgetConfig = {
3937
4206
  *
3938
4207
  * @example
3939
4208
  * ```typescript
3940
- * // Ephemeral widget no message history written anywhere
4209
+ * // Ephemeral widget: no message history written anywhere
3941
4210
  * config: {
3942
4211
  * persistState: false
3943
4212
  * }
@@ -4009,7 +4278,7 @@ export type AgentWidgetApproval = {
4009
4278
  * Agent-authored justification for this specific call (the agent's own
4010
4279
  * claim about its intent, extracted server-side from the reserved
4011
4280
  * `_approvalReason` parameter). Render it attributed to the agent and as
4012
- * plain text it is approver context, not a system statement.
4281
+ * plain text: it is approver context, not a system statement.
4013
4282
  */
4014
4283
  reason?: string;
4015
4284
  parameters?: unknown;
@@ -4021,16 +4290,16 @@ export type AgentWidgetMessageVariant = "assistant" | "reasoning" | "tool" | "ap
4021
4290
  /**
4022
4291
  * Per-turn / per-step stop reason emitted by the runtime on
4023
4292
  * `agent_turn_complete` and `step_complete` SSE events. The vocabulary is
4024
- * owned by the upstream Runtype API do not extend without coordination.
4293
+ * owned by the upstream Runtype API: do not extend without coordination.
4025
4294
  *
4026
- * - `end_turn` natural completion (no affordance needed)
4027
- * - `max_tool_calls` agent loop tripped the configured tool-call ceiling
4028
- * - `length` provider hit max output tokens
4029
- * - `content_filter` provider content filter intervened
4030
- * - `error` provider/runtime error (prefer existing error rendering)
4031
- * - `unknown` explicitly reported but uninformative
4295
+ * - `end_turn`: natural completion (no affordance needed)
4296
+ * - `max_tool_calls`: agent loop tripped the configured tool-call ceiling
4297
+ * - `length`: provider hit max output tokens
4298
+ * - `content_filter`: provider content filter intervened
4299
+ * - `error`: provider/runtime error (prefer existing error rendering)
4300
+ * - `unknown`: explicitly reported but uninformative
4032
4301
  *
4033
- * Absent (`undefined`) means "not reported" distinct from `'unknown'`.
4302
+ * Absent (`undefined`) means "not reported": distinct from `'unknown'`.
4034
4303
  */
4035
4304
  export type StopReasonKind = RuntypeStopReasonKind;
4036
4305
 
@@ -4215,7 +4484,7 @@ export type InjectMessageOptions = {
4215
4484
 
4216
4485
  /**
4217
4486
  * Raw structured payload (typically a JSON string) representing the
4218
- * full directive that produced this message e.g. `{ "text": "...",
4487
+ * full directive that produced this message: e.g. `{ "text": "...",
4219
4488
  * "component": "Foo", "props": {...} }`.
4220
4489
  *
4221
4490
  * Mirrors the field populated by stream parsers during normal LLM
@@ -4251,7 +4520,7 @@ export type InjectSystemMessageOptions = Omit<InjectMessageOptions, "role">;
4251
4520
 
4252
4521
  /**
4253
4522
  * Options for injecting an assistant message that renders as a component
4254
- * directive sugar over `injectAssistantMessage` for the common case of
4523
+ * directive: sugar over `injectAssistantMessage` for the common case of
4255
4524
  * "render this registered component, same as if the LLM had emitted it".
4256
4525
  *
4257
4526
  * Equivalent to calling `injectAssistantMessage({ content: text, rawContent: