vanilla-agent 1.2.0 → 1.4.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/README.md +323 -5
- package/dist/index.cjs +7 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +151 -2
- package/dist/index.d.ts +151 -2
- package/dist/index.global.js +51 -48
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/widget.css +21 -0
- package/package.json +4 -2
- package/src/client.test.ts +197 -0
- package/src/client.ts +330 -15
- package/src/components/forms.ts +2 -0
- package/src/components/message-bubble.ts +9 -4
- package/src/components/messages.ts +5 -3
- package/src/components/panel.ts +1 -0
- package/src/components/reasoning-bubble.ts +26 -8
- package/src/components/tool-bubble.ts +139 -22
- package/src/index.ts +9 -1
- package/src/plugins/registry.ts +2 -0
- package/src/plugins/types.ts +2 -0
- package/src/runtime/init.ts +4 -1
- package/src/session.ts +7 -2
- package/src/styles/widget.css +21 -0
- package/src/types.ts +124 -0
- package/src/ui.ts +147 -7
- package/src/utils/constants.ts +2 -0
- package/src/utils/dom.ts +2 -0
- package/src/utils/formatting.test.ts +160 -0
- package/src/utils/formatting.ts +252 -1
- package/src/utils/positioning.ts +2 -0
- package/src/utils/theme.ts +2 -0
package/README.md
CHANGED
|
@@ -49,6 +49,7 @@ createAgentExperience(inlineHost, {
|
|
|
49
49
|
// Floating launcher with runtime updates
|
|
50
50
|
const controller = initAgentWidget({
|
|
51
51
|
target: '#launcher-root',
|
|
52
|
+
windowKey: 'chatController', // Optional: stores controller on window.chatController
|
|
52
53
|
config: {
|
|
53
54
|
...DEFAULT_WIDGET_CONFIG,
|
|
54
55
|
apiUrl: proxyUrl,
|
|
@@ -69,12 +70,26 @@ controller.update({
|
|
|
69
70
|
});
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
### Initialization options
|
|
74
|
+
|
|
75
|
+
`initAgentWidget` accepts the following options:
|
|
76
|
+
|
|
77
|
+
| Option | Type | Description |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `target` | `string \| HTMLElement` | CSS selector or element where widget mounts. |
|
|
80
|
+
| `config` | `AgentWidgetConfig` | Widget configuration object (see [Configuration reference](#configuration-reference) below). |
|
|
81
|
+
| `useShadowDom` | `boolean` | Use Shadow DOM for style isolation (default: `true`). |
|
|
82
|
+
| `onReady` | `() => void` | Callback fired when widget is initialized. |
|
|
83
|
+
| `windowKey` | `string` | If provided, stores the controller on `window[windowKey]` for global access. Automatically cleaned up on `destroy()`. |
|
|
84
|
+
|
|
72
85
|
> **Security note:** When you return HTML from `postprocessMessage`, make sure you sanitise it before injecting into the page. The provided postprocessors (`markdownPostprocessor`, `directivePostprocessor`) do not perform sanitisation.
|
|
73
86
|
|
|
74
87
|
|
|
75
88
|
### Programmatic control
|
|
76
89
|
|
|
77
|
-
`initAgentWidget` (and `createAgentExperience`) return a controller with
|
|
90
|
+
`initAgentWidget` (and `createAgentExperience`) return a controller with methods to programmatically control the widget.
|
|
91
|
+
|
|
92
|
+
#### Basic controls
|
|
78
93
|
|
|
79
94
|
```ts
|
|
80
95
|
const chat = initAgentWidget({
|
|
@@ -84,8 +99,129 @@ const chat = initAgentWidget({
|
|
|
84
99
|
|
|
85
100
|
document.getElementById('open-chat')?.addEventListener('click', () => chat.open())
|
|
86
101
|
document.getElementById('toggle-chat')?.addEventListener('click', () => chat.toggle())
|
|
102
|
+
document.getElementById('close-chat')?.addEventListener('click', () => chat.close())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Message hooks
|
|
106
|
+
|
|
107
|
+
You can programmatically set messages, submit messages, and control voice recognition:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const chat = initAgentWidget({
|
|
111
|
+
target: '#launcher-root',
|
|
112
|
+
config: { /* ... */ }
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Set a message in the input field (doesn't submit)
|
|
116
|
+
chat.setMessage("Hello, I need help")
|
|
117
|
+
|
|
118
|
+
// Submit a message (uses textarea value if no argument provided)
|
|
119
|
+
chat.submitMessage()
|
|
120
|
+
// Or submit a specific message
|
|
121
|
+
chat.submitMessage("What are your hours?")
|
|
122
|
+
|
|
123
|
+
// Start voice recognition
|
|
124
|
+
chat.startVoiceRecognition()
|
|
125
|
+
|
|
126
|
+
// Stop voice recognition
|
|
127
|
+
chat.stopVoiceRecognition()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
All hook methods return `boolean` indicating success (`true`) or failure (`false`). They will automatically open the widget if it's currently closed (when launcher is enabled).
|
|
131
|
+
|
|
132
|
+
#### Clear chat
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const chat = initAgentWidget({
|
|
136
|
+
target: '#launcher-root',
|
|
137
|
+
config: { /* ... */ }
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Clear all messages programmatically
|
|
141
|
+
chat.clearChat()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Accessing from window
|
|
145
|
+
|
|
146
|
+
To access the controller globally (e.g., from browser console or external scripts), use the `windowKey` option:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const chat = initAgentWidget({
|
|
150
|
+
target: '#launcher-root',
|
|
151
|
+
windowKey: 'chatController', // Stores controller on window.chatController
|
|
152
|
+
config: { /* ... */ }
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Now accessible globally
|
|
156
|
+
window.chatController.setMessage("Hello from console!")
|
|
157
|
+
window.chatController.submitMessage("Test message")
|
|
158
|
+
window.chatController.startVoiceRecognition()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Message Types
|
|
162
|
+
|
|
163
|
+
The widget uses `AgentWidgetMessage` objects to represent messages in the conversation. You can access these through `postprocessMessage` callbacks or by inspecting the session's message array.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
type AgentWidgetMessage = {
|
|
167
|
+
id: string; // Unique message identifier
|
|
168
|
+
role: "user" | "assistant" | "system";
|
|
169
|
+
content: string; // Message text content
|
|
170
|
+
createdAt: string; // ISO timestamp
|
|
171
|
+
streaming?: boolean; // Whether message is still streaming
|
|
172
|
+
variant?: "assistant" | "reasoning" | "tool";
|
|
173
|
+
sequence?: number; // Message ordering
|
|
174
|
+
reasoning?: AgentWidgetReasoning;
|
|
175
|
+
toolCall?: AgentWidgetToolCall;
|
|
176
|
+
tools?: AgentWidgetToolCall[];
|
|
177
|
+
viaVoice?: boolean; // Indicates if user message was sent via voice input
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**`viaVoice` field**: Set to `true` when a user message is sent through voice recognition. This allows you to implement voice-specific behaviors, such as automatically reactivating voice recognition after assistant responses. You can check this field in your `postprocessMessage` callback:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
postprocessMessage: ({ message, text, streaming }) => {
|
|
185
|
+
if (message.role === 'user' && message.viaVoice) {
|
|
186
|
+
console.log('User sent message via voice');
|
|
187
|
+
}
|
|
188
|
+
return text;
|
|
189
|
+
}
|
|
87
190
|
```
|
|
88
191
|
|
|
192
|
+
Alternatively, manually assign the controller:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const chat = initAgentWidget({ /* ... */ })
|
|
196
|
+
window.chatController = chat
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Events
|
|
200
|
+
|
|
201
|
+
The widget dispatches custom events that you can listen to for integration with your application:
|
|
202
|
+
|
|
203
|
+
#### `vanilla-agent:clear-chat`
|
|
204
|
+
|
|
205
|
+
Dispatched when the user clicks the "Clear chat" button or when `chat.clearChat()` is called programmatically.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
window.addEventListener("vanilla-agent:clear-chat", (event) => {
|
|
209
|
+
console.log("Chat cleared at:", event.detail.timestamp);
|
|
210
|
+
// Clear your localStorage, reset state, etc.
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Event detail:**
|
|
215
|
+
- `timestamp`: ISO timestamp string of when the chat was cleared
|
|
216
|
+
|
|
217
|
+
**Use cases:**
|
|
218
|
+
- Clear localStorage chat history
|
|
219
|
+
- Reset application state
|
|
220
|
+
- Track analytics events
|
|
221
|
+
- Sync with backend
|
|
222
|
+
|
|
223
|
+
**Note:** The widget automatically clears the `"vanilla-agent-chat-history"` localStorage key by default when chat is cleared. If you set `clearChatHistoryStorageKey` in the config, it will also clear that additional key. You can still listen to this event for additional custom behavior.
|
|
224
|
+
|
|
89
225
|
### Travrse adapter
|
|
90
226
|
|
|
91
227
|
This package ships with a Travrse adapter by default. The proxy handles all flow configuration, keeping the client lightweight and flexible.
|
|
@@ -130,7 +266,9 @@ The easiest way is to use the automatic installer script. It handles loading CSS
|
|
|
130
266
|
theme: {
|
|
131
267
|
accent: '#2563eb',
|
|
132
268
|
surface: '#ffffff'
|
|
133
|
-
}
|
|
269
|
+
},
|
|
270
|
+
// Optional: configure stream parser for JSON/XML responses
|
|
271
|
+
// streamParser: () => window.AgentWidget.createJsonStreamParser()
|
|
134
272
|
}
|
|
135
273
|
};
|
|
136
274
|
</script>
|
|
@@ -175,8 +313,9 @@ For more control, manually load CSS and JavaScript:
|
|
|
175
313
|
|
|
176
314
|
<!-- Initialize widget -->
|
|
177
315
|
<script>
|
|
178
|
-
window.AgentWidget.initAgentWidget({
|
|
316
|
+
const chatController = window.AgentWidget.initAgentWidget({
|
|
179
317
|
target: '#vanilla-agent-anchor', // or 'body' for floating launcher
|
|
318
|
+
windowKey: 'chatWidget', // Optional: stores controller on window.chatWidget
|
|
180
319
|
config: {
|
|
181
320
|
apiUrl: '/api/chat/dispatch',
|
|
182
321
|
launcher: {
|
|
@@ -187,9 +326,14 @@ For more control, manually load CSS and JavaScript:
|
|
|
187
326
|
theme: {
|
|
188
327
|
accent: '#111827',
|
|
189
328
|
surface: '#f5f5f5'
|
|
190
|
-
}
|
|
329
|
+
},
|
|
330
|
+
// Optional: configure stream parser for JSON/XML responses
|
|
331
|
+
streamParser: window.AgentWidget.createJsonStreamParser // or createXmlParser, createPlainTextParser
|
|
191
332
|
}
|
|
192
333
|
});
|
|
334
|
+
|
|
335
|
+
// Controller is now available as window.chatWidget (if windowKey was used)
|
|
336
|
+
// or use the returned chatController variable
|
|
193
337
|
</script>
|
|
194
338
|
```
|
|
195
339
|
|
|
@@ -206,7 +350,14 @@ Replace `VERSION` with `latest` for auto-updates, or a specific version like `0.
|
|
|
206
350
|
- `index.global.js` - Widget JavaScript (IIFE format)
|
|
207
351
|
- `install.global.js` - Automatic installer script
|
|
208
352
|
|
|
209
|
-
The script build exposes a `window.AgentWidget` global with `initAgentWidget()` and other exports
|
|
353
|
+
The script build exposes a `window.AgentWidget` global with `initAgentWidget()` and other exports, including parser functions:
|
|
354
|
+
|
|
355
|
+
- `window.AgentWidget.initAgentWidget()` - Initialize the widget
|
|
356
|
+
- `window.AgentWidget.createPlainTextParser()` - Plain text parser (default)
|
|
357
|
+
- `window.AgentWidget.createJsonStreamParser()` - JSON parser using schema-stream
|
|
358
|
+
- `window.AgentWidget.createXmlParser()` - XML parser
|
|
359
|
+
- `window.AgentWidget.markdownPostprocessor()` - Markdown postprocessor
|
|
360
|
+
- `window.AgentWidget.directivePostprocessor()` - Directive postprocessor
|
|
210
361
|
|
|
211
362
|
### Using default configuration
|
|
212
363
|
|
|
@@ -254,12 +405,179 @@ This ensures all configuration values are set to sensible defaults while allowin
|
|
|
254
405
|
| `initialMessages` | `AgentWidgetMessage[]` | Seed the conversation transcript. |
|
|
255
406
|
| `suggestionChips` | `string[]` | Render quick reply buttons above the composer. |
|
|
256
407
|
| `postprocessMessage` | `(ctx) => string` | Transform message text before it renders (return HTML). Combine with `markdownPostprocessor` for rich output. |
|
|
408
|
+
| `streamParser` | `() => AgentWidgetStreamParser` | Custom stream parser for detecting formats and extracting text from streaming responses. Handles JSON, XML, or custom formats. See [Stream Parser Configuration](#stream-parser-configuration) below. |
|
|
409
|
+
| `clearChatHistoryStorageKey` | `string` | Additional localStorage key to clear when the clear chat button is clicked. The widget automatically clears `"vanilla-agent-chat-history"` by default. Use this option to clear additional keys (e.g., if you're using a custom storage key). |
|
|
257
410
|
| `formEndpoint` | `string` | Endpoint used by built-in directives (defaults to `/form`). |
|
|
258
411
|
| `launcherWidth` | `string` | CSS width applied to the floating launcher panel (e.g. `320px`, `90vw`). Defaults to `min(400px, calc(100vw - 24px))`. |
|
|
259
412
|
| `debug` | `boolean` | Emits verbose logs to `console`. |
|
|
260
413
|
|
|
261
414
|
All options are safe to mutate via `initAgentWidget(...).update(newConfig)`.
|
|
262
415
|
|
|
416
|
+
### Stream Parser Configuration
|
|
417
|
+
|
|
418
|
+
The widget can parse structured responses (JSON, XML, etc.) that stream in chunk by chunk, extracting the `text` field for display. By default, it uses a schema-stream based JSON parser. You can provide a custom parser to handle different formats, structures, or parsing strategies.
|
|
419
|
+
|
|
420
|
+
**Key benefits of the unified stream parser:**
|
|
421
|
+
- **Format detection**: Automatically detects if content matches your parser's format
|
|
422
|
+
- **Extensible**: Handle JSON, XML, or any custom structured format
|
|
423
|
+
- **Incremental parsing**: Extract text as it streams in, not just when complete
|
|
424
|
+
|
|
425
|
+
**Using built-in parsers with ESM/Modules:**
|
|
426
|
+
|
|
427
|
+
```javascript
|
|
428
|
+
import { initAgentWidget, createPlainTextParser, createJsonStreamParser, createXmlParser } from 'vanilla-agent';
|
|
429
|
+
|
|
430
|
+
const controller = initAgentWidget({
|
|
431
|
+
target: '#chat-root',
|
|
432
|
+
config: {
|
|
433
|
+
apiUrl: '/api/chat/dispatch',
|
|
434
|
+
streamParser: createJsonStreamParser // Use JSON parser
|
|
435
|
+
// Or: createXmlParser for XML, createPlainTextParser for plain text (default)
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Using built-in parsers with CDN Script Tags:**
|
|
441
|
+
|
|
442
|
+
```html
|
|
443
|
+
<script src="https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/index.global.js"></script>
|
|
444
|
+
<script>
|
|
445
|
+
window.AgentWidget.initAgentWidget({
|
|
446
|
+
target: '#chat-root',
|
|
447
|
+
config: {
|
|
448
|
+
apiUrl: '/api/chat/dispatch',
|
|
449
|
+
streamParser: window.AgentWidget.createJsonStreamParser // JSON parser
|
|
450
|
+
// Or: window.AgentWidget.createXmlParser for XML
|
|
451
|
+
// Or: window.AgentWidget.createPlainTextParser for plain text (default)
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
</script>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Using with automatic installer script:**
|
|
458
|
+
|
|
459
|
+
```html
|
|
460
|
+
<script>
|
|
461
|
+
window.siteAgentConfig = {
|
|
462
|
+
target: 'body',
|
|
463
|
+
config: {
|
|
464
|
+
apiUrl: '/api/chat/dispatch',
|
|
465
|
+
// Note: streamParser must be set after the script loads, or use a function
|
|
466
|
+
streamParser: function() {
|
|
467
|
+
return window.AgentWidget.createJsonStreamParser();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
</script>
|
|
472
|
+
<script src="https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/install.global.js"></script>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Alternatively, you can set it after the script loads:
|
|
476
|
+
|
|
477
|
+
```html
|
|
478
|
+
<script>
|
|
479
|
+
window.siteAgentConfig = {
|
|
480
|
+
target: 'body',
|
|
481
|
+
config: {
|
|
482
|
+
apiUrl: '/api/chat/dispatch'
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
</script>
|
|
486
|
+
<script src="https://cdn.jsdelivr.net/npm/vanilla-agent@latest/dist/install.global.js"></script>
|
|
487
|
+
<script>
|
|
488
|
+
// Set parser after AgentWidget is loaded
|
|
489
|
+
if (window.siteAgentConfig && window.AgentWidget) {
|
|
490
|
+
window.siteAgentConfig.config.streamParser = window.AgentWidget.createJsonStreamParser;
|
|
491
|
+
}
|
|
492
|
+
</script>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Custom JSON parser example:**
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
const jsonParser = () => {
|
|
499
|
+
let extractedText = null;
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
// Extract text field from JSON as it streams in
|
|
503
|
+
// Return null if not JSON or text not available yet
|
|
504
|
+
processChunk(accumulatedContent) {
|
|
505
|
+
const trimmed = accumulatedContent.trim();
|
|
506
|
+
// Return null if not JSON format
|
|
507
|
+
if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const match = accumulatedContent.match(/"text"\s*:\s*"([^"]*(?:\\.[^"]*)*)"/);
|
|
512
|
+
if (match) {
|
|
513
|
+
extractedText = match[1].replace(/\\"/g, '"').replace(/\\n/g, '\n');
|
|
514
|
+
return extractedText;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
getExtractedText() {
|
|
520
|
+
return extractedText;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const controller = initAgentWidget({
|
|
526
|
+
target: '#chat-root',
|
|
527
|
+
config: {
|
|
528
|
+
apiUrl: '/api/chat/dispatch',
|
|
529
|
+
streamParser: jsonParser
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Custom XML parser example:**
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
const xmlParser = () => {
|
|
538
|
+
let extractedText = null;
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
processChunk(accumulatedContent) {
|
|
542
|
+
// Return null if not XML format
|
|
543
|
+
if (!accumulatedContent.trim().startsWith('<')) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Extract text from <text>...</text> tags
|
|
548
|
+
const match = accumulatedContent.match(/<text[^>]*>([\s\S]*?)<\/text>/);
|
|
549
|
+
if (match) {
|
|
550
|
+
extractedText = match[1];
|
|
551
|
+
return extractedText;
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
getExtractedText() {
|
|
557
|
+
return extractedText;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**Parser interface:**
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
interface AgentWidgetStreamParser {
|
|
567
|
+
// Process a chunk and return extracted text (if available)
|
|
568
|
+
// Return null if the content doesn't match this parser's format or text is not yet available
|
|
569
|
+
processChunk(accumulatedContent: string): Promise<string | null> | string | null;
|
|
570
|
+
|
|
571
|
+
// Get the currently extracted text (may be partial)
|
|
572
|
+
getExtractedText(): string | null;
|
|
573
|
+
|
|
574
|
+
// Optional cleanup when parsing is complete
|
|
575
|
+
close?(): Promise<void> | void;
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
The parser's `processChunk` method is called for each chunk. If the content matches your parser's format, return the extracted text. If it doesn't match (or text isn't available yet), return `null` and the content will be treated as plain text. This allows you to display the text value as it streams in without showing raw structured characters.
|
|
580
|
+
|
|
263
581
|
### Optional proxy server
|
|
264
582
|
|
|
265
583
|
The proxy server handles flow configuration and forwards requests to Travrse. You can configure it in three ways:
|