@runtypelabs/persona 3.5.2 → 3.7.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 (53) hide show
  1. package/dist/index.cjs +46 -46
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +44 -0
  4. package/dist/index.d.ts +44 -0
  5. package/dist/index.global.js +70 -70
  6. package/dist/index.global.js.map +1 -1
  7. package/dist/index.js +46 -46
  8. package/dist/index.js.map +1 -1
  9. package/dist/theme-editor.cjs +18015 -0
  10. package/dist/theme-editor.d.cts +3888 -0
  11. package/dist/theme-editor.d.ts +3888 -0
  12. package/dist/theme-editor.js +17909 -0
  13. package/dist/theme-reference.cjs +1 -1
  14. package/dist/theme-reference.d.cts +33 -0
  15. package/dist/theme-reference.d.ts +33 -0
  16. package/dist/theme-reference.js +1 -1
  17. package/dist/widget.css +69 -25
  18. package/package.json +9 -7
  19. package/src/components/artifact-card.ts +1 -1
  20. package/src/components/composer-builder.ts +16 -29
  21. package/src/components/demo-carousel.ts +5 -5
  22. package/src/components/event-stream-view.test.ts +142 -0
  23. package/src/components/event-stream-view.ts +68 -29
  24. package/src/components/header-builder.ts +2 -2
  25. package/src/components/launcher.ts +9 -0
  26. package/src/components/message-bubble.ts +9 -3
  27. package/src/components/suggestions.ts +1 -1
  28. package/src/defaults.ts +24 -9
  29. package/src/scroll-to-bottom-defaults.test.ts +13 -0
  30. package/src/styles/widget.css +69 -25
  31. package/src/theme-editor/color-utils.ts +252 -0
  32. package/src/theme-editor/index.ts +131 -0
  33. package/src/theme-editor/presets.ts +144 -0
  34. package/src/theme-editor/preview-utils.ts +265 -0
  35. package/src/theme-editor/preview.ts +445 -0
  36. package/src/theme-editor/role-mappings.ts +343 -0
  37. package/src/theme-editor/sections.test.ts +43 -0
  38. package/src/theme-editor/sections.ts +994 -0
  39. package/src/theme-editor/state.ts +298 -0
  40. package/src/theme-editor/types.ts +177 -0
  41. package/src/theme-editor.ts +2 -0
  42. package/src/theme-reference.ts +8 -0
  43. package/src/types/theme.ts +11 -0
  44. package/src/types.ts +22 -0
  45. package/src/ui.scroll.test.ts +554 -0
  46. package/src/ui.ts +223 -133
  47. package/src/utils/auto-follow.test.ts +110 -0
  48. package/src/utils/auto-follow.ts +112 -0
  49. package/src/utils/plugins.ts +1 -1
  50. package/src/utils/theme.test.ts +44 -8
  51. package/src/utils/theme.ts +11 -11
  52. package/src/utils/tokens.ts +137 -41
  53. package/widget.css +0 -1
