@adia-ai/a2ui-compose 0.0.1
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 +86 -0
- package/README.md +181 -0
- package/engine/artifacts.js +262 -0
- package/engine/constitution.md +78 -0
- package/engine/context-store.js +218 -0
- package/engine/generator.js +500 -0
- package/engine/pattern-export.js +149 -0
- package/engine/pipeline/engine.js +289 -0
- package/engine/pipeline/types.js +91 -0
- package/engine/reference.js +115 -0
- package/engine/state.js +15 -0
- package/engines/monolithic/_shared.js +1320 -0
- package/engines/monolithic/generate-instant.js +229 -0
- package/engines/monolithic/generate-pro.js +367 -0
- package/engines/monolithic/generate-thinking.js +211 -0
- package/engines/registry.js +195 -0
- package/engines/zettel/_smoke.js +37 -0
- package/engines/zettel/composer.js +146 -0
- package/engines/zettel/fragment-library.js +209 -0
- package/engines/zettel/generate.js +15 -0
- package/engines/zettel/generator-adapter.js +202 -0
- package/engines/zettel/session-store.js +121 -0
- package/engines/zettel/synthesizer.js +343 -0
- package/evals/harness.mjs +193 -0
- package/index.js +16 -0
- package/llm/adapters/anthropic.js +106 -0
- package/llm/adapters/gemini.js +99 -0
- package/llm/adapters/index.js +138 -0
- package/llm/adapters/openai.js +85 -0
- package/llm/adapters/sse.js +50 -0
- package/llm/llm-bridge.js +214 -0
- package/llm/llm-stub.js +69 -0
- package/package.json +41 -0
- package/transpiler/transpiler-maps.js +277 -0
- package/transpiler/transpiler.js +820 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Store — Cross-surface shared state (A008 §8).
|
|
3
|
+
*
|
|
4
|
+
* Manages named contexts that multiple surfaces can read from and write to.
|
|
5
|
+
* When the first participating surface mounts, the context is populated.
|
|
6
|
+
* When the last participating surface unmounts, the context is eligible
|
|
7
|
+
* for garbage collection.
|
|
8
|
+
*
|
|
9
|
+
* This is the horizontal equivalent of A007's provider (vertical, parent→child).
|
|
10
|
+
* Contexts live outside any single surface's lifecycle.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} ContextEntry
|
|
15
|
+
* @property {string} name
|
|
16
|
+
* @property {object} shape — Schema of the context data
|
|
17
|
+
* @property {object} data — Current data
|
|
18
|
+
* @property {Set<string>} participants — Surface IDs consuming this context
|
|
19
|
+
* @property {object} source — How to populate { uri, refresh, params }
|
|
20
|
+
* @property {boolean} populated — Whether data has been fetched
|
|
21
|
+
* @property {Set<(data: object) => void>} listeners — Change listeners
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export class ContextStore {
|
|
25
|
+
/** @type {Map<string, ContextEntry>} */
|
|
26
|
+
#contexts = new Map();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Define a shared context (usually from a surface manifest).
|
|
30
|
+
*
|
|
31
|
+
* @param {string} name — Context name
|
|
32
|
+
* @param {object} config
|
|
33
|
+
* @param {object} config.shape — Data shape schema
|
|
34
|
+
* @param {object} [config.source] — { uri, refresh, params }
|
|
35
|
+
*/
|
|
36
|
+
define(name, config) {
|
|
37
|
+
if (this.#contexts.has(name)) return; // Already defined
|
|
38
|
+
|
|
39
|
+
this.#contexts.set(name, {
|
|
40
|
+
name,
|
|
41
|
+
shape: config.shape || {},
|
|
42
|
+
data: {},
|
|
43
|
+
participants: new Set(),
|
|
44
|
+
source: config.source || null,
|
|
45
|
+
populated: false,
|
|
46
|
+
listeners: new Set(),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Register a surface as a participant in a context.
|
|
52
|
+
* If this is the first participant and a source is defined, triggers population.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} contextName
|
|
55
|
+
* @param {string} surfaceId
|
|
56
|
+
* @param {(uri: string, params: Record<string, string>) => Promise<unknown>} [fetcher] — Data fetcher
|
|
57
|
+
* @param {Record<string, string>} [params]
|
|
58
|
+
*/
|
|
59
|
+
async join(contextName, surfaceId, fetcher, params = {}) {
|
|
60
|
+
let ctx = this.#contexts.get(contextName);
|
|
61
|
+
if (!ctx) {
|
|
62
|
+
// Auto-define if not pre-defined
|
|
63
|
+
ctx = { name: contextName, shape: {}, data: {}, participants: new Set(), source: null, populated: false, listeners: new Set() };
|
|
64
|
+
this.#contexts.set(contextName, ctx);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ctx.participants.add(surfaceId);
|
|
68
|
+
|
|
69
|
+
// Populate on first join (if source exists and not already populated)
|
|
70
|
+
if (!ctx.populated && ctx.source && fetcher) {
|
|
71
|
+
try {
|
|
72
|
+
let uri = ctx.source.uri;
|
|
73
|
+
const resolvedParams = { ...ctx.source.params, ...params };
|
|
74
|
+
for (const [key, spec] of Object.entries(resolvedParams)) {
|
|
75
|
+
const value = typeof spec === 'string' ? spec : spec.value || '';
|
|
76
|
+
uri = uri.replace(`{${key}}`, encodeURIComponent(value));
|
|
77
|
+
}
|
|
78
|
+
ctx.data = await fetcher(uri, resolvedParams);
|
|
79
|
+
ctx.populated = true;
|
|
80
|
+
this.#notify(contextName);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.warn(`ContextStore: failed to populate "${contextName}":`, err.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return ctx.data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Unregister a surface from a context.
|
|
91
|
+
* If no participants remain, mark for potential cleanup.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} contextName
|
|
94
|
+
* @param {string} surfaceId
|
|
95
|
+
*/
|
|
96
|
+
leave(contextName, surfaceId) {
|
|
97
|
+
const ctx = this.#contexts.get(contextName);
|
|
98
|
+
if (!ctx) return;
|
|
99
|
+
|
|
100
|
+
ctx.participants.delete(surfaceId);
|
|
101
|
+
|
|
102
|
+
// Eligible for GC when no participants
|
|
103
|
+
if (ctx.participants.size === 0) {
|
|
104
|
+
ctx.populated = false;
|
|
105
|
+
ctx.data = {};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the current data for a context.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} contextName
|
|
113
|
+
* @returns {object | null}
|
|
114
|
+
*/
|
|
115
|
+
get(contextName) {
|
|
116
|
+
return this.#contexts.get(contextName)?.data ?? null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Read a specific key from a context.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} contextName
|
|
123
|
+
* @param {string} key — Dot-separated path (e.g., "patient.name")
|
|
124
|
+
* @returns {unknown}
|
|
125
|
+
*/
|
|
126
|
+
read(contextName, key) {
|
|
127
|
+
const data = this.get(contextName);
|
|
128
|
+
if (!data || !key) return data;
|
|
129
|
+
|
|
130
|
+
const segments = key.split('.');
|
|
131
|
+
let current = data;
|
|
132
|
+
for (const seg of segments) {
|
|
133
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
134
|
+
current = current[seg];
|
|
135
|
+
}
|
|
136
|
+
return current;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Update data in a context. Notifies all listeners.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} contextName
|
|
143
|
+
* @param {object} data — Partial or full data update (merged)
|
|
144
|
+
*/
|
|
145
|
+
update(contextName, data) {
|
|
146
|
+
const ctx = this.#contexts.get(contextName);
|
|
147
|
+
if (!ctx) return;
|
|
148
|
+
|
|
149
|
+
ctx.data = { ...ctx.data, ...data };
|
|
150
|
+
ctx.populated = true;
|
|
151
|
+
this.#notify(contextName);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Subscribe to context changes.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} contextName
|
|
158
|
+
* @param {(data: object) => void} listener
|
|
159
|
+
* @returns {() => void} — Unsubscribe function
|
|
160
|
+
*/
|
|
161
|
+
subscribe(contextName, listener) {
|
|
162
|
+
const ctx = this.#contexts.get(contextName);
|
|
163
|
+
if (!ctx) return () => {};
|
|
164
|
+
|
|
165
|
+
ctx.listeners.add(listener);
|
|
166
|
+
return () => ctx.listeners.delete(listener);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if a context is populated with data.
|
|
171
|
+
* @param {string} contextName
|
|
172
|
+
* @returns {boolean}
|
|
173
|
+
*/
|
|
174
|
+
isPopulated(contextName) {
|
|
175
|
+
return this.#contexts.get(contextName)?.populated ?? false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get all defined context names.
|
|
180
|
+
* @returns {string[]}
|
|
181
|
+
*/
|
|
182
|
+
get contextNames() {
|
|
183
|
+
return [...this.#contexts.keys()];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get diagnostic info for a context.
|
|
188
|
+
* @param {string} contextName
|
|
189
|
+
*/
|
|
190
|
+
inspect(contextName) {
|
|
191
|
+
const ctx = this.#contexts.get(contextName);
|
|
192
|
+
if (!ctx) return null;
|
|
193
|
+
return {
|
|
194
|
+
name: ctx.name,
|
|
195
|
+
populated: ctx.populated,
|
|
196
|
+
participantCount: ctx.participants.size,
|
|
197
|
+
participants: [...ctx.participants],
|
|
198
|
+
listenerCount: ctx.listeners.size,
|
|
199
|
+
dataKeys: Object.keys(ctx.data),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Clear all contexts. */
|
|
204
|
+
clear() {
|
|
205
|
+
this.#contexts.clear();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#notify(contextName) {
|
|
209
|
+
const ctx = this.#contexts.get(contextName);
|
|
210
|
+
if (!ctx) return;
|
|
211
|
+
for (const listener of ctx.listeners) {
|
|
212
|
+
try { listener(ctx.data); } catch { /* listener error */ }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Singleton shared context store for the application. */
|
|
218
|
+
export const sharedContextStore = new ContextStore();
|