@lokascript/framework 2.0.0 → 2.2.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 +1438 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +247 -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/tokenization/index.js +133 -0
- package/dist/core/tokenization/index.js.map +1 -1
- package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
- package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +3 -1
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
- package/dist/core/types.d.ts +81 -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 +3264 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3223 -132
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +86 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +34 -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 +31 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1934 -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/to-runtime-ast.d.ts +53 -0
- package/dist/ir/to-runtime-ast.d.ts.map +1 -0
- package/dist/ir/types.d.ts +151 -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 +8673 -8260
- 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/tokenization/morphology/base-normalizer.ts +249 -0
- package/src/core/tokenization/morphology/index.ts +3 -1
- package/src/core/types.ts +147 -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 +29 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/conformance.test.ts +120 -0
- package/src/ir/explicit-parser.test.ts +612 -0
- package/src/ir/explicit-parser.ts +793 -0
- package/src/ir/explicit-renderer.test.ts +229 -0
- package/src/ir/explicit-renderer.ts +198 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +77 -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 +1187 -0
- package/src/ir/protocol-json.ts +751 -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/to-runtime-ast.ts +203 -0
- package/src/ir/types.ts +191 -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,455 @@ 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
|
+
const collect = options.collectDiagnostics === true;
|
|
1700
|
+
const diagnostics = [];
|
|
1701
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
|
|
1702
|
+
throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
|
|
1703
|
+
}
|
|
1704
|
+
const content = trimmed.slice(1, -1).trim();
|
|
1705
|
+
if (!content) {
|
|
1706
|
+
throw new Error("Empty explicit statement");
|
|
1707
|
+
}
|
|
1708
|
+
const tokens = tokenizeExplicit(content);
|
|
1709
|
+
if (tokens.length === 0) {
|
|
1710
|
+
throw new Error("No command specified in explicit statement");
|
|
1711
|
+
}
|
|
1712
|
+
const command = tokens[0].toLowerCase();
|
|
1713
|
+
const roles = /* @__PURE__ */ new Map();
|
|
1714
|
+
const refSet = options.referenceSet ?? DEFAULT_REFERENCES;
|
|
1715
|
+
const schema = options.schemaLookup?.getSchema(command);
|
|
1716
|
+
const validRoleNames = schema ? new Set(schema.roles.map((r) => r.role)) : null;
|
|
1717
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
1718
|
+
const token = tokens[i];
|
|
1719
|
+
if (token.startsWith("+") || token.startsWith("~")) {
|
|
1720
|
+
const enabled = token.startsWith("+");
|
|
1721
|
+
const flagName = token.slice(1);
|
|
1722
|
+
if (!flagName) {
|
|
1723
|
+
if (collect) {
|
|
1724
|
+
diagnostics.push({
|
|
1725
|
+
severity: "error",
|
|
1726
|
+
code: "EMPTY_FLAG",
|
|
1727
|
+
message: `Empty flag name: "${token}"`,
|
|
1728
|
+
source: "schema"
|
|
1729
|
+
});
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
throw new Error(`Empty flag name: "${token}"`);
|
|
1733
|
+
}
|
|
1734
|
+
if (validRoleNames && !validRoleNames.has(flagName)) {
|
|
1735
|
+
const roleList = [...validRoleNames].join(", ");
|
|
1736
|
+
if (collect) {
|
|
1737
|
+
diagnostics.push({
|
|
1738
|
+
severity: "error",
|
|
1739
|
+
code: "UNKNOWN_FLAG",
|
|
1740
|
+
message: `Unknown flag "${flagName}" for command "${command}"`,
|
|
1741
|
+
source: "schema",
|
|
1742
|
+
suggestions: [...validRoleNames].map((r) => `+${r}`)
|
|
1743
|
+
});
|
|
1744
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
const colonIndex = token.indexOf(":");
|
|
1755
|
+
if (colonIndex === -1) {
|
|
1756
|
+
if (collect) {
|
|
1757
|
+
diagnostics.push({
|
|
1758
|
+
severity: "error",
|
|
1759
|
+
code: "INVALID_ROLE_FORMAT",
|
|
1760
|
+
message: `Invalid role format: "${token}". Expected role:value or +flag`,
|
|
1761
|
+
source: "schema"
|
|
1762
|
+
});
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
|
|
1766
|
+
}
|
|
1767
|
+
const roleName = token.slice(0, colonIndex);
|
|
1768
|
+
if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
|
|
1769
|
+
const roleList = [...validRoleNames].join(", ");
|
|
1770
|
+
if (collect) {
|
|
1771
|
+
diagnostics.push({
|
|
1772
|
+
severity: "error",
|
|
1773
|
+
code: "UNKNOWN_ROLE",
|
|
1774
|
+
message: `Unknown role "${roleName}" for command "${command}"`,
|
|
1775
|
+
source: "schema",
|
|
1776
|
+
suggestions: [...validRoleNames]
|
|
1777
|
+
});
|
|
1778
|
+
} else {
|
|
1779
|
+
throw new Error(
|
|
1780
|
+
`Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const role = roleName;
|
|
1785
|
+
const valueStr = token.slice(colonIndex + 1);
|
|
1786
|
+
if (STRUCTURAL_ROLES.has(roleName) && valueStr.startsWith("[") && isNestedCommand(valueStr)) {
|
|
1787
|
+
const nestedEnd = findMatchingBracket(token, colonIndex + 1);
|
|
1788
|
+
const nestedSyntax = token.slice(colonIndex + 1, nestedEnd + 1);
|
|
1789
|
+
roles.set(role, { type: "expression", raw: nestedSyntax });
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
const value = parseExplicitValue(valueStr, refSet);
|
|
1793
|
+
roles.set(role, value);
|
|
1794
|
+
}
|
|
1795
|
+
if (schema && command !== "on") {
|
|
1796
|
+
for (const roleSpec of schema.roles) {
|
|
1797
|
+
if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
|
|
1798
|
+
if (collect) {
|
|
1799
|
+
diagnostics.push({
|
|
1800
|
+
severity: "error",
|
|
1801
|
+
code: "MISSING_REQUIRED_ROLE",
|
|
1802
|
+
message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
|
|
1803
|
+
source: "schema"
|
|
1804
|
+
});
|
|
1805
|
+
} else {
|
|
1806
|
+
throw new Error(
|
|
1807
|
+
`Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
const attachDiagnostics = (node) => {
|
|
1814
|
+
if (diagnostics.length === 0) return node;
|
|
1815
|
+
return { ...node, diagnostics };
|
|
1816
|
+
};
|
|
1817
|
+
switch (command) {
|
|
1818
|
+
case "on": {
|
|
1819
|
+
const eventValue = roles.get("event");
|
|
1820
|
+
if (!eventValue) {
|
|
1821
|
+
if (collect) {
|
|
1822
|
+
diagnostics.push({
|
|
1823
|
+
severity: "error",
|
|
1824
|
+
code: "MISSING_EVENT_ROLE",
|
|
1825
|
+
message: "Event handler requires event role: [on event:click ...]",
|
|
1826
|
+
source: "schema"
|
|
1827
|
+
});
|
|
1828
|
+
return attachDiagnostics(
|
|
1829
|
+
createEventHandlerNode("on", roles, [], {
|
|
1830
|
+
sourceLanguage: "explicit"
|
|
1831
|
+
})
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
throw new Error("Event handler requires event role: [on event:click ...]");
|
|
1835
|
+
}
|
|
1836
|
+
const body = extractStructuralBody(roles, "body", options);
|
|
1837
|
+
roles.delete("body");
|
|
1838
|
+
return attachDiagnostics(
|
|
1839
|
+
createEventHandlerNode("on", roles, body, {
|
|
1840
|
+
sourceLanguage: "explicit"
|
|
1841
|
+
})
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
case "if": {
|
|
1845
|
+
const thenBranch = extractStructuralBody(roles, "then", options);
|
|
1846
|
+
const elseBranch = extractStructuralBody(roles, "else", options);
|
|
1847
|
+
roles.delete("then");
|
|
1848
|
+
roles.delete("else");
|
|
1849
|
+
return attachDiagnostics(
|
|
1850
|
+
createConditionalNode(
|
|
1851
|
+
command,
|
|
1852
|
+
roles,
|
|
1853
|
+
thenBranch,
|
|
1854
|
+
elseBranch.length > 0 ? elseBranch : void 0,
|
|
1855
|
+
{
|
|
1856
|
+
sourceLanguage: "explicit"
|
|
1857
|
+
}
|
|
1858
|
+
)
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
case "repeat": {
|
|
1862
|
+
const loopBody = extractStructuralBody(roles, "loop-body", options);
|
|
1863
|
+
const loopVariantValue = roles.get("loop-variant");
|
|
1864
|
+
const variableValue = roles.get("variable");
|
|
1865
|
+
const indexVariableValue = roles.get("index-variable");
|
|
1866
|
+
roles.delete("loop-body");
|
|
1867
|
+
roles.delete("loop-variant");
|
|
1868
|
+
roles.delete("variable");
|
|
1869
|
+
roles.delete("index-variable");
|
|
1870
|
+
let variant;
|
|
1871
|
+
if (loopVariantValue && loopVariantValue.type === "literal" && typeof loopVariantValue.value === "string") {
|
|
1872
|
+
variant = loopVariantValue.value;
|
|
1873
|
+
} else if (roles.has("quantity")) {
|
|
1874
|
+
variant = "times";
|
|
1875
|
+
} else if (roles.has("source")) {
|
|
1876
|
+
variant = "for";
|
|
1877
|
+
} else if (roles.has("condition")) {
|
|
1878
|
+
variant = "while";
|
|
1879
|
+
} else {
|
|
1880
|
+
variant = "forever";
|
|
1881
|
+
}
|
|
1882
|
+
const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
|
|
1883
|
+
const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
|
|
1884
|
+
return attachDiagnostics(
|
|
1885
|
+
createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
|
|
1886
|
+
sourceLanguage: "explicit"
|
|
1887
|
+
})
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
default:
|
|
1891
|
+
return attachDiagnostics(
|
|
1892
|
+
createCommandNode(command, roles, {
|
|
1893
|
+
sourceLanguage: "explicit"
|
|
1894
|
+
})
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function isExplicitSyntax(input) {
|
|
1899
|
+
const trimmed = input.trim();
|
|
1900
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
1901
|
+
}
|
|
1902
|
+
function extractStructuralBody(roles, roleName, options) {
|
|
1903
|
+
const value = roles.get(roleName);
|
|
1904
|
+
if (!value) return [];
|
|
1905
|
+
if (value.type === "expression") {
|
|
1906
|
+
return [parseExplicit(value.raw, options)];
|
|
1907
|
+
}
|
|
1908
|
+
return [];
|
|
1909
|
+
}
|
|
1910
|
+
function inferSelectorKind(value) {
|
|
1911
|
+
if (!value) return void 0;
|
|
1912
|
+
if (value.startsWith("#")) return "id";
|
|
1913
|
+
if (value.startsWith(".")) return "class";
|
|
1914
|
+
if (value.startsWith("[")) return "attribute";
|
|
1915
|
+
if (value.startsWith("<") || value.startsWith("*")) return "element";
|
|
1916
|
+
if (/[>+~ ]/.test(value) && value.length > 1) return "complex";
|
|
1917
|
+
return void 0;
|
|
1918
|
+
}
|
|
1919
|
+
function tokenizeExplicit(content) {
|
|
1920
|
+
const tokens = [];
|
|
1921
|
+
let current = "";
|
|
1922
|
+
let inString = false;
|
|
1923
|
+
let stringChar = "";
|
|
1924
|
+
let bracketDepth = 0;
|
|
1925
|
+
for (let i = 0; i < content.length; i++) {
|
|
1926
|
+
const char = content[i];
|
|
1927
|
+
if (inString) {
|
|
1928
|
+
current += char;
|
|
1929
|
+
if (char === stringChar && content[i - 1] !== "\\") {
|
|
1930
|
+
inString = false;
|
|
1931
|
+
}
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (char === '"' || char === "'") {
|
|
1935
|
+
inString = true;
|
|
1936
|
+
stringChar = char;
|
|
1937
|
+
current += char;
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (char === "[") {
|
|
1941
|
+
bracketDepth++;
|
|
1942
|
+
current += char;
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
if (char === "]") {
|
|
1946
|
+
bracketDepth--;
|
|
1947
|
+
current += char;
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
if (char === " " && bracketDepth === 0) {
|
|
1951
|
+
if (current) {
|
|
1952
|
+
tokens.push(current);
|
|
1953
|
+
current = "";
|
|
1954
|
+
}
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
current += char;
|
|
1958
|
+
}
|
|
1959
|
+
if (current) {
|
|
1960
|
+
tokens.push(current);
|
|
1961
|
+
}
|
|
1962
|
+
return tokens;
|
|
1963
|
+
}
|
|
1964
|
+
function parseExplicitValue(valueStr, referenceSet) {
|
|
1965
|
+
if (valueStr.startsWith("#") || valueStr.startsWith(".") || valueStr.startsWith("[") || valueStr.startsWith("@") || valueStr.startsWith("*")) {
|
|
1966
|
+
return createSelector(valueStr, inferSelectorKind(valueStr));
|
|
1967
|
+
}
|
|
1968
|
+
if (valueStr.startsWith('"') || valueStr.startsWith("'")) {
|
|
1969
|
+
const inner = valueStr.slice(1, -1);
|
|
1970
|
+
return createLiteral(inner, "string");
|
|
1971
|
+
}
|
|
1972
|
+
if (valueStr === "true") return createLiteral(true, "boolean");
|
|
1973
|
+
if (valueStr === "false") return createLiteral(false, "boolean");
|
|
1974
|
+
const lowerRef = valueStr.toLowerCase();
|
|
1975
|
+
if (isValidReference2(lowerRef, referenceSet)) {
|
|
1976
|
+
return createReference(lowerRef);
|
|
1977
|
+
}
|
|
1978
|
+
const numMatch = valueStr.match(/^(-?\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
1979
|
+
if (numMatch) {
|
|
1980
|
+
const num = parseFloat(numMatch[1]);
|
|
1981
|
+
const suffix = numMatch[2];
|
|
1982
|
+
if (suffix) {
|
|
1983
|
+
return createLiteral(valueStr, "duration");
|
|
1984
|
+
}
|
|
1985
|
+
return createLiteral(num, "number");
|
|
1986
|
+
}
|
|
1987
|
+
return createLiteral(valueStr);
|
|
1988
|
+
}
|
|
1989
|
+
function findMatchingBracket(str, start) {
|
|
1990
|
+
let depth = 0;
|
|
1991
|
+
for (let i = start; i < str.length; i++) {
|
|
1992
|
+
if (str[i] === "[") depth++;
|
|
1993
|
+
else if (str[i] === "]") {
|
|
1994
|
+
depth--;
|
|
1995
|
+
if (depth === 0) return i;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return str.length - 1;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// src/ir/explicit-renderer.ts
|
|
2002
|
+
function renderExplicit(node) {
|
|
2003
|
+
let result;
|
|
2004
|
+
if (node.kind === "compound") {
|
|
2005
|
+
const compoundNode = node;
|
|
2006
|
+
const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
|
|
2007
|
+
result = renderedStatements.join(` ${compoundNode.chainType} `);
|
|
2008
|
+
} else {
|
|
2009
|
+
result = renderBracketCommand(node);
|
|
2010
|
+
}
|
|
2011
|
+
if (node.annotations && node.annotations.length > 0) {
|
|
2012
|
+
const annParts = node.annotations.map(
|
|
2013
|
+
(ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
|
|
2014
|
+
);
|
|
2015
|
+
return annParts.join(" ") + " " + result;
|
|
2016
|
+
}
|
|
2017
|
+
return result;
|
|
2018
|
+
}
|
|
2019
|
+
function renderBracketCommand(node) {
|
|
2020
|
+
const parts = [node.action];
|
|
2021
|
+
for (const [role, value] of node.roles) {
|
|
2022
|
+
if (value.type === "flag") {
|
|
2023
|
+
parts.push(value.enabled ? `+${value.name}` : `~${value.name}`);
|
|
2024
|
+
} else {
|
|
2025
|
+
parts.push(`${role}:${valueToString(value)}`);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (node.kind === "event-handler") {
|
|
2029
|
+
const eventNode = node;
|
|
2030
|
+
if (eventNode.body && eventNode.body.length > 0) {
|
|
2031
|
+
const bodyParts = eventNode.body.map((n) => renderExplicit(n));
|
|
2032
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
if (node.kind === "conditional") {
|
|
2036
|
+
const condNode = node;
|
|
2037
|
+
if (condNode.thenBranch && condNode.thenBranch.length > 0) {
|
|
2038
|
+
const thenParts = condNode.thenBranch.map((n) => renderExplicit(n));
|
|
2039
|
+
parts.push(`then:${thenParts.join(" ")}`);
|
|
2040
|
+
}
|
|
2041
|
+
if (condNode.elseBranch && condNode.elseBranch.length > 0) {
|
|
2042
|
+
const elseParts = condNode.elseBranch.map((n) => renderExplicit(n));
|
|
2043
|
+
parts.push(`else:${elseParts.join(" ")}`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (node.kind === "loop") {
|
|
2047
|
+
const loopNode = node;
|
|
2048
|
+
if (loopNode.loopVariant) {
|
|
2049
|
+
parts.push(`loop-variant:${loopNode.loopVariant}`);
|
|
2050
|
+
}
|
|
2051
|
+
if (loopNode.loopVariable) {
|
|
2052
|
+
parts.push(`variable:${loopNode.loopVariable}`);
|
|
2053
|
+
}
|
|
2054
|
+
if (loopNode.indexVariable) {
|
|
2055
|
+
parts.push(`index-variable:${loopNode.indexVariable}`);
|
|
2056
|
+
}
|
|
2057
|
+
if (loopNode.body && loopNode.body.length > 0) {
|
|
2058
|
+
const bodyParts = loopNode.body.map((n) => renderExplicit(n));
|
|
2059
|
+
parts.push(`loop-body:${bodyParts.join(" ")}`);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
if (node.kind === "command") {
|
|
2063
|
+
const cmd = node;
|
|
2064
|
+
if (cmd.body && cmd.body.length > 0) {
|
|
2065
|
+
const bodyParts = cmd.body.map((n) => renderExplicit(n));
|
|
2066
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
2067
|
+
}
|
|
2068
|
+
if (cmd.catchBranch && cmd.catchBranch.length > 0) {
|
|
2069
|
+
const catchParts = cmd.catchBranch.map((n) => renderExplicit(n));
|
|
2070
|
+
parts.push(`catch:${catchParts.join(" ")}`);
|
|
2071
|
+
}
|
|
2072
|
+
if (cmd.finallyBranch && cmd.finallyBranch.length > 0) {
|
|
2073
|
+
const finallyParts = cmd.finallyBranch.map((n) => renderExplicit(n));
|
|
2074
|
+
parts.push(`finally:${finallyParts.join(" ")}`);
|
|
2075
|
+
}
|
|
2076
|
+
if (cmd.asyncVariant) {
|
|
2077
|
+
parts.push(`async-variant:${cmd.asyncVariant}`);
|
|
2078
|
+
}
|
|
2079
|
+
if (cmd.asyncBody && cmd.asyncBody.length > 0) {
|
|
2080
|
+
const asyncParts = cmd.asyncBody.map((n) => renderExplicit(n));
|
|
2081
|
+
parts.push(`async-body:${asyncParts.join(" ")}`);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
return `[${parts.join(" ")}]`;
|
|
2085
|
+
}
|
|
2086
|
+
function valueToString(value) {
|
|
2087
|
+
switch (value.type) {
|
|
2088
|
+
case "literal":
|
|
2089
|
+
if (typeof value.value === "string") {
|
|
2090
|
+
if (value.dataType === "string" || /\s/.test(value.value)) {
|
|
2091
|
+
return `"${value.value}"`;
|
|
2092
|
+
}
|
|
2093
|
+
return value.value;
|
|
2094
|
+
}
|
|
2095
|
+
return String(value.value);
|
|
2096
|
+
case "selector":
|
|
2097
|
+
return value.value;
|
|
2098
|
+
case "reference":
|
|
2099
|
+
return value.value;
|
|
2100
|
+
case "property-path":
|
|
2101
|
+
return `${valueToString(value.object)}'s ${value.property}`;
|
|
2102
|
+
case "expression":
|
|
2103
|
+
return value.raw;
|
|
2104
|
+
case "flag":
|
|
2105
|
+
return value.name;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
1537
2109
|
// src/api/create-dsl.ts
|
|
1538
2110
|
var DSLRegistry = class {
|
|
1539
2111
|
constructor(config) {
|
|
@@ -1571,12 +2143,23 @@ var MultilingualDSLImpl = class {
|
|
|
1571
2143
|
if (config.codeGenerator) {
|
|
1572
2144
|
this.codeGenerator = config.codeGenerator;
|
|
1573
2145
|
}
|
|
2146
|
+
const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
|
|
2147
|
+
this.schemaLookup = {
|
|
2148
|
+
getSchema: (action) => schemaMap.get(action)
|
|
2149
|
+
};
|
|
1574
2150
|
}
|
|
1575
2151
|
parse(input, language) {
|
|
1576
2152
|
const result = this.parseWithConfidence(input, language);
|
|
1577
2153
|
return result.node;
|
|
1578
2154
|
}
|
|
1579
2155
|
parseWithConfidence(input, language) {
|
|
2156
|
+
if (isExplicitSyntax(input)) {
|
|
2157
|
+
const node = parseExplicit(input, { schemaLookup: this.schemaLookup });
|
|
2158
|
+
if (!this.schemaLookup.getSchema(node.action)) {
|
|
2159
|
+
throw new Error(`No schema for action "${node.action}" in this DSL`);
|
|
2160
|
+
}
|
|
2161
|
+
return { node, confidence: 1 };
|
|
2162
|
+
}
|
|
1580
2163
|
const tokenizer = this.registry.getTokenizer(language);
|
|
1581
2164
|
if (!tokenizer) {
|
|
1582
2165
|
throw new Error(`No tokenizer registered for language: ${language}`);
|
|
@@ -1620,6 +2203,7 @@ var MultilingualDSLImpl = class {
|
|
|
1620
2203
|
}
|
|
1621
2204
|
}
|
|
1622
2205
|
translate(input, fromLanguage, toLanguage) {
|
|
2206
|
+
if (isExplicitSyntax(input)) return input;
|
|
1623
2207
|
return this.transformer.transform(input, fromLanguage, toLanguage);
|
|
1624
2208
|
}
|
|
1625
2209
|
compile(input, language) {
|
|
@@ -1683,6 +2267,723 @@ function createMultilingualDSL(config) {
|
|
|
1683
2267
|
return new MultilingualDSLImpl(config, registry, transformer);
|
|
1684
2268
|
}
|
|
1685
2269
|
|
|
2270
|
+
// src/prompts/prompt-generator.ts
|
|
2271
|
+
function generatePrompt(config) {
|
|
2272
|
+
const outputFormat = config.outputFormat ?? "both";
|
|
2273
|
+
const examplesPerCommand = config.examplesPerCommand ?? 2;
|
|
2274
|
+
const sections = [
|
|
2275
|
+
buildProtocolSection(),
|
|
2276
|
+
buildValueTypeSection(),
|
|
2277
|
+
buildCommandsSection(config.schemas, outputFormat, examplesPerCommand),
|
|
2278
|
+
buildOutputFormatSection(outputFormat),
|
|
2279
|
+
buildErrorRecoverySection()
|
|
2280
|
+
];
|
|
2281
|
+
let finalSections = sections;
|
|
2282
|
+
if (config.maxTokens && config.maxTokens > 0) {
|
|
2283
|
+
finalSections = truncateToTokenBudget(sections, config.maxTokens);
|
|
2284
|
+
}
|
|
2285
|
+
const text = finalSections.map((s) => `## ${s.title}
|
|
2286
|
+
|
|
2287
|
+
${s.content}`).join("\n\n---\n\n");
|
|
2288
|
+
const totalRoles = config.schemas.reduce((sum, s) => sum + s.roles.length, 0);
|
|
2289
|
+
const approximateTokens = estimateTokens(text);
|
|
2290
|
+
const metadata = {
|
|
2291
|
+
domain: config.domain,
|
|
2292
|
+
commandCount: config.schemas.length,
|
|
2293
|
+
roleCount: totalRoles,
|
|
2294
|
+
approximateTokens
|
|
2295
|
+
};
|
|
2296
|
+
return { text, sections: finalSections, metadata };
|
|
2297
|
+
}
|
|
2298
|
+
function generateExamples(schema, count = 2) {
|
|
2299
|
+
const combos = generateRoleCombinations(schema);
|
|
2300
|
+
const examples = [];
|
|
2301
|
+
for (let i = 0; i < Math.min(count, combos.length); i++) {
|
|
2302
|
+
const combo = combos[i];
|
|
2303
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2304
|
+
for (const role of combo) {
|
|
2305
|
+
const sample = sampleValue(role);
|
|
2306
|
+
roles.set(role.role, sample);
|
|
2307
|
+
}
|
|
2308
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
2309
|
+
for (const [roleName, sample] of roles) {
|
|
2310
|
+
nodeRoles.set(roleName, toSemanticValue(sample));
|
|
2311
|
+
}
|
|
2312
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
2313
|
+
const explicit = renderExplicit(node);
|
|
2314
|
+
const jsonRoles = {};
|
|
2315
|
+
for (const [roleName, sample] of roles) {
|
|
2316
|
+
jsonRoles[roleName] = sample;
|
|
2317
|
+
}
|
|
2318
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
2319
|
+
examples.push({ explicit, json });
|
|
2320
|
+
}
|
|
2321
|
+
return examples;
|
|
2322
|
+
}
|
|
2323
|
+
function buildProtocolSection() {
|
|
2324
|
+
const content = `LokaScript Explicit Syntax (LSE) is a bracket-based format for imperative commands.
|
|
2325
|
+
|
|
2326
|
+
### Syntax
|
|
2327
|
+
|
|
2328
|
+
\`\`\`
|
|
2329
|
+
[command role1:value1 role2:value2 +flag1 ~flag2]
|
|
2330
|
+
\`\`\`
|
|
2331
|
+
|
|
2332
|
+
- **Command**: The first token inside brackets (lowercased)
|
|
2333
|
+
- **Role pair**: \`name:value\` \u2014 a named semantic role with a typed value (no space around colon)
|
|
2334
|
+
- **Enabled flag**: \`+name\` \u2014 boolean attribute present
|
|
2335
|
+
- **Disabled flag**: \`~name\` \u2014 boolean attribute negated
|
|
2336
|
+
- **Nested body**: \`body:[command ...]\` \u2014 a bracket command inside a role value
|
|
2337
|
+
|
|
2338
|
+
### Rules
|
|
2339
|
+
|
|
2340
|
+
1. Commands are always lowercased
|
|
2341
|
+
2. Role names preserve their original case
|
|
2342
|
+
3. No spaces around the colon in role:value pairs
|
|
2343
|
+
4. Strings with spaces must be quoted: \`patient:"hello world"\`
|
|
2344
|
+
5. Selectors start with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`
|
|
2345
|
+
6. Output must be valid bracket syntax: \`[action role:value ...]\``;
|
|
2346
|
+
return {
|
|
2347
|
+
id: "protocol",
|
|
2348
|
+
title: "LSE Protocol",
|
|
2349
|
+
content,
|
|
2350
|
+
approximateTokens: estimateTokens(content)
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2353
|
+
function buildValueTypeSection() {
|
|
2354
|
+
const content = `Values in LSE are classified by their syntactic form (first match wins):
|
|
2355
|
+
|
|
2356
|
+
| Type | Syntax | Example |
|
|
2357
|
+
|------|--------|---------|
|
|
2358
|
+
| Selector | Starts with \`#\` \`.\` \`[\` \`@\` \`*\` | \`#button\`, \`.active\`, \`[data-id]\` |
|
|
2359
|
+
| String | Quoted with \`"\` or \`'\` | \`"hello world"\`, \`'json'\` |
|
|
2360
|
+
| Boolean | Exact: \`true\` / \`false\` | \`visible:true\` |
|
|
2361
|
+
| Reference | Built-in name | \`me\`, \`you\`, \`it\`, \`result\`, \`event\`, \`target\`, \`body\` |
|
|
2362
|
+
| Duration | Number + suffix | \`500ms\`, \`2s\`, \`1m\`, \`1h\` |
|
|
2363
|
+
| Number | Digits with optional decimal | \`5\`, \`3.14\`, \`-1\` |
|
|
2364
|
+
| Plain | Fallback (any non-whitespace) | \`/api/users\`, \`json\`, \`production\` |
|
|
2365
|
+
|
|
2366
|
+
**Important**: Classification is by prefix, not by intent. \`#true\` is a selector (not boolean). \`event\` as a value is a reference (not plain text).`;
|
|
2367
|
+
return {
|
|
2368
|
+
id: "value-types",
|
|
2369
|
+
title: "Value Types",
|
|
2370
|
+
content,
|
|
2371
|
+
approximateTokens: estimateTokens(content)
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
function buildCommandsSection(schemas, outputFormat, examplesPerCommand) {
|
|
2375
|
+
const parts = [];
|
|
2376
|
+
for (const schema of schemas) {
|
|
2377
|
+
parts.push(formatCommandDoc(schema, outputFormat, examplesPerCommand));
|
|
2378
|
+
}
|
|
2379
|
+
const content = parts.join("\n\n");
|
|
2380
|
+
return {
|
|
2381
|
+
id: "commands",
|
|
2382
|
+
title: "Available Commands",
|
|
2383
|
+
content,
|
|
2384
|
+
approximateTokens: estimateTokens(content)
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function buildOutputFormatSection(outputFormat) {
|
|
2388
|
+
let content;
|
|
2389
|
+
if (outputFormat === "explicit") {
|
|
2390
|
+
content = `Output valid LSE bracket syntax. Each command must be wrapped in brackets:
|
|
2391
|
+
|
|
2392
|
+
\`\`\`
|
|
2393
|
+
[command role:value ...]
|
|
2394
|
+
\`\`\`
|
|
2395
|
+
|
|
2396
|
+
Do NOT output JSON. Only use bracket syntax.`;
|
|
2397
|
+
} else if (outputFormat === "json") {
|
|
2398
|
+
content = `Output valid JSON in the LLM-simplified format:
|
|
2399
|
+
|
|
2400
|
+
\`\`\`json
|
|
2401
|
+
{
|
|
2402
|
+
"action": "command-name",
|
|
2403
|
+
"roles": {
|
|
2404
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
\`\`\`
|
|
2408
|
+
|
|
2409
|
+
Valid value types: \`selector\`, \`literal\`, \`reference\`, \`expression\`.
|
|
2410
|
+
Do NOT output bracket syntax. Only use JSON.`;
|
|
2411
|
+
} else {
|
|
2412
|
+
content = `You may output EITHER format:
|
|
2413
|
+
|
|
2414
|
+
**Bracket syntax** (preferred for single commands):
|
|
2415
|
+
\`\`\`
|
|
2416
|
+
[command role:value ...]
|
|
2417
|
+
\`\`\`
|
|
2418
|
+
|
|
2419
|
+
**JSON format** (preferred when structured data is needed):
|
|
2420
|
+
\`\`\`json
|
|
2421
|
+
{
|
|
2422
|
+
"action": "command-name",
|
|
2423
|
+
"roles": {
|
|
2424
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
\`\`\`
|
|
2428
|
+
|
|
2429
|
+
Both formats are equally valid. Use whichever is more natural for the context.`;
|
|
2430
|
+
}
|
|
2431
|
+
return {
|
|
2432
|
+
id: "output-format",
|
|
2433
|
+
title: "Output Format",
|
|
2434
|
+
content,
|
|
2435
|
+
approximateTokens: estimateTokens(content)
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function buildErrorRecoverySection() {
|
|
2439
|
+
const content = `If you're unsure about a command or role:
|
|
2440
|
+
|
|
2441
|
+
1. **Unknown command**: Use the closest matching command from the Available Commands section. Do not invent commands.
|
|
2442
|
+
2. **Unknown role**: Use only the roles listed for each command. Do not add roles not in the schema.
|
|
2443
|
+
3. **Ambiguous value type**: When in doubt, use \`expression\` type (it's the most permissive).
|
|
2444
|
+
4. **Missing required role**: Always include all required roles. Check the Required/Optional labels.
|
|
2445
|
+
5. **Selector vs plain value**: If a value starts with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`, it's a selector. URLs like \`/api/data\` are plain values.
|
|
2446
|
+
|
|
2447
|
+
When generating LSE, prefer explicit role labeling over positional guessing. Every value should have a role name.`;
|
|
2448
|
+
return {
|
|
2449
|
+
id: "error-recovery",
|
|
2450
|
+
title: "Error Recovery",
|
|
2451
|
+
content,
|
|
2452
|
+
approximateTokens: estimateTokens(content)
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
function formatCommandDoc(schema, outputFormat, examplesPerCommand) {
|
|
2456
|
+
const lines = [];
|
|
2457
|
+
lines.push(`### \`${schema.action}\` \u2014 ${schema.description}`);
|
|
2458
|
+
lines.push("");
|
|
2459
|
+
const required = schema.roles.filter((r) => r.required);
|
|
2460
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
2461
|
+
if (required.length > 0) {
|
|
2462
|
+
lines.push("**Required roles:**");
|
|
2463
|
+
for (const role of required) {
|
|
2464
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
if (optional.length > 0) {
|
|
2468
|
+
lines.push("**Optional roles:**");
|
|
2469
|
+
for (const role of optional) {
|
|
2470
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
const examples = generateExamples(schema, examplesPerCommand);
|
|
2474
|
+
if (examples.length > 0) {
|
|
2475
|
+
lines.push("");
|
|
2476
|
+
if (outputFormat === "explicit" || outputFormat === "both") {
|
|
2477
|
+
lines.push("**Bracket syntax:**");
|
|
2478
|
+
for (const ex of examples) {
|
|
2479
|
+
lines.push(`\`\`\`
|
|
2480
|
+
${ex.explicit}
|
|
2481
|
+
\`\`\``);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
if (outputFormat === "json" || outputFormat === "both") {
|
|
2485
|
+
lines.push("**JSON format:**");
|
|
2486
|
+
for (const ex of examples) {
|
|
2487
|
+
lines.push(`\`\`\`json
|
|
2488
|
+
${JSON.stringify(ex.json, null, 2)}
|
|
2489
|
+
\`\`\``);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return lines.join("\n");
|
|
2494
|
+
}
|
|
2495
|
+
function formatExpectedTypes(role) {
|
|
2496
|
+
return role.expectedTypes.join(" | ");
|
|
2497
|
+
}
|
|
2498
|
+
function generateRoleCombinations(schema) {
|
|
2499
|
+
const required = schema.roles.filter((r) => r.required);
|
|
2500
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
2501
|
+
const combos = [];
|
|
2502
|
+
if (required.length > 0) {
|
|
2503
|
+
combos.push(required);
|
|
2504
|
+
}
|
|
2505
|
+
for (const opt of optional) {
|
|
2506
|
+
combos.push([...required, opt]);
|
|
2507
|
+
}
|
|
2508
|
+
if (optional.length >= 2) {
|
|
2509
|
+
combos.push([...required, ...optional]);
|
|
2510
|
+
}
|
|
2511
|
+
if (combos.length === 0) {
|
|
2512
|
+
combos.push(schema.roles.slice());
|
|
2513
|
+
}
|
|
2514
|
+
return combos;
|
|
2515
|
+
}
|
|
2516
|
+
function sampleValue(role) {
|
|
2517
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
2518
|
+
switch (primaryType) {
|
|
2519
|
+
case "selector":
|
|
2520
|
+
return sampleSelector(role.role);
|
|
2521
|
+
case "literal":
|
|
2522
|
+
return sampleLiteral(role.role);
|
|
2523
|
+
case "reference":
|
|
2524
|
+
return { type: "reference", value: "me" };
|
|
2525
|
+
case "expression":
|
|
2526
|
+
return sampleExpression(role.role);
|
|
2527
|
+
case "flag":
|
|
2528
|
+
return { type: "flag", value: true };
|
|
2529
|
+
default:
|
|
2530
|
+
return { type: "literal", value: "example" };
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
function sampleSelector(roleName) {
|
|
2534
|
+
const selectors = {
|
|
2535
|
+
patient: ".active",
|
|
2536
|
+
destination: "#output",
|
|
2537
|
+
source: "#input",
|
|
2538
|
+
target: "#target"
|
|
2539
|
+
};
|
|
2540
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
2541
|
+
}
|
|
2542
|
+
function sampleLiteral(roleName) {
|
|
2543
|
+
const literals = {
|
|
2544
|
+
duration: "30s",
|
|
2545
|
+
interval: "5s",
|
|
2546
|
+
delay: "500ms",
|
|
2547
|
+
timeout: "10s",
|
|
2548
|
+
quantity: 5,
|
|
2549
|
+
limit: 10,
|
|
2550
|
+
style: "json",
|
|
2551
|
+
manner: "rolling",
|
|
2552
|
+
format: "html"
|
|
2553
|
+
};
|
|
2554
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
2555
|
+
}
|
|
2556
|
+
function sampleExpression(roleName) {
|
|
2557
|
+
const expressions = {
|
|
2558
|
+
source: "/api/data",
|
|
2559
|
+
destination: "#output",
|
|
2560
|
+
patient: ".active",
|
|
2561
|
+
instrument: "toUpperCase",
|
|
2562
|
+
condition: "age > 18",
|
|
2563
|
+
style: "json",
|
|
2564
|
+
duration: "5s",
|
|
2565
|
+
url: "/api/users"
|
|
2566
|
+
};
|
|
2567
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
2568
|
+
}
|
|
2569
|
+
function toSemanticValue(sample) {
|
|
2570
|
+
switch (sample.type) {
|
|
2571
|
+
case "selector":
|
|
2572
|
+
return {
|
|
2573
|
+
type: "selector",
|
|
2574
|
+
value: String(sample.value),
|
|
2575
|
+
selectorKind: detectSelectorKind(String(sample.value))
|
|
2576
|
+
};
|
|
2577
|
+
case "literal":
|
|
2578
|
+
return {
|
|
2579
|
+
type: "literal",
|
|
2580
|
+
value: sample.value,
|
|
2581
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2582
|
+
};
|
|
2583
|
+
case "reference":
|
|
2584
|
+
return { type: "reference", value: String(sample.value) };
|
|
2585
|
+
case "expression":
|
|
2586
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
2587
|
+
case "flag":
|
|
2588
|
+
return { type: "flag", value: sample.value };
|
|
2589
|
+
default:
|
|
2590
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
function detectSelectorKind(selector) {
|
|
2594
|
+
if (selector.startsWith("#")) return "id";
|
|
2595
|
+
if (selector.startsWith(".")) return "class";
|
|
2596
|
+
if (selector.startsWith("[")) return "attribute";
|
|
2597
|
+
return "complex";
|
|
2598
|
+
}
|
|
2599
|
+
function estimateTokens(text) {
|
|
2600
|
+
return Math.ceil(text.length / 4);
|
|
2601
|
+
}
|
|
2602
|
+
function truncateToTokenBudget(sections, maxTokens) {
|
|
2603
|
+
const result = [];
|
|
2604
|
+
let budget = maxTokens;
|
|
2605
|
+
for (const section of sections) {
|
|
2606
|
+
if (section.approximateTokens <= budget) {
|
|
2607
|
+
result.push(section);
|
|
2608
|
+
budget -= section.approximateTokens;
|
|
2609
|
+
} else {
|
|
2610
|
+
const charBudget = Math.max(budget * 4, 40);
|
|
2611
|
+
const truncatedContent = section.content.slice(0, charBudget) + "\n\n*(truncated)*";
|
|
2612
|
+
result.push({
|
|
2613
|
+
...section,
|
|
2614
|
+
content: truncatedContent,
|
|
2615
|
+
approximateTokens: estimateTokens(truncatedContent)
|
|
2616
|
+
});
|
|
2617
|
+
break;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return result;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// src/training/schema-synthesizer.ts
|
|
2624
|
+
function synthesizeFromSchemas(schemas, config) {
|
|
2625
|
+
const maxPerCommand = config.maxPairsPerCommand ?? 5;
|
|
2626
|
+
const minQuality = config.minQuality ?? 0.5;
|
|
2627
|
+
const shuffleRoles = config.shuffleRoles ?? true;
|
|
2628
|
+
const languages = config.languages ?? ["en"];
|
|
2629
|
+
const pairs = [];
|
|
2630
|
+
let idCounter = 0;
|
|
2631
|
+
for (const schema of schemas) {
|
|
2632
|
+
const combos = generateRoleCombinations2(schema);
|
|
2633
|
+
for (const language of languages) {
|
|
2634
|
+
let commandPairCount = 0;
|
|
2635
|
+
for (const combo of combos) {
|
|
2636
|
+
if (commandPairCount >= maxPerCommand) break;
|
|
2637
|
+
const pair = buildTrainingPair(
|
|
2638
|
+
schema,
|
|
2639
|
+
combo,
|
|
2640
|
+
language,
|
|
2641
|
+
config.domain,
|
|
2642
|
+
`${config.domain}-synth-${idCounter++}`
|
|
2643
|
+
);
|
|
2644
|
+
if (pair.quality >= minQuality) {
|
|
2645
|
+
pairs.push(pair);
|
|
2646
|
+
commandPairCount++;
|
|
2647
|
+
}
|
|
2648
|
+
if (shuffleRoles && combo.length >= 2 && commandPairCount < maxPerCommand) {
|
|
2649
|
+
const shuffled = [...combo].reverse();
|
|
2650
|
+
const shuffledPair = buildTrainingPair(
|
|
2651
|
+
schema,
|
|
2652
|
+
shuffled,
|
|
2653
|
+
language,
|
|
2654
|
+
config.domain,
|
|
2655
|
+
`${config.domain}-synth-${idCounter++}`
|
|
2656
|
+
);
|
|
2657
|
+
if (shuffledPair.quality >= minQuality) {
|
|
2658
|
+
pairs.push(shuffledPair);
|
|
2659
|
+
commandPairCount++;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
const metadata = {
|
|
2666
|
+
domain: config.domain,
|
|
2667
|
+
commandCount: schemas.length,
|
|
2668
|
+
pairCount: pairs.length,
|
|
2669
|
+
bySource: { synthetic: pairs.length },
|
|
2670
|
+
languages
|
|
2671
|
+
};
|
|
2672
|
+
return { pairs, metadata };
|
|
2673
|
+
}
|
|
2674
|
+
function generateRoleCombinations2(schema) {
|
|
2675
|
+
const required = schema.roles.filter((r) => r.required);
|
|
2676
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
2677
|
+
const combos = [];
|
|
2678
|
+
if (required.length > 0) {
|
|
2679
|
+
combos.push(required);
|
|
2680
|
+
}
|
|
2681
|
+
for (const opt of optional) {
|
|
2682
|
+
combos.push([...required, opt]);
|
|
2683
|
+
}
|
|
2684
|
+
if (optional.length >= 2) {
|
|
2685
|
+
combos.push([...required, ...optional]);
|
|
2686
|
+
}
|
|
2687
|
+
if (combos.length === 0) {
|
|
2688
|
+
combos.push(schema.roles.slice());
|
|
2689
|
+
}
|
|
2690
|
+
return combos;
|
|
2691
|
+
}
|
|
2692
|
+
function buildTrainingPair(schema, roles, language, domain, id) {
|
|
2693
|
+
const sampledRoles = /* @__PURE__ */ new Map();
|
|
2694
|
+
for (const role of roles) {
|
|
2695
|
+
sampledRoles.set(role.role, sampleValue2(role));
|
|
2696
|
+
}
|
|
2697
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
2698
|
+
for (const [name, sample] of sampledRoles) {
|
|
2699
|
+
nodeRoles.set(name, toSemanticValue2(sample));
|
|
2700
|
+
}
|
|
2701
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
2702
|
+
const explicit = renderExplicit(node);
|
|
2703
|
+
const jsonRoles = {};
|
|
2704
|
+
for (const [name, sample] of sampledRoles) {
|
|
2705
|
+
jsonRoles[name] = sample;
|
|
2706
|
+
}
|
|
2707
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
2708
|
+
const natural = buildNaturalForm(schema, sampledRoles, language);
|
|
2709
|
+
const requiredCount = schema.roles.filter((r) => r.required).length;
|
|
2710
|
+
const filledRequired = roles.filter((r) => r.required).length;
|
|
2711
|
+
const quality = requiredCount > 0 ? filledRequired / requiredCount : 0.8;
|
|
2712
|
+
return {
|
|
2713
|
+
id,
|
|
2714
|
+
natural,
|
|
2715
|
+
language,
|
|
2716
|
+
explicit,
|
|
2717
|
+
json,
|
|
2718
|
+
domain,
|
|
2719
|
+
action: schema.action,
|
|
2720
|
+
source: "synthetic",
|
|
2721
|
+
quality
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
function buildNaturalForm(schema, roles, language) {
|
|
2725
|
+
const parts = [schema.action];
|
|
2726
|
+
for (const roleSpec of schema.roles) {
|
|
2727
|
+
const sample = roles.get(roleSpec.role);
|
|
2728
|
+
if (!sample) continue;
|
|
2729
|
+
const marker = roleSpec.markerOverride?.[language];
|
|
2730
|
+
if (marker) {
|
|
2731
|
+
parts.push(marker);
|
|
2732
|
+
}
|
|
2733
|
+
parts.push(String(sample.value));
|
|
2734
|
+
}
|
|
2735
|
+
return parts.join(" ");
|
|
2736
|
+
}
|
|
2737
|
+
function sampleValue2(role) {
|
|
2738
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
2739
|
+
switch (primaryType) {
|
|
2740
|
+
case "selector":
|
|
2741
|
+
return sampleSelector2(role.role);
|
|
2742
|
+
case "literal":
|
|
2743
|
+
return sampleLiteral2(role.role);
|
|
2744
|
+
case "reference":
|
|
2745
|
+
return { type: "reference", value: "me" };
|
|
2746
|
+
case "expression":
|
|
2747
|
+
return sampleExpression2(role.role);
|
|
2748
|
+
case "flag":
|
|
2749
|
+
return { type: "flag", value: true };
|
|
2750
|
+
default:
|
|
2751
|
+
return { type: "literal", value: "example" };
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
function sampleSelector2(roleName) {
|
|
2755
|
+
const selectors = {
|
|
2756
|
+
patient: ".active",
|
|
2757
|
+
destination: "#output",
|
|
2758
|
+
source: "#input",
|
|
2759
|
+
target: "#target"
|
|
2760
|
+
};
|
|
2761
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
2762
|
+
}
|
|
2763
|
+
function sampleLiteral2(roleName) {
|
|
2764
|
+
const literals = {
|
|
2765
|
+
duration: "30s",
|
|
2766
|
+
interval: "5s",
|
|
2767
|
+
delay: "500ms",
|
|
2768
|
+
timeout: "10s",
|
|
2769
|
+
quantity: 5,
|
|
2770
|
+
limit: 10,
|
|
2771
|
+
style: "json",
|
|
2772
|
+
manner: "rolling",
|
|
2773
|
+
format: "html"
|
|
2774
|
+
};
|
|
2775
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
2776
|
+
}
|
|
2777
|
+
function sampleExpression2(roleName) {
|
|
2778
|
+
const expressions = {
|
|
2779
|
+
source: "/api/data",
|
|
2780
|
+
destination: "#output",
|
|
2781
|
+
patient: ".active",
|
|
2782
|
+
instrument: "toUpperCase",
|
|
2783
|
+
condition: "age > 18",
|
|
2784
|
+
style: "json",
|
|
2785
|
+
duration: "5s",
|
|
2786
|
+
url: "/api/users"
|
|
2787
|
+
};
|
|
2788
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
2789
|
+
}
|
|
2790
|
+
function toSemanticValue2(sample) {
|
|
2791
|
+
switch (sample.type) {
|
|
2792
|
+
case "selector":
|
|
2793
|
+
return {
|
|
2794
|
+
type: "selector",
|
|
2795
|
+
value: String(sample.value),
|
|
2796
|
+
selectorKind: detectSelectorKind2(String(sample.value))
|
|
2797
|
+
};
|
|
2798
|
+
case "literal":
|
|
2799
|
+
return {
|
|
2800
|
+
type: "literal",
|
|
2801
|
+
value: sample.value,
|
|
2802
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2803
|
+
};
|
|
2804
|
+
case "reference":
|
|
2805
|
+
return { type: "reference", value: String(sample.value) };
|
|
2806
|
+
case "expression":
|
|
2807
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
2808
|
+
case "flag":
|
|
2809
|
+
return { type: "flag", value: sample.value };
|
|
2810
|
+
default:
|
|
2811
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
function detectSelectorKind2(selector) {
|
|
2815
|
+
if (selector.startsWith("#")) return "id";
|
|
2816
|
+
if (selector.startsWith(".")) return "class";
|
|
2817
|
+
if (selector.startsWith("[")) return "attribute";
|
|
2818
|
+
return "complex";
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// src/feedback/feedback-formatter.ts
|
|
2822
|
+
function buildFeedback(input, inputFormat, diagnostics, schemaLookup, action) {
|
|
2823
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
2824
|
+
const feedbackDiagnostics = diagnostics.map((d) => toFeedbackDiagnostic(d));
|
|
2825
|
+
const hints = generateHints(feedbackDiagnostics, schemaLookup, action);
|
|
2826
|
+
let schemaInfo;
|
|
2827
|
+
let correctedExample;
|
|
2828
|
+
if (action && schemaLookup) {
|
|
2829
|
+
const schema = schemaLookup.getSchema(action);
|
|
2830
|
+
if (schema) {
|
|
2831
|
+
schemaInfo = extractSchemaInfo(schema);
|
|
2832
|
+
if (hasErrors) {
|
|
2833
|
+
correctedExample = generateCorrectedExample(schema);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return {
|
|
2838
|
+
accepted: !hasErrors,
|
|
2839
|
+
input: { format: inputFormat, text: input },
|
|
2840
|
+
diagnostics: feedbackDiagnostics,
|
|
2841
|
+
hints,
|
|
2842
|
+
...schemaInfo && { schema: schemaInfo },
|
|
2843
|
+
...correctedExample && { correctedExample }
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function toFeedbackDiagnostic(d) {
|
|
2847
|
+
const fixType = classifyFixType(d);
|
|
2848
|
+
return {
|
|
2849
|
+
severity: d.severity,
|
|
2850
|
+
code: d.code || "unknown",
|
|
2851
|
+
message: d.message,
|
|
2852
|
+
...d.suggestions?.[0] && { suggestion: d.suggestions[0] },
|
|
2853
|
+
...fixType && { fixType }
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
function classifyFixType(d) {
|
|
2857
|
+
const code = (d.code || "").toLowerCase();
|
|
2858
|
+
const msg = d.message.toLowerCase();
|
|
2859
|
+
if (code.includes("missing-role") || code.includes("missing_role") || msg.includes("required role") || msg.includes("missing role")) {
|
|
2860
|
+
return "missing_role";
|
|
2861
|
+
}
|
|
2862
|
+
if (code.includes("invalid-type") || code.includes("invalid_type") || code.includes("type-mismatch") || msg.includes("invalid type") || msg.includes("expected type")) {
|
|
2863
|
+
return "invalid_type";
|
|
2864
|
+
}
|
|
2865
|
+
if (code.includes("unknown-command") || code.includes("unknown_command") || msg.includes("unknown command") || msg.includes("not recognized") && msg.includes("command")) {
|
|
2866
|
+
return "unknown_command";
|
|
2867
|
+
}
|
|
2868
|
+
if (code.includes("unknown-role") || code.includes("unknown_role") || msg.includes("unknown role") || msg.includes("not recognized") && msg.includes("role")) {
|
|
2869
|
+
return "unknown_role";
|
|
2870
|
+
}
|
|
2871
|
+
if (code.includes("parse") || code.includes("syntax") || msg.includes("syntax error") || msg.includes("parse error") || msg.includes("unexpected")) {
|
|
2872
|
+
return "syntax_error";
|
|
2873
|
+
}
|
|
2874
|
+
return void 0;
|
|
2875
|
+
}
|
|
2876
|
+
function generateHints(diagnostics, schemaLookup, action) {
|
|
2877
|
+
const hints = [];
|
|
2878
|
+
const schema = action && schemaLookup ? schemaLookup.getSchema(action) : void 0;
|
|
2879
|
+
for (const d of diagnostics) {
|
|
2880
|
+
if (d.severity !== "error") continue;
|
|
2881
|
+
switch (d.fixType) {
|
|
2882
|
+
case "missing_role": {
|
|
2883
|
+
if (schema) {
|
|
2884
|
+
const required = schema.roles.filter((r) => r.required).map((r) => `'${r.role}' (${r.description}, type: ${r.expectedTypes.join("|")})`).join(", ");
|
|
2885
|
+
hints.push(`Required roles for '${action}': ${required}`);
|
|
2886
|
+
} else {
|
|
2887
|
+
hints.push("Check that all required roles are present.");
|
|
2888
|
+
}
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
case "unknown_command": {
|
|
2892
|
+
hints.push(
|
|
2893
|
+
`Command not recognized. Ensure you're using a valid command from the domain schema.`
|
|
2894
|
+
);
|
|
2895
|
+
break;
|
|
2896
|
+
}
|
|
2897
|
+
case "unknown_role": {
|
|
2898
|
+
if (schema) {
|
|
2899
|
+
const validRoles = schema.roles.map((r) => `'${r.role}'`).join(", ");
|
|
2900
|
+
hints.push(`Valid roles for '${action}': ${validRoles}`);
|
|
2901
|
+
} else {
|
|
2902
|
+
hints.push("Use only roles defined in the command schema.");
|
|
2903
|
+
}
|
|
2904
|
+
break;
|
|
2905
|
+
}
|
|
2906
|
+
case "invalid_type": {
|
|
2907
|
+
hints.push(
|
|
2908
|
+
"Check value types: selectors start with #.[@*, strings use quotes, numbers are plain digits."
|
|
2909
|
+
);
|
|
2910
|
+
break;
|
|
2911
|
+
}
|
|
2912
|
+
case "syntax_error": {
|
|
2913
|
+
hints.push("Ensure bracket syntax: [command role:value ...]. No spaces around the colon.");
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
default: {
|
|
2917
|
+
if (d.suggestion) {
|
|
2918
|
+
hints.push(d.suggestion);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return [...new Set(hints)];
|
|
2924
|
+
}
|
|
2925
|
+
function extractSchemaInfo(schema) {
|
|
2926
|
+
const requiredRoles = [];
|
|
2927
|
+
const optionalRoles = [];
|
|
2928
|
+
const roleDescriptions = {};
|
|
2929
|
+
for (const role of schema.roles) {
|
|
2930
|
+
if (role.required) {
|
|
2931
|
+
requiredRoles.push(role.role);
|
|
2932
|
+
} else {
|
|
2933
|
+
optionalRoles.push(role.role);
|
|
2934
|
+
}
|
|
2935
|
+
roleDescriptions[role.role] = role.description;
|
|
2936
|
+
}
|
|
2937
|
+
return {
|
|
2938
|
+
action: schema.action,
|
|
2939
|
+
requiredRoles,
|
|
2940
|
+
optionalRoles,
|
|
2941
|
+
roleDescriptions
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
function generateCorrectedExample(schema) {
|
|
2945
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2946
|
+
const jsonRoles = {};
|
|
2947
|
+
for (const role of schema.roles) {
|
|
2948
|
+
if (!role.required) continue;
|
|
2949
|
+
const sample = sampleDefaultValue(role.role, role.expectedTypes[0] || "expression");
|
|
2950
|
+
roles.set(role.role, toSemanticValue3(sample));
|
|
2951
|
+
jsonRoles[role.role] = sample;
|
|
2952
|
+
}
|
|
2953
|
+
const node = createCommandNode(schema.action, roles);
|
|
2954
|
+
const explicit = renderExplicit(node);
|
|
2955
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
2956
|
+
return { explicit, json };
|
|
2957
|
+
}
|
|
2958
|
+
function sampleDefaultValue(roleName, expectedType) {
|
|
2959
|
+
switch (expectedType) {
|
|
2960
|
+
case "selector":
|
|
2961
|
+
return { type: "selector", value: `#${roleName}` };
|
|
2962
|
+
case "expression":
|
|
2963
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
2964
|
+
case "literal":
|
|
2965
|
+
return { type: "literal", value: `<${roleName}-value>` };
|
|
2966
|
+
case "reference":
|
|
2967
|
+
return { type: "reference", value: "me" };
|
|
2968
|
+
default:
|
|
2969
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
function toSemanticValue3(sample) {
|
|
2973
|
+
switch (sample.type) {
|
|
2974
|
+
case "selector":
|
|
2975
|
+
return { type: "selector", value: sample.value, selectorKind: "id" };
|
|
2976
|
+
case "expression":
|
|
2977
|
+
return { type: "expression", value: sample.value, raw: sample.value };
|
|
2978
|
+
case "literal":
|
|
2979
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
2980
|
+
case "reference":
|
|
2981
|
+
return { type: "reference", value: sample.value };
|
|
2982
|
+
default:
|
|
2983
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
|
|
1686
2987
|
// src/api/domain-registry.ts
|
|
1687
2988
|
var DomainRegistry = class {
|
|
1688
2989
|
constructor() {
|
|
@@ -1700,6 +3001,17 @@ var DomainRegistry = class {
|
|
|
1700
3001
|
}
|
|
1701
3002
|
this.descriptors.set(descriptor.name, descriptor);
|
|
1702
3003
|
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Attach schemas to an already-registered domain.
|
|
3006
|
+
* Useful for lazy loading schemas after initial registration.
|
|
3007
|
+
*/
|
|
3008
|
+
setSchemas(name, schemas) {
|
|
3009
|
+
const existing = this.descriptors.get(name);
|
|
3010
|
+
if (!existing) {
|
|
3011
|
+
throw new Error(`Domain not registered: ${name}`);
|
|
3012
|
+
}
|
|
3013
|
+
this.descriptors.set(name, { ...existing, schemas });
|
|
3014
|
+
}
|
|
1703
3015
|
/**
|
|
1704
3016
|
* Get all registered domain names.
|
|
1705
3017
|
*/
|
|
@@ -1769,6 +3081,71 @@ var DomainRegistry = class {
|
|
|
1769
3081
|
return this.getDSL(descriptor);
|
|
1770
3082
|
}
|
|
1771
3083
|
// ---------------------------------------------------------------------------
|
|
3084
|
+
// LLM ↔ LSE Convenience Methods
|
|
3085
|
+
// ---------------------------------------------------------------------------
|
|
3086
|
+
/**
|
|
3087
|
+
* Generate an LLM system prompt for a domain from its schemas.
|
|
3088
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
3089
|
+
*/
|
|
3090
|
+
generatePrompt(domain, config) {
|
|
3091
|
+
const descriptor = this.descriptors.get(domain);
|
|
3092
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
3093
|
+
return generatePrompt({
|
|
3094
|
+
domain,
|
|
3095
|
+
description: descriptor.description,
|
|
3096
|
+
schemas: descriptor.schemas,
|
|
3097
|
+
...config
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Synthesize training data for a domain from its schemas.
|
|
3102
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
3103
|
+
*/
|
|
3104
|
+
generateTrainingData(domain, config) {
|
|
3105
|
+
const descriptor = this.descriptors.get(domain);
|
|
3106
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
3107
|
+
return synthesizeFromSchemas(descriptor.schemas, {
|
|
3108
|
+
domain,
|
|
3109
|
+
...config
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Build structured feedback for a failed LSE input in a domain.
|
|
3114
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
3115
|
+
*/
|
|
3116
|
+
buildFeedback(domain, input, inputFormat, diagnostics) {
|
|
3117
|
+
const descriptor = this.descriptors.get(domain);
|
|
3118
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
3119
|
+
const schemaLookup = this.createSchemaLookup(descriptor.schemas);
|
|
3120
|
+
let action;
|
|
3121
|
+
try {
|
|
3122
|
+
if (inputFormat === "explicit") {
|
|
3123
|
+
const node = parseExplicit(input, { schemaLookup });
|
|
3124
|
+
action = node.action;
|
|
3125
|
+
}
|
|
3126
|
+
} catch {
|
|
3127
|
+
}
|
|
3128
|
+
return buildFeedback(input, inputFormat, diagnostics, schemaLookup, action);
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Get schemas for a domain (if available).
|
|
3132
|
+
*/
|
|
3133
|
+
getSchemas(domain) {
|
|
3134
|
+
const descriptor = this.descriptors.get(domain);
|
|
3135
|
+
if (!descriptor?.schemas) return null;
|
|
3136
|
+
return descriptor.schemas;
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Create a SchemaLookup from a domain's schemas.
|
|
3140
|
+
*/
|
|
3141
|
+
createSchemaLookup(schemas) {
|
|
3142
|
+
const map = /* @__PURE__ */ new Map();
|
|
3143
|
+
for (const s of schemas) {
|
|
3144
|
+
map.set(s.action, s);
|
|
3145
|
+
}
|
|
3146
|
+
return { getSchema: (action) => map.get(action) };
|
|
3147
|
+
}
|
|
3148
|
+
// ---------------------------------------------------------------------------
|
|
1772
3149
|
// Private: DSL/renderer lifecycle
|
|
1773
3150
|
// ---------------------------------------------------------------------------
|
|
1774
3151
|
async getDSL(descriptor) {
|
|
@@ -1868,7 +3245,7 @@ function generateToolDefinitions(desc) {
|
|
|
1868
3245
|
case "parse":
|
|
1869
3246
|
tools.push({
|
|
1870
3247
|
name: `parse_${desc.name}`,
|
|
1871
|
-
description: `Parse a ${desc.description} input into a semantic representation. Supports: ${langList}.`,
|
|
3248
|
+
description: `Parse a ${desc.description} input into a semantic representation (action, roles, confidence). Supports: ${langList}.`,
|
|
1872
3249
|
inputSchema: {
|
|
1873
3250
|
type: "object",
|
|
1874
3251
|
properties: {
|
|
@@ -1889,7 +3266,7 @@ function generateToolDefinitions(desc) {
|
|
|
1889
3266
|
case "compile":
|
|
1890
3267
|
tools.push({
|
|
1891
3268
|
name: `compile_${desc.name}`,
|
|
1892
|
-
description: `Compile a ${desc.description} input to target code. Supports: ${langList}.`,
|
|
3269
|
+
description: `Compile a ${desc.description} input to ${desc.outputDescription ?? "target code"}. Supports: ${langList}.`,
|
|
1893
3270
|
inputSchema: {
|
|
1894
3271
|
type: "object",
|
|
1895
3272
|
properties: {
|
|
@@ -1910,7 +3287,7 @@ function generateToolDefinitions(desc) {
|
|
|
1910
3287
|
case "validate":
|
|
1911
3288
|
tools.push({
|
|
1912
3289
|
name: `validate_${desc.name}`,
|
|
1913
|
-
description: `
|
|
3290
|
+
description: `Check ${desc.description} syntax for errors. Returns {valid, errors[]}. Supports: ${langList}.`,
|
|
1914
3291
|
inputSchema: {
|
|
1915
3292
|
type: "object",
|
|
1916
3293
|
properties: {
|
|
@@ -1931,7 +3308,7 @@ function generateToolDefinitions(desc) {
|
|
|
1931
3308
|
case "translate":
|
|
1932
3309
|
tools.push({
|
|
1933
3310
|
name: `translate_${desc.name}`,
|
|
1934
|
-
description: `Translate ${desc.description} input between natural languages
|
|
3311
|
+
description: `Translate ${desc.description} input between natural languages (requires 'from' and 'to' codes). Supports: ${langList}.`,
|
|
1935
3312
|
inputSchema: {
|
|
1936
3313
|
type: "object",
|
|
1937
3314
|
properties: {
|
|
@@ -1997,9 +3374,9 @@ var CrossDomainDispatcher = class {
|
|
|
1997
3374
|
if (domainNames.length === 0) return null;
|
|
1998
3375
|
const candidates = [];
|
|
1999
3376
|
for (const name of domainNames) {
|
|
2000
|
-
const dsl = await this.registry.getDSLForDomain(name);
|
|
2001
|
-
if (!dsl) continue;
|
|
2002
3377
|
try {
|
|
3378
|
+
const dsl = await this.registry.getDSLForDomain(name);
|
|
3379
|
+
if (!dsl) continue;
|
|
2003
3380
|
const { node, confidence } = dsl.parseWithConfidence(input, language);
|
|
2004
3381
|
if (confidence >= this.minConfidence) {
|
|
2005
3382
|
candidates.push({ domain: name, node, confidence, dsl });
|
|
@@ -2008,10 +3385,16 @@ var CrossDomainDispatcher = class {
|
|
|
2008
3385
|
}
|
|
2009
3386
|
}
|
|
2010
3387
|
if (candidates.length === 0) return null;
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
3388
|
+
if (this.priority.length > 0) {
|
|
3389
|
+
candidates.sort((a, b) => {
|
|
3390
|
+
const pa = this.getPriorityIndex(a.domain);
|
|
3391
|
+
const pb = this.getPriorityIndex(b.domain);
|
|
3392
|
+
if (pa !== pb) return pa - pb;
|
|
3393
|
+
return b.confidence - a.confidence;
|
|
3394
|
+
});
|
|
3395
|
+
} else {
|
|
3396
|
+
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
3397
|
+
}
|
|
2015
3398
|
return candidates[0];
|
|
2016
3399
|
}
|
|
2017
3400
|
/**
|
|
@@ -2055,6 +3438,40 @@ var CrossDomainDispatcher = class {
|
|
|
2055
3438
|
const result = detected.dsl.compile(input, language);
|
|
2056
3439
|
return { ...result, domain: detected.domain };
|
|
2057
3440
|
}
|
|
3441
|
+
/**
|
|
3442
|
+
* Compile multi-line input where each line may belong to a different domain.
|
|
3443
|
+
* Detects the domain for each line and compiles it, returning per-line results.
|
|
3444
|
+
* Empty lines and comment lines are skipped.
|
|
3445
|
+
*/
|
|
3446
|
+
async compileComposite(input, language = "en") {
|
|
3447
|
+
const lines = input.split("\n");
|
|
3448
|
+
const statements = [];
|
|
3449
|
+
const errors = [];
|
|
3450
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3451
|
+
const line = lines[i];
|
|
3452
|
+
if (isSkippableLine(line)) continue;
|
|
3453
|
+
const trimmed = line.trim();
|
|
3454
|
+
const detected = await this.detect(trimmed, language);
|
|
3455
|
+
if (detected) {
|
|
3456
|
+
const result = detected.dsl.compile(trimmed, language);
|
|
3457
|
+
statements.push({
|
|
3458
|
+
line: i + 1,
|
|
3459
|
+
input: trimmed,
|
|
3460
|
+
domain: detected.domain,
|
|
3461
|
+
ok: result.ok,
|
|
3462
|
+
code: result.code,
|
|
3463
|
+
errors: result.errors
|
|
3464
|
+
});
|
|
3465
|
+
} else {
|
|
3466
|
+
errors.push({
|
|
3467
|
+
line: i + 1,
|
|
3468
|
+
input: trimmed,
|
|
3469
|
+
message: "No domain matched this input"
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
return { statements, errors };
|
|
3474
|
+
}
|
|
2058
3475
|
/**
|
|
2059
3476
|
* Validate input against all domains, returning the best match.
|
|
2060
3477
|
*/
|