@urbicon-ui/blocks 6.40.3 → 6.43.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.
Files changed (84) hide show
  1. package/dist/components/Calendar/calendar.variants.d.ts +63 -63
  2. package/dist/components/Chat/A2UIView/A2UINode.svelte +20 -39
  3. package/dist/components/Chat/A2UIView/A2UIView.svelte +47 -50
  4. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.d.ts +17 -0
  5. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.js +69 -0
  6. package/dist/components/Chat/A2UIView/a2ui-catalog.d.ts +95 -0
  7. package/dist/components/Chat/A2UIView/a2ui-catalog.js +87 -0
  8. package/dist/components/Chat/A2UIView/a2ui-node-common.d.ts +40 -0
  9. package/dist/components/Chat/A2UIView/a2ui-node-common.js +72 -0
  10. package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +21 -10
  11. package/dist/components/Chat/A2UIView/a2ui-prompt.js +170 -24
  12. package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +1 -1
  13. package/dist/components/Chat/A2UIView/a2ui-registry.js +2 -1
  14. package/dist/components/Chat/A2UIView/a2ui-render.d.ts +30 -0
  15. package/dist/components/Chat/A2UIView/a2ui-render.js +61 -23
  16. package/dist/components/Chat/A2UIView/a2ui-router.d.ts +136 -0
  17. package/dist/components/Chat/A2UIView/a2ui-router.js +264 -0
  18. package/dist/components/Chat/A2UIView/a2ui-schema.d.ts +45 -0
  19. package/dist/components/Chat/A2UIView/a2ui-schema.js +133 -0
  20. package/dist/components/Chat/A2UIView/a2ui-stream.d.ts +120 -0
  21. package/dist/components/Chat/A2UIView/a2ui-stream.js +406 -0
  22. package/dist/components/Chat/A2UIView/a2ui-transcript.d.ts +65 -0
  23. package/dist/components/Chat/A2UIView/a2ui-transcript.js +101 -0
  24. package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +33 -1
  25. package/dist/components/Chat/A2UIView/a2ui-validate.js +167 -63
  26. package/dist/components/Chat/A2UIView/a2ui.types.d.ts +50 -0
  27. package/dist/components/Chat/A2UIView/a2ui.types.js +38 -1
  28. package/dist/components/Chat/A2UIView/index.d.ts +35 -2
  29. package/dist/components/Chat/A2UIView/index.js +13 -0
  30. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte +831 -0
  31. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte.d.ts +4 -0
  32. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.d.ts +14 -0
  33. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.js +87 -0
  34. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.d.ts +78 -0
  35. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.js +783 -0
  36. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.d.ts +43 -0
  37. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.js +33 -0
  38. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.d.ts +7 -0
  39. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.js +86 -0
  40. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +19 -19
  41. package/dist/components/CopyButton/copy-button.variants.d.ts +4 -4
  42. package/dist/components/FileUpload/FileUpload.svelte +8 -5
  43. package/dist/components/FileUpload/fileUpload.variants.js +15 -4
  44. package/dist/components/PinInput/PinInput.svelte +1 -0
  45. package/dist/components/PinInput/pin-input.variants.js +14 -1
  46. package/dist/components/TimeInput/time-input.variants.js +16 -1
  47. package/dist/i18n/index.d.ts +2 -398
  48. package/dist/internal/field-chrome.d.ts +18 -1
  49. package/dist/internal/field-chrome.js +18 -1
  50. package/dist/primitives/Alert/alert.variants.d.ts +8 -8
  51. package/dist/primitives/Avatar/avatar.variants.js +4 -1
  52. package/dist/primitives/Badge/badge.variants.js +11 -2
  53. package/dist/primitives/Button/button.variants.js +4 -1
  54. package/dist/primitives/Card/card.variants.js +4 -1
  55. package/dist/primitives/Checkbox/checkbox.variants.js +7 -1
  56. package/dist/primitives/Combobox/Combobox.svelte +9 -1
  57. package/dist/primitives/Combobox/combobox.variants.d.ts +25 -0
  58. package/dist/primitives/Combobox/combobox.variants.js +32 -3
  59. package/dist/primitives/Combobox/index.d.ts +8 -2
  60. package/dist/primitives/Dialog/dialog.variants.js +4 -1
  61. package/dist/primitives/Drawer/drawer.variants.js +4 -1
  62. package/dist/primitives/Input/input.variants.js +17 -1
  63. package/dist/primitives/JourneyTimeline/journey-timeline.variants.d.ts +22 -22
  64. package/dist/primitives/Menu/menu.variants.js +5 -1
  65. package/dist/primitives/Progress/progress.variants.js +5 -2
  66. package/dist/primitives/RadioGroup/radioGroup.variants.js +68 -9
  67. package/dist/primitives/Select/select.variants.js +13 -4
  68. package/dist/primitives/Slider/Slider.svelte +4 -1
  69. package/dist/primitives/Slider/slider.variants.js +9 -2
  70. package/dist/primitives/Spinner/spinner.variants.d.ts +16 -16
  71. package/dist/primitives/Stepper/stepper.variants.d.ts +11 -11
  72. package/dist/primitives/Textarea/textarea.variants.js +15 -1
  73. package/dist/primitives/Toast/Toaster.svelte +6 -9
  74. package/dist/primitives/Toast/toast.variants.d.ts +12 -12
  75. package/dist/primitives/Toast/toast.variants.js +5 -1
  76. package/dist/primitives/Toggle/toggle.variants.js +92 -18
  77. package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
  78. package/dist/primitives/Tooltip/tooltip.variants.js +5 -1
  79. package/dist/style/themes/index.css +8 -2
  80. package/dist/utils/guide.svelte.js +16 -4
  81. package/dist/utils/persistent-state.svelte.d.ts +25 -1
  82. package/dist/utils/persistent-state.svelte.js +98 -10
  83. package/docs/MIGRATION-v5.md +29 -0
  84. package/package.json +3 -3
