@lokascript/framework 2.0.0 → 2.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/dist/aot/index.d.ts +1 -0
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/registry-bridge.d.ts +27 -0
- package/dist/aot/registry-bridge.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/dispatcher.d.ts +22 -0
- package/dist/api/dispatcher.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts +41 -0
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +1351 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +114 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js +74 -10
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
- package/dist/core/types.d.ts +80 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +40 -0
- package/dist/core/types.js.map +1 -1
- package/dist/feedback/confidence-gate.d.ts +28 -0
- package/dist/feedback/confidence-gate.d.ts.map +1 -0
- package/dist/feedback/feedback-formatter.d.ts +21 -0
- package/dist/feedback/feedback-formatter.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +11 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/pattern-tracker.d.ts +47 -0
- package/dist/feedback/pattern-tracker.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +113 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/generation/index.js +2 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +2685 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2652 -71
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +31 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +17 -0
- package/dist/ir/explicit-renderer.d.ts.map +1 -0
- package/dist/ir/from-interchange.d.ts +51 -0
- package/dist/ir/from-interchange.d.ts.map +1 -0
- package/dist/ir/index.d.ts +29 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1557 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/json-schema.d.ts +40 -0
- package/dist/ir/json-schema.d.ts.map +1 -0
- package/dist/ir/protocol-json.d.ts +73 -0
- package/dist/ir/protocol-json.d.ts.map +1 -0
- package/dist/ir/references.d.ts +19 -0
- package/dist/ir/references.d.ts.map +1 -0
- package/dist/ir/types.d.ts +141 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/parsing/index.js +74 -10
- package/dist/parsing/index.js.map +1 -1
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-generator.d.ts +49 -0
- package/dist/prompts/prompt-generator.d.ts.map +1 -0
- package/dist/prompts/types.d.ts +64 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +17 -0
- package/dist/schema/command-schema.d.ts.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/training/index.d.ts +11 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/jsonl-writer.d.ts +42 -0
- package/dist/training/jsonl-writer.d.ts.map +1 -0
- package/dist/training/schema-synthesizer.d.ts +20 -0
- package/dist/training/schema-synthesizer.d.ts.map +1 -0
- package/dist/training/types.d.ts +73 -0
- package/dist/training/types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aot/index.ts +1 -0
- package/src/aot/registry-bridge.test.ts +104 -0
- package/src/aot/registry-bridge.ts +66 -0
- package/src/api/create-dsl.test.ts +306 -0
- package/src/api/create-dsl.ts +21 -0
- package/src/api/dispatcher.test.ts +66 -0
- package/src/api/dispatcher.ts +77 -9
- package/src/api/domain-registry.ts +120 -4
- package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
- package/src/core/pattern-matching/pattern-matcher.ts +102 -19
- package/src/core/types.ts +145 -2
- package/src/feedback/confidence-gate.test.ts +111 -0
- package/src/feedback/confidence-gate.ts +70 -0
- package/src/feedback/feedback-formatter.test.ts +198 -0
- package/src/feedback/feedback-formatter.ts +298 -0
- package/src/feedback/index.ts +24 -0
- package/src/feedback/pattern-tracker.test.ts +198 -0
- package/src/feedback/pattern-tracker.ts +140 -0
- package/src/feedback/types.ts +149 -0
- package/src/generation/pattern-generator.ts +2 -2
- package/src/index.ts +22 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/explicit-parser.test.ts +306 -0
- package/src/ir/explicit-parser.ts +419 -0
- package/src/ir/explicit-renderer.test.ts +144 -0
- package/src/ir/explicit-renderer.ts +146 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +66 -0
- package/src/ir/json-schema.test.ts +281 -0
- package/src/ir/json-schema.ts +299 -0
- package/src/ir/protocol-json.test.ts +1185 -0
- package/src/ir/protocol-json.ts +745 -0
- package/src/ir/references.test.ts +38 -0
- package/src/ir/references.ts +33 -0
- package/src/ir/round-trip.test.ts +557 -0
- package/src/ir/types.ts +181 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/prompt-generator.test.ts +386 -0
- package/src/prompts/prompt-generator.ts +531 -0
- package/src/prompts/types.ts +89 -0
- package/src/schema/command-schema.ts +19 -0
- package/src/training/index.ts +13 -0
- package/src/training/jsonl-writer.test.ts +138 -0
- package/src/training/jsonl-writer.ts +67 -0
- package/src/training/schema-synthesizer.test.ts +250 -0
- package/src/training/schema-synthesizer.ts +306 -0
- package/src/training/types.ts +103 -0
package/dist/api/index.js
CHANGED
|
@@ -11,6 +11,65 @@ function createReference(value) {
|
|
|
11
11
|
function createPropertyPath(object, property) {
|
|
12
12
|
return { type: "property-path", object, property };
|
|
13
13
|
}
|
|
14
|
+
function createFlag(name, enabled = true) {
|
|
15
|
+
return { type: "flag", name, enabled };
|
|
16
|
+
}
|
|
17
|
+
function createCommandNode(action, roles, metadata) {
|
|
18
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
19
|
+
const node = {
|
|
20
|
+
kind: "command",
|
|
21
|
+
action,
|
|
22
|
+
roles: rolesMap
|
|
23
|
+
};
|
|
24
|
+
if (metadata) {
|
|
25
|
+
return { ...node, metadata };
|
|
26
|
+
}
|
|
27
|
+
return node;
|
|
28
|
+
}
|
|
29
|
+
function createEventHandlerNode(action, roles, body, metadata, eventModifiers) {
|
|
30
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
31
|
+
const base = {
|
|
32
|
+
kind: "event-handler",
|
|
33
|
+
action,
|
|
34
|
+
roles: rolesMap,
|
|
35
|
+
body
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
...eventModifiers && { eventModifiers },
|
|
40
|
+
...metadata && { metadata }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createConditionalNode(action, roles, thenBranch, elseBranch, metadata) {
|
|
44
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
45
|
+
const base = {
|
|
46
|
+
kind: "conditional",
|
|
47
|
+
action,
|
|
48
|
+
roles: rolesMap,
|
|
49
|
+
thenBranch
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
...elseBranch && { elseBranch },
|
|
54
|
+
...metadata && { metadata }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function createLoopNode(action, roles, loopVariant, body, loopVariable, indexVariable, metadata) {
|
|
58
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
59
|
+
const base = {
|
|
60
|
+
kind: "loop",
|
|
61
|
+
action,
|
|
62
|
+
roles: rolesMap,
|
|
63
|
+
loopVariant,
|
|
64
|
+
body
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
...base,
|
|
68
|
+
...loopVariable && { loopVariable },
|
|
69
|
+
...indexVariable && { indexVariable },
|
|
70
|
+
...metadata && { metadata }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
14
73
|
|
|
15
74
|
// src/core/logger.ts
|
|
16
75
|
var DebugLogger = class {
|
|
@@ -224,7 +283,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
224
283
|
* a token and the immediately following pattern token fails, the matcher
|
|
225
284
|
* resets to before the optional role and retries the failed token.
|
|
226
285
|
*/
|
|
227
|
-
matchTokenSequence(tokens, patternTokens, captured) {
|
|
286
|
+
matchTokenSequence(tokens, patternTokens, captured, parentStopMarkers) {
|
|
228
287
|
const firstPatternToken = patternTokens[0];
|
|
229
288
|
const patternExpectsConjunction = firstPatternToken?.type === "literal" && (firstPatternToken.value === "and" || firstPatternToken.value === "then" || firstPatternToken.alternatives?.includes("and") || firstPatternToken.alternatives?.includes("then"));
|
|
230
289
|
if (this.currentProfile?.code === "ar" && !patternExpectsConjunction) {
|
|
@@ -248,6 +307,9 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
248
307
|
);
|
|
249
308
|
if (patternToken.type === "role" && patternToken.greedy) {
|
|
250
309
|
const stopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
310
|
+
if (parentStopMarkers) {
|
|
311
|
+
for (const m of parentStopMarkers) stopMarkers.add(m);
|
|
312
|
+
}
|
|
251
313
|
const values = [];
|
|
252
314
|
while (!tokens.isAtEnd()) {
|
|
253
315
|
const nextToken = tokens.peek();
|
|
@@ -267,8 +329,58 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
267
329
|
return false;
|
|
268
330
|
}
|
|
269
331
|
}
|
|
332
|
+
if (patternToken.type === "group") {
|
|
333
|
+
const siblingStopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
334
|
+
if (parentStopMarkers) {
|
|
335
|
+
for (const m of parentStopMarkers) siblingStopMarkers.add(m);
|
|
336
|
+
}
|
|
337
|
+
const groupMatched = this.matchGroupToken(
|
|
338
|
+
tokens,
|
|
339
|
+
patternToken,
|
|
340
|
+
captured,
|
|
341
|
+
siblingStopMarkers
|
|
342
|
+
);
|
|
343
|
+
if (groupMatched) {
|
|
344
|
+
prevOptionalMark = null;
|
|
345
|
+
prevOptionalRole = null;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (this.isOptional(patternToken)) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
270
353
|
const isOptionalRole = patternToken.type === "role" && patternToken.optional === true;
|
|
271
354
|
const markBefore = isOptionalRole ? tokens.mark() : null;
|
|
355
|
+
const isRoleToken = patternToken.type === "role";
|
|
356
|
+
if (isRoleToken && !isOptionalRole) {
|
|
357
|
+
const nextPT = i + 1 < patternTokens.length ? patternTokens[i + 1] : null;
|
|
358
|
+
if (nextPT?.type === "literal") {
|
|
359
|
+
const currentToken = tokens.peek();
|
|
360
|
+
if (currentToken && this.getMatchType(currentToken, nextPT.value) !== "none") {
|
|
361
|
+
this.logger.debug(
|
|
362
|
+
" >> MARKER-STEAL GUARD: current token matches next literal, skipping role"
|
|
363
|
+
);
|
|
364
|
+
this.logger.debug(" >> Token match FAILED");
|
|
365
|
+
if (this.isOptional(patternToken)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (prevOptionalMark && prevOptionalRole) {
|
|
369
|
+
this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
|
|
370
|
+
tokens.reset(prevOptionalMark);
|
|
371
|
+
captured.delete(prevOptionalRole);
|
|
372
|
+
prevOptionalMark = null;
|
|
373
|
+
prevOptionalRole = null;
|
|
374
|
+
const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
|
|
375
|
+
this.logger.debug(" >> Backtrack retry result:", retryMatched);
|
|
376
|
+
if (retryMatched) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
272
384
|
const matched = this.matchPatternToken(tokens, patternToken, captured);
|
|
273
385
|
this.logger.debug(" >> Match result:", matched);
|
|
274
386
|
if (matched) {
|
|
@@ -389,7 +501,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
389
501
|
const possessiveValue = this.tryMatchPossessiveExpression(tokens);
|
|
390
502
|
if (possessiveValue) {
|
|
391
503
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
392
|
-
if (!
|
|
504
|
+
if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
|
|
393
505
|
return patternToken.optional || false;
|
|
394
506
|
}
|
|
395
507
|
}
|
|
@@ -399,7 +511,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
399
511
|
const methodCallValue = this.tryMatchMethodCallExpression(tokens);
|
|
400
512
|
if (methodCallValue) {
|
|
401
513
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
402
|
-
if (!
|
|
514
|
+
if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
|
|
403
515
|
return patternToken.optional || false;
|
|
404
516
|
}
|
|
405
517
|
}
|
|
@@ -419,7 +531,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
419
531
|
const propertyAccessValue = this.tryMatchPropertyAccessExpression(tokens);
|
|
420
532
|
if (propertyAccessValue) {
|
|
421
533
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
422
|
-
if (!
|
|
534
|
+
if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
|
|
423
535
|
return patternToken.optional || false;
|
|
424
536
|
}
|
|
425
537
|
}
|
|
@@ -739,10 +851,15 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
739
851
|
* (e.g., a value like 'hello') to sit between groups without blocking later
|
|
740
852
|
* marker matches.
|
|
741
853
|
*/
|
|
742
|
-
matchGroupToken(tokens, patternToken, captured) {
|
|
854
|
+
matchGroupToken(tokens, patternToken, captured, parentStopMarkers) {
|
|
743
855
|
const mark = tokens.mark();
|
|
744
856
|
const capturedBefore = new Set(captured.keys());
|
|
745
|
-
const success = this.matchTokenSequence(
|
|
857
|
+
const success = this.matchTokenSequence(
|
|
858
|
+
tokens,
|
|
859
|
+
patternToken.tokens,
|
|
860
|
+
captured,
|
|
861
|
+
parentStopMarkers
|
|
862
|
+
);
|
|
746
863
|
if (success) return true;
|
|
747
864
|
tokens.reset(mark);
|
|
748
865
|
for (const role of captured.keys()) {
|
|
@@ -764,7 +881,12 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
764
881
|
"- skipping intervening tokens"
|
|
765
882
|
);
|
|
766
883
|
for (let s = 0; s < offset; s++) tokens.advance();
|
|
767
|
-
const retrySuccess = this.matchTokenSequence(
|
|
884
|
+
const retrySuccess = this.matchTokenSequence(
|
|
885
|
+
tokens,
|
|
886
|
+
patternToken.tokens,
|
|
887
|
+
captured,
|
|
888
|
+
parentStopMarkers
|
|
889
|
+
);
|
|
768
890
|
if (retrySuccess) return true;
|
|
769
891
|
tokens.reset(mark);
|
|
770
892
|
for (const role of captured.keys()) {
|
|
@@ -831,7 +953,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
831
953
|
break;
|
|
832
954
|
}
|
|
833
955
|
}
|
|
834
|
-
break;
|
|
956
|
+
if (!pt.optional) break;
|
|
835
957
|
}
|
|
836
958
|
}
|
|
837
959
|
return markers;
|
|
@@ -841,8 +963,9 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
841
963
|
*/
|
|
842
964
|
isStopMarker(token, stopMarkers) {
|
|
843
965
|
if (stopMarkers.size === 0) return false;
|
|
844
|
-
|
|
845
|
-
|
|
966
|
+
if (stopMarkers.has(token.value.toLowerCase())) return true;
|
|
967
|
+
if (token.normalized && stopMarkers.has(token.normalized.toLowerCase())) return true;
|
|
968
|
+
return false;
|
|
846
969
|
}
|
|
847
970
|
/**
|
|
848
971
|
* Convert a language token to a semantic value.
|
|
@@ -1241,7 +1364,7 @@ function addRoleWithMarker(tokens, roleSpec, profile) {
|
|
|
1241
1364
|
if (marker) {
|
|
1242
1365
|
const markerInfo = profile.roleMarkers?.[roleSpec.role];
|
|
1243
1366
|
const defaultPosition = profile.wordOrder === "SOV" ? "after" : "before";
|
|
1244
|
-
const position = markerInfo?.position ?? defaultPosition;
|
|
1367
|
+
const position = roleSpec.markerPosition ?? markerInfo?.position ?? defaultPosition;
|
|
1245
1368
|
if (isOptional) {
|
|
1246
1369
|
if (position === "before") {
|
|
1247
1370
|
tokens.push({
|
|
@@ -1534,6 +1657,368 @@ var InMemoryProfileProvider = class {
|
|
|
1534
1657
|
}
|
|
1535
1658
|
};
|
|
1536
1659
|
|
|
1660
|
+
// src/ir/references.ts
|
|
1661
|
+
var DEFAULT_REFERENCES = /* @__PURE__ */ new Set([
|
|
1662
|
+
"me",
|
|
1663
|
+
"you",
|
|
1664
|
+
"it",
|
|
1665
|
+
"result",
|
|
1666
|
+
"event",
|
|
1667
|
+
"target",
|
|
1668
|
+
"body"
|
|
1669
|
+
]);
|
|
1670
|
+
function isValidReference2(value, referenceSet = DEFAULT_REFERENCES) {
|
|
1671
|
+
return referenceSet.has(value);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// src/ir/explicit-parser.ts
|
|
1675
|
+
var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
1676
|
+
"body",
|
|
1677
|
+
"then",
|
|
1678
|
+
"else",
|
|
1679
|
+
"condition",
|
|
1680
|
+
"loop-body",
|
|
1681
|
+
"variable",
|
|
1682
|
+
"catch",
|
|
1683
|
+
// v1.2: try/catch/finally
|
|
1684
|
+
"finally"
|
|
1685
|
+
// v1.2: try/catch/finally
|
|
1686
|
+
]);
|
|
1687
|
+
function isNestedCommand(value) {
|
|
1688
|
+
const inner = value.slice(1, -1);
|
|
1689
|
+
let depth = 0;
|
|
1690
|
+
for (const ch of inner) {
|
|
1691
|
+
if (ch === "[") depth++;
|
|
1692
|
+
else if (ch === "]") depth--;
|
|
1693
|
+
else if (depth === 0 && (ch === " " || ch === ":")) return true;
|
|
1694
|
+
}
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
function parseExplicit(input, options = {}) {
|
|
1698
|
+
const trimmed = input.trim();
|
|
1699
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
|
|
1700
|
+
throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
|
|
1701
|
+
}
|
|
1702
|
+
const content = trimmed.slice(1, -1).trim();
|
|
1703
|
+
if (!content) {
|
|
1704
|
+
throw new Error("Empty explicit statement");
|
|
1705
|
+
}
|
|
1706
|
+
const tokens = tokenizeExplicit(content);
|
|
1707
|
+
if (tokens.length === 0) {
|
|
1708
|
+
throw new Error("No command specified in explicit statement");
|
|
1709
|
+
}
|
|
1710
|
+
const command = tokens[0].toLowerCase();
|
|
1711
|
+
const roles = /* @__PURE__ */ new Map();
|
|
1712
|
+
const refSet = options.referenceSet ?? DEFAULT_REFERENCES;
|
|
1713
|
+
const schema = options.schemaLookup?.getSchema(command);
|
|
1714
|
+
const validRoleNames = schema ? new Set(schema.roles.map((r) => r.role)) : null;
|
|
1715
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
1716
|
+
const token = tokens[i];
|
|
1717
|
+
if (token.startsWith("+") || token.startsWith("~")) {
|
|
1718
|
+
const enabled = token.startsWith("+");
|
|
1719
|
+
const flagName = token.slice(1);
|
|
1720
|
+
if (!flagName) {
|
|
1721
|
+
throw new Error(`Empty flag name: "${token}"`);
|
|
1722
|
+
}
|
|
1723
|
+
if (validRoleNames && !validRoleNames.has(flagName)) {
|
|
1724
|
+
const roleList = [...validRoleNames].join(", ");
|
|
1725
|
+
throw new Error(
|
|
1726
|
+
`Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
const colonIndex = token.indexOf(":");
|
|
1733
|
+
if (colonIndex === -1) {
|
|
1734
|
+
throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
|
|
1735
|
+
}
|
|
1736
|
+
const roleName = token.slice(0, colonIndex);
|
|
1737
|
+
if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
|
|
1738
|
+
const roleList = [...validRoleNames].join(", ");
|
|
1739
|
+
throw new Error(
|
|
1740
|
+
`Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
const role = roleName;
|
|
1744
|
+
const valueStr = token.slice(colonIndex + 1);
|
|
1745
|
+
if (STRUCTURAL_ROLES.has(roleName) && valueStr.startsWith("[") && isNestedCommand(valueStr)) {
|
|
1746
|
+
const nestedEnd = findMatchingBracket(token, colonIndex + 1);
|
|
1747
|
+
const nestedSyntax = token.slice(colonIndex + 1, nestedEnd + 1);
|
|
1748
|
+
roles.set(role, { type: "expression", raw: nestedSyntax });
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
const value = parseExplicitValue(valueStr, refSet);
|
|
1752
|
+
roles.set(role, value);
|
|
1753
|
+
}
|
|
1754
|
+
if (schema && command !== "on") {
|
|
1755
|
+
for (const roleSpec of schema.roles) {
|
|
1756
|
+
if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
|
|
1757
|
+
throw new Error(
|
|
1758
|
+
`Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
switch (command) {
|
|
1764
|
+
case "on": {
|
|
1765
|
+
const eventValue = roles.get("event");
|
|
1766
|
+
if (!eventValue) {
|
|
1767
|
+
throw new Error("Event handler requires event role: [on event:click ...]");
|
|
1768
|
+
}
|
|
1769
|
+
const body = extractStructuralBody(roles, "body", options);
|
|
1770
|
+
roles.delete("body");
|
|
1771
|
+
return createEventHandlerNode("on", roles, body, {
|
|
1772
|
+
sourceLanguage: "explicit"
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
case "if": {
|
|
1776
|
+
const thenBranch = extractStructuralBody(roles, "then", options);
|
|
1777
|
+
const elseBranch = extractStructuralBody(roles, "else", options);
|
|
1778
|
+
roles.delete("then");
|
|
1779
|
+
roles.delete("else");
|
|
1780
|
+
return createConditionalNode(
|
|
1781
|
+
command,
|
|
1782
|
+
roles,
|
|
1783
|
+
thenBranch,
|
|
1784
|
+
elseBranch.length > 0 ? elseBranch : void 0,
|
|
1785
|
+
{
|
|
1786
|
+
sourceLanguage: "explicit"
|
|
1787
|
+
}
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
case "repeat": {
|
|
1791
|
+
const loopBody = extractStructuralBody(roles, "loop-body", options);
|
|
1792
|
+
const loopVariantValue = roles.get("loop-variant");
|
|
1793
|
+
const variableValue = roles.get("variable");
|
|
1794
|
+
const indexVariableValue = roles.get("index-variable");
|
|
1795
|
+
roles.delete("loop-body");
|
|
1796
|
+
roles.delete("loop-variant");
|
|
1797
|
+
roles.delete("variable");
|
|
1798
|
+
roles.delete("index-variable");
|
|
1799
|
+
let variant;
|
|
1800
|
+
if (loopVariantValue && loopVariantValue.type === "literal" && typeof loopVariantValue.value === "string") {
|
|
1801
|
+
variant = loopVariantValue.value;
|
|
1802
|
+
} else if (roles.has("quantity")) {
|
|
1803
|
+
variant = "times";
|
|
1804
|
+
} else if (roles.has("source")) {
|
|
1805
|
+
variant = "for";
|
|
1806
|
+
} else if (roles.has("condition")) {
|
|
1807
|
+
variant = "while";
|
|
1808
|
+
} else {
|
|
1809
|
+
variant = "forever";
|
|
1810
|
+
}
|
|
1811
|
+
const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
|
|
1812
|
+
const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
|
|
1813
|
+
return createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
|
|
1814
|
+
sourceLanguage: "explicit"
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
default:
|
|
1818
|
+
return createCommandNode(command, roles, {
|
|
1819
|
+
sourceLanguage: "explicit"
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
function isExplicitSyntax(input) {
|
|
1824
|
+
const trimmed = input.trim();
|
|
1825
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
1826
|
+
}
|
|
1827
|
+
function extractStructuralBody(roles, roleName, options) {
|
|
1828
|
+
const value = roles.get(roleName);
|
|
1829
|
+
if (!value) return [];
|
|
1830
|
+
if (value.type === "expression") {
|
|
1831
|
+
return [parseExplicit(value.raw, options)];
|
|
1832
|
+
}
|
|
1833
|
+
return [];
|
|
1834
|
+
}
|
|
1835
|
+
function inferSelectorKind(value) {
|
|
1836
|
+
if (!value) return void 0;
|
|
1837
|
+
if (value.startsWith("#")) return "id";
|
|
1838
|
+
if (value.startsWith(".")) return "class";
|
|
1839
|
+
if (value.startsWith("[")) return "attribute";
|
|
1840
|
+
if (value.startsWith("<") || value.startsWith("*")) return "element";
|
|
1841
|
+
if (/[>+~ ]/.test(value) && value.length > 1) return "complex";
|
|
1842
|
+
return void 0;
|
|
1843
|
+
}
|
|
1844
|
+
function tokenizeExplicit(content) {
|
|
1845
|
+
const tokens = [];
|
|
1846
|
+
let current = "";
|
|
1847
|
+
let inString = false;
|
|
1848
|
+
let stringChar = "";
|
|
1849
|
+
let bracketDepth = 0;
|
|
1850
|
+
for (let i = 0; i < content.length; i++) {
|
|
1851
|
+
const char = content[i];
|
|
1852
|
+
if (inString) {
|
|
1853
|
+
current += char;
|
|
1854
|
+
if (char === stringChar && content[i - 1] !== "\\") {
|
|
1855
|
+
inString = false;
|
|
1856
|
+
}
|
|
1857
|
+
continue;
|
|
1858
|
+
}
|
|
1859
|
+
if (char === '"' || char === "'") {
|
|
1860
|
+
inString = true;
|
|
1861
|
+
stringChar = char;
|
|
1862
|
+
current += char;
|
|
1863
|
+
continue;
|
|
1864
|
+
}
|
|
1865
|
+
if (char === "[") {
|
|
1866
|
+
bracketDepth++;
|
|
1867
|
+
current += char;
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
if (char === "]") {
|
|
1871
|
+
bracketDepth--;
|
|
1872
|
+
current += char;
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
if (char === " " && bracketDepth === 0) {
|
|
1876
|
+
if (current) {
|
|
1877
|
+
tokens.push(current);
|
|
1878
|
+
current = "";
|
|
1879
|
+
}
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
current += char;
|
|
1883
|
+
}
|
|
1884
|
+
if (current) {
|
|
1885
|
+
tokens.push(current);
|
|
1886
|
+
}
|
|
1887
|
+
return tokens;
|
|
1888
|
+
}
|
|
1889
|
+
function parseExplicitValue(valueStr, referenceSet) {
|
|
1890
|
+
if (valueStr.startsWith("#") || valueStr.startsWith(".") || valueStr.startsWith("[") || valueStr.startsWith("@") || valueStr.startsWith("*")) {
|
|
1891
|
+
return createSelector(valueStr, inferSelectorKind(valueStr));
|
|
1892
|
+
}
|
|
1893
|
+
if (valueStr.startsWith('"') || valueStr.startsWith("'")) {
|
|
1894
|
+
const inner = valueStr.slice(1, -1);
|
|
1895
|
+
return createLiteral(inner, "string");
|
|
1896
|
+
}
|
|
1897
|
+
if (valueStr === "true") return createLiteral(true, "boolean");
|
|
1898
|
+
if (valueStr === "false") return createLiteral(false, "boolean");
|
|
1899
|
+
const lowerRef = valueStr.toLowerCase();
|
|
1900
|
+
if (isValidReference2(lowerRef, referenceSet)) {
|
|
1901
|
+
return createReference(lowerRef);
|
|
1902
|
+
}
|
|
1903
|
+
const numMatch = valueStr.match(/^(-?\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
1904
|
+
if (numMatch) {
|
|
1905
|
+
const num = parseFloat(numMatch[1]);
|
|
1906
|
+
const suffix = numMatch[2];
|
|
1907
|
+
if (suffix) {
|
|
1908
|
+
return createLiteral(valueStr, "duration");
|
|
1909
|
+
}
|
|
1910
|
+
return createLiteral(num, "number");
|
|
1911
|
+
}
|
|
1912
|
+
return createLiteral(valueStr);
|
|
1913
|
+
}
|
|
1914
|
+
function findMatchingBracket(str, start) {
|
|
1915
|
+
let depth = 0;
|
|
1916
|
+
for (let i = start; i < str.length; i++) {
|
|
1917
|
+
if (str[i] === "[") depth++;
|
|
1918
|
+
else if (str[i] === "]") {
|
|
1919
|
+
depth--;
|
|
1920
|
+
if (depth === 0) return i;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return str.length - 1;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// src/ir/explicit-renderer.ts
|
|
1927
|
+
function renderExplicit(node) {
|
|
1928
|
+
if (node.kind === "compound") {
|
|
1929
|
+
const compoundNode = node;
|
|
1930
|
+
const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
|
|
1931
|
+
return renderedStatements.join(` ${compoundNode.chainType} `);
|
|
1932
|
+
}
|
|
1933
|
+
const parts = [node.action];
|
|
1934
|
+
for (const [role, value] of node.roles) {
|
|
1935
|
+
if (value.type === "flag") {
|
|
1936
|
+
parts.push(value.enabled ? `+${value.name}` : `~${value.name}`);
|
|
1937
|
+
} else {
|
|
1938
|
+
parts.push(`${role}:${valueToString(value)}`);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
if (node.kind === "event-handler") {
|
|
1942
|
+
const eventNode = node;
|
|
1943
|
+
if (eventNode.body && eventNode.body.length > 0) {
|
|
1944
|
+
const bodyParts = eventNode.body.map((n) => renderExplicit(n));
|
|
1945
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
if (node.kind === "conditional") {
|
|
1949
|
+
const condNode = node;
|
|
1950
|
+
if (condNode.thenBranch && condNode.thenBranch.length > 0) {
|
|
1951
|
+
const thenParts = condNode.thenBranch.map((n) => renderExplicit(n));
|
|
1952
|
+
parts.push(`then:${thenParts.join(" ")}`);
|
|
1953
|
+
}
|
|
1954
|
+
if (condNode.elseBranch && condNode.elseBranch.length > 0) {
|
|
1955
|
+
const elseParts = condNode.elseBranch.map((n) => renderExplicit(n));
|
|
1956
|
+
parts.push(`else:${elseParts.join(" ")}`);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
if (node.kind === "loop") {
|
|
1960
|
+
const loopNode = node;
|
|
1961
|
+
if (loopNode.loopVariant) {
|
|
1962
|
+
parts.push(`loop-variant:${loopNode.loopVariant}`);
|
|
1963
|
+
}
|
|
1964
|
+
if (loopNode.loopVariable) {
|
|
1965
|
+
parts.push(`variable:${loopNode.loopVariable}`);
|
|
1966
|
+
}
|
|
1967
|
+
if (loopNode.indexVariable) {
|
|
1968
|
+
parts.push(`index-variable:${loopNode.indexVariable}`);
|
|
1969
|
+
}
|
|
1970
|
+
if (loopNode.body && loopNode.body.length > 0) {
|
|
1971
|
+
const bodyParts = loopNode.body.map((n) => renderExplicit(n));
|
|
1972
|
+
parts.push(`loop-body:${bodyParts.join(" ")}`);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
if (node.kind === "command") {
|
|
1976
|
+
const cmd = node;
|
|
1977
|
+
if (cmd.body && cmd.body.length > 0) {
|
|
1978
|
+
const bodyParts = cmd.body.map((n) => renderExplicit(n));
|
|
1979
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
1980
|
+
}
|
|
1981
|
+
if (cmd.catchBranch && cmd.catchBranch.length > 0) {
|
|
1982
|
+
const catchParts = cmd.catchBranch.map((n) => renderExplicit(n));
|
|
1983
|
+
parts.push(`catch:${catchParts.join(" ")}`);
|
|
1984
|
+
}
|
|
1985
|
+
if (cmd.finallyBranch && cmd.finallyBranch.length > 0) {
|
|
1986
|
+
const finallyParts = cmd.finallyBranch.map((n) => renderExplicit(n));
|
|
1987
|
+
parts.push(`finally:${finallyParts.join(" ")}`);
|
|
1988
|
+
}
|
|
1989
|
+
if (cmd.asyncVariant) {
|
|
1990
|
+
parts.push(`async-variant:${cmd.asyncVariant}`);
|
|
1991
|
+
}
|
|
1992
|
+
if (cmd.asyncBody && cmd.asyncBody.length > 0) {
|
|
1993
|
+
const asyncParts = cmd.asyncBody.map((n) => renderExplicit(n));
|
|
1994
|
+
parts.push(`async-body:${asyncParts.join(" ")}`);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return `[${parts.join(" ")}]`;
|
|
1998
|
+
}
|
|
1999
|
+
function valueToString(value) {
|
|
2000
|
+
switch (value.type) {
|
|
2001
|
+
case "literal":
|
|
2002
|
+
if (typeof value.value === "string") {
|
|
2003
|
+
if (value.dataType === "string" || /\s/.test(value.value)) {
|
|
2004
|
+
return `"${value.value}"`;
|
|
2005
|
+
}
|
|
2006
|
+
return value.value;
|
|
2007
|
+
}
|
|
2008
|
+
return String(value.value);
|
|
2009
|
+
case "selector":
|
|
2010
|
+
return value.value;
|
|
2011
|
+
case "reference":
|
|
2012
|
+
return value.value;
|
|
2013
|
+
case "property-path":
|
|
2014
|
+
return `${valueToString(value.object)}'s ${value.property}`;
|
|
2015
|
+
case "expression":
|
|
2016
|
+
return value.raw;
|
|
2017
|
+
case "flag":
|
|
2018
|
+
return value.name;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1537
2022
|
// src/api/create-dsl.ts
|
|
1538
2023
|
var DSLRegistry = class {
|
|
1539
2024
|
constructor(config) {
|
|
@@ -1571,12 +2056,23 @@ var MultilingualDSLImpl = class {
|
|
|
1571
2056
|
if (config.codeGenerator) {
|
|
1572
2057
|
this.codeGenerator = config.codeGenerator;
|
|
1573
2058
|
}
|
|
2059
|
+
const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
|
|
2060
|
+
this.schemaLookup = {
|
|
2061
|
+
getSchema: (action) => schemaMap.get(action)
|
|
2062
|
+
};
|
|
1574
2063
|
}
|
|
1575
2064
|
parse(input, language) {
|
|
1576
2065
|
const result = this.parseWithConfidence(input, language);
|
|
1577
2066
|
return result.node;
|
|
1578
2067
|
}
|
|
1579
2068
|
parseWithConfidence(input, language) {
|
|
2069
|
+
if (isExplicitSyntax(input)) {
|
|
2070
|
+
const node = parseExplicit(input, { schemaLookup: this.schemaLookup });
|
|
2071
|
+
if (!this.schemaLookup.getSchema(node.action)) {
|
|
2072
|
+
throw new Error(`No schema for action "${node.action}" in this DSL`);
|
|
2073
|
+
}
|
|
2074
|
+
return { node, confidence: 1 };
|
|
2075
|
+
}
|
|
1580
2076
|
const tokenizer = this.registry.getTokenizer(language);
|
|
1581
2077
|
if (!tokenizer) {
|
|
1582
2078
|
throw new Error(`No tokenizer registered for language: ${language}`);
|
|
@@ -1620,6 +2116,7 @@ var MultilingualDSLImpl = class {
|
|
|
1620
2116
|
}
|
|
1621
2117
|
}
|
|
1622
2118
|
translate(input, fromLanguage, toLanguage) {
|
|
2119
|
+
if (isExplicitSyntax(input)) return input;
|
|
1623
2120
|
return this.transformer.transform(input, fromLanguage, toLanguage);
|
|
1624
2121
|
}
|
|
1625
2122
|
compile(input, language) {
|
|
@@ -1683,6 +2180,723 @@ function createMultilingualDSL(config) {
|
|
|
1683
2180
|
return new MultilingualDSLImpl(config, registry, transformer);
|
|
1684
2181
|
}
|
|
1685
2182
|
|
|
2183
|
+
// src/prompts/prompt-generator.ts
|
|
2184
|
+
function generatePrompt(config) {
|
|
2185
|
+
const outputFormat = config.outputFormat ?? "both";
|
|
2186
|
+
const examplesPerCommand = config.examplesPerCommand ?? 2;
|
|
2187
|
+
const sections = [
|
|
2188
|
+
buildProtocolSection(),
|
|
2189
|
+
buildValueTypeSection(),
|
|
2190
|
+
buildCommandsSection(config.schemas, outputFormat, examplesPerCommand),
|
|
2191
|
+
buildOutputFormatSection(outputFormat),
|
|
2192
|
+
buildErrorRecoverySection()
|
|
2193
|
+
];
|
|
2194
|
+
let finalSections = sections;
|
|
2195
|
+
if (config.maxTokens && config.maxTokens > 0) {
|
|
2196
|
+
finalSections = truncateToTokenBudget(sections, config.maxTokens);
|
|
2197
|
+
}
|
|
2198
|
+
const text = finalSections.map((s) => `## ${s.title}
|
|
2199
|
+
|
|
2200
|
+
${s.content}`).join("\n\n---\n\n");
|
|
2201
|
+
const totalRoles = config.schemas.reduce((sum, s) => sum + s.roles.length, 0);
|
|
2202
|
+
const approximateTokens = estimateTokens(text);
|
|
2203
|
+
const metadata = {
|
|
2204
|
+
domain: config.domain,
|
|
2205
|
+
commandCount: config.schemas.length,
|
|
2206
|
+
roleCount: totalRoles,
|
|
2207
|
+
approximateTokens
|
|
2208
|
+
};
|
|
2209
|
+
return { text, sections: finalSections, metadata };
|
|
2210
|
+
}
|
|
2211
|
+
function generateExamples(schema, count = 2) {
|
|
2212
|
+
const combos = generateRoleCombinations(schema);
|
|
2213
|
+
const examples = [];
|
|
2214
|
+
for (let i = 0; i < Math.min(count, combos.length); i++) {
|
|
2215
|
+
const combo = combos[i];
|
|
2216
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2217
|
+
for (const role of combo) {
|
|
2218
|
+
const sample = sampleValue(role);
|
|
2219
|
+
roles.set(role.role, sample);
|
|
2220
|
+
}
|
|
2221
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
2222
|
+
for (const [roleName, sample] of roles) {
|
|
2223
|
+
nodeRoles.set(roleName, toSemanticValue(sample));
|
|
2224
|
+
}
|
|
2225
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
2226
|
+
const explicit = renderExplicit(node);
|
|
2227
|
+
const jsonRoles = {};
|
|
2228
|
+
for (const [roleName, sample] of roles) {
|
|
2229
|
+
jsonRoles[roleName] = sample;
|
|
2230
|
+
}
|
|
2231
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
2232
|
+
examples.push({ explicit, json });
|
|
2233
|
+
}
|
|
2234
|
+
return examples;
|
|
2235
|
+
}
|
|
2236
|
+
function buildProtocolSection() {
|
|
2237
|
+
const content = `LokaScript Explicit Syntax (LSE) is a bracket-based format for imperative commands.
|
|
2238
|
+
|
|
2239
|
+
### Syntax
|
|
2240
|
+
|
|
2241
|
+
\`\`\`
|
|
2242
|
+
[command role1:value1 role2:value2 +flag1 ~flag2]
|
|
2243
|
+
\`\`\`
|
|
2244
|
+
|
|
2245
|
+
- **Command**: The first token inside brackets (lowercased)
|
|
2246
|
+
- **Role pair**: \`name:value\` \u2014 a named semantic role with a typed value (no space around colon)
|
|
2247
|
+
- **Enabled flag**: \`+name\` \u2014 boolean attribute present
|
|
2248
|
+
- **Disabled flag**: \`~name\` \u2014 boolean attribute negated
|
|
2249
|
+
- **Nested body**: \`body:[command ...]\` \u2014 a bracket command inside a role value
|
|
2250
|
+
|
|
2251
|
+
### Rules
|
|
2252
|
+
|
|
2253
|
+
1. Commands are always lowercased
|
|
2254
|
+
2. Role names preserve their original case
|
|
2255
|
+
3. No spaces around the colon in role:value pairs
|
|
2256
|
+
4. Strings with spaces must be quoted: \`patient:"hello world"\`
|
|
2257
|
+
5. Selectors start with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`
|
|
2258
|
+
6. Output must be valid bracket syntax: \`[action role:value ...]\``;
|
|
2259
|
+
return {
|
|
2260
|
+
id: "protocol",
|
|
2261
|
+
title: "LSE Protocol",
|
|
2262
|
+
content,
|
|
2263
|
+
approximateTokens: estimateTokens(content)
|
|
2264
|
+
};
|
|
2265
|
+
}
|
|
2266
|
+
function buildValueTypeSection() {
|
|
2267
|
+
const content = `Values in LSE are classified by their syntactic form (first match wins):
|
|
2268
|
+
|
|
2269
|
+
| Type | Syntax | Example |
|
|
2270
|
+
|------|--------|---------|
|
|
2271
|
+
| Selector | Starts with \`#\` \`.\` \`[\` \`@\` \`*\` | \`#button\`, \`.active\`, \`[data-id]\` |
|
|
2272
|
+
| String | Quoted with \`"\` or \`'\` | \`"hello world"\`, \`'json'\` |
|
|
2273
|
+
| Boolean | Exact: \`true\` / \`false\` | \`visible:true\` |
|
|
2274
|
+
| Reference | Built-in name | \`me\`, \`you\`, \`it\`, \`result\`, \`event\`, \`target\`, \`body\` |
|
|
2275
|
+
| Duration | Number + suffix | \`500ms\`, \`2s\`, \`1m\`, \`1h\` |
|
|
2276
|
+
| Number | Digits with optional decimal | \`5\`, \`3.14\`, \`-1\` |
|
|
2277
|
+
| Plain | Fallback (any non-whitespace) | \`/api/users\`, \`json\`, \`production\` |
|
|
2278
|
+
|
|
2279
|
+
**Important**: Classification is by prefix, not by intent. \`#true\` is a selector (not boolean). \`event\` as a value is a reference (not plain text).`;
|
|
2280
|
+
return {
|
|
2281
|
+
id: "value-types",
|
|
2282
|
+
title: "Value Types",
|
|
2283
|
+
content,
|
|
2284
|
+
approximateTokens: estimateTokens(content)
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
function buildCommandsSection(schemas, outputFormat, examplesPerCommand) {
|
|
2288
|
+
const parts = [];
|
|
2289
|
+
for (const schema of schemas) {
|
|
2290
|
+
parts.push(formatCommandDoc(schema, outputFormat, examplesPerCommand));
|
|
2291
|
+
}
|
|
2292
|
+
const content = parts.join("\n\n");
|
|
2293
|
+
return {
|
|
2294
|
+
id: "commands",
|
|
2295
|
+
title: "Available Commands",
|
|
2296
|
+
content,
|
|
2297
|
+
approximateTokens: estimateTokens(content)
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
function buildOutputFormatSection(outputFormat) {
|
|
2301
|
+
let content;
|
|
2302
|
+
if (outputFormat === "explicit") {
|
|
2303
|
+
content = `Output valid LSE bracket syntax. Each command must be wrapped in brackets:
|
|
2304
|
+
|
|
2305
|
+
\`\`\`
|
|
2306
|
+
[command role:value ...]
|
|
2307
|
+
\`\`\`
|
|
2308
|
+
|
|
2309
|
+
Do NOT output JSON. Only use bracket syntax.`;
|
|
2310
|
+
} else if (outputFormat === "json") {
|
|
2311
|
+
content = `Output valid JSON in the LLM-simplified format:
|
|
2312
|
+
|
|
2313
|
+
\`\`\`json
|
|
2314
|
+
{
|
|
2315
|
+
"action": "command-name",
|
|
2316
|
+
"roles": {
|
|
2317
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
\`\`\`
|
|
2321
|
+
|
|
2322
|
+
Valid value types: \`selector\`, \`literal\`, \`reference\`, \`expression\`.
|
|
2323
|
+
Do NOT output bracket syntax. Only use JSON.`;
|
|
2324
|
+
} else {
|
|
2325
|
+
content = `You may output EITHER format:
|
|
2326
|
+
|
|
2327
|
+
**Bracket syntax** (preferred for single commands):
|
|
2328
|
+
\`\`\`
|
|
2329
|
+
[command role:value ...]
|
|
2330
|
+
\`\`\`
|
|
2331
|
+
|
|
2332
|
+
**JSON format** (preferred when structured data is needed):
|
|
2333
|
+
\`\`\`json
|
|
2334
|
+
{
|
|
2335
|
+
"action": "command-name",
|
|
2336
|
+
"roles": {
|
|
2337
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
\`\`\`
|
|
2341
|
+
|
|
2342
|
+
Both formats are equally valid. Use whichever is more natural for the context.`;
|
|
2343
|
+
}
|
|
2344
|
+
return {
|
|
2345
|
+
id: "output-format",
|
|
2346
|
+
title: "Output Format",
|
|
2347
|
+
content,
|
|
2348
|
+
approximateTokens: estimateTokens(content)
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function buildErrorRecoverySection() {
|
|
2352
|
+
const content = `If you're unsure about a command or role:
|
|
2353
|
+
|
|
2354
|
+
1. **Unknown command**: Use the closest matching command from the Available Commands section. Do not invent commands.
|
|
2355
|
+
2. **Unknown role**: Use only the roles listed for each command. Do not add roles not in the schema.
|
|
2356
|
+
3. **Ambiguous value type**: When in doubt, use \`expression\` type (it's the most permissive).
|
|
2357
|
+
4. **Missing required role**: Always include all required roles. Check the Required/Optional labels.
|
|
2358
|
+
5. **Selector vs plain value**: If a value starts with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`, it's a selector. URLs like \`/api/data\` are plain values.
|
|
2359
|
+
|
|
2360
|
+
When generating LSE, prefer explicit role labeling over positional guessing. Every value should have a role name.`;
|
|
2361
|
+
return {
|
|
2362
|
+
id: "error-recovery",
|
|
2363
|
+
title: "Error Recovery",
|
|
2364
|
+
content,
|
|
2365
|
+
approximateTokens: estimateTokens(content)
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
function formatCommandDoc(schema, outputFormat, examplesPerCommand) {
|
|
2369
|
+
const lines = [];
|
|
2370
|
+
lines.push(`### \`${schema.action}\` \u2014 ${schema.description}`);
|
|
2371
|
+
lines.push("");
|
|
2372
|
+
const required = schema.roles.filter((r) => r.required);
|
|
2373
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
2374
|
+
if (required.length > 0) {
|
|
2375
|
+
lines.push("**Required roles:**");
|
|
2376
|
+
for (const role of required) {
|
|
2377
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
if (optional.length > 0) {
|
|
2381
|
+
lines.push("**Optional roles:**");
|
|
2382
|
+
for (const role of optional) {
|
|
2383
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
const examples = generateExamples(schema, examplesPerCommand);
|
|
2387
|
+
if (examples.length > 0) {
|
|
2388
|
+
lines.push("");
|
|
2389
|
+
if (outputFormat === "explicit" || outputFormat === "both") {
|
|
2390
|
+
lines.push("**Bracket syntax:**");
|
|
2391
|
+
for (const ex of examples) {
|
|
2392
|
+
lines.push(`\`\`\`
|
|
2393
|
+
${ex.explicit}
|
|
2394
|
+
\`\`\``);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (outputFormat === "json" || outputFormat === "both") {
|
|
2398
|
+
lines.push("**JSON format:**");
|
|
2399
|
+
for (const ex of examples) {
|
|
2400
|
+
lines.push(`\`\`\`json
|
|
2401
|
+
${JSON.stringify(ex.json, null, 2)}
|
|
2402
|
+
\`\`\``);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
return lines.join("\n");
|
|
2407
|
+
}
|
|
2408
|
+
function formatExpectedTypes(role) {
|
|
2409
|
+
return role.expectedTypes.join(" | ");
|
|
2410
|
+
}
|
|
2411
|
+
function generateRoleCombinations(schema) {
|
|
2412
|
+
const required = schema.roles.filter((r) => r.required);
|
|
2413
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
2414
|
+
const combos = [];
|
|
2415
|
+
if (required.length > 0) {
|
|
2416
|
+
combos.push(required);
|
|
2417
|
+
}
|
|
2418
|
+
for (const opt of optional) {
|
|
2419
|
+
combos.push([...required, opt]);
|
|
2420
|
+
}
|
|
2421
|
+
if (optional.length >= 2) {
|
|
2422
|
+
combos.push([...required, ...optional]);
|
|
2423
|
+
}
|
|
2424
|
+
if (combos.length === 0) {
|
|
2425
|
+
combos.push(schema.roles.slice());
|
|
2426
|
+
}
|
|
2427
|
+
return combos;
|
|
2428
|
+
}
|
|
2429
|
+
function sampleValue(role) {
|
|
2430
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
2431
|
+
switch (primaryType) {
|
|
2432
|
+
case "selector":
|
|
2433
|
+
return sampleSelector(role.role);
|
|
2434
|
+
case "literal":
|
|
2435
|
+
return sampleLiteral(role.role);
|
|
2436
|
+
case "reference":
|
|
2437
|
+
return { type: "reference", value: "me" };
|
|
2438
|
+
case "expression":
|
|
2439
|
+
return sampleExpression(role.role);
|
|
2440
|
+
case "flag":
|
|
2441
|
+
return { type: "flag", value: true };
|
|
2442
|
+
default:
|
|
2443
|
+
return { type: "literal", value: "example" };
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
function sampleSelector(roleName) {
|
|
2447
|
+
const selectors = {
|
|
2448
|
+
patient: ".active",
|
|
2449
|
+
destination: "#output",
|
|
2450
|
+
source: "#input",
|
|
2451
|
+
target: "#target"
|
|
2452
|
+
};
|
|
2453
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
2454
|
+
}
|
|
2455
|
+
function sampleLiteral(roleName) {
|
|
2456
|
+
const literals = {
|
|
2457
|
+
duration: "30s",
|
|
2458
|
+
interval: "5s",
|
|
2459
|
+
delay: "500ms",
|
|
2460
|
+
timeout: "10s",
|
|
2461
|
+
quantity: 5,
|
|
2462
|
+
limit: 10,
|
|
2463
|
+
style: "json",
|
|
2464
|
+
manner: "rolling",
|
|
2465
|
+
format: "html"
|
|
2466
|
+
};
|
|
2467
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
2468
|
+
}
|
|
2469
|
+
function sampleExpression(roleName) {
|
|
2470
|
+
const expressions = {
|
|
2471
|
+
source: "/api/data",
|
|
2472
|
+
destination: "#output",
|
|
2473
|
+
patient: ".active",
|
|
2474
|
+
instrument: "toUpperCase",
|
|
2475
|
+
condition: "age > 18",
|
|
2476
|
+
style: "json",
|
|
2477
|
+
duration: "5s",
|
|
2478
|
+
url: "/api/users"
|
|
2479
|
+
};
|
|
2480
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
2481
|
+
}
|
|
2482
|
+
function toSemanticValue(sample) {
|
|
2483
|
+
switch (sample.type) {
|
|
2484
|
+
case "selector":
|
|
2485
|
+
return {
|
|
2486
|
+
type: "selector",
|
|
2487
|
+
value: String(sample.value),
|
|
2488
|
+
selectorKind: detectSelectorKind(String(sample.value))
|
|
2489
|
+
};
|
|
2490
|
+
case "literal":
|
|
2491
|
+
return {
|
|
2492
|
+
type: "literal",
|
|
2493
|
+
value: sample.value,
|
|
2494
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2495
|
+
};
|
|
2496
|
+
case "reference":
|
|
2497
|
+
return { type: "reference", value: String(sample.value) };
|
|
2498
|
+
case "expression":
|
|
2499
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
2500
|
+
case "flag":
|
|
2501
|
+
return { type: "flag", value: sample.value };
|
|
2502
|
+
default:
|
|
2503
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
function detectSelectorKind(selector) {
|
|
2507
|
+
if (selector.startsWith("#")) return "id";
|
|
2508
|
+
if (selector.startsWith(".")) return "class";
|
|
2509
|
+
if (selector.startsWith("[")) return "attribute";
|
|
2510
|
+
return "complex";
|
|
2511
|
+
}
|
|
2512
|
+
function estimateTokens(text) {
|
|
2513
|
+
return Math.ceil(text.length / 4);
|
|
2514
|
+
}
|
|
2515
|
+
function truncateToTokenBudget(sections, maxTokens) {
|
|
2516
|
+
const result = [];
|
|
2517
|
+
let budget = maxTokens;
|
|
2518
|
+
for (const section of sections) {
|
|
2519
|
+
if (section.approximateTokens <= budget) {
|
|
2520
|
+
result.push(section);
|
|
2521
|
+
budget -= section.approximateTokens;
|
|
2522
|
+
} else {
|
|
2523
|
+
const charBudget = Math.max(budget * 4, 40);
|
|
2524
|
+
const truncatedContent = section.content.slice(0, charBudget) + "\n\n*(truncated)*";
|
|
2525
|
+
result.push({
|
|
2526
|
+
...section,
|
|
2527
|
+
content: truncatedContent,
|
|
2528
|
+
approximateTokens: estimateTokens(truncatedContent)
|
|
2529
|
+
});
|
|
2530
|
+
break;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
return result;
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/training/schema-synthesizer.ts
|
|
2537
|
+
function synthesizeFromSchemas(schemas, config) {
|
|
2538
|
+
const maxPerCommand = config.maxPairsPerCommand ?? 5;
|
|
2539
|
+
const minQuality = config.minQuality ?? 0.5;
|
|
2540
|
+
const shuffleRoles = config.shuffleRoles ?? true;
|
|
2541
|
+
const languages = config.languages ?? ["en"];
|
|
2542
|
+
const pairs = [];
|
|
2543
|
+
let idCounter = 0;
|
|
2544
|
+
for (const schema of schemas) {
|
|
2545
|
+
const combos = generateRoleCombinations2(schema);
|
|
2546
|
+
for (const language of languages) {
|
|
2547
|
+
let commandPairCount = 0;
|
|
2548
|
+
for (const combo of combos) {
|
|
2549
|
+
if (commandPairCount >= maxPerCommand) break;
|
|
2550
|
+
const pair = buildTrainingPair(
|
|
2551
|
+
schema,
|
|
2552
|
+
combo,
|
|
2553
|
+
language,
|
|
2554
|
+
config.domain,
|
|
2555
|
+
`${config.domain}-synth-${idCounter++}`
|
|
2556
|
+
);
|
|
2557
|
+
if (pair.quality >= minQuality) {
|
|
2558
|
+
pairs.push(pair);
|
|
2559
|
+
commandPairCount++;
|
|
2560
|
+
}
|
|
2561
|
+
if (shuffleRoles && combo.length >= 2 && commandPairCount < maxPerCommand) {
|
|
2562
|
+
const shuffled = [...combo].reverse();
|
|
2563
|
+
const shuffledPair = buildTrainingPair(
|
|
2564
|
+
schema,
|
|
2565
|
+
shuffled,
|
|
2566
|
+
language,
|
|
2567
|
+
config.domain,
|
|
2568
|
+
`${config.domain}-synth-${idCounter++}`
|
|
2569
|
+
);
|
|
2570
|
+
if (shuffledPair.quality >= minQuality) {
|
|
2571
|
+
pairs.push(shuffledPair);
|
|
2572
|
+
commandPairCount++;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
const metadata = {
|
|
2579
|
+
domain: config.domain,
|
|
2580
|
+
commandCount: schemas.length,
|
|
2581
|
+
pairCount: pairs.length,
|
|
2582
|
+
bySource: { synthetic: pairs.length },
|
|
2583
|
+
languages
|
|
2584
|
+
};
|
|
2585
|
+
return { pairs, metadata };
|
|
2586
|
+
}
|
|
2587
|
+
function generateRoleCombinations2(schema) {
|
|
2588
|
+
const required = schema.roles.filter((r) => r.required);
|
|
2589
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
2590
|
+
const combos = [];
|
|
2591
|
+
if (required.length > 0) {
|
|
2592
|
+
combos.push(required);
|
|
2593
|
+
}
|
|
2594
|
+
for (const opt of optional) {
|
|
2595
|
+
combos.push([...required, opt]);
|
|
2596
|
+
}
|
|
2597
|
+
if (optional.length >= 2) {
|
|
2598
|
+
combos.push([...required, ...optional]);
|
|
2599
|
+
}
|
|
2600
|
+
if (combos.length === 0) {
|
|
2601
|
+
combos.push(schema.roles.slice());
|
|
2602
|
+
}
|
|
2603
|
+
return combos;
|
|
2604
|
+
}
|
|
2605
|
+
function buildTrainingPair(schema, roles, language, domain, id) {
|
|
2606
|
+
const sampledRoles = /* @__PURE__ */ new Map();
|
|
2607
|
+
for (const role of roles) {
|
|
2608
|
+
sampledRoles.set(role.role, sampleValue2(role));
|
|
2609
|
+
}
|
|
2610
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
2611
|
+
for (const [name, sample] of sampledRoles) {
|
|
2612
|
+
nodeRoles.set(name, toSemanticValue2(sample));
|
|
2613
|
+
}
|
|
2614
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
2615
|
+
const explicit = renderExplicit(node);
|
|
2616
|
+
const jsonRoles = {};
|
|
2617
|
+
for (const [name, sample] of sampledRoles) {
|
|
2618
|
+
jsonRoles[name] = sample;
|
|
2619
|
+
}
|
|
2620
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
2621
|
+
const natural = buildNaturalForm(schema, sampledRoles, language);
|
|
2622
|
+
const requiredCount = schema.roles.filter((r) => r.required).length;
|
|
2623
|
+
const filledRequired = roles.filter((r) => r.required).length;
|
|
2624
|
+
const quality = requiredCount > 0 ? filledRequired / requiredCount : 0.8;
|
|
2625
|
+
return {
|
|
2626
|
+
id,
|
|
2627
|
+
natural,
|
|
2628
|
+
language,
|
|
2629
|
+
explicit,
|
|
2630
|
+
json,
|
|
2631
|
+
domain,
|
|
2632
|
+
action: schema.action,
|
|
2633
|
+
source: "synthetic",
|
|
2634
|
+
quality
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
function buildNaturalForm(schema, roles, language) {
|
|
2638
|
+
const parts = [schema.action];
|
|
2639
|
+
for (const roleSpec of schema.roles) {
|
|
2640
|
+
const sample = roles.get(roleSpec.role);
|
|
2641
|
+
if (!sample) continue;
|
|
2642
|
+
const marker = roleSpec.markerOverride?.[language];
|
|
2643
|
+
if (marker) {
|
|
2644
|
+
parts.push(marker);
|
|
2645
|
+
}
|
|
2646
|
+
parts.push(String(sample.value));
|
|
2647
|
+
}
|
|
2648
|
+
return parts.join(" ");
|
|
2649
|
+
}
|
|
2650
|
+
function sampleValue2(role) {
|
|
2651
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
2652
|
+
switch (primaryType) {
|
|
2653
|
+
case "selector":
|
|
2654
|
+
return sampleSelector2(role.role);
|
|
2655
|
+
case "literal":
|
|
2656
|
+
return sampleLiteral2(role.role);
|
|
2657
|
+
case "reference":
|
|
2658
|
+
return { type: "reference", value: "me" };
|
|
2659
|
+
case "expression":
|
|
2660
|
+
return sampleExpression2(role.role);
|
|
2661
|
+
case "flag":
|
|
2662
|
+
return { type: "flag", value: true };
|
|
2663
|
+
default:
|
|
2664
|
+
return { type: "literal", value: "example" };
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function sampleSelector2(roleName) {
|
|
2668
|
+
const selectors = {
|
|
2669
|
+
patient: ".active",
|
|
2670
|
+
destination: "#output",
|
|
2671
|
+
source: "#input",
|
|
2672
|
+
target: "#target"
|
|
2673
|
+
};
|
|
2674
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
2675
|
+
}
|
|
2676
|
+
function sampleLiteral2(roleName) {
|
|
2677
|
+
const literals = {
|
|
2678
|
+
duration: "30s",
|
|
2679
|
+
interval: "5s",
|
|
2680
|
+
delay: "500ms",
|
|
2681
|
+
timeout: "10s",
|
|
2682
|
+
quantity: 5,
|
|
2683
|
+
limit: 10,
|
|
2684
|
+
style: "json",
|
|
2685
|
+
manner: "rolling",
|
|
2686
|
+
format: "html"
|
|
2687
|
+
};
|
|
2688
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
2689
|
+
}
|
|
2690
|
+
function sampleExpression2(roleName) {
|
|
2691
|
+
const expressions = {
|
|
2692
|
+
source: "/api/data",
|
|
2693
|
+
destination: "#output",
|
|
2694
|
+
patient: ".active",
|
|
2695
|
+
instrument: "toUpperCase",
|
|
2696
|
+
condition: "age > 18",
|
|
2697
|
+
style: "json",
|
|
2698
|
+
duration: "5s",
|
|
2699
|
+
url: "/api/users"
|
|
2700
|
+
};
|
|
2701
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
2702
|
+
}
|
|
2703
|
+
function toSemanticValue2(sample) {
|
|
2704
|
+
switch (sample.type) {
|
|
2705
|
+
case "selector":
|
|
2706
|
+
return {
|
|
2707
|
+
type: "selector",
|
|
2708
|
+
value: String(sample.value),
|
|
2709
|
+
selectorKind: detectSelectorKind2(String(sample.value))
|
|
2710
|
+
};
|
|
2711
|
+
case "literal":
|
|
2712
|
+
return {
|
|
2713
|
+
type: "literal",
|
|
2714
|
+
value: sample.value,
|
|
2715
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2716
|
+
};
|
|
2717
|
+
case "reference":
|
|
2718
|
+
return { type: "reference", value: String(sample.value) };
|
|
2719
|
+
case "expression":
|
|
2720
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
2721
|
+
case "flag":
|
|
2722
|
+
return { type: "flag", value: sample.value };
|
|
2723
|
+
default:
|
|
2724
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
function detectSelectorKind2(selector) {
|
|
2728
|
+
if (selector.startsWith("#")) return "id";
|
|
2729
|
+
if (selector.startsWith(".")) return "class";
|
|
2730
|
+
if (selector.startsWith("[")) return "attribute";
|
|
2731
|
+
return "complex";
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
// src/feedback/feedback-formatter.ts
|
|
2735
|
+
function buildFeedback(input, inputFormat, diagnostics, schemaLookup, action) {
|
|
2736
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
2737
|
+
const feedbackDiagnostics = diagnostics.map((d) => toFeedbackDiagnostic(d));
|
|
2738
|
+
const hints = generateHints(feedbackDiagnostics, schemaLookup, action);
|
|
2739
|
+
let schemaInfo;
|
|
2740
|
+
let correctedExample;
|
|
2741
|
+
if (action && schemaLookup) {
|
|
2742
|
+
const schema = schemaLookup.getSchema(action);
|
|
2743
|
+
if (schema) {
|
|
2744
|
+
schemaInfo = extractSchemaInfo(schema);
|
|
2745
|
+
if (hasErrors) {
|
|
2746
|
+
correctedExample = generateCorrectedExample(schema);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
return {
|
|
2751
|
+
accepted: !hasErrors,
|
|
2752
|
+
input: { format: inputFormat, text: input },
|
|
2753
|
+
diagnostics: feedbackDiagnostics,
|
|
2754
|
+
hints,
|
|
2755
|
+
...schemaInfo && { schema: schemaInfo },
|
|
2756
|
+
...correctedExample && { correctedExample }
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
function toFeedbackDiagnostic(d) {
|
|
2760
|
+
const fixType = classifyFixType(d);
|
|
2761
|
+
return {
|
|
2762
|
+
severity: d.severity,
|
|
2763
|
+
code: d.code || "unknown",
|
|
2764
|
+
message: d.message,
|
|
2765
|
+
...d.suggestions?.[0] && { suggestion: d.suggestions[0] },
|
|
2766
|
+
...fixType && { fixType }
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
function classifyFixType(d) {
|
|
2770
|
+
const code = (d.code || "").toLowerCase();
|
|
2771
|
+
const msg = d.message.toLowerCase();
|
|
2772
|
+
if (code.includes("missing-role") || code.includes("missing_role") || msg.includes("required role") || msg.includes("missing role")) {
|
|
2773
|
+
return "missing_role";
|
|
2774
|
+
}
|
|
2775
|
+
if (code.includes("invalid-type") || code.includes("invalid_type") || code.includes("type-mismatch") || msg.includes("invalid type") || msg.includes("expected type")) {
|
|
2776
|
+
return "invalid_type";
|
|
2777
|
+
}
|
|
2778
|
+
if (code.includes("unknown-command") || code.includes("unknown_command") || msg.includes("unknown command") || msg.includes("not recognized") && msg.includes("command")) {
|
|
2779
|
+
return "unknown_command";
|
|
2780
|
+
}
|
|
2781
|
+
if (code.includes("unknown-role") || code.includes("unknown_role") || msg.includes("unknown role") || msg.includes("not recognized") && msg.includes("role")) {
|
|
2782
|
+
return "unknown_role";
|
|
2783
|
+
}
|
|
2784
|
+
if (code.includes("parse") || code.includes("syntax") || msg.includes("syntax error") || msg.includes("parse error") || msg.includes("unexpected")) {
|
|
2785
|
+
return "syntax_error";
|
|
2786
|
+
}
|
|
2787
|
+
return void 0;
|
|
2788
|
+
}
|
|
2789
|
+
function generateHints(diagnostics, schemaLookup, action) {
|
|
2790
|
+
const hints = [];
|
|
2791
|
+
const schema = action && schemaLookup ? schemaLookup.getSchema(action) : void 0;
|
|
2792
|
+
for (const d of diagnostics) {
|
|
2793
|
+
if (d.severity !== "error") continue;
|
|
2794
|
+
switch (d.fixType) {
|
|
2795
|
+
case "missing_role": {
|
|
2796
|
+
if (schema) {
|
|
2797
|
+
const required = schema.roles.filter((r) => r.required).map((r) => `'${r.role}' (${r.description}, type: ${r.expectedTypes.join("|")})`).join(", ");
|
|
2798
|
+
hints.push(`Required roles for '${action}': ${required}`);
|
|
2799
|
+
} else {
|
|
2800
|
+
hints.push("Check that all required roles are present.");
|
|
2801
|
+
}
|
|
2802
|
+
break;
|
|
2803
|
+
}
|
|
2804
|
+
case "unknown_command": {
|
|
2805
|
+
hints.push(
|
|
2806
|
+
`Command not recognized. Ensure you're using a valid command from the domain schema.`
|
|
2807
|
+
);
|
|
2808
|
+
break;
|
|
2809
|
+
}
|
|
2810
|
+
case "unknown_role": {
|
|
2811
|
+
if (schema) {
|
|
2812
|
+
const validRoles = schema.roles.map((r) => `'${r.role}'`).join(", ");
|
|
2813
|
+
hints.push(`Valid roles for '${action}': ${validRoles}`);
|
|
2814
|
+
} else {
|
|
2815
|
+
hints.push("Use only roles defined in the command schema.");
|
|
2816
|
+
}
|
|
2817
|
+
break;
|
|
2818
|
+
}
|
|
2819
|
+
case "invalid_type": {
|
|
2820
|
+
hints.push(
|
|
2821
|
+
"Check value types: selectors start with #.[@*, strings use quotes, numbers are plain digits."
|
|
2822
|
+
);
|
|
2823
|
+
break;
|
|
2824
|
+
}
|
|
2825
|
+
case "syntax_error": {
|
|
2826
|
+
hints.push("Ensure bracket syntax: [command role:value ...]. No spaces around the colon.");
|
|
2827
|
+
break;
|
|
2828
|
+
}
|
|
2829
|
+
default: {
|
|
2830
|
+
if (d.suggestion) {
|
|
2831
|
+
hints.push(d.suggestion);
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
return [...new Set(hints)];
|
|
2837
|
+
}
|
|
2838
|
+
function extractSchemaInfo(schema) {
|
|
2839
|
+
const requiredRoles = [];
|
|
2840
|
+
const optionalRoles = [];
|
|
2841
|
+
const roleDescriptions = {};
|
|
2842
|
+
for (const role of schema.roles) {
|
|
2843
|
+
if (role.required) {
|
|
2844
|
+
requiredRoles.push(role.role);
|
|
2845
|
+
} else {
|
|
2846
|
+
optionalRoles.push(role.role);
|
|
2847
|
+
}
|
|
2848
|
+
roleDescriptions[role.role] = role.description;
|
|
2849
|
+
}
|
|
2850
|
+
return {
|
|
2851
|
+
action: schema.action,
|
|
2852
|
+
requiredRoles,
|
|
2853
|
+
optionalRoles,
|
|
2854
|
+
roleDescriptions
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
function generateCorrectedExample(schema) {
|
|
2858
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2859
|
+
const jsonRoles = {};
|
|
2860
|
+
for (const role of schema.roles) {
|
|
2861
|
+
if (!role.required) continue;
|
|
2862
|
+
const sample = sampleDefaultValue(role.role, role.expectedTypes[0] || "expression");
|
|
2863
|
+
roles.set(role.role, toSemanticValue3(sample));
|
|
2864
|
+
jsonRoles[role.role] = sample;
|
|
2865
|
+
}
|
|
2866
|
+
const node = createCommandNode(schema.action, roles);
|
|
2867
|
+
const explicit = renderExplicit(node);
|
|
2868
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
2869
|
+
return { explicit, json };
|
|
2870
|
+
}
|
|
2871
|
+
function sampleDefaultValue(roleName, expectedType) {
|
|
2872
|
+
switch (expectedType) {
|
|
2873
|
+
case "selector":
|
|
2874
|
+
return { type: "selector", value: `#${roleName}` };
|
|
2875
|
+
case "expression":
|
|
2876
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
2877
|
+
case "literal":
|
|
2878
|
+
return { type: "literal", value: `<${roleName}-value>` };
|
|
2879
|
+
case "reference":
|
|
2880
|
+
return { type: "reference", value: "me" };
|
|
2881
|
+
default:
|
|
2882
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
function toSemanticValue3(sample) {
|
|
2886
|
+
switch (sample.type) {
|
|
2887
|
+
case "selector":
|
|
2888
|
+
return { type: "selector", value: sample.value, selectorKind: "id" };
|
|
2889
|
+
case "expression":
|
|
2890
|
+
return { type: "expression", value: sample.value, raw: sample.value };
|
|
2891
|
+
case "literal":
|
|
2892
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
2893
|
+
case "reference":
|
|
2894
|
+
return { type: "reference", value: sample.value };
|
|
2895
|
+
default:
|
|
2896
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
|
|
1686
2900
|
// src/api/domain-registry.ts
|
|
1687
2901
|
var DomainRegistry = class {
|
|
1688
2902
|
constructor() {
|
|
@@ -1700,6 +2914,17 @@ var DomainRegistry = class {
|
|
|
1700
2914
|
}
|
|
1701
2915
|
this.descriptors.set(descriptor.name, descriptor);
|
|
1702
2916
|
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Attach schemas to an already-registered domain.
|
|
2919
|
+
* Useful for lazy loading schemas after initial registration.
|
|
2920
|
+
*/
|
|
2921
|
+
setSchemas(name, schemas) {
|
|
2922
|
+
const existing = this.descriptors.get(name);
|
|
2923
|
+
if (!existing) {
|
|
2924
|
+
throw new Error(`Domain not registered: ${name}`);
|
|
2925
|
+
}
|
|
2926
|
+
this.descriptors.set(name, { ...existing, schemas });
|
|
2927
|
+
}
|
|
1703
2928
|
/**
|
|
1704
2929
|
* Get all registered domain names.
|
|
1705
2930
|
*/
|
|
@@ -1769,6 +2994,71 @@ var DomainRegistry = class {
|
|
|
1769
2994
|
return this.getDSL(descriptor);
|
|
1770
2995
|
}
|
|
1771
2996
|
// ---------------------------------------------------------------------------
|
|
2997
|
+
// LLM ↔ LSE Convenience Methods
|
|
2998
|
+
// ---------------------------------------------------------------------------
|
|
2999
|
+
/**
|
|
3000
|
+
* Generate an LLM system prompt for a domain from its schemas.
|
|
3001
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
3002
|
+
*/
|
|
3003
|
+
generatePrompt(domain, config) {
|
|
3004
|
+
const descriptor = this.descriptors.get(domain);
|
|
3005
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
3006
|
+
return generatePrompt({
|
|
3007
|
+
domain,
|
|
3008
|
+
description: descriptor.description,
|
|
3009
|
+
schemas: descriptor.schemas,
|
|
3010
|
+
...config
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Synthesize training data for a domain from its schemas.
|
|
3015
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
3016
|
+
*/
|
|
3017
|
+
generateTrainingData(domain, config) {
|
|
3018
|
+
const descriptor = this.descriptors.get(domain);
|
|
3019
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
3020
|
+
return synthesizeFromSchemas(descriptor.schemas, {
|
|
3021
|
+
domain,
|
|
3022
|
+
...config
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Build structured feedback for a failed LSE input in a domain.
|
|
3027
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
3028
|
+
*/
|
|
3029
|
+
buildFeedback(domain, input, inputFormat, diagnostics) {
|
|
3030
|
+
const descriptor = this.descriptors.get(domain);
|
|
3031
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
3032
|
+
const schemaLookup = this.createSchemaLookup(descriptor.schemas);
|
|
3033
|
+
let action;
|
|
3034
|
+
try {
|
|
3035
|
+
if (inputFormat === "explicit") {
|
|
3036
|
+
const node = parseExplicit(input, { schemaLookup });
|
|
3037
|
+
action = node.action;
|
|
3038
|
+
}
|
|
3039
|
+
} catch {
|
|
3040
|
+
}
|
|
3041
|
+
return buildFeedback(input, inputFormat, diagnostics, schemaLookup, action);
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* Get schemas for a domain (if available).
|
|
3045
|
+
*/
|
|
3046
|
+
getSchemas(domain) {
|
|
3047
|
+
const descriptor = this.descriptors.get(domain);
|
|
3048
|
+
if (!descriptor?.schemas) return null;
|
|
3049
|
+
return descriptor.schemas;
|
|
3050
|
+
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Create a SchemaLookup from a domain's schemas.
|
|
3053
|
+
*/
|
|
3054
|
+
createSchemaLookup(schemas) {
|
|
3055
|
+
const map = /* @__PURE__ */ new Map();
|
|
3056
|
+
for (const s of schemas) {
|
|
3057
|
+
map.set(s.action, s);
|
|
3058
|
+
}
|
|
3059
|
+
return { getSchema: (action) => map.get(action) };
|
|
3060
|
+
}
|
|
3061
|
+
// ---------------------------------------------------------------------------
|
|
1772
3062
|
// Private: DSL/renderer lifecycle
|
|
1773
3063
|
// ---------------------------------------------------------------------------
|
|
1774
3064
|
async getDSL(descriptor) {
|
|
@@ -1868,7 +3158,7 @@ function generateToolDefinitions(desc) {
|
|
|
1868
3158
|
case "parse":
|
|
1869
3159
|
tools.push({
|
|
1870
3160
|
name: `parse_${desc.name}`,
|
|
1871
|
-
description: `Parse a ${desc.description} input into a semantic representation. Supports: ${langList}.`,
|
|
3161
|
+
description: `Parse a ${desc.description} input into a semantic representation (action, roles, confidence). Supports: ${langList}.`,
|
|
1872
3162
|
inputSchema: {
|
|
1873
3163
|
type: "object",
|
|
1874
3164
|
properties: {
|
|
@@ -1889,7 +3179,7 @@ function generateToolDefinitions(desc) {
|
|
|
1889
3179
|
case "compile":
|
|
1890
3180
|
tools.push({
|
|
1891
3181
|
name: `compile_${desc.name}`,
|
|
1892
|
-
description: `Compile a ${desc.description} input to target code. Supports: ${langList}.`,
|
|
3182
|
+
description: `Compile a ${desc.description} input to ${desc.outputDescription ?? "target code"}. Supports: ${langList}.`,
|
|
1893
3183
|
inputSchema: {
|
|
1894
3184
|
type: "object",
|
|
1895
3185
|
properties: {
|
|
@@ -1910,7 +3200,7 @@ function generateToolDefinitions(desc) {
|
|
|
1910
3200
|
case "validate":
|
|
1911
3201
|
tools.push({
|
|
1912
3202
|
name: `validate_${desc.name}`,
|
|
1913
|
-
description: `
|
|
3203
|
+
description: `Check ${desc.description} syntax for errors. Returns {valid, errors[]}. Supports: ${langList}.`,
|
|
1914
3204
|
inputSchema: {
|
|
1915
3205
|
type: "object",
|
|
1916
3206
|
properties: {
|
|
@@ -1931,7 +3221,7 @@ function generateToolDefinitions(desc) {
|
|
|
1931
3221
|
case "translate":
|
|
1932
3222
|
tools.push({
|
|
1933
3223
|
name: `translate_${desc.name}`,
|
|
1934
|
-
description: `Translate ${desc.description} input between natural languages
|
|
3224
|
+
description: `Translate ${desc.description} input between natural languages (requires 'from' and 'to' codes). Supports: ${langList}.`,
|
|
1935
3225
|
inputSchema: {
|
|
1936
3226
|
type: "object",
|
|
1937
3227
|
properties: {
|
|
@@ -1997,9 +3287,9 @@ var CrossDomainDispatcher = class {
|
|
|
1997
3287
|
if (domainNames.length === 0) return null;
|
|
1998
3288
|
const candidates = [];
|
|
1999
3289
|
for (const name of domainNames) {
|
|
2000
|
-
const dsl = await this.registry.getDSLForDomain(name);
|
|
2001
|
-
if (!dsl) continue;
|
|
2002
3290
|
try {
|
|
3291
|
+
const dsl = await this.registry.getDSLForDomain(name);
|
|
3292
|
+
if (!dsl) continue;
|
|
2003
3293
|
const { node, confidence } = dsl.parseWithConfidence(input, language);
|
|
2004
3294
|
if (confidence >= this.minConfidence) {
|
|
2005
3295
|
candidates.push({ domain: name, node, confidence, dsl });
|
|
@@ -2008,10 +3298,16 @@ var CrossDomainDispatcher = class {
|
|
|
2008
3298
|
}
|
|
2009
3299
|
}
|
|
2010
3300
|
if (candidates.length === 0) return null;
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
3301
|
+
if (this.priority.length > 0) {
|
|
3302
|
+
candidates.sort((a, b) => {
|
|
3303
|
+
const pa = this.getPriorityIndex(a.domain);
|
|
3304
|
+
const pb = this.getPriorityIndex(b.domain);
|
|
3305
|
+
if (pa !== pb) return pa - pb;
|
|
3306
|
+
return b.confidence - a.confidence;
|
|
3307
|
+
});
|
|
3308
|
+
} else {
|
|
3309
|
+
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
3310
|
+
}
|
|
2015
3311
|
return candidates[0];
|
|
2016
3312
|
}
|
|
2017
3313
|
/**
|
|
@@ -2055,6 +3351,40 @@ var CrossDomainDispatcher = class {
|
|
|
2055
3351
|
const result = detected.dsl.compile(input, language);
|
|
2056
3352
|
return { ...result, domain: detected.domain };
|
|
2057
3353
|
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Compile multi-line input where each line may belong to a different domain.
|
|
3356
|
+
* Detects the domain for each line and compiles it, returning per-line results.
|
|
3357
|
+
* Empty lines and comment lines are skipped.
|
|
3358
|
+
*/
|
|
3359
|
+
async compileComposite(input, language = "en") {
|
|
3360
|
+
const lines = input.split("\n");
|
|
3361
|
+
const statements = [];
|
|
3362
|
+
const errors = [];
|
|
3363
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3364
|
+
const line = lines[i];
|
|
3365
|
+
if (isSkippableLine(line)) continue;
|
|
3366
|
+
const trimmed = line.trim();
|
|
3367
|
+
const detected = await this.detect(trimmed, language);
|
|
3368
|
+
if (detected) {
|
|
3369
|
+
const result = detected.dsl.compile(trimmed, language);
|
|
3370
|
+
statements.push({
|
|
3371
|
+
line: i + 1,
|
|
3372
|
+
input: trimmed,
|
|
3373
|
+
domain: detected.domain,
|
|
3374
|
+
ok: result.ok,
|
|
3375
|
+
code: result.code,
|
|
3376
|
+
errors: result.errors
|
|
3377
|
+
});
|
|
3378
|
+
} else {
|
|
3379
|
+
errors.push({
|
|
3380
|
+
line: i + 1,
|
|
3381
|
+
input: trimmed,
|
|
3382
|
+
message: "No domain matched this input"
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
return { statements, errors };
|
|
3387
|
+
}
|
|
2058
3388
|
/**
|
|
2059
3389
|
* Validate input against all domains, returning the best match.
|
|
2060
3390
|
*/
|