vanilla-agent 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -93,6 +93,66 @@ export type AgentWidgetActionEventPayload = {
93
93
  message: AgentWidgetMessage;
94
94
  };
95
95
 
96
+ /**
97
+ * Feedback event payload for upvote/downvote actions on messages
98
+ */
99
+ export type AgentWidgetMessageFeedback = {
100
+ type: "upvote" | "downvote";
101
+ messageId: string;
102
+ message: AgentWidgetMessage;
103
+ };
104
+
105
+ /**
106
+ * Configuration for message action buttons (copy, upvote, downvote)
107
+ */
108
+ export type AgentWidgetMessageActionsConfig = {
109
+ /**
110
+ * Enable/disable message actions entirely
111
+ * @default true
112
+ */
113
+ enabled?: boolean;
114
+ /**
115
+ * Show copy button
116
+ * @default true
117
+ */
118
+ showCopy?: boolean;
119
+ /**
120
+ * Show upvote button
121
+ * @default false (requires backend)
122
+ */
123
+ showUpvote?: boolean;
124
+ /**
125
+ * Show downvote button
126
+ * @default false (requires backend)
127
+ */
128
+ showDownvote?: boolean;
129
+ /**
130
+ * Visibility mode: 'always' shows buttons always, 'hover' shows on hover only
131
+ * @default 'hover'
132
+ */
133
+ visibility?: "always" | "hover";
134
+ /**
135
+ * Horizontal alignment of action buttons
136
+ * @default 'right'
137
+ */
138
+ align?: "left" | "center" | "right";
139
+ /**
140
+ * Layout style for action buttons
141
+ * - 'pill-inside': Compact floating pill around just the buttons (default for hover)
142
+ * - 'row-inside': Full-width row at the bottom of the message
143
+ * @default 'pill-inside'
144
+ */
145
+ layout?: "pill-inside" | "row-inside";
146
+ /**
147
+ * Callback when user submits feedback (upvote/downvote)
148
+ */
149
+ onFeedback?: (feedback: AgentWidgetMessageFeedback) => void;
150
+ /**
151
+ * Callback when user copies a message
152
+ */
153
+ onCopy?: (message: AgentWidgetMessage) => void;
154
+ };
155
+
96
156
  export type AgentWidgetStateEvent = {
97
157
  open: boolean;
98
158
  source: "user" | "auto" | "api" | "system";
@@ -114,6 +174,8 @@ export type AgentWidgetControllerEventMap = {
114
174
  "widget:opened": AgentWidgetStateEvent;
115
175
  "widget:closed": AgentWidgetStateEvent;
116
176
  "widget:state": AgentWidgetStateSnapshot;
177
+ "message:feedback": AgentWidgetMessageFeedback;
178
+ "message:copy": AgentWidgetMessage;
117
179
  };
118
180
 
119
181
  export type AgentWidgetFeatureFlags = {
@@ -250,6 +312,18 @@ export type AgentWidgetLauncherConfig = {
250
312
  closeButtonTooltipText?: string;
251
313
  closeButtonShowTooltip?: boolean;
252
314
  clearChat?: AgentWidgetClearChatConfig;
315
+ /**
316
+ * Border style for the launcher button.
317
+ * @example "1px solid #e5e7eb" | "2px solid #3b82f6" | "none"
318
+ * @default "1px solid #e5e7eb"
319
+ */
320
+ border?: string;
321
+ /**
322
+ * Box shadow for the launcher button.
323
+ * @example "0 10px 15px -3px rgba(0,0,0,0.1)" | "none"
324
+ * @default "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)"
325
+ */
326
+ shadow?: string;
253
327
  };
254
328
 
255
329
  export type AgentWidgetSendButtonConfig = {
@@ -442,6 +516,62 @@ export type AgentWidgetCustomFetch = (
442
516
  */
443
517
  export type AgentWidgetHeadersFunction = () => Record<string, string> | Promise<Record<string, string>>;
444
518
 
519
+ // ============================================================================
520
+ // Client Token Types
521
+ // ============================================================================
522
+
523
+ /**
524
+ * Session information returned after client token initialization.
525
+ * Contains session ID, expiry time, flow info, and config from the server.
526
+ */
527
+ export type ClientSession = {
528
+ /** Unique session identifier */
529
+ sessionId: string;
530
+ /** When the session expires */
531
+ expiresAt: Date;
532
+ /** Flow information */
533
+ flow: {
534
+ id: string;
535
+ name: string;
536
+ description: string | null;
537
+ };
538
+ /** Configuration from the server */
539
+ config: {
540
+ welcomeMessage: string | null;
541
+ placeholder: string;
542
+ theme: Record<string, unknown> | null;
543
+ };
544
+ };
545
+
546
+ /**
547
+ * Raw API response from /v1/client/init endpoint
548
+ */
549
+ export type ClientInitResponse = {
550
+ session_id: string;
551
+ expires_at: string;
552
+ flow: {
553
+ id: string;
554
+ name: string;
555
+ description: string | null;
556
+ };
557
+ config: {
558
+ welcome_message: string | null;
559
+ placeholder: string;
560
+ theme: Record<string, unknown> | null;
561
+ };
562
+ };
563
+
564
+ /**
565
+ * Request payload for /v1/client/chat endpoint
566
+ */
567
+ export type ClientChatRequest = {
568
+ session_id: string;
569
+ messages: Array<{
570
+ role: 'user' | 'assistant' | 'system';
571
+ content: string;
572
+ }>;
573
+ };
574
+
445
575
  // ============================================================================
446
576
  // Layout Configuration Types
447
577
  // ============================================================================
@@ -610,9 +740,315 @@ export type AgentWidgetLayoutConfig = {
610
740
  slots?: Partial<Record<WidgetLayoutSlot, SlotRenderer>>;
611
741
  };
612
742
 
743
+ // ============================================================================
744
+ // Markdown Configuration Types
745
+ // ============================================================================
746
+
747
+ /**
748
+ * Token types for marked renderer methods
749
+ */
750
+ export type AgentWidgetMarkdownHeadingToken = {
751
+ type: "heading";
752
+ raw: string;
753
+ depth: 1 | 2 | 3 | 4 | 5 | 6;
754
+ text: string;
755
+ tokens: unknown[];
756
+ };
757
+
758
+ export type AgentWidgetMarkdownCodeToken = {
759
+ type: "code";
760
+ raw: string;
761
+ text: string;
762
+ lang?: string;
763
+ escaped?: boolean;
764
+ };
765
+
766
+ export type AgentWidgetMarkdownBlockquoteToken = {
767
+ type: "blockquote";
768
+ raw: string;
769
+ text: string;
770
+ tokens: unknown[];
771
+ };
772
+
773
+ export type AgentWidgetMarkdownTableToken = {
774
+ type: "table";
775
+ raw: string;
776
+ header: Array<{ text: string; tokens: unknown[] }>;
777
+ rows: Array<Array<{ text: string; tokens: unknown[] }>>;
778
+ align: Array<"left" | "center" | "right" | null>;
779
+ };
780
+
781
+ export type AgentWidgetMarkdownLinkToken = {
782
+ type: "link";
783
+ raw: string;
784
+ href: string;
785
+ title: string | null;
786
+ text: string;
787
+ tokens: unknown[];
788
+ };
789
+
790
+ export type AgentWidgetMarkdownImageToken = {
791
+ type: "image";
792
+ raw: string;
793
+ href: string;
794
+ title: string | null;
795
+ text: string;
796
+ };
797
+
798
+ export type AgentWidgetMarkdownListToken = {
799
+ type: "list";
800
+ raw: string;
801
+ ordered: boolean;
802
+ start: number | "";
803
+ loose: boolean;
804
+ items: unknown[];
805
+ };
806
+
807
+ export type AgentWidgetMarkdownListItemToken = {
808
+ type: "list_item";
809
+ raw: string;
810
+ task: boolean;
811
+ checked?: boolean;
812
+ loose: boolean;
813
+ text: string;
814
+ tokens: unknown[];
815
+ };
816
+
817
+ export type AgentWidgetMarkdownParagraphToken = {
818
+ type: "paragraph";
819
+ raw: string;
820
+ text: string;
821
+ tokens: unknown[];
822
+ };
823
+
824
+ export type AgentWidgetMarkdownCodespanToken = {
825
+ type: "codespan";
826
+ raw: string;
827
+ text: string;
828
+ };
829
+
830
+ export type AgentWidgetMarkdownStrongToken = {
831
+ type: "strong";
832
+ raw: string;
833
+ text: string;
834
+ tokens: unknown[];
835
+ };
836
+
837
+ export type AgentWidgetMarkdownEmToken = {
838
+ type: "em";
839
+ raw: string;
840
+ text: string;
841
+ tokens: unknown[];
842
+ };
843
+
844
+ /**
845
+ * Custom renderer overrides for markdown elements.
846
+ * Each method receives the token and should return an HTML string.
847
+ * Return `false` to use the default renderer.
848
+ *
849
+ * @example
850
+ * ```typescript
851
+ * renderer: {
852
+ * heading(token) {
853
+ * return `<h${token.depth} class="custom-heading">${token.text}</h${token.depth}>`;
854
+ * },
855
+ * link(token) {
856
+ * return `<a href="${token.href}" target="_blank" rel="noopener">${token.text}</a>`;
857
+ * }
858
+ * }
859
+ * ```
860
+ */
861
+ export type AgentWidgetMarkdownRendererOverrides = {
862
+ /** Override heading rendering (h1-h6) */
863
+ heading?: (token: AgentWidgetMarkdownHeadingToken) => string | false;
864
+ /** Override code block rendering */
865
+ code?: (token: AgentWidgetMarkdownCodeToken) => string | false;
866
+ /** Override blockquote rendering */
867
+ blockquote?: (token: AgentWidgetMarkdownBlockquoteToken) => string | false;
868
+ /** Override table rendering */
869
+ table?: (token: AgentWidgetMarkdownTableToken) => string | false;
870
+ /** Override link rendering */
871
+ link?: (token: AgentWidgetMarkdownLinkToken) => string | false;
872
+ /** Override image rendering */
873
+ image?: (token: AgentWidgetMarkdownImageToken) => string | false;
874
+ /** Override list rendering (ul/ol) */
875
+ list?: (token: AgentWidgetMarkdownListToken) => string | false;
876
+ /** Override list item rendering */
877
+ listitem?: (token: AgentWidgetMarkdownListItemToken) => string | false;
878
+ /** Override paragraph rendering */
879
+ paragraph?: (token: AgentWidgetMarkdownParagraphToken) => string | false;
880
+ /** Override inline code rendering */
881
+ codespan?: (token: AgentWidgetMarkdownCodespanToken) => string | false;
882
+ /** Override strong/bold rendering */
883
+ strong?: (token: AgentWidgetMarkdownStrongToken) => string | false;
884
+ /** Override emphasis/italic rendering */
885
+ em?: (token: AgentWidgetMarkdownEmToken) => string | false;
886
+ /** Override horizontal rule rendering */
887
+ hr?: () => string | false;
888
+ /** Override line break rendering */
889
+ br?: () => string | false;
890
+ /** Override deleted/strikethrough rendering */
891
+ del?: (token: { type: "del"; raw: string; text: string; tokens: unknown[] }) => string | false;
892
+ /** Override checkbox rendering (in task lists) */
893
+ checkbox?: (token: { checked: boolean }) => string | false;
894
+ /** Override HTML passthrough */
895
+ html?: (token: { type: "html"; raw: string; text: string }) => string | false;
896
+ /** Override text rendering */
897
+ text?: (token: { type: "text"; raw: string; text: string }) => string | false;
898
+ };
899
+
900
+ /**
901
+ * Markdown parsing options (subset of marked options)
902
+ */
903
+ export type AgentWidgetMarkdownOptions = {
904
+ /**
905
+ * Enable GitHub Flavored Markdown (tables, strikethrough, autolinks).
906
+ * @default true
907
+ */
908
+ gfm?: boolean;
909
+ /**
910
+ * Convert \n in paragraphs into <br>.
911
+ * @default true
912
+ */
913
+ breaks?: boolean;
914
+ /**
915
+ * Conform to original markdown.pl as much as possible.
916
+ * @default false
917
+ */
918
+ pedantic?: boolean;
919
+ /**
920
+ * Add id attributes to headings.
921
+ * @default false
922
+ */
923
+ headerIds?: boolean;
924
+ /**
925
+ * Prefix for heading id attributes.
926
+ * @default ""
927
+ */
928
+ headerPrefix?: string;
929
+ /**
930
+ * Mangle email addresses for spam protection.
931
+ * @default true
932
+ */
933
+ mangle?: boolean;
934
+ /**
935
+ * Silent mode - don't throw on parse errors.
936
+ * @default false
937
+ */
938
+ silent?: boolean;
939
+ };
940
+
941
+ /**
942
+ * Markdown configuration for customizing how markdown is rendered in chat messages.
943
+ * Provides three levels of control:
944
+ *
945
+ * 1. **CSS Variables** - Override styles via `--cw-md-*` CSS custom properties
946
+ * 2. **Parsing Options** - Configure marked behavior via `options`
947
+ * 3. **Custom Renderers** - Full control via `renderer` overrides
948
+ *
949
+ * @example
950
+ * ```typescript
951
+ * // Level 2: Configure parsing options
952
+ * config: {
953
+ * markdown: {
954
+ * options: {
955
+ * gfm: true,
956
+ * breaks: true,
957
+ * headerIds: true
958
+ * }
959
+ * }
960
+ * }
961
+ * ```
962
+ *
963
+ * @example
964
+ * ```typescript
965
+ * // Level 3: Custom renderers
966
+ * config: {
967
+ * markdown: {
968
+ * renderer: {
969
+ * heading(token) {
970
+ * return `<h${token.depth} class="custom-h${token.depth}">${token.text}</h${token.depth}>`;
971
+ * },
972
+ * link(token) {
973
+ * return `<a href="${token.href}" target="_blank">${token.text}</a>`;
974
+ * },
975
+ * table(token) {
976
+ * // Wrap tables in a scrollable container
977
+ * return `<div class="table-scroll">${this.parser.parse(token.tokens)}</div>`;
978
+ * }
979
+ * }
980
+ * }
981
+ * }
982
+ * ```
983
+ */
984
+ export type AgentWidgetMarkdownConfig = {
985
+ /**
986
+ * Markdown parsing options.
987
+ * These are passed directly to the marked parser.
988
+ */
989
+ options?: AgentWidgetMarkdownOptions;
990
+
991
+ /**
992
+ * Custom renderer overrides for specific markdown elements.
993
+ * Each method receives a token object and should return an HTML string.
994
+ * Return `false` to fall back to the default renderer.
995
+ */
996
+ renderer?: AgentWidgetMarkdownRendererOverrides;
997
+
998
+ /**
999
+ * Disable default markdown CSS styles.
1000
+ * When true, the widget won't apply any default styles to markdown elements,
1001
+ * allowing you to provide your own CSS.
1002
+ *
1003
+ * @default false
1004
+ */
1005
+ disableDefaultStyles?: boolean;
1006
+ };
1007
+
613
1008
  export type AgentWidgetConfig = {
614
1009
  apiUrl?: string;
615
1010
  flowId?: string;
1011
+ /**
1012
+ * Client token for direct browser-to-API communication.
1013
+ * When set, the widget uses /v1/client/* endpoints instead of /v1/dispatch.
1014
+ * Mutually exclusive with apiKey/headers authentication.
1015
+ *
1016
+ * @example
1017
+ * ```typescript
1018
+ * config: {
1019
+ * clientToken: 'ct_live_flow01k7_a8b9c0d1e2f3g4h5i6j7k8l9'
1020
+ * }
1021
+ * ```
1022
+ */
1023
+ clientToken?: string;
1024
+ /**
1025
+ * Callback when session is initialized (client token mode only).
1026
+ * Receives session info including expiry time.
1027
+ *
1028
+ * @example
1029
+ * ```typescript
1030
+ * config: {
1031
+ * onSessionInit: (session) => {
1032
+ * console.log('Session started:', session.sessionId);
1033
+ * }
1034
+ * }
1035
+ * ```
1036
+ */
1037
+ onSessionInit?: (session: ClientSession) => void;
1038
+ /**
1039
+ * Callback when session expires or errors (client token mode only).
1040
+ * Widget should prompt user to refresh.
1041
+ *
1042
+ * @example
1043
+ * ```typescript
1044
+ * config: {
1045
+ * onSessionExpired: () => {
1046
+ * alert('Your session has expired. Please refresh the page.');
1047
+ * }
1048
+ * }
1049
+ * ```
1050
+ */
1051
+ onSessionExpired?: () => void;
616
1052
  /**
617
1053
  * Static headers to include with each request.
618
1054
  * For dynamic headers (e.g., auth tokens), use `getHeaders` instead.
@@ -819,6 +1255,57 @@ export type AgentWidgetConfig = {
819
1255
  * ```
820
1256
  */
821
1257
  layout?: AgentWidgetLayoutConfig;
1258
+
1259
+ /**
1260
+ * Markdown rendering configuration.
1261
+ * Customize how markdown is parsed and rendered in chat messages.
1262
+ *
1263
+ * Override methods:
1264
+ * 1. **CSS Variables** - Override `--cw-md-*` variables in your stylesheet
1265
+ * 2. **Options** - Configure marked parser behavior
1266
+ * 3. **Renderers** - Custom rendering functions for specific elements
1267
+ * 4. **postprocessMessage** - Complete control over message transformation
1268
+ *
1269
+ * @example
1270
+ * ```typescript
1271
+ * config: {
1272
+ * markdown: {
1273
+ * options: { breaks: true, gfm: true },
1274
+ * renderer: {
1275
+ * link(token) {
1276
+ * return `<a href="${token.href}" target="_blank">${token.text}</a>`;
1277
+ * }
1278
+ * }
1279
+ * }
1280
+ * }
1281
+ * ```
1282
+ */
1283
+ markdown?: AgentWidgetMarkdownConfig;
1284
+
1285
+ /**
1286
+ * Configuration for message action buttons (copy, upvote, downvote).
1287
+ * Shows action buttons on assistant messages for user feedback.
1288
+ *
1289
+ * @example
1290
+ * ```typescript
1291
+ * config: {
1292
+ * messageActions: {
1293
+ * enabled: true,
1294
+ * showCopy: true,
1295
+ * showUpvote: true,
1296
+ * showDownvote: true,
1297
+ * visibility: 'hover',
1298
+ * onFeedback: (feedback) => {
1299
+ * console.log('Feedback:', feedback.type, feedback.messageId);
1300
+ * },
1301
+ * onCopy: (message) => {
1302
+ * console.log('Copied message:', message.id);
1303
+ * }
1304
+ * }
1305
+ * }
1306
+ * ```
1307
+ */
1308
+ messageActions?: AgentWidgetMessageActionsConfig;
822
1309
  };
823
1310
 
824
1311
  export type AgentWidgetMessageRole = "user" | "assistant" | "system";
package/src/ui.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { escapeHtml } from "./postprocessors";
1
+ import { escapeHtml, createMarkdownProcessorFromConfig } from "./postprocessors";
2
2
  import { AgentWidgetSession, AgentWidgetSessionStatus } from "./session";
3
3
  import {
4
4
  AgentWidgetConfig,
@@ -11,7 +11,8 @@ import {
11
11
  AgentWidgetStateEvent,
12
12
  AgentWidgetStateSnapshot,
13
13
  WidgetLayoutSlot,
14
- SlotRenderer
14
+ SlotRenderer,
15
+ AgentWidgetMessageFeedback
15
16
  } from "./types";
16
17
  import { applyThemeVariables } from "./utils/theme";
17
18
  import { renderLucideIcon } from "./utils/icons";
@@ -21,7 +22,7 @@ import { createLauncherButton } from "./components/launcher";
21
22
  import { createWrapper, buildPanel, buildHeader, buildComposer, attachHeaderToContainer } from "./components/panel";
22
23
  import { positionMap } from "./utils/positioning";
23
24
  import type { HeaderElements, ComposerElements } from "./components/panel";
24
- import { MessageTransform } from "./components/message-bubble";
25
+ import { MessageTransform, MessageActionCallbacks } from "./components/message-bubble";
25
26
  import { createStandardBubble, createTypingIndicator } from "./components/message-bubble";
26
27
  import { createReasoningBubble } from "./components/reasoning-bubble";
27
28
  import { createToolBubble } from "./components/tool-bubble";
@@ -96,6 +97,12 @@ const buildPostprocessor = (
96
97
  cfg: AgentWidgetConfig | undefined,
97
98
  actionManager?: ReturnType<typeof createActionManager>
98
99
  ): MessageTransform => {
100
+ // Create markdown processor from config if markdown config is provided
101
+ // This allows users to enable markdown rendering via config.markdown
102
+ const markdownProcessor = cfg?.markdown
103
+ ? createMarkdownProcessorFromConfig(cfg.markdown)
104
+ : null;
105
+
99
106
  return (context) => {
100
107
  let nextText = context.text ?? "";
101
108
  const rawPayload = context.message.rawContent ?? null;
@@ -116,6 +123,7 @@ const buildPostprocessor = (
116
123
  }
117
124
  }
118
125
 
126
+ // Priority: postprocessMessage > markdown config > escapeHtml
119
127
  if (cfg?.postprocessMessage) {
120
128
  return cfg.postprocessMessage({
121
129
  ...context,
@@ -124,6 +132,11 @@ const buildPostprocessor = (
124
132
  });
125
133
  }
126
134
 
135
+ // Use markdown processor if markdown config is provided
136
+ if (markdownProcessor) {
137
+ return markdownProcessor(nextText);
138
+ }
139
+
127
140
  return escapeHtml(nextText);
128
141
  };
129
142
  };
@@ -216,6 +229,16 @@ export const createAgentExperience = (
216
229
  let showReasoning = config.features?.showReasoning ?? true;
217
230
  let showToolCalls = config.features?.showToolCalls ?? true;
218
231
 
232
+ // Create message action callbacks that emit events
233
+ const messageActionCallbacks: MessageActionCallbacks = {
234
+ onCopy: (message: AgentWidgetMessage) => {
235
+ eventBus.emit("message:copy", message);
236
+ },
237
+ onFeedback: (feedback: AgentWidgetMessageFeedback) => {
238
+ eventBus.emit("message:feedback", feedback);
239
+ }
240
+ };
241
+
219
242
  // Get status indicator config
220
243
  const statusConfig = config.statusIndicator ?? {};
221
244
  const getStatusText = (status: AgentWidgetSessionStatus): string => {
@@ -865,7 +888,13 @@ export const createAgentExperience = (
865
888
  bubble = matchingPlugin.renderMessage({
866
889
  message,
867
890
  defaultRenderer: () => {
868
- const b = createStandardBubble(message, transform, messageLayoutConfig);
891
+ const b = createStandardBubble(
892
+ message,
893
+ transform,
894
+ messageLayoutConfig,
895
+ config.messageActions,
896
+ messageActionCallbacks
897
+ );
869
898
  if (message.role !== "user") {
870
899
  enhanceWithForms(b, message, config, session);
871
900
  }
@@ -945,7 +974,13 @@ export const createAgentExperience = (
945
974
  streaming: Boolean(message.streaming)
946
975
  });
947
976
  } else {
948
- bubble = createStandardBubble(message, transform, messageLayoutConfig);
977
+ bubble = createStandardBubble(
978
+ message,
979
+ transform,
980
+ messageLayoutConfig,
981
+ config.messageActions,
982
+ messageActionCallbacks
983
+ );
949
984
  }
950
985
  if (message.role !== "user" && bubble) {
951
986
  enhanceWithForms(bubble, message, config, session);