@adonis-agora/agent 0.70.1 → 0.72.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/CHANGELOG.md +22 -0
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +3 -0
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/src/a2ui/adapter.d.ts +45 -0
- package/dist/src/a2ui/adapter.d.ts.map +1 -0
- package/dist/src/a2ui/adapter.js +291 -0
- package/dist/src/a2ui/adapter.js.map +1 -0
- package/dist/src/a2ui/core.d.ts +258 -0
- package/dist/src/a2ui/core.d.ts.map +1 -0
- package/dist/src/a2ui/core.js +635 -0
- package/dist/src/a2ui/core.js.map +1 -0
- package/dist/src/a2ui/index.d.ts +10 -0
- package/dist/src/a2ui/index.d.ts.map +1 -0
- package/dist/src/a2ui/index.js +10 -0
- package/dist/src/a2ui/index.js.map +1 -0
- package/dist/src/ag-ui/adapter.d.ts +14 -0
- package/dist/src/ag-ui/adapter.d.ts.map +1 -1
- package/dist/src/ag-ui/adapter.js +34 -2
- package/dist/src/ag-ui/adapter.js.map +1 -1
- package/dist/src/ag-ui/core/encoder.d.ts +7 -0
- package/dist/src/ag-ui/core/encoder.d.ts.map +1 -1
- package/dist/src/ag-ui/core/encoder.js +12 -1
- package/dist/src/ag-ui/core/encoder.js.map +1 -1
- package/dist/src/ag-ui/core/input.d.ts +7 -0
- package/dist/src/ag-ui/core/input.d.ts.map +1 -1
- package/dist/src/ag-ui/core/input.js +7 -0
- package/dist/src/ag-ui/core/input.js.map +1 -1
- package/dist/src/ag-ui/core/types.d.ts +8 -0
- package/dist/src/ag-ui/core/types.d.ts.map +1 -1
- package/dist/src/ag-ui/core/types.js.map +1 -1
- package/dist/src/genui/actions.d.ts +96 -0
- package/dist/src/genui/actions.d.ts.map +1 -0
- package/dist/src/genui/actions.js +192 -0
- package/dist/src/genui/actions.js.map +1 -0
- package/dist/src/genui/builtins.d.ts +1 -1
- package/dist/src/genui/builtins.d.ts.map +1 -1
- package/dist/src/genui/builtins.js +1 -0
- package/dist/src/genui/builtins.js.map +1 -1
- package/dist/src/genui/catalog.d.ts +9 -0
- package/dist/src/genui/catalog.d.ts.map +1 -1
- package/dist/src/genui/catalog.js +2 -0
- package/dist/src/genui/catalog.js.map +1 -1
- package/dist/src/genui/index.d.ts +9 -0
- package/dist/src/genui/index.d.ts.map +1 -1
- package/dist/src/genui/index.js +6 -2
- package/dist/src/genui/index.js.map +1 -1
- package/dist/src/genui/progressive.d.ts +3 -1
- package/dist/src/genui/progressive.d.ts.map +1 -1
- package/dist/src/genui/progressive.js +27 -3
- package/dist/src/genui/progressive.js.map +1 -1
- package/dist/src/genui/public.d.ts +2 -0
- package/dist/src/genui/public.d.ts.map +1 -1
- package/dist/src/genui/public.js +2 -0
- package/dist/src/genui/public.js.map +1 -1
- package/dist/src/genui/sandbox.d.ts +169 -0
- package/dist/src/genui/sandbox.d.ts.map +1 -0
- package/dist/src/genui/sandbox.js +307 -0
- package/dist/src/genui/sandbox.js.map +1 -0
- package/dist/src/genui/tools.d.ts.map +1 -1
- package/dist/src/genui/tools.js +36 -0
- package/dist/src/genui/tools.js.map +1 -1
- package/dist/src/genui/tree.d.ts +2 -2
- package/dist/src/genui/tree.d.ts.map +1 -1
- package/dist/src/genui/tree.js +24 -6
- package/dist/src/genui/tree.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/react/core/components/message-item.d.ts.map +1 -1
- package/dist/src/react/core/components/message-item.js +6 -3
- package/dist/src/react/core/components/message-item.js.map +1 -1
- package/dist/src/react/core/genui/generative-ui.d.ts +19 -2
- package/dist/src/react/core/genui/generative-ui.d.ts.map +1 -1
- package/dist/src/react/core/genui/generative-ui.js +14 -3
- package/dist/src/react/core/genui/generative-ui.js.map +1 -1
- package/dist/src/react/core/genui/index.d.ts +4 -1
- package/dist/src/react/core/genui/index.d.ts.map +1 -1
- package/dist/src/react/core/genui/index.js +4 -1
- package/dist/src/react/core/genui/index.js.map +1 -1
- package/dist/src/react/core/genui/sandbox.d.ts +91 -0
- package/dist/src/react/core/genui/sandbox.d.ts.map +1 -0
- package/dist/src/react/core/genui/sandbox.js +242 -0
- package/dist/src/react/core/genui/sandbox.js.map +1 -0
- package/dist/src/react/core/genui/types.d.ts +6 -0
- package/dist/src/react/core/genui/types.d.ts.map +1 -1
- package/dist/src/react/core/genui/ui-action-chip.d.ts +21 -0
- package/dist/src/react/core/genui/ui-action-chip.d.ts.map +1 -0
- package/dist/src/react/core/genui/ui-action-chip.js +18 -0
- package/dist/src/react/core/genui/ui-action-chip.js.map +1 -0
- package/dist/src/react/core/index.d.ts +1 -1
- package/dist/src/react/core/index.d.ts.map +1 -1
- package/dist/src/react/core/index.js.map +1 -1
- package/dist/src/react/core/transcript/index.d.ts +1 -1
- package/dist/src/react/core/transcript/index.d.ts.map +1 -1
- package/dist/src/react/core/transcript/index.js.map +1 -1
- package/dist/src/react/core/transcript/model.d.ts +9 -0
- package/dist/src/react/core/transcript/model.d.ts.map +1 -1
- package/dist/src/react/core/transcript/model.js +3 -0
- package/dist/src/react/core/transcript/model.js.map +1 -1
- package/dist/src/react/core/transcript/use-chat-transcript.d.ts +6 -1
- package/dist/src/react/core/transcript/use-chat-transcript.d.ts.map +1 -1
- package/dist/src/react/core/transcript/use-chat-transcript.js +8 -1
- package/dist/src/react/core/transcript/use-chat-transcript.js.map +1 -1
- package/dist/src/react/core/use-agent-chat.d.ts +3 -0
- package/dist/src/react/core/use-agent-chat.d.ts.map +1 -1
- package/dist/src/react/core/use-agent-chat.js +8 -0
- package/dist/src/react/core/use-agent-chat.js.map +1 -1
- package/dist/src/spi/protocol-adapter.d.ts +3 -0
- package/dist/src/spi/protocol-adapter.d.ts.map +1 -1
- package/package.json +30 -22
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import { readUiAction, readUiActionText, } from '../genui/actions.js';
|
|
2
|
+
import { toJsonSchema } from '../genui/schema.js';
|
|
3
|
+
import { componentToText } from '../genui/text.js';
|
|
4
|
+
import { GENUI_TREE_COMPONENT } from '../genui/tree.js';
|
|
5
|
+
/** The A2UI protocol version every message is stamped with. */
|
|
6
|
+
export const A2UI_VERSION = 'v0.9';
|
|
7
|
+
/** The catalog id of A2UI v0.9's basic catalog (what `@a2ui/react`'s `basicCatalog.id` is). */
|
|
8
|
+
export const A2UI_BASIC_CATALOG_ID = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json';
|
|
9
|
+
/**
|
|
10
|
+
* The id the AG-UI A2UI binding knows the basic catalog by: `@ag-ui/a2ui-middleware`,
|
|
11
|
+
* `@ag-ui/a2ui-toolkit` and CopilotKit's A2UI renderer (1.77, on `@a2ui/web_core` 0.10) register it
|
|
12
|
+
* under this earlier v0.9 id, not {@link A2UI_BASIC_CATALOG_ID}. The AG-UI transport's default.
|
|
13
|
+
*/
|
|
14
|
+
export const A2UI_LEGACY_BASIC_CATALOG_ID = 'https://a2ui.org/specification/v0_9/basic_catalog.json';
|
|
15
|
+
/** Every id the basic catalog goes by: the current one first. */
|
|
16
|
+
export const A2UI_BASIC_CATALOG_IDS = Object.freeze([
|
|
17
|
+
A2UI_BASIC_CATALOG_ID,
|
|
18
|
+
A2UI_LEGACY_BASIC_CATALOG_ID,
|
|
19
|
+
]);
|
|
20
|
+
/** Whether a catalog id names A2UI v0.9's basic catalog (under either of its ids). */
|
|
21
|
+
export function isA2uiBasicCatalogId(id) {
|
|
22
|
+
return typeof id === 'string' && A2UI_BASIC_CATALOG_IDS.includes(id);
|
|
23
|
+
}
|
|
24
|
+
/** The components of A2UI v0.9's basic catalog. */
|
|
25
|
+
export const A2UI_BASIC_COMPONENTS = [
|
|
26
|
+
'Text',
|
|
27
|
+
'Image',
|
|
28
|
+
'Icon',
|
|
29
|
+
'Video',
|
|
30
|
+
'AudioPlayer',
|
|
31
|
+
'Row',
|
|
32
|
+
'Column',
|
|
33
|
+
'List',
|
|
34
|
+
'Card',
|
|
35
|
+
'Tabs',
|
|
36
|
+
'Modal',
|
|
37
|
+
'Divider',
|
|
38
|
+
'Button',
|
|
39
|
+
'CheckBox',
|
|
40
|
+
'TextField',
|
|
41
|
+
'DateTimeInput',
|
|
42
|
+
'ChoicePicker',
|
|
43
|
+
'Slider',
|
|
44
|
+
];
|
|
45
|
+
/** The AG-UI activity type A2UI surfaces travel under (AG-UI's A2UI middleware, CopilotKit). */
|
|
46
|
+
export const A2UI_ACTIVITY_TYPE = 'a2ui-surface';
|
|
47
|
+
/** The key of the A2UI messages in an `a2ui-surface` activity's content. */
|
|
48
|
+
export const A2UI_OPERATIONS_KEY = 'a2ui_operations';
|
|
49
|
+
/** Action names the transports answer themselves: deciding a parked approval. */
|
|
50
|
+
export const A2UI_APPROVE_ACTION = 'agora.approve';
|
|
51
|
+
export const A2UI_REJECT_ACTION = 'agora.reject';
|
|
52
|
+
function isRecord(value) {
|
|
53
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
const str = (value, fallback = '') => typeof value === 'string' ? value : typeof value === 'number' ? String(value) : fallback;
|
|
56
|
+
function text(id, value, variant) {
|
|
57
|
+
return { id, component: 'Text', text: value, ...(variant !== undefined ? { variant } : {}) };
|
|
58
|
+
}
|
|
59
|
+
/** A column of `[heading?, ...children]` — what most builtins come down to. */
|
|
60
|
+
function titled(ctx, title, children, extra = []) {
|
|
61
|
+
const heading = typeof title === 'string' && title.length > 0;
|
|
62
|
+
const titleId = ctx.derive('title');
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
id: ctx.id,
|
|
66
|
+
component: 'Column',
|
|
67
|
+
children: heading ? [titleId, ...children] : [...children],
|
|
68
|
+
},
|
|
69
|
+
...(heading ? [text(titleId, title, 'h4')] : []),
|
|
70
|
+
...extra,
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
function cards(ctx, items, lines) {
|
|
74
|
+
const list = Array.isArray(items) ? items.filter(isRecord) : [];
|
|
75
|
+
const out = [];
|
|
76
|
+
const ids = [];
|
|
77
|
+
list.forEach((item, index) => {
|
|
78
|
+
const card = ctx.derive(`item${index}`);
|
|
79
|
+
const column = ctx.derive(`item${index}col`);
|
|
80
|
+
const parts = lines(item).filter(([value]) => value.length > 0);
|
|
81
|
+
ids.push(card);
|
|
82
|
+
out.push({ id: card, component: 'Card', child: column });
|
|
83
|
+
out.push({
|
|
84
|
+
id: column,
|
|
85
|
+
component: 'Column',
|
|
86
|
+
children: parts.map((_, line) => ctx.derive(`item${index}l${line}`)),
|
|
87
|
+
});
|
|
88
|
+
parts.forEach(([value, variant], line) => {
|
|
89
|
+
out.push(text(ctx.derive(`item${index}l${line}`), value, variant));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
return [{ id: ctx.id, component: 'Row', children: ids }, ...out];
|
|
93
|
+
}
|
|
94
|
+
function table(ctx, title, columns, rows) {
|
|
95
|
+
const out = [];
|
|
96
|
+
const header = ctx.derive('header');
|
|
97
|
+
const rowIds = rows.map((_, index) => ctx.derive(`row${index}`));
|
|
98
|
+
out.push({
|
|
99
|
+
id: header,
|
|
100
|
+
component: 'Row',
|
|
101
|
+
children: columns.map((_, index) => ctx.derive(`h${index}`)),
|
|
102
|
+
});
|
|
103
|
+
columns.forEach((column, index) => {
|
|
104
|
+
out.push({ ...text(ctx.derive(`h${index}`), column.label, 'caption'), weight: 1 });
|
|
105
|
+
});
|
|
106
|
+
rows.forEach((row, r) => {
|
|
107
|
+
out.push({
|
|
108
|
+
id: rowIds[r],
|
|
109
|
+
component: 'Row',
|
|
110
|
+
children: columns.map((_, c) => ctx.derive(`r${r}c${c}`)),
|
|
111
|
+
});
|
|
112
|
+
columns.forEach((column, c) => {
|
|
113
|
+
out.push({ ...text(ctx.derive(`r${r}c${c}`), str(row[column.key])), weight: 1 });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return titled(ctx, title, [header, ...rowIds], out);
|
|
117
|
+
}
|
|
118
|
+
function columnsOf(value) {
|
|
119
|
+
return (Array.isArray(value) ? value : [])
|
|
120
|
+
.filter(isRecord)
|
|
121
|
+
.filter((column) => typeof column.key === 'string')
|
|
122
|
+
.map((column) => ({
|
|
123
|
+
key: column.key,
|
|
124
|
+
label: str(column.label, column.key),
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
function rowsOf(value) {
|
|
128
|
+
return (Array.isArray(value) ? value : []).filter(isRecord);
|
|
129
|
+
}
|
|
130
|
+
/** The library builtins, mapped onto A2UI's basic catalog. */
|
|
131
|
+
export const A2UI_BUILTIN_MAPPERS = Object.freeze({
|
|
132
|
+
Stack: (props, ctx) => [
|
|
133
|
+
{
|
|
134
|
+
id: ctx.id,
|
|
135
|
+
component: props.direction === 'row' ? 'Row' : 'Column',
|
|
136
|
+
children: [...ctx.children],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
Card: (props, ctx) => {
|
|
140
|
+
const inner = ctx.derive('body');
|
|
141
|
+
const subtitle = ctx.derive('subtitle');
|
|
142
|
+
const hasSubtitle = typeof props.subtitle === 'string' && props.subtitle.length > 0;
|
|
143
|
+
return [
|
|
144
|
+
{ id: ctx.id, component: 'Card', child: inner },
|
|
145
|
+
...titled({ ...ctx, id: inner }, props.title, hasSubtitle ? [subtitle, ...ctx.children] : ctx.children, hasSubtitle ? [text(subtitle, props.subtitle, 'caption')] : []),
|
|
146
|
+
];
|
|
147
|
+
},
|
|
148
|
+
Heading: (props, ctx) => [text(ctx.id, str(props.text), 'h3')],
|
|
149
|
+
Text: (props, ctx) => [text(ctx.id, str(props.text), props.muted === true ? 'caption' : 'body')],
|
|
150
|
+
Badge: (props, ctx) => [text(ctx.id, str(props.text), 'caption')],
|
|
151
|
+
Callout: (props, ctx) => {
|
|
152
|
+
const body = ctx.derive('body');
|
|
153
|
+
const message = ctx.derive('text');
|
|
154
|
+
return [
|
|
155
|
+
{ id: ctx.id, component: 'Card', child: body },
|
|
156
|
+
...titled({ ...ctx, id: body }, props.title, [message], [text(message, str(props.text))]),
|
|
157
|
+
];
|
|
158
|
+
},
|
|
159
|
+
Link: (props, ctx) => {
|
|
160
|
+
const label = ctx.derive('label');
|
|
161
|
+
return [
|
|
162
|
+
{
|
|
163
|
+
id: ctx.id,
|
|
164
|
+
component: 'Button',
|
|
165
|
+
variant: 'borderless',
|
|
166
|
+
child: label,
|
|
167
|
+
action: { functionCall: { call: 'openUrl', args: { url: str(props.url) } } },
|
|
168
|
+
},
|
|
169
|
+
text(label, str(props.text, str(props.url))),
|
|
170
|
+
];
|
|
171
|
+
},
|
|
172
|
+
Image: (props, ctx) => [
|
|
173
|
+
{
|
|
174
|
+
id: ctx.id,
|
|
175
|
+
component: 'Image',
|
|
176
|
+
url: str(props.url),
|
|
177
|
+
...(typeof props.alt === 'string' ? { description: props.alt } : {}),
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
CodeBlock: (props, ctx) => [text(ctx.id, `\`\`\`\n${str(props.code)}\n\`\`\``)],
|
|
181
|
+
KpiCards: (props, ctx) => cards(ctx, props.items, (item) => [
|
|
182
|
+
[str(item.label), 'caption'],
|
|
183
|
+
[str(item.value), 'h3'],
|
|
184
|
+
[str(item.delta), 'caption'],
|
|
185
|
+
]),
|
|
186
|
+
SourceCards: (props, ctx) => cards(ctx, props.items, (item) => [
|
|
187
|
+
[str(item.title), 'h5'],
|
|
188
|
+
[str(item.snippet), 'body'],
|
|
189
|
+
[str(item.source, str(item.url)), 'caption'],
|
|
190
|
+
]),
|
|
191
|
+
DataTable: (props, ctx) => table(ctx, props.title, columnsOf(props.columns), rowsOf(props.rows)),
|
|
192
|
+
// The basic catalog has no chart: its points, as a table — what a text client gets too.
|
|
193
|
+
Chart: (props, ctx) => {
|
|
194
|
+
const xKey = str(props.xKey);
|
|
195
|
+
const series = (Array.isArray(props.series) ? props.series : [])
|
|
196
|
+
.filter(isRecord)
|
|
197
|
+
.filter((each) => typeof each.key === 'string')
|
|
198
|
+
.map((each) => ({ key: each.key, label: str(each.label, each.key) }));
|
|
199
|
+
return table(ctx, props.title, [{ key: xKey, label: xKey }, ...series], rowsOf(props.data));
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
/** Ids A2UI renderers accept everywhere: the node ids (`root.0.1`) and derived ones (`root.0~title`). */
|
|
203
|
+
function safeId(id) {
|
|
204
|
+
return id.replace(/[^A-Za-z0-9_.~:-]/g, '_');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* A `ui` frame as A2UI components, flat, the root's id `root`. A composed tree (`genui:tree`) maps
|
|
208
|
+
* node by node — node ids are their positions (`root`, `root.0`, `root.0.2`), stable from the first
|
|
209
|
+
* preview to the final frame, so a streamed surface only ever grows. A node still held back
|
|
210
|
+
* (`held`) is left out: its parent references an id that arrives later, which A2UI draws as a
|
|
211
|
+
* placeholder.
|
|
212
|
+
*/
|
|
213
|
+
export function toA2uiComponents(frame, options = {}) {
|
|
214
|
+
const props = isRecord(frame.props) ? frame.props : {};
|
|
215
|
+
const root = frame.component === GENUI_TREE_COMPONENT
|
|
216
|
+
? isRecord(props.root) && typeof props.root.type === 'string'
|
|
217
|
+
? props.root
|
|
218
|
+
: undefined
|
|
219
|
+
: { type: frame.component, props };
|
|
220
|
+
if (root === undefined)
|
|
221
|
+
return [];
|
|
222
|
+
const out = [];
|
|
223
|
+
const visit = (node, id, depth) => {
|
|
224
|
+
if (depth > 32 || node.held === true)
|
|
225
|
+
return;
|
|
226
|
+
const children = (Array.isArray(node.children) ? node.children : []).filter((child) => isRecord(child) && typeof child.type === 'string');
|
|
227
|
+
const childIds = children.map((_, index) => `${id}.${index}`);
|
|
228
|
+
const nodeProps = isRecord(node.props) ? node.props : {};
|
|
229
|
+
const ctx = {
|
|
230
|
+
id,
|
|
231
|
+
children: childIds,
|
|
232
|
+
derive: (suffix) => `${id}~${suffix}`,
|
|
233
|
+
incomplete: node.incomplete === true,
|
|
234
|
+
text: () => options.catalog !== undefined ? componentToText(options.catalog, node.type, nodeProps) : '',
|
|
235
|
+
};
|
|
236
|
+
out.push(...mapNode(node.type, nodeProps, ctx, options));
|
|
237
|
+
children.forEach((child, index) => {
|
|
238
|
+
visit(child, childIds[index], depth + 1);
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
visit(root, 'root', 1);
|
|
242
|
+
return out.map((component) => ({ ...component, id: safeId(component.id) })).map(fixRefs);
|
|
243
|
+
}
|
|
244
|
+
function fixRefs(component) {
|
|
245
|
+
const next = { ...component };
|
|
246
|
+
if (Array.isArray(next.children))
|
|
247
|
+
next.children = next.children.map((id) => safeId(String(id)));
|
|
248
|
+
for (const key of ['child', 'trigger', 'content']) {
|
|
249
|
+
if (typeof next[key] === 'string')
|
|
250
|
+
next[key] = safeId(next[key]);
|
|
251
|
+
}
|
|
252
|
+
return next;
|
|
253
|
+
}
|
|
254
|
+
function mapNode(type, props, ctx, options) {
|
|
255
|
+
const own = options.components?.[type];
|
|
256
|
+
if (typeof own === 'function')
|
|
257
|
+
return withRoot(own(props, ctx), ctx, type);
|
|
258
|
+
if (own === 'custom' || (own === undefined && options.unmapped === 'custom')) {
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
...props,
|
|
262
|
+
id: ctx.id,
|
|
263
|
+
component: type,
|
|
264
|
+
...(ctx.children.length > 0 ? { children: [...ctx.children] } : {}),
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
const builtin = A2UI_BUILTIN_MAPPERS[type];
|
|
269
|
+
if (builtin !== undefined)
|
|
270
|
+
return withRoot(builtin(props, ctx), ctx, type);
|
|
271
|
+
// Nothing maps it: its words, then whatever it holds.
|
|
272
|
+
const words = ctx.text();
|
|
273
|
+
if (ctx.children.length === 0)
|
|
274
|
+
return [text(ctx.id, words)];
|
|
275
|
+
const label = ctx.derive('text');
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
id: ctx.id,
|
|
279
|
+
component: 'Column',
|
|
280
|
+
children: words.length > 0 ? [label, ...ctx.children] : [...ctx.children],
|
|
281
|
+
},
|
|
282
|
+
...(words.length > 0 ? [text(label, words)] : []),
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
function withRoot(components, ctx, type) {
|
|
286
|
+
if (!components.some((component) => component.id === ctx.id)) {
|
|
287
|
+
throw new Error(`a2ui: the mapping of ${type} must return a component with id "${ctx.id}"`);
|
|
288
|
+
}
|
|
289
|
+
return components;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* {@link toA2uiComponents} that never throws: a mapping that fails (an app mapper that throws or
|
|
293
|
+
* forgets the root id) draws the component's text instead of breaking the stream it rides on.
|
|
294
|
+
*/
|
|
295
|
+
function safeComponents(frame, options) {
|
|
296
|
+
try {
|
|
297
|
+
return toA2uiComponents(frame, options);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
const props = isRecord(frame.props) ? frame.props : {};
|
|
301
|
+
const words = options.catalog !== undefined ? componentToText(options.catalog, frame.component, props) : '';
|
|
302
|
+
return words.length > 0 ? [text('root', words)] : [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/** The messages that draw (or redraw) a surface: `createSurface` the first time, then its components. */
|
|
306
|
+
export function a2uiSurfaceMessages(surfaceId, components, options) {
|
|
307
|
+
const out = [];
|
|
308
|
+
if (options.create) {
|
|
309
|
+
out.push({
|
|
310
|
+
version: A2UI_VERSION,
|
|
311
|
+
createSurface: {
|
|
312
|
+
surfaceId,
|
|
313
|
+
catalogId: options.catalogId ?? options.basicCatalogId ?? A2UI_BASIC_CATALOG_ID,
|
|
314
|
+
...(options.theme !== undefined ? { theme: options.theme } : {}),
|
|
315
|
+
...(options.sendDataModel === true ? { sendDataModel: true } : {}),
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (components.length > 0) {
|
|
320
|
+
out.push({ version: A2UI_VERSION, updateComponents: { surfaceId, components } });
|
|
321
|
+
}
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
/** A surface id A2UI clients accept, from a `ui` frame id (`<toolCallId>:ui:0`). */
|
|
325
|
+
export function a2uiSurfaceId(frameId) {
|
|
326
|
+
return frameId.replace(/[^A-Za-z0-9_.~:-]/g, '_');
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* A2UI's AG-UI binding (the AG-UI A2UI middleware, CopilotKit's renderer): one `ACTIVITY_SNAPSHOT`
|
|
330
|
+
* of type `a2ui-surface` per `ui` frame, carrying the whole surface (`createSurface` +
|
|
331
|
+
* `updateComponents`) with `replace: true` — so each preview of a streaming tree repaints the same
|
|
332
|
+
* activity in place, keyed by the frame id.
|
|
333
|
+
*/
|
|
334
|
+
export function a2uiActivityEvent(frame, options = {}) {
|
|
335
|
+
const props = isRecord(frame.props) ? frame.props : {};
|
|
336
|
+
const surfaceId = a2uiSurfaceId(frame.id);
|
|
337
|
+
// A withdrawn preview: the surface it painted goes.
|
|
338
|
+
if (frame.partial === true && Object.keys(props).length === 0) {
|
|
339
|
+
return {
|
|
340
|
+
type: 'ACTIVITY_SNAPSHOT',
|
|
341
|
+
messageId: `a2ui-surface-${surfaceId}`,
|
|
342
|
+
activityType: A2UI_ACTIVITY_TYPE,
|
|
343
|
+
content: { [A2UI_OPERATIONS_KEY]: [{ version: A2UI_VERSION, deleteSurface: { surfaceId } }] },
|
|
344
|
+
replace: true,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const components = safeComponents(frame, options);
|
|
348
|
+
if (components.length === 0)
|
|
349
|
+
return null;
|
|
350
|
+
return {
|
|
351
|
+
type: 'ACTIVITY_SNAPSHOT',
|
|
352
|
+
messageId: `a2ui-surface-${surfaceId}`,
|
|
353
|
+
activityType: A2UI_ACTIVITY_TYPE,
|
|
354
|
+
content: {
|
|
355
|
+
[A2UI_OPERATIONS_KEY]: a2uiSurfaceMessages(surfaceId, components, {
|
|
356
|
+
...options,
|
|
357
|
+
create: true,
|
|
358
|
+
}),
|
|
359
|
+
},
|
|
360
|
+
replace: true,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* An A2UI catalog definition (v0.9 inline catalog shape) for an agent catalog's components sent
|
|
365
|
+
* as custom components: `{ catalogId, components: { Name: JSON Schema } }` — what a client
|
|
366
|
+
* registers, or hands an A2UI-aware runtime (`a2uiClientCapabilities.inlineCatalogs`).
|
|
367
|
+
*/
|
|
368
|
+
export function a2uiCatalog(catalog, options) {
|
|
369
|
+
const components = {};
|
|
370
|
+
for (const definition of catalog.modelComponents()) {
|
|
371
|
+
if (options.only !== undefined && !options.only.includes(definition.name))
|
|
372
|
+
continue;
|
|
373
|
+
const schema = toJsonSchema(definition.props) ?? { type: 'object' };
|
|
374
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
375
|
+
components[definition.name] = {
|
|
376
|
+
type: 'object',
|
|
377
|
+
description: definition.description,
|
|
378
|
+
properties: {
|
|
379
|
+
component: { const: definition.name },
|
|
380
|
+
...properties,
|
|
381
|
+
...(definition.children === true
|
|
382
|
+
? {
|
|
383
|
+
children: {
|
|
384
|
+
$ref: 'https://a2ui.org/specification/v0_9/common_types.json#/$defs/ChildList',
|
|
385
|
+
},
|
|
386
|
+
}
|
|
387
|
+
: {}),
|
|
388
|
+
},
|
|
389
|
+
required: ['component', ...(Array.isArray(schema.required) ? schema.required : [])],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return { catalogId: options.catalogId, components };
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* A client-to-server A2UI message, or an AG-UI `forwardedProps.a2uiAction`, as a {@link UiAction}:
|
|
396
|
+
* v0.9 `{ version, action: { name, surfaceId, sourceComponentId, timestamp, context } }`, v0.8
|
|
397
|
+
* `{ userAction: { … } }`, or the bare action object. A string when it is not one.
|
|
398
|
+
*/
|
|
399
|
+
export function readA2uiAction(raw, options = {}) {
|
|
400
|
+
if (!isRecord(raw))
|
|
401
|
+
return 'an A2UI action must be an object';
|
|
402
|
+
const inner = isRecord(raw.action) ? raw.action : isRecord(raw.userAction) ? raw.userAction : raw;
|
|
403
|
+
if (typeof inner.name !== 'string')
|
|
404
|
+
return 'an A2UI action carries a name';
|
|
405
|
+
return readUiAction({
|
|
406
|
+
source: 'a2ui',
|
|
407
|
+
name: inner.name,
|
|
408
|
+
context: isRecord(inner.context) ? inner.context : {},
|
|
409
|
+
...(typeof inner.surfaceId === 'string' ? { surfaceId: inner.surfaceId } : {}),
|
|
410
|
+
...(typeof inner.sourceComponentId === 'string'
|
|
411
|
+
? { componentId: inner.sourceComponentId }
|
|
412
|
+
: {}),
|
|
413
|
+
...(typeof inner.timestamp === 'string' ? { timestamp: inner.timestamp } : {}),
|
|
414
|
+
...(typeof inner.text === 'string' ? { text: inner.text } : {}),
|
|
415
|
+
}, { ...options, source: 'a2ui' });
|
|
416
|
+
}
|
|
417
|
+
/** The surface an approval interrupt is shown on: its message, and Approve / Reject buttons. */
|
|
418
|
+
export function a2uiApprovalComponents(interrupt) {
|
|
419
|
+
const button = (id, label, name, variant) => [
|
|
420
|
+
{
|
|
421
|
+
id,
|
|
422
|
+
component: 'Button',
|
|
423
|
+
variant,
|
|
424
|
+
child: `${id}~label`,
|
|
425
|
+
action: { event: { name, context: { interruptId: interrupt.id } } },
|
|
426
|
+
},
|
|
427
|
+
text(`${id}~label`, label),
|
|
428
|
+
];
|
|
429
|
+
return [
|
|
430
|
+
{ id: 'root', component: 'Card', child: 'body' },
|
|
431
|
+
{ id: 'body', component: 'Column', children: ['message', 'buttons'] },
|
|
432
|
+
text('message', interrupt.message ?? 'Approve?', 'body'),
|
|
433
|
+
{ id: 'buttons', component: 'Row', children: ['approve', 'reject'] },
|
|
434
|
+
...button('approve', 'Approve', A2UI_APPROVE_ACTION, 'primary'),
|
|
435
|
+
...button('reject', 'Reject', A2UI_REJECT_ACTION, 'default'),
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* One AG-UI run as an A2UI message stream — the projection the A2UI transport serves. Stateful
|
|
440
|
+
* (which surfaces exist), pure otherwise: feed it the run's AG-UI events in order.
|
|
441
|
+
*
|
|
442
|
+
* - `CUSTOM agora.ui` → a surface per `ui` frame id, created once and updated in place (previews
|
|
443
|
+
* included); a withdrawn preview deletes it;
|
|
444
|
+
* - text messages → a text surface each (see {@link A2uiStreamOptions.text});
|
|
445
|
+
* - an approval interrupt → a surface with Approve / Reject buttons ({@link A2UI_APPROVE_ACTION});
|
|
446
|
+
* any other interrupt → a text surface saying to answer it in the app;
|
|
447
|
+
* - `RUN_ERROR` → a text surface with the message.
|
|
448
|
+
*/
|
|
449
|
+
export class A2uiProjector {
|
|
450
|
+
options;
|
|
451
|
+
surfaces = new Set();
|
|
452
|
+
texts = new Map();
|
|
453
|
+
constructor(options = {}) {
|
|
454
|
+
this.options = options;
|
|
455
|
+
}
|
|
456
|
+
project(event) {
|
|
457
|
+
switch (event.type) {
|
|
458
|
+
case 'CUSTOM':
|
|
459
|
+
return event.name === 'agora.ui' ? this.ui(event.value) : [];
|
|
460
|
+
case 'TEXT_MESSAGE_START':
|
|
461
|
+
if (this.options.text === 'omit')
|
|
462
|
+
return [];
|
|
463
|
+
this.texts.set(event.messageId, '');
|
|
464
|
+
return this.surface(`text-${event.messageId}`, [
|
|
465
|
+
{ id: 'root', component: 'Text', text: { path: '/text' } },
|
|
466
|
+
]);
|
|
467
|
+
case 'TEXT_MESSAGE_CONTENT': {
|
|
468
|
+
if (this.options.text === 'omit')
|
|
469
|
+
return [];
|
|
470
|
+
const next = `${this.texts.get(event.messageId) ?? ''}${event.delta}`;
|
|
471
|
+
this.texts.set(event.messageId, next);
|
|
472
|
+
return [
|
|
473
|
+
{
|
|
474
|
+
version: A2UI_VERSION,
|
|
475
|
+
updateDataModel: { surfaceId: `text-${event.messageId}`, path: '/text', value: next },
|
|
476
|
+
},
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
case 'RUN_FINISHED': {
|
|
480
|
+
const outcome = event.outcome;
|
|
481
|
+
if (!isRecord(outcome) || outcome.type !== 'interrupt')
|
|
482
|
+
return [];
|
|
483
|
+
const interrupts = Array.isArray(outcome.interrupts) ? outcome.interrupts : [];
|
|
484
|
+
return interrupts.filter(isRecord).flatMap((interrupt) => {
|
|
485
|
+
const id = a2uiSurfaceId(`interrupt-${String(interrupt.id)}`);
|
|
486
|
+
return interrupt.reason === 'tool_approval'
|
|
487
|
+
? this.surface(id, a2uiApprovalComponents(interrupt))
|
|
488
|
+
: this.surface(id, [
|
|
489
|
+
text('root', `${str(interrupt.message, 'The assistant is waiting for an answer.')} (answer it in the app)`),
|
|
490
|
+
]);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
case 'RUN_ERROR':
|
|
494
|
+
return this.surface(`error-${this.surfaces.size}`, [text('root', event.message)]);
|
|
495
|
+
default:
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
surface(surfaceId, components) {
|
|
500
|
+
const create = !this.surfaces.has(surfaceId);
|
|
501
|
+
this.surfaces.add(surfaceId);
|
|
502
|
+
return a2uiSurfaceMessages(surfaceId, components, {
|
|
503
|
+
...this.options,
|
|
504
|
+
// Text, approvals and errors are basic-catalog components whatever the app's catalog is.
|
|
505
|
+
catalogId: this.options.basicCatalogId ?? A2UI_BASIC_CATALOG_ID,
|
|
506
|
+
create,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
ui(value) {
|
|
510
|
+
if (!isRecord(value) || typeof value.id !== 'string' || typeof value.component !== 'string') {
|
|
511
|
+
return [];
|
|
512
|
+
}
|
|
513
|
+
const surfaceId = a2uiSurfaceId(value.id);
|
|
514
|
+
const props = isRecord(value.props) ? value.props : {};
|
|
515
|
+
if (value.partial === true && Object.keys(props).length === 0) {
|
|
516
|
+
if (!this.surfaces.delete(surfaceId))
|
|
517
|
+
return [];
|
|
518
|
+
return [{ version: A2UI_VERSION, deleteSurface: { surfaceId } }];
|
|
519
|
+
}
|
|
520
|
+
const components = safeComponents({ component: value.component, props: props }, this.options);
|
|
521
|
+
if (components.length === 0)
|
|
522
|
+
return [];
|
|
523
|
+
const create = !this.surfaces.has(surfaceId);
|
|
524
|
+
this.surfaces.add(surfaceId);
|
|
525
|
+
return a2uiSurfaceMessages(surfaceId, components, { ...this.options, create });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function catalogIdsOf(value) {
|
|
529
|
+
if (!isRecord(value) || !Array.isArray(value.supportedCatalogIds))
|
|
530
|
+
return undefined;
|
|
531
|
+
return value.supportedCatalogIds.filter((id) => typeof id === 'string');
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* The catalog ids a client says it draws, from its `a2uiClientCapabilities`: v0.9's
|
|
535
|
+
* `{ 'v0.9': { supportedCatalogIds } }` (what `MessageProcessor.getRendererCapabilities()` builds),
|
|
536
|
+
* a `v0.9.1` entry, or a flat `{ supportedCatalogIds }`. `undefined` when it names none.
|
|
537
|
+
*/
|
|
538
|
+
export function readA2uiClientCapabilities(raw) {
|
|
539
|
+
if (!isRecord(raw))
|
|
540
|
+
return undefined;
|
|
541
|
+
const ids = [
|
|
542
|
+
...(catalogIdsOf(raw['v0.9']) ?? []),
|
|
543
|
+
...(catalogIdsOf(raw['v0.9.1']) ?? []),
|
|
544
|
+
...(catalogIdsOf(raw) ?? []),
|
|
545
|
+
];
|
|
546
|
+
return ids.length > 0 ? [...new Set(ids)] : undefined;
|
|
547
|
+
}
|
|
548
|
+
/** The AG-UI context entry CopilotKit's A2UI provider sends ("A2UI catalog capabilities: …"). */
|
|
549
|
+
const AG_UI_CATALOG_CONTEXT = /^A2UI catalog capabilities/i;
|
|
550
|
+
/**
|
|
551
|
+
* The catalog ids an AG-UI client advertises: `forwardedProps.a2uiClientCapabilities`, or the
|
|
552
|
+
* `context` entry CopilotKit's A2UI provider adds (its value lists the ids, one `- <id>` per line).
|
|
553
|
+
*/
|
|
554
|
+
export function readAgUiA2uiCatalogIds(input) {
|
|
555
|
+
const forwarded = isRecord(input.forwardedProps)
|
|
556
|
+
? readA2uiClientCapabilities(input.forwardedProps.a2uiClientCapabilities)
|
|
557
|
+
: undefined;
|
|
558
|
+
if (forwarded !== undefined)
|
|
559
|
+
return forwarded;
|
|
560
|
+
const ids = [];
|
|
561
|
+
for (const entry of input.context ?? []) {
|
|
562
|
+
if (!isRecord(entry) || typeof entry.description !== 'string')
|
|
563
|
+
continue;
|
|
564
|
+
if (!AG_UI_CATALOG_CONTEXT.test(entry.description) || typeof entry.value !== 'string')
|
|
565
|
+
continue;
|
|
566
|
+
for (const line of entry.value.split('\n')) {
|
|
567
|
+
const id = /^\s*-\s+(\S+:\/\/\S+)/.exec(line)?.[1];
|
|
568
|
+
if (id !== undefined)
|
|
569
|
+
ids.push(id);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return ids.length > 0 ? [...new Set(ids)] : undefined;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Options for one client: the basic catalog under the id the client advertises (else the
|
|
576
|
+
* configured `basicCatalogId`, a basic `catalogId`, or the transport's `fallback`), and `catalogId`
|
|
577
|
+
* resolved — an app catalog stays as configured; the basic catalog takes the negotiated id.
|
|
578
|
+
*/
|
|
579
|
+
export function negotiateA2uiCatalog(options, supported, fallback = A2UI_BASIC_CATALOG_ID) {
|
|
580
|
+
const advertised = (supported ?? []).filter(isA2uiBasicCatalogId);
|
|
581
|
+
const preferred = [options.basicCatalogId, options.catalogId].find((id) => id !== undefined && advertised.includes(id));
|
|
582
|
+
const basic = preferred ??
|
|
583
|
+
advertised[0] ??
|
|
584
|
+
options.basicCatalogId ??
|
|
585
|
+
(isA2uiBasicCatalogId(options.catalogId) ? options.catalogId : undefined) ??
|
|
586
|
+
fallback;
|
|
587
|
+
const catalogId = options.catalogId === undefined || isA2uiBasicCatalogId(options.catalogId)
|
|
588
|
+
? basic
|
|
589
|
+
: options.catalogId;
|
|
590
|
+
return { ...options, basicCatalogId: basic, catalogId };
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* A stored thread as A2UI, for a client that reopens it: each user message as its line, each
|
|
594
|
+
* assistant step as the surfaces the live stream drew — its text (one `Text`), then every `ui`
|
|
595
|
+
* frame it kept (the last props per id), mapped exactly as the stream maps them, under the same
|
|
596
|
+
* surface ids. An approval the thread is still waiting on is not redrawn: ask again by sending.
|
|
597
|
+
*/
|
|
598
|
+
export function a2uiThreadReplay(messages, options = {}) {
|
|
599
|
+
const out = [];
|
|
600
|
+
const basic = options.basicCatalogId ?? A2UI_BASIC_CATALOG_ID;
|
|
601
|
+
for (const message of messages) {
|
|
602
|
+
if (message.role === 'user') {
|
|
603
|
+
const action = readUiActionText(message.content);
|
|
604
|
+
out.push({
|
|
605
|
+
role: 'user',
|
|
606
|
+
id: message.id,
|
|
607
|
+
text: action?.text ?? message.content,
|
|
608
|
+
...(action !== null ? { action } : {}),
|
|
609
|
+
});
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (message.role !== 'assistant')
|
|
613
|
+
continue;
|
|
614
|
+
const surfaces = [];
|
|
615
|
+
if (options.text !== 'omit' && message.content.trim().length > 0) {
|
|
616
|
+
surfaces.push(...a2uiSurfaceMessages(`text-${message.id}`, [text('root', message.content)], {
|
|
617
|
+
...options,
|
|
618
|
+
catalogId: basic,
|
|
619
|
+
create: true,
|
|
620
|
+
}));
|
|
621
|
+
}
|
|
622
|
+
for (const frame of message.ui ?? []) {
|
|
623
|
+
if (frame.partial === true)
|
|
624
|
+
continue;
|
|
625
|
+
const components = safeComponents(frame, options);
|
|
626
|
+
if (components.length === 0)
|
|
627
|
+
continue;
|
|
628
|
+
surfaces.push(...a2uiSurfaceMessages(a2uiSurfaceId(frame.id), components, { ...options, create: true }));
|
|
629
|
+
}
|
|
630
|
+
if (surfaces.length > 0)
|
|
631
|
+
out.push({ role: 'assistant', id: message.id, messages: surfaces });
|
|
632
|
+
}
|
|
633
|
+
return out;
|
|
634
|
+
}
|
|
635
|
+
//# sourceMappingURL=core.js.map
|