agentlang 0.10.1 → 0.10.2

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.
Files changed (95) hide show
  1. package/out/api/http.d.ts.map +1 -1
  2. package/out/api/http.js +136 -0
  3. package/out/api/http.js.map +1 -1
  4. package/out/language/generated/ast.d.ts +27 -2
  5. package/out/language/generated/ast.d.ts.map +1 -1
  6. package/out/language/generated/ast.js +20 -0
  7. package/out/language/generated/ast.js.map +1 -1
  8. package/out/language/generated/grammar.d.ts.map +1 -1
  9. package/out/language/generated/grammar.js +246 -206
  10. package/out/language/generated/grammar.js.map +1 -1
  11. package/out/language/main.cjs +263 -206
  12. package/out/language/main.cjs.map +2 -2
  13. package/out/language/parser.d.ts.map +1 -1
  14. package/out/language/parser.js +28 -2
  15. package/out/language/parser.js.map +1 -1
  16. package/out/language/syntax.d.ts +14 -0
  17. package/out/language/syntax.d.ts.map +1 -1
  18. package/out/language/syntax.js +60 -27
  19. package/out/language/syntax.js.map +1 -1
  20. package/out/runtime/api.d.ts +2 -0
  21. package/out/runtime/api.d.ts.map +1 -1
  22. package/out/runtime/api.js +3 -0
  23. package/out/runtime/api.js.map +1 -1
  24. package/out/runtime/datefns.d.ts +34 -0
  25. package/out/runtime/datefns.d.ts.map +1 -0
  26. package/out/runtime/datefns.js +82 -0
  27. package/out/runtime/datefns.js.map +1 -0
  28. package/out/runtime/exec-graph.d.ts.map +1 -1
  29. package/out/runtime/exec-graph.js +22 -3
  30. package/out/runtime/exec-graph.js.map +1 -1
  31. package/out/runtime/interpreter.d.ts +9 -0
  32. package/out/runtime/interpreter.d.ts.map +1 -1
  33. package/out/runtime/interpreter.js +71 -7
  34. package/out/runtime/interpreter.js.map +1 -1
  35. package/out/runtime/module.d.ts +8 -0
  36. package/out/runtime/module.d.ts.map +1 -1
  37. package/out/runtime/module.js +23 -0
  38. package/out/runtime/module.js.map +1 -1
  39. package/out/runtime/modules/ai.d.ts +7 -3
  40. package/out/runtime/modules/ai.d.ts.map +1 -1
  41. package/out/runtime/modules/ai.js +67 -21
  42. package/out/runtime/modules/ai.js.map +1 -1
  43. package/out/runtime/modules/core.d.ts.map +1 -1
  44. package/out/runtime/modules/core.js +5 -1
  45. package/out/runtime/modules/core.js.map +1 -1
  46. package/out/runtime/monitor.d.ts +6 -0
  47. package/out/runtime/monitor.d.ts.map +1 -1
  48. package/out/runtime/monitor.js +21 -1
  49. package/out/runtime/monitor.js.map +1 -1
  50. package/out/runtime/relgraph.d.ts.map +1 -1
  51. package/out/runtime/relgraph.js +7 -3
  52. package/out/runtime/relgraph.js.map +1 -1
  53. package/out/runtime/resolvers/interface.d.ts +3 -2
  54. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  55. package/out/runtime/resolvers/interface.js +3 -2
  56. package/out/runtime/resolvers/interface.js.map +1 -1
  57. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  58. package/out/runtime/resolvers/sqldb/dbutil.js +17 -4
  59. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  60. package/out/runtime/resolvers/sqldb/impl.d.ts +1 -1
  61. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  62. package/out/runtime/resolvers/sqldb/impl.js +17 -7
  63. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  64. package/out/runtime/util.d.ts +3 -2
  65. package/out/runtime/util.d.ts.map +1 -1
  66. package/out/runtime/util.js +13 -2
  67. package/out/runtime/util.js.map +1 -1
  68. package/out/syntaxes/agentlang.monarch.js +1 -1
  69. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  70. package/out/test-harness.d.ts +36 -0
  71. package/out/test-harness.d.ts.map +1 -0
  72. package/out/test-harness.js +341 -0
  73. package/out/test-harness.js.map +1 -0
  74. package/package.json +4 -1
  75. package/src/api/http.ts +138 -0
  76. package/src/language/agentlang.langium +3 -1
  77. package/src/language/generated/ast.ts +32 -1
  78. package/src/language/generated/grammar.ts +246 -206
  79. package/src/language/parser.ts +33 -1
  80. package/src/language/syntax.ts +71 -24
  81. package/src/runtime/api.ts +5 -0
  82. package/src/runtime/datefns.ts +112 -0
  83. package/src/runtime/exec-graph.ts +23 -2
  84. package/src/runtime/interpreter.ts +82 -6
  85. package/src/runtime/module.ts +26 -0
  86. package/src/runtime/modules/ai.ts +78 -31
  87. package/src/runtime/modules/core.ts +5 -1
  88. package/src/runtime/monitor.ts +27 -1
  89. package/src/runtime/relgraph.ts +7 -3
  90. package/src/runtime/resolvers/interface.ts +4 -2
  91. package/src/runtime/resolvers/sqldb/dbutil.ts +20 -6
  92. package/src/runtime/resolvers/sqldb/impl.ts +17 -7
  93. package/src/runtime/util.ts +19 -2
  94. package/src/syntaxes/agentlang.monarch.ts +1 -1
  95. package/src/test-harness.ts +423 -0
