@adia-ai/a2ui-compose 0.5.18 → 0.5.19
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 +4 -0
- package/core/generator.js +14 -11
- package/package.json +1 -1
- package/strategies/monolithic/_shared.js +13 -1
- package/strategies/monolithic/generate-instant.js +9 -9
- package/strategies/monolithic/generate-pro.js +8 -8
- package/strategies/monolithic/generate-thinking.js +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,10 @@ generator graph.
|
|
|
11
11
|
|
|
12
12
|
_No pending changes._
|
|
13
13
|
|
|
14
|
+
## [0.5.19] - 2026-05-17
|
|
15
|
+
|
|
16
|
+
_Lockstep ride-along (no source change in this package; companion to web-components v0.5.19 — see root CHANGELOG)._
|
|
17
|
+
|
|
14
18
|
## [0.5.18] - 2026-05-16
|
|
15
19
|
|
|
16
20
|
_Lockstep ride-along (no source change in this package; companion to web-components v0.5.18 — see root CHANGELOG)._
|
package/core/generator.js
CHANGED
|
@@ -60,9 +60,10 @@ import { generateThinking } from '../strategies/monolithic/generate-thinking.js'
|
|
|
60
60
|
* or from a saved-canvas JSON file. Backward compatible: if omitted, the
|
|
61
61
|
* store lookup via `executionId` still works.
|
|
62
62
|
* @param {object} [opts.llmAdapter] — LLM adapter (defaults to auto-detected)
|
|
63
|
+
* @param {object} [opts.context] — Ontology context (domain, tasks) parsed by plan_app_state
|
|
63
64
|
* @returns {Promise<{ executionId: string, messages: object[], validation: object, suggestions: string[] }>}
|
|
64
65
|
*/
|
|
65
|
-
export async function generateUI({ intent, engine: engineName = 'monolithic', mode = 'instant', executionId, currentCanvas, llmAdapter, model, sessionId }) {
|
|
66
|
+
export async function generateUI({ intent, engine: engineName = 'monolithic', mode = 'instant', executionId, currentCanvas, llmAdapter, model, sessionId, context }) {
|
|
66
67
|
// Iteration signal: either an executionId (server-stateful path) OR a
|
|
67
68
|
// currentCanvas (client-stateless path). Both mean "refine existing UI".
|
|
68
69
|
const hasClientCanvas = !!(currentCanvas && (currentCanvas.components?.length || currentCanvas.messages?.length));
|
|
@@ -138,6 +139,7 @@ export async function generateUI({ intent, engine: engineName = 'monolithic', mo
|
|
|
138
139
|
sessionId,
|
|
139
140
|
analysis,
|
|
140
141
|
priorComponentsFromPayload,
|
|
142
|
+
context, // Pass the ontology context to the engine
|
|
141
143
|
});
|
|
142
144
|
const totalMs = Date.now() - t0;
|
|
143
145
|
|
|
@@ -236,9 +238,10 @@ export async function generateUI({ intent, engine: engineName = 'monolithic', mo
|
|
|
236
238
|
* @param {string} [opts.executionId]
|
|
237
239
|
* @param {object} [opts.llmAdapter]
|
|
238
240
|
* @param {(query: string) => Promise<{ results: { title: string, snippet: string, url: string }[] }>} [opts.search] — Web search function
|
|
241
|
+
* @param {object} [opts.context] — Ontology context (domain, tasks) parsed by plan_app_state
|
|
239
242
|
* @yields {object}
|
|
240
243
|
*/
|
|
241
|
-
export async function* generateUIStream({ intent, executionId, llmAdapter, model, search, currentCanvas }) {
|
|
244
|
+
export async function* generateUIStream({ intent, executionId, llmAdapter, model, search, currentCanvas, context }) {
|
|
242
245
|
// currentCanvas accepted for API parity with generateUI. The streaming
|
|
243
246
|
// path's downstream consumers don't yet read it, but parity prevents the
|
|
244
247
|
// client from having to branch its call shape per mode. Tier-2 will
|
|
@@ -320,7 +323,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
320
323
|
// was a tier mismatch. Fixed by running a fast keyword search here.
|
|
321
324
|
// (b) The synthesizer composes from below-threshold patterns too; "0
|
|
322
325
|
// patterns" reading as "nothing relevant" actively misleads the user.
|
|
323
|
-
const
|
|
326
|
+
const catalogContext = await getContext(intent, 2);
|
|
324
327
|
const topPatternsRaw = searchBlocks(intent).slice(0, 5);
|
|
325
328
|
const candidateLines = topPatternsRaw.length
|
|
326
329
|
? topPatternsRaw.map(p => {
|
|
@@ -330,14 +333,14 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
330
333
|
})
|
|
331
334
|
: [' (no keyword match — synthesizer will compose from components)'];
|
|
332
335
|
engine.submitStage(executionId, 'analyze', {
|
|
333
|
-
context, componentCount:
|
|
336
|
+
context: catalogContext, componentCount: catalogContext.components.length, patternCount: topPatternsRaw.length,
|
|
334
337
|
topPatterns: topPatternsRaw.map(p => ({ name: p.name, score: p.score, domain: p.domain })),
|
|
335
|
-
confidence:
|
|
338
|
+
confidence: catalogContext.components.length > 0 ? 0.85 : 0.5,
|
|
336
339
|
});
|
|
337
340
|
yield {
|
|
338
341
|
type: 'status',
|
|
339
342
|
stage: 'analyze',
|
|
340
|
-
message: `${
|
|
343
|
+
message: `${catalogContext.components.length} components inspected · ${topPatternsRaw.length} pattern candidate${topPatternsRaw.length === 1 ? '' : 's'}`,
|
|
341
344
|
outcomes: ['Top retrievals:', ...candidateLines],
|
|
342
345
|
};
|
|
343
346
|
|
|
@@ -382,7 +385,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
382
385
|
try {
|
|
383
386
|
const subResult = await adapter.complete({
|
|
384
387
|
messages: [{ role: 'user', content: subtask.intent }],
|
|
385
|
-
systemPrompt: await buildSystemPrompt(
|
|
388
|
+
systemPrompt: await buildSystemPrompt(catalogContext, patterns, researchContext, intent, null, context),
|
|
386
389
|
});
|
|
387
390
|
const subMessages = parseA2UIResponse(subResult.content, { executionId, mode: 'stream', intent: subtask.intent });
|
|
388
391
|
subtaskResults.push({ label: subtask.label, messages: subMessages });
|
|
@@ -400,7 +403,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
400
403
|
subtasks: decomposition.subtasks.length, confidence: 0.8,
|
|
401
404
|
});
|
|
402
405
|
|
|
403
|
-
const composedValidation = validateSchema(composed, { intent });
|
|
406
|
+
const composedValidation = validateSchema(composed, { intent, context });
|
|
404
407
|
|
|
405
408
|
engine.submitStage(executionId, 'validate', {
|
|
406
409
|
...composedValidation, confidence: composedValidation.score / 100,
|
|
@@ -433,7 +436,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
433
436
|
}
|
|
434
437
|
|
|
435
438
|
// ── Stage 4: Generate via LLM stream ──
|
|
436
|
-
const systemPrompt = await buildSystemPrompt(
|
|
439
|
+
const systemPrompt = await buildSystemPrompt(catalogContext, patterns, researchContext, intent, null, context);
|
|
437
440
|
const chatMessages = buildChatMessages(intent, previousExecId || executionId);
|
|
438
441
|
|
|
439
442
|
yield { type: 'status', stage: 'generate', message: 'Streaming from LLM...' };
|
|
@@ -473,7 +476,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
473
476
|
messages, source: 'llm-stream', confidence: 0.8,
|
|
474
477
|
});
|
|
475
478
|
|
|
476
|
-
let validation = validateSchema(messages, { intent });
|
|
479
|
+
let validation = validateSchema(messages, { intent, context });
|
|
477
480
|
|
|
478
481
|
// One repair attempt if invalid (non-streaming, quick fix)
|
|
479
482
|
if (!validation.valid) {
|
|
@@ -485,7 +488,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
|
|
|
485
488
|
systemPrompt,
|
|
486
489
|
});
|
|
487
490
|
messages = parseA2UIResponse(repairResponse.content, { executionId, mode: 'stream', intent });
|
|
488
|
-
validation = validateSchema(messages, { intent });
|
|
491
|
+
validation = validateSchema(messages, { intent, context });
|
|
489
492
|
} catch { /* keep original */ }
|
|
490
493
|
}
|
|
491
494
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/a2ui-compose",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "AdiaUI A2UI compose engine — framework-agnostic. Takes natural-language intents + a catalog and produces A2UI protocol messages. Pairs with `@adia-ai/a2ui-retrieval` (intent classification, catalog lookup) and `@adia-ai/a2ui-validator` (schema + semantic checks).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -142,7 +142,7 @@ async function getComponentCatalog() {
|
|
|
142
142
|
return _componentCatalog;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
export async function buildSystemPrompt(context, patterns, researchContext = '', intent = '', composition = null) {
|
|
145
|
+
export async function buildSystemPrompt(context, patterns, researchContext = '', intent = '', composition = null, ontologyContext = null) {
|
|
146
146
|
const parts = [];
|
|
147
147
|
|
|
148
148
|
// ── Role + output format ──
|
|
@@ -154,6 +154,18 @@ Output format: [{ "type": "updateComponents", "surfaceId": "default", "component
|
|
|
154
154
|
Each component: { "id": "<unique>", "component": "<Type>", "children": ["<childId>", ...], ...props }
|
|
155
155
|
The root must have id "root". Use short, descriptive IDs (e.g., "hdr", "email-field", "submit-btn").`);
|
|
156
156
|
|
|
157
|
+
// ── Ontology Context Injection ──
|
|
158
|
+
if (ontologyContext) {
|
|
159
|
+
parts.push(`
|
|
160
|
+
CRITICAL CONSTRAINT: You must strictly adhere to the following Domain and Task models.
|
|
161
|
+
Do not hallucinate features, bounds, or data inputs that violate these structures.
|
|
162
|
+
- Domain Entities: ${(ontologyContext.domain?.entities || []).join(', ') || 'N/A'}
|
|
163
|
+
- Domain Metrics: ${(ontologyContext.domain?.metrics || []).join(', ') || 'N/A'}
|
|
164
|
+
- Primary Tasks: ${(ontologyContext.tasks?.primary || []).join(', ') || 'N/A'}
|
|
165
|
+
- Experience Mode: ${ontologyContext.experience?.mode || 'N/A'}
|
|
166
|
+
- Experience Shell: ${ontologyContext.experience?.shell || 'N/A'}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
// ── Corpus context (§56, v0.4.5) ──
|
|
158
170
|
// Tells the LLM how the system surrounding it is wired so it doesn't
|
|
159
171
|
// hallucinate component names and so it understands what MATCHED PATTERN /
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
generateSuggestions,
|
|
17
17
|
} from './_shared.js';
|
|
18
18
|
|
|
19
|
-
export async function generateInstant({ intent, executionId, storeId, analysis, priorComponentsFromPayload }) {
|
|
19
|
+
export async function generateInstant({ intent, executionId, storeId, analysis, priorComponentsFromPayload, context }) {
|
|
20
20
|
// Forward-compat: instant mode is pattern-match-only and doesn't iterate
|
|
21
21
|
// on a prior canvas yet. Accept the param so callers can pass uniformly.
|
|
22
22
|
void priorComponentsFromPayload;
|
|
@@ -56,13 +56,13 @@ export async function generateInstant({ intent, executionId, storeId, analysis,
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// ── Analyze
|
|
60
|
-
const
|
|
61
|
-
engine.submitStage(
|
|
62
|
-
context,
|
|
63
|
-
componentCount:
|
|
64
|
-
patternCount:
|
|
65
|
-
confidence:
|
|
59
|
+
// ── Stage 2: Analyze ──
|
|
60
|
+
const catalogContext = await getContext(intent, 1);
|
|
61
|
+
engine.submitStage(executionId, 'analyze', {
|
|
62
|
+
context: catalogContext,
|
|
63
|
+
componentCount: catalogContext.components.length,
|
|
64
|
+
patternCount: catalogContext.patterns.length,
|
|
65
|
+
confidence: catalogContext.patterns.length > 0 ? 0.9 : 0.6,
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
// ── Plan stage ──
|
|
@@ -168,7 +168,7 @@ export async function generateInstant({ intent, executionId, storeId, analysis,
|
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
// ── Validate stage ──
|
|
171
|
-
const validation = validateSchema(messages, { intent });
|
|
171
|
+
const validation = validateSchema(messages, { intent, context });
|
|
172
172
|
engine.submitStage(execId, 'validate', {
|
|
173
173
|
...validation,
|
|
174
174
|
confidence: validation.score / 100,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
generateSuggestions,
|
|
26
26
|
} from './_shared.js';
|
|
27
27
|
|
|
28
|
-
export async function generatePro({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload }) {
|
|
28
|
+
export async function generatePro({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload, context }) {
|
|
29
29
|
const execId = executionId;
|
|
30
30
|
|
|
31
31
|
// The steelman is the enriched brief produced by the prompt-analyzer in
|
|
@@ -89,9 +89,9 @@ export async function generatePro({ intent, executionId, storeId, llmAdapter, an
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// ── Stage 2: Analyze ──
|
|
92
|
-
const
|
|
92
|
+
const catalogContext = await getContext(intent, 2);
|
|
93
93
|
engine.submitStage(execId, 'analyze', {
|
|
94
|
-
context, componentCount:
|
|
94
|
+
context: catalogContext, componentCount: catalogContext.components.length, patternCount: catalogContext.patterns.length,
|
|
95
95
|
confidence: 0.85,
|
|
96
96
|
});
|
|
97
97
|
|
|
@@ -241,7 +241,7 @@ ${chunkRefHtml}
|
|
|
241
241
|
|
|
242
242
|
if (bestPattern && bestPattern.template) {
|
|
243
243
|
// PRO PATH: Adapt the matched pattern via LLM
|
|
244
|
-
systemPrompt = await buildSystemPrompt(
|
|
244
|
+
systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, null, context);
|
|
245
245
|
|
|
246
246
|
const adaptPrompt = hasPriorCanvas
|
|
247
247
|
? `Adapt this UI for the intent: "${intent}"${driftGuard}
|
|
@@ -291,7 +291,7 @@ Instructions:
|
|
|
291
291
|
if (isRecording()) { lastRawResponse = response.content; lastTokens = response.usage || null; }
|
|
292
292
|
} else if (hasFragments) {
|
|
293
293
|
// COMPOSE PATH: multiple fragment patterns found for subtasks — compose in a single LLM call
|
|
294
|
-
systemPrompt = await buildSystemPrompt(
|
|
294
|
+
systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, { fragmentPatterns, decomposition }, context);
|
|
295
295
|
|
|
296
296
|
const fragmentContext = fragmentPatterns.map(f =>
|
|
297
297
|
`## ${f.label} (from "${f.pattern.name}" pattern)\n${JSON.stringify(f.pattern.template, null, 1)}`
|
|
@@ -332,7 +332,7 @@ Instructions:
|
|
|
332
332
|
if (isRecording()) { lastRawResponse = response.content; lastTokens = response.usage || null; }
|
|
333
333
|
} else {
|
|
334
334
|
// No pattern — fall through to full thinking-style generation
|
|
335
|
-
systemPrompt = await buildSystemPrompt(
|
|
335
|
+
systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, null, context);
|
|
336
336
|
|
|
337
337
|
if (hasPriorCanvas) {
|
|
338
338
|
// Multi-turn: inject current canvas as explicit iteration context
|
|
@@ -404,7 +404,7 @@ ${buildCanvasDiffPrompt(intent, priorComponents, { originalIntent })}`;
|
|
|
404
404
|
});
|
|
405
405
|
|
|
406
406
|
// ── Stage 5: Validate + repair ──
|
|
407
|
-
let validation = validateSchema(messages, { intent });
|
|
407
|
+
let validation = validateSchema(messages, { intent, context });
|
|
408
408
|
if (!validation.valid) {
|
|
409
409
|
try {
|
|
410
410
|
const failedChecks = validation.checks.filter(c => !c.passed);
|
|
@@ -413,7 +413,7 @@ ${buildCanvasDiffPrompt(intent, priorComponents, { originalIntent })}`;
|
|
|
413
413
|
systemPrompt,
|
|
414
414
|
});
|
|
415
415
|
messages = parseA2UIResponse(repairResponse.content, { executionId: execId, intent, mode: 'pro', stopReason: repairResponse.stopReason });
|
|
416
|
-
validation = validateSchema(messages, { intent });
|
|
416
|
+
validation = validateSchema(messages, { intent, context });
|
|
417
417
|
} catch { /* keep original */ }
|
|
418
418
|
}
|
|
419
419
|
engine.submitStage(execId, 'validate', { ...validation, confidence: validation.score / 100 });
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
generateSuggestions,
|
|
22
22
|
} from './_shared.js';
|
|
23
23
|
|
|
24
|
-
export async function generateThinking({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload }) {
|
|
24
|
+
export async function generateThinking({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload, context }) {
|
|
25
25
|
// Note: thinking mode currently composes from scratch each turn. The
|
|
26
26
|
// priorComponentsFromPayload param is accepted for forward-compatibility
|
|
27
27
|
// with multi-turn thinking iterations; not used in this pass yet.
|
|
@@ -72,12 +72,12 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// ── Stage 2: Analyze (tier 2 = 5000 token budget) ──
|
|
75
|
-
const
|
|
75
|
+
const catalogContext = await getContext(intent, 2);
|
|
76
76
|
engine.submitStage(execId, 'analyze', {
|
|
77
|
-
context,
|
|
78
|
-
componentCount:
|
|
79
|
-
patternCount:
|
|
80
|
-
confidence:
|
|
77
|
+
context: catalogContext,
|
|
78
|
+
componentCount: catalogContext.components.length,
|
|
79
|
+
patternCount: catalogContext.patterns.length,
|
|
80
|
+
confidence: catalogContext.components.length > 0 ? 0.85 : 0.5,
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
// ── Stage 3: Plan ──
|
|
@@ -91,7 +91,7 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
|
|
|
91
91
|
engine.submitStage(execId, 'plan', plan);
|
|
92
92
|
|
|
93
93
|
// ── Stage 4: Generate via LLM ──
|
|
94
|
-
const systemPrompt = await buildSystemPrompt(
|
|
94
|
+
const systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, null, context);
|
|
95
95
|
const chatMessages = buildChatMessages(intent, storeId || execId);
|
|
96
96
|
// Prepend the analyzer's enriched signals to the first user message so the
|
|
97
97
|
// LLM treats them as ground truth instead of re-deriving from a terse prompt.
|
|
@@ -129,7 +129,7 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
|
|
|
129
129
|
// Either failing triggers a repair attempt. Catalog failures are typically
|
|
130
130
|
// small (unknown prop, wrong enum, missing required id) and fix cleanly in
|
|
131
131
|
// one repair round. Three-attempt cap preserved from the original loop.
|
|
132
|
-
let validation = validateSchema(messages, { intent });
|
|
132
|
+
let validation = validateSchema(messages, { intent, context });
|
|
133
133
|
let catalogValidation = await validateCatalog(messages);
|
|
134
134
|
let attempts = 0;
|
|
135
135
|
|
|
@@ -146,7 +146,7 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
|
|
|
146
146
|
});
|
|
147
147
|
messages = parseA2UIResponse(repairResponse.content, { executionId: execId, mode: 'thinking', intent, stopReason: repairResponse.stopReason });
|
|
148
148
|
if (isRecording()) { lastRawResponse = repairResponse.content; lastTokens = repairResponse.usage || lastTokens; }
|
|
149
|
-
validation = validateSchema(messages, { intent });
|
|
149
|
+
validation = validateSchema(messages, { intent, context });
|
|
150
150
|
catalogValidation = await validateCatalog(messages);
|
|
151
151
|
} catch {
|
|
152
152
|
break; // stop repair loop on adapter error
|