@adia-ai/a2ui-compose 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -1
- package/README.md +5 -6
- package/engines/zettel/chunk-composer.js +182 -0
- package/engines/zettel/chunk-refiner.js +514 -0
- package/engines/zettel/chunk-synthesizer.js +235 -0
- package/engines/zettel/issue-reporter.js +380 -0
- package/engines/zettel/state-cache.js +153 -0
- package/package.json +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State-cache — bounded LRU for multi-turn gen-UI compositions.
|
|
3
|
+
*
|
|
4
|
+
* Keyed by `state_id`. Bounded by `maxSize` (default 64; configurable via the
|
|
5
|
+
* `A2UI_STATE_CACHE_SIZE` env var). When the cache is at capacity, the
|
|
6
|
+
* least-recently-touched entry is evicted on the next `set`. Entries are
|
|
7
|
+
* touched on every `get` and on every overwriting `set`. `peek` reads without
|
|
8
|
+
* touching recency — used by the issue-reporter when attaching traces.
|
|
9
|
+
*
|
|
10
|
+
* Pure data structure: no I/O, no LLM, no async. Safe to instantiate as a
|
|
11
|
+
* server-process singleton; survives only as long as the MCP process.
|
|
12
|
+
*
|
|
13
|
+
* Recommended entry shape (caller-enforced — the cache itself stores any object):
|
|
14
|
+
* {
|
|
15
|
+
* state_id, // mirrors the key
|
|
16
|
+
* intent, // user's request that produced this state
|
|
17
|
+
* html, // materialized output
|
|
18
|
+
* plan, // chunk-binding plan (or null for refinements)
|
|
19
|
+
* ops_history, // chronological A2UI message list
|
|
20
|
+
* parent_state_id, // chain-back for refinements (null for created roots)
|
|
21
|
+
* created_at, // ISO timestamp
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Spec: docs/specs/genui-multiturn-architecture.md §2.2 + §4.4.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const DEFAULT_MAX_SIZE = 64;
|
|
28
|
+
|
|
29
|
+
function envMaxSize() {
|
|
30
|
+
const v = process.env.A2UI_STATE_CACHE_SIZE;
|
|
31
|
+
if (!v) return null;
|
|
32
|
+
const n = parseInt(v, 10);
|
|
33
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class StateCache {
|
|
37
|
+
constructor({ maxSize } = {}) {
|
|
38
|
+
this.maxSize = maxSize ?? envMaxSize() ?? DEFAULT_MAX_SIZE;
|
|
39
|
+
this._map = new Map();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Insert or update; touches recency. Returns the state_id. */
|
|
43
|
+
set(state_id, state) {
|
|
44
|
+
if (this._map.has(state_id)) {
|
|
45
|
+
this._map.delete(state_id);
|
|
46
|
+
} else if (this._map.size >= this.maxSize) {
|
|
47
|
+
const firstKey = this._map.keys().next().value;
|
|
48
|
+
this._map.delete(firstKey);
|
|
49
|
+
}
|
|
50
|
+
this._map.set(state_id, state);
|
|
51
|
+
return state_id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Read; touches recency on hit. Returns null on miss. */
|
|
55
|
+
get(state_id) {
|
|
56
|
+
if (!this._map.has(state_id)) return null;
|
|
57
|
+
const entry = this._map.get(state_id);
|
|
58
|
+
this._map.delete(state_id);
|
|
59
|
+
this._map.set(state_id, entry);
|
|
60
|
+
return entry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Read without touching recency. Returns null on miss. */
|
|
64
|
+
peek(state_id) {
|
|
65
|
+
return this._map.get(state_id) ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
has(state_id) {
|
|
69
|
+
return this._map.has(state_id);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Returns true if removed, false if absent. */
|
|
73
|
+
evict(state_id) {
|
|
74
|
+
return this._map.delete(state_id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Insertion-ordered list of keys (oldest → newest). */
|
|
78
|
+
list() {
|
|
79
|
+
return Array.from(this._map.keys());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
size() {
|
|
83
|
+
return this._map.size;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
clear() {
|
|
87
|
+
this._map.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Mint a state_id for a freshly created composition.
|
|
93
|
+
*
|
|
94
|
+
* Format: `<intent-prefix>-<rand4>-v<N>-<unix-min>`
|
|
95
|
+
* - intent-prefix — first whitespace-delimited word of the intent, lowercased,
|
|
96
|
+
* non-alphanumeric stripped, capped at 12 chars; falls back to `state` when empty.
|
|
97
|
+
* - rand4 — 4 hex chars; prevents collisions on identical intents.
|
|
98
|
+
* - v<N> — 1-based version (v1 for created, v2+ for refinements).
|
|
99
|
+
* - unix-min — Math.floor(Date.now() / 60000), giving a compact minute-resolution
|
|
100
|
+
* timestamp that humans can scan for chronological order.
|
|
101
|
+
*
|
|
102
|
+
* Spec: docs/specs/genui-multiturn-architecture.md §2.2.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} intent
|
|
105
|
+
* @param {number} [version=1]
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
export function mintStateId(intent, version = 1) {
|
|
109
|
+
const prefix = (intent ?? '')
|
|
110
|
+
.toLowerCase()
|
|
111
|
+
.split(/\s+/)[0]
|
|
112
|
+
?.replace(/[^a-z0-9]/g, '')
|
|
113
|
+
?.slice(0, 12) || 'state';
|
|
114
|
+
const rand4 = Math.floor(Math.random() * 0xffff).toString(16).padStart(4, '0');
|
|
115
|
+
const unixMin = Math.floor(Date.now() / 60000);
|
|
116
|
+
const v = Math.max(1, Math.floor(version));
|
|
117
|
+
return `${prefix}-${rand4}-v${v}-${unixMin}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Mint the next state_id in a refinement chain.
|
|
122
|
+
*
|
|
123
|
+
* Preserves the parent's prefix + rand4 stem so the lineage is visible at a
|
|
124
|
+
* glance (`dash-3f9a-v1-…` → `dash-3f9a-v2-…`). Bumps the version and
|
|
125
|
+
* timestamp. Falls back to a fresh `mintStateId` if the parent id doesn't
|
|
126
|
+
* match the expected format.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} parentStateId
|
|
129
|
+
* @param {number} version — should be parent's version + 1
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
export function mintNextStateId(parentStateId, version) {
|
|
133
|
+
const m = parentStateId?.match(/^([a-z0-9]+)-([a-f0-9]{4})-v\d+-\d+$/);
|
|
134
|
+
if (!m) return mintStateId(parentStateId ?? '', version);
|
|
135
|
+
const [, prefix, rand4] = m;
|
|
136
|
+
const unixMin = Math.floor(Date.now() / 60000);
|
|
137
|
+
const v = Math.max(1, Math.floor(version));
|
|
138
|
+
return `${prefix}-${rand4}-v${v}-${unixMin}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let _cache = null;
|
|
142
|
+
|
|
143
|
+
/** Module-level singleton. Lazily instantiated. */
|
|
144
|
+
export function getStateCache(opts) {
|
|
145
|
+
if (!_cache) _cache = new StateCache(opts);
|
|
146
|
+
return _cache;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Replace the singleton; useful for tests + server boot. */
|
|
150
|
+
export function resetStateCache(opts) {
|
|
151
|
+
_cache = new StateCache(opts);
|
|
152
|
+
return _cache;
|
|
153
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/a2ui-compose",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
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": {
|