@@ -70,7 +70,6 @@ import {
70
70
  } from '../agents/common.js';
71
71
  import { logger } from '../logger.js';
72
72
  import { FlowStep } from '../agents/flows.js';
73
- import Handlebars from 'handlebars';
74
73
  import { Statement } from '../../language/generated/ast.js';
75
74
  import { isMonitoringEnabled, TtlCache } from '../state.js';
76
75
  import { isNodeEnv } from '../../utils/runtime.js';
@@ -107,6 +106,7 @@ entity ${AgentEntityName} {
107
106
  flows String @optional,
108
107
  validate String @optional,
109
108
  retry String @optional,
109
+ saveResponseAs String @optional,
110
110
  llm String
111
111
  }
112
112
 
@@ -323,6 +323,7 @@ export class AgentInstance {
323
323
  flows: string | undefined;
324
324
  validate: string | undefined;
325
325
  retry: string | undefined;
326
+ saveResponseAs: string | undefined;
326
327
  stateless: boolean = false;
327
328
  private toolsArray: string[] | undefined = undefined;
328
329
  private hasModuleTools = false;
@@ -645,7 +646,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
645
646
  const spad = env.getScratchPad();
646
647
  if (spad !== undefined && Object.keys(spad).length > 0) {
647
648
  if (finalInstruction.indexOf('{{') > 0) {
648
- return AgentInstance.maybeRewriteTemplatePatterns(spad, finalInstruction, env);
649
+ return env.maybeRewriteTemplatePatterns(finalInstruction, spad);
649
650
  } else {
650
651
  const ctx = JSON.stringify(spad);
651
652
  return `${finalInstruction}\nSome additional context:\n${ctx}`;
@@ -657,6 +658,7 @@ Only return a pure JSON object with no extra text, annotations etc.`;
657
658
  }
658
659
 
659
660
  private static UserTag = 'user';
661
+ private static FileTag = 'file';
660
662
 
661
663
  private async getFullInstructions(
662
664
  env: Environment,
@@ -670,13 +672,21 @@ Only return a pure JSON object with no extra text, annotations etc.`;
670
672
  }
671
673
  }
672
674
 
673
- private static maybeRewriteTemplatePatterns(
674
- scratchPad: any,
675
- instruction: string,
676
- env: Environment
677
- ): string {
678
- const templ = Handlebars.compile(env.rewriteTemplateMappings(instruction));
679
- return templ(scratchPad);
675
+ private async maybeFillInFileContents(instructions: string, env: Environment): Promise<string> {
676
+ const chatId = env.getActiveChatId();
677
+ const FC = '$FC';
678
+ if (chatId && instructions.indexOf(`<${AgentInstance.FileTag}>`) > 0) {
679
+ const ext = extractAndRemoveAllXmlTaggedText(instructions, AgentInstance.FileTag, FC);
680
+ if (ext.extracted) {
681
+ instructions = ext.updatedText;
682
+ for (let i = 0; i < ext.extracted.length; ++i) {
683
+ const fileName = ext.extracted[i];
684
+ const contents = await loadLocalAgentResult(chatId, fileName);
685
+ if (contents) instructions = instructions.replace(FC, contents);
686
+ }
687
+ }
688
+ }
689
+ return instructions;
680
690
  }
681
691
 
682
692
  maybeValidateJsonResponse(response: string | undefined): object | undefined {
@@ -875,7 +885,8 @@ Only return a pure JSON object with no extra text, annotations etc.`;
875
885
  }
876
886
  let tmpMsg = message;
877
887
  if (extractedText?.extracted) {
878
- tmpMsg = `${tmpMsg}\n${extractedText.extracted.join('\n')}`;
888
+ const s = await this.maybeFillInFileContents(extractedText.extracted.join('\n'), env);
889
+ tmpMsg = `${tmpMsg}\n${s}`;
879
890
  }
880
891
  const hmsg = await this.maybeAddRelevantDocuments(
881
892
  this.maybeAddFlowContext(tmpMsg, env),
@@ -927,6 +938,13 @@ Only return a pure JSON object with no extra text, annotations etc.`;
927
938
  await saveAgentChatSession(chatId, msgs, env);
928
939
  }
929
940
  if (monitoringEnabled) env.setMonitorEntryLlmResponse(response.content);
941
+ if (monitoringEnabled && response.sysMsg.usage_metadata) {
942
+ const u = response.sysMsg.usage_metadata;
943
+ env.setMonitorEntryLlmTokenUsage(u.input_tokens, u.output_tokens, u.total_tokens);
944
+ }
945
+ if (this.saveResponseAs) {
946
+ await saveAgentResponse(this.saveResponseAs, response.content, env);
947
+ }
930
948
  env.setLastResult(response.content);
931
949
  } catch (err: any) {
932
950
  logger.error(`Error while invoking ${agentName} - ${err}`);
@@ -1448,15 +1466,9 @@ export async function saveFlowStepResultLocally(
1448
1466
  chatId: string,
1449
1467
  step: string,
1450
1468
  result: string,
1451
- suspensionId: string
1452
- ): Promise<Instance | undefined> {
1469
+ suspensionId?: string | undefined
1470
+ ): Promise<string> {
1453
1471
  const fs = await getFileSystem();
1454
- const attrs = newInstanceAttributes()
1455
- .set('chatId', chatId)
1456
- .set('step', step)
1457
- .set('result', result)
1458
- .set('suspensionId', suspensionId);
1459
- const inst = makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1460
1472
  const rootDirName = LocalFlowStepsRootDirName;
1461
1473
  if (!(await fs.exists(rootDirName))) {
1462
1474
  await fs.mkdir(rootDirName);
@@ -1469,8 +1481,18 @@ export async function saveFlowStepResultLocally(
1469
1481
  if (await fs.exists(fileName)) {
1470
1482
  await fs.unlink(fileName);
1471
1483
  }
1472
- await fs.writeFile(fileName, JSON.stringify(inst.attributesAsObject(true)));
1473
- return inst;
1484
+ let s = result;
1485
+ if (suspensionId) {
1486
+ const attrs = newInstanceAttributes()
1487
+ .set('chatId', chatId)
1488
+ .set('step', step)
1489
+ .set('result', result)
1490
+ .set('suspensionId', suspensionId);
1491
+ const inst = makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1492
+ s = JSON.stringify(inst.attributesAsObject(true));
1493
+ }
1494
+ await fs.writeFile(fileName, s);
1495
+ return s;
1474
1496
  }
1475
1497
 
1476
1498
  export async function saveFlowStepResult(
@@ -1479,9 +1501,10 @@ export async function saveFlowStepResult(
1479
1501
  result: string,
1480
1502
  suspensionId: string,
1481
1503
  env: Environment
1482
- ): Promise<Instance | undefined> {
1504
+ ): Promise<boolean> {
1483
1505
  if (LocalAgentgFlow) {
1484
- return await saveFlowStepResultLocally(chatId, step, result, suspensionId);
1506
+ await saveFlowStepResultLocally(chatId, step, result, suspensionId);
1507
+ return true;
1485
1508
  } else {
1486
1509
  const t = `${CoreAIModuleName}/${AgentFlowStep}`;
1487
1510
  try {
@@ -1495,11 +1518,11 @@ export async function saveFlowStepResult(
1495
1518
  undefined,
1496
1519
  env
1497
1520
  );
1498
- if (isInstanceOfType(inst, t)) return inst;
1499
- else return undefined;
1521
+ if (isInstanceOfType(inst, t)) return true;
1522
+ else return false;
1500
1523
  } catch (reason: any) {
1501
1524
  logger.error(`failed to save flow result for step ${step} - ${reason}`);
1502
- return undefined;
1525
+ return false;
1503
1526
  }
1504
1527
  }
1505
1528
  }
@@ -1537,10 +1560,10 @@ export async function loadFlowStepResults(chatId: string): Promise<Instance[]> {
1537
1560
  }
1538
1561
  }
1539
1562
 
1540
- export async function loadLocalFlowStep(
1563
+ export async function loadLocalAgentResult(
1541
1564
  chatId: string,
1542
1565
  step: string
1543
- ): Promise<Instance | undefined> {
1566
+ ): Promise<string | undefined> {
1544
1567
  const fs = await getFileSystem();
1545
1568
  const dirName = `${LocalFlowStepsRootDirName}/${chatId}`;
1546
1569
  if (await fs.exists(dirName)) {
@@ -1548,10 +1571,7 @@ export async function loadLocalFlowStep(
1548
1571
  for (let i = 0; i < fileNames.length; ++i) {
1549
1572
  const fileName = fileNames[i];
1550
1573
  if (fileName === step) {
1551
- const attrs = objectToInstanceAttributes(
1552
- JSON.parse(await fs.readFile(`${dirName}/${fileName}`))
1553
- );
1554
- return makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1574
+ return await fs.readFile(`${dirName}/${fileName}`);
1555
1575
  }
1556
1576
  }
1557
1577
  return undefined;
@@ -1560,6 +1580,19 @@ export async function loadLocalFlowStep(
1560
1580
  }
1561
1581
  }
1562
1582
 
1583
+ export async function loadLocalFlowStep(
1584
+ chatId: string,
1585
+ step: string
1586
+ ): Promise<Instance | undefined> {
1587
+ const contents = await loadLocalAgentResult(chatId, step);
1588
+ if (contents) {
1589
+ const attrs = objectToInstanceAttributes(JSON.parse(contents));
1590
+ return makeInstance(CoreAIModuleName, AgentFlowStep, attrs);
1591
+ } else {
1592
+ return undefined;
1593
+ }
1594
+ }
1595
+
1563
1596
  export async function loadFlowStep(chatId: string, step: string): Promise<Instance | undefined> {
1564
1597
  if (LocalAgentgFlow) {
1565
1598
  return await loadLocalFlowStep(chatId, step);
@@ -1639,3 +1672,17 @@ export async function fetchAndCreateDocument(
1639
1672
  throw new Error(`Failed to fetch document: ${title} from ${url}`);
1640
1673
  }
1641
1674
  }
1675
+
1676
+ export async function saveAgentResponse(
1677
+ fileName: string,
1678
+ response: string,
1679
+ env: Environment
1680
+ ): Promise<string> {
1681
+ const chatId = env.getActiveChatId();
1682
+ if (chatId) {
1683
+ await saveFlowStepResultLocally(chatId, fileName, response);
1684
+ } else {
1685
+ logger.warn(`No chatId set, ${fileName} was not saved.`);
1686
+ }
1687
+ return response;
1688
+ }
@@ -749,5 +749,9 @@ export function migrationDowns(inst: Instance): string[] | undefined {
749
749
 
750
750
  export async function doRawQuery(q: any): Promise<any> {
751
751
  const qs = objectToQueryPattern(q);
752
- return await parseAndEvaluateStatement(qs);
752
+ const result = await parseAndEvaluateStatement(qs);
753
+ return {
754
+ query: qs,
755
+ result: result.map((res: any) => (res.attributes ? Object.fromEntries(res.attributes) : res)),
756
+ };
753
757
  }
@@ -10,6 +10,9 @@ export class MonitorEntry {
10
10
  private llm: boolean = false;
11
11
  private llmPrompt: string | undefined;
12
12
  private llmResponse: string | undefined;
13
+ private llmInputTokens: number | undefined;
14
+ private llmOutputTokens: number | undefined;
15
+ private llmTotalTokens: number | undefined;
13
16
  private planner: boolean = false;
14
17
  private flowStep: boolean = false;
15
18
  private flow: boolean = false;
@@ -25,6 +28,10 @@ export class MonitorEntry {
25
28
  return this.input;
26
29
  }
27
30
 
31
+ getTimestamp(): number {
32
+ return this.timestamp;
33
+ }
34
+
28
35
  setResult(result: any): MonitorEntry {
29
36
  if (this.result === undefined) {
30
37
  this.result = result;
@@ -68,6 +75,13 @@ export class MonitorEntry {
68
75
  return this;
69
76
  }
70
77
 
78
+ setLlmTokenUsage(input: number, output: number, total: number): MonitorEntry {
79
+ this.llmInputTokens = input;
80
+ this.llmOutputTokens = output;
81
+ this.llmTotalTokens = total;
82
+ return this;
83
+ }
84
+
71
85
  flagAsPlanner(): MonitorEntry {
72
86
  this.llm = true;
73
87
  if (this.flowStep || this.flow || this.decision) {
@@ -137,6 +151,11 @@ export class MonitorEntry {
137
151
  llmObj.isFlowStep = this.flowStep;
138
152
  llmObj.isDecision = this.decision;
139
153
  llmObj.isFlow = this.flow;
154
+ if (this.llmInputTokens !== undefined) {
155
+ llmObj.inputTokens = this.llmInputTokens;
156
+ llmObj.outputTokens = this.llmOutputTokens;
157
+ llmObj.totalTokens = this.llmTotalTokens;
158
+ }
140
159
  obj.llm = llmObj;
141
160
  }
142
161
  obj.label = this.input;
@@ -269,9 +288,16 @@ export class Monitor {
269
288
  return this;
270
289
  }
271
290
 
291
+ setEntryLlmTokenUsage(input: number, output: number, total: number): Monitor {
292
+ if (this.lastEntry !== undefined) {
293
+ this.lastEntry.setLlmTokenUsage(input, output, total);
294
+ }
295
+ return this;
296
+ }
297
+
272
298
  private finalizeLastEntry(): void {
273
299
  if (this.lastEntry) {
274
- const ms = Date.now() - this.lastEntrySetAtMs;
300
+ const ms = Date.now() - this.lastEntry.getTimestamp();
275
301
  this.lastEntry.setLatencyMs(ms);
276
302
  if (MonitoringCallback !== undefined) {
277
303
  MonitoringCallback(this.lastEntry);
@@ -51,8 +51,11 @@ export class RelationshipGraph {
51
51
  node: RelationshipGraphNode,
52
52
  onNode: Function,
53
53
  onContainsRelationship: Function,
54
- onBetweenRelationship: Function
54
+ onBetweenRelationship: Function,
55
+ visited: Set<RelationshipGraphNode>
55
56
  ) {
57
+ if (visited.has(node)) return;
58
+ visited.add(node);
56
59
  const n = node.entity.asFqName();
57
60
  onNode(n, node.edges);
58
61
  node.edges.forEach((edge: RelationshipGraphEdge) => {
@@ -60,13 +63,14 @@ export class RelationshipGraph {
60
63
  ? onContainsRelationship
61
64
  : onBetweenRelationship;
62
65
  rf(n, edge.node.entity.asFqName(), edge.relationship);
63
- this.walkEdges(edge.node, onNode, onContainsRelationship, onBetweenRelationship);
66
+ this.walkEdges(edge.node, onNode, onContainsRelationship, onBetweenRelationship, visited);
64
67
  });
65
68
  }
66
69
 
67
70
  walk(onNode: Function, onContainsRelationship: Function, onBetweenRelationship: Function) {
71
+ const visited = new Set<RelationshipGraphNode>();
68
72
  this.nodes.forEach((node: RelationshipGraphNode) => {
69
- this.walkEdges(node, onNode, onContainsRelationship, onBetweenRelationship);
73
+ this.walkEdges(node, onNode, onContainsRelationship, onBetweenRelationship, visited);
70
74
  });
71
75
  }
72
76
  }
@@ -121,13 +121,15 @@ export class Resolver {
121
121
  /**
122
122
  * Return all instances connected to connectedInstance via the given between-relationship
123
123
  * @param relationship Between relationship
124
- * @param connectedInstance The instance to traveres the relationship from
124
+ * @param connectedInstance The instance to traverse the relationship from
125
125
  * @param inst Target instance with query attributes
126
+ * @param connectedAlias For self-referencing relationships, the alias of the connected instance's role
126
127
  */
127
128
  public async queryConnectedInstances(
128
129
  relationship: Relationship,
129
130
  connectedInstance: Instance,
130
- inst: Instance
131
+ inst: Instance,
132
+ connectedAlias?: string
131
133
  ): Promise<any> {
132
134
  return this.notImpl(`queryConnectedInstances(${relationship}, ${connectedInstance}, ${inst})`);
133
135
  }
@@ -188,13 +188,27 @@ function ormSchemaFromRecordSchema(
188
188
  const fqName = makeFqName(moduleName, entityName);
189
189
  getAllOneToOneRelationshipsForEntity(moduleName, entityName, allBetRels).forEach(
190
190
  (re: Relationship) => {
191
- const colName = re.getInverseAliasForName(fqName);
192
- if (cols.has(colName)) {
193
- throw new Error(
194
- `Cannot establish relationship ${re.name}, ${entityName}.${colName} already exists`
195
- );
191
+ if (re.isSelfReferencing()) {
192
+ // Self-referencing one-to-one: add columns for both aliases
193
+ // so each side of the relationship can be stored on the entity.
194
+ // Both must be nullable since they're set after entity creation.
195
+ [re.node1.alias, re.node2.alias].forEach((colName: string) => {
196
+ if (cols.has(colName)) {
197
+ throw new Error(
198
+ `Cannot establish relationship ${re.name}, ${entityName}.${colName} already exists`
199
+ );
200
+ }
201
+ cols.set(colName, { type: 'varchar', unique: true });
202
+ });
203
+ } else {
204
+ const colName = re.getInverseAliasForName(fqName);
205
+ if (cols.has(colName)) {
206
+ throw new Error(
207
+ `Cannot establish relationship ${re.name}, ${entityName}.${colName} already exists`
208
+ );
209
+ }
210
+ cols.set(colName, { type: 'varchar', unique: true });
196
211
  }
197
- cols.set(colName, { type: 'varchar', unique: true });
198
212
  }
199
213
  );
200
214
  if (relsSpec.size > 0) {
@@ -480,16 +480,17 @@ export class SqlDbResolver extends Resolver {
480
480
  public override async queryConnectedInstances(
481
481
  relationship: Relationship,
482
482
  connectedInstance: Instance,
483
- inst: Instance
483
+ inst: Instance,
484
+ connectedAlias?: string
484
485
  ): Promise<Instance[]> {
485
486
  let result = SqlDbResolver.EmptyResultSet;
486
487
  if (relationship.isOneToOne()) {
487
- const col = relationship.getAliasFor(connectedInstance);
488
+ const col = relationship.getAliasForConnected(connectedInstance, connectedAlias);
488
489
  inst.addQuery(col, '=', connectedInstance.lookup(PathAttributeName));
489
490
  return await this.queryInstances(inst, false);
490
491
  } else {
491
- const from = relationship.getAliasFor(connectedInstance);
492
- const to = relationship.getInverseAliasFor(connectedInstance);
492
+ const from = relationship.getAliasForConnected(connectedInstance, connectedAlias);
493
+ const to = relationship.getInverseAliasForConnected(connectedInstance, connectedAlias);
493
494
  await getAllConnected(
494
495
  asTableReference(inst.moduleName, inst.name),
495
496
  inst.queryAttributesAsObject(),
@@ -797,9 +798,18 @@ function ensureOneToOneAttributes(inst: Instance) {
797
798
  const betRels = getAllBetweenRelationships();
798
799
  getAllOneToOneRelationshipsForEntity(inst.moduleName, inst.name, betRels).forEach(
799
800
  (re: Relationship) => {
800
- const n = re.getInverseAliasFor(inst);
801
- if (!inst.attributes.has(n)) {
802
- inst.attributes.set(n, crypto.randomUUID());
801
+ if (re.isSelfReferencing()) {
802
+ // Self-referencing one-to-one: ensure both alias columns have placeholder values
803
+ [re.node1.alias, re.node2.alias].forEach((n: string) => {
804
+ if (!inst.attributes.has(n)) {
805
+ inst.attributes.set(n, crypto.randomUUID());
806
+ }
807
+ });
808
+ } else {
809
+ const n = re.getInverseAliasFor(inst);
810
+ if (!inst.attributes.has(n)) {
811
+ inst.attributes.set(n, crypto.randomUUID());
812
+ }
803
813
  }
804
814
  }
805
815
  );
@@ -2,6 +2,7 @@ import { isNodeEnv, path } from '../utils/runtime.js';
2
2
  import {
3
3
  AliasSpec,
4
4
  CatchSpec,
5
+ EmptySpec,
5
6
  ExtendsClause,
6
7
  isLiteral,
7
8
  MapEntry,
@@ -441,6 +442,18 @@ export function firstCatchSpec(stmt: Statement): CatchSpec | undefined {
441
442
  return undefined;
442
443
  }
443
444
 
445
+ export function firstEmptySpec(stmt: Statement): EmptySpec | undefined {
446
+ if (stmt.hints) {
447
+ for (let i = 0; i < stmt.hints.length; ++i) {
448
+ const rh = stmt.hints[i];
449
+ if (rh.emptySpec) {
450
+ return rh.emptySpec;
451
+ }
452
+ }
453
+ }
454
+ return undefined;
455
+ }
456
+
444
457
  function maybeExtractEntryName(n: string): string {
445
458
  const i = n.indexOf('$');
446
459
  if (i > 0) {
@@ -671,7 +684,11 @@ export type ExtractedText = {
671
684
  };
672
685
 
673
686
  // extract all data between a given xml tag from within an arbitray text.
674
- export function extractAndRemoveAllXmlTaggedText(text: string, tagName: string): ExtractedText {
687
+ export function extractAndRemoveAllXmlTaggedText(
688
+ text: string,
689
+ tagName: string,
690
+ replaceWith: string = ''
691
+ ): ExtractedText {
675
692
  const pattern = `<${tagName}\\b[^>]*>([\\s\\S]*?)</${tagName}>`;
676
693
  const regex = new RegExp(pattern, 'gi');
677
694
 
@@ -683,7 +700,7 @@ export function extractAndRemoveAllXmlTaggedText(text: string, tagName: string):
683
700
  extracted.push(match[1]);
684
701
  }
685
702
 
686
- updatedText = text.replace(regex, '');
703
+ updatedText = text.replace(regex, replaceWith);
687
704
 
688
705
  return { extracted, updatedText };
689
706
  }
@@ -1,7 +1,7 @@
1
1
  // Monarch syntax highlighting for the agentlang language.
2
2
  export default {
3
3
  keywords: [
4
- '@actions','@after','@as','@asc','@async','@before','@catch','@desc','@distinct','@enum','@expr','@from','@full_join','@groupBy','@inner_join','@into','@join','@left_join','@meta','@oneof','@orderBy','@public','@rbac','@ref','@right_join','@then','@upsert','@where','@withRole','@with_unique','agent','agentlang/retry','allow','and','attempts','await','backoff','between','case','commitTransaction','contains','create','decision','delete','directive','else','entity','error','eval','event','extends','false','flow','for','glossaryEntry','if','import','in','like','module','not','not_found','onSubscription','or','purge','query','read','record','relationship','resolver','return','roles','rollbackTransaction','scenario','startTransaction','subscribe','throw','true','update','upsert','where','workflow'
4
+ '@actions','@after','@as','@asc','@async','@before','@catch','@desc','@distinct','@empty','@enum','@expr','@from','@full_join','@groupBy','@inner_join','@into','@join','@left_join','@meta','@oneof','@orderBy','@public','@rbac','@ref','@right_join','@then','@upsert','@where','@withRole','@with_unique','agent','agentlang/retry','allow','and','attempts','await','backoff','between','case','commitTransaction','contains','create','decision','delete','directive','else','entity','error','eval','event','extends','false','flow','for','glossaryEntry','if','import','in','like','module','not','not_found','onSubscription','or','purge','query','read','record','relationship','resolver','return','roles','rollbackTransaction','scenario','startTransaction','subscribe','throw','true','update','upsert','where','workflow'
5
5
  ],
6
6
  operators: [
7
7
  '!=','*','+',',','-','-->','.','/',':',';','<','<=','<>','=','==','>','>=','?','@'