vanilla-agent 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,10 +78,15 @@ export const createStandardBubble = (
78
78
  });
79
79
  bubble.appendChild(contentDiv);
80
80
 
81
- // Add typing indicator if this is a streaming assistant message with content
82
- if (message.streaming && message.role === "assistant" && message.content && message.content.trim()) {
83
- const typingIndicator = createTypingIndicator();
84
- bubble.appendChild(typingIndicator);
81
+ // Add typing indicator if this is a streaming assistant message
82
+ // Show it when streaming starts (even if content is empty), hide it once content appears
83
+ if (message.streaming && message.role === "assistant") {
84
+ // Only show typing indicator if content is empty or just starting
85
+ // Hide it once we have substantial content
86
+ if (!message.content || !message.content.trim()) {
87
+ const typingIndicator = createTypingIndicator();
88
+ bubble.appendChild(typingIndicator);
89
+ }
85
90
  }
86
91
 
87
92
  return bubble;
@@ -1,5 +1,5 @@
1
1
  import { createElement, createFragment } from "../utils/dom";
2
- import { AgentWidgetMessage } from "../types";
2
+ import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
3
3
  import { MessageTransform } from "./message-bubble";
4
4
  import { createStandardBubble } from "./message-bubble";
5
5
  import { createReasoningBubble } from "./reasoning-bubble";
@@ -10,7 +10,8 @@ export const renderMessages = (
10
10
  messages: AgentWidgetMessage[],
11
11
  transform: MessageTransform,
12
12
  showReasoning: boolean,
13
- showToolCalls: boolean
13
+ showToolCalls: boolean,
14
+ config?: AgentWidgetConfig
14
15
  ) => {
15
16
  container.innerHTML = "";
16
17
  const fragment = createFragment();
@@ -22,7 +23,7 @@ export const renderMessages = (
22
23
  bubble = createReasoningBubble(message);
23
24
  } else if (message.variant === "tool" && message.toolCall) {
24
25
  if (!showToolCalls) return;
25
- bubble = createToolBubble(message);
26
+ bubble = createToolBubble(message, config);
26
27
  } else {
27
28
  bubble = createStandardBubble(message, transform);
28
29
  }
@@ -43,3 +44,4 @@ export const renderMessages = (
43
44
 
44
45
 
45
46
 
47
+
@@ -443,6 +443,7 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
443
443
  "div",
444
444
  "tvw-flex tvw-flex-1 tvw-min-h-0 tvw-flex-col tvw-gap-6 tvw-overflow-y-auto tvw-bg-cw-container tvw-px-6 tvw-py-6"
445
445
  );
446
+ body.id = "vanilla-agent-scroll-container";
446
447
  const introCard = createElement(
447
448
  "div",
448
449
  "tvw-rounded-2xl tvw-bg-cw-surface tvw-p-6 tvw-shadow-sm"
@@ -1,6 +1,7 @@
1
1
  import { createElement } from "../utils/dom";
2
2
  import { AgentWidgetMessage } from "../types";
3
3
  import { describeReasonStatus } from "../utils/formatting";
4
+ import { renderLucideIcon } from "../utils/icons";
4
5
 
5
6
  // Expansion state per widget instance
6
7
  const reasoningExpansionState = new Set<string>();
@@ -10,6 +11,7 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
10
11
  const bubble = createElement(
11
12
  "div",
12
13
  [
14
+ "tvw-w-full",
13
15
  "tvw-max-w-[85%]",
14
16
  "tvw-rounded-2xl",
15
17
  "tvw-bg-cw-surface",
@@ -36,7 +38,7 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
36
38
  header.setAttribute("aria-expanded", expanded ? "true" : "false");
37
39
 
38
40
  const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
39
- const title = createElement("span", "tvw-text-xs tvw-font-semibold tvw-text-cw-primary");
41
+ const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
40
42
  title.textContent = "Thinking...";
41
43
  headerContent.appendChild(title);
42
44
 
@@ -50,13 +52,20 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
50
52
  title.style.display = "";
51
53
  }
52
54
 
53
- const toggleLabel = createElement(
54
- "span",
55
- "tvw-text-xs tvw-text-cw-primary"
56
- );
57
- toggleLabel.textContent = expanded ? "Hide" : "Show";
55
+ const toggleIcon = createElement("div", "tvw-flex tvw-items-center");
56
+ const iconColor = "currentColor";
57
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
58
+ if (chevronIcon) {
59
+ toggleIcon.appendChild(chevronIcon);
60
+ } else {
61
+ // Fallback to text if icon fails
62
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
63
+ }
58
64
 
59
- header.append(headerContent, toggleLabel);
65
+ const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-ml-auto");
66
+ headerMeta.append(toggleIcon);
67
+
68
+ header.append(headerContent, headerMeta);
60
69
 
61
70
  const content = createElement(
62
71
  "div",
@@ -78,7 +87,16 @@ export const createReasoningBubble = (message: AgentWidgetMessage): HTMLElement
78
87
 
79
88
  const applyExpansionState = () => {
80
89
  header.setAttribute("aria-expanded", expanded ? "true" : "false");
81
- toggleLabel.textContent = expanded ? "Hide" : "Show";
90
+ // Update chevron icon
91
+ toggleIcon.innerHTML = "";
92
+ const iconColor = "currentColor";
93
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
94
+ if (chevronIcon) {
95
+ toggleIcon.appendChild(chevronIcon);
96
+ } else {
97
+ // Fallback to text if icon fails
98
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
99
+ }
82
100
  content.style.display = expanded ? "" : "none";
83
101
  };
84
102
 
@@ -1,15 +1,19 @@
1
1
  import { createElement } from "../utils/dom";
2
- import { AgentWidgetMessage } from "../types";
2
+ import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
3
3
  import { formatUnknownValue, describeToolTitle } from "../utils/formatting";
4
+ import { renderLucideIcon } from "../utils/icons";
4
5
 
5
6
  // Expansion state per widget instance
6
7
  const toolExpansionState = new Set<string>();
7
8
 
8
- export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
9
+ export const createToolBubble = (message: AgentWidgetMessage, config?: AgentWidgetConfig): HTMLElement => {
9
10
  const tool = message.toolCall;
11
+ const toolCallConfig = config?.toolCall ?? {};
12
+
10
13
  const bubble = createElement(
11
14
  "div",
12
15
  [
16
+ "tvw-w-full",
13
17
  "tvw-max-w-[85%]",
14
18
  "tvw-rounded-2xl",
15
19
  "tvw-bg-cw-surface",
@@ -23,6 +27,20 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
23
27
  ].join(" ")
24
28
  );
25
29
 
30
+ // Apply bubble-level styles
31
+ if (toolCallConfig.backgroundColor) {
32
+ bubble.style.backgroundColor = toolCallConfig.backgroundColor;
33
+ }
34
+ if (toolCallConfig.borderColor) {
35
+ bubble.style.borderColor = toolCallConfig.borderColor;
36
+ }
37
+ if (toolCallConfig.borderWidth) {
38
+ bubble.style.borderWidth = toolCallConfig.borderWidth;
39
+ }
40
+ if (toolCallConfig.borderRadius) {
41
+ bubble.style.borderRadius = toolCallConfig.borderRadius;
42
+ }
43
+
26
44
  if (!tool) {
27
45
  return bubble;
28
46
  }
@@ -35,25 +53,39 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
35
53
  header.type = "button";
36
54
  header.setAttribute("aria-expanded", expanded ? "true" : "false");
37
55
 
56
+ // Apply header styles
57
+ if (toolCallConfig.headerBackgroundColor) {
58
+ header.style.backgroundColor = toolCallConfig.headerBackgroundColor;
59
+ }
60
+ if (toolCallConfig.headerPaddingX) {
61
+ header.style.paddingLeft = toolCallConfig.headerPaddingX;
62
+ header.style.paddingRight = toolCallConfig.headerPaddingX;
63
+ }
64
+ if (toolCallConfig.headerPaddingY) {
65
+ header.style.paddingTop = toolCallConfig.headerPaddingY;
66
+ header.style.paddingBottom = toolCallConfig.headerPaddingY;
67
+ }
68
+
38
69
  const headerContent = createElement("div", "tvw-flex tvw-flex-col tvw-text-left");
39
70
  const title = createElement("span", "tvw-text-xs tvw-text-cw-primary");
71
+ if (toolCallConfig.headerTextColor) {
72
+ title.style.color = toolCallConfig.headerTextColor;
73
+ }
40
74
  title.textContent = describeToolTitle(tool);
41
75
  headerContent.appendChild(title);
42
76
 
43
- if (tool.name) {
44
- const name = createElement("span", "tvw-text-[11px] tvw-text-cw-muted");
45
- name.textContent = tool.name;
46
- headerContent.appendChild(name);
77
+ const toggleIcon = createElement("div", "tvw-flex tvw-items-center");
78
+ const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
79
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
80
+ if (chevronIcon) {
81
+ toggleIcon.appendChild(chevronIcon);
82
+ } else {
83
+ // Fallback to text if icon fails
84
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
47
85
  }
48
86
 
49
- const toggleLabel = createElement(
50
- "span",
51
- "tvw-text-xs tvw-text-cw-primary"
52
- );
53
- toggleLabel.textContent = expanded ? "Hide" : "Show";
54
-
55
- const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-gap-2");
56
- headerMeta.append(toggleLabel);
87
+ const headerMeta = createElement("div", "tvw-flex tvw-items-center tvw-gap-2 tvw-ml-auto");
88
+ headerMeta.append(toggleIcon);
57
89
 
58
90
  header.append(headerContent, headerMeta);
59
91
 
@@ -63,17 +95,60 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
63
95
  );
64
96
  content.style.display = expanded ? "" : "none";
65
97
 
98
+ // Apply content styles
99
+ if (toolCallConfig.contentBackgroundColor) {
100
+ content.style.backgroundColor = toolCallConfig.contentBackgroundColor;
101
+ }
102
+ if (toolCallConfig.contentTextColor) {
103
+ content.style.color = toolCallConfig.contentTextColor;
104
+ }
105
+ if (toolCallConfig.contentPaddingX) {
106
+ content.style.paddingLeft = toolCallConfig.contentPaddingX;
107
+ content.style.paddingRight = toolCallConfig.contentPaddingX;
108
+ }
109
+ if (toolCallConfig.contentPaddingY) {
110
+ content.style.paddingTop = toolCallConfig.contentPaddingY;
111
+ content.style.paddingBottom = toolCallConfig.contentPaddingY;
112
+ }
113
+
114
+ // Add tool name at the top of content
115
+ if (tool.name) {
116
+ const toolName = createElement("div", "tvw-text-xs tvw-text-cw-muted tvw-italic");
117
+ if (toolCallConfig.contentTextColor) {
118
+ toolName.style.color = toolCallConfig.contentTextColor;
119
+ } else if (toolCallConfig.headerTextColor) {
120
+ toolName.style.color = toolCallConfig.headerTextColor;
121
+ }
122
+ toolName.textContent = tool.name;
123
+ content.appendChild(toolName);
124
+ }
125
+
66
126
  if (tool.args !== undefined) {
67
127
  const argsBlock = createElement("div", "tvw-space-y-1");
68
128
  const argsLabel = createElement(
69
129
  "div",
70
- "tvw-font-xxs tvw-font-medium tvw-text-cw-muted"
130
+ "tvw-text-xs tvw-text-cw-muted"
71
131
  );
132
+ if (toolCallConfig.labelTextColor) {
133
+ argsLabel.style.color = toolCallConfig.labelTextColor;
134
+ }
72
135
  argsLabel.textContent = "Arguments";
73
136
  const argsPre = createElement(
74
137
  "pre",
75
- "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-font-xxs tvw-text-cw-primary"
138
+ "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
76
139
  );
140
+ // Ensure font size matches header text (0.75rem / 12px)
141
+ argsPre.style.fontSize = "0.75rem";
142
+ argsPre.style.lineHeight = "1rem";
143
+ if (toolCallConfig.codeBlockBackgroundColor) {
144
+ argsPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
145
+ }
146
+ if (toolCallConfig.codeBlockBorderColor) {
147
+ argsPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
148
+ }
149
+ if (toolCallConfig.codeBlockTextColor) {
150
+ argsPre.style.color = toolCallConfig.codeBlockTextColor;
151
+ }
77
152
  argsPre.textContent = formatUnknownValue(tool.args);
78
153
  argsBlock.append(argsLabel, argsPre);
79
154
  content.appendChild(argsBlock);
@@ -83,13 +158,28 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
83
158
  const logsBlock = createElement("div", "tvw-space-y-1");
84
159
  const logsLabel = createElement(
85
160
  "div",
86
- "tvw-font-xxs tvw-font-medium tvw-text-cw-muted"
161
+ "tvw-text-xs tvw-text-cw-muted"
87
162
  );
163
+ if (toolCallConfig.labelTextColor) {
164
+ logsLabel.style.color = toolCallConfig.labelTextColor;
165
+ }
88
166
  logsLabel.textContent = "Activity";
89
167
  const logsPre = createElement(
90
168
  "pre",
91
- "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-font-xxs tvw-text-cw-primary"
169
+ "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
92
170
  );
171
+ // Ensure font size matches header text (0.75rem / 12px)
172
+ logsPre.style.fontSize = "0.75rem";
173
+ logsPre.style.lineHeight = "1rem";
174
+ if (toolCallConfig.codeBlockBackgroundColor) {
175
+ logsPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
176
+ }
177
+ if (toolCallConfig.codeBlockBorderColor) {
178
+ logsPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
179
+ }
180
+ if (toolCallConfig.codeBlockTextColor) {
181
+ logsPre.style.color = toolCallConfig.codeBlockTextColor;
182
+ }
93
183
  logsPre.textContent = tool.chunks.join("\n");
94
184
  logsBlock.append(logsLabel, logsPre);
95
185
  content.appendChild(logsBlock);
@@ -99,13 +189,28 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
99
189
  const resultBlock = createElement("div", "tvw-space-y-1");
100
190
  const resultLabel = createElement(
101
191
  "div",
102
- "tvw-font-xxs tvw-text-sm tvw-text-cw-muted"
192
+ "tvw-text-xs tvw-text-cw-muted"
103
193
  );
194
+ if (toolCallConfig.labelTextColor) {
195
+ resultLabel.style.color = toolCallConfig.labelTextColor;
196
+ }
104
197
  resultLabel.textContent = "Result";
105
198
  const resultPre = createElement(
106
199
  "pre",
107
- "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-font-xxs tvw-text-cw-primary"
200
+ "tvw-max-h-48 tvw-overflow-auto tvw-whitespace-pre-wrap tvw-rounded-lg tvw-border tvw-border-gray-100 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-xs tvw-text-cw-primary"
108
201
  );
202
+ // Ensure font size matches header text (0.75rem / 12px)
203
+ resultPre.style.fontSize = "0.75rem";
204
+ resultPre.style.lineHeight = "1rem";
205
+ if (toolCallConfig.codeBlockBackgroundColor) {
206
+ resultPre.style.backgroundColor = toolCallConfig.codeBlockBackgroundColor;
207
+ }
208
+ if (toolCallConfig.codeBlockBorderColor) {
209
+ resultPre.style.borderColor = toolCallConfig.codeBlockBorderColor;
210
+ }
211
+ if (toolCallConfig.codeBlockTextColor) {
212
+ resultPre.style.color = toolCallConfig.codeBlockTextColor;
213
+ }
109
214
  resultPre.textContent = formatUnknownValue(tool.result);
110
215
  resultBlock.append(resultLabel, resultPre);
111
216
  content.appendChild(resultBlock);
@@ -114,15 +219,27 @@ export const createToolBubble = (message: AgentWidgetMessage): HTMLElement => {
114
219
  if (tool.status === "complete" && typeof tool.duration === "number") {
115
220
  const duration = createElement(
116
221
  "div",
117
- "tvw-font-xxs tvw-text-cw-muted"
222
+ "tvw-text-xs tvw-text-cw-muted"
118
223
  );
224
+ if (toolCallConfig.contentTextColor) {
225
+ duration.style.color = toolCallConfig.contentTextColor;
226
+ }
119
227
  duration.textContent = `Duration: ${tool.duration}ms`;
120
228
  content.appendChild(duration);
121
229
  }
122
230
 
123
231
  const applyToolExpansion = () => {
124
232
  header.setAttribute("aria-expanded", expanded ? "true" : "false");
125
- toggleLabel.textContent = expanded ? "Hide" : "Show";
233
+ // Update chevron icon
234
+ toggleIcon.innerHTML = "";
235
+ const iconColor = toolCallConfig.toggleTextColor || toolCallConfig.headerTextColor || "currentColor";
236
+ const chevronIcon = renderLucideIcon(expanded ? "chevron-up" : "chevron-down", 16, iconColor, 2);
237
+ if (chevronIcon) {
238
+ toggleIcon.appendChild(chevronIcon);
239
+ } else {
240
+ // Fallback to text if icon fails
241
+ toggleIcon.textContent = expanded ? "Hide" : "Show";
242
+ }
126
243
  content.style.display = expanded ? "" : "none";
127
244
  };
128
245
 
package/src/index.ts CHANGED
@@ -10,7 +10,9 @@ export type {
10
10
  AgentWidgetInitOptions,
11
11
  AgentWidgetMessage,
12
12
  AgentWidgetLauncherConfig,
13
- AgentWidgetEvent
13
+ AgentWidgetEvent,
14
+ AgentWidgetStreamParser,
15
+ AgentWidgetStreamParserResult
14
16
  } from "./types";
15
17
 
16
18
  export { initAgentWidgetFn as initAgentWidget };
@@ -28,6 +30,12 @@ export {
28
30
  escapeHtml,
29
31
  directivePostprocessor
30
32
  } from "./postprocessors";
33
+ export {
34
+ createPlainTextParser,
35
+ createJsonStreamParser,
36
+ createRegexJsonParser,
37
+ createXmlParser
38
+ } from "./utils/formatting";
31
39
  export type { AgentWidgetInitHandle };
32
40
 
33
41
  // Plugin system exports
@@ -72,3 +72,5 @@ export const pluginRegistry = new PluginRegistry();
72
72
 
73
73
 
74
74
 
75
+
76
+
@@ -90,3 +90,5 @@ export interface AgentWidgetPlugin {
90
90
 
91
91
 
92
92
 
93
+
94
+
@@ -1,5 +1,5 @@
1
1
  import { createAgentExperience, AgentWidgetController } from "../ui";
2
- import { AgentWidgetConfig, AgentWidgetInitOptions } from "../types";
2
+ import { AgentWidgetConfig, AgentWidgetInitOptions, AgentWidgetEvent } from "../types";
3
3
 
4
4
  const ensureTarget = (target: string | HTMLElement): HTMLElement => {
5
5
  if (typeof window === "undefined" || typeof document === "undefined") {
@@ -145,6 +145,9 @@ export const initAgentWidget = (
145
145
  stopVoiceRecognition() {
146
146
  return controller.stopVoiceRecognition();
147
147
  },
148
+ injectTestMessage(event: AgentWidgetEvent) {
149
+ controller.injectTestMessage(event);
150
+ },
148
151
  destroy() {
149
152
  controller.destroy();
150
153
  host.remove();
package/src/session.ts CHANGED
@@ -60,6 +60,10 @@ export class AgentWidgetSession {
60
60
  return this.streaming;
61
61
  }
62
62
 
63
+ public injectTestEvent(event: AgentWidgetEvent) {
64
+ this.handleEvent(event);
65
+ }
66
+
63
67
  public async sendMessage(rawInput: string, options?: { viaVoice?: boolean }) {
64
68
  const input = rawInput.trim();
65
69
  if (!input) return;
@@ -56,6 +56,14 @@
56
56
  justify-content: center;
57
57
  }
58
58
 
59
+ .tvw-justify-between {
60
+ justify-content: space-between;
61
+ }
62
+
63
+ .tvw-justify-end {
64
+ justify-content: flex-end;
65
+ }
66
+
59
67
  .tvw-gap-1 {
60
68
  gap: 0.25rem;
61
69
  }
@@ -160,6 +168,10 @@
160
168
  background-color: var(--cw-primary, #111827);
161
169
  }
162
170
 
171
+ .tvw-italic {
172
+ font-style: italic;
173
+ }
174
+
163
175
  .tvw-text-base {
164
176
  font-size: 1rem;
165
177
  line-height: 1.5rem;
@@ -823,3 +835,12 @@ form:focus-within textarea {
823
835
  margin: 0.5rem 0;
824
836
  border-radius: 0.375rem;
825
837
  }
838
+
839
+ /* Ensure links in user messages match the text color */
840
+ #vanilla-agent-root .tvw-text-white a,
841
+ #vanilla-agent-root .tvw-text-white a:visited,
842
+ #vanilla-agent-root .tvw-text-white a:hover,
843
+ #vanilla-agent-root .tvw-text-white a:active {
844
+ color: inherit;
845
+ text-decoration: underline;
846
+ }
package/src/types.ts CHANGED
@@ -138,6 +138,81 @@ export type AgentWidgetVoiceRecognitionConfig = {
138
138
  showRecordingIndicator?: boolean;
139
139
  };
140
140
 
141
+ export type AgentWidgetToolCallConfig = {
142
+ backgroundColor?: string;
143
+ borderColor?: string;
144
+ borderWidth?: string;
145
+ borderRadius?: string;
146
+ headerBackgroundColor?: string;
147
+ headerTextColor?: string;
148
+ headerPaddingX?: string;
149
+ headerPaddingY?: string;
150
+ contentBackgroundColor?: string;
151
+ contentTextColor?: string;
152
+ contentPaddingX?: string;
153
+ contentPaddingY?: string;
154
+ codeBlockBackgroundColor?: string;
155
+ codeBlockBorderColor?: string;
156
+ codeBlockTextColor?: string;
157
+ toggleTextColor?: string;
158
+ labelTextColor?: string;
159
+ };
160
+
161
+ /**
162
+ * Interface for pluggable stream parsers that extract text from streaming responses.
163
+ * Parsers handle incremental parsing to extract text values from structured formats (JSON, XML, etc.).
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * const jsonParser: AgentWidgetStreamParser = {
168
+ * processChunk: async (content) => {
169
+ * // Extract text from JSON - return null if not JSON or text not available yet
170
+ * if (!content.trim().startsWith('{')) return null;
171
+ * const match = content.match(/"text"\s*:\s*"([^"]*)"/);
172
+ * return match ? match[1] : null;
173
+ * },
174
+ * getExtractedText: () => extractedText
175
+ * };
176
+ * ```
177
+ */
178
+ export interface AgentWidgetStreamParserResult {
179
+ /**
180
+ * The extracted text to display (may be partial during streaming)
181
+ */
182
+ text: string | null;
183
+
184
+ /**
185
+ * Optional: The raw accumulated content (useful for middleware that needs the original format)
186
+ */
187
+ raw?: string;
188
+ }
189
+
190
+ export interface AgentWidgetStreamParser {
191
+ /**
192
+ * Process a chunk of content and return the extracted text (if available).
193
+ * This method is called for each chunk as it arrives during streaming.
194
+ * Return null if the content doesn't match this parser's format or if text is not yet available.
195
+ *
196
+ * @param accumulatedContent - The full accumulated content so far (including new chunk)
197
+ * @returns The extracted text value and optionally raw content, or null if not yet available or format doesn't match
198
+ */
199
+ processChunk(accumulatedContent: string): Promise<AgentWidgetStreamParserResult | string | null> | AgentWidgetStreamParserResult | string | null;
200
+
201
+ /**
202
+ * Get the currently extracted text value (may be partial).
203
+ * This is called synchronously to get the latest extracted text without processing.
204
+ *
205
+ * @returns The currently extracted text value, or null if not yet available
206
+ */
207
+ getExtractedText(): string | null;
208
+
209
+ /**
210
+ * Clean up any resources when parsing is complete.
211
+ */
212
+ close?(): Promise<void> | void;
213
+ }
214
+
215
+
141
216
  export type AgentWidgetConfig = {
142
217
  apiUrl?: string;
143
218
  flowId?: string;
@@ -159,12 +234,44 @@ export type AgentWidgetConfig = {
159
234
  sendButton?: AgentWidgetSendButtonConfig;
160
235
  statusIndicator?: AgentWidgetStatusIndicatorConfig;
161
236
  voiceRecognition?: AgentWidgetVoiceRecognitionConfig;
237
+ toolCall?: AgentWidgetToolCallConfig;
162
238
  postprocessMessage?: (context: {
163
239
  text: string;
164
240
  message: AgentWidgetMessage;
165
241
  streaming: boolean;
166
242
  }) => string;
167
243
  plugins?: AgentWidgetPlugin[];
244
+ /**
245
+ * Custom stream parser for extracting text from streaming structured responses.
246
+ * Handles incremental parsing of JSON, XML, or other formats.
247
+ * If not provided, uses the default JSON parser.
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * streamParser: () => ({
252
+ * processChunk: async (content) => {
253
+ * // Return null if not your format, or extracted text if available
254
+ * if (!content.trim().startsWith('{')) return null;
255
+ * return extractText(content);
256
+ * },
257
+ * getExtractedText: () => extractedText
258
+ * })
259
+ * ```
260
+ */
261
+ streamParser?: () => AgentWidgetStreamParser;
262
+ /**
263
+ * Additional localStorage key to clear when the clear chat button is clicked.
264
+ * The widget automatically clears `"vanilla-agent-chat-history"` by default.
265
+ * Use this option to clear additional keys (e.g., if you're using a custom storage key).
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * config: {
270
+ * clearChatHistoryStorageKey: "my-custom-chat-history"
271
+ * }
272
+ * ```
273
+ */
274
+ clearChatHistoryStorageKey?: string;
168
275
  };
169
276
 
170
277
  export type AgentWidgetMessageRole = "user" | "assistant" | "system";