@@ -0,0 +1,3888 @@
1
+ type TokenType = 'color' | 'spacing' | 'typography' | 'shadow' | 'border' | 'radius';
2
+ type TokenReference<_T extends TokenType = TokenType> = string;
3
+ interface ColorShade {
4
+ 50?: string;
5
+ 100?: string;
6
+ 200?: string;
7
+ 300?: string;
8
+ 400?: string;
9
+ 500?: string;
10
+ 600?: string;
11
+ 700?: string;
12
+ 800?: string;
13
+ 900?: string;
14
+ 950?: string;
15
+ [key: string]: string | undefined;
16
+ }
17
+ interface ColorPalette {
18
+ gray: ColorShade;
19
+ primary: ColorShade;
20
+ secondary: ColorShade;
21
+ accent: ColorShade;
22
+ success: ColorShade;
23
+ warning: ColorShade;
24
+ error: ColorShade;
25
+ info: ColorShade;
26
+ [key: string]: ColorShade;
27
+ }
28
+ interface SpacingScale {
29
+ 0: string;
30
+ 1: string;
31
+ 2: string;
32
+ 3: string;
33
+ 4: string;
34
+ 5: string;
35
+ 6: string;
36
+ 8: string;
37
+ 10: string;
38
+ 12: string;
39
+ 16: string;
40
+ 20: string;
41
+ 24: string;
42
+ 32: string;
43
+ 40: string;
44
+ 48: string;
45
+ 56: string;
46
+ 64: string;
47
+ [key: string]: string;
48
+ }
49
+ interface ShadowScale {
50
+ none: string;
51
+ sm: string;
52
+ md: string;
53
+ lg: string;
54
+ xl: string;
55
+ '2xl': string;
56
+ [key: string]: string;
57
+ }
58
+ interface BorderScale {
59
+ none: string;
60
+ sm: string;
61
+ md: string;
62
+ lg: string;
63
+ [key: string]: string;
64
+ }
65
+ interface RadiusScale {
66
+ none: string;
67
+ sm: string;
68
+ md: string;
69
+ lg: string;
70
+ xl: string;
71
+ full: string;
72
+ [key: string]: string;
73
+ }
74
+ interface TypographyScale {
75
+ fontFamily: {
76
+ sans: string;
77
+ serif: string;
78
+ mono: string;
79
+ };
80
+ fontSize: {
81
+ xs: string;
82
+ sm: string;
83
+ base: string;
84
+ lg: string;
85
+ xl: string;
86
+ '2xl': string;
87
+ '3xl': string;
88
+ '4xl': string;
89
+ };
90
+ fontWeight: {
91
+ normal: string;
92
+ medium: string;
93
+ semibold: string;
94
+ bold: string;
95
+ };
96
+ lineHeight: {
97
+ tight: string;
98
+ normal: string;
99
+ relaxed: string;
100
+ };
101
+ }
102
+ interface SemanticColors {
103
+ primary: TokenReference<'color'>;
104
+ secondary: TokenReference<'color'>;
105
+ accent: TokenReference<'color'>;
106
+ surface: TokenReference<'color'>;
107
+ background: TokenReference<'color'>;
108
+ container: TokenReference<'color'>;
109
+ text: TokenReference<'color'>;
110
+ textMuted: TokenReference<'color'>;
111
+ textInverse: TokenReference<'color'>;
112
+ border: TokenReference<'color'>;
113
+ divider: TokenReference<'color'>;
114
+ interactive: {
115
+ default: TokenReference<'color'>;
116
+ hover: TokenReference<'color'>;
117
+ focus: TokenReference<'color'>;
118
+ active: TokenReference<'color'>;
119
+ disabled: TokenReference<'color'>;
120
+ };
121
+ feedback: {
122
+ success: TokenReference<'color'>;
123
+ warning: TokenReference<'color'>;
124
+ error: TokenReference<'color'>;
125
+ info: TokenReference<'color'>;
126
+ };
127
+ }
128
+ interface SemanticSpacing {
129
+ xs: TokenReference<'spacing'>;
130
+ sm: TokenReference<'spacing'>;
131
+ md: TokenReference<'spacing'>;
132
+ lg: TokenReference<'spacing'>;
133
+ xl: TokenReference<'spacing'>;
134
+ '2xl': TokenReference<'spacing'>;
135
+ }
136
+ interface SemanticTypography {
137
+ fontFamily: TokenReference<'typography'>;
138
+ fontSize: TokenReference<'typography'>;
139
+ fontWeight: TokenReference<'typography'>;
140
+ lineHeight: TokenReference<'typography'>;
141
+ }
142
+ interface SemanticTokens {
143
+ colors: SemanticColors;
144
+ spacing: SemanticSpacing;
145
+ typography: SemanticTypography;
146
+ }
147
+ interface ComponentTokenSet {
148
+ background?: TokenReference<'color'>;
149
+ foreground?: TokenReference<'color'>;
150
+ border?: TokenReference<'color'>;
151
+ borderRadius?: TokenReference<'radius'>;
152
+ padding?: TokenReference<'spacing'>;
153
+ margin?: TokenReference<'spacing'>;
154
+ shadow?: TokenReference<'shadow'>;
155
+ opacity?: number;
156
+ }
157
+ interface ButtonTokens extends ComponentTokenSet {
158
+ primary: ComponentTokenSet;
159
+ secondary: ComponentTokenSet;
160
+ ghost: ComponentTokenSet;
161
+ }
162
+ interface InputTokens extends ComponentTokenSet {
163
+ background: TokenReference<'color'>;
164
+ placeholder: TokenReference<'color'>;
165
+ focus: {
166
+ border: TokenReference<'color'>;
167
+ ring: TokenReference<'color'>;
168
+ };
169
+ }
170
+ interface LauncherTokens extends ComponentTokenSet {
171
+ size: string;
172
+ iconSize: string;
173
+ shadow: TokenReference<'shadow'>;
174
+ }
175
+ interface PanelTokens extends ComponentTokenSet {
176
+ width: string;
177
+ maxWidth: string;
178
+ height: string;
179
+ maxHeight: string;
180
+ }
181
+ interface HeaderTokens extends ComponentTokenSet {
182
+ background: TokenReference<'color'>;
183
+ border: TokenReference<'color'>;
184
+ borderRadius: TokenReference<'radius'>;
185
+ /** Background of the rounded avatar tile next to the title (Lucide / emoji / image). */
186
+ iconBackground: TokenReference<'color'>;
187
+ /** Foreground (glyph stroke or emoji text) on the header avatar tile. */
188
+ iconForeground: TokenReference<'color'>;
189
+ /** Header title line (next to the icon, or minimal layout title). */
190
+ titleForeground: TokenReference<'color'>;
191
+ /** Header subtitle line under the title. */
192
+ subtitleForeground: TokenReference<'color'>;
193
+ /** Default color for clear / close icon buttons when launcher overrides are unset. */
194
+ actionIconForeground: TokenReference<'color'>;
195
+ /** Box-shadow on the header (e.g., a fade shadow to replace the default border). */
196
+ shadow?: string;
197
+ /** Override the header bottom border (e.g., `none`). */
198
+ borderBottom?: string;
199
+ }
200
+ interface MessageTokens {
201
+ user: {
202
+ background: TokenReference<'color'>;
203
+ text: TokenReference<'color'>;
204
+ borderRadius: TokenReference<'radius'>;
205
+ /** User bubble box-shadow (token ref or raw CSS, e.g. `none`). */
206
+ shadow?: string;
207
+ };
208
+ assistant: {
209
+ background: TokenReference<'color'>;
210
+ text: TokenReference<'color'>;
211
+ borderRadius: TokenReference<'radius'>;
212
+ /** Assistant bubble border color (CSS color). */
213
+ border?: TokenReference<'color'>;
214
+ /** Assistant bubble box-shadow (token ref or raw CSS, e.g. `none`). */
215
+ shadow?: string;
216
+ };
217
+ }
218
+ interface MarkdownTokens {
219
+ inlineCode: {
220
+ background: TokenReference<'color'>;
221
+ foreground: TokenReference<'color'>;
222
+ };
223
+ /** Foreground for `<a>` in rendered markdown (assistant bubbles + artifact pane). */
224
+ link?: {
225
+ foreground: TokenReference<'color'>;
226
+ };
227
+ /**
228
+ * Body font for rendered markdown blocks (artifact pane + markdown bubbles).
229
+ * Use a raw CSS `font-family` value, e.g. `Georgia, serif`.
230
+ */
231
+ prose?: {
232
+ fontFamily?: string;
233
+ };
234
+ /** Optional heading scale overrides (raw CSS or resolvable token paths). */
235
+ heading?: {
236
+ h1?: {
237
+ fontSize?: string;
238
+ fontWeight?: string;
239
+ };
240
+ h2?: {
241
+ fontSize?: string;
242
+ fontWeight?: string;
243
+ };
244
+ };
245
+ }
246
+ interface VoiceTokens {
247
+ recording: {
248
+ indicator: TokenReference<'color'>;
249
+ background: TokenReference<'color'>;
250
+ border: TokenReference<'color'>;
251
+ };
252
+ processing: {
253
+ icon: TokenReference<'color'>;
254
+ background: TokenReference<'color'>;
255
+ };
256
+ speaking: {
257
+ icon: TokenReference<'color'>;
258
+ };
259
+ }
260
+ interface ApprovalTokens {
261
+ requested: {
262
+ background: TokenReference<'color'>;
263
+ border: TokenReference<'color'>;
264
+ text: TokenReference<'color'>;
265
+ };
266
+ approve: ComponentTokenSet;
267
+ deny: ComponentTokenSet;
268
+ }
269
+ interface AttachmentTokens {
270
+ image: {
271
+ background: TokenReference<'color'>;
272
+ border: TokenReference<'color'>;
273
+ };
274
+ }
275
+ /** Tool-call row chrome (collapsible tool bubbles). */
276
+ interface ToolBubbleTokens {
277
+ /** Box-shadow for tool bubbles (token ref or raw CSS, e.g. `none`). */
278
+ shadow: string;
279
+ }
280
+ /** Reasoning / “thinking” row chrome. */
281
+ interface ReasoningBubbleTokens {
282
+ shadow: string;
283
+ }
284
+ /** Composer (message input) chrome. */
285
+ interface ComposerChromeTokens {
286
+ /** Box-shadow on the composer form (raw CSS, e.g. `none`). */
287
+ shadow: string;
288
+ }
289
+ /** Artifact toolbar chrome. */
290
+ interface ArtifactToolbarTokens {
291
+ iconHoverColor?: string;
292
+ iconHoverBackground?: string;
293
+ iconPadding?: string;
294
+ iconBorderRadius?: string;
295
+ iconBorder?: string;
296
+ toggleGroupGap?: string;
297
+ toggleBorderRadius?: string;
298
+ copyBackground?: string;
299
+ copyBorder?: string;
300
+ copyColor?: string;
301
+ copyBorderRadius?: string;
302
+ copyPadding?: string;
303
+ copyMenuBackground?: string;
304
+ copyMenuBorder?: string;
305
+ copyMenuShadow?: string;
306
+ copyMenuBorderRadius?: string;
307
+ copyMenuItemHoverBackground?: string;
308
+ /** Base background of icon buttons (defaults to --persona-surface). */
309
+ iconBackground?: string;
310
+ /** Border on the toolbar (e.g., `none` to remove the bottom border). */
311
+ toolbarBorder?: string;
312
+ }
313
+ /** Artifact tab strip chrome. */
314
+ interface ArtifactTabTokens {
315
+ background?: string;
316
+ activeBackground?: string;
317
+ activeBorder?: string;
318
+ borderRadius?: string;
319
+ textColor?: string;
320
+ /** Hover background for inactive tabs. */
321
+ hoverBackground?: string;
322
+ /** Tab list container background. */
323
+ listBackground?: string;
324
+ /** Tab list container border color. */
325
+ listBorderColor?: string;
326
+ /** Tab list container padding (CSS shorthand). */
327
+ listPadding?: string;
328
+ }
329
+ /** Artifact pane chrome. */
330
+ interface ArtifactPaneTokens {
331
+ /**
332
+ * Background for the artifact column (toolbar + content), resolved from the theme.
333
+ * Defaults to `semantic.colors.container` so the pane matches assistant message surfaces.
334
+ * `features.artifacts.layout.paneBackground` still wins when set (layout escape hatch).
335
+ */
336
+ background?: string;
337
+ toolbarBackground?: string;
338
+ }
339
+ /** Icon button chrome (used by createIconButton). */
340
+ interface IconButtonTokens {
341
+ background?: string;
342
+ border?: string;
343
+ color?: string;
344
+ padding?: string;
345
+ borderRadius?: string;
346
+ hoverBackground?: string;
347
+ hoverColor?: string;
348
+ /** Background when aria-pressed="true". */
349
+ activeBackground?: string;
350
+ /** Border color when aria-pressed="true". */
351
+ activeBorder?: string;
352
+ }
353
+ /** Label button chrome (used by createLabelButton). */
354
+ interface LabelButtonTokens {
355
+ background?: string;
356
+ border?: string;
357
+ color?: string;
358
+ padding?: string;
359
+ borderRadius?: string;
360
+ hoverBackground?: string;
361
+ fontSize?: string;
362
+ gap?: string;
363
+ }
364
+ /** Scroll-to-bottom pill chrome shared by transcript + event stream. */
365
+ interface ScrollToBottomTokens extends ComponentTokenSet {
366
+ size?: string;
367
+ gap?: string;
368
+ fontSize?: string;
369
+ iconSize?: string;
370
+ }
371
+ /** Toggle group chrome (used by createToggleGroup). */
372
+ interface ToggleGroupTokens {
373
+ /** Gap between toggle buttons. Default: 0 (connected). */
374
+ gap?: string;
375
+ /** Border radius for first/last buttons. */
376
+ borderRadius?: string;
377
+ }
378
+ interface ComponentTokens {
379
+ button: ButtonTokens;
380
+ input: InputTokens;
381
+ launcher: LauncherTokens;
382
+ panel: PanelTokens;
383
+ header: HeaderTokens;
384
+ message: MessageTokens;
385
+ /** Markdown surfaces (chat + artifact pane). */
386
+ markdown?: MarkdownTokens;
387
+ voice: VoiceTokens;
388
+ approval: ApprovalTokens;
389
+ attachment: AttachmentTokens;
390
+ toolBubble: ToolBubbleTokens;
391
+ reasoningBubble: ReasoningBubbleTokens;
392
+ composer: ComposerChromeTokens;
393
+ /** Icon button styling tokens. */
394
+ iconButton?: IconButtonTokens;
395
+ /** Label button styling tokens. */
396
+ labelButton?: LabelButtonTokens;
397
+ /** Scroll-to-bottom indicator styling tokens. */
398
+ scrollToBottom?: ScrollToBottomTokens;
399
+ /** Toggle group styling tokens. */
400
+ toggleGroup?: ToggleGroupTokens;
401
+ /** Artifact toolbar, tab strip, and pane chrome. */
402
+ artifact?: {
403
+ toolbar?: ArtifactToolbarTokens;
404
+ tab?: ArtifactTabTokens;
405
+ pane?: ArtifactPaneTokens;
406
+ };
407
+ }
408
+ interface PaletteExtras {
409
+ transitions?: Record<string, string>;
410
+ easings?: Record<string, string>;
411
+ }
412
+ interface PersonaThemeBase {
413
+ palette: {
414
+ colors: ColorPalette;
415
+ spacing: SpacingScale;
416
+ typography: TypographyScale;
417
+ shadows: ShadowScale;
418
+ borders: BorderScale;
419
+ radius: RadiusScale;
420
+ } & PaletteExtras;
421
+ }
422
+ interface PersonaThemeSemantic {
423
+ semantic: SemanticTokens;
424
+ }
425
+ interface PersonaThemeComponents {
426
+ components: ComponentTokens;
427
+ }
428
+ type PersonaTheme = PersonaThemeBase & PersonaThemeSemantic & PersonaThemeComponents;
429
+ /** Recursive partial for `config.theme` / `config.darkTheme` overrides. */
430
+ type DeepPartial<T> = T extends object ? {
431
+ [P in keyof T]?: DeepPartial<T[P]>;
432
+ } : T;
433
+
434
+ /**
435
+ * Plugin interface for customizing widget components
436
+ */
437
+ interface AgentWidgetPlugin {
438
+ /**
439
+ * Unique identifier for the plugin
440
+ */
441
+ id: string;
442
+ /**
443
+ * Optional priority (higher = runs first). Default: 0
444
+ */
445
+ priority?: number;
446
+ /**
447
+ * Custom renderer for message bubbles
448
+ * Return null to use default renderer
449
+ */
450
+ renderMessage?: (context: {
451
+ message: AgentWidgetMessage;
452
+ defaultRenderer: () => HTMLElement;
453
+ config: AgentWidgetConfig;
454
+ }) => HTMLElement | null;
455
+ /**
456
+ * Custom renderer for launcher button
457
+ * Return null to use default renderer
458
+ */
459
+ renderLauncher?: (context: {
460
+ config: AgentWidgetConfig;
461
+ defaultRenderer: () => HTMLElement;
462
+ onToggle: () => void;
463
+ }) => HTMLElement | null;
464
+ /**
465
+ * Custom renderer for panel header
466
+ * Return null to use default renderer
467
+ */
468
+ renderHeader?: (context: {
469
+ config: AgentWidgetConfig;
470
+ defaultRenderer: () => HTMLElement;
471
+ onClose?: () => void;
472
+ }) => HTMLElement | null;
473
+ /**
474
+ * Custom renderer for composer/input area
475
+ * Return null to use default renderer
476
+ */
477
+ renderComposer?: (context: {
478
+ config: AgentWidgetConfig;
479
+ defaultRenderer: () => HTMLElement;
480
+ onSubmit: (text: string) => void;
481
+ /**
482
+ * When true, the assistant stream is active — same moment `session.isStreaming()` becomes true.
483
+ * Prefer wiring controls to `data-persona-composer-disable-when-streaming` plus `setComposerDisabled`
484
+ * in the host, or react to `footer.dataset.personaComposerStreaming === "true"`.
485
+ */
486
+ streaming: boolean;
487
+ /**
488
+ * Legacy alias: host disables the primary submit control while `streaming` is true.
489
+ * @deprecated Use `streaming` for new plugins.
490
+ */
491
+ disabled: boolean;
492
+ /** Opens the hidden file input when `config.attachments.enabled` is true (no-op otherwise). */
493
+ openAttachmentPicker: () => void;
494
+ /** From `config.composer.models` */
495
+ models?: Array<{
496
+ id: string;
497
+ label: string;
498
+ }>;
499
+ /** From `config.composer.selectedModelId` */
500
+ selectedModelId?: string;
501
+ /** Updates `config.composer.selectedModelId` for the running widget instance. */
502
+ onModelChange?: (modelId: string) => void;
503
+ /**
504
+ * Same behavior as the built-in mic when voice is enabled.
505
+ * Omitted when `config.voiceRecognition.enabled` is not true.
506
+ */
507
+ onVoiceToggle?: () => void;
508
+ }) => HTMLElement | null;
509
+ /**
510
+ * Custom renderer for reasoning bubbles
511
+ * Return null to use default renderer
512
+ */
513
+ renderReasoning?: (context: {
514
+ message: AgentWidgetMessage;
515
+ defaultRenderer: () => HTMLElement;
516
+ config: AgentWidgetConfig;
517
+ }) => HTMLElement | null;
518
+ /**
519
+ * Custom renderer for tool call bubbles
520
+ * Return null to use default renderer
521
+ */
522
+ renderToolCall?: (context: {
523
+ message: AgentWidgetMessage;
524
+ defaultRenderer: () => HTMLElement;
525
+ config: AgentWidgetConfig;
526
+ }) => HTMLElement | null;
527
+ /**
528
+ * Custom renderer for approval bubbles
529
+ * Return null to use default renderer
530
+ */
531
+ renderApproval?: (context: {
532
+ message: AgentWidgetMessage;
533
+ defaultRenderer: () => HTMLElement;
534
+ config: AgentWidgetConfig;
535
+ }) => HTMLElement | null;
536
+ /**
537
+ * Custom renderer for loading indicator
538
+ * Return null to use default renderer (or config-based renderer)
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * renderLoadingIndicator: ({ location, defaultRenderer }) => {
543
+ * if (location === 'standalone') {
544
+ * const el = document.createElement('div');
545
+ * el.textContent = 'Thinking...';
546
+ * return el;
547
+ * }
548
+ * return defaultRenderer();
549
+ * }
550
+ * ```
551
+ */
552
+ renderLoadingIndicator?: (context: LoadingIndicatorRenderContext) => HTMLElement | null;
553
+ /**
554
+ * Custom renderer for idle state indicator.
555
+ * Called when the widget is idle (not streaming) and has at least one message.
556
+ * Return an HTMLElement to display, or null to hide (default).
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * renderIdleIndicator: ({ lastMessage, messageCount }) => {
561
+ * if (messageCount === 0) return null;
562
+ * if (lastMessage?.role !== 'assistant') return null;
563
+ * const el = document.createElement('div');
564
+ * el.className = 'idle-pulse';
565
+ * el.setAttribute('data-preserve-animation', 'true');
566
+ * return el;
567
+ * }
568
+ * ```
569
+ */
570
+ renderIdleIndicator?: (context: IdleIndicatorRenderContext) => HTMLElement | null;
571
+ /**
572
+ * Custom renderer for the entire event stream view.
573
+ * Return null to use default renderer.
574
+ */
575
+ renderEventStreamView?: (context: EventStreamViewRenderContext) => HTMLElement | null;
576
+ /**
577
+ * Custom renderer for individual event stream rows.
578
+ * Return null to use default renderer.
579
+ */
580
+ renderEventStreamRow?: (context: EventStreamRowRenderContext) => HTMLElement | null;
581
+ /**
582
+ * Custom renderer for the event stream toolbar/header bar.
583
+ * Return null to use default renderer.
584
+ */
585
+ renderEventStreamToolbar?: (context: EventStreamToolbarRenderContext) => HTMLElement | null;
586
+ /**
587
+ * Custom renderer for the expanded event payload display.
588
+ * Return null to use default renderer.
589
+ */
590
+ renderEventStreamPayload?: (context: EventStreamPayloadRenderContext) => HTMLElement | null;
591
+ /**
592
+ * Called when plugin is registered
593
+ */
594
+ onRegister?: () => void;
595
+ /**
596
+ * Called when plugin is unregistered
597
+ */
598
+ onUnregister?: () => void;
599
+ }
600
+
601
+ /**
602
+ * Text content part for multi-modal messages
603
+ */
604
+ type TextContentPart = {
605
+ type: 'text';
606
+ text: string;
607
+ };
608
+ /**
609
+ * Image content part for multi-modal messages
610
+ * Supports base64 data URIs or URLs
611
+ */
612
+ type ImageContentPart = {
613
+ type: 'image';
614
+ image: string;
615
+ mimeType?: string;
616
+ alt?: string;
617
+ };
618
+ /**
619
+ * File content part for multi-modal messages
620
+ * Supports PDF, TXT, DOCX, and other document types
621
+ */
622
+ type FileContentPart = {
623
+ type: 'file';
624
+ data: string;
625
+ mimeType: string;
626
+ filename: string;
627
+ };
628
+ /**
629
+ * Union type for all content part types
630
+ */
631
+ type ContentPart = TextContentPart | ImageContentPart | FileContentPart;
632
+ /**
633
+ * Message content can be a simple string or an array of content parts
634
+ */
635
+ type MessageContent = string | ContentPart[];
636
+ type AgentWidgetContextProviderContext = {
637
+ messages: AgentWidgetMessage[];
638
+ config: AgentWidgetConfig;
639
+ };
640
+ type AgentWidgetContextProvider = (context: AgentWidgetContextProviderContext) => Record<string, unknown> | void | Promise<Record<string, unknown> | void>;
641
+ type AgentWidgetRequestPayloadMessage = {
642
+ role: AgentWidgetMessageRole;
643
+ content: MessageContent;
644
+ createdAt: string;
645
+ };
646
+ type AgentWidgetRequestPayload = {
647
+ messages: AgentWidgetRequestPayloadMessage[];
648
+ flowId?: string;
649
+ context?: Record<string, unknown>;
650
+ metadata?: Record<string, unknown>;
651
+ /** Per-turn template variables for /v1/client/chat (merged as root-level {{var}} in Runtype). */
652
+ inputs?: Record<string, unknown>;
653
+ };
654
+ /**
655
+ * Configuration for agent loop behavior.
656
+ */
657
+ type AgentLoopConfig = {
658
+ /** Maximum number of agent turns (1-100). The loop continues while the model calls tools. */
659
+ maxTurns: number;
660
+ /** Maximum cost budget in USD. Agent stops when exceeded. */
661
+ maxCost?: number;
662
+ /** Enable periodic reflection during execution */
663
+ enableReflection?: boolean;
664
+ /** Number of iterations between reflections (1-50) */
665
+ reflectionInterval?: number;
666
+ };
667
+ /**
668
+ * Configuration for agent tools (search, code execution, MCP servers, etc.)
669
+ */
670
+ type AgentToolsConfig = {
671
+ /** Tool IDs to enable (e.g., "builtin:exa", "builtin:dalle", "builtin:openai_web_search") */
672
+ toolIds?: string[];
673
+ /** Per-tool configuration overrides keyed by tool ID */
674
+ toolConfigs?: Record<string, Record<string, unknown>>;
675
+ /** Inline tool definitions for runtime-defined tools */
676
+ runtimeTools?: Array<Record<string, unknown>>;
677
+ /** Custom MCP server connections */
678
+ mcpServers?: Array<Record<string, unknown>>;
679
+ /** Maximum number of tool invocations per execution */
680
+ maxToolCalls?: number;
681
+ /** Tool approval configuration for human-in-the-loop workflows */
682
+ approval?: {
683
+ /** Tool names/patterns to require approval for, or true for all tools */
684
+ require: string[] | boolean;
685
+ /** Approval timeout in milliseconds (default: 300000 / 5 minutes) */
686
+ timeout?: number;
687
+ };
688
+ };
689
+ /** Artifact kinds for the Persona sidebar and dispatch payload */
690
+ type PersonaArtifactKind = "markdown" | "component";
691
+ /**
692
+ * Agent configuration for agent execution mode.
693
+ * When provided in the widget config, enables agent loop execution instead of flow dispatch.
694
+ */
695
+ type ArtifactConfigPayload = {
696
+ enabled: true;
697
+ types: PersonaArtifactKind[];
698
+ };
699
+ type AgentConfig = {
700
+ /** Agent display name */
701
+ name: string;
702
+ /** Model identifier (e.g., 'openai:gpt-4o-mini', 'qwen/qwen3-8b') */
703
+ model: string;
704
+ /** System prompt for the agent */
705
+ systemPrompt: string;
706
+ /** Temperature for model responses */
707
+ temperature?: number;
708
+ /** Tool configuration for the agent */
709
+ tools?: AgentToolsConfig;
710
+ /** Persona artifacts — sibling of tools (virtual agent / API parity) */
711
+ artifacts?: ArtifactConfigPayload;
712
+ /** Loop configuration for multi-turn execution */
713
+ loopConfig?: AgentLoopConfig;
714
+ };
715
+ /**
716
+ * Options for agent execution requests.
717
+ */
718
+ type AgentRequestOptions = {
719
+ /** Whether to stream the response (should be true for widget usage) */
720
+ streamResponse?: boolean;
721
+ /** Record mode: 'virtual' for no persistence, 'existing'/'create' for database records */
722
+ recordMode?: 'virtual' | 'existing' | 'create';
723
+ /** Whether to store results server-side */
724
+ storeResults?: boolean;
725
+ /** Enable debug mode for additional event data */
726
+ debugMode?: boolean;
727
+ };
728
+ /**
729
+ * Metadata attached to messages created during agent execution.
730
+ */
731
+ type AgentMessageMetadata = {
732
+ executionId?: string;
733
+ iteration?: number;
734
+ turnId?: string;
735
+ agentName?: string;
736
+ };
737
+ type AgentWidgetRequestMiddlewareContext = {
738
+ payload: AgentWidgetRequestPayload;
739
+ config: AgentWidgetConfig;
740
+ };
741
+ type AgentWidgetRequestMiddleware = (context: AgentWidgetRequestMiddlewareContext) => AgentWidgetRequestPayload | void | Promise<AgentWidgetRequestPayload | void>;
742
+ type AgentWidgetParsedAction = {
743
+ type: string;
744
+ payload: Record<string, unknown>;
745
+ raw?: unknown;
746
+ };
747
+ type AgentWidgetActionParserInput = {
748
+ text: string;
749
+ message: AgentWidgetMessage;
750
+ };
751
+ type AgentWidgetActionParser = (input: AgentWidgetActionParserInput) => AgentWidgetParsedAction | null | undefined;
752
+ type AgentWidgetActionHandlerResult = {
753
+ handled?: boolean;
754
+ displayText?: string;
755
+ persistMessage?: boolean;
756
+ resubmit?: boolean;
757
+ };
758
+ type AgentWidgetActionContext = {
759
+ message: AgentWidgetMessage;
760
+ metadata: Record<string, unknown>;
761
+ updateMetadata: (updater: (prev: Record<string, unknown>) => Record<string, unknown>) => void;
762
+ document: Document | null;
763
+ /**
764
+ * Trigger automatic model continuation.
765
+ * Call this AFTER completing async operations (e.g., injecting search results)
766
+ * to have the model analyze the injected data.
767
+ *
768
+ * Use this instead of returning `resubmit: true` for handlers that do async work,
769
+ * as it ensures the continuation happens after the data is available in context.
770
+ *
771
+ * @example
772
+ * // In an action handler
773
+ * const results = await fetchProducts(query);
774
+ * session.injectAssistantMessage({ content: formatResults(results) });
775
+ * context.triggerResubmit();
776
+ */
777
+ triggerResubmit: () => void;
778
+ };
779
+ type AgentWidgetActionHandler = (action: AgentWidgetParsedAction, context: AgentWidgetActionContext) => AgentWidgetActionHandlerResult | void;
780
+ type AgentWidgetStoredState = {
781
+ messages?: AgentWidgetMessage[];
782
+ metadata?: Record<string, unknown>;
783
+ };
784
+ interface AgentWidgetStorageAdapter {
785
+ load?: () => AgentWidgetStoredState | null | Promise<AgentWidgetStoredState | null>;
786
+ save?: (state: AgentWidgetStoredState) => void | Promise<void>;
787
+ clear?: () => void | Promise<void>;
788
+ }
789
+ type AgentWidgetVoiceStateEvent = {
790
+ active: boolean;
791
+ source: "user" | "auto" | "restore" | "system";
792
+ timestamp: number;
793
+ };
794
+ type AgentWidgetActionEventPayload = {
795
+ action: AgentWidgetParsedAction;
796
+ message: AgentWidgetMessage;
797
+ };
798
+ /**
799
+ * Feedback event payload for upvote/downvote actions on messages
800
+ */
801
+ type AgentWidgetMessageFeedback = {
802
+ type: "upvote" | "downvote";
803
+ messageId: string;
804
+ message: AgentWidgetMessage;
805
+ };
806
+ /**
807
+ * Configuration for message action buttons (copy, upvote, downvote)
808
+ *
809
+ * **Client Token Mode**: When using `clientToken`, feedback is automatically
810
+ * sent to your Runtype backend. Just enable the buttons and you're done!
811
+ * The `onFeedback` and `onCopy` callbacks are optional for additional local handling.
812
+ *
813
+ * @example
814
+ * ```typescript
815
+ * // With clientToken - feedback is automatic!
816
+ * config: {
817
+ * clientToken: 'ct_live_...',
818
+ * messageActions: {
819
+ * showUpvote: true,
820
+ * showDownvote: true,
821
+ * // No onFeedback needed - sent to backend automatically
822
+ * }
823
+ * }
824
+ * ```
825
+ */
826
+ type AgentWidgetMessageActionsConfig = {
827
+ /**
828
+ * Enable/disable message actions entirely
829
+ * @default true
830
+ */
831
+ enabled?: boolean;
832
+ /**
833
+ * Show copy button
834
+ * @default true
835
+ */
836
+ showCopy?: boolean;
837
+ /**
838
+ * Show upvote button.
839
+ * When using `clientToken`, feedback is sent to the backend automatically.
840
+ * @default false
841
+ */
842
+ showUpvote?: boolean;
843
+ /**
844
+ * Show downvote button.
845
+ * When using `clientToken`, feedback is sent to the backend automatically.
846
+ * @default false
847
+ */
848
+ showDownvote?: boolean;
849
+ /**
850
+ * Visibility mode: 'always' shows buttons always, 'hover' shows on hover only
851
+ * @default 'hover'
852
+ */
853
+ visibility?: "always" | "hover";
854
+ /**
855
+ * Horizontal alignment of action buttons
856
+ * @default 'right'
857
+ */
858
+ align?: "left" | "center" | "right";
859
+ /**
860
+ * Layout style for action buttons
861
+ * - 'pill-inside': Compact floating pill around just the buttons (default for hover)
862
+ * - 'row-inside': Full-width row at the bottom of the message
863
+ * @default 'pill-inside'
864
+ */
865
+ layout?: "pill-inside" | "row-inside";
866
+ /**
867
+ * Callback when user submits feedback (upvote/downvote).
868
+ *
869
+ * **Note**: When using `clientToken`, feedback is AUTOMATICALLY sent to your
870
+ * backend via `/v1/client/feedback`. This callback is called IN ADDITION to
871
+ * the automatic submission, useful for updating local UI or analytics.
872
+ */
873
+ onFeedback?: (feedback: AgentWidgetMessageFeedback) => void;
874
+ /**
875
+ * Callback when user copies a message.
876
+ *
877
+ * **Note**: When using `clientToken`, copy events are AUTOMATICALLY tracked
878
+ * via `/v1/client/feedback`. This callback is called IN ADDITION to the
879
+ * automatic tracking.
880
+ */
881
+ onCopy?: (message: AgentWidgetMessage) => void;
882
+ };
883
+ type AgentWidgetStateEvent = {
884
+ open: boolean;
885
+ source: "user" | "auto" | "api" | "system";
886
+ timestamp: number;
887
+ };
888
+ type AgentWidgetStateSnapshot = {
889
+ open: boolean;
890
+ launcherEnabled: boolean;
891
+ voiceActive: boolean;
892
+ streaming: boolean;
893
+ };
894
+ type AgentWidgetControllerEventMap = {
895
+ "user:message": AgentWidgetMessage;
896
+ "assistant:message": AgentWidgetMessage;
897
+ "assistant:complete": AgentWidgetMessage;
898
+ "voice:state": AgentWidgetVoiceStateEvent;
899
+ "action:detected": AgentWidgetActionEventPayload;
900
+ "action:resubmit": AgentWidgetActionEventPayload;
901
+ "widget:opened": AgentWidgetStateEvent;
902
+ "widget:closed": AgentWidgetStateEvent;
903
+ "widget:state": AgentWidgetStateSnapshot;
904
+ "message:feedback": AgentWidgetMessageFeedback;
905
+ "message:copy": AgentWidgetMessage;
906
+ "eventStream:opened": {
907
+ timestamp: number;
908
+ };
909
+ "eventStream:closed": {
910
+ timestamp: number;
911
+ };
912
+ "approval:requested": {
913
+ approval: AgentWidgetApproval;
914
+ message: AgentWidgetMessage;
915
+ };
916
+ "approval:resolved": {
917
+ approval: AgentWidgetApproval;
918
+ decision: string;
919
+ };
920
+ };
921
+ /**
922
+ * Layout for the artifact split / drawer (CSS lengths unless noted).
923
+ *
924
+ * **Close behavior:** In desktop split mode, the artifact chrome `Close` control uses the same
925
+ * dismiss path as the mobile drawer (`onDismiss` on the artifact pane): the pane is hidden until
926
+ * new artifact content arrives or the host calls `showArtifacts()` on the widget handle.
927
+ */
928
+ type AgentWidgetArtifactsLayoutConfig = {
929
+ /** Flex gap between chat column and artifact pane. @default 0.5rem */
930
+ splitGap?: string;
931
+ /** Artifact column width in split mode. @default 40% */
932
+ paneWidth?: string;
933
+ /** Max width of artifact column. @default 28rem */
934
+ paneMaxWidth?: string;
935
+ /** Min width of artifact column (optional). */
936
+ paneMinWidth?: string;
937
+ /**
938
+ * When the floating panel is at most this wide (px), use in-panel drawer for artifacts
939
+ * instead of a side-by-side split (viewport can still be wide).
940
+ * @default 520
941
+ */
942
+ narrowHostMaxWidth?: number;
943
+ /**
944
+ * When true (default), widen the launcher panel while artifacts are visible and not user-dismissed.
945
+ * No-op for inline embed (`launcher.enabled === false`).
946
+ */
947
+ expandLauncherPanelWhenOpen?: boolean;
948
+ /** Panel width when expanded (launcher + artifacts visible). @default min(720px, calc(100vw - 24px)) */
949
+ expandedPanelWidth?: string;
950
+ /**
951
+ * When true, shows a drag handle between chat and artifact columns in desktop split mode only
952
+ * (hidden in narrow-host drawer and viewport ≤640px). Width is not persisted across reloads.
953
+ */
954
+ resizable?: boolean;
955
+ /** Min artifact column width while resizing. Only `px` strings are supported. @default 200px */
956
+ resizableMinWidth?: string;
957
+ /** Optional max artifact width cap while resizing (`px` only). Layout still bounds by chat min width. */
958
+ resizableMaxWidth?: string;
959
+ /**
960
+ * Visual treatment for the artifact column in split mode.
961
+ * - `'panel'` — bordered sidebar with left border, gap, and shadow (default).
962
+ * - `'seamless'` — flush with chat: no border or shadow, container background, zero gap.
963
+ * @default 'panel'
964
+ */
965
+ paneAppearance?: "panel" | "seamless";
966
+ /** Border radius on the artifact pane (CSS length). Works with any `paneAppearance`. */
967
+ paneBorderRadius?: string;
968
+ /** CSS `box-shadow` on the artifact pane. Set `"none"` to suppress the default shadow. */
969
+ paneShadow?: string;
970
+ /**
971
+ * Full `border` shorthand for the artifact `<aside>` (all sides). Overrides default pane borders.
972
+ * Example: `"1px solid #cccccc"`.
973
+ */
974
+ paneBorder?: string;
975
+ /**
976
+ * `border-left` shorthand only — typical for split view next to chat (with or without resizer).
977
+ * Ignored if `paneBorder` is set. Example: `"1px solid #cccccc"`.
978
+ */
979
+ paneBorderLeft?: string;
980
+ /**
981
+ * Desktop split only (not narrow-host drawer / not ≤640px): square the **main chat card’s**
982
+ * top-right and bottom-right radii, and round the **artifact pane’s** top-right and bottom-right
983
+ * to match `persona-rounded-2xl` (`--persona-radius-lg`) so the two columns read as one shell.
984
+ */
985
+ unifiedSplitChrome?: boolean;
986
+ /**
987
+ * When `unifiedSplitChrome` is true, outer-right corner radius on the artifact column (CSS length).
988
+ * @default matches theme large radius (`--persona-radius-lg`)
989
+ */
990
+ unifiedSplitOuterRadius?: string;
991
+ /**
992
+ * Strongest override: solid background for the artifact column (CSS color). Sets `--persona-artifact-pane-bg`
993
+ * on the widget root. Leave unset to use theme `components.artifact.pane.background` (defaults to semantic
994
+ * container) so light/dark stays consistent.
995
+ */
996
+ paneBackground?: string;
997
+ /**
998
+ * Horizontal padding for artifact toolbar and content (CSS length), e.g. `24px`.
999
+ */
1000
+ panePadding?: string;
1001
+ /**
1002
+ * Toolbar layout preset.
1003
+ * - `default` — "Artifacts" title, horizontal tabs, text close.
1004
+ * - `document` — view/source toggle, document title, copy / refresh / close; tab strip hidden when only one artifact.
1005
+ * @default 'default'
1006
+ */
1007
+ toolbarPreset?: "default" | "document";
1008
+ /**
1009
+ * When `toolbarPreset` is `document`, show a visible "Copy" label next to the copy icon.
1010
+ */
1011
+ documentToolbarShowCopyLabel?: boolean;
1012
+ /**
1013
+ * When `toolbarPreset` is `document`, show a small chevron after the copy control (e.g. menu affordance).
1014
+ */
1015
+ documentToolbarShowCopyChevron?: boolean;
1016
+ /** Document toolbar icon buttons (view, code, copy, refresh, close) — CSS color. Sets `--persona-artifact-doc-toolbar-icon-color` on the widget root. */
1017
+ documentToolbarIconColor?: string;
1018
+ /** Active view/source toggle background. Sets `--persona-artifact-doc-toggle-active-bg`. */
1019
+ documentToolbarToggleActiveBackground?: string;
1020
+ /** Active view/source toggle border color. Sets `--persona-artifact-doc-toggle-active-border`. */
1021
+ documentToolbarToggleActiveBorderColor?: string;
1022
+ /**
1023
+ * Invoked when the document toolbar Refresh control is used (before the pane re-renders).
1024
+ * Use to replay `connectStream`, refetch, etc.
1025
+ */
1026
+ onDocumentToolbarRefresh?: () => void | Promise<void>;
1027
+ /**
1028
+ * Optional copy dropdown entries (shown when `documentToolbarShowCopyChevron` is true and this array is non-empty).
1029
+ * The main Copy control still performs default copy unless `onDocumentToolbarCopyMenuSelect` handles everything.
1030
+ */
1031
+ documentToolbarCopyMenuItems?: Array<{
1032
+ id: string;
1033
+ label: string;
1034
+ }>;
1035
+ /**
1036
+ * When set, invoked for the chevron menu (and can override default copy per `actionId`).
1037
+ */
1038
+ onDocumentToolbarCopyMenuSelect?: (payload: {
1039
+ actionId: string;
1040
+ artifactId: string | null;
1041
+ markdown: string;
1042
+ jsonPayload: string;
1043
+ }) => void | Promise<void>;
1044
+ };
1045
+ type AgentWidgetArtifactsFeature = {
1046
+ /** When true, Persona shows the artifact pane and handles artifact_* SSE events */
1047
+ enabled?: boolean;
1048
+ /** If set, artifact events for other types are ignored */
1049
+ allowedTypes?: PersonaArtifactKind[];
1050
+ /** Split / drawer dimensions and launcher widen behavior */
1051
+ layout?: AgentWidgetArtifactsLayoutConfig;
1052
+ /**
1053
+ * Called when an artifact card action is triggered (open, download).
1054
+ * Return `true` to prevent the default behavior.
1055
+ */
1056
+ onArtifactAction?: (action: {
1057
+ type: 'open' | 'download';
1058
+ artifactId: string;
1059
+ }) => boolean | void;
1060
+ /**
1061
+ * Custom renderer for artifact reference cards shown in the message thread.
1062
+ * Return an HTMLElement to replace the default card, or `null` to use the default.
1063
+ */
1064
+ renderCard?: (context: {
1065
+ artifact: {
1066
+ artifactId: string;
1067
+ title: string;
1068
+ artifactType: string;
1069
+ status: string;
1070
+ };
1071
+ config: AgentWidgetConfig;
1072
+ defaultRenderer: () => HTMLElement;
1073
+ }) => HTMLElement | null;
1074
+ };
1075
+ type AgentWidgetScrollToBottomFeature = {
1076
+ /**
1077
+ * When true, Persona shows a scroll-to-bottom affordance when the user breaks
1078
+ * away from the latest transcript or event stream content.
1079
+ * @default true
1080
+ */
1081
+ enabled?: boolean;
1082
+ /**
1083
+ * Lucide icon name used for the affordance.
1084
+ * @default "arrow-down"
1085
+ */
1086
+ iconName?: string;
1087
+ /**
1088
+ * Optional label text shown next to the icon. Set to an empty string for an
1089
+ * icon-only affordance.
1090
+ * @default ""
1091
+ */
1092
+ label?: string;
1093
+ };
1094
+ type AgentWidgetFeatureFlags = {
1095
+ showReasoning?: boolean;
1096
+ showToolCalls?: boolean;
1097
+ showEventStreamToggle?: boolean;
1098
+ /** Shared transcript + event stream scroll-to-bottom affordance. */
1099
+ scrollToBottom?: AgentWidgetScrollToBottomFeature;
1100
+ /** Configuration for the Event Stream inspector view */
1101
+ eventStream?: EventStreamConfig;
1102
+ /** Optional artifact sidebar (split pane / mobile drawer) */
1103
+ artifacts?: AgentWidgetArtifactsFeature;
1104
+ };
1105
+ type SSEEventRecord = {
1106
+ id: string;
1107
+ type: string;
1108
+ timestamp: number;
1109
+ payload: string;
1110
+ };
1111
+ /**
1112
+ * Badge color configuration for event stream event types.
1113
+ */
1114
+ type EventStreamBadgeColor = {
1115
+ /** Background color (CSS value) */
1116
+ bg: string;
1117
+ /** Text color (CSS value) */
1118
+ text: string;
1119
+ };
1120
+ /**
1121
+ * Configuration for the Event Stream inspector view.
1122
+ */
1123
+ type EventStreamConfig = {
1124
+ /**
1125
+ * Custom badge color mappings by event type prefix or exact type.
1126
+ * Keys are matched as exact match first, then prefix match (keys ending with "_").
1127
+ * @example { "flow_": { bg: "#dcfce7", text: "#166534" }, "error": { bg: "#fecaca", text: "#991b1b" } }
1128
+ */
1129
+ badgeColors?: Record<string, EventStreamBadgeColor>;
1130
+ /**
1131
+ * Timestamp display format.
1132
+ * - "relative": Shows time offset from first event (+0.000s, +0.361s)
1133
+ * - "absolute": Shows wall-clock time (HH:MM:SS.mmm)
1134
+ * @default "relative"
1135
+ */
1136
+ timestampFormat?: "absolute" | "relative";
1137
+ /**
1138
+ * Whether to show sequential event numbers (1, 2, 3...).
1139
+ * @default true
1140
+ */
1141
+ showSequenceNumbers?: boolean;
1142
+ /**
1143
+ * Maximum events to keep in the ring buffer.
1144
+ * @default 500
1145
+ */
1146
+ maxEvents?: number;
1147
+ /**
1148
+ * Fields to extract from event payloads for description text.
1149
+ * The first matching field value is displayed after the badge.
1150
+ * @default ["flowName", "stepName", "name", "tool", "toolName"]
1151
+ */
1152
+ descriptionFields?: string[];
1153
+ /**
1154
+ * Custom CSS class names to append to event stream UI elements.
1155
+ * Each value is a space-separated class string appended to the element's default classes.
1156
+ */
1157
+ classNames?: {
1158
+ /** The toggle button in the widget header (activity icon). */
1159
+ toggleButton?: string;
1160
+ /** Additional classes applied to the toggle button when the event stream is open. */
1161
+ toggleButtonActive?: string;
1162
+ /** The outer event stream panel/container. */
1163
+ panel?: string;
1164
+ /** The toolbar header bar (title, filter, copy all). */
1165
+ headerBar?: string;
1166
+ /** The search bar wrapper. */
1167
+ searchBar?: string;
1168
+ /** The search text input. */
1169
+ searchInput?: string;
1170
+ /** Each event row wrapper. */
1171
+ eventRow?: string;
1172
+ /** The "new events" scroll indicator pill. */
1173
+ scrollIndicator?: string;
1174
+ };
1175
+ };
1176
+ /**
1177
+ * Context for the renderEventStreamView plugin hook.
1178
+ */
1179
+ type EventStreamViewRenderContext = {
1180
+ config: AgentWidgetConfig;
1181
+ events: SSEEventRecord[];
1182
+ defaultRenderer: () => HTMLElement;
1183
+ onClose?: () => void;
1184
+ };
1185
+ /**
1186
+ * Context for the renderEventStreamRow plugin hook.
1187
+ */
1188
+ type EventStreamRowRenderContext = {
1189
+ event: SSEEventRecord;
1190
+ index: number;
1191
+ config: AgentWidgetConfig;
1192
+ defaultRenderer: () => HTMLElement;
1193
+ isExpanded: boolean;
1194
+ onToggleExpand: () => void;
1195
+ };
1196
+ /**
1197
+ * Context for the renderEventStreamToolbar plugin hook.
1198
+ */
1199
+ type EventStreamToolbarRenderContext = {
1200
+ config: AgentWidgetConfig;
1201
+ defaultRenderer: () => HTMLElement;
1202
+ eventCount: number;
1203
+ filteredCount: number;
1204
+ onFilterChange: (type: string) => void;
1205
+ onSearchChange: (term: string) => void;
1206
+ };
1207
+ /**
1208
+ * Context for the renderEventStreamPayload plugin hook.
1209
+ */
1210
+ type EventStreamPayloadRenderContext = {
1211
+ event: SSEEventRecord;
1212
+ config: AgentWidgetConfig;
1213
+ defaultRenderer: () => HTMLElement;
1214
+ parsedPayload: unknown;
1215
+ };
1216
+ type AgentWidgetDockConfig = {
1217
+ /**
1218
+ * Side of the wrapped container where the docked panel should render.
1219
+ * @default "right"
1220
+ */
1221
+ side?: "left" | "right";
1222
+ /**
1223
+ * Expanded width of the docked panel.
1224
+ * @default "420px"
1225
+ */
1226
+ width?: string;
1227
+ /**
1228
+ * When false, the dock column snaps between `0` and `width` with no CSS transition so main
1229
+ * content does not reflow during the open/close animation.
1230
+ * @default true
1231
+ */
1232
+ animate?: boolean;
1233
+ /**
1234
+ * How the dock panel is shown.
1235
+ * - `"resize"` (default): a flex column grows/shrinks between `0` and `width` (main content reflows).
1236
+ * - `"overlay"`: panel is absolutely positioned and translates in/out **over** full-width content.
1237
+ * - `"push"`: a wide inner track `[content at shell width][panel]` translates horizontally so the panel
1238
+ * appears to push the workspace aside **without** animating the content column width (Shopify-style).
1239
+ * - `"emerge"`: like `"resize"`, the flex column animates so **page content reflows**; the chat
1240
+ * panel keeps a **fixed** `dock.width` (not squeezed while the column grows), clipped by the slot so
1241
+ * it appears to emerge at full width like a floating widget.
1242
+ */
1243
+ reveal?: "resize" | "overlay" | "push" | "emerge";
1244
+ };
1245
+ type AgentWidgetLauncherConfig = {
1246
+ enabled?: boolean;
1247
+ title?: string;
1248
+ subtitle?: string;
1249
+ textHidden?: boolean;
1250
+ iconUrl?: string;
1251
+ agentIconText?: string;
1252
+ agentIconName?: string;
1253
+ agentIconHidden?: boolean;
1254
+ position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
1255
+ /**
1256
+ * Controls how the launcher panel is mounted relative to the host page.
1257
+ * - "floating": default floating launcher / panel behavior
1258
+ * - "docked": wraps the target container and renders as a sibling dock
1259
+ *
1260
+ * @default "floating"
1261
+ */
1262
+ mountMode?: "floating" | "docked";
1263
+ /**
1264
+ * Layout configuration for docked mode.
1265
+ */
1266
+ dock?: AgentWidgetDockConfig;
1267
+ autoExpand?: boolean;
1268
+ width?: string;
1269
+ /**
1270
+ * When true, the widget panel will fill the full height of its container.
1271
+ * Useful for sidebar layouts where the chat should take up the entire viewport height.
1272
+ * The widget will use flex layout to ensure header stays at top, messages scroll in middle,
1273
+ * and composer stays fixed at bottom.
1274
+ *
1275
+ * @default false
1276
+ */
1277
+ fullHeight?: boolean;
1278
+ /**
1279
+ * When true, the widget panel will be positioned as a sidebar flush with the viewport edges.
1280
+ * The panel will have:
1281
+ * - No border-radius (square corners)
1282
+ * - No margins (flush with top, left/right, and bottom edges)
1283
+ * - Full viewport height
1284
+ * - Subtle shadow on the edge facing the content
1285
+ * - No border between footer and messages
1286
+ *
1287
+ * Use with `position` to control which side ('bottom-left' for left sidebar, 'bottom-right' for right sidebar).
1288
+ * Automatically enables fullHeight when true.
1289
+ *
1290
+ * @default false
1291
+ */
1292
+ sidebarMode?: boolean;
1293
+ /**
1294
+ * Width of the sidebar panel when sidebarMode is true.
1295
+ * @default "420px"
1296
+ */
1297
+ sidebarWidth?: string;
1298
+ /**
1299
+ * Offset (in pixels) to subtract from the calculated panel height.
1300
+ * Useful for adjusting the panel height when there are other fixed elements on the page.
1301
+ * Only applies when not in fullHeight or sidebarMode.
1302
+ *
1303
+ * @default 0
1304
+ */
1305
+ heightOffset?: number;
1306
+ /**
1307
+ * When true, the widget panel expands to fill the full viewport on mobile devices.
1308
+ * Removes border-radius, margins, and shadows for a native app-like experience.
1309
+ * Applies when viewport width is at or below `mobileBreakpoint`.
1310
+ *
1311
+ * @default true
1312
+ */
1313
+ mobileFullscreen?: boolean;
1314
+ /**
1315
+ * Viewport width (in pixels) at or below which the widget enters mobile fullscreen mode.
1316
+ * Only applies when `mobileFullscreen` is true.
1317
+ *
1318
+ * @default 640
1319
+ */
1320
+ mobileBreakpoint?: number;
1321
+ /**
1322
+ * CSS z-index applied to the widget wrapper when it is in a positioned mode
1323
+ * (floating panel, mobile fullscreen, or sidebar). Increase this value if
1324
+ * other elements on the host page appear on top of the widget.
1325
+ *
1326
+ * @default 9999 in overlay modes (mobile fullscreen / sidebar); 50 for the regular floating panel
1327
+ */
1328
+ zIndex?: number;
1329
+ callToActionIconText?: string;
1330
+ callToActionIconName?: string;
1331
+ callToActionIconColor?: string;
1332
+ callToActionIconBackgroundColor?: string;
1333
+ callToActionIconHidden?: boolean;
1334
+ callToActionIconPadding?: string;
1335
+ agentIconSize?: string;
1336
+ callToActionIconSize?: string;
1337
+ headerIconSize?: string;
1338
+ headerIconName?: string;
1339
+ headerIconHidden?: boolean;
1340
+ closeButtonSize?: string;
1341
+ closeButtonColor?: string;
1342
+ closeButtonBackgroundColor?: string;
1343
+ closeButtonBorderWidth?: string;
1344
+ closeButtonBorderColor?: string;
1345
+ closeButtonBorderRadius?: string;
1346
+ closeButtonPaddingX?: string;
1347
+ closeButtonPaddingY?: string;
1348
+ closeButtonPlacement?: "inline" | "top-right";
1349
+ closeButtonIconName?: string;
1350
+ closeButtonIconText?: string;
1351
+ closeButtonTooltipText?: string;
1352
+ closeButtonShowTooltip?: boolean;
1353
+ clearChat?: AgentWidgetClearChatConfig;
1354
+ /**
1355
+ * Border style for the launcher button.
1356
+ * @example "1px solid #e5e7eb" | "2px solid #3b82f6" | "none"
1357
+ * @default "1px solid #e5e7eb"
1358
+ */
1359
+ border?: string;
1360
+ /**
1361
+ * Box shadow for the launcher button.
1362
+ * @example "0 10px 15px -3px rgba(0,0,0,0.1)" | "none"
1363
+ * @default "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)"
1364
+ */
1365
+ shadow?: string;
1366
+ /**
1367
+ * CSS `max-width` for the floating launcher button when the panel is closed.
1368
+ * Title and subtitle each truncate with an ellipsis when space is tight; full strings are available via the native `title` tooltip. Does not affect the open chat panel (`width` / `launcherWidth`).
1369
+ *
1370
+ * @example "min(380px, calc(100vw - 48px))"
1371
+ */
1372
+ collapsedMaxWidth?: string;
1373
+ };
1374
+ type AgentWidgetSendButtonConfig = {
1375
+ borderWidth?: string;
1376
+ borderColor?: string;
1377
+ paddingX?: string;
1378
+ paddingY?: string;
1379
+ iconText?: string;
1380
+ iconName?: string;
1381
+ useIcon?: boolean;
1382
+ tooltipText?: string;
1383
+ showTooltip?: boolean;
1384
+ backgroundColor?: string;
1385
+ textColor?: string;
1386
+ size?: string;
1387
+ };
1388
+ /** Optional composer UI state for custom `renderComposer` implementations. */
1389
+ type AgentWidgetComposerConfig = {
1390
+ models?: Array<{
1391
+ id: string;
1392
+ label: string;
1393
+ }>;
1394
+ /** Current selection; host or plugin may update this at runtime. */
1395
+ selectedModelId?: string;
1396
+ };
1397
+ type AgentWidgetClearChatConfig = {
1398
+ enabled?: boolean;
1399
+ placement?: "inline" | "top-right";
1400
+ iconName?: string;
1401
+ iconColor?: string;
1402
+ backgroundColor?: string;
1403
+ borderWidth?: string;
1404
+ borderColor?: string;
1405
+ borderRadius?: string;
1406
+ size?: string;
1407
+ paddingX?: string;
1408
+ paddingY?: string;
1409
+ tooltipText?: string;
1410
+ showTooltip?: boolean;
1411
+ };
1412
+ type AgentWidgetStatusIndicatorConfig = {
1413
+ visible?: boolean;
1414
+ /** Text alignment. Default: 'right'. */
1415
+ align?: 'left' | 'center' | 'right';
1416
+ idleText?: string;
1417
+ /** URL to open in a new tab when the idle text is clicked. */
1418
+ idleLink?: string;
1419
+ connectingText?: string;
1420
+ connectedText?: string;
1421
+ errorText?: string;
1422
+ };
1423
+ type AgentWidgetVoiceRecognitionConfig = {
1424
+ enabled?: boolean;
1425
+ pauseDuration?: number;
1426
+ /** Text shown in the user message placeholder while voice is being processed. Default: "🎤 Processing voice..." */
1427
+ processingText?: string;
1428
+ /** Text shown in the assistant message if voice processing fails. Default: "Voice processing failed. Please try again." */
1429
+ processingErrorText?: string;
1430
+ iconName?: string;
1431
+ iconSize?: string;
1432
+ iconColor?: string;
1433
+ backgroundColor?: string;
1434
+ borderColor?: string;
1435
+ borderWidth?: string;
1436
+ paddingX?: string;
1437
+ paddingY?: string;
1438
+ tooltipText?: string;
1439
+ showTooltip?: boolean;
1440
+ recordingIconColor?: string;
1441
+ recordingBackgroundColor?: string;
1442
+ recordingBorderColor?: string;
1443
+ showRecordingIndicator?: boolean;
1444
+ /** Icon name shown while processing voice input. Default: "loader" */
1445
+ processingIconName?: string;
1446
+ /** Icon color during processing. Inherits idle iconColor if not set */
1447
+ processingIconColor?: string;
1448
+ /** Button background color during processing. Inherits idle backgroundColor if not set */
1449
+ processingBackgroundColor?: string;
1450
+ /** Button border color during processing. Inherits idle borderColor if not set */
1451
+ processingBorderColor?: string;
1452
+ /** Icon name shown while agent is speaking. Default: "volume-2" (or "square" in cancel mode) */
1453
+ speakingIconName?: string;
1454
+ /** Icon color while speaking. Inherits idle iconColor if not set */
1455
+ speakingIconColor?: string;
1456
+ /** Button background color while speaking. Inherits idle backgroundColor if not set */
1457
+ speakingBackgroundColor?: string;
1458
+ /** Button border color while speaking. Inherits idle borderColor if not set */
1459
+ speakingBorderColor?: string;
1460
+ autoResume?: boolean | "assistant";
1461
+ provider?: {
1462
+ type: 'browser' | 'runtype' | 'custom';
1463
+ browser?: {
1464
+ language?: string;
1465
+ continuous?: boolean;
1466
+ };
1467
+ runtype?: {
1468
+ agentId: string;
1469
+ clientToken: string;
1470
+ host?: string;
1471
+ voiceId?: string;
1472
+ /** Duration of silence (ms) before auto-stopping recording. Default: 2000 */
1473
+ pauseDuration?: number;
1474
+ /** RMS volume threshold below which counts as silence. Default: 0.01 */
1475
+ silenceThreshold?: number;
1476
+ };
1477
+ custom?: any;
1478
+ };
1479
+ };
1480
+ /**
1481
+ * Text-to-speech configuration for reading assistant messages aloud.
1482
+ * Currently supports the Web Speech API (`speechSynthesis`).
1483
+ *
1484
+ * @example
1485
+ * ```typescript
1486
+ * textToSpeech: {
1487
+ * enabled: true,
1488
+ * provider: 'browser',
1489
+ * voice: 'Google US English',
1490
+ * rate: 1.2,
1491
+ * pitch: 1.0
1492
+ * }
1493
+ * ```
1494
+ */
1495
+ type TextToSpeechConfig = {
1496
+ /** Enable text-to-speech for assistant messages */
1497
+ enabled: boolean;
1498
+ /**
1499
+ * TTS provider.
1500
+ * - `'browser'` — Use the Web Speech API for all assistant messages (default).
1501
+ * - `'runtype'` — Server handles TTS for voice interactions.
1502
+ * Set `browserFallback: true` to also speak text-typed responses via the browser.
1503
+ */
1504
+ provider?: 'browser' | 'runtype';
1505
+ /**
1506
+ * When `provider` is `'runtype'`, fall back to browser TTS for assistant
1507
+ * messages that the server didn't already speak (e.g. text-typed messages).
1508
+ * Has no effect when provider is `'browser'` (browser TTS is always used).
1509
+ * @default false
1510
+ */
1511
+ browserFallback?: boolean;
1512
+ /** Voice name to use for browser TTS (e.g., 'Google US English'). If not found, uses auto-detect. */
1513
+ voice?: string;
1514
+ /**
1515
+ * Custom voice picker called when `voice` is not set.
1516
+ * Receives the full list of available `SpeechSynthesisVoice` objects and
1517
+ * should return the one to use. If not provided, the SDK auto-detects the
1518
+ * best English voice.
1519
+ *
1520
+ * @example
1521
+ * ```typescript
1522
+ * pickVoice: (voices) => voices.find(v => v.lang === 'fr-FR') ?? voices[0]
1523
+ * ```
1524
+ */
1525
+ pickVoice?: (voices: SpeechSynthesisVoice[]) => SpeechSynthesisVoice;
1526
+ /** Speech rate (0.1 - 10). Default: 1 */
1527
+ rate?: number;
1528
+ /** Speech pitch (0 - 2). Default: 1 */
1529
+ pitch?: number;
1530
+ };
1531
+ /**
1532
+ * Configuration for tool approval bubbles.
1533
+ * Controls styling, labels, and behavior of the approval UI.
1534
+ */
1535
+ type AgentWidgetApprovalConfig = {
1536
+ /** Background color of the approval bubble */
1537
+ backgroundColor?: string;
1538
+ /** Border color of the approval bubble */
1539
+ borderColor?: string;
1540
+ /** Color for the title text */
1541
+ titleColor?: string;
1542
+ /** Color for the description text */
1543
+ descriptionColor?: string;
1544
+ /** Background color for the approve button */
1545
+ approveButtonColor?: string;
1546
+ /** Text color for the approve button */
1547
+ approveButtonTextColor?: string;
1548
+ /** Background color for the deny button */
1549
+ denyButtonColor?: string;
1550
+ /** Text color for the deny button */
1551
+ denyButtonTextColor?: string;
1552
+ /** Background color for the parameters block */
1553
+ parameterBackgroundColor?: string;
1554
+ /** Text color for the parameters block */
1555
+ parameterTextColor?: string;
1556
+ /** Title text displayed above the description */
1557
+ title?: string;
1558
+ /** Label for the approve button */
1559
+ approveLabel?: string;
1560
+ /** Label for the deny button */
1561
+ denyLabel?: string;
1562
+ /**
1563
+ * Custom handler for approval decisions.
1564
+ * Return void to let the SDK auto-resolve via the API,
1565
+ * or return a Response/ReadableStream for custom handling.
1566
+ */
1567
+ onDecision?: (data: {
1568
+ approvalId: string;
1569
+ executionId: string;
1570
+ agentId: string;
1571
+ toolName: string;
1572
+ }, decision: 'approved' | 'denied') => Promise<Response | ReadableStream<Uint8Array> | void>;
1573
+ };
1574
+ type AgentWidgetToolCallConfig$1 = {
1575
+ /** Box-shadow for tool-call bubbles; overrides `theme.toolBubbleShadow` when set. */
1576
+ shadow?: string;
1577
+ backgroundColor?: string;
1578
+ borderColor?: string;
1579
+ borderWidth?: string;
1580
+ borderRadius?: string;
1581
+ headerBackgroundColor?: string;
1582
+ headerTextColor?: string;
1583
+ headerPaddingX?: string;
1584
+ headerPaddingY?: string;
1585
+ contentBackgroundColor?: string;
1586
+ contentTextColor?: string;
1587
+ contentPaddingX?: string;
1588
+ contentPaddingY?: string;
1589
+ codeBlockBackgroundColor?: string;
1590
+ codeBlockBorderColor?: string;
1591
+ codeBlockTextColor?: string;
1592
+ toggleTextColor?: string;
1593
+ labelTextColor?: string;
1594
+ };
1595
+ type AgentWidgetSuggestionChipsConfig = {
1596
+ fontFamily?: "sans-serif" | "serif" | "mono";
1597
+ fontWeight?: string;
1598
+ paddingX?: string;
1599
+ paddingY?: string;
1600
+ };
1601
+ /**
1602
+ * Interface for pluggable stream parsers that extract text from streaming responses.
1603
+ * Parsers handle incremental parsing to extract text values from structured formats (JSON, XML, etc.).
1604
+ *
1605
+ * @example
1606
+ * ```typescript
1607
+ * const jsonParser: AgentWidgetStreamParser = {
1608
+ * processChunk: async (content) => {
1609
+ * // Extract text from JSON - return null if not JSON or text not available yet
1610
+ * if (!content.trim().startsWith('{')) return null;
1611
+ * const match = content.match(/"text"\s*:\s*"([^"]*)"/);
1612
+ * return match ? match[1] : null;
1613
+ * },
1614
+ * getExtractedText: () => extractedText
1615
+ * };
1616
+ * ```
1617
+ */
1618
+ interface AgentWidgetStreamParserResult {
1619
+ /**
1620
+ * The extracted text to display (may be partial during streaming)
1621
+ */
1622
+ text: string | null;
1623
+ /**
1624
+ * The raw accumulated content. Built-in parsers always populate this so
1625
+ * downstream middleware (action handlers, logging, etc.) can
1626
+ * inspect/parse the original structured payload.
1627
+ */
1628
+ raw?: string;
1629
+ }
1630
+ interface AgentWidgetStreamParser {
1631
+ /**
1632
+ * Process a chunk of content and return the extracted text (if available).
1633
+ * This method is called for each chunk as it arrives during streaming.
1634
+ * Return null if the content doesn't match this parser's format or if text is not yet available.
1635
+ *
1636
+ * @param accumulatedContent - The full accumulated content so far (including new chunk)
1637
+ * @returns The extracted text value and optionally raw content, or null if not yet available or format doesn't match
1638
+ */
1639
+ processChunk(accumulatedContent: string): Promise<AgentWidgetStreamParserResult | string | null> | AgentWidgetStreamParserResult | string | null;
1640
+ /**
1641
+ * Get the currently extracted text value (may be partial).
1642
+ * This is called synchronously to get the latest extracted text without processing.
1643
+ *
1644
+ * @returns The currently extracted text value, or null if not yet available
1645
+ */
1646
+ getExtractedText(): string | null;
1647
+ /**
1648
+ * Clean up any resources when parsing is complete.
1649
+ */
1650
+ close?(): Promise<void> | void;
1651
+ }
1652
+ /**
1653
+ * Component renderer function signature for custom components
1654
+ */
1655
+ type AgentWidgetComponentRenderer = (props: Record<string, unknown>, context: {
1656
+ message: AgentWidgetMessage;
1657
+ config: AgentWidgetConfig;
1658
+ updateProps: (newProps: Record<string, unknown>) => void;
1659
+ }) => HTMLElement;
1660
+ /**
1661
+ * Result from custom SSE event parser
1662
+ */
1663
+ type AgentWidgetSSEEventResult = {
1664
+ /** Text content to display */
1665
+ text?: string;
1666
+ /** Whether the stream is complete */
1667
+ done?: boolean;
1668
+ /** Error message if an error occurred */
1669
+ error?: string;
1670
+ /** Text segment identity — when this changes, a new assistant message bubble is created */
1671
+ partId?: string;
1672
+ } | null;
1673
+ /**
1674
+ * Custom SSE event parser function
1675
+ * Allows transforming non-standard SSE event formats to persona's expected format
1676
+ */
1677
+ type AgentWidgetSSEEventParser = (eventData: unknown) => AgentWidgetSSEEventResult | Promise<AgentWidgetSSEEventResult>;
1678
+ /**
1679
+ * Custom fetch function for full control over API requests
1680
+ * Use this for custom authentication, request transformation, etc.
1681
+ */
1682
+ type AgentWidgetCustomFetch = (url: string, init: RequestInit, payload: AgentWidgetRequestPayload) => Promise<Response>;
1683
+ /**
1684
+ * Dynamic headers function - called before each request
1685
+ */
1686
+ type AgentWidgetHeadersFunction = () => Record<string, string> | Promise<Record<string, string>>;
1687
+ /**
1688
+ * Session information returned after client token initialization.
1689
+ * Contains session ID, expiry time, flow info, and config from the server.
1690
+ */
1691
+ type ClientSession = {
1692
+ /** Unique session identifier */
1693
+ sessionId: string;
1694
+ /** When the session expires */
1695
+ expiresAt: Date;
1696
+ /** Flow information */
1697
+ flow: {
1698
+ id: string;
1699
+ name: string;
1700
+ description: string | null;
1701
+ };
1702
+ /** Configuration from the server */
1703
+ config: {
1704
+ welcomeMessage: string | null;
1705
+ placeholder: string;
1706
+ theme: Record<string, unknown> | null;
1707
+ };
1708
+ };
1709
+ /** Icon button in the header title row (minimal layout). */
1710
+ type AgentWidgetHeaderTrailingAction = {
1711
+ id: string;
1712
+ /** Lucide icon name, e.g. `chevron-down` */
1713
+ icon?: string;
1714
+ label?: string;
1715
+ ariaLabel?: string;
1716
+ /**
1717
+ * When set, clicking this action opens a dropdown menu.
1718
+ * Menu item selections fire `onAction(menuItemId)`.
1719
+ */
1720
+ menuItems?: Array<{
1721
+ id: string;
1722
+ label: string;
1723
+ icon?: string;
1724
+ destructive?: boolean;
1725
+ dividerBefore?: boolean;
1726
+ }>;
1727
+ };
1728
+ /**
1729
+ * Context provided to header render functions
1730
+ */
1731
+ type HeaderRenderContext = {
1732
+ config: AgentWidgetConfig;
1733
+ onClose?: () => void;
1734
+ onClearChat?: () => void;
1735
+ /** Built from `layout.header.trailingActions` for custom `render` implementations. */
1736
+ trailingActions?: AgentWidgetHeaderTrailingAction[];
1737
+ /** Fired when a built-in trailing action is activated (same as `layout.header.onAction`). */
1738
+ onAction?: (actionId: string) => void;
1739
+ };
1740
+ /**
1741
+ * Context provided to message render functions
1742
+ */
1743
+ type MessageRenderContext = {
1744
+ message: AgentWidgetMessage;
1745
+ config: AgentWidgetConfig;
1746
+ streaming: boolean;
1747
+ };
1748
+ /**
1749
+ * Context provided to slot render functions
1750
+ */
1751
+ type SlotRenderContext = {
1752
+ config: AgentWidgetConfig;
1753
+ defaultContent: () => HTMLElement | null;
1754
+ };
1755
+ /**
1756
+ * Header layout configuration
1757
+ * Allows customization of the header section appearance and behavior
1758
+ */
1759
+ type AgentWidgetHeaderLayoutConfig = {
1760
+ /**
1761
+ * Layout preset: "default" | "minimal"
1762
+ * - default: Standard layout with icon, title, subtitle, and buttons
1763
+ * - minimal: Simplified layout with just title and close button
1764
+ */
1765
+ layout?: "default" | "minimal";
1766
+ /** Show/hide the header icon */
1767
+ showIcon?: boolean;
1768
+ /** Show/hide the title */
1769
+ showTitle?: boolean;
1770
+ /** Show/hide the subtitle */
1771
+ showSubtitle?: boolean;
1772
+ /** Show/hide the close button */
1773
+ showCloseButton?: boolean;
1774
+ /** Show/hide the clear chat button */
1775
+ showClearChat?: boolean;
1776
+ /**
1777
+ * Custom renderer for complete header override
1778
+ * When provided, replaces the entire header with custom content
1779
+ */
1780
+ render?: (context: HeaderRenderContext) => HTMLElement;
1781
+ /**
1782
+ * Shown after the title in `minimal` header layout (e.g. chevron menu affordance).
1783
+ */
1784
+ trailingActions?: AgentWidgetHeaderTrailingAction[];
1785
+ /** Called when a `trailingActions` button is clicked. */
1786
+ onAction?: (actionId: string) => void;
1787
+ /**
1788
+ * Called when the header title row is clicked.
1789
+ * Useful for dropdown menus or navigation triggered from the header.
1790
+ * When set, the title row becomes visually interactive (cursor: pointer).
1791
+ */
1792
+ onTitleClick?: () => void;
1793
+ /** Style config for the title row hover effect (minimal layout). */
1794
+ titleRowHover?: {
1795
+ /** Hover background color. */
1796
+ background?: string;
1797
+ /** Hover border color. */
1798
+ border?: string;
1799
+ /** Border radius for the pill shape. */
1800
+ borderRadius?: string;
1801
+ /** Padding inside the pill. */
1802
+ padding?: string;
1803
+ };
1804
+ /**
1805
+ * Replaces the title with a combo button (label + chevron + dropdown menu).
1806
+ * When set, `trailingActions`, `onTitleClick`, and `titleRowHover` are ignored
1807
+ * since the combo button handles all of these internally.
1808
+ */
1809
+ titleMenu?: {
1810
+ /** Dropdown menu items. */
1811
+ menuItems: Array<{
1812
+ id: string;
1813
+ label: string;
1814
+ icon?: string;
1815
+ destructive?: boolean;
1816
+ dividerBefore?: boolean;
1817
+ }>;
1818
+ /** Called when a menu item is selected. */
1819
+ onSelect: (id: string) => void;
1820
+ /** Hover pill style. */
1821
+ hover?: {
1822
+ background?: string;
1823
+ border?: string;
1824
+ borderRadius?: string;
1825
+ padding?: string;
1826
+ };
1827
+ };
1828
+ };
1829
+ /**
1830
+ * Avatar configuration for message bubbles
1831
+ */
1832
+ type AgentWidgetAvatarConfig = {
1833
+ /** Whether to show avatars */
1834
+ show?: boolean;
1835
+ /** Position of avatar relative to message bubble */
1836
+ position?: "left" | "right";
1837
+ /** URL or emoji for user avatar */
1838
+ userAvatar?: string;
1839
+ /** URL or emoji for assistant avatar */
1840
+ assistantAvatar?: string;
1841
+ };
1842
+ /**
1843
+ * Timestamp configuration for message bubbles
1844
+ */
1845
+ type AgentWidgetTimestampConfig = {
1846
+ /** Whether to show timestamps */
1847
+ show?: boolean;
1848
+ /** Position of timestamp relative to message */
1849
+ position?: "inline" | "below";
1850
+ /** Custom formatter for timestamp display */
1851
+ format?: (date: Date) => string;
1852
+ };
1853
+ /**
1854
+ * Message layout configuration
1855
+ * Allows customization of how chat messages are displayed
1856
+ */
1857
+ type AgentWidgetMessageLayoutConfig = {
1858
+ /**
1859
+ * Layout preset: "bubble" | "flat" | "minimal"
1860
+ * - bubble: Standard chat bubble appearance (default)
1861
+ * - flat: Flat messages without bubble styling
1862
+ * - minimal: Minimal styling with reduced padding/borders
1863
+ */
1864
+ layout?: "bubble" | "flat" | "minimal";
1865
+ /** Avatar configuration */
1866
+ avatar?: AgentWidgetAvatarConfig;
1867
+ /** Timestamp configuration */
1868
+ timestamp?: AgentWidgetTimestampConfig;
1869
+ /** Group consecutive messages from the same role */
1870
+ groupConsecutive?: boolean;
1871
+ /**
1872
+ * Custom renderer for user messages
1873
+ * When provided, replaces the default user message rendering
1874
+ */
1875
+ renderUserMessage?: (context: MessageRenderContext) => HTMLElement;
1876
+ /**
1877
+ * Custom renderer for assistant messages
1878
+ * When provided, replaces the default assistant message rendering
1879
+ */
1880
+ renderAssistantMessage?: (context: MessageRenderContext) => HTMLElement;
1881
+ };
1882
+ /**
1883
+ * Available layout slots for content injection
1884
+ */
1885
+ type WidgetLayoutSlot = "header-left" | "header-center" | "header-right" | "body-top" | "messages" | "body-bottom" | "footer-top" | "composer" | "footer-bottom";
1886
+ /**
1887
+ * Slot renderer function signature
1888
+ * Returns HTMLElement to render in the slot, or null to use default content
1889
+ */
1890
+ type SlotRenderer = (context: SlotRenderContext) => HTMLElement | null;
1891
+ /**
1892
+ * Main layout configuration
1893
+ * Provides comprehensive control over widget layout and appearance
1894
+ *
1895
+ * @example
1896
+ * ```typescript
1897
+ * config: {
1898
+ * layout: {
1899
+ * header: { layout: "minimal" },
1900
+ * messages: {
1901
+ * avatar: { show: true, assistantAvatar: "/bot.png" },
1902
+ * timestamp: { show: true, position: "below" }
1903
+ * },
1904
+ * slots: {
1905
+ * "footer-top": () => {
1906
+ * const el = document.createElement("div");
1907
+ * el.textContent = "Powered by AI";
1908
+ * return el;
1909
+ * }
1910
+ * }
1911
+ * }
1912
+ * }
1913
+ * ```
1914
+ */
1915
+ type AgentWidgetLayoutConfig = {
1916
+ /** Header layout configuration */
1917
+ header?: AgentWidgetHeaderLayoutConfig;
1918
+ /** Message layout configuration */
1919
+ messages?: AgentWidgetMessageLayoutConfig;
1920
+ /** Slot renderers for custom content injection */
1921
+ slots?: Partial<Record<WidgetLayoutSlot, SlotRenderer>>;
1922
+ /**
1923
+ * Show/hide the header section entirely.
1924
+ * When false, the header (including icon, title, buttons) is completely hidden.
1925
+ * @default true
1926
+ */
1927
+ showHeader?: boolean;
1928
+ /**
1929
+ * Show/hide the footer/composer section entirely.
1930
+ * When false, the footer (including input field, send button, suggestions) is completely hidden.
1931
+ * Useful for read-only conversation previews.
1932
+ * @default true
1933
+ */
1934
+ showFooter?: boolean;
1935
+ /**
1936
+ * Max width for the content area (messages + composer).
1937
+ * Applied with `margin: 0 auto` for centering.
1938
+ * Accepts any CSS width value (e.g. "90ch", "720px", "80%").
1939
+ */
1940
+ contentMaxWidth?: string;
1941
+ };
1942
+ /**
1943
+ * Token types for marked renderer methods
1944
+ */
1945
+ type AgentWidgetMarkdownHeadingToken = {
1946
+ type: "heading";
1947
+ raw: string;
1948
+ depth: 1 | 2 | 3 | 4 | 5 | 6;
1949
+ text: string;
1950
+ tokens: unknown[];
1951
+ };
1952
+ type AgentWidgetMarkdownCodeToken = {
1953
+ type: "code";
1954
+ raw: string;
1955
+ text: string;
1956
+ lang?: string;
1957
+ escaped?: boolean;
1958
+ };
1959
+ type AgentWidgetMarkdownBlockquoteToken = {
1960
+ type: "blockquote";
1961
+ raw: string;
1962
+ text: string;
1963
+ tokens: unknown[];
1964
+ };
1965
+ type AgentWidgetMarkdownTableToken = {
1966
+ type: "table";
1967
+ raw: string;
1968
+ header: Array<{
1969
+ text: string;
1970
+ tokens: unknown[];
1971
+ }>;
1972
+ rows: Array<Array<{
1973
+ text: string;
1974
+ tokens: unknown[];
1975
+ }>>;
1976
+ align: Array<"left" | "center" | "right" | null>;
1977
+ };
1978
+ type AgentWidgetMarkdownLinkToken = {
1979
+ type: "link";
1980
+ raw: string;
1981
+ href: string;
1982
+ title: string | null;
1983
+ text: string;
1984
+ tokens: unknown[];
1985
+ };
1986
+ type AgentWidgetMarkdownImageToken = {
1987
+ type: "image";
1988
+ raw: string;
1989
+ href: string;
1990
+ title: string | null;
1991
+ text: string;
1992
+ };
1993
+ type AgentWidgetMarkdownListToken = {
1994
+ type: "list";
1995
+ raw: string;
1996
+ ordered: boolean;
1997
+ start: number | "";
1998
+ loose: boolean;
1999
+ items: unknown[];
2000
+ };
2001
+ type AgentWidgetMarkdownListItemToken = {
2002
+ type: "list_item";
2003
+ raw: string;
2004
+ task: boolean;
2005
+ checked?: boolean;
2006
+ loose: boolean;
2007
+ text: string;
2008
+ tokens: unknown[];
2009
+ };
2010
+ type AgentWidgetMarkdownParagraphToken = {
2011
+ type: "paragraph";
2012
+ raw: string;
2013
+ text: string;
2014
+ tokens: unknown[];
2015
+ };
2016
+ type AgentWidgetMarkdownCodespanToken = {
2017
+ type: "codespan";
2018
+ raw: string;
2019
+ text: string;
2020
+ };
2021
+ type AgentWidgetMarkdownStrongToken = {
2022
+ type: "strong";
2023
+ raw: string;
2024
+ text: string;
2025
+ tokens: unknown[];
2026
+ };
2027
+ type AgentWidgetMarkdownEmToken = {
2028
+ type: "em";
2029
+ raw: string;
2030
+ text: string;
2031
+ tokens: unknown[];
2032
+ };
2033
+ /**
2034
+ * Custom renderer overrides for markdown elements.
2035
+ * Each method receives the token and should return an HTML string.
2036
+ * Return `false` to use the default renderer.
2037
+ *
2038
+ * @example
2039
+ * ```typescript
2040
+ * renderer: {
2041
+ * heading(token) {
2042
+ * return `<h${token.depth} class="custom-heading">${token.text}</h${token.depth}>`;
2043
+ * },
2044
+ * link(token) {
2045
+ * return `<a href="${token.href}" target="_blank" rel="noopener">${token.text}</a>`;
2046
+ * }
2047
+ * }
2048
+ * ```
2049
+ */
2050
+ type AgentWidgetMarkdownRendererOverrides = {
2051
+ /** Override heading rendering (h1-h6) */
2052
+ heading?: (token: AgentWidgetMarkdownHeadingToken) => string | false;
2053
+ /** Override code block rendering */
2054
+ code?: (token: AgentWidgetMarkdownCodeToken) => string | false;
2055
+ /** Override blockquote rendering */
2056
+ blockquote?: (token: AgentWidgetMarkdownBlockquoteToken) => string | false;
2057
+ /** Override table rendering */
2058
+ table?: (token: AgentWidgetMarkdownTableToken) => string | false;
2059
+ /** Override link rendering */
2060
+ link?: (token: AgentWidgetMarkdownLinkToken) => string | false;
2061
+ /** Override image rendering */
2062
+ image?: (token: AgentWidgetMarkdownImageToken) => string | false;
2063
+ /** Override list rendering (ul/ol) */
2064
+ list?: (token: AgentWidgetMarkdownListToken) => string | false;
2065
+ /** Override list item rendering */
2066
+ listitem?: (token: AgentWidgetMarkdownListItemToken) => string | false;
2067
+ /** Override paragraph rendering */
2068
+ paragraph?: (token: AgentWidgetMarkdownParagraphToken) => string | false;
2069
+ /** Override inline code rendering */
2070
+ codespan?: (token: AgentWidgetMarkdownCodespanToken) => string | false;
2071
+ /** Override strong/bold rendering */
2072
+ strong?: (token: AgentWidgetMarkdownStrongToken) => string | false;
2073
+ /** Override emphasis/italic rendering */
2074
+ em?: (token: AgentWidgetMarkdownEmToken) => string | false;
2075
+ /** Override horizontal rule rendering */
2076
+ hr?: () => string | false;
2077
+ /** Override line break rendering */
2078
+ br?: () => string | false;
2079
+ /** Override deleted/strikethrough rendering */
2080
+ del?: (token: {
2081
+ type: "del";
2082
+ raw: string;
2083
+ text: string;
2084
+ tokens: unknown[];
2085
+ }) => string | false;
2086
+ /** Override checkbox rendering (in task lists) */
2087
+ checkbox?: (token: {
2088
+ checked: boolean;
2089
+ }) => string | false;
2090
+ /** Override HTML passthrough */
2091
+ html?: (token: {
2092
+ type: "html";
2093
+ raw: string;
2094
+ text: string;
2095
+ }) => string | false;
2096
+ /** Override text rendering */
2097
+ text?: (token: {
2098
+ type: "text";
2099
+ raw: string;
2100
+ text: string;
2101
+ }) => string | false;
2102
+ };
2103
+ /**
2104
+ * Markdown parsing options (subset of marked options)
2105
+ */
2106
+ type AgentWidgetMarkdownOptions = {
2107
+ /**
2108
+ * Enable GitHub Flavored Markdown (tables, strikethrough, autolinks).
2109
+ * @default true
2110
+ */
2111
+ gfm?: boolean;
2112
+ /**
2113
+ * Convert \n in paragraphs into <br>.
2114
+ * @default true
2115
+ */
2116
+ breaks?: boolean;
2117
+ /**
2118
+ * Conform to original markdown.pl as much as possible.
2119
+ * @default false
2120
+ */
2121
+ pedantic?: boolean;
2122
+ /**
2123
+ * Add id attributes to headings.
2124
+ * @default false
2125
+ */
2126
+ headerIds?: boolean;
2127
+ /**
2128
+ * Prefix for heading id attributes.
2129
+ * @default ""
2130
+ */
2131
+ headerPrefix?: string;
2132
+ /**
2133
+ * Mangle email addresses for spam protection.
2134
+ * @default true
2135
+ */
2136
+ mangle?: boolean;
2137
+ /**
2138
+ * Silent mode - don't throw on parse errors.
2139
+ * @default false
2140
+ */
2141
+ silent?: boolean;
2142
+ };
2143
+ /**
2144
+ * Markdown configuration for customizing how markdown is rendered in chat messages.
2145
+ * Provides three levels of control:
2146
+ *
2147
+ * 1. **CSS Variables** - Override styles via `--cw-md-*` CSS custom properties
2148
+ * 2. **Parsing Options** - Configure marked behavior via `options`
2149
+ * 3. **Custom Renderers** - Full control via `renderer` overrides
2150
+ *
2151
+ * @example
2152
+ * ```typescript
2153
+ * // Level 2: Configure parsing options
2154
+ * config: {
2155
+ * markdown: {
2156
+ * options: {
2157
+ * gfm: true,
2158
+ * breaks: true,
2159
+ * headerIds: true
2160
+ * }
2161
+ * }
2162
+ * }
2163
+ * ```
2164
+ *
2165
+ * @example
2166
+ * ```typescript
2167
+ * // Level 3: Custom renderers
2168
+ * config: {
2169
+ * markdown: {
2170
+ * renderer: {
2171
+ * heading(token) {
2172
+ * return `<h${token.depth} class="custom-h${token.depth}">${token.text}</h${token.depth}>`;
2173
+ * },
2174
+ * link(token) {
2175
+ * return `<a href="${token.href}" target="_blank">${token.text}</a>`;
2176
+ * },
2177
+ * table(token) {
2178
+ * // Wrap tables in a scrollable container
2179
+ * return `<div class="table-scroll">${this.parser.parse(token.tokens)}</div>`;
2180
+ * }
2181
+ * }
2182
+ * }
2183
+ * }
2184
+ * ```
2185
+ */
2186
+ type AgentWidgetMarkdownConfig = {
2187
+ /**
2188
+ * Markdown parsing options.
2189
+ * These are passed directly to the marked parser.
2190
+ */
2191
+ options?: AgentWidgetMarkdownOptions;
2192
+ /**
2193
+ * Custom renderer overrides for specific markdown elements.
2194
+ * Each method receives a token object and should return an HTML string.
2195
+ * Return `false` to fall back to the default renderer.
2196
+ */
2197
+ renderer?: AgentWidgetMarkdownRendererOverrides;
2198
+ /**
2199
+ * Disable default markdown CSS styles.
2200
+ * When true, the widget won't apply any default styles to markdown elements,
2201
+ * allowing you to provide your own CSS.
2202
+ *
2203
+ * @default false
2204
+ */
2205
+ disableDefaultStyles?: boolean;
2206
+ };
2207
+ /**
2208
+ * Configuration for file attachments in the composer.
2209
+ * Enables users to attach images to their messages.
2210
+ *
2211
+ * @example
2212
+ * ```typescript
2213
+ * config: {
2214
+ * attachments: {
2215
+ * enabled: true,
2216
+ * allowedTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp'],
2217
+ * maxFileSize: 5 * 1024 * 1024, // 5MB
2218
+ * maxFiles: 4
2219
+ * }
2220
+ * }
2221
+ * ```
2222
+ */
2223
+ type AgentWidgetAttachmentsConfig = {
2224
+ /**
2225
+ * Enable/disable file attachments.
2226
+ * @default false
2227
+ */
2228
+ enabled?: boolean;
2229
+ /**
2230
+ * Allowed MIME types for attachments.
2231
+ * @default ['image/png', 'image/jpeg', 'image/gif', 'image/webp']
2232
+ */
2233
+ allowedTypes?: string[];
2234
+ /**
2235
+ * Maximum file size in bytes.
2236
+ * @default 10485760 (10MB)
2237
+ */
2238
+ maxFileSize?: number;
2239
+ /**
2240
+ * Maximum number of files per message.
2241
+ * @default 4
2242
+ */
2243
+ maxFiles?: number;
2244
+ /**
2245
+ * Button icon name (from Lucide icons).
2246
+ * @default 'image-plus'
2247
+ */
2248
+ buttonIconName?: string;
2249
+ /**
2250
+ * Tooltip text for the attachment button.
2251
+ * @default 'Attach image'
2252
+ */
2253
+ buttonTooltipText?: string;
2254
+ /**
2255
+ * Callback when a file is rejected (wrong type or too large).
2256
+ */
2257
+ onFileRejected?: (file: File, reason: 'type' | 'size' | 'count') => void;
2258
+ };
2259
+ /**
2260
+ * Configuration for persisting widget state across page navigations.
2261
+ * Stores open/closed state, voice recognition state, and voice mode in browser storage.
2262
+ *
2263
+ * @example
2264
+ * ```typescript
2265
+ * config: {
2266
+ * persistState: true // Use defaults: sessionStorage, persist open state
2267
+ * }
2268
+ * ```
2269
+ *
2270
+ * @example
2271
+ * ```typescript
2272
+ * config: {
2273
+ * persistState: {
2274
+ * storage: 'local', // Use localStorage instead of sessionStorage
2275
+ * keyPrefix: 'myapp-', // Custom prefix for storage keys
2276
+ * persist: {
2277
+ * openState: true,
2278
+ * voiceState: true,
2279
+ * focusInput: true
2280
+ * },
2281
+ * clearOnChatClear: true
2282
+ * }
2283
+ * }
2284
+ * ```
2285
+ */
2286
+ type AgentWidgetPersistStateConfig = {
2287
+ /**
2288
+ * Storage type to use.
2289
+ * @default 'session'
2290
+ */
2291
+ storage?: 'local' | 'session';
2292
+ /**
2293
+ * Prefix for storage keys.
2294
+ * @default 'persona-'
2295
+ */
2296
+ keyPrefix?: string;
2297
+ /**
2298
+ * What state to persist.
2299
+ */
2300
+ persist?: {
2301
+ /**
2302
+ * Persist widget open/closed state.
2303
+ * @default true
2304
+ */
2305
+ openState?: boolean;
2306
+ /**
2307
+ * Persist voice recognition state.
2308
+ * @default true
2309
+ */
2310
+ voiceState?: boolean;
2311
+ /**
2312
+ * Focus input when restoring open state.
2313
+ * @default true
2314
+ */
2315
+ focusInput?: boolean;
2316
+ };
2317
+ /**
2318
+ * Clear persisted state when chat is cleared.
2319
+ * @default true
2320
+ */
2321
+ clearOnChatClear?: boolean;
2322
+ };
2323
+ /**
2324
+ * Context provided to loading indicator render functions.
2325
+ * Used for customizing the loading indicator appearance.
2326
+ */
2327
+ type LoadingIndicatorRenderContext = {
2328
+ /**
2329
+ * Full widget configuration for accessing theme, etc.
2330
+ */
2331
+ config: AgentWidgetConfig;
2332
+ /**
2333
+ * Current streaming state (always true when indicator is shown)
2334
+ */
2335
+ streaming: boolean;
2336
+ /**
2337
+ * Location where the indicator is rendered:
2338
+ * - 'inline': Inside a streaming assistant message bubble (when content is empty)
2339
+ * - 'standalone': Separate bubble while waiting for stream to start
2340
+ */
2341
+ location: 'inline' | 'standalone';
2342
+ /**
2343
+ * Function to render the default 3-dot bouncing indicator.
2344
+ * Call this if you want to use the default for certain cases.
2345
+ */
2346
+ defaultRenderer: () => HTMLElement;
2347
+ };
2348
+ /**
2349
+ * Context provided to idle indicator render functions.
2350
+ * Used for customizing the idle state indicator appearance.
2351
+ */
2352
+ type IdleIndicatorRenderContext = {
2353
+ /**
2354
+ * Full widget configuration for accessing theme, etc.
2355
+ */
2356
+ config: AgentWidgetConfig;
2357
+ /**
2358
+ * The last message in the conversation (if any).
2359
+ * Useful for conditional rendering based on who spoke last.
2360
+ */
2361
+ lastMessage: AgentWidgetMessage | undefined;
2362
+ /**
2363
+ * Total number of messages in the conversation.
2364
+ */
2365
+ messageCount: number;
2366
+ };
2367
+ /**
2368
+ * Configuration for customizing the loading indicator.
2369
+ * The loading indicator is shown while waiting for a response or
2370
+ * when an assistant message is streaming but has no content yet.
2371
+ *
2372
+ * @example
2373
+ * ```typescript
2374
+ * // Custom animated spinner
2375
+ * config: {
2376
+ * loadingIndicator: {
2377
+ * render: ({ location }) => {
2378
+ * const el = document.createElement('div');
2379
+ * el.innerHTML = '<svg class="spinner">...</svg>';
2380
+ * el.setAttribute('data-preserve-animation', 'true');
2381
+ * return el;
2382
+ * }
2383
+ * }
2384
+ * }
2385
+ * ```
2386
+ *
2387
+ * @example
2388
+ * ```typescript
2389
+ * // Different indicators by location
2390
+ * config: {
2391
+ * loadingIndicator: {
2392
+ * render: ({ location, defaultRenderer }) => {
2393
+ * if (location === 'inline') {
2394
+ * return defaultRenderer(); // Use default for inline
2395
+ * }
2396
+ * // Custom for standalone
2397
+ * const el = document.createElement('div');
2398
+ * el.textContent = 'Thinking...';
2399
+ * return el;
2400
+ * }
2401
+ * }
2402
+ * }
2403
+ * ```
2404
+ *
2405
+ * @example
2406
+ * ```typescript
2407
+ * // Hide loading indicator entirely
2408
+ * config: {
2409
+ * loadingIndicator: {
2410
+ * render: () => null
2411
+ * }
2412
+ * }
2413
+ * ```
2414
+ */
2415
+ type AgentWidgetLoadingIndicatorConfig = {
2416
+ /**
2417
+ * Whether to show the bubble background and border around the standalone loading indicator.
2418
+ * Set to false to render the loading indicator without any bubble styling.
2419
+ * @default true
2420
+ */
2421
+ showBubble?: boolean;
2422
+ /**
2423
+ * Custom render function for the loading indicator.
2424
+ * Return an HTMLElement to display, or null to hide the indicator.
2425
+ *
2426
+ * For custom animations, add `data-preserve-animation="true"` attribute
2427
+ * to prevent the DOM morpher from interrupting the animation.
2428
+ */
2429
+ render?: (context: LoadingIndicatorRenderContext) => HTMLElement | null;
2430
+ /**
2431
+ * Render function for the idle state indicator.
2432
+ * Called when the widget is idle (not streaming) and has at least one message.
2433
+ * Return an HTMLElement to display, or null to hide (default).
2434
+ *
2435
+ * For animations, add `data-preserve-animation="true"` attribute
2436
+ * to prevent the DOM morpher from interrupting the animation.
2437
+ *
2438
+ * @example
2439
+ * ```typescript
2440
+ * loadingIndicator: {
2441
+ * renderIdle: ({ lastMessage }) => {
2442
+ * // Only show idle indicator after assistant messages
2443
+ * if (lastMessage?.role !== 'assistant') return null;
2444
+ * const el = document.createElement('div');
2445
+ * el.className = 'pulse-dot';
2446
+ * el.setAttribute('data-preserve-animation', 'true');
2447
+ * return el;
2448
+ * }
2449
+ * }
2450
+ * ```
2451
+ */
2452
+ renderIdle?: (context: IdleIndicatorRenderContext) => HTMLElement | null;
2453
+ };
2454
+ type AgentWidgetConfig = {
2455
+ apiUrl?: string;
2456
+ flowId?: string;
2457
+ /**
2458
+ * Agent configuration for agent execution mode.
2459
+ * When provided, the widget uses agent loop execution instead of flow dispatch.
2460
+ * Mutually exclusive with `flowId`.
2461
+ *
2462
+ * @example
2463
+ * ```typescript
2464
+ * config: {
2465
+ * agent: {
2466
+ * name: 'Assistant',
2467
+ * model: 'openai:gpt-4o-mini',
2468
+ * systemPrompt: 'You are a helpful assistant.',
2469
+ * loopConfig: { maxTurns: 5 }
2470
+ * }
2471
+ * }
2472
+ * ```
2473
+ */
2474
+ agent?: AgentConfig;
2475
+ /**
2476
+ * Options for agent execution requests.
2477
+ * Only used when `agent` is configured.
2478
+ *
2479
+ * @default { streamResponse: true, recordMode: 'virtual' }
2480
+ */
2481
+ agentOptions?: AgentRequestOptions;
2482
+ /**
2483
+ * Controls how multiple agent iterations are displayed in the chat UI.
2484
+ * Only used when `agent` is configured.
2485
+ *
2486
+ * - `'separate'`: Each iteration creates a new assistant message bubble
2487
+ * - `'merged'`: All iterations stream into a single assistant message
2488
+ *
2489
+ * @default 'separate'
2490
+ */
2491
+ iterationDisplay?: 'separate' | 'merged';
2492
+ /**
2493
+ * Client token for direct browser-to-API communication.
2494
+ * When set, the widget uses /v1/client/* endpoints instead of /v1/dispatch.
2495
+ * Mutually exclusive with apiKey/headers authentication.
2496
+ *
2497
+ * @example
2498
+ * ```typescript
2499
+ * config: {
2500
+ * clientToken: 'ct_live_flow01k7_a8b9c0d1e2f3g4h5i6j7k8l9'
2501
+ * }
2502
+ * ```
2503
+ */
2504
+ clientToken?: string;
2505
+ /**
2506
+ * Callback when session is initialized (client token mode only).
2507
+ * Receives session info including expiry time.
2508
+ *
2509
+ * @example
2510
+ * ```typescript
2511
+ * config: {
2512
+ * onSessionInit: (session) => {
2513
+ * console.log('Session started:', session.sessionId);
2514
+ * }
2515
+ * }
2516
+ * ```
2517
+ */
2518
+ onSessionInit?: (session: ClientSession) => void;
2519
+ /**
2520
+ * Callback when session expires or errors (client token mode only).
2521
+ * Widget should prompt user to refresh.
2522
+ *
2523
+ * @example
2524
+ * ```typescript
2525
+ * config: {
2526
+ * onSessionExpired: () => {
2527
+ * alert('Your session has expired. Please refresh the page.');
2528
+ * }
2529
+ * }
2530
+ * ```
2531
+ */
2532
+ onSessionExpired?: () => void;
2533
+ /**
2534
+ * Get stored session ID for session resumption (client token mode only).
2535
+ * Called when initializing a new session to check if there's a previous session_id
2536
+ * that should be passed to /client/init to resume the same conversation record.
2537
+ *
2538
+ * @example
2539
+ * ```typescript
2540
+ * config: {
2541
+ * getStoredSessionId: () => {
2542
+ * const stored = localStorage.getItem('session_id');
2543
+ * return stored || null;
2544
+ * }
2545
+ * }
2546
+ * ```
2547
+ */
2548
+ getStoredSessionId?: () => string | null;
2549
+ /**
2550
+ * Store session ID for session resumption (client token mode only).
2551
+ * Called when a new session is initialized to persist the session_id
2552
+ * so it can be used to resume the conversation later.
2553
+ *
2554
+ * @example
2555
+ * ```typescript
2556
+ * config: {
2557
+ * setStoredSessionId: (sessionId) => {
2558
+ * localStorage.setItem('session_id', sessionId);
2559
+ * }
2560
+ * }
2561
+ * ```
2562
+ */
2563
+ setStoredSessionId?: (sessionId: string) => void;
2564
+ /**
2565
+ * Static headers to include with each request.
2566
+ * For dynamic headers (e.g., auth tokens), use `getHeaders` instead.
2567
+ */
2568
+ headers?: Record<string, string>;
2569
+ /**
2570
+ * Dynamic headers function - called before each request.
2571
+ * Useful for adding auth tokens that may change.
2572
+ * @example
2573
+ * ```typescript
2574
+ * getHeaders: async () => ({
2575
+ * 'Authorization': `Bearer ${await getAuthToken()}`
2576
+ * })
2577
+ * ```
2578
+ */
2579
+ getHeaders?: AgentWidgetHeadersFunction;
2580
+ copy?: {
2581
+ welcomeTitle?: string;
2582
+ welcomeSubtitle?: string;
2583
+ inputPlaceholder?: string;
2584
+ sendButtonLabel?: string;
2585
+ /**
2586
+ * When false, the welcome / intro card is not shown above the message list.
2587
+ * @default true
2588
+ */
2589
+ showWelcomeCard?: boolean;
2590
+ };
2591
+ /**
2592
+ * Semantic design tokens (`palette`, `semantic`, `components`).
2593
+ * Omit for library defaults.
2594
+ */
2595
+ theme?: DeepPartial<PersonaTheme>;
2596
+ /**
2597
+ * Dark-mode token overrides. Merged over `theme` when the active scheme is dark.
2598
+ */
2599
+ darkTheme?: DeepPartial<PersonaTheme>;
2600
+ /**
2601
+ * Color scheme mode for the widget.
2602
+ * - 'light': Always use light theme (default)
2603
+ * - 'dark': Always use dark theme
2604
+ * - 'auto': Automatically detect from page (HTML class or prefers-color-scheme)
2605
+ *
2606
+ * When 'auto', detection order:
2607
+ * 1. Check if `<html>` has 'dark' class
2608
+ * 2. Fall back to `prefers-color-scheme: dark` media query
2609
+ *
2610
+ * @default 'light'
2611
+ */
2612
+ colorScheme?: 'auto' | 'light' | 'dark';
2613
+ features?: AgentWidgetFeatureFlags;
2614
+ /**
2615
+ * When true, focus the chat input after the panel opens and the open animation completes.
2616
+ * Applies to launcher mode (user click, controller.open(), autoExpand) and inline mode (on init).
2617
+ * Skip when voice is active to avoid stealing focus from voice UI.
2618
+ * @default false
2619
+ */
2620
+ autoFocusInput?: boolean;
2621
+ launcher?: AgentWidgetLauncherConfig;
2622
+ initialMessages?: AgentWidgetMessage[];
2623
+ suggestionChips?: string[];
2624
+ suggestionChipsConfig?: AgentWidgetSuggestionChipsConfig;
2625
+ debug?: boolean;
2626
+ formEndpoint?: string;
2627
+ launcherWidth?: string;
2628
+ sendButton?: AgentWidgetSendButtonConfig;
2629
+ statusIndicator?: AgentWidgetStatusIndicatorConfig;
2630
+ voiceRecognition?: AgentWidgetVoiceRecognitionConfig;
2631
+ /**
2632
+ * Text-to-speech configuration for reading assistant messages aloud.
2633
+ * Uses the browser's Web Speech API (`speechSynthesis`).
2634
+ *
2635
+ * @example
2636
+ * ```typescript
2637
+ * config: {
2638
+ * textToSpeech: {
2639
+ * enabled: true,
2640
+ * voice: 'Google US English',
2641
+ * rate: 1.0,
2642
+ * pitch: 1.0
2643
+ * }
2644
+ * }
2645
+ * ```
2646
+ */
2647
+ textToSpeech?: TextToSpeechConfig;
2648
+ toolCall?: AgentWidgetToolCallConfig$1;
2649
+ /**
2650
+ * Configuration for tool approval bubbles.
2651
+ * Set to `false` to disable built-in approval handling entirely.
2652
+ *
2653
+ * @example
2654
+ * ```typescript
2655
+ * config: {
2656
+ * approval: {
2657
+ * title: "Permission Required",
2658
+ * approveLabel: "Allow",
2659
+ * denyLabel: "Block",
2660
+ * approveButtonColor: "#16a34a"
2661
+ * }
2662
+ * }
2663
+ * ```
2664
+ */
2665
+ approval?: AgentWidgetApprovalConfig | false;
2666
+ postprocessMessage?: (context: {
2667
+ text: string;
2668
+ message: AgentWidgetMessage;
2669
+ streaming: boolean;
2670
+ raw?: string;
2671
+ }) => string;
2672
+ plugins?: AgentWidgetPlugin[];
2673
+ contextProviders?: AgentWidgetContextProvider[];
2674
+ requestMiddleware?: AgentWidgetRequestMiddleware;
2675
+ actionParsers?: AgentWidgetActionParser[];
2676
+ actionHandlers?: AgentWidgetActionHandler[];
2677
+ storageAdapter?: AgentWidgetStorageAdapter;
2678
+ /**
2679
+ * Called after state is loaded from the storage adapter, but before the widget
2680
+ * initializes with that state. Use this to transform or inject messages based
2681
+ * on external state (e.g., navigation flags, checkout returns).
2682
+ *
2683
+ * This hook runs synchronously and must return the (potentially modified) state.
2684
+ *
2685
+ * Returning `{ state, open: true }` also signals that the widget panel should
2686
+ * open after initialization — useful when injecting a post-navigation message
2687
+ * that the user should immediately see.
2688
+ *
2689
+ * @example
2690
+ * ```typescript
2691
+ * // Plain state transform (existing form, still supported)
2692
+ * config: {
2693
+ * onStateLoaded: (state) => {
2694
+ * const navMessage = consumeNavigationFlag();
2695
+ * if (navMessage) {
2696
+ * return {
2697
+ * ...state,
2698
+ * messages: [...(state.messages || []), {
2699
+ * id: `nav-${Date.now()}`,
2700
+ * role: 'assistant',
2701
+ * content: navMessage,
2702
+ * createdAt: new Date().toISOString()
2703
+ * }]
2704
+ * };
2705
+ * }
2706
+ * return state;
2707
+ * }
2708
+ * }
2709
+ * ```
2710
+ *
2711
+ * @example
2712
+ * ```typescript
2713
+ * // Return { state, open: true } to also open the panel
2714
+ * config: {
2715
+ * onStateLoaded: (state) => {
2716
+ * const navMessage = consumeNavigationFlag();
2717
+ * if (navMessage) {
2718
+ * return {
2719
+ * state: {
2720
+ * ...state,
2721
+ * messages: [...(state.messages || []), {
2722
+ * id: `nav-${Date.now()}`,
2723
+ * role: 'assistant',
2724
+ * content: navMessage,
2725
+ * createdAt: new Date().toISOString()
2726
+ * }]
2727
+ * },
2728
+ * open: true
2729
+ * };
2730
+ * }
2731
+ * return state;
2732
+ * }
2733
+ * }
2734
+ * ```
2735
+ */
2736
+ onStateLoaded?: (state: AgentWidgetStoredState) => AgentWidgetStoredState | {
2737
+ state: AgentWidgetStoredState;
2738
+ open?: boolean;
2739
+ };
2740
+ /**
2741
+ * Registry of custom components that can be rendered from JSON directives.
2742
+ * Components are registered by name and can be invoked via JSON responses
2743
+ * with the format: `{"component": "ComponentName", "props": {...}}`
2744
+ *
2745
+ * @example
2746
+ * ```typescript
2747
+ * config: {
2748
+ * components: {
2749
+ * ProductCard: (props, context) => {
2750
+ * const card = document.createElement("div");
2751
+ * card.innerHTML = `<h3>${props.title}</h3><p>$${props.price}</p>`;
2752
+ * return card;
2753
+ * }
2754
+ * }
2755
+ * }
2756
+ * ```
2757
+ */
2758
+ components?: Record<string, AgentWidgetComponentRenderer>;
2759
+ /**
2760
+ * Enable component streaming. When true, component props will be updated
2761
+ * incrementally as they stream in from the JSON response.
2762
+ *
2763
+ * @default true
2764
+ */
2765
+ enableComponentStreaming?: boolean;
2766
+ /**
2767
+ * When false, JSON component directives render without the default bubble chrome
2768
+ * (surface background, border, extra padding). Use for wide custom cards in the transcript.
2769
+ * @default true
2770
+ */
2771
+ wrapComponentDirectiveInBubble?: boolean;
2772
+ /**
2773
+ * Custom stream parser for extracting text from streaming structured responses.
2774
+ * Handles incremental parsing of JSON, XML, or other formats.
2775
+ * If not provided, uses the default JSON parser.
2776
+ *
2777
+ * @example
2778
+ * ```typescript
2779
+ * streamParser: () => ({
2780
+ * processChunk: async (content) => {
2781
+ * // Return null if not your format, or extracted text if available
2782
+ * if (!content.trim().startsWith('{')) return null;
2783
+ * return extractText(content);
2784
+ * },
2785
+ * getExtractedText: () => extractedText
2786
+ * })
2787
+ * ```
2788
+ */
2789
+ streamParser?: () => AgentWidgetStreamParser;
2790
+ /**
2791
+ * Additional localStorage key to clear when the clear chat button is clicked.
2792
+ * The widget automatically clears `"persona-chat-history"` by default.
2793
+ * Use this option to clear additional keys (e.g., if you're using a custom storage key).
2794
+ *
2795
+ * @example
2796
+ * ```typescript
2797
+ * config: {
2798
+ * clearChatHistoryStorageKey: "my-custom-chat-history"
2799
+ * }
2800
+ * ```
2801
+ */
2802
+ clearChatHistoryStorageKey?: string;
2803
+ /**
2804
+ * Built-in parser type selector. Provides an easy way to choose a parser without importing functions.
2805
+ * If both `parserType` and `streamParser` are provided, `streamParser` takes precedence.
2806
+ *
2807
+ * - `"plain"` - Plain text parser (default). Passes through text as-is.
2808
+ * - `"json"` - JSON parser using partial-json. Extracts `text` field from JSON objects incrementally.
2809
+ * - `"regex-json"` - Regex-based JSON parser. Less robust but faster fallback for simple JSON.
2810
+ * - `"xml"` - XML parser. Extracts text content from XML tags.
2811
+ *
2812
+ * @example
2813
+ * ```typescript
2814
+ * config: {
2815
+ * parserType: "json" // Use built-in JSON parser
2816
+ * }
2817
+ * ```
2818
+ *
2819
+ * @example
2820
+ * ```typescript
2821
+ * config: {
2822
+ * parserType: "json",
2823
+ * streamParser: () => customParser() // Custom parser overrides parserType
2824
+ * }
2825
+ * ```
2826
+ */
2827
+ parserType?: "plain" | "json" | "regex-json" | "xml";
2828
+ /**
2829
+ * Custom fetch function for full control over API requests.
2830
+ * Use this for custom authentication, request/response transformation, etc.
2831
+ *
2832
+ * When provided, this function is called instead of the default fetch.
2833
+ * You receive the URL, RequestInit, and the payload that would be sent.
2834
+ *
2835
+ * @example
2836
+ * ```typescript
2837
+ * config: {
2838
+ * customFetch: async (url, init, payload) => {
2839
+ * // Transform request for your API format
2840
+ * const myPayload = {
2841
+ * flow: { id: 'my-flow-id' },
2842
+ * messages: payload.messages,
2843
+ * options: { stream_response: true }
2844
+ * };
2845
+ *
2846
+ * // Add auth header
2847
+ * const token = await getAuthToken();
2848
+ *
2849
+ * return fetch('/my-api/dispatch', {
2850
+ * method: 'POST',
2851
+ * headers: {
2852
+ * 'Content-Type': 'application/json',
2853
+ * 'Authorization': `Bearer ${token}`
2854
+ * },
2855
+ * body: JSON.stringify(myPayload),
2856
+ * signal: init.signal
2857
+ * });
2858
+ * }
2859
+ * }
2860
+ * ```
2861
+ */
2862
+ customFetch?: AgentWidgetCustomFetch;
2863
+ /**
2864
+ * Custom SSE event parser for non-standard streaming response formats.
2865
+ *
2866
+ * Use this when your API returns SSE events in a different format than expected.
2867
+ * Return `{ text }` for text chunks, `{ done: true }` for completion,
2868
+ * `{ error }` for errors, or `null` to ignore the event.
2869
+ *
2870
+ * @example
2871
+ * ```typescript
2872
+ * // For Runtype API format
2873
+ * config: {
2874
+ * parseSSEEvent: (data) => {
2875
+ * if ((data.type === 'step_delta' || data.type === 'step_chunk') && (data.delta || data.chunk)) {
2876
+ * return { text: data.delta ?? data.chunk };
2877
+ * }
2878
+ * if (data.type === 'flow_complete') {
2879
+ * return { done: true };
2880
+ * }
2881
+ * if (data.type === 'step_error') {
2882
+ * return { error: data.error };
2883
+ * }
2884
+ * return null; // Ignore other events
2885
+ * }
2886
+ * }
2887
+ * ```
2888
+ */
2889
+ parseSSEEvent?: AgentWidgetSSEEventParser;
2890
+ /**
2891
+ * Layout configuration for customizing widget appearance and structure.
2892
+ * Provides control over header, messages, and content slots.
2893
+ *
2894
+ * @example
2895
+ * ```typescript
2896
+ * config: {
2897
+ * layout: {
2898
+ * header: { layout: "minimal" },
2899
+ * messages: { avatar: { show: true } }
2900
+ * }
2901
+ * }
2902
+ * ```
2903
+ */
2904
+ layout?: AgentWidgetLayoutConfig;
2905
+ /**
2906
+ * Markdown rendering configuration.
2907
+ * Customize how markdown is parsed and rendered in chat messages.
2908
+ *
2909
+ * Override methods:
2910
+ * 1. **CSS Variables** - Override `--cw-md-*` variables in your stylesheet
2911
+ * 2. **Options** - Configure marked parser behavior
2912
+ * 3. **Renderers** - Custom rendering functions for specific elements
2913
+ * 4. **postprocessMessage** - Complete control over message transformation
2914
+ *
2915
+ * @example
2916
+ * ```typescript
2917
+ * config: {
2918
+ * markdown: {
2919
+ * options: { breaks: true, gfm: true },
2920
+ * renderer: {
2921
+ * link(token) {
2922
+ * return `<a href="${token.href}" target="_blank">${token.text}</a>`;
2923
+ * }
2924
+ * }
2925
+ * }
2926
+ * }
2927
+ * ```
2928
+ */
2929
+ markdown?: AgentWidgetMarkdownConfig;
2930
+ /**
2931
+ * HTML sanitization for rendered message content.
2932
+ *
2933
+ * The widget renders AI-generated markdown as HTML. By default, all HTML
2934
+ * output is sanitized using DOMPurify to prevent XSS attacks.
2935
+ *
2936
+ * - `true` (default): sanitize using built-in DOMPurify
2937
+ * - `false`: disable sanitization (only use with fully trusted content sources)
2938
+ * - `(html: string) => string`: custom sanitizer function
2939
+ *
2940
+ * @default true
2941
+ */
2942
+ sanitize?: boolean | ((html: string) => string);
2943
+ /**
2944
+ * Configuration for message action buttons (copy, upvote, downvote).
2945
+ * Shows action buttons on assistant messages for user feedback.
2946
+ *
2947
+ * @example
2948
+ * ```typescript
2949
+ * config: {
2950
+ * messageActions: {
2951
+ * enabled: true,
2952
+ * showCopy: true,
2953
+ * showUpvote: true,
2954
+ * showDownvote: true,
2955
+ * visibility: 'hover',
2956
+ * onFeedback: (feedback) => {
2957
+ * console.log('Feedback:', feedback.type, feedback.messageId);
2958
+ * },
2959
+ * onCopy: (message) => {
2960
+ * console.log('Copied message:', message.id);
2961
+ * }
2962
+ * }
2963
+ * }
2964
+ * ```
2965
+ */
2966
+ messageActions?: AgentWidgetMessageActionsConfig;
2967
+ /**
2968
+ * Configuration for file attachments in the composer.
2969
+ * When enabled, users can attach images to their messages.
2970
+ *
2971
+ * @example
2972
+ * ```typescript
2973
+ * config: {
2974
+ * attachments: {
2975
+ * enabled: true,
2976
+ * maxFileSize: 5 * 1024 * 1024, // 5MB
2977
+ * maxFiles: 4
2978
+ * }
2979
+ * }
2980
+ * ```
2981
+ */
2982
+ attachments?: AgentWidgetAttachmentsConfig;
2983
+ /**
2984
+ * Composer extras for custom `renderComposer` plugins (model picker, etc.).
2985
+ * `selectedModelId` may be updated at runtime by the host.
2986
+ */
2987
+ composer?: AgentWidgetComposerConfig;
2988
+ /**
2989
+ * Persist widget state (open/closed, voice mode) across page navigations.
2990
+ * When `true`, uses default settings with sessionStorage.
2991
+ * When an object, allows customizing storage type, key prefix, and what to persist.
2992
+ *
2993
+ * @example
2994
+ * ```typescript
2995
+ * // Simple usage - persist open state in sessionStorage
2996
+ * config: {
2997
+ * persistState: true
2998
+ * }
2999
+ * ```
3000
+ *
3001
+ * @example
3002
+ * ```typescript
3003
+ * // Advanced usage
3004
+ * config: {
3005
+ * persistState: {
3006
+ * storage: 'local', // Use localStorage
3007
+ * persist: {
3008
+ * openState: true,
3009
+ * voiceState: true,
3010
+ * focusInput: true
3011
+ * }
3012
+ * }
3013
+ * }
3014
+ * ```
3015
+ */
3016
+ persistState?: boolean | AgentWidgetPersistStateConfig;
3017
+ /**
3018
+ * Configuration for customizing the loading indicator.
3019
+ * The loading indicator is shown while waiting for a response or
3020
+ * when an assistant message is streaming but has no content yet.
3021
+ *
3022
+ * @example
3023
+ * ```typescript
3024
+ * config: {
3025
+ * loadingIndicator: {
3026
+ * render: ({ location, defaultRenderer }) => {
3027
+ * if (location === 'standalone') {
3028
+ * const el = document.createElement('div');
3029
+ * el.textContent = 'Thinking...';
3030
+ * return el;
3031
+ * }
3032
+ * return defaultRenderer();
3033
+ * }
3034
+ * }
3035
+ * }
3036
+ * ```
3037
+ */
3038
+ loadingIndicator?: AgentWidgetLoadingIndicatorConfig;
3039
+ };
3040
+ type AgentWidgetMessageRole = "user" | "assistant" | "system";
3041
+ type AgentWidgetReasoning = {
3042
+ id: string;
3043
+ status: "pending" | "streaming" | "complete";
3044
+ chunks: string[];
3045
+ startedAt?: number;
3046
+ completedAt?: number;
3047
+ durationMs?: number;
3048
+ };
3049
+ type AgentWidgetToolCall = {
3050
+ id: string;
3051
+ name?: string;
3052
+ status: "pending" | "running" | "complete";
3053
+ args?: unknown;
3054
+ chunks?: string[];
3055
+ result?: unknown;
3056
+ duration?: number;
3057
+ startedAt?: number;
3058
+ completedAt?: number;
3059
+ durationMs?: number;
3060
+ };
3061
+ /**
3062
+ * Represents a tool approval request in the chat conversation.
3063
+ * Created when the agent requires human approval before executing a tool.
3064
+ */
3065
+ type AgentWidgetApproval = {
3066
+ id: string;
3067
+ status: "pending" | "approved" | "denied" | "timeout";
3068
+ agentId: string;
3069
+ executionId: string;
3070
+ toolName: string;
3071
+ toolType?: string;
3072
+ description: string;
3073
+ parameters?: unknown;
3074
+ resolvedAt?: number;
3075
+ };
3076
+ type AgentWidgetMessageVariant = "assistant" | "reasoning" | "tool" | "approval";
3077
+ /**
3078
+ * Represents a message in the chat conversation.
3079
+ *
3080
+ * @property id - Unique message identifier
3081
+ * @property role - Message role: "user", "assistant", or "system"
3082
+ * @property content - Message text content (for display)
3083
+ * @property contentParts - Original multi-modal content parts (for API requests)
3084
+ * @property createdAt - ISO timestamp when message was created
3085
+ * @property streaming - Whether message is still streaming (for assistant messages)
3086
+ * @property variant - Message variant for assistant messages: "assistant", "reasoning", or "tool"
3087
+ * @property sequence - Message ordering number
3088
+ * @property reasoning - Reasoning data for assistant reasoning messages
3089
+ * @property toolCall - Tool call data for assistant tool messages
3090
+ * @property tools - Array of tool calls
3091
+ * @property viaVoice - Set to `true` when a user message is sent via voice recognition.
3092
+ * Useful for implementing voice-specific behaviors like auto-reactivation.
3093
+ */
3094
+ type AgentWidgetMessage = {
3095
+ id: string;
3096
+ role: AgentWidgetMessageRole;
3097
+ content: string;
3098
+ createdAt: string;
3099
+ /**
3100
+ * Original multi-modal content parts for this message.
3101
+ * When present, this is sent to the API instead of `content`.
3102
+ * The `content` field contains the text-only representation for display.
3103
+ */
3104
+ contentParts?: ContentPart[];
3105
+ streaming?: boolean;
3106
+ variant?: AgentWidgetMessageVariant;
3107
+ sequence?: number;
3108
+ reasoning?: AgentWidgetReasoning;
3109
+ toolCall?: AgentWidgetToolCall;
3110
+ tools?: AgentWidgetToolCall[];
3111
+ /** Approval data for messages with variant "approval" */
3112
+ approval?: AgentWidgetApproval;
3113
+ viaVoice?: boolean;
3114
+ /**
3115
+ * Set to `true` on placeholder messages injected during Runtype voice processing.
3116
+ * Use this in `messageTransform` to detect and customize voice processing placeholders.
3117
+ *
3118
+ * @example
3119
+ * messageTransform: ({ text, message }) => {
3120
+ * if (message.voiceProcessing && message.role === 'user') {
3121
+ * return '<div class="my-voice-spinner">Transcribing...</div>';
3122
+ * }
3123
+ * return text;
3124
+ * }
3125
+ */
3126
+ voiceProcessing?: boolean;
3127
+ /**
3128
+ * Raw structured payload for this message (e.g., JSON action response).
3129
+ * Populated automatically when structured parsers run.
3130
+ */
3131
+ rawContent?: string;
3132
+ /**
3133
+ * LLM-specific content for API requests.
3134
+ * When present, this is sent to the LLM instead of `content`.
3135
+ *
3136
+ * Priority for API payload:
3137
+ * 1. `contentParts` (if present, used as-is for multi-modal)
3138
+ * 2. `llmContent` (if present, sent as string)
3139
+ * 3. `rawContent` (backward compatibility with structured parsers)
3140
+ * 4. `content` (fallback - display content)
3141
+ *
3142
+ * The `content` field is always used for UI display.
3143
+ *
3144
+ * @example
3145
+ * // Show full details to user, send summary to LLM
3146
+ * {
3147
+ * content: "**Product:** iPhone 15 Pro\n**Price:** $1,199\n**SKU:** IP15P-256",
3148
+ * llmContent: "[Product search: iPhone 15 Pro, $1199]"
3149
+ * }
3150
+ */
3151
+ llmContent?: string;
3152
+ /**
3153
+ * Text segment identity for chronological ordering.
3154
+ * When present, identifies which text segment this message represents
3155
+ * (e.g., "text_0", "text_1") for messages split at tool boundaries.
3156
+ */
3157
+ partId?: string;
3158
+ /**
3159
+ * Metadata for messages created during agent loop execution.
3160
+ * Contains execution context like iteration number and turn ID.
3161
+ */
3162
+ agentMetadata?: AgentMessageMetadata;
3163
+ };
3164
+ /**
3165
+ * Options for injecting a message into the conversation.
3166
+ * Supports dual-content where UI display differs from LLM context.
3167
+ *
3168
+ * @example
3169
+ * // Same content for user and LLM
3170
+ * {
3171
+ * role: 'assistant',
3172
+ * content: 'Here are your search results...'
3173
+ * }
3174
+ *
3175
+ * @example
3176
+ * // Different content: user sees full details, LLM sees summary
3177
+ * {
3178
+ * role: 'assistant',
3179
+ * content: '**Found 3 products:**\n- iPhone 15 Pro ($1,199)\n- iPhone 15 ($999)',
3180
+ * llmContent: '[Search results: 3 iPhones, $999-$1199]'
3181
+ * }
3182
+ */
3183
+ type InjectMessageOptions = {
3184
+ /**
3185
+ * Message role: "assistant", "user", or "system"
3186
+ */
3187
+ role: AgentWidgetMessageRole;
3188
+ /**
3189
+ * Content displayed to the user in the chat UI.
3190
+ * This is what appears in the message bubble.
3191
+ */
3192
+ content: string;
3193
+ /**
3194
+ * Content sent to the LLM in API requests.
3195
+ * When omitted, `content` is used for both display and LLM.
3196
+ *
3197
+ * Use cases:
3198
+ * - Redacted content: Show full product details to user, send summary to LLM
3199
+ * - Structured data: Show formatted markdown to user, send JSON to LLM
3200
+ * - Token optimization: Show verbose content to user, send concise version to LLM
3201
+ */
3202
+ llmContent?: string;
3203
+ /**
3204
+ * Multi-modal content parts for the LLM (images, files).
3205
+ * Takes precedence over `llmContent` when present.
3206
+ * The `content` field is still used for UI display.
3207
+ */
3208
+ contentParts?: ContentPart[];
3209
+ /**
3210
+ * Optional message ID. If omitted, auto-generated based on role.
3211
+ */
3212
+ id?: string;
3213
+ /**
3214
+ * Optional creation timestamp (ISO string). If omitted, uses current time.
3215
+ */
3216
+ createdAt?: string;
3217
+ /**
3218
+ * Optional sequence number for ordering.
3219
+ */
3220
+ sequence?: number;
3221
+ /**
3222
+ * Whether the message is still streaming (for incremental updates).
3223
+ * @default false
3224
+ */
3225
+ streaming?: boolean;
3226
+ /**
3227
+ * Mark this message as a voice processing placeholder.
3228
+ * Consumers can detect this in `messageTransform` to render custom UI.
3229
+ */
3230
+ voiceProcessing?: boolean;
3231
+ };
3232
+ /**
3233
+ * Options for injecting assistant messages (most common case).
3234
+ * Role defaults to 'assistant'.
3235
+ */
3236
+ type InjectAssistantMessageOptions = Omit<InjectMessageOptions, "role">;
3237
+ /**
3238
+ * Options for injecting user messages.
3239
+ * Role defaults to 'user'.
3240
+ */
3241
+ type InjectUserMessageOptions = Omit<InjectMessageOptions, "role">;
3242
+ /**
3243
+ * Options for injecting system messages.
3244
+ * Role defaults to 'system'.
3245
+ */
3246
+ type InjectSystemMessageOptions = Omit<InjectMessageOptions, "role">;
3247
+ type PersonaArtifactRecord = {
3248
+ id: string;
3249
+ artifactType: PersonaArtifactKind;
3250
+ title?: string;
3251
+ status: "streaming" | "complete";
3252
+ markdown?: string;
3253
+ component?: string;
3254
+ props?: Record<string, unknown>;
3255
+ };
3256
+ /** Programmatic artifact upsert (controller / window API) */
3257
+ type PersonaArtifactManualUpsert = {
3258
+ id?: string;
3259
+ artifactType: "markdown";
3260
+ title?: string;
3261
+ content: string;
3262
+ } | {
3263
+ id?: string;
3264
+ artifactType: "component";
3265
+ title?: string;
3266
+ component: string;
3267
+ props?: Record<string, unknown>;
3268
+ };
3269
+ type AgentWidgetEvent = {
3270
+ type: "message";
3271
+ message: AgentWidgetMessage;
3272
+ } | {
3273
+ type: "status";
3274
+ status: "connecting" | "connected" | "error" | "idle";
3275
+ } | {
3276
+ type: "error";
3277
+ error: Error;
3278
+ } | {
3279
+ type: "artifact_start";
3280
+ id: string;
3281
+ artifactType: PersonaArtifactKind;
3282
+ title?: string;
3283
+ component?: string;
3284
+ } | {
3285
+ type: "artifact_delta";
3286
+ id: string;
3287
+ artDelta: string;
3288
+ } | {
3289
+ type: "artifact_update";
3290
+ id: string;
3291
+ props: Record<string, unknown>;
3292
+ component?: string;
3293
+ } | {
3294
+ type: "artifact_complete";
3295
+ id: string;
3296
+ };
3297
+
3298
+ /** Field definition types for the declarative configurator system (headless — no DOM) */
3299
+
3300
+ type FieldType = 'color' | 'slider' | 'toggle' | 'select' | 'text' | 'chip-list' | 'color-scale' | 'token-ref' | 'role-assignment';
3301
+ interface SliderOptions {
3302
+ min: number;
3303
+ max: number;
3304
+ step: number;
3305
+ unit?: 'px' | 'rem' | 'none';
3306
+ /** Treat max value as 9999px (border-radius: full) */
3307
+ isRadiusFull?: boolean;
3308
+ }
3309
+ interface SelectOption {
3310
+ value: string;
3311
+ label: string;
3312
+ }
3313
+ interface ColorScaleOptions {
3314
+ /** Which palette color family (e.g., 'primary', 'gray') */
3315
+ colorFamily: string;
3316
+ }
3317
+ interface TokenRefOptions {
3318
+ /** Token type to filter available references */
3319
+ tokenType: 'color' | 'spacing' | 'radius' | 'shadow' | 'typography';
3320
+ /** Available palette families to reference */
3321
+ families?: string[];
3322
+ }
3323
+ /** Which kind of value a role target expects */
3324
+ type RoleTargetKind = 'background' | 'foreground' | 'border' | 'accent';
3325
+ /** A single token path that a role assignment writes to */
3326
+ interface RoleTarget {
3327
+ /** Theme token path (e.g., 'components.message.user.background') */
3328
+ path: string;
3329
+ /** What kind of value this target expects */
3330
+ kind: RoleTargetKind;
3331
+ }
3332
+ /** An intensity preset (e.g., Solid uses .500 shades, Soft uses .100 shades) */
3333
+ interface RoleIntensity {
3334
+ id: string;
3335
+ label: string;
3336
+ }
3337
+ /** Options for a role-assignment field */
3338
+ interface RoleAssignmentOptions {
3339
+ /** Unique role identifier */
3340
+ roleId: string;
3341
+ /** Helper text shown below the role name */
3342
+ helper: string;
3343
+ /** Token paths this role writes to */
3344
+ targets: RoleTarget[];
3345
+ /** Available intensity presets */
3346
+ intensities: RoleIntensity[];
3347
+ /** Which data-persona-theme-zone this role corresponds to (for preview highlighting) */
3348
+ previewZone?: string;
3349
+ }
3350
+ interface FieldDef {
3351
+ id: string;
3352
+ label: string;
3353
+ description?: string;
3354
+ type: FieldType;
3355
+ /** Dot-path into the config/theme object */
3356
+ path: string;
3357
+ defaultValue?: unknown;
3358
+ /** Slider-specific options */
3359
+ slider?: SliderOptions;
3360
+ /** Select-specific options */
3361
+ options?: SelectOption[];
3362
+ /** Color-scale-specific options */
3363
+ colorScale?: ColorScaleOptions;
3364
+ /** Token-ref-specific options */
3365
+ tokenRef?: TokenRefOptions;
3366
+ /** Role-assignment-specific options */
3367
+ roleAssignment?: RoleAssignmentOptions;
3368
+ /** CSS property hint for value formatting */
3369
+ cssProperty?: string;
3370
+ /** Whether this is a theme path (vs config path) */
3371
+ isThemePath?: boolean;
3372
+ /** Convert stored value into a control-friendly value */
3373
+ formatValue?: (value: unknown) => unknown;
3374
+ /** Convert control input back into the stored value shape */
3375
+ parseValue?: (value: unknown) => unknown;
3376
+ }
3377
+ interface SectionDef {
3378
+ id: string;
3379
+ title: string;
3380
+ description?: string;
3381
+ fields: FieldDef[];
3382
+ /** Whether the section starts collapsed */
3383
+ collapsed?: boolean;
3384
+ /** Preset buttons for this section */
3385
+ presets?: SectionPreset[];
3386
+ }
3387
+ interface SectionPreset {
3388
+ id: string;
3389
+ label: string;
3390
+ values: Record<string, unknown>;
3391
+ }
3392
+ interface TabDef {
3393
+ id: string;
3394
+ label: string;
3395
+ icon?: string;
3396
+ sections: SectionDef[];
3397
+ }
3398
+ interface SubGroupDef {
3399
+ label: string;
3400
+ sections: SectionDef[];
3401
+ /** When true, the sub-group starts collapsed and must be explicitly expanded */
3402
+ collapsedByDefault?: boolean;
3403
+ }
3404
+ /** Extract the toolCall config type from AgentWidgetConfig */
3405
+ type AgentWidgetToolCallConfig = NonNullable<AgentWidgetConfig['toolCall']>;
3406
+ interface ThemeEditorPreset {
3407
+ id: string;
3408
+ name: string;
3409
+ description: string;
3410
+ theme: DeepPartial<PersonaTheme>;
3411
+ darkTheme?: DeepPartial<PersonaTheme>;
3412
+ /** Tool call styling for light mode */
3413
+ toolCall?: AgentWidgetToolCallConfig;
3414
+ /** Tool call styling for dark mode (falls back to toolCall if not set) */
3415
+ darkToolCall?: AgentWidgetToolCallConfig;
3416
+ preview: {
3417
+ primary: string;
3418
+ surface: string;
3419
+ accent: string;
3420
+ };
3421
+ darkPreview?: {
3422
+ primary: string;
3423
+ surface: string;
3424
+ accent: string;
3425
+ };
3426
+ /** Tags for filtering/categorization */
3427
+ tags?: string[];
3428
+ }
3429
+ interface ConfiguratorSnapshot {
3430
+ version: 2;
3431
+ config: Record<string, unknown>;
3432
+ theme: PersonaTheme;
3433
+ }
3434
+ type ConfigChangeListener = (config: AgentWidgetConfig, theme: PersonaTheme) => void;
3435
+ /** Callback for when a control value changes */
3436
+ type OnChangeCallback = (path: string, value: unknown) => void;
3437
+
3438
+ /** Headless state management for the theme editor (no DOM, no localStorage, no side effects) */
3439
+
3440
+ declare class ThemeEditorState {
3441
+ private config;
3442
+ private theme;
3443
+ private listeners;
3444
+ private history;
3445
+ private historyIndex;
3446
+ private suppressHistory;
3447
+ constructor(initialTheme?: Partial<PersonaTheme>, initialConfig?: Partial<AgentWidgetConfig>, options?: {
3448
+ mergeDefaults?: boolean;
3449
+ });
3450
+ /**
3451
+ * Get a value using a dot-path.
3452
+ * - `theme.*` → reads from the PersonaTheme
3453
+ * - `darkTheme.*` → reads from config.darkTheme
3454
+ * - everything else → reads from the AgentWidgetConfig
3455
+ */
3456
+ get(path: string): unknown;
3457
+ getTheme(): PersonaTheme;
3458
+ getConfig(): AgentWidgetConfig;
3459
+ /**
3460
+ * Set a value using a dot-path.
3461
+ * - `theme.*` → writes into the PersonaTheme
3462
+ * - `darkTheme.*` → writes into config.darkTheme
3463
+ * - everything else → writes into AgentWidgetConfig
3464
+ */
3465
+ set(path: string, value: unknown): void;
3466
+ /** Batch-set multiple paths at once */
3467
+ setBatch(updates: Record<string, unknown>): void;
3468
+ /** Replace the entire theme */
3469
+ setTheme(theme: PersonaTheme): void;
3470
+ /** Replace the entire config (for preset loading) */
3471
+ setFullConfig(config: AgentWidgetConfig, theme?: PersonaTheme): void;
3472
+ /** Import a snapshot (v2 or raw theme) */
3473
+ importSnapshot(snapshot: unknown): void;
3474
+ /** Reset to defaults */
3475
+ resetToDefaults(): void;
3476
+ canUndo(): boolean;
3477
+ canRedo(): boolean;
3478
+ getHistoryLength(): number;
3479
+ getHistoryIndex(): number;
3480
+ undo(): void;
3481
+ redo(): void;
3482
+ exportSnapshot(): ConfiguratorSnapshot;
3483
+ onChange(listener: ConfigChangeListener): () => void;
3484
+ private syncThemeIntoConfig;
3485
+ private notifyListeners;
3486
+ private recordHistory;
3487
+ private pushHistorySnapshot;
3488
+ private restoreSnapshot;
3489
+ }
3490
+
3491
+ /** Declarative section/field definitions for the theme editor (pure data — no DOM, no render logic) */
3492
+
3493
+ declare const STYLE_SECTIONS: SectionDef[];
3494
+ declare const PALETTE_SECTION: SectionDef;
3495
+ declare const SEMANTIC_COLORS_SECTION: SectionDef;
3496
+ declare const COLORS_SECTIONS: SectionDef[];
3497
+ /** Shared shape sections (not scoped to light/dark) */
3498
+ declare const COMPONENT_SHAPE_SECTIONS: SectionDef[];
3499
+ /** Component color sections (can be scoped for light/dark) */
3500
+ declare const COMPONENT_COLOR_SECTIONS: SectionDef[];
3501
+ declare const COMPONENTS_SECTIONS: SectionDef[];
3502
+ declare const CONFIGURE_SUB_GROUPS: SubGroupDef[];
3503
+ declare const CONFIGURE_SECTIONS: SectionDef[];
3504
+ /** Section 1: Theme — color mode selection */
3505
+ declare const THEME_SECTION: SectionDef;
3506
+ /** Section 2: Brand Palette — primary colors + collapsed status colors */
3507
+ declare const BRAND_PALETTE_SECTION: SectionDef;
3508
+ /** Section 2b: Status palette — collapsed under Brand Palette */
3509
+ declare const STATUS_PALETTE_SECTION: SectionDef;
3510
+ /** Section 3: Interface Roles — the main theming surface */
3511
+ declare const INTERFACE_ROLES_SECTION: SectionDef;
3512
+ /** Section 4: Status Colors — feedback semantic tokens */
3513
+ declare const STATUS_COLORS_SECTION: SectionDef;
3514
+ /** Section 5: Advanced Tokens — entry point for drill-downs (no fields) */
3515
+ declare const ADVANCED_TOKENS_SECTION: SectionDef;
3516
+ /** V2 Style tab sections — outcome-oriented editor */
3517
+ declare const STYLE_SECTIONS_V2: SectionDef[];
3518
+ declare const ALL_TABS: TabDef[];
3519
+ type ThemeScope = 'theme' | 'darkTheme';
3520
+ type ThemeVariant = 'light' | 'dark';
3521
+ /** Look up a section by ID from an array of sections */
3522
+ declare function findSection(sections: SectionDef[], id: string): SectionDef;
3523
+ /** Create a light/dark scoped copy of a section */
3524
+ declare function scopeSection(section: SectionDef, scope: ThemeScope, variant: ThemeVariant, collapsed?: boolean | undefined): SectionDef;
3525
+
3526
+ /** Theme editor presets — unified collection of built-in presets */
3527
+
3528
+ declare const BUILT_IN_PRESETS: ThemeEditorPreset[];
3529
+ /** All built-in presets */
3530
+ declare const THEME_EDITOR_PRESETS: ThemeEditorPreset[];
3531
+ /** Look up a preset by ID */
3532
+ declare function getThemeEditorPreset(id: string): ThemeEditorPreset | undefined;
3533
+
3534
+ type AgentWidgetSessionStatus = "idle" | "connecting" | "connected" | "error";
3535
+
3536
+ /**
3537
+ * Feedback UI components for CSAT and NPS collection
3538
+ */
3539
+ type CSATFeedbackOptions = {
3540
+ /** Callback when user submits CSAT feedback */
3541
+ onSubmit: (rating: number, comment?: string) => void | Promise<void>;
3542
+ /** Callback when user dismisses the feedback form */
3543
+ onDismiss?: () => void;
3544
+ /** Title text */
3545
+ title?: string;
3546
+ /** Subtitle/question text */
3547
+ subtitle?: string;
3548
+ /** Placeholder for optional comment field */
3549
+ commentPlaceholder?: string;
3550
+ /** Submit button text */
3551
+ submitText?: string;
3552
+ /** Skip button text */
3553
+ skipText?: string;
3554
+ /** Show comment field */
3555
+ showComment?: boolean;
3556
+ /** Rating labels (5 items for ratings 1-5) */
3557
+ ratingLabels?: [string, string, string, string, string];
3558
+ };
3559
+ type NPSFeedbackOptions = {
3560
+ /** Callback when user submits NPS feedback */
3561
+ onSubmit: (rating: number, comment?: string) => void | Promise<void>;
3562
+ /** Callback when user dismisses the feedback form */
3563
+ onDismiss?: () => void;
3564
+ /** Title text */
3565
+ title?: string;
3566
+ /** Subtitle/question text */
3567
+ subtitle?: string;
3568
+ /** Placeholder for optional comment field */
3569
+ commentPlaceholder?: string;
3570
+ /** Submit button text */
3571
+ submitText?: string;
3572
+ /** Skip button text */
3573
+ skipText?: string;
3574
+ /** Show comment field */
3575
+ showComment?: boolean;
3576
+ /** Low label (left side) */
3577
+ lowLabel?: string;
3578
+ /** High label (right side) */
3579
+ highLabel?: string;
3580
+ };
3581
+
3582
+ type Controller = {
3583
+ update: (config: AgentWidgetConfig) => void;
3584
+ destroy: () => void;
3585
+ open: () => void;
3586
+ close: () => void;
3587
+ toggle: () => void;
3588
+ clearChat: () => void;
3589
+ setMessage: (message: string) => boolean;
3590
+ submitMessage: (message?: string) => boolean;
3591
+ startVoiceRecognition: () => boolean;
3592
+ stopVoiceRecognition: () => boolean;
3593
+ /**
3594
+ * Inject a message into the conversation with dual-content support.
3595
+ * Auto-opens the widget if closed and launcher is enabled.
3596
+ */
3597
+ injectMessage: (options: InjectMessageOptions) => AgentWidgetMessage;
3598
+ /**
3599
+ * Convenience method for injecting assistant messages.
3600
+ */
3601
+ injectAssistantMessage: (options: InjectAssistantMessageOptions) => AgentWidgetMessage;
3602
+ /**
3603
+ * Convenience method for injecting user messages.
3604
+ */
3605
+ injectUserMessage: (options: InjectUserMessageOptions) => AgentWidgetMessage;
3606
+ /**
3607
+ * Convenience method for injecting system messages.
3608
+ */
3609
+ injectSystemMessage: (options: InjectSystemMessageOptions) => AgentWidgetMessage;
3610
+ /**
3611
+ * Inject multiple messages in a single batch with one sort and one render pass.
3612
+ */
3613
+ injectMessageBatch: (optionsList: InjectMessageOptions[]) => AgentWidgetMessage[];
3614
+ /**
3615
+ * @deprecated Use injectMessage() instead.
3616
+ */
3617
+ injectTestMessage: (event: AgentWidgetEvent) => void;
3618
+ getMessages: () => AgentWidgetMessage[];
3619
+ getStatus: () => AgentWidgetSessionStatus;
3620
+ getPersistentMetadata: () => Record<string, unknown>;
3621
+ updatePersistentMetadata: (updater: (prev: Record<string, unknown>) => Record<string, unknown>) => void;
3622
+ on: <K extends keyof AgentWidgetControllerEventMap>(event: K, handler: (payload: AgentWidgetControllerEventMap[K]) => void) => () => void;
3623
+ off: <K extends keyof AgentWidgetControllerEventMap>(event: K, handler: (payload: AgentWidgetControllerEventMap[K]) => void) => void;
3624
+ isOpen: () => boolean;
3625
+ isVoiceActive: () => boolean;
3626
+ getState: () => AgentWidgetStateSnapshot;
3627
+ showCSATFeedback: (options?: Partial<CSATFeedbackOptions>) => void;
3628
+ showNPSFeedback: (options?: Partial<NPSFeedbackOptions>) => void;
3629
+ submitCSATFeedback: (rating: number, comment?: string) => Promise<void>;
3630
+ submitNPSFeedback: (rating: number, comment?: string) => Promise<void>;
3631
+ /**
3632
+ * Connect an external SSE stream and process it through the SDK's
3633
+ * native event pipeline (tools, reasoning, streaming text, etc.).
3634
+ */
3635
+ connectStream: (stream: ReadableStream<Uint8Array>, options?: {
3636
+ assistantMessageId?: string;
3637
+ }) => Promise<void>;
3638
+ /** Push a raw event into the event stream buffer (for testing/debugging) */
3639
+ __pushEventStreamEvent: (event: {
3640
+ type: string;
3641
+ payload: unknown;
3642
+ }) => void;
3643
+ /** Opens the event stream panel */
3644
+ showEventStream: () => void;
3645
+ /** Closes the event stream panel */
3646
+ hideEventStream: () => void;
3647
+ /** Returns current visibility state of the event stream panel */
3648
+ isEventStreamVisible: () => boolean;
3649
+ /** Show artifact sidebar (no-op if features.artifacts.enabled is false) */
3650
+ showArtifacts: () => void;
3651
+ /** Hide artifact sidebar */
3652
+ hideArtifacts: () => void;
3653
+ /** Upsert an artifact programmatically */
3654
+ upsertArtifact: (manual: PersonaArtifactManualUpsert) => PersonaArtifactRecord | null;
3655
+ selectArtifact: (id: string) => void;
3656
+ clearArtifacts: () => void;
3657
+ /**
3658
+ * Focus the chat input. Returns true if focus succeeded, false if panel is closed
3659
+ * (launcher mode) or textarea is unavailable.
3660
+ */
3661
+ focusInput: () => boolean;
3662
+ /**
3663
+ * Programmatically resolve a pending approval.
3664
+ * @param approvalId - The approval ID to resolve
3665
+ * @param decision - "approved" or "denied"
3666
+ */
3667
+ resolveApproval: (approvalId: string, decision: 'approved' | 'denied') => Promise<void>;
3668
+ };
3669
+ type AgentWidgetController = Controller;
3670
+
3671
+ /**
3672
+ * Shared preview building blocks for theme editor preview renderers.
3673
+ * Used by both `createThemePreview()` (simple API) and the configurator's
3674
+ * advanced preview system. Separate file for code-splitting.
3675
+ */
3676
+
3677
+ declare const DEVICE_DIMENSIONS: Record<string, {
3678
+ w: number;
3679
+ h: number;
3680
+ }>;
3681
+ declare const ZOOM_MIN = 0.15;
3682
+ declare const ZOOM_MAX = 1.5;
3683
+ declare const SHELL_STYLE_ID = "persona-preview-shell-theme";
3684
+ declare const PREVIEW_STORAGE_ADAPTER: {
3685
+ load: () => null;
3686
+ save: () => void;
3687
+ clear: () => void;
3688
+ };
3689
+ declare const HOME_SUGGESTION_CHIPS: string[];
3690
+ declare function escapeHtml(str: string): string;
3691
+ type PreviewShellPalette = {
3692
+ pageBg: string;
3693
+ chromeBg: string;
3694
+ chromeBorder: string;
3695
+ dot: string;
3696
+ skeleton: string;
3697
+ cardBg: string;
3698
+ cardBorder: string;
3699
+ };
3700
+ declare function getShellPalette(shellMode: 'light' | 'dark'): PreviewShellPalette;
3701
+ declare function buildShellCss(shellMode: 'light' | 'dark'): string;
3702
+ declare function applyShellTheme(iframe: HTMLIFrameElement, shellMode: 'light' | 'dark'): void;
3703
+ /** Browser chrome mock with skeleton content cards */
3704
+ declare const MOCK_BROWSER_CONTENT = "\n <div class=\"preview-iframe-mock\" aria-hidden=\"true\">\n <div class=\"preview-iframe-chrome\">\n <span class=\"preview-iframe-dot\"></span>\n <span class=\"preview-iframe-dot\"></span>\n <span class=\"preview-iframe-dot\"></span>\n </div>\n <div class=\"preview-iframe-copy\">\n <div class=\"preview-iframe-line hero\"></div>\n <div class=\"preview-iframe-line body\"></div>\n <div class=\"preview-iframe-line body\"></div>\n <div class=\"preview-iframe-grid\">\n <div class=\"preview-iframe-card\"></div>\n <div class=\"preview-iframe-card\"></div>\n <div class=\"preview-iframe-card\"></div>\n </div>\n <div class=\"preview-iframe-line body\"></div>\n <div class=\"preview-iframe-line body\"></div>\n </div>\n </div>";
3705
+ /** Docked workspace skeleton (cards + rows inside content area) */
3706
+ declare const MOCK_WORKSPACE_CONTENT = "\n <div class=\"preview-workspace-content-shell\" aria-hidden=\"true\">\n <div class=\"preview-iframe-line hero\"></div>\n <div class=\"preview-iframe-line body\"></div>\n <div class=\"preview-workspace-row\">\n <div class=\"preview-workspace-card\"></div>\n <div class=\"preview-workspace-card\"></div>\n </div>\n <div class=\"preview-workspace-row\">\n <div class=\"preview-workspace-card short\"></div>\n <div class=\"preview-workspace-card short\"></div>\n <div class=\"preview-workspace-card short\"></div>\n </div>\n </div>";
3707
+ /**
3708
+ * Build a basic iframe srcdoc with mock page chrome and widget mount point.
3709
+ * For advanced use cases (background URLs, embed detection), build custom srcdoc
3710
+ * using the exported templates and shell CSS utilities.
3711
+ */
3712
+ declare function buildSrcdoc(mountId: string, shellMode: 'light' | 'dark', docked: boolean, widgetCssPath: string): string;
3713
+ type PreviewScene = 'home' | 'conversation' | 'minimized' | 'artifact';
3714
+ declare function createPreviewMessages(scene: PreviewScene): AgentWidgetMessage[];
3715
+ declare function applySceneConfig(base: AgentWidgetConfig, scene: PreviewScene): AgentWidgetConfig;
3716
+
3717
+ interface PreviewConfigOptions {
3718
+ config?: Partial<AgentWidgetConfig>;
3719
+ theme?: DeepPartial<PersonaTheme>;
3720
+ darkTheme?: DeepPartial<PersonaTheme>;
3721
+ scene?: PreviewScene;
3722
+ }
3723
+ declare function buildPreviewConfig(options: PreviewConfigOptions, shellModeOverride?: 'light' | 'dark'): AgentWidgetConfig;
3724
+
3725
+ /**
3726
+ * Imperative preview renderer for the theme editor.
3727
+ * Manages iframe-based widget previews with device frames, zoom, scenes, and compare mode.
3728
+ * No external DOM dependencies — only needs a container element to mount into.
3729
+ *
3730
+ * For advanced preview needs (background URLs, inline editing, contrast checking),
3731
+ * use the lifecycle hooks in `ThemePreviewOptions` and import shared building blocks
3732
+ * from `./preview-utils` directly.
3733
+ */
3734
+
3735
+ type PreviewDevice = 'desktop' | 'mobile';
3736
+
3737
+ type PreviewShellMode = 'light' | 'dark';
3738
+ type CompareMode = 'off' | 'baseline' | 'themes';
3739
+ /** Context passed to lifecycle hooks after mounting or updating */
3740
+ interface PreviewLifecycleContext {
3741
+ iframes: HTMLIFrameElement[];
3742
+ controllers: AgentWidgetController[];
3743
+ }
3744
+ interface ThemePreviewOptions {
3745
+ /** Device frame dimensions */
3746
+ device?: PreviewDevice;
3747
+ /** Widget state */
3748
+ scene?: PreviewScene;
3749
+ /** Browser chrome appearance */
3750
+ shellMode?: PreviewShellMode;
3751
+ /** Side-by-side comparison */
3752
+ compareMode?: CompareMode;
3753
+ /** Widget config */
3754
+ config?: Partial<AgentWidgetConfig>;
3755
+ /** Light mode theme */
3756
+ theme?: DeepPartial<PersonaTheme>;
3757
+ /** Dark mode theme */
3758
+ darkTheme?: DeepPartial<PersonaTheme>;
3759
+ /** Zoom level (0.15–1.5), or undefined for auto-fit */
3760
+ zoom?: number;
3761
+ /** Path to widget.css (defaults to looking for /widget-dist/widget.css) */
3762
+ widgetCssPath?: string;
3763
+ /** Config for the baseline side of a baseline comparison */
3764
+ baselineConfig?: Partial<AgentWidgetConfig>;
3765
+ /** Theme for the baseline side of a baseline comparison */
3766
+ baselineTheme?: DeepPartial<PersonaTheme>;
3767
+ /** Dark theme for the baseline side of a baseline comparison */
3768
+ baselineDarkTheme?: DeepPartial<PersonaTheme>;
3769
+ /** Called after all iframes load and widgets mount */
3770
+ onAfterMount?: (ctx: PreviewLifecycleContext) => void;
3771
+ /** Called after fast-path controller updates */
3772
+ onAfterUpdate?: (ctx: PreviewLifecycleContext) => void;
3773
+ /** Called before controllers are destroyed */
3774
+ onBeforeDestroy?: () => void;
3775
+ /** Called whenever the preview scale changes */
3776
+ onScaleChange?: (scale: number) => void;
3777
+ /** Override iframe srcdoc generation (for background URLs, etc.) */
3778
+ buildSrcdoc?: (mountId: string, shellMode: PreviewShellMode, docked: boolean, cssPath: string) => string;
3779
+ /** Override container HTML injection (for Idiomorph, etc.) */
3780
+ morphContainer?: (container: HTMLElement, html: string) => void;
3781
+ }
3782
+ interface ThemePreviewHandle {
3783
+ /** Update the preview (fast path when possible, full remount when needed) */
3784
+ update(options: Partial<ThemePreviewOptions>): void;
3785
+ /** Destroy preview and clean up */
3786
+ destroy(): void;
3787
+ /** Get live widget controllers */
3788
+ getControllers(): AgentWidgetController[];
3789
+ /** Recalculate auto-fit zoom */
3790
+ fitToContainer(): void;
3791
+ /** Get all preview iframes */
3792
+ getIframes(): HTMLIFrameElement[];
3793
+ /** Get current computed scale */
3794
+ getScale(): number;
3795
+ /** Set explicit zoom (or undefined to auto-fit) */
3796
+ setZoom(zoom: number | undefined): void;
3797
+ }
3798
+ declare function createThemePreview(container: HTMLElement, initialOptions: ThemePreviewOptions): ThemePreviewHandle;
3799
+
3800
+ /**
3801
+ * Interface Role → Token mapping layer.
3802
+ *
3803
+ * Maps high-level editor choices (family + intensity) to concrete palette
3804
+ * token references across multiple component/semantic paths. This is the
3805
+ * core of the "Interface Roles" editor section — one picker writes to
3806
+ * many tokens atomically.
3807
+ *
3808
+ * All functions are pure and headless (no DOM).
3809
+ */
3810
+
3811
+ declare const ROLE_INTENSITIES: RoleIntensity[];
3812
+ declare const ROLE_FAMILIES: readonly ["primary", "secondary", "accent", "gray"];
3813
+ type RoleFamily = (typeof ROLE_FAMILIES)[number];
3814
+ /** Display labels for palette families in the editor */
3815
+ declare const ROLE_FAMILY_LABELS: Record<RoleFamily, string>;
3816
+ declare const ROLE_SURFACES: RoleAssignmentOptions;
3817
+ declare const ROLE_HEADER: RoleAssignmentOptions;
3818
+ declare const ROLE_USER_MESSAGES: RoleAssignmentOptions;
3819
+ declare const ROLE_ASSISTANT_MESSAGES: RoleAssignmentOptions;
3820
+ declare const ROLE_PRIMARY_ACTIONS: RoleAssignmentOptions;
3821
+ declare const ROLE_SCROLL_TO_BOTTOM: RoleAssignmentOptions;
3822
+ declare const ROLE_INPUT: RoleAssignmentOptions;
3823
+ declare const ROLE_LINKS_FOCUS: RoleAssignmentOptions;
3824
+ declare const ROLE_BORDERS: RoleAssignmentOptions;
3825
+ /** All interface role definitions in display order */
3826
+ declare const ALL_ROLES: RoleAssignmentOptions[];
3827
+ /**
3828
+ * Resolve a role assignment (family + intensity) into concrete token writes.
3829
+ *
3830
+ * Returns a map of `{ "theme.{path}": "palette.colors.{family}.{shade}" }`.
3831
+ * The `theme.` prefix is added so callers can pass the result directly to
3832
+ * `state.setBatch()`.
3833
+ */
3834
+ declare function resolveRoleAssignment(family: string, intensity: string, role: RoleAssignmentOptions): Record<string, string>;
3835
+ /** Result of detecting a role assignment from current state */
3836
+ interface DetectedRoleAssignment {
3837
+ family: RoleFamily;
3838
+ intensity: string;
3839
+ }
3840
+ /**
3841
+ * Detect the current role assignment by reading token values and matching
3842
+ * against known palette reference patterns.
3843
+ *
3844
+ * @param getValue - Function to read a theme token value (e.g., `(p) => state.get('theme.' + p)`)
3845
+ * @param role - The role definition to detect against
3846
+ * @returns Detected assignment or null if tokens don't match a known pattern
3847
+ */
3848
+ declare function detectRoleAssignment(getValue: (path: string) => unknown, role: RoleAssignmentOptions): DetectedRoleAssignment | null;
3849
+
3850
+ /** Color parsing, normalization, and scale generation utilities (pure functions — no DOM) */
3851
+
3852
+ interface ParsedCssValue {
3853
+ value: number;
3854
+ unit: 'px' | 'rem';
3855
+ }
3856
+ declare function parseCssValue(cssValue: string): ParsedCssValue;
3857
+ declare function formatCssValue(value: number, unit: 'px' | 'rem'): string;
3858
+ declare function convertToPx(value: number, unit: 'px' | 'rem'): number;
3859
+ declare function convertFromPx(pxValue: number, unit: 'px' | 'rem'): number;
3860
+ declare function normalizeColorValue(value: string): string;
3861
+ declare function isValidHex(value: string): boolean;
3862
+ /**
3863
+ * Compute WCAG 2.x contrast ratio between two hex colors.
3864
+ * Returns a number ≥ 1 (e.g. 4.5 for AA normal text).
3865
+ */
3866
+ declare function wcagContrastRatio(hex1: string, hex2: string): number;
3867
+ declare function hexToHsl(hex: string): {
3868
+ h: number;
3869
+ s: number;
3870
+ l: number;
3871
+ };
3872
+ declare function hslToHex(h: number, s: number, l: number): string;
3873
+ /**
3874
+ * Generate a full color scale (50-950) from a base color (shade 500).
3875
+ * Uses HSL-based lightness interpolation for natural-looking scales.
3876
+ */
3877
+ declare function generateColorScale(baseHex: string): ColorShade;
3878
+ declare const SHADE_KEYS: readonly ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
3879
+ declare const COLOR_FAMILIES: readonly ["primary", "secondary", "accent", "gray", "success", "warning", "error", "info"];
3880
+ declare function paletteColorPath(family: string, shade: string): string;
3881
+ /**
3882
+ * Resolve a theme dot-path to a concrete CSS color string.
3883
+ * Follows token references recursively (palette.*, semantic.*, components.*).
3884
+ */
3885
+ declare function resolveThemeColorPath(get: (path: string) => unknown, path: string, depth?: number): string;
3886
+ declare function tokenRefDisplayName(path: string): string;
3887
+
3888
+ export { ADVANCED_TOKENS_SECTION, ALL_ROLES, ALL_TABS, BRAND_PALETTE_SECTION, BUILT_IN_PRESETS, COLORS_SECTIONS, COLOR_FAMILIES, COMPONENTS_SECTIONS, COMPONENT_COLOR_SECTIONS, COMPONENT_SHAPE_SECTIONS, CONFIGURE_SECTIONS, CONFIGURE_SUB_GROUPS, type ColorScaleOptions, type CompareMode, type ConfigChangeListener, type ConfiguratorSnapshot, DEVICE_DIMENSIONS, type DetectedRoleAssignment, type FieldDef, type FieldType, HOME_SUGGESTION_CHIPS, INTERFACE_ROLES_SECTION, MOCK_BROWSER_CONTENT, MOCK_WORKSPACE_CONTENT, type OnChangeCallback, PALETTE_SECTION, PREVIEW_STORAGE_ADAPTER, type PreviewConfigOptions, type PreviewDevice, type PreviewLifecycleContext, type PreviewScene, type PreviewShellMode, type PreviewShellPalette, ROLE_ASSISTANT_MESSAGES, ROLE_BORDERS, ROLE_FAMILIES, ROLE_FAMILY_LABELS, ROLE_HEADER, ROLE_INPUT, ROLE_INTENSITIES, ROLE_LINKS_FOCUS, ROLE_PRIMARY_ACTIONS, ROLE_SCROLL_TO_BOTTOM, ROLE_SURFACES, ROLE_USER_MESSAGES, type RoleAssignmentOptions, type RoleFamily, type RoleIntensity, type RoleTarget, type RoleTargetKind, SEMANTIC_COLORS_SECTION, SHADE_KEYS, SHELL_STYLE_ID, STATUS_COLORS_SECTION, STATUS_PALETTE_SECTION, STYLE_SECTIONS, STYLE_SECTIONS_V2, type SectionDef, type SectionPreset, type SelectOption, type SliderOptions, type SubGroupDef, THEME_EDITOR_PRESETS, THEME_SECTION, type TabDef, type ThemeEditorPreset, ThemeEditorState, type ThemePreviewHandle, type ThemePreviewOptions, type TokenRefOptions, ZOOM_MAX, ZOOM_MIN, applySceneConfig, applyShellTheme, buildPreviewConfig, buildShellCss, buildSrcdoc, convertFromPx, convertToPx, createPreviewMessages, createThemePreview, detectRoleAssignment, escapeHtml, findSection, formatCssValue, generateColorScale, getShellPalette, getThemeEditorPreset, hexToHsl, hslToHex, isValidHex, normalizeColorValue, paletteColorPath, parseCssValue, resolveRoleAssignment, resolveThemeColorPath, scopeSection, tokenRefDisplayName, wcagContrastRatio };