@urbicon-ui/blocks 6.40.4 → 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 (72) hide show
  1. package/dist/components/Chat/A2UIView/A2UINode.svelte +20 -39
  2. package/dist/components/Chat/A2UIView/A2UIView.svelte +47 -50
  3. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.d.ts +17 -0
  4. package/dist/components/Chat/A2UIView/a2ui-basic-catalog.js +69 -0
  5. package/dist/components/Chat/A2UIView/a2ui-catalog.d.ts +95 -0
  6. package/dist/components/Chat/A2UIView/a2ui-catalog.js +87 -0
  7. package/dist/components/Chat/A2UIView/a2ui-node-common.d.ts +40 -0
  8. package/dist/components/Chat/A2UIView/a2ui-node-common.js +72 -0
  9. package/dist/components/Chat/A2UIView/a2ui-prompt.d.ts +21 -10
  10. package/dist/components/Chat/A2UIView/a2ui-prompt.js +170 -24
  11. package/dist/components/Chat/A2UIView/a2ui-registry.d.ts +1 -1
  12. package/dist/components/Chat/A2UIView/a2ui-registry.js +2 -1
  13. package/dist/components/Chat/A2UIView/a2ui-render.d.ts +30 -0
  14. package/dist/components/Chat/A2UIView/a2ui-render.js +61 -23
  15. package/dist/components/Chat/A2UIView/a2ui-router.d.ts +136 -0
  16. package/dist/components/Chat/A2UIView/a2ui-router.js +264 -0
  17. package/dist/components/Chat/A2UIView/a2ui-schema.d.ts +45 -0
  18. package/dist/components/Chat/A2UIView/a2ui-schema.js +133 -0
  19. package/dist/components/Chat/A2UIView/a2ui-stream.d.ts +120 -0
  20. package/dist/components/Chat/A2UIView/a2ui-stream.js +406 -0
  21. package/dist/components/Chat/A2UIView/a2ui-transcript.d.ts +65 -0
  22. package/dist/components/Chat/A2UIView/a2ui-transcript.js +101 -0
  23. package/dist/components/Chat/A2UIView/a2ui-validate.d.ts +33 -1
  24. package/dist/components/Chat/A2UIView/a2ui-validate.js +167 -63
  25. package/dist/components/Chat/A2UIView/a2ui.types.d.ts +50 -0
  26. package/dist/components/Chat/A2UIView/a2ui.types.js +38 -1
  27. package/dist/components/Chat/A2UIView/index.d.ts +35 -2
  28. package/dist/components/Chat/A2UIView/index.js +13 -0
  29. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte +831 -0
  30. package/dist/components/Chat/A2UIView/urbicon/UrbiconA2UINode.svelte.d.ts +4 -0
  31. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.d.ts +14 -0
  32. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-catalog.js +87 -0
  33. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.d.ts +78 -0
  34. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon-registry.js +783 -0
  35. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.d.ts +43 -0
  36. package/dist/components/Chat/A2UIView/urbicon/a2ui-urbicon.variants.js +33 -0
  37. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.d.ts +7 -0
  38. package/dist/components/Chat/A2UIView/urbicon/urbicon-axes.gen.js +86 -0
  39. package/dist/components/Chat/ChatMessage/chat-message.variants.d.ts +19 -19
  40. package/dist/components/FileUpload/fileUpload.variants.js +15 -4
  41. package/dist/components/PinInput/PinInput.svelte +1 -0
  42. package/dist/components/PinInput/pin-input.variants.js +14 -1
  43. package/dist/components/TimeInput/time-input.variants.js +16 -1
  44. package/dist/internal/field-chrome.d.ts +18 -1
  45. package/dist/internal/field-chrome.js +18 -1
  46. package/dist/primitives/Avatar/avatar.variants.js +4 -1
  47. package/dist/primitives/Badge/badge.variants.js +11 -2
  48. package/dist/primitives/Button/button.variants.js +4 -1
  49. package/dist/primitives/Card/card.variants.js +4 -1
  50. package/dist/primitives/Checkbox/checkbox.variants.js +7 -1
  51. package/dist/primitives/Combobox/Combobox.svelte +9 -1
  52. package/dist/primitives/Combobox/combobox.variants.d.ts +25 -0
  53. package/dist/primitives/Combobox/combobox.variants.js +32 -3
  54. package/dist/primitives/Combobox/index.d.ts +8 -2
  55. package/dist/primitives/Dialog/dialog.variants.js +4 -1
  56. package/dist/primitives/Drawer/drawer.variants.js +4 -1
  57. package/dist/primitives/Input/input.variants.js +17 -1
  58. package/dist/primitives/Menu/menu.variants.js +5 -1
  59. package/dist/primitives/Progress/progress.variants.js +5 -2
  60. package/dist/primitives/RadioGroup/radioGroup.variants.js +68 -9
  61. package/dist/primitives/Select/select.variants.js +13 -4
  62. package/dist/primitives/Slider/Slider.svelte +4 -1
  63. package/dist/primitives/Slider/slider.variants.js +9 -2
  64. package/dist/primitives/Textarea/textarea.variants.js +15 -1
  65. package/dist/primitives/Toast/toast.variants.js +5 -1
  66. package/dist/primitives/Toggle/toggle.variants.js +92 -18
  67. package/dist/primitives/Tooltip/tooltip.variants.d.ts +3 -3
  68. package/dist/primitives/Tooltip/tooltip.variants.js +5 -1
  69. package/dist/utils/persistent-state.svelte.d.ts +25 -1
  70. package/dist/utils/persistent-state.svelte.js +98 -10
  71. package/docs/MIGRATION-v5.md +29 -0
  72. package/package.json +3 -3
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Surface routing across message boundaries.
3
+ *
4
+ * `A2UIView` is payload-driven: one view renders one envelope array, and its
5
+ * processor lives with that view. In a chat that means one view per assistant
6
+ * message — so a surface dies with the message that created it. The A2UI spec
7
+ * says the opposite: a `surfaceId` is "globally unique for the renderer's
8
+ * lifetime", `deleteSurface` is the only way to retire one, and
9
+ * `updateComponents`/`updateDataModel` are patches an agent may send at any
10
+ * later point. A model that patches a form it built two turns ago is reading
11
+ * the protocol correctly; the renderer just had nowhere to put the envelope.
12
+ *
13
+ * This router closes that gap WITHOUT giving the engine a second state
14
+ * lifetime. It is a pure bookkeeping layer over the transcript: it remembers
15
+ * which source (message + a2ui part) owns which surface, and splits each
16
+ * source's envelopes into the ones that stay put and the ones that belong to an
17
+ * earlier source's payload. The consumer appends those to that payload
18
+ * (`[...payload, ...patch]`, keeping element identity) and `A2UIView` consumes
19
+ * them incrementally — two-way edits the user already made survive, because the
20
+ * view never rebuilds.
21
+ *
22
+ * The transcript stays the single source of truth: every envelope still lives
23
+ * in exactly one payload, so undo (`revoke`) and replay are plain array work.
24
+ *
25
+ * Zero-dependency and Svelte-free — usable from a server-side transcript
26
+ * reducer, not just from the browser.
27
+ */
28
+ import { A2UI_ISSUE_CODES } from './a2ui.types.js';
29
+ /** The four operations an envelope may carry, in the order the spec lists them. */
30
+ const OP_KEYS = ['createSurface', 'updateComponents', 'updateDataModel', 'deleteSurface'];
31
+ function isPlainObject(value) {
32
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
33
+ }
34
+ /**
35
+ * Read `(operation, surfaceId)` off an envelope — tolerantly, and without
36
+ * duplicating the validator. Anything that is not exactly one known operation
37
+ * with a non-empty string `surfaceId` returns `undefined`: the router then
38
+ * leaves the envelope where it is and the processor reports the real fault.
39
+ */
40
+ function readTarget(envelope) {
41
+ if (!isPlainObject(envelope))
42
+ return null;
43
+ const ops = OP_KEYS.filter((key) => envelope[key] !== undefined);
44
+ if (ops.length !== 1)
45
+ return null;
46
+ const op = ops[0];
47
+ const body = envelope[op];
48
+ if (!isPlainObject(body))
49
+ return null;
50
+ const surfaceId = body.surfaceId;
51
+ if (typeof surfaceId !== 'string' || surfaceId === '')
52
+ return null;
53
+ return { op, surfaceId };
54
+ }
55
+ /**
56
+ * Tracks surface ownership across sources and routes envelopes to the payload
57
+ * that owns their surface.
58
+ *
59
+ * A "source" is one A2UI payload in the transcript — in a chat app, one `a2ui`
60
+ * part of one message. The key is the consumer's to choose; it only has to be
61
+ * stable for that payload's lifetime (e.g. `` `${messageId}#${partIndex}` ``).
62
+ *
63
+ * Ownership rules:
64
+ * - `createSurface` for an unknown id → the calling source owns it.
65
+ * - `createSurface` for an id owned by ANOTHER source → ownership transfers to
66
+ * the caller (last writer wins, so the new surface actually renders) and a
67
+ * `SURFACE_RECREATED` issue is raised for the agent.
68
+ * - Any operation on a surface owned by another source → routed to that owner.
69
+ * - Any operation on an UNKNOWN surface stays with the caller, so the
70
+ * processor's `NO_SURFACE` error surfaces instead of vanishing here.
71
+ * - `deleteSurface` routes to the owner and then frees the id for re-use.
72
+ *
73
+ * @example One assistant turn, wired into the transcript
74
+ * ```ts
75
+ * const result = router.route(`${messageId}#0`, part.payload);
76
+ * part.payload = result.own;
77
+ * for (const patch of result.patches) appendToPayload(patch.targetKey, patch.envelopes);
78
+ * for (const surfaceId of result.promoted) markLongLived(surfaceId);
79
+ * ```
80
+ */
81
+ export class A2uiSurfaceRouter {
82
+ /** surfaceId → owning sourceKey. */
83
+ #owners = new Map();
84
+ #sources = new Map();
85
+ /**
86
+ * surfaceId → the sources that patched it from the outside. Counting the
87
+ * patchers (rather than flagging a boolean) keeps the promotion signal honest
88
+ * when a turn is regenerated: take the last outside patch away and the
89
+ * surface stops being long-lived, so a later patch promotes it afresh.
90
+ */
91
+ #patchedBy = new Map();
92
+ /**
93
+ * Detach a source: release the surfaces it owns, undo its share of every
94
+ * promotion, and report the envelopes it had pushed elsewhere.
95
+ */
96
+ #detach(sourceKey, source) {
97
+ for (const [surfaceId, owner] of this.#owners) {
98
+ if (owner === sourceKey) {
99
+ this.#owners.delete(surfaceId);
100
+ this.#patchedBy.delete(surfaceId);
101
+ }
102
+ }
103
+ for (const surfaceId of source.patched) {
104
+ const patchers = this.#patchedBy.get(surfaceId);
105
+ if (!patchers)
106
+ continue;
107
+ patchers.delete(sourceKey);
108
+ if (patchers.size === 0)
109
+ this.#patchedBy.delete(surfaceId);
110
+ }
111
+ const patches = [];
112
+ for (const [targetKey, envelopes] of source.sent) {
113
+ if (envelopes.length > 0)
114
+ patches.push({ targetKey, envelopes });
115
+ }
116
+ return patches;
117
+ }
118
+ /**
119
+ * Route one source's envelope list.
120
+ *
121
+ * Call it as often as you like — on every streamed token, if that is when the
122
+ * payload grows. Only envelopes appended since the last call are examined; an
123
+ * unchanged prefix is never re-routed, so patches are never duplicated. If
124
+ * the prefix DID change (a regenerated turn), the source is rebuilt from
125
+ * scratch and its previous patches come back in `revoked`.
126
+ *
127
+ * @param sourceKey Stable id of the payload being routed.
128
+ * @param envelopes The payload's accumulated envelopes, in stream order.
129
+ */
130
+ route(sourceKey, envelopes) {
131
+ const previous = this.#sources.get(sourceKey);
132
+ const isAppend = previous !== undefined &&
133
+ envelopes.length >= previous.seen.length &&
134
+ previous.seen.every((envelope, index) => envelope === envelopes[index]);
135
+ const revoked = [];
136
+ let source;
137
+ let startIndex;
138
+ if (isAppend) {
139
+ source = previous;
140
+ startIndex = previous.seen.length;
141
+ }
142
+ else {
143
+ // Rebuild: hand back whatever this source had pushed into other payloads
144
+ // and drop the surfaces it owned, so re-routing re-establishes them.
145
+ if (previous)
146
+ revoked.push(...this.#detach(sourceKey, previous));
147
+ source = { seen: [], own: [], sent: new Map(), patched: new Set() };
148
+ this.#sources.set(sourceKey, source);
149
+ startIndex = 0;
150
+ }
151
+ const patches = new Map();
152
+ const promoted = [];
153
+ const issues = [];
154
+ for (let index = startIndex; index < envelopes.length; index++) {
155
+ const envelope = envelopes[index];
156
+ source.seen.push(envelope);
157
+ const target = readTarget(envelope);
158
+ if (!target) {
159
+ source.own.push(envelope);
160
+ continue;
161
+ }
162
+ const { op, surfaceId } = target;
163
+ const owner = this.#owners.get(surfaceId);
164
+ if (op === 'createSurface') {
165
+ if (owner !== undefined && owner !== sourceKey) {
166
+ issues.push({
167
+ severity: 'warning',
168
+ code: A2UI_ISSUE_CODES.SURFACE_RECREATED,
169
+ surfaceId,
170
+ message: `Surface "${surfaceId}" was already created earlier in this conversation. ` +
171
+ "A surfaceId must stay unique for the renderer's lifetime — to change an " +
172
+ 'existing surface send updateComponents/updateDataModel for it instead of ' +
173
+ 'createSurface, or pick a new id.'
174
+ });
175
+ }
176
+ // Last writer wins either way: the newest source owns the id, so its
177
+ // surface renders and later patches follow it. A recreated surface is
178
+ // a fresh one — its promotion history goes with the old incarnation.
179
+ this.#owners.set(surfaceId, sourceKey);
180
+ this.#patchedBy.delete(surfaceId);
181
+ source.own.push(envelope);
182
+ continue;
183
+ }
184
+ // An unknown surface stays here on purpose — fail loud through the
185
+ // processor rather than silently dropping the envelope.
186
+ if (owner === undefined || owner === sourceKey) {
187
+ source.own.push(envelope);
188
+ if (op === 'deleteSurface' && owner === sourceKey)
189
+ this.#owners.delete(surfaceId);
190
+ continue;
191
+ }
192
+ // The surface lives in an earlier payload — send the patch there.
193
+ const bucket = patches.get(owner) ?? [];
194
+ bucket.push(envelope);
195
+ patches.set(owner, bucket);
196
+ const sent = source.sent.get(owner) ?? [];
197
+ sent.push(envelope);
198
+ source.sent.set(owner, sent);
199
+ const patchers = this.#patchedBy.get(surfaceId) ?? new Set();
200
+ const wasLongLived = patchers.size > 0;
201
+ patchers.add(sourceKey);
202
+ this.#patchedBy.set(surfaceId, patchers);
203
+ source.patched.add(surfaceId);
204
+ if (!wasLongLived)
205
+ promoted.push(surfaceId);
206
+ if (op === 'deleteSurface') {
207
+ this.#owners.delete(surfaceId);
208
+ this.#patchedBy.delete(surfaceId);
209
+ }
210
+ }
211
+ return {
212
+ own: source.own.slice(),
213
+ patches: [...patches].map(([targetKey, list]) => ({ targetKey, envelopes: list })),
214
+ revoked,
215
+ promoted,
216
+ issues
217
+ };
218
+ }
219
+ /**
220
+ * Forget a source entirely — call it when a message leaves the transcript
221
+ * (regenerate, delete, branch switch). The returned patches are the envelopes
222
+ * this source had pushed into other payloads; remove them by reference so no
223
+ * orphaned patch survives its author.
224
+ */
225
+ revoke(sourceKey) {
226
+ const source = this.#sources.get(sourceKey);
227
+ if (!source)
228
+ return [];
229
+ this.#sources.delete(sourceKey);
230
+ return this.#detach(sourceKey, source);
231
+ }
232
+ /** The source whose payload currently holds `surfaceId`, if any. */
233
+ ownerOf(surfaceId) {
234
+ return this.#owners.get(surfaceId);
235
+ }
236
+ /**
237
+ * Whether a surface has been patched from a later source — the promotion
238
+ * predicate, for clients that need to ask after the fact rather than react to
239
+ * {@link A2uiRouteResult.promoted}.
240
+ */
241
+ isLongLived(surfaceId) {
242
+ return (this.#patchedBy.get(surfaceId)?.size ?? 0) > 0;
243
+ }
244
+ /** Every surface currently owned by a source, in creation order. */
245
+ surfaceIds() {
246
+ return [...this.#owners.keys()];
247
+ }
248
+ /**
249
+ * Every source key routed so far, in first-seen order.
250
+ *
251
+ * Needed to retire a source whose payload no longer shows it: a message whose
252
+ * envelopes ALL travelled to earlier surfaces keeps no a2ui part of its own,
253
+ * so the caller cannot reconstruct its keys by counting parts.
254
+ */
255
+ sourceKeys() {
256
+ return [...this.#sources.keys()];
257
+ }
258
+ /** Drop all bookkeeping (new conversation). */
259
+ reset() {
260
+ this.#owners.clear();
261
+ this.#sources.clear();
262
+ this.#patchedBy.clear();
263
+ }
264
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Optional per-surface DATA schema for the A2UI engine — the data-model side of
3
+ * the registry's prop typing (findings §4.3). A schema declares the shape of the
4
+ * fields a surface's data model may hold (JSON Pointer → type + optional
5
+ * enum/format/description). It does two things:
6
+ * - documents the declared fields in the system prompt (`a2uiDataSchemaSection`),
7
+ * so the agent knows which paths exist and what they hold;
8
+ * - validates `updateDataModel` writes against those declarations
9
+ * (`validateSchemaWrite`), turning a type mismatch on a declared pointer into a
10
+ * `SCHEMA_TYPE_MISMATCH` error and a write to an undeclared top-level branch
11
+ * into a `SCHEMA_UNDECLARED_PATH` warning — both relayed through the same
12
+ * `onValidationError` feedback loop the agent already consumes.
13
+ *
14
+ * Pure TS, no Svelte. Deliberately MINIMAL (v1): exact-pointer type/enum checks
15
+ * and a top-level "did you mean to write here?" warning. Deferred: required
16
+ * fields, nested object schemas, cross-field constraints, and transporting the
17
+ * schema inside the envelope.
18
+ */
19
+ import { type A2uiValidationIssue } from './a2ui.types.js';
20
+ /** The JSON primitive/shape a declared field holds. */
21
+ export type A2uiSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
22
+ /** One declared field: its type plus optional enum/format/description for the prompt. */
23
+ export interface A2uiSchemaField {
24
+ type: A2uiSchemaType;
25
+ /** Agent-facing description; emitted into the prompt verbatim. */
26
+ description?: string;
27
+ /** Allowed literal values (documented + enforced for string/number fields). */
28
+ enum?: readonly (string | number)[];
29
+ /** A format hint (e.g. `date`, `time`, `email`) — documented, not enforced in v1. */
30
+ format?: string;
31
+ }
32
+ /** Surface data schema: absolute JSON Pointer → field declaration. */
33
+ export type A2uiDataSchema = Readonly<Record<string, A2uiSchemaField>>;
34
+ /**
35
+ * Validate one `updateDataModel` write against the schema. `pointer` is the
36
+ * write target (`''`/`'/'`/`undefined` = whole model); `value` is the written
37
+ * value (`undefined` for a delete — never flagged). Returns any schema issues.
38
+ */
39
+ export declare function validateSchemaWrite(schema: A2uiDataSchema, pointer: string | undefined, value: unknown, surfaceId?: string): A2uiValidationIssue[];
40
+ /**
41
+ * Render the schema as a prompt section listing every declared field, its type
42
+ * and any enum/format/description. Appended by the app after `a2uiSystemPrompt`
43
+ * (like the transport section), NOT baked into the generator.
44
+ */
45
+ export declare function a2uiDataSchemaSection(schema: A2uiDataSchema): string;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Optional per-surface DATA schema for the A2UI engine — the data-model side of
3
+ * the registry's prop typing (findings §4.3). A schema declares the shape of the
4
+ * fields a surface's data model may hold (JSON Pointer → type + optional
5
+ * enum/format/description). It does two things:
6
+ * - documents the declared fields in the system prompt (`a2uiDataSchemaSection`),
7
+ * so the agent knows which paths exist and what they hold;
8
+ * - validates `updateDataModel` writes against those declarations
9
+ * (`validateSchemaWrite`), turning a type mismatch on a declared pointer into a
10
+ * `SCHEMA_TYPE_MISMATCH` error and a write to an undeclared top-level branch
11
+ * into a `SCHEMA_UNDECLARED_PATH` warning — both relayed through the same
12
+ * `onValidationError` feedback loop the agent already consumes.
13
+ *
14
+ * Pure TS, no Svelte. Deliberately MINIMAL (v1): exact-pointer type/enum checks
15
+ * and a top-level "did you mean to write here?" warning. Deferred: required
16
+ * fields, nested object schemas, cross-field constraints, and transporting the
17
+ * schema inside the envelope.
18
+ */
19
+ import { A2UI_ISSUE_CODES } from './a2ui.types.js';
20
+ import { getAtPointer } from './a2ui-data.js';
21
+ function typeMatches(type, value) {
22
+ switch (type) {
23
+ case 'string':
24
+ return typeof value === 'string';
25
+ case 'number':
26
+ return typeof value === 'number' && Number.isFinite(value);
27
+ case 'integer':
28
+ return typeof value === 'number' && Number.isInteger(value);
29
+ case 'boolean':
30
+ return typeof value === 'boolean';
31
+ case 'array':
32
+ return Array.isArray(value);
33
+ case 'object':
34
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
35
+ }
36
+ }
37
+ /** The first pointer segment (`/booking/date` → `booking`); `''` for the root. */
38
+ function topSegment(pointer) {
39
+ const trimmed = pointer.startsWith('/') ? pointer.slice(1) : pointer;
40
+ return trimmed.split('/')[0] ?? '';
41
+ }
42
+ function fieldIssues(field, pointer, value, surfaceId) {
43
+ if (!typeMatches(field.type, value)) {
44
+ return [
45
+ {
46
+ severity: 'error',
47
+ code: A2UI_ISSUE_CODES.SCHEMA_TYPE_MISMATCH,
48
+ surfaceId,
49
+ path: pointer,
50
+ message: `Data at "${pointer}" must be a ${field.type} per the surface schema`
51
+ }
52
+ ];
53
+ }
54
+ if (field.enum &&
55
+ (typeof value === 'string' || typeof value === 'number') &&
56
+ !field.enum.includes(value)) {
57
+ return [
58
+ {
59
+ severity: 'error',
60
+ code: A2UI_ISSUE_CODES.SCHEMA_TYPE_MISMATCH,
61
+ surfaceId,
62
+ path: pointer,
63
+ message: `Data at "${pointer}" must be one of: ${field.enum.join(', ')}`
64
+ }
65
+ ];
66
+ }
67
+ return [];
68
+ }
69
+ /**
70
+ * Validate one `updateDataModel` write against the schema. `pointer` is the
71
+ * write target (`''`/`'/'`/`undefined` = whole model); `value` is the written
72
+ * value (`undefined` for a delete — never flagged). Returns any schema issues.
73
+ */
74
+ export function validateSchemaWrite(schema, pointer, value, surfaceId) {
75
+ if (value === undefined)
76
+ return []; // a delete never violates a type
77
+ const whole = pointer === undefined || pointer === '' || pointer === '/';
78
+ if (whole) {
79
+ // Whole-model write: check every declared field against the new model.
80
+ const issues = [];
81
+ for (const [declaredPointer, field] of Object.entries(schema)) {
82
+ const at = getAtPointer(value, declaredPointer);
83
+ if (at !== undefined)
84
+ issues.push(...fieldIssues(field, declaredPointer, at, surfaceId));
85
+ }
86
+ return issues;
87
+ }
88
+ // `Object.hasOwn`, not a bare lookup: a pointer like `/toString` or `/__proto__`
89
+ // would otherwise resolve an inherited Object.prototype member and mis-report a
90
+ // type mismatch on a field that was never declared.
91
+ const field = Object.hasOwn(schema, pointer) ? schema[pointer] : undefined;
92
+ if (field)
93
+ return fieldIssues(field, pointer, value, surfaceId);
94
+ // Undeclared pointer: warn only when its whole top-level branch is unknown, so
95
+ // a write into a declared object (a deeper path we don't model yet) stays quiet.
96
+ const top = topSegment(pointer);
97
+ const declared = Object.keys(schema).some((declaredPointer) => topSegment(declaredPointer) === top);
98
+ if (!declared) {
99
+ return [
100
+ {
101
+ severity: 'warning',
102
+ code: A2UI_ISSUE_CODES.SCHEMA_UNDECLARED_PATH,
103
+ surfaceId,
104
+ path: pointer,
105
+ message: `Data path "${pointer}" is not declared in the surface schema`
106
+ }
107
+ ];
108
+ }
109
+ return [];
110
+ }
111
+ /**
112
+ * Render the schema as a prompt section listing every declared field, its type
113
+ * and any enum/format/description. Appended by the app after `a2uiSystemPrompt`
114
+ * (like the transport section), NOT baked into the generator.
115
+ */
116
+ export function a2uiDataSchemaSection(schema) {
117
+ const lines = [
118
+ '## Data schema',
119
+ '',
120
+ 'The surface data model holds these fields (bind inputs to them with { path }',
121
+ 'and initialize them via updateDataModel). Write only the declared types:'
122
+ ];
123
+ for (const [pointer, field] of Object.entries(schema)) {
124
+ const bits = [field.type];
125
+ if (field.enum)
126
+ bits.push(`one of: ${field.enum.join(' | ')}`);
127
+ if (field.format)
128
+ bits.push(`format ${field.format}`);
129
+ const suffix = field.description ? ` — ${field.description}` : '';
130
+ lines.push(`- ${pointer} (${bits.join('; ')})${suffix}`);
131
+ }
132
+ return lines.join('\n');
133
+ }
@@ -0,0 +1,120 @@
1
+ /** A streamed text segment. Structurally assignable to blocks' `ChatMessagePart` (`text`). */
2
+ export interface A2uiStreamTextPart {
3
+ type: 'text';
4
+ text: string;
5
+ }
6
+ /** A streamed A2UI segment. `payload` is the accumulated envelope array (assignable to `ChatMessagePart` `a2ui`). */
7
+ export interface A2uiStreamUiPart {
8
+ type: 'a2ui';
9
+ payload: unknown[];
10
+ }
11
+ export type A2uiStreamPart = A2uiStreamTextPart | A2uiStreamUiPart;
12
+ /** A non-fatal finding from the split (a malformed JSONL line, an unterminated fence). */
13
+ export interface A2uiStreamIssue {
14
+ code: 'UNPARSEABLE_LINE' | 'UNTERMINATED_FENCE';
15
+ message: string;
16
+ /** The offending source line, when applicable. */
17
+ line?: string;
18
+ }
19
+ /**
20
+ * The info string that marks a fence as A2UI. Shared by the parser and by
21
+ * `a2uiFencedTransportSection()` so the prompt can never name a different tag
22
+ * than the splitter accepts.
23
+ */
24
+ export declare const A2UI_FENCE_TAG = "a2ui";
25
+ export declare class A2uiStreamSplitter {
26
+ /** Committed parts (immutable; replaced wholesale on every mutation). */
27
+ private committed;
28
+ private mode;
29
+ /**
30
+ * When in text mode inside a *regular* (non-a2ui) markdown code fence, the
31
+ * marker that opened it. A `` ```a2ui `` line inside such a block is literal
32
+ * code, NOT a surface — this is what stops a quoted example from executing.
33
+ */
34
+ private codeFence;
35
+ /** Buffered trailing line that has not yet seen its newline. */
36
+ private pending;
37
+ private rawText;
38
+ private readonly issueList;
39
+ /**
40
+ * Raw lines of an in-progress multi-line envelope (a pretty-printed object
41
+ * spanning several fence lines), plus the depth-scanner state that tracks
42
+ * whether the object has closed. Empty/zeroed while envelopes arrive as
43
+ * proper single-line JSONL.
44
+ */
45
+ private objLines;
46
+ private objDepth;
47
+ private objInString;
48
+ private objEscape;
49
+ /** Feed the next token / chunk of model output. */
50
+ push(chunk: string): void;
51
+ /** Signal end of stream: flush the trailing partial line and settle state. */
52
+ end(): void;
53
+ private asText;
54
+ private resetObjBuffer;
55
+ /**
56
+ * Advance the depth scanner across one line: brace/bracket depth outside of
57
+ * strings, with `\"`-escape handling inside them. Returns `false` when the
58
+ * buffer can never become valid JSON — depth underflow, or a string left
59
+ * open at end-of-line (raw newlines are illegal inside JSON strings).
60
+ */
61
+ private scanDepth;
62
+ /**
63
+ * Abandon the current a2ui fence: drop an empty surface, record an issue and
64
+ * re-emit the buffered lines (plus the offending one, if any) as text inside
65
+ * a synthetic ``` code block. `codeFence` is armed so the fence's real
66
+ * closing ``` closes that block instead of opening a stray one downstream.
67
+ */
68
+ private fenceFallback;
69
+ private processLine;
70
+ /**
71
+ * The current parts, including the buffered trailing text tail for a
72
+ * responsive stream. A partial line that might still become a fence marker is
73
+ * held back (never flashed). a2ui parts are carried by reference so A2UIView
74
+ * consumes them incrementally.
75
+ */
76
+ snapshot(): A2uiStreamPart[];
77
+ /** The raw model output fed so far (fences included) — the faithful wire form. */
78
+ get raw(): string;
79
+ /** Accumulated non-fatal issues (malformed JSONL, unterminated fence). */
80
+ get issues(): A2uiStreamIssue[];
81
+ }
82
+ /** Options for {@link a2uiFencedTransportSection}. */
83
+ export interface A2uiTransportSectionOptions {
84
+ /**
85
+ * Prefix your client puts on a user turn that reports an action, followed by
86
+ * the JSON `A2uiActionEvent`. Set to `false` if actions reach the agent some
87
+ * other way (a tool result, a structured field) — the round-channel paragraph
88
+ * is then omitted. @default '[ui-action]'
89
+ */
90
+ actionPrefix?: string | false;
91
+ /**
92
+ * Prefix for a turn that reports validation issues as JSON, so the agent can
93
+ * repair the surface. `false` omits it. @default '[ui-error]'
94
+ */
95
+ errorPrefix?: string | false;
96
+ }
97
+ /**
98
+ * The transport half of the fenced-JSONL contract: the prompt section that makes
99
+ * an agent emit exactly what {@link A2uiStreamSplitter} parses.
100
+ *
101
+ * `a2uiSystemPrompt()` deliberately says nothing about transport, because how
102
+ * envelopes travel is the app's business. This is the transport for apps that
103
+ * take the model's ordinary text stream and let it open a ` ```a2ui ` fence —
104
+ * the setup the splitter exists for. Append it after `a2uiSystemPrompt()` (and
105
+ * after `a2uiDataSchemaSection()`, if you use one).
106
+ *
107
+ * Anything domain-specific — which tools to call, what never to invent — stays
108
+ * yours to append; this covers only the wire format and the return path.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const system = [
113
+ * a2uiSystemPrompt({ catalog: urbiconA2uiCatalogSpec }),
114
+ * a2uiDataSchemaSection(MY_SCHEMA),
115
+ * a2uiFencedTransportSection(),
116
+ * MY_DOMAIN_RULES
117
+ * ].join('\n\n');
118
+ * ```
119
+ */
120
+ export declare function a2uiFencedTransportSection(options?: A2uiTransportSectionOptions): string;