@llui/vite-plugin 0.0.34 → 0.0.35
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/dist/msg-schema.d.ts +15 -3
- package/dist/msg-schema.d.ts.map +1 -1
- package/dist/msg-schema.js +254 -18
- package/dist/msg-schema.js.map +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +37 -4
- package/dist/transform.js.map +1 -1
- package/package.json +1 -1
package/dist/msg-schema.d.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
/**
|
|
3
|
-
* The "bare type" of a field. Covers
|
|
3
|
+
* The "bare type" of a field. Covers five cases:
|
|
4
4
|
* - primitive keyword as a string: `'string'`, `'number'`, `'boolean'`, `'unknown'`
|
|
5
|
-
* -
|
|
5
|
+
* - literal union: `{enum: ['a', 'b']}` for strings, `{enum: [1, 2, 3]}`
|
|
6
|
+
* for numbers, `{enum: [true]}` for booleans. Mixed-type literal
|
|
7
|
+
* unions stay `'unknown'`.
|
|
6
8
|
* - nested object shape: `{kind: 'object', shape: {...}}` — emitted when
|
|
7
9
|
* a field's type is a local interface/type alias the extractor could
|
|
8
10
|
* follow (depth-limited; cross-file references stay `'unknown'`).
|
|
9
11
|
* - array of element type: `{kind: 'array', element: <bare type>}`.
|
|
12
|
+
* - discriminated union of objects: `{kind: 'discriminated-union',
|
|
13
|
+
* discriminant: 'kind', variants: {a: {...}, b: {...}}}`. Emitted
|
|
14
|
+
* when every member of a union is an object literal sharing one
|
|
15
|
+
* literal-string property name with distinct values. Symmetric with
|
|
16
|
+
* how the top-level Msg union itself is encoded — same shape,
|
|
17
|
+
* recursed.
|
|
10
18
|
*
|
|
11
19
|
* The synthesizer in `@llui/agent`'s `list_actions` walks these to build
|
|
12
20
|
* copy-paste-ready payload examples; the validator in `send_message`
|
|
@@ -14,13 +22,17 @@ import ts from 'typescript';
|
|
|
14
22
|
* is the reducer's job).
|
|
15
23
|
*/
|
|
16
24
|
export type MsgFieldType = string | {
|
|
17
|
-
enum: string
|
|
25
|
+
enum: ReadonlyArray<string | number | boolean>;
|
|
18
26
|
} | {
|
|
19
27
|
kind: 'object';
|
|
20
28
|
shape: Record<string, MsgField>;
|
|
21
29
|
} | {
|
|
22
30
|
kind: 'array';
|
|
23
31
|
element: MsgFieldType;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'discriminated-union';
|
|
34
|
+
discriminant: string;
|
|
35
|
+
variants: Record<string, Record<string, MsgField>>;
|
|
24
36
|
};
|
|
25
37
|
/**
|
|
26
38
|
* Rich per-field descriptor. Emitted only when there's something
|
package/dist/msg-schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"msg-schema.d.ts","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B
|
|
1
|
+
{"version":3,"file":"msg-schema.d.ts","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN;IAAE,IAAI,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACxC;IACE,IAAI,EAAE,qBAAqB,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;CACnD,CAAA;AAEL;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB;sCACkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,YAAY,CAAA;AAElD,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;CACnD;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,IAAI,YAAY,CAE1D;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,CAAC,EAAE,QAAQ,GAAG,YAAY,CAEnD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAc,GAAG,SAAS,GAAG,IAAI,CAE3F;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAiB,GAAG,SAAS,GAAG,IAAI,CAEjG;AAqBD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,GAAG,EAAE,CAAC,oBAAoB,CAAC,CAAA;AAsD1E;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,EAAE,CAAC,iBAAiB,EAC5B,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,SAAqB,GAC/B,QAAQ,CAkBV;AAkOD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,EAAE,CAAC,QAAQ,EACjB,SAAS,GAAE,SAAqB,EAChC,KAAK,SAAkB,GACtB,YAAY,CAyGd"}
|
package/dist/msg-schema.js
CHANGED
|
@@ -108,11 +108,230 @@ export function buildFieldDescriptor(member, source, typeIndex = new Map()) {
|
|
|
108
108
|
* collapses to `'unknown'`; the synthesizer emits `null` and the
|
|
109
109
|
* agent falls back to free-form filling.
|
|
110
110
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
111
|
+
* 5 covers the realistic ceiling for production Msg payloads —
|
|
112
|
+
* `Matrix/AddCriteria.criteria[].format.kind` lives at depth 4, and
|
|
113
|
+
* adding one buffer level catches common app shapes. Higher depths
|
|
114
|
+
* grow the bundle linearly; recursive cycles still terminate via the
|
|
115
|
+
* decrement-and-bail rule on every recurse.
|
|
114
116
|
*/
|
|
115
|
-
const MAX_FIELD_DEPTH =
|
|
117
|
+
const MAX_FIELD_DEPTH = 5;
|
|
118
|
+
/**
|
|
119
|
+
* Detect literal-only unions whose members all share one primitive
|
|
120
|
+
* type — `'a' | 'b' | 'c'`, `1 | 2 | 3`, or `true | false`. Returns
|
|
121
|
+
* the enum descriptor on success; null if any member isn't a literal
|
|
122
|
+
* of the same type as the others.
|
|
123
|
+
*
|
|
124
|
+
* Mixed-type unions (`'a' | 1`) and unions that include non-literal
|
|
125
|
+
* members fall through. The agent gets `'unknown'` for those rather
|
|
126
|
+
* than an enum that loses the type information mid-list.
|
|
127
|
+
*/
|
|
128
|
+
function tryExtractLiteralUnion(union) {
|
|
129
|
+
const values = [];
|
|
130
|
+
let kind = null;
|
|
131
|
+
for (const member of union.types) {
|
|
132
|
+
if (!ts.isLiteralTypeNode(member))
|
|
133
|
+
return null;
|
|
134
|
+
const lit = member.literal;
|
|
135
|
+
if (ts.isStringLiteral(lit)) {
|
|
136
|
+
if (kind === null)
|
|
137
|
+
kind = 'string';
|
|
138
|
+
else if (kind !== 'string')
|
|
139
|
+
return null;
|
|
140
|
+
values.push(lit.text);
|
|
141
|
+
}
|
|
142
|
+
else if (ts.isNumericLiteral(lit)) {
|
|
143
|
+
if (kind === null)
|
|
144
|
+
kind = 'number';
|
|
145
|
+
else if (kind !== 'number')
|
|
146
|
+
return null;
|
|
147
|
+
const n = Number(lit.text);
|
|
148
|
+
if (!Number.isFinite(n))
|
|
149
|
+
return null;
|
|
150
|
+
values.push(n);
|
|
151
|
+
}
|
|
152
|
+
else if (lit.kind === ts.SyntaxKind.TrueKeyword) {
|
|
153
|
+
if (kind === null)
|
|
154
|
+
kind = 'boolean';
|
|
155
|
+
else if (kind !== 'boolean')
|
|
156
|
+
return null;
|
|
157
|
+
values.push(true);
|
|
158
|
+
}
|
|
159
|
+
else if (lit.kind === ts.SyntaxKind.FalseKeyword) {
|
|
160
|
+
if (kind === null)
|
|
161
|
+
kind = 'boolean';
|
|
162
|
+
else if (kind !== 'boolean')
|
|
163
|
+
return null;
|
|
164
|
+
values.push(false);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (values.length === 0)
|
|
171
|
+
return null;
|
|
172
|
+
return { enum: values };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Detect a discriminated union of object types — every member is an
|
|
176
|
+
* object literal (or named type alias resolving to one) and every
|
|
177
|
+
* member declares the same property as a string-literal type with a
|
|
178
|
+
* value distinct from every other member's. Examples:
|
|
179
|
+
*
|
|
180
|
+
* {kind:'a'} | {kind:'b', x:number} → discriminant 'kind'
|
|
181
|
+
* {tag:'x',v:1} | {tag:'y',v:'s'} → discriminant 'tag'
|
|
182
|
+
*
|
|
183
|
+
* Returns the union descriptor on success; null on any failure
|
|
184
|
+
* (different shape per branch, no shared discriminant key, non-literal
|
|
185
|
+
* discriminant value, primitive member, etc.). Bailing to null lets
|
|
186
|
+
* the caller emit `'unknown'` rather than a partially-valid descriptor.
|
|
187
|
+
*
|
|
188
|
+
* `depth` is the budget for resolving each branch's payload. The
|
|
189
|
+
* caller subtracts one before calling, since detecting the union
|
|
190
|
+
* itself doesn't consume budget — recursing into branches does.
|
|
191
|
+
*/
|
|
192
|
+
function tryExtractDiscriminatedUnion(union, typeIndex, depth) {
|
|
193
|
+
// Resolve each branch to its underlying object literal node, chasing
|
|
194
|
+
// through type-alias references in the local index. Returns null if
|
|
195
|
+
// any branch isn't an object-literal-shaped type.
|
|
196
|
+
const branches = [];
|
|
197
|
+
for (const member of union.types) {
|
|
198
|
+
const lit = resolveToTypeLiteral(member, typeIndex);
|
|
199
|
+
if (lit === null)
|
|
200
|
+
return null;
|
|
201
|
+
branches.push(lit);
|
|
202
|
+
}
|
|
203
|
+
if (branches.length === 0)
|
|
204
|
+
return null;
|
|
205
|
+
// Find a property name that EVERY branch declares with a string-
|
|
206
|
+
// literal value, and where the values are pairwise distinct.
|
|
207
|
+
// Iterate over the first branch's properties; for each candidate
|
|
208
|
+
// name, check the rest.
|
|
209
|
+
const first = branches[0];
|
|
210
|
+
if (!first)
|
|
211
|
+
return null;
|
|
212
|
+
let discriminant = null;
|
|
213
|
+
let firstBranchValue = null;
|
|
214
|
+
for (const member of first.members) {
|
|
215
|
+
if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
|
|
216
|
+
continue;
|
|
217
|
+
if (!member.type)
|
|
218
|
+
continue;
|
|
219
|
+
if (!ts.isLiteralTypeNode(member.type) || !ts.isStringLiteral(member.type.literal))
|
|
220
|
+
continue;
|
|
221
|
+
const candidate = member.name.text;
|
|
222
|
+
const valuesByBranch = [member.type.literal.text];
|
|
223
|
+
let ok = true;
|
|
224
|
+
for (let i = 1; i < branches.length; i++) {
|
|
225
|
+
const branch = branches[i];
|
|
226
|
+
if (!branch) {
|
|
227
|
+
ok = false;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
const otherValue = literalDiscriminantValue(branch, candidate);
|
|
231
|
+
if (otherValue === null) {
|
|
232
|
+
ok = false;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
valuesByBranch.push(otherValue);
|
|
236
|
+
}
|
|
237
|
+
if (!ok)
|
|
238
|
+
continue;
|
|
239
|
+
// All distinct?
|
|
240
|
+
const uniq = new Set(valuesByBranch);
|
|
241
|
+
if (uniq.size !== valuesByBranch.length)
|
|
242
|
+
continue;
|
|
243
|
+
discriminant = candidate;
|
|
244
|
+
firstBranchValue = member.type.literal.text;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
if (discriminant === null || firstBranchValue === null)
|
|
248
|
+
return null;
|
|
249
|
+
// Build the variant payload map. Each variant's payload is the
|
|
250
|
+
// branch's properties EXCEPT the discriminant itself (which the
|
|
251
|
+
// synthesizer re-adds at example time, like the top-level Msg `type`).
|
|
252
|
+
const variants = {};
|
|
253
|
+
for (const branch of branches) {
|
|
254
|
+
const value = literalDiscriminantValue(branch, discriminant);
|
|
255
|
+
if (value === null)
|
|
256
|
+
return null;
|
|
257
|
+
const fields = {};
|
|
258
|
+
for (const member of branch.members) {
|
|
259
|
+
if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
|
|
260
|
+
continue;
|
|
261
|
+
const name = member.name.text;
|
|
262
|
+
if (name === discriminant)
|
|
263
|
+
continue;
|
|
264
|
+
const baseType = member.type
|
|
265
|
+
? resolveFieldType(member.type, typeIndex, depth)
|
|
266
|
+
: 'unknown';
|
|
267
|
+
const optional = member.questionToken !== undefined;
|
|
268
|
+
fields[name] = optional ? { type: baseType, optional: true } : baseType;
|
|
269
|
+
}
|
|
270
|
+
variants[value] = fields;
|
|
271
|
+
}
|
|
272
|
+
return { kind: 'discriminated-union', discriminant, variants };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Resolve a type node down to an inline object-literal type node,
|
|
276
|
+
* following one level of named-reference indirection through the local
|
|
277
|
+
* index. Returns null when the type isn't (or can't be reduced to) an
|
|
278
|
+
* object literal. We only chase one hop because every additional hop
|
|
279
|
+
* needs a depth budget to terminate, and discriminated-union detection
|
|
280
|
+
* is bounded by the outer caller's budget already.
|
|
281
|
+
*/
|
|
282
|
+
function resolveToTypeLiteral(t, typeIndex) {
|
|
283
|
+
if (ts.isTypeLiteralNode(t))
|
|
284
|
+
return t;
|
|
285
|
+
if (ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName)) {
|
|
286
|
+
const target = typeIndex.get(t.typeName.text);
|
|
287
|
+
if (!target)
|
|
288
|
+
return null;
|
|
289
|
+
if (ts.isInterfaceDeclaration(target)) {
|
|
290
|
+
// Synthesize a TypeLiteralNode-like shape from the interface
|
|
291
|
+
// members. Cheaper than reconstructing the AST: we only need
|
|
292
|
+
// the members to drive collectInlineShape semantics, but the
|
|
293
|
+
// discriminated-union detector reads property signatures, which
|
|
294
|
+
// interfaces have directly. We shim via a property-list view.
|
|
295
|
+
return interfaceToTypeLiteralLike(target);
|
|
296
|
+
}
|
|
297
|
+
if (ts.isTypeNode(target)) {
|
|
298
|
+
// Type alias: recurse one level.
|
|
299
|
+
return resolveToTypeLiteral(target, typeIndex);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Adapter for interface declarations: discriminated-union detection
|
|
306
|
+
* and field iteration only need the members list, which both
|
|
307
|
+
* `TypeLiteralNode` and `InterfaceDeclaration` expose. We return the
|
|
308
|
+
* interface cast as a TypeLiteralNode-shaped object so the rest of
|
|
309
|
+
* this file's helpers (which check `ts.isPropertySignature(member)`)
|
|
310
|
+
* work uniformly across both node kinds.
|
|
311
|
+
*/
|
|
312
|
+
function interfaceToTypeLiteralLike(iface) {
|
|
313
|
+
return { members: iface.members };
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Read a string-literal property value from an object-literal-like
|
|
317
|
+
* member list, or null if the named property isn't present, isn't a
|
|
318
|
+
* property signature, or isn't typed as a string literal.
|
|
319
|
+
*/
|
|
320
|
+
function literalDiscriminantValue(lit, name) {
|
|
321
|
+
for (const member of lit.members) {
|
|
322
|
+
if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
|
|
323
|
+
continue;
|
|
324
|
+
if (member.name.text !== name)
|
|
325
|
+
continue;
|
|
326
|
+
if (!member.type)
|
|
327
|
+
return null;
|
|
328
|
+
if (ts.isLiteralTypeNode(member.type) && ts.isStringLiteral(member.type.literal)) {
|
|
329
|
+
return member.type.literal.text;
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
116
335
|
export function resolveFieldType(type, typeIndex = new Map(), depth = MAX_FIELD_DEPTH) {
|
|
117
336
|
// Primitive keywords
|
|
118
337
|
if (type.kind === ts.SyntaxKind.StringKeyword)
|
|
@@ -121,21 +340,38 @@ export function resolveFieldType(type, typeIndex = new Map(), depth = MAX_FIELD_
|
|
|
121
340
|
return 'number';
|
|
122
341
|
if (type.kind === ts.SyntaxKind.BooleanKeyword)
|
|
123
342
|
return 'boolean';
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
343
|
+
// Standalone literal type — `flag: true` or `value: 5`. Single-value
|
|
344
|
+
// enum so the schema records the constant rather than collapsing it
|
|
345
|
+
// to 'unknown'. Useful for sentinel discriminants outside discriminated
|
|
346
|
+
// unions (e.g. `kind: 'always-this-one'` on a non-union type).
|
|
347
|
+
if (ts.isLiteralTypeNode(type)) {
|
|
348
|
+
const lit = type.literal;
|
|
349
|
+
if (ts.isStringLiteral(lit))
|
|
350
|
+
return { enum: [lit.text] };
|
|
351
|
+
if (ts.isNumericLiteral(lit)) {
|
|
352
|
+
const n = Number(lit.text);
|
|
353
|
+
return Number.isFinite(n) ? { enum: [n] } : 'unknown';
|
|
136
354
|
}
|
|
137
|
-
if (
|
|
138
|
-
return { enum:
|
|
355
|
+
if (lit.kind === ts.SyntaxKind.TrueKeyword)
|
|
356
|
+
return { enum: [true] };
|
|
357
|
+
if (lit.kind === ts.SyntaxKind.FalseKeyword)
|
|
358
|
+
return { enum: [false] };
|
|
359
|
+
}
|
|
360
|
+
// Union of literals — 'a' | 'b' (strings), 1 | 2 | 3 (numbers), or
|
|
361
|
+
// true / false (booleans). Mixed-type unions ('a' | 1) bail to
|
|
362
|
+
// 'unknown' — the LLM can't reason about that shape from the schema
|
|
363
|
+
// alone, so we'd rather not emit a misleading enum than enumerate
|
|
364
|
+
// the values without their types.
|
|
365
|
+
if (ts.isUnionTypeNode(type)) {
|
|
366
|
+
const enumResult = tryExtractLiteralUnion(type);
|
|
367
|
+
if (enumResult !== null)
|
|
368
|
+
return enumResult;
|
|
369
|
+
// Discriminated union of object literals — depth-budgeted because
|
|
370
|
+
// each branch may contain its own nested shapes.
|
|
371
|
+
if (depth > 0) {
|
|
372
|
+
const discResult = tryExtractDiscriminatedUnion(type, typeIndex, depth - 1);
|
|
373
|
+
if (discResult !== null)
|
|
374
|
+
return discResult;
|
|
139
375
|
}
|
|
140
376
|
}
|
|
141
377
|
// Below this point, all branches need depth budget. Bail out cheaply.
|
package/dist/msg-schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"msg-schema.js","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * The \"bare type\" of a field. Covers four cases:\n * - primitive keyword as a string: `'string'`, `'number'`, `'boolean'`, `'unknown'`\n * - string-literal union: `{enum: ['a', 'b']}`\n * - nested object shape: `{kind: 'object', shape: {...}}` — emitted when\n * a field's type is a local interface/type alias the extractor could\n * follow (depth-limited; cross-file references stay `'unknown'`).\n * - array of element type: `{kind: 'array', element: <bare type>}`.\n *\n * The synthesizer in `@llui/agent`'s `list_actions` walks these to build\n * copy-paste-ready payload examples; the validator in `send_message`\n * walks them too (treating object/array as \"any\" since deep validation\n * is the reducer's job).\n */\nexport type MsgFieldType =\n | string\n | { enum: string[] }\n | { kind: 'object'; shape: Record<string, MsgField> }\n | { kind: 'array'; element: MsgFieldType }\n\n/**\n * Rich per-field descriptor. Emitted only when there's something\n * beyond the bare type to communicate — optionality, an explicit\n * priority hint, or a freeform agent hint. When everything but `type`\n * is unset, the producer emits the bare `MsgFieldType` instead so\n * variants without annotations stay byte-cheap in the bundle.\n */\nexport interface MsgFieldRich {\n type: MsgFieldType\n /** Mirrors TypeScript's `?:` optional marker. Required fields omit this. */\n optional?: boolean\n /**\n * Strength signal for optional fields. Borrows RFC 2119's `SHOULD`:\n * the LLM ought to fill it in unless it has a specific reason not\n * to. Required fields don't carry a priority — TS already conveys\n * \"must\" via the type system. Currently the only level; future\n * extensions could add `'recommended'` or similar.\n */\n priority?: 'should'\n /** Freeform consequence-shaped explanation. Surfaced verbatim to\n * the LLM at affordance time. */\n hint?: string\n}\n\nexport type MsgField = MsgFieldType | MsgFieldRich\n\nexport interface MsgSchema {\n discriminant: string\n variants: Record<string, Record<string, MsgField>>\n}\n\n/** True when `f` is a rich descriptor (object with `type` key). */\nexport function isRichField(f: MsgField): f is MsgFieldRich {\n return typeof f === 'object' && f !== null && !Array.isArray(f) && 'type' in f\n}\n\n/** Extracts the bare type from either descriptor form. */\nexport function fieldType(f: MsgField): MsgFieldType {\n return isRichField(f) ? f.type : f\n}\n\nexport function extractMsgSchema(source: string, typeName: string = 'Msg'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nexport function extractEffectSchema(source: string, typeName: string = 'Effect'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nfunction extractDiscriminatedUnionSchema(source: string, typeName: string): MsgSchema | null {\n const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n const typeIndex = buildTypeIndex(sf)\n\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== typeName) continue\n\n const variants: MsgSchema['variants'] = {}\n collectVariants(stmt.type, variants, source, typeIndex)\n\n if (Object.keys(variants).length === 0) return null\n\n return { discriminant: 'type', variants }\n }\n\n return null\n}\n\n/**\n * Index of type aliases and interfaces visible from a source file,\n * keyed by name. Lets the field-type resolver follow `Criterion[]` →\n * `interface Criterion { … }` and emit a nested object shape rather\n * than `'unknown'`.\n *\n * The cross-file resolver pipeline (`cross-file-resolver.ts`) builds\n * an enriched index that includes types imported from sibling files —\n * follow `GridSorting` → `'rank' | 'crit-X' | 'crit-Y'` → `{enum: […]}`\n * even when the alias lives in `./state.ts` not the Msg-defining file.\n */\nexport type TypeIndex = Map<string, ts.TypeNode | ts.InterfaceDeclaration>\n\nfunction buildTypeIndex(sf: ts.SourceFile): TypeIndex {\n const index: TypeIndex = new Map()\n for (const stmt of sf.statements) {\n if (ts.isTypeAliasDeclaration(stmt)) {\n index.set(stmt.name.text, stmt.type)\n } else if (ts.isInterfaceDeclaration(stmt)) {\n index.set(stmt.name.text, stmt)\n }\n }\n return index\n}\n\nfunction collectVariants(\n type: ts.TypeNode,\n variants: MsgSchema['variants'],\n source: string,\n typeIndex: TypeIndex,\n): void {\n if (ts.isUnionTypeNode(type)) {\n for (const member of type.types) {\n collectVariants(member, variants, source, typeIndex)\n }\n return\n }\n\n if (ts.isTypeLiteralNode(type)) {\n let discriminantValue: string | null = null\n const fields: Record<string, MsgField> = {}\n\n for (const member of type.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n\n const name = member.name.text\n const memberType = member.type\n\n if (name === 'type' && memberType) {\n // Extract the discriminant value\n if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {\n discriminantValue = memberType.literal.text\n }\n continue\n }\n\n fields[name] = buildFieldDescriptor(member, source, typeIndex)\n }\n\n if (discriminantValue) {\n variants[discriminantValue] = fields\n }\n }\n}\n\n/**\n * Build a single field descriptor from a property signature: type,\n * optionality, and any `@should(\"…\")` JSDoc hint. Emits the compact\n * bare form when there's nothing extra to communicate; otherwise the\n * rich `{type, optional?, priority?, hint?}` shape.\n *\n * Exported so the cross-file resolver (which walks the same property\n * signatures when the Msg type lives in a different file from the\n * `component()` call) can produce identical descriptors. Without\n * sharing this helper, JSDoc hints would silently disappear whenever\n * a Msg union got resolved across module boundaries.\n */\nexport function buildFieldDescriptor(\n member: ts.PropertySignature,\n source: string,\n typeIndex: TypeIndex = new Map(),\n): MsgField {\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, MAX_FIELD_DEPTH)\n : 'unknown'\n const optional = member.questionToken !== undefined\n const jsdoc = readMemberJSDoc(source, member)\n const hint = readShouldHint(jsdoc)\n\n if (!optional && hint === null) {\n return baseType\n }\n const rich: MsgFieldRich = { type: baseType }\n if (optional) rich.optional = true\n if (hint !== null) {\n rich.priority = 'should'\n rich.hint = hint\n }\n return rich\n}\n\n/**\n * Recursion bound for nested type resolution. Stops the extractor\n * before it spirals on self-referential or mutually-recursive types\n * (`type Tree = { children: Tree[] }`). At depth 0 every reference\n * collapses to `'unknown'`; the synthesizer emits `null` and the\n * agent falls back to free-form filling.\n *\n * 3 covers the common cases (Msg payload → Criterion → ValueMeta),\n * keeps the bundle bounded, and is well under the Tarjan-style\n * depths needed for actual recursive types.\n */\nconst MAX_FIELD_DEPTH = 3\n\nexport function resolveFieldType(\n type: ts.TypeNode,\n typeIndex: TypeIndex = new Map(),\n depth = MAX_FIELD_DEPTH,\n): MsgFieldType {\n // Primitive keywords\n if (type.kind === ts.SyntaxKind.StringKeyword) return 'string'\n if (type.kind === ts.SyntaxKind.NumberKeyword) return 'number'\n if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean'\n\n // String literal union: 'a' | 'b' | 'c'\n if (ts.isUnionTypeNode(type)) {\n const literals: string[] = []\n let allLiterals = true\n for (const member of type.types) {\n if (ts.isLiteralTypeNode(member) && ts.isStringLiteral(member.literal)) {\n literals.push(member.literal.text)\n } else {\n allLiterals = false\n break\n }\n }\n if (allLiterals && literals.length > 0) {\n return { enum: literals }\n }\n }\n\n // Below this point, all branches need depth budget. Bail out cheaply.\n if (depth <= 0) return 'unknown'\n\n // Inline object literal — `{a: number; b: string}` directly.\n if (ts.isTypeLiteralNode(type)) {\n return { kind: 'object', shape: collectInlineShape(type, typeIndex, depth - 1) }\n }\n\n // Array type — `T[]` and `readonly T[]`.\n if (ts.isArrayTypeNode(type)) {\n return { kind: 'array', element: resolveFieldType(type.elementType, typeIndex, depth - 1) }\n }\n // Generic Array<T> (less common in app code but compiler may produce it).\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'Array' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // ReadonlyArray<T> → same shape; the readonly modifier is purely a\n // TypeScript-side concern that the agent never observes at runtime.\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'ReadonlyArray' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // `readonly T[]` parses as TypeOperator(readonly) wrapping ArrayType.\n if (ts.isTypeOperatorNode(type) && type.operator === ts.SyntaxKind.ReadonlyKeyword) {\n return resolveFieldType(type.type, typeIndex, depth)\n }\n\n // Named type reference — chase it through the local index.\n if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {\n const target = typeIndex.get(type.typeName.text)\n if (target) {\n if (ts.isInterfaceDeclaration(target)) {\n return {\n kind: 'object',\n shape: collectInterfaceShape(target, typeIndex, depth - 1),\n }\n }\n // Type alias — recurse on its body. `type Foo = …` could resolve\n // to anything (object literal, array, union, primitive); each\n // already has its own branch above.\n return resolveFieldType(target, typeIndex, depth - 1)\n }\n // Reference to a type the index doesn't know about — typically\n // imported from another module. Cross-file resolution is the\n // separate cross-file-resolver pipeline's job; leave this as\n // unknown rather than fabricating a misleading shape.\n return 'unknown'\n }\n\n return 'unknown'\n}\n\nfunction collectInlineShape(\n lit: ts.TypeLiteralNode,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of lit.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\nfunction collectInterfaceShape(\n iface: ts.InterfaceDeclaration,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of iface.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\n/**\n * Read the leading JSDoc block immediately above `member`. The\n * TypeScript parser doesn't attach JSDoc to interior property\n * signatures, so we re-scan the source between the previous member's\n * end (or the type-literal's `{`) and this member's start, and return\n * the last `/** … *\\/` block found there. Returns `''` when none.\n */\nfunction readMemberJSDoc(source: string, member: ts.PropertySignature): string {\n const ranges = ts.getLeadingCommentRanges(source, member.pos) ?? []\n // Walk in order, keeping only `/** */` blocks. Multiple back-to-back\n // JSDocs concatenate (matches msg-annotations.ts's existing behavior).\n const docs = ranges\n .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)\n .map((r) => source.slice(r.pos, r.end))\n .filter((txt) => txt.startsWith('/**'))\n return docs.join('\\n')\n}\n\n/**\n * Match `@should(\"…\")` (and curly-quote variant) anywhere in the\n * JSDoc. Mirrors msg-annotations.ts's `@intent` parser — same grammar,\n * same tolerance for either ASCII or curly quotes.\n *\n * Returns the unescaped string content, or null when the tag is\n * absent or malformed.\n */\nfunction readShouldHint(comment: string): string | null {\n if (!comment) return null\n const match = comment.match(/@should\\s*\\(\\s*[\"“]([^\"”]*)[\"”]\\s*\\)/)\n return match?.[1] ?? null\n}\n"]}
|
|
1
|
+
{"version":3,"file":"msg-schema.js","sourceRoot":"","sources":["../src/msg-schema.ts"],"names":[],"mappings":"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","sourcesContent":["import ts from 'typescript'\n\n/**\n * The \"bare type\" of a field. Covers five cases:\n * - primitive keyword as a string: `'string'`, `'number'`, `'boolean'`, `'unknown'`\n * - literal union: `{enum: ['a', 'b']}` for strings, `{enum: [1, 2, 3]}`\n * for numbers, `{enum: [true]}` for booleans. Mixed-type literal\n * unions stay `'unknown'`.\n * - nested object shape: `{kind: 'object', shape: {...}}` — emitted when\n * a field's type is a local interface/type alias the extractor could\n * follow (depth-limited; cross-file references stay `'unknown'`).\n * - array of element type: `{kind: 'array', element: <bare type>}`.\n * - discriminated union of objects: `{kind: 'discriminated-union',\n * discriminant: 'kind', variants: {a: {...}, b: {...}}}`. Emitted\n * when every member of a union is an object literal sharing one\n * literal-string property name with distinct values. Symmetric with\n * how the top-level Msg union itself is encoded — same shape,\n * recursed.\n *\n * The synthesizer in `@llui/agent`'s `list_actions` walks these to build\n * copy-paste-ready payload examples; the validator in `send_message`\n * walks them too (treating object/array as \"any\" since deep validation\n * is the reducer's job).\n */\nexport type MsgFieldType =\n | string\n | { enum: ReadonlyArray<string | number | boolean> }\n | { kind: 'object'; shape: Record<string, MsgField> }\n | { kind: 'array'; element: MsgFieldType }\n | {\n kind: 'discriminated-union'\n discriminant: string\n variants: Record<string, Record<string, MsgField>>\n }\n\n/**\n * Rich per-field descriptor. Emitted only when there's something\n * beyond the bare type to communicate — optionality, an explicit\n * priority hint, or a freeform agent hint. When everything but `type`\n * is unset, the producer emits the bare `MsgFieldType` instead so\n * variants without annotations stay byte-cheap in the bundle.\n */\nexport interface MsgFieldRich {\n type: MsgFieldType\n /** Mirrors TypeScript's `?:` optional marker. Required fields omit this. */\n optional?: boolean\n /**\n * Strength signal for optional fields. Borrows RFC 2119's `SHOULD`:\n * the LLM ought to fill it in unless it has a specific reason not\n * to. Required fields don't carry a priority — TS already conveys\n * \"must\" via the type system. Currently the only level; future\n * extensions could add `'recommended'` or similar.\n */\n priority?: 'should'\n /** Freeform consequence-shaped explanation. Surfaced verbatim to\n * the LLM at affordance time. */\n hint?: string\n}\n\nexport type MsgField = MsgFieldType | MsgFieldRich\n\nexport interface MsgSchema {\n discriminant: string\n variants: Record<string, Record<string, MsgField>>\n}\n\n/** True when `f` is a rich descriptor (object with `type` key). */\nexport function isRichField(f: MsgField): f is MsgFieldRich {\n return typeof f === 'object' && f !== null && !Array.isArray(f) && 'type' in f\n}\n\n/** Extracts the bare type from either descriptor form. */\nexport function fieldType(f: MsgField): MsgFieldType {\n return isRichField(f) ? f.type : f\n}\n\nexport function extractMsgSchema(source: string, typeName: string = 'Msg'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nexport function extractEffectSchema(source: string, typeName: string = 'Effect'): MsgSchema | null {\n return extractDiscriminatedUnionSchema(source, typeName)\n}\n\nfunction extractDiscriminatedUnionSchema(source: string, typeName: string): MsgSchema | null {\n const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true)\n const typeIndex = buildTypeIndex(sf)\n\n for (const stmt of sf.statements) {\n if (!ts.isTypeAliasDeclaration(stmt)) continue\n if (stmt.name.text !== typeName) continue\n\n const variants: MsgSchema['variants'] = {}\n collectVariants(stmt.type, variants, source, typeIndex)\n\n if (Object.keys(variants).length === 0) return null\n\n return { discriminant: 'type', variants }\n }\n\n return null\n}\n\n/**\n * Index of type aliases and interfaces visible from a source file,\n * keyed by name. Lets the field-type resolver follow `Criterion[]` →\n * `interface Criterion { … }` and emit a nested object shape rather\n * than `'unknown'`.\n *\n * The cross-file resolver pipeline (`cross-file-resolver.ts`) builds\n * an enriched index that includes types imported from sibling files —\n * follow `GridSorting` → `'rank' | 'crit-X' | 'crit-Y'` → `{enum: […]}`\n * even when the alias lives in `./state.ts` not the Msg-defining file.\n */\nexport type TypeIndex = Map<string, ts.TypeNode | ts.InterfaceDeclaration>\n\nfunction buildTypeIndex(sf: ts.SourceFile): TypeIndex {\n const index: TypeIndex = new Map()\n for (const stmt of sf.statements) {\n if (ts.isTypeAliasDeclaration(stmt)) {\n index.set(stmt.name.text, stmt.type)\n } else if (ts.isInterfaceDeclaration(stmt)) {\n index.set(stmt.name.text, stmt)\n }\n }\n return index\n}\n\nfunction collectVariants(\n type: ts.TypeNode,\n variants: MsgSchema['variants'],\n source: string,\n typeIndex: TypeIndex,\n): void {\n if (ts.isUnionTypeNode(type)) {\n for (const member of type.types) {\n collectVariants(member, variants, source, typeIndex)\n }\n return\n }\n\n if (ts.isTypeLiteralNode(type)) {\n let discriminantValue: string | null = null\n const fields: Record<string, MsgField> = {}\n\n for (const member of type.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n\n const name = member.name.text\n const memberType = member.type\n\n if (name === 'type' && memberType) {\n // Extract the discriminant value\n if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {\n discriminantValue = memberType.literal.text\n }\n continue\n }\n\n fields[name] = buildFieldDescriptor(member, source, typeIndex)\n }\n\n if (discriminantValue) {\n variants[discriminantValue] = fields\n }\n }\n}\n\n/**\n * Build a single field descriptor from a property signature: type,\n * optionality, and any `@should(\"…\")` JSDoc hint. Emits the compact\n * bare form when there's nothing extra to communicate; otherwise the\n * rich `{type, optional?, priority?, hint?}` shape.\n *\n * Exported so the cross-file resolver (which walks the same property\n * signatures when the Msg type lives in a different file from the\n * `component()` call) can produce identical descriptors. Without\n * sharing this helper, JSDoc hints would silently disappear whenever\n * a Msg union got resolved across module boundaries.\n */\nexport function buildFieldDescriptor(\n member: ts.PropertySignature,\n source: string,\n typeIndex: TypeIndex = new Map(),\n): MsgField {\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, MAX_FIELD_DEPTH)\n : 'unknown'\n const optional = member.questionToken !== undefined\n const jsdoc = readMemberJSDoc(source, member)\n const hint = readShouldHint(jsdoc)\n\n if (!optional && hint === null) {\n return baseType\n }\n const rich: MsgFieldRich = { type: baseType }\n if (optional) rich.optional = true\n if (hint !== null) {\n rich.priority = 'should'\n rich.hint = hint\n }\n return rich\n}\n\n/**\n * Recursion bound for nested type resolution. Stops the extractor\n * before it spirals on self-referential or mutually-recursive types\n * (`type Tree = { children: Tree[] }`). At depth 0 every reference\n * collapses to `'unknown'`; the synthesizer emits `null` and the\n * agent falls back to free-form filling.\n *\n * 5 covers the realistic ceiling for production Msg payloads —\n * `Matrix/AddCriteria.criteria[].format.kind` lives at depth 4, and\n * adding one buffer level catches common app shapes. Higher depths\n * grow the bundle linearly; recursive cycles still terminate via the\n * decrement-and-bail rule on every recurse.\n */\nconst MAX_FIELD_DEPTH = 5\n\n/**\n * Detect literal-only unions whose members all share one primitive\n * type — `'a' | 'b' | 'c'`, `1 | 2 | 3`, or `true | false`. Returns\n * the enum descriptor on success; null if any member isn't a literal\n * of the same type as the others.\n *\n * Mixed-type unions (`'a' | 1`) and unions that include non-literal\n * members fall through. The agent gets `'unknown'` for those rather\n * than an enum that loses the type information mid-list.\n */\nfunction tryExtractLiteralUnion(\n union: ts.UnionTypeNode,\n): { enum: Array<string | number | boolean> } | null {\n const values: Array<string | number | boolean> = []\n let kind: 'string' | 'number' | 'boolean' | null = null\n\n for (const member of union.types) {\n if (!ts.isLiteralTypeNode(member)) return null\n const lit = member.literal\n if (ts.isStringLiteral(lit)) {\n if (kind === null) kind = 'string'\n else if (kind !== 'string') return null\n values.push(lit.text)\n } else if (ts.isNumericLiteral(lit)) {\n if (kind === null) kind = 'number'\n else if (kind !== 'number') return null\n const n = Number(lit.text)\n if (!Number.isFinite(n)) return null\n values.push(n)\n } else if (lit.kind === ts.SyntaxKind.TrueKeyword) {\n if (kind === null) kind = 'boolean'\n else if (kind !== 'boolean') return null\n values.push(true)\n } else if (lit.kind === ts.SyntaxKind.FalseKeyword) {\n if (kind === null) kind = 'boolean'\n else if (kind !== 'boolean') return null\n values.push(false)\n } else {\n return null\n }\n }\n\n if (values.length === 0) return null\n return { enum: values }\n}\n\n/**\n * Detect a discriminated union of object types — every member is an\n * object literal (or named type alias resolving to one) and every\n * member declares the same property as a string-literal type with a\n * value distinct from every other member's. Examples:\n *\n * {kind:'a'} | {kind:'b', x:number} → discriminant 'kind'\n * {tag:'x',v:1} | {tag:'y',v:'s'} → discriminant 'tag'\n *\n * Returns the union descriptor on success; null on any failure\n * (different shape per branch, no shared discriminant key, non-literal\n * discriminant value, primitive member, etc.). Bailing to null lets\n * the caller emit `'unknown'` rather than a partially-valid descriptor.\n *\n * `depth` is the budget for resolving each branch's payload. The\n * caller subtracts one before calling, since detecting the union\n * itself doesn't consume budget — recursing into branches does.\n */\nfunction tryExtractDiscriminatedUnion(\n union: ts.UnionTypeNode,\n typeIndex: TypeIndex,\n depth: number,\n): {\n kind: 'discriminated-union'\n discriminant: string\n variants: Record<string, Record<string, MsgField>>\n} | null {\n // Resolve each branch to its underlying object literal node, chasing\n // through type-alias references in the local index. Returns null if\n // any branch isn't an object-literal-shaped type.\n const branches: ts.TypeLiteralNode[] = []\n for (const member of union.types) {\n const lit = resolveToTypeLiteral(member, typeIndex)\n if (lit === null) return null\n branches.push(lit)\n }\n if (branches.length === 0) return null\n\n // Find a property name that EVERY branch declares with a string-\n // literal value, and where the values are pairwise distinct.\n // Iterate over the first branch's properties; for each candidate\n // name, check the rest.\n const first = branches[0]\n if (!first) return null\n let discriminant: string | null = null\n let firstBranchValue: string | null = null\n for (const member of first.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n if (!member.type) continue\n if (!ts.isLiteralTypeNode(member.type) || !ts.isStringLiteral(member.type.literal)) continue\n const candidate = member.name.text\n\n const valuesByBranch: string[] = [member.type.literal.text]\n let ok = true\n for (let i = 1; i < branches.length; i++) {\n const branch = branches[i]\n if (!branch) {\n ok = false\n break\n }\n const otherValue = literalDiscriminantValue(branch, candidate)\n if (otherValue === null) {\n ok = false\n break\n }\n valuesByBranch.push(otherValue)\n }\n if (!ok) continue\n\n // All distinct?\n const uniq = new Set(valuesByBranch)\n if (uniq.size !== valuesByBranch.length) continue\n\n discriminant = candidate\n firstBranchValue = member.type.literal.text\n break\n }\n\n if (discriminant === null || firstBranchValue === null) return null\n\n // Build the variant payload map. Each variant's payload is the\n // branch's properties EXCEPT the discriminant itself (which the\n // synthesizer re-adds at example time, like the top-level Msg `type`).\n const variants: Record<string, Record<string, MsgField>> = {}\n for (const branch of branches) {\n const value = literalDiscriminantValue(branch, discriminant)\n if (value === null) return null\n const fields: Record<string, MsgField> = {}\n for (const member of branch.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n if (name === discriminant) continue\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n fields[name] = optional ? { type: baseType, optional: true } : baseType\n }\n variants[value] = fields\n }\n\n return { kind: 'discriminated-union', discriminant, variants }\n}\n\n/**\n * Resolve a type node down to an inline object-literal type node,\n * following one level of named-reference indirection through the local\n * index. Returns null when the type isn't (or can't be reduced to) an\n * object literal. We only chase one hop because every additional hop\n * needs a depth budget to terminate, and discriminated-union detection\n * is bounded by the outer caller's budget already.\n */\nfunction resolveToTypeLiteral(t: ts.TypeNode, typeIndex: TypeIndex): ts.TypeLiteralNode | null {\n if (ts.isTypeLiteralNode(t)) return t\n if (ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName)) {\n const target = typeIndex.get(t.typeName.text)\n if (!target) return null\n if (ts.isInterfaceDeclaration(target)) {\n // Synthesize a TypeLiteralNode-like shape from the interface\n // members. Cheaper than reconstructing the AST: we only need\n // the members to drive collectInlineShape semantics, but the\n // discriminated-union detector reads property signatures, which\n // interfaces have directly. We shim via a property-list view.\n return interfaceToTypeLiteralLike(target)\n }\n if (ts.isTypeNode(target)) {\n // Type alias: recurse one level.\n return resolveToTypeLiteral(target, typeIndex)\n }\n }\n return null\n}\n\n/**\n * Adapter for interface declarations: discriminated-union detection\n * and field iteration only need the members list, which both\n * `TypeLiteralNode` and `InterfaceDeclaration` expose. We return the\n * interface cast as a TypeLiteralNode-shaped object so the rest of\n * this file's helpers (which check `ts.isPropertySignature(member)`)\n * work uniformly across both node kinds.\n */\nfunction interfaceToTypeLiteralLike(iface: ts.InterfaceDeclaration): ts.TypeLiteralNode {\n return { members: iface.members } as unknown as ts.TypeLiteralNode\n}\n\n/**\n * Read a string-literal property value from an object-literal-like\n * member list, or null if the named property isn't present, isn't a\n * property signature, or isn't typed as a string literal.\n */\nfunction literalDiscriminantValue(lit: ts.TypeLiteralNode, name: string): string | null {\n for (const member of lit.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n if (member.name.text !== name) continue\n if (!member.type) return null\n if (ts.isLiteralTypeNode(member.type) && ts.isStringLiteral(member.type.literal)) {\n return member.type.literal.text\n }\n return null\n }\n return null\n}\n\nexport function resolveFieldType(\n type: ts.TypeNode,\n typeIndex: TypeIndex = new Map(),\n depth = MAX_FIELD_DEPTH,\n): MsgFieldType {\n // Primitive keywords\n if (type.kind === ts.SyntaxKind.StringKeyword) return 'string'\n if (type.kind === ts.SyntaxKind.NumberKeyword) return 'number'\n if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'boolean'\n\n // Standalone literal type — `flag: true` or `value: 5`. Single-value\n // enum so the schema records the constant rather than collapsing it\n // to 'unknown'. Useful for sentinel discriminants outside discriminated\n // unions (e.g. `kind: 'always-this-one'` on a non-union type).\n if (ts.isLiteralTypeNode(type)) {\n const lit = type.literal\n if (ts.isStringLiteral(lit)) return { enum: [lit.text] }\n if (ts.isNumericLiteral(lit)) {\n const n = Number(lit.text)\n return Number.isFinite(n) ? { enum: [n] } : 'unknown'\n }\n if (lit.kind === ts.SyntaxKind.TrueKeyword) return { enum: [true] }\n if (lit.kind === ts.SyntaxKind.FalseKeyword) return { enum: [false] }\n }\n\n // Union of literals — 'a' | 'b' (strings), 1 | 2 | 3 (numbers), or\n // true / false (booleans). Mixed-type unions ('a' | 1) bail to\n // 'unknown' — the LLM can't reason about that shape from the schema\n // alone, so we'd rather not emit a misleading enum than enumerate\n // the values without their types.\n if (ts.isUnionTypeNode(type)) {\n const enumResult = tryExtractLiteralUnion(type)\n if (enumResult !== null) return enumResult\n\n // Discriminated union of object literals — depth-budgeted because\n // each branch may contain its own nested shapes.\n if (depth > 0) {\n const discResult = tryExtractDiscriminatedUnion(type, typeIndex, depth - 1)\n if (discResult !== null) return discResult\n }\n }\n\n // Below this point, all branches need depth budget. Bail out cheaply.\n if (depth <= 0) return 'unknown'\n\n // Inline object literal — `{a: number; b: string}` directly.\n if (ts.isTypeLiteralNode(type)) {\n return { kind: 'object', shape: collectInlineShape(type, typeIndex, depth - 1) }\n }\n\n // Array type — `T[]` and `readonly T[]`.\n if (ts.isArrayTypeNode(type)) {\n return { kind: 'array', element: resolveFieldType(type.elementType, typeIndex, depth - 1) }\n }\n // Generic Array<T> (less common in app code but compiler may produce it).\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'Array' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // ReadonlyArray<T> → same shape; the readonly modifier is purely a\n // TypeScript-side concern that the agent never observes at runtime.\n if (\n ts.isTypeReferenceNode(type) &&\n ts.isIdentifier(type.typeName) &&\n type.typeName.text === 'ReadonlyArray' &&\n type.typeArguments?.length === 1 &&\n type.typeArguments[0]\n ) {\n return {\n kind: 'array',\n element: resolveFieldType(type.typeArguments[0], typeIndex, depth - 1),\n }\n }\n // `readonly T[]` parses as TypeOperator(readonly) wrapping ArrayType.\n if (ts.isTypeOperatorNode(type) && type.operator === ts.SyntaxKind.ReadonlyKeyword) {\n return resolveFieldType(type.type, typeIndex, depth)\n }\n\n // Named type reference — chase it through the local index.\n if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {\n const target = typeIndex.get(type.typeName.text)\n if (target) {\n if (ts.isInterfaceDeclaration(target)) {\n return {\n kind: 'object',\n shape: collectInterfaceShape(target, typeIndex, depth - 1),\n }\n }\n // Type alias — recurse on its body. `type Foo = …` could resolve\n // to anything (object literal, array, union, primitive); each\n // already has its own branch above.\n return resolveFieldType(target, typeIndex, depth - 1)\n }\n // Reference to a type the index doesn't know about — typically\n // imported from another module. Cross-file resolution is the\n // separate cross-file-resolver pipeline's job; leave this as\n // unknown rather than fabricating a misleading shape.\n return 'unknown'\n }\n\n return 'unknown'\n}\n\nfunction collectInlineShape(\n lit: ts.TypeLiteralNode,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of lit.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\nfunction collectInterfaceShape(\n iface: ts.InterfaceDeclaration,\n typeIndex: TypeIndex,\n depth: number,\n): Record<string, MsgField> {\n const shape: Record<string, MsgField> = {}\n for (const member of iface.members) {\n if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name)) continue\n const name = member.name.text\n const baseType: MsgFieldType = member.type\n ? resolveFieldType(member.type, typeIndex, depth)\n : 'unknown'\n const optional = member.questionToken !== undefined\n if (!optional) {\n shape[name] = baseType\n } else {\n shape[name] = { type: baseType, optional: true }\n }\n }\n return shape\n}\n\n/**\n * Read the leading JSDoc block immediately above `member`. The\n * TypeScript parser doesn't attach JSDoc to interior property\n * signatures, so we re-scan the source between the previous member's\n * end (or the type-literal's `{`) and this member's start, and return\n * the last `/** … *\\/` block found there. Returns `''` when none.\n */\nfunction readMemberJSDoc(source: string, member: ts.PropertySignature): string {\n const ranges = ts.getLeadingCommentRanges(source, member.pos) ?? []\n // Walk in order, keeping only `/** */` blocks. Multiple back-to-back\n // JSDocs concatenate (matches msg-annotations.ts's existing behavior).\n const docs = ranges\n .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)\n .map((r) => source.slice(r.pos, r.end))\n .filter((txt) => txt.startsWith('/**'))\n return docs.join('\\n')\n}\n\n/**\n * Match `@should(\"…\")` (and curly-quote variant) anywhere in the\n * JSDoc. Mirrors msg-annotations.ts's `@intent` parser — same grammar,\n * same tolerance for either ASCII or curly quotes.\n *\n * Returns the unescaped string content, or null when the tag is\n * absent or malformed.\n */\nfunction readShouldHint(comment: string): string | null {\n if (!comment) return null\n const match = comment.match(/@should\\s*\\(\\s*[\"“]([^\"”]*)[\"”]\\s*\\)/)\n return match?.[1] ?? null\n}\n"]}
|
package/dist/transform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAIpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,qBAAqB,EAA2B,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,kBAAkB,EAAkB,MAAM,mBAAmB,CAAA;AA6KtE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;IAC/C,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAA;CACtD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,EACf,WAAW,CAAC,EAAE,mBAAmB,EACjC,YAAY,CAAC,EAAE,mBAAmB,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAqYnD;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../src/transform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAA;AAE3B,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EAIpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,qBAAqB,EAA2B,MAAM,sBAAsB,CAAA;AACrF,OAAO,EAAE,kBAAkB,EAAkB,MAAM,mBAAmB,CAAA;AA6KtE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,GAAG,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAA;IAC/C,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;IACnD,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAA;CACtD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,UAAQ,EACf,iBAAiB,UAAQ,EACzB,OAAO,GAAE,MAAM,GAAG,IAAW,EAC7B,OAAO,UAAQ,EACf,WAAW,CAAC,EAAE,mBAAmB,EACjC,YAAY,CAAC,EAAE,mBAAmB,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAAG,IAAI,CAqYnD;AAusID,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAoGjC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CA4B7D;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,GAAG,MAAM,GAAG,IAAI,CAcrF;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CASzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,GAChE,MAAM,CAYR"}
|
package/dist/transform.js
CHANGED
|
@@ -2263,12 +2263,15 @@ function buildFieldDescriptorExpr(descriptor, f) {
|
|
|
2263
2263
|
return f.createObjectLiteralExpression(props);
|
|
2264
2264
|
}
|
|
2265
2265
|
// The remaining cases are bare type-shape variants emitted by
|
|
2266
|
-
// `resolveFieldType`: enum, object, array.
|
|
2267
|
-
// key is present so we never confuse an inline
|
|
2268
|
-
// ({enum: [...]}) with a deeply-nested shape descriptor.
|
|
2266
|
+
// `resolveFieldType`: enum, object, array, discriminated-union.
|
|
2267
|
+
// Discriminate by which key is present so we never confuse an inline
|
|
2268
|
+
// object literal ({enum: [...]}) with a deeply-nested shape descriptor.
|
|
2269
2269
|
if ('enum' in descriptor) {
|
|
2270
|
+
// Mixed-typed enum values (string/number/boolean). Each enum entry
|
|
2271
|
+
// emits with its native literal kind so JSON round-trips preserve
|
|
2272
|
+
// the type information — `1` stays a number, not stringified.
|
|
2270
2273
|
return f.createObjectLiteralExpression([
|
|
2271
|
-
f.createPropertyAssignment('enum', f.createArrayLiteralExpression(descriptor.enum.map((v) =>
|
|
2274
|
+
f.createPropertyAssignment('enum', f.createArrayLiteralExpression(descriptor.enum.map((v) => emitEnumValue(v, f)))),
|
|
2272
2275
|
]);
|
|
2273
2276
|
}
|
|
2274
2277
|
if ('kind' in descriptor && descriptor.kind === 'object') {
|
|
@@ -2284,12 +2287,42 @@ function buildFieldDescriptorExpr(descriptor, f) {
|
|
|
2284
2287
|
f.createPropertyAssignment('shape', f.createObjectLiteralExpression(shapeProps)),
|
|
2285
2288
|
]);
|
|
2286
2289
|
}
|
|
2290
|
+
if ('kind' in descriptor && descriptor.kind === 'discriminated-union') {
|
|
2291
|
+
// Inner-union variants are themselves field maps (per branch),
|
|
2292
|
+
// recursed through the same builder. Symmetric with how the
|
|
2293
|
+
// top-level Msg union's variants are represented.
|
|
2294
|
+
const variantProps = [];
|
|
2295
|
+
for (const [discValue, fields] of Object.entries(descriptor.variants)) {
|
|
2296
|
+
const fieldProps = [];
|
|
2297
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
2298
|
+
fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(k), buildFieldDescriptorExpr(v, f)));
|
|
2299
|
+
}
|
|
2300
|
+
variantProps.push(f.createPropertyAssignment(f.createStringLiteral(discValue), f.createObjectLiteralExpression(fieldProps, true)));
|
|
2301
|
+
}
|
|
2302
|
+
return f.createObjectLiteralExpression([
|
|
2303
|
+
f.createPropertyAssignment('kind', f.createStringLiteral('discriminated-union')),
|
|
2304
|
+
f.createPropertyAssignment('discriminant', f.createStringLiteral(descriptor.discriminant)),
|
|
2305
|
+
f.createPropertyAssignment('variants', f.createObjectLiteralExpression(variantProps, true)),
|
|
2306
|
+
]);
|
|
2307
|
+
}
|
|
2287
2308
|
// Array — `{kind: 'array', element: <bare type>}`.
|
|
2288
2309
|
return f.createObjectLiteralExpression([
|
|
2289
2310
|
f.createPropertyAssignment('kind', f.createStringLiteral('array')),
|
|
2290
2311
|
f.createPropertyAssignment('element', buildFieldDescriptorExpr(descriptor.element, f)),
|
|
2291
2312
|
]);
|
|
2292
2313
|
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Emit a single enum value as the right TS literal kind. Numbers as
|
|
2316
|
+
* numeric literals, booleans as keyword expressions, strings as string
|
|
2317
|
+
* literals — preserves the type at the wire (JSON round-trips correctly).
|
|
2318
|
+
*/
|
|
2319
|
+
function emitEnumValue(v, f) {
|
|
2320
|
+
if (typeof v === 'string')
|
|
2321
|
+
return f.createStringLiteral(v);
|
|
2322
|
+
if (typeof v === 'number')
|
|
2323
|
+
return f.createNumericLiteral(v);
|
|
2324
|
+
return v ? f.createTrue() : f.createFalse();
|
|
2325
|
+
}
|
|
2293
2326
|
function hasNonDefaultAnnotation(a) {
|
|
2294
2327
|
for (const v of Object.values(a)) {
|
|
2295
2328
|
if (v.intent !== null)
|