vanilla-agent 1.9.0 → 1.11.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 +303 -8
- package/dist/index.cjs +46 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +581 -1
- package/dist/index.d.ts +581 -1
- package/dist/index.global.js +69 -30
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +141 -12
- package/src/components/composer-builder.ts +366 -0
- package/src/components/forms.ts +1 -0
- package/src/components/header-builder.ts +454 -0
- package/src/components/header-layouts.ts +303 -0
- package/src/components/message-bubble.ts +251 -34
- package/src/components/panel.ts +46 -684
- package/src/components/registry.ts +87 -0
- package/src/defaults.ts +49 -1
- package/src/index.ts +64 -2
- package/src/plugins/registry.ts +1 -0
- package/src/plugins/types.ts +1 -0
- package/src/runtime/init.ts +26 -0
- package/src/types.ts +381 -0
- package/src/ui.ts +521 -40
- package/src/utils/component-middleware.ts +137 -0
- package/src/utils/component-parser.ts +119 -0
- package/src/utils/constants.ts +1 -0
- package/src/utils/dom.ts +1 -0
- package/src/utils/formatting.ts +33 -8
- package/src/utils/positioning.ts +1 -0
- package/src/utils/theme.ts +1 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { AgentWidgetMessage, AgentWidgetConfig } from "../types";
|
|
2
|
+
import { componentRegistry, ComponentContext } from "../components/registry";
|
|
3
|
+
import { ComponentDirective, createComponentStreamParser } from "./component-parser";
|
|
4
|
+
import { createStandardBubble, MessageTransform } from "../components/message-bubble";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for component middleware
|
|
8
|
+
*/
|
|
9
|
+
export interface ComponentMiddlewareOptions {
|
|
10
|
+
config: AgentWidgetConfig;
|
|
11
|
+
message: AgentWidgetMessage;
|
|
12
|
+
transform: MessageTransform;
|
|
13
|
+
onPropsUpdate?: (props: Record<string, unknown>) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders a component directive into an HTMLElement
|
|
18
|
+
*/
|
|
19
|
+
export function renderComponentDirective(
|
|
20
|
+
directive: ComponentDirective,
|
|
21
|
+
options: ComponentMiddlewareOptions
|
|
22
|
+
): HTMLElement | null {
|
|
23
|
+
const { config, message, onPropsUpdate } = options;
|
|
24
|
+
|
|
25
|
+
// Get component renderer from registry
|
|
26
|
+
const renderer = componentRegistry.get(directive.component);
|
|
27
|
+
if (!renderer) {
|
|
28
|
+
// Component not found, fall back to default rendering
|
|
29
|
+
console.warn(
|
|
30
|
+
`[ComponentMiddleware] Component "${directive.component}" not found in registry. Falling back to default rendering.`
|
|
31
|
+
);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create component context
|
|
36
|
+
const context: ComponentContext = {
|
|
37
|
+
message,
|
|
38
|
+
config,
|
|
39
|
+
updateProps: (newProps: Record<string, unknown>) => {
|
|
40
|
+
if (onPropsUpdate) {
|
|
41
|
+
onPropsUpdate(newProps);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Render the component
|
|
48
|
+
const element = renderer(directive.props, context);
|
|
49
|
+
return element;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(
|
|
52
|
+
`[ComponentMiddleware] Error rendering component "${directive.component}":`,
|
|
53
|
+
error
|
|
54
|
+
);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates middleware that processes component directives from streamed JSON
|
|
61
|
+
*/
|
|
62
|
+
export function createComponentMiddleware() {
|
|
63
|
+
const parser = createComponentStreamParser();
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
/**
|
|
67
|
+
* Process accumulated content and extract component directive
|
|
68
|
+
*/
|
|
69
|
+
processChunk: (accumulatedContent: string): ComponentDirective | null => {
|
|
70
|
+
return parser.processChunk(accumulatedContent);
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get the currently extracted directive
|
|
75
|
+
*/
|
|
76
|
+
getDirective: (): ComponentDirective | null => {
|
|
77
|
+
return parser.getExtractedDirective();
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Reset the parser state
|
|
82
|
+
*/
|
|
83
|
+
reset: () => {
|
|
84
|
+
parser.reset();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Checks if a message contains a component directive in its raw content
|
|
91
|
+
*/
|
|
92
|
+
export function hasComponentDirective(message: AgentWidgetMessage): boolean {
|
|
93
|
+
if (!message.rawContent) return false;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(message.rawContent);
|
|
97
|
+
return (
|
|
98
|
+
typeof parsed === "object" &&
|
|
99
|
+
parsed !== null &&
|
|
100
|
+
"component" in parsed &&
|
|
101
|
+
typeof parsed.component === "string"
|
|
102
|
+
);
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extracts component directive from a complete message
|
|
110
|
+
*/
|
|
111
|
+
export function extractComponentDirectiveFromMessage(
|
|
112
|
+
message: AgentWidgetMessage
|
|
113
|
+
): ComponentDirective | null {
|
|
114
|
+
if (!message.rawContent) return null;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(message.rawContent);
|
|
118
|
+
if (
|
|
119
|
+
typeof parsed === "object" &&
|
|
120
|
+
parsed !== null &&
|
|
121
|
+
"component" in parsed &&
|
|
122
|
+
typeof parsed.component === "string"
|
|
123
|
+
) {
|
|
124
|
+
return {
|
|
125
|
+
component: parsed.component,
|
|
126
|
+
props: (parsed.props && typeof parsed.props === "object" && parsed.props !== null
|
|
127
|
+
? parsed.props
|
|
128
|
+
: {}) as Record<string, unknown>,
|
|
129
|
+
raw: message.rawContent
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Not valid JSON or not a component directive
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { parse as parsePartialJson, STR, OBJ } from "partial-json";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a component directive extracted from JSON
|
|
5
|
+
*/
|
|
6
|
+
export interface ComponentDirective {
|
|
7
|
+
component: string;
|
|
8
|
+
props: Record<string, unknown>;
|
|
9
|
+
raw: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a parsed object is a component directive
|
|
14
|
+
*/
|
|
15
|
+
function isComponentDirective(obj: unknown): obj is { component: string; props?: unknown } {
|
|
16
|
+
if (!obj || typeof obj !== "object") return false;
|
|
17
|
+
if (!("component" in obj)) return false;
|
|
18
|
+
const component = (obj as { component: unknown }).component;
|
|
19
|
+
return typeof component === "string" && component.length > 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extracts component directive from parsed JSON object
|
|
24
|
+
*/
|
|
25
|
+
function extractComponentDirective(
|
|
26
|
+
parsed: unknown,
|
|
27
|
+
rawJson: string
|
|
28
|
+
): ComponentDirective | null {
|
|
29
|
+
if (!isComponentDirective(parsed)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const props = parsed.props && typeof parsed.props === "object" && parsed.props !== null
|
|
34
|
+
? (parsed.props as Record<string, unknown>)
|
|
35
|
+
: {};
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
component: parsed.component,
|
|
39
|
+
props,
|
|
40
|
+
raw: rawJson
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a parser that extracts component directives from JSON streams
|
|
46
|
+
* This parser looks for objects with a "component" field and extracts
|
|
47
|
+
* the component name and props incrementally as they stream in.
|
|
48
|
+
*/
|
|
49
|
+
export function createComponentStreamParser() {
|
|
50
|
+
let extractedDirective: ComponentDirective | null = null;
|
|
51
|
+
let processedLength = 0;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
/**
|
|
55
|
+
* Get the currently extracted component directive
|
|
56
|
+
*/
|
|
57
|
+
getExtractedDirective: (): ComponentDirective | null => {
|
|
58
|
+
return extractedDirective;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Process a chunk of JSON and extract component directive if present
|
|
63
|
+
*/
|
|
64
|
+
processChunk: (accumulatedContent: string): ComponentDirective | null => {
|
|
65
|
+
// Validate that the accumulated content looks like JSON
|
|
66
|
+
const trimmed = accumulatedContent.trim();
|
|
67
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Skip if no new content
|
|
72
|
+
if (accumulatedContent.length <= processedLength) {
|
|
73
|
+
return extractedDirective;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// Parse partial JSON - allow partial strings and objects during streaming
|
|
78
|
+
// STR | OBJ allows incomplete strings and objects during streaming
|
|
79
|
+
const parsed = parsePartialJson(accumulatedContent, STR | OBJ);
|
|
80
|
+
|
|
81
|
+
// Try to extract component directive
|
|
82
|
+
const directive = extractComponentDirective(parsed, accumulatedContent);
|
|
83
|
+
if (directive) {
|
|
84
|
+
extractedDirective = directive;
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// If parsing fails completely, keep the last extracted directive
|
|
88
|
+
// This can happen with very malformed JSON during streaming
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Update processed length
|
|
92
|
+
processedLength = accumulatedContent.length;
|
|
93
|
+
|
|
94
|
+
return extractedDirective;
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reset the parser state
|
|
99
|
+
*/
|
|
100
|
+
reset: () => {
|
|
101
|
+
extractedDirective = null;
|
|
102
|
+
processedLength = 0;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Type guard to check if an object is a component directive
|
|
109
|
+
*/
|
|
110
|
+
export function isComponentDirectiveType(obj: unknown): obj is ComponentDirective {
|
|
111
|
+
return (
|
|
112
|
+
typeof obj === "object" &&
|
|
113
|
+
obj !== null &&
|
|
114
|
+
"component" in obj &&
|
|
115
|
+
typeof (obj as { component: unknown }).component === "string" &&
|
|
116
|
+
"props" in obj &&
|
|
117
|
+
typeof (obj as { props: unknown }).props === "object"
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/utils/constants.ts
CHANGED
package/src/utils/dom.ts
CHANGED
package/src/utils/formatting.ts
CHANGED
|
@@ -263,8 +263,8 @@ export const createJsonStreamParser = (): AgentWidgetStreamParser => {
|
|
|
263
263
|
|
|
264
264
|
// Skip if no new content
|
|
265
265
|
if (accumulatedContent.length <= processedLength) {
|
|
266
|
-
return extractedText !== null
|
|
267
|
-
? { text: extractedText, raw: accumulatedContent }
|
|
266
|
+
return extractedText !== null || extractedText === ""
|
|
267
|
+
? { text: extractedText || "", raw: accumulatedContent }
|
|
268
268
|
: null;
|
|
269
269
|
}
|
|
270
270
|
|
|
@@ -273,9 +273,21 @@ export const createJsonStreamParser = (): AgentWidgetStreamParser => {
|
|
|
273
273
|
// STR | OBJ allows incomplete strings and objects during streaming
|
|
274
274
|
const parsed = parsePartialJson(accumulatedContent, STR | OBJ);
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
if (parsed && typeof parsed === "object") {
|
|
277
|
+
// Check for component directives - extract text if present for combined text+component
|
|
278
|
+
if (parsed.component && typeof parsed.component === "string") {
|
|
279
|
+
// For component directives, extract text if present, otherwise empty
|
|
280
|
+
extractedText = typeof parsed.text === "string" ? parsed.text : "";
|
|
281
|
+
}
|
|
282
|
+
// Check for form directives - these also don't have text fields
|
|
283
|
+
else if (parsed.type === "init" && parsed.form) {
|
|
284
|
+
// For form directives, return empty - they're handled by form postprocessor
|
|
285
|
+
extractedText = "";
|
|
286
|
+
}
|
|
287
|
+
// Extract text field if available
|
|
288
|
+
else if (typeof parsed.text === "string") {
|
|
289
|
+
extractedText = parsed.text;
|
|
290
|
+
}
|
|
279
291
|
}
|
|
280
292
|
} catch (error) {
|
|
281
293
|
// If parsing fails completely, keep the last extracted text
|
|
@@ -285,7 +297,8 @@ export const createJsonStreamParser = (): AgentWidgetStreamParser => {
|
|
|
285
297
|
// Update processed length
|
|
286
298
|
processedLength = accumulatedContent.length;
|
|
287
299
|
|
|
288
|
-
//
|
|
300
|
+
// Always return raw JSON for component/form directive detection
|
|
301
|
+
// Return empty string for text if it's a component/form directive
|
|
289
302
|
if (extractedText !== null) {
|
|
290
303
|
return {
|
|
291
304
|
text: extractedText,
|
|
@@ -318,6 +331,18 @@ export const createFlexibleJsonStreamParser = (
|
|
|
318
331
|
// Default text extractor that handles common patterns
|
|
319
332
|
const defaultExtractor = (parsed: any): string | null => {
|
|
320
333
|
if (!parsed || typeof parsed !== "object") return null;
|
|
334
|
+
|
|
335
|
+
// Check for component directives - extract text if present for combined text+component
|
|
336
|
+
if (parsed.component && typeof parsed.component === "string") {
|
|
337
|
+
// For component directives, extract text if present, otherwise empty
|
|
338
|
+
return typeof parsed.text === "string" ? parsed.text : "";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Check for form directives - these also don't have text fields
|
|
342
|
+
if (parsed.type === "init" && parsed.form) {
|
|
343
|
+
// For form directives, return empty - they're handled by form postprocessor
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
321
346
|
|
|
322
347
|
// Check for action-based text fields
|
|
323
348
|
if (parsed.action) {
|
|
@@ -373,8 +398,8 @@ export const createFlexibleJsonStreamParser = (
|
|
|
373
398
|
// Update processed length
|
|
374
399
|
processedLength = accumulatedContent.length;
|
|
375
400
|
|
|
376
|
-
// Always return the raw JSON for action parsing
|
|
377
|
-
// Text may be null
|
|
401
|
+
// Always return the raw JSON for action parsing and component detection
|
|
402
|
+
// Text may be null or empty for component/form directives, that's ok
|
|
378
403
|
return {
|
|
379
404
|
text: extractedText || "",
|
|
380
405
|
raw: accumulatedContent
|
package/src/utils/positioning.ts
CHANGED
package/src/utils/theme.ts
CHANGED