@@ -0,0 +1,406 @@
1
+ // A2UI stream splitter — the piece between a model's token stream and A2UIView.
2
+ //
3
+ // Pair it with `a2uiFencedTransportSection()` (below), which tells the agent to
4
+ // emit exactly the format this parses. The two are one contract: change the
5
+ // fence tag here and the prompt changes with it, so a consumer can never end up
6
+ // with a parser and a prompt that disagree.
7
+ //
8
+ // The agent writes ordinary Markdown text and emits UI as a fenced block:
9
+ //
10
+ // Here is your form:
11
+ // ```a2ui
12
+ // {"version":"v0.9.1","createSurface":{"surfaceId":"s1","catalogId":"…"}}
13
+ // {"version":"v0.9.1","updateComponents":{"surfaceId":"s1","components":[…]}}
14
+ // ```
15
+ // Anything else you'd like?
16
+ //
17
+ // This module turns a *token stream* of that text into an ordered list of
18
+ // message parts — text parts and `a2ui` parts — that ChatMessage renders in
19
+ // order (the `a2ui` part flowing into A2UIView). It is a small state machine:
20
+ //
21
+ // • text mode → append incoming text to the current text part.
22
+ // • a fenced line ` ```a2ui ` opens UI mode and starts a fresh a2ui part.
23
+ // • in UI mode, a COMPLETE line that parses as JSON is appended to the a2ui
24
+ // part's payload array (immutably: `[...prev, env]`, so A2UIView consumes
25
+ // it incrementally and keeps prior envelope object identity).
26
+ // • a line that does NOT parse but opens a JSON object (models like to
27
+ // pretty-print a large updateComponents across lines) starts a multi-line
28
+ // envelope buffer; a string-aware brace/bracket depth scanner decides when
29
+ // the object is complete, and only then is the whole buffer parsed.
30
+ // • a fenced line ` ``` ` closes UI mode, back to text.
31
+ // • a line/buffer that can never become valid JSON falls the rest of the
32
+ // fence back to text (re-emitted inside a synthetic ``` code block so the
33
+ // fence's REAL closing ``` closes that block in the rendered Markdown
34
+ // instead of opening a stray one) and records an issue.
35
+ //
36
+ // The splitter is CHUNK-DECOMPOSITION-INVARIANT: it buffers a partial trailing
37
+ // line and only ever acts on complete lines (plus a final flush at `end()`), so
38
+ // the committed result is identical no matter how the input was sliced into
39
+ // chunks — fence markers may straddle chunk boundaries, text may sit before /
40
+ // between / after fences, and the last line may be incomplete.
41
+ /**
42
+ * Parse a line as a CommonMark code-fence marker, or `null` if it is not one.
43
+ * A fence is ` ``` `+ or `~~~`+ with 0–3 leading spaces (≥4 = indented code); a
44
+ * backtick fence's info string may not contain a backtick.
45
+ */
46
+ function parseFence(line) {
47
+ const m = /^( {0,3})(`{3,}|~{3,})[ \t]*(.*?)[ \t]*$/.exec(line);
48
+ if (!m)
49
+ return null;
50
+ const char = m[2][0];
51
+ const info = m[3];
52
+ if (char === '`' && info.includes('`'))
53
+ return null;
54
+ return { char, len: m[2].length, info, indent: m[1].length };
55
+ }
56
+ /**
57
+ * The info string that marks a fence as A2UI. Shared by the parser and by
58
+ * `a2uiFencedTransportSection()` so the prompt can never name a different tag
59
+ * than the splitter accepts.
60
+ */
61
+ export const A2UI_FENCE_TAG = 'a2ui';
62
+ const FENCE = '```';
63
+ /** Our special opener: a column-0 backtick fence whose info string is exactly `a2ui`. */
64
+ function isA2uiOpen(f) {
65
+ return f !== null && f.char === '`' && f.indent === 0 && f.info === A2UI_FENCE_TAG;
66
+ }
67
+ /** A bare closing fence that terminates a fence opened with `open`. */
68
+ function isCloseOf(f, open) {
69
+ return f !== null && f.char === open.char && f.len >= open.len && f.info === '';
70
+ }
71
+ /** Could this partial (newline-free) line still grow into our ` ```a2ui ` fence marker? */
72
+ function couldStartFence(s) {
73
+ const t = s.replace(/^[ \t]+/, '');
74
+ if (t === '')
75
+ return false;
76
+ return `${FENCE}${A2UI_FENCE_TAG}`.startsWith(t) || t.startsWith(FENCE) || t.startsWith('~~~');
77
+ }
78
+ // ── Pure, immutable part-array transforms ───────────────────────────────────
79
+ // Each returns a NEW array; existing part objects (and, crucially, the parsed
80
+ // envelope objects inside a2ui payloads) are carried by reference.
81
+ function withAppendedText(parts, s) {
82
+ if (s === '')
83
+ return parts;
84
+ const last = parts[parts.length - 1];
85
+ if (last && last.type === 'text') {
86
+ return [...parts.slice(0, -1), { type: 'text', text: last.text + s }];
87
+ }
88
+ return [...parts, { type: 'text', text: s }];
89
+ }
90
+ function withStartedA2ui(parts) {
91
+ return [...parts, { type: 'a2ui', payload: [] }];
92
+ }
93
+ function withAppendedEnvelope(parts, env) {
94
+ const last = parts[parts.length - 1];
95
+ if (last?.type !== 'a2ui')
96
+ return parts;
97
+ return [...parts.slice(0, -1), { type: 'a2ui', payload: [...last.payload, env] }];
98
+ }
99
+ /** Drop a trailing a2ui part that never received an envelope (empty fence). */
100
+ function withoutEmptyTrailingA2ui(parts) {
101
+ const last = parts[parts.length - 1];
102
+ if (last && last.type === 'a2ui' && last.payload.length === 0)
103
+ return parts.slice(0, -1);
104
+ return parts;
105
+ }
106
+ export class A2uiStreamSplitter {
107
+ /** Committed parts (immutable; replaced wholesale on every mutation). */
108
+ committed = [];
109
+ mode = 'text';
110
+ /**
111
+ * When in text mode inside a *regular* (non-a2ui) markdown code fence, the
112
+ * marker that opened it. A `` ```a2ui `` line inside such a block is literal
113
+ * code, NOT a surface — this is what stops a quoted example from executing.
114
+ */
115
+ codeFence = null;
116
+ /** Buffered trailing line that has not yet seen its newline. */
117
+ pending = '';
118
+ rawText = '';
119
+ issueList = [];
120
+ /**
121
+ * Raw lines of an in-progress multi-line envelope (a pretty-printed object
122
+ * spanning several fence lines), plus the depth-scanner state that tracks
123
+ * whether the object has closed. Empty/zeroed while envelopes arrive as
124
+ * proper single-line JSONL.
125
+ */
126
+ objLines = [];
127
+ objDepth = 0;
128
+ objInString = false;
129
+ objEscape = false;
130
+ /** Feed the next token / chunk of model output. */
131
+ push(chunk) {
132
+ if (chunk === '')
133
+ return;
134
+ this.rawText += chunk;
135
+ this.pending += chunk;
136
+ for (;;) {
137
+ const nl = this.pending.indexOf('\n');
138
+ if (nl === -1)
139
+ break;
140
+ const line = this.pending.slice(0, nl);
141
+ this.pending = this.pending.slice(nl + 1);
142
+ this.processLine(line, true);
143
+ }
144
+ }
145
+ /** Signal end of stream: flush the trailing partial line and settle state. */
146
+ end() {
147
+ if (this.pending !== '') {
148
+ const line = this.pending;
149
+ this.pending = '';
150
+ this.processLine(line, false);
151
+ }
152
+ if (this.mode === 'fence') {
153
+ // Stream ended inside a fence — settle to text and drop an empty surface.
154
+ this.committed = withoutEmptyTrailingA2ui(this.committed);
155
+ this.mode = 'text';
156
+ // An envelope still buffered mid-object is re-emitted as text (inside a
157
+ // synthetic code block) so a truncated stream never swallows it silently.
158
+ if (this.objLines.length > 0) {
159
+ this.asText('```', true);
160
+ for (const buffered of this.objLines)
161
+ this.asText(buffered, true);
162
+ this.resetObjBuffer();
163
+ }
164
+ this.issueList.push({
165
+ code: 'UNTERMINATED_FENCE',
166
+ message: 'The a2ui fenced block was not closed before the stream ended.'
167
+ });
168
+ }
169
+ }
170
+ asText(line, terminated) {
171
+ this.committed = withAppendedText(this.committed, terminated ? `${line}\n` : line);
172
+ }
173
+ resetObjBuffer() {
174
+ this.objLines = [];
175
+ this.objDepth = 0;
176
+ this.objInString = false;
177
+ this.objEscape = false;
178
+ }
179
+ /**
180
+ * Advance the depth scanner across one line: brace/bracket depth outside of
181
+ * strings, with `\"`-escape handling inside them. Returns `false` when the
182
+ * buffer can never become valid JSON — depth underflow, or a string left
183
+ * open at end-of-line (raw newlines are illegal inside JSON strings).
184
+ */
185
+ scanDepth(line) {
186
+ for (const ch of line) {
187
+ if (this.objEscape) {
188
+ this.objEscape = false;
189
+ continue;
190
+ }
191
+ if (this.objInString) {
192
+ if (ch === '\\')
193
+ this.objEscape = true;
194
+ else if (ch === '"')
195
+ this.objInString = false;
196
+ continue;
197
+ }
198
+ if (ch === '"')
199
+ this.objInString = true;
200
+ else if (ch === '{' || ch === '[')
201
+ this.objDepth++;
202
+ else if (ch === '}' || ch === ']') {
203
+ this.objDepth--;
204
+ if (this.objDepth < 0)
205
+ return false;
206
+ }
207
+ }
208
+ return !this.objInString;
209
+ }
210
+ /**
211
+ * Abandon the current a2ui fence: drop an empty surface, record an issue and
212
+ * re-emit the buffered lines (plus the offending one, if any) as text inside
213
+ * a synthetic ``` code block. `codeFence` is armed so the fence's real
214
+ * closing ``` closes that block instead of opening a stray one downstream.
215
+ */
216
+ fenceFallback(message, badLine, terminated = true) {
217
+ this.issueList.push({
218
+ code: 'UNPARSEABLE_LINE',
219
+ message,
220
+ line: badLine ?? this.objLines.join('\n')
221
+ });
222
+ this.committed = withoutEmptyTrailingA2ui(this.committed);
223
+ this.mode = 'text';
224
+ this.codeFence = { char: '`', len: 3 };
225
+ this.asText('```', true);
226
+ const lines = badLine === undefined ? this.objLines : [...this.objLines, badLine];
227
+ for (let i = 0; i < lines.length; i++) {
228
+ this.asText(lines[i], i < lines.length - 1 ? true : terminated);
229
+ }
230
+ this.resetObjBuffer();
231
+ }
232
+ processLine(rawLine, terminated) {
233
+ // Normalize CRLF: a trailing \r would otherwise defeat fence detection
234
+ // (the marker regex ends at whitespace, not \r) and leak into JSONL lines.
235
+ const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
236
+ const fence = parseFence(line);
237
+ if (this.mode === 'fence') {
238
+ // Inside OUR a2ui block (opened by ```a2ui). Only a bare ``` closes it;
239
+ // every other line feeds the JSONL parser (with a multi-line object
240
+ // buffer for pretty-printed envelopes).
241
+ if (isCloseOf(fence, { char: '`', len: 3 })) {
242
+ if (this.objLines.length > 0) {
243
+ // The fence closed on a still-open envelope — fall it back to text,
244
+ // then re-process this ``` so it closes the synthetic code block.
245
+ this.fenceFallback('An a2ui envelope was still incomplete when its fence closed; falling back to text.');
246
+ this.processLine(rawLine, terminated);
247
+ return;
248
+ }
249
+ this.committed = withoutEmptyTrailingA2ui(this.committed);
250
+ this.mode = 'text';
251
+ return;
252
+ }
253
+ if (this.objLines.length > 0) {
254
+ // Continuation of a multi-line envelope.
255
+ const viable = this.scanDepth(line);
256
+ this.objLines.push(line);
257
+ if (!viable) {
258
+ this.fenceFallback('An a2ui envelope was not valid JSON; falling back to text.', undefined, terminated);
259
+ return;
260
+ }
261
+ if (this.objDepth === 0) {
262
+ const joined = this.objLines.join('\n');
263
+ try {
264
+ const env = JSON.parse(joined);
265
+ this.committed = withAppendedEnvelope(this.committed, env);
266
+ this.resetObjBuffer();
267
+ }
268
+ catch {
269
+ this.fenceFallback('An a2ui envelope was not valid JSON; falling back to text.', undefined, terminated);
270
+ }
271
+ }
272
+ return;
273
+ }
274
+ if (line.trim() === '')
275
+ return; // ignore blank lines between envelopes
276
+ try {
277
+ const env = JSON.parse(line);
278
+ this.committed = withAppendedEnvelope(this.committed, env);
279
+ }
280
+ catch {
281
+ // Not a complete envelope. If the line opens a JSON object, buffer it
282
+ // as the start of a multi-line envelope; anything else is garbage and
283
+ // falls the rest of the fence back to text.
284
+ if (line.trimStart().startsWith('{')) {
285
+ const viable = this.scanDepth(line);
286
+ this.objLines.push(line);
287
+ if (!viable || this.objDepth === 0) {
288
+ // Underflow, an unterminated string, or a braces-balanced line
289
+ // that still failed to parse — it can never become valid JSON.
290
+ this.fenceFallback('An a2ui line was not valid JSON; falling back to text.', undefined, terminated);
291
+ }
292
+ return;
293
+ }
294
+ this.fenceFallback('An a2ui line was not valid JSON; falling back to text.', line, terminated);
295
+ }
296
+ return;
297
+ }
298
+ // text mode, inside a regular (non-a2ui) code block: everything is verbatim
299
+ // text — a ```a2ui here is quoted code, never a surface — until the matching
300
+ // closing fence, which is itself emitted as text.
301
+ if (this.codeFence) {
302
+ if (isCloseOf(fence, this.codeFence))
303
+ this.codeFence = null;
304
+ this.asText(line, terminated);
305
+ return;
306
+ }
307
+ // plain text mode.
308
+ if (isA2uiOpen(fence)) {
309
+ this.mode = 'fence';
310
+ this.committed = withStartedA2ui(this.committed);
311
+ return;
312
+ }
313
+ if (fence) {
314
+ // A regular code fence opens: track it so a nested ```a2ui stays literal.
315
+ this.codeFence = { char: fence.char, len: fence.len };
316
+ }
317
+ this.asText(line, terminated);
318
+ }
319
+ /**
320
+ * The current parts, including the buffered trailing text tail for a
321
+ * responsive stream. A partial line that might still become a fence marker is
322
+ * held back (never flashed). a2ui parts are carried by reference so A2UIView
323
+ * consumes them incrementally.
324
+ */
325
+ snapshot() {
326
+ if (this.mode === 'text' && this.pending !== '' && !couldStartFence(this.pending)) {
327
+ return withAppendedText(this.committed, this.pending);
328
+ }
329
+ return this.committed;
330
+ }
331
+ /** The raw model output fed so far (fences included) — the faithful wire form. */
332
+ get raw() {
333
+ return this.rawText;
334
+ }
335
+ /** Accumulated non-fatal issues (malformed JSONL, unterminated fence). */
336
+ get issues() {
337
+ return this.issueList;
338
+ }
339
+ }
340
+ /**
341
+ * The transport half of the fenced-JSONL contract: the prompt section that makes
342
+ * an agent emit exactly what {@link A2uiStreamSplitter} parses.
343
+ *
344
+ * `a2uiSystemPrompt()` deliberately says nothing about transport, because how
345
+ * envelopes travel is the app's business. This is the transport for apps that
346
+ * take the model's ordinary text stream and let it open a ` ```a2ui ` fence —
347
+ * the setup the splitter exists for. Append it after `a2uiSystemPrompt()` (and
348
+ * after `a2uiDataSchemaSection()`, if you use one).
349
+ *
350
+ * Anything domain-specific — which tools to call, what never to invent — stays
351
+ * yours to append; this covers only the wire format and the return path.
352
+ *
353
+ * @example
354
+ * ```ts
355
+ * const system = [
356
+ * a2uiSystemPrompt({ catalog: urbiconA2uiCatalogSpec }),
357
+ * a2uiDataSchemaSection(MY_SCHEMA),
358
+ * a2uiFencedTransportSection(),
359
+ * MY_DOMAIN_RULES
360
+ * ].join('\n\n');
361
+ * ```
362
+ */
363
+ export function a2uiFencedTransportSection(options) {
364
+ const actionPrefix = options?.actionPrefix ?? '[ui-action]';
365
+ const errorPrefix = options?.errorPrefix ?? '[ui-error]';
366
+ const open = `${FENCE}${A2UI_FENCE_TAG}`;
367
+ const lines = [
368
+ '## Transport — how your UI reaches this client',
369
+ '',
370
+ 'Write normal Markdown prose. When (and only when) a form, a chooser, or a',
371
+ 'structured surface would genuinely help the user more than prose, emit the UI',
372
+ `as a fenced code block tagged ${open} containing A2UI envelopes as JSONL —`,
373
+ 'one complete JSON envelope per line, no blank lines, no trailing commentary',
374
+ 'inside the fence. Example:',
375
+ '',
376
+ open,
377
+ '{"version":"v0.9.1","createSurface":{"surfaceId":"form-1","catalogId":"…"}}',
378
+ '{"version":"v0.9.1","updateComponents":{"surfaceId":"form-1","components":[ … ]}}',
379
+ FENCE,
380
+ '',
381
+ 'Rules for the fence:',
382
+ `- Open the block with a line that is exactly ${open} and close it with ${FENCE}.`,
383
+ '- Emit createSurface FIRST, then the updateComponents / updateDataModel',
384
+ ' envelopes. Every envelope has "version":"v0.9.1".',
385
+ '- One envelope per line, as compact single-line JSON — never pretty-print an',
386
+ ' envelope across multiple lines (a large updateComponents stays on ONE line).',
387
+ '- You may put prose before and/or after the fence. Do not nest fences.',
388
+ '- Patching an EARLIER surface works the same way: open a fence and send the',
389
+ ' updateComponents / updateDataModel envelopes for its surfaceId (no',
390
+ ' createSurface). The client routes them to the message that shows that',
391
+ ' surface, so it updates where it already stands — say in prose what you',
392
+ ' changed, since it may be scrolled out of view.'
393
+ ];
394
+ if (actionPrefix || errorPrefix) {
395
+ lines.push('', '## Interaction round-channel', '');
396
+ }
397
+ if (actionPrefix) {
398
+ lines.push('When the user interacts with a surface you sent, the client sends you a new', `user turn whose text begins with \`${actionPrefix} \` followed by a compact JSON`, 'object: { name, surfaceId, sourceComponentId, timestamp, context }, plus', "`dataModel` (the surface's full state) when you created it with", '"sendDataModel": true. Treat it as the user activating a control on your', 'surface — usually the right answer is to patch THAT surface, not to send a new', 'one.');
399
+ }
400
+ if (errorPrefix) {
401
+ if (actionPrefix)
402
+ lines.push('');
403
+ lines.push('If a surface you sent failed validation, the next user turn is prefixed with a', `\`${errorPrefix} \` line carrying the validation issues as JSON. Read it, correct`, 'the offending envelopes, and re-emit a valid surface.');
404
+ }
405
+ return lines.join('\n');
406
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Transcript-side wiring for {@link A2uiSurfaceRouter}.
3
+ *
4
+ * The router decides WHERE an envelope belongs; these functions carry it there.
5
+ * They are pure over `ChatMessageData[]` — every one returns a new message list
6
+ * and never touches component state — so the whole cross-message patch flow is
7
+ * testable without mounting the chat.
8
+ *
9
+ * Addressing: a source key is `` `${messageId}#${n}` ``, where `n` counts the
10
+ * a2ui parts of that message in render order. Message ids may contain `#`, so
11
+ * the key is always split at the LAST one.
12
+ */
13
+ import type { ChatMessageData, ChatMessagePart } from '../chat.types.js';
14
+ import type { A2uiValidationIssue } from './a2ui.types.js';
15
+ import type { A2uiSurfaceRouter } from './a2ui-router.js';
16
+ /** A surface that a later message patched, and the message it lives in. */
17
+ export interface PatchedSurface {
18
+ surfaceId: string;
19
+ targetMessageId: string;
20
+ }
21
+ export interface RouteMessageResult {
22
+ /** The transcript with every delivered/withdrawn patch applied. */
23
+ messages: ChatMessageData[];
24
+ /** The routed message's own parts — foreign envelopes removed. */
25
+ parts: ChatMessagePart[];
26
+ /** Router findings (e.g. a re-created surfaceId) for the agent's error channel. */
27
+ issues: A2uiValidationIssue[];
28
+ /** Surfaces this call promoted to long-lived, with their home message. */
29
+ promoted: PatchedSurface[];
30
+ /**
31
+ * Ids of the messages that received envelopes in this call — every target,
32
+ * not just newly promoted ones.
33
+ *
34
+ * A patched message must render as if it were streaming for as long as the
35
+ * patch is still arriving: envelopes land one by one, and a container that
36
+ * names children from the NEXT envelope is normal mid-stream (the agent is
37
+ * even told to send containers first). Without that grace the settled message
38
+ * flags each intermediate state as a dangling reference, the user watches
39
+ * error chips appear and vanish, and the agent gets told to repair UI that was
40
+ * never broken.
41
+ */
42
+ targets: string[];
43
+ }
44
+ export declare const sourceKey: (messageId: string, partIndex: number) => string;
45
+ export declare const messageIdOf: (key: string) => string;
46
+ /** Rewrite the payload of the one a2ui part a source key addresses. */
47
+ export declare function editA2uiPayload(messages: ChatMessageData[], key: string, edit: (payload: unknown[]) => unknown[]): ChatMessageData[];
48
+ /**
49
+ * Route one message's a2ui parts: keep what belongs to it, deliver the rest to
50
+ * the payloads that own those surfaces.
51
+ *
52
+ * A part whose envelopes ALL travelled elsewhere is dropped — it would render
53
+ * as an empty surface. A part that is merely still empty (fence open, no
54
+ * complete envelope yet) is kept, so the streaming placeholder survives.
55
+ */
56
+ export declare function routeMessageParts(router: A2uiSurfaceRouter, messages: ChatMessageData[], messageId: string, parts: ChatMessagePart[]): RouteMessageResult;
57
+ /**
58
+ * Drop a message from the router's books and take back everything it had
59
+ * patched into other messages — for regenerate, retry and deletion.
60
+ *
61
+ * The sources come from the router, not from counting the message's parts: a
62
+ * message whose envelopes all travelled elsewhere keeps no a2ui part, and its
63
+ * patches would otherwise outlive it.
64
+ */
65
+ export declare function revokeMessage(router: A2uiSurfaceRouter, messages: ChatMessageData[], messageId: string): ChatMessageData[];
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Transcript-side wiring for {@link A2uiSurfaceRouter}.
3
+ *
4
+ * The router decides WHERE an envelope belongs; these functions carry it there.
5
+ * They are pure over `ChatMessageData[]` — every one returns a new message list
6
+ * and never touches component state — so the whole cross-message patch flow is
7
+ * testable without mounting the chat.
8
+ *
9
+ * Addressing: a source key is `` `${messageId}#${n}` ``, where `n` counts the
10
+ * a2ui parts of that message in render order. Message ids may contain `#`, so
11
+ * the key is always split at the LAST one.
12
+ */
13
+ export const sourceKey = (messageId, partIndex) => `${messageId}#${partIndex}`;
14
+ export const messageIdOf = (key) => key.slice(0, key.lastIndexOf('#'));
15
+ const partIndexOf = (key) => Number(key.slice(key.lastIndexOf('#') + 1));
16
+ /** Rewrite the payload of the one a2ui part a source key addresses. */
17
+ export function editA2uiPayload(messages, key, edit) {
18
+ const messageId = messageIdOf(key);
19
+ const partIndex = partIndexOf(key);
20
+ return messages.map((message) => {
21
+ if (message.id !== messageId)
22
+ return message;
23
+ let seen = -1;
24
+ return {
25
+ ...message,
26
+ parts: message.parts.map((part) => {
27
+ if (part.type !== 'a2ui')
28
+ return part;
29
+ seen += 1;
30
+ if (seen !== partIndex)
31
+ return part;
32
+ return { ...part, payload: edit(part.payload ?? []) };
33
+ })
34
+ };
35
+ });
36
+ }
37
+ const deliver = (messages, patch) => editA2uiPayload(messages, patch.targetKey, (payload) => [...payload, ...patch.envelopes]);
38
+ // Removal is by reference: the router hands back the very objects it delivered,
39
+ // so a withdrawn patch leaves the surviving envelopes (and their order) alone.
40
+ const withdraw = (messages, patch) => editA2uiPayload(messages, patch.targetKey, (payload) => payload.filter((envelope) => !patch.envelopes.includes(envelope)));
41
+ /**
42
+ * Route one message's a2ui parts: keep what belongs to it, deliver the rest to
43
+ * the payloads that own those surfaces.
44
+ *
45
+ * A part whose envelopes ALL travelled elsewhere is dropped — it would render
46
+ * as an empty surface. A part that is merely still empty (fence open, no
47
+ * complete envelope yet) is kept, so the streaming placeholder survives.
48
+ */
49
+ export function routeMessageParts(router, messages, messageId, parts) {
50
+ let next = messages;
51
+ let partIndex = -1;
52
+ const issues = [];
53
+ const promoted = [];
54
+ const targets = new Set();
55
+ const routed = [];
56
+ for (const part of parts) {
57
+ if (part.type !== 'a2ui') {
58
+ routed.push(part);
59
+ continue;
60
+ }
61
+ partIndex += 1;
62
+ const payload = part.payload ?? [];
63
+ const result = router.route(sourceKey(messageId, partIndex), payload);
64
+ for (const patch of result.revoked)
65
+ next = withdraw(next, patch);
66
+ for (const patch of result.patches) {
67
+ next = deliver(next, patch);
68
+ targets.add(messageIdOf(patch.targetKey));
69
+ }
70
+ issues.push(...result.issues);
71
+ // Every patch in this call went to a target; a promoted surface is hosted by
72
+ // the target its own envelopes travelled to.
73
+ for (const surfaceId of result.promoted) {
74
+ const owner = router.ownerOf(surfaceId);
75
+ if (owner)
76
+ promoted.push({ surfaceId, targetMessageId: messageIdOf(owner) });
77
+ }
78
+ if (payload.length > 0 && result.own.length === 0)
79
+ continue;
80
+ routed.push({ ...part, payload: result.own });
81
+ }
82
+ return { messages: next, parts: routed, issues, promoted, targets: [...targets] };
83
+ }
84
+ /**
85
+ * Drop a message from the router's books and take back everything it had
86
+ * patched into other messages — for regenerate, retry and deletion.
87
+ *
88
+ * The sources come from the router, not from counting the message's parts: a
89
+ * message whose envelopes all travelled elsewhere keeps no a2ui part, and its
90
+ * patches would otherwise outlive it.
91
+ */
92
+ export function revokeMessage(router, messages, messageId) {
93
+ let next = messages;
94
+ for (const key of router.sourceKeys()) {
95
+ if (messageIdOf(key) !== messageId)
96
+ continue;
97
+ for (const patch of router.revoke(key))
98
+ next = withdraw(next, patch);
99
+ }
100
+ return next;
101
+ }
@@ -21,6 +21,8 @@
21
21
  * because "dangling" is a warning mid-stream and an error once settled.
22
22
  */
23
23
  import { type A2uiValidationIssue } from './a2ui.types.js';
24
+ import { type A2uiCatalogSpec } from './a2ui-catalog.js';
25
+ import { type A2uiDataSchema } from './a2ui-schema.js';
24
26
  export interface A2uiComponentInstance {
25
27
  id: string;
26
28
  component: string;
@@ -35,6 +37,19 @@ export interface A2uiSurfaceState {
35
37
  /** Root of the surface data model (mutated in place by two-way edits). */
36
38
  dataModel: unknown;
37
39
  issues: A2uiValidationIssue[];
40
+ /**
41
+ * The catalog resolved from `createSurface.catalogId` (or the default when the
42
+ * id names no configured catalog). Every downstream check — registry lookup,
43
+ * icon set, flex containers, per-component checks — reads it, so surfaces on
44
+ * different catalogs validate and render independently.
45
+ */
46
+ catalog: A2uiCatalogSpec;
47
+ /**
48
+ * `createSurface.sendDataModel` — when true, every action dispatched from this
49
+ * surface carries the full data model (see `A2uiActionEvent.dataModel`), so
50
+ * the agent sees the user's input even for fields it left out of `context`.
51
+ */
52
+ sendDataModel: boolean;
38
53
  }
39
54
  export interface A2uiProcessor {
40
55
  surfaces: Map<string, A2uiSurfaceState>;
@@ -42,7 +57,24 @@ export interface A2uiProcessor {
42
57
  /** Validate + apply one envelope. `index` positions it as `/messages/<index>` in issue paths. */
43
58
  apply(envelope: unknown, index: number): void;
44
59
  }
45
- export declare function createA2uiProcessor(): A2uiProcessor;
60
+ /** Options for {@link createA2uiProcessor}. Omitting them yields the Basic-only default. */
61
+ export interface A2uiProcessorOptions {
62
+ /**
63
+ * The catalogs this processor understands, in priority order. `catalogs[0]`
64
+ * is the default/fallback. Defaults to `[basicA2uiCatalogSpec]` — a
65
+ * single-catalog processor accepts any `catalogId` string silently
66
+ * (back-compat); an unknown id only warns once there are ≥ 2 catalogs.
67
+ */
68
+ catalogs?: readonly A2uiCatalogSpec[];
69
+ /**
70
+ * Optional data schema. When set, every `updateDataModel` write is validated
71
+ * against it — a type mismatch on a declared pointer is a `SCHEMA_TYPE_MISMATCH`
72
+ * error, a write to an undeclared top-level branch a `SCHEMA_UNDECLARED_PATH`
73
+ * warning. Omitting it disables schema validation entirely (back-compat).
74
+ */
75
+ dataSchema?: A2uiDataSchema;
76
+ }
77
+ export declare function createA2uiProcessor(options?: A2uiProcessorOptions): A2uiProcessor;
46
78
  /**
47
79
  * Normalize a render payload into an envelope list. Accepts an envelope array,
48
80
  * a single envelope, or the golden-file `{ messages: [...] }` wrapper. Returns