agentlang 0.10.1 → 0.10.3

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 (178) hide show
  1. package/README.md +7 -14
  2. package/out/api/http.d.ts +4 -0
  3. package/out/api/http.d.ts.map +1 -1
  4. package/out/api/http.js +307 -26
  5. package/out/api/http.js.map +1 -1
  6. package/out/cli/main.d.ts.map +1 -1
  7. package/out/cli/main.js +3 -0
  8. package/out/cli/main.js.map +1 -1
  9. package/out/extension/main.cjs +250 -250
  10. package/out/extension/main.cjs.map +2 -2
  11. package/out/language/agentlang-validator.d.ts.map +1 -1
  12. package/out/language/agentlang-validator.js +4 -0
  13. package/out/language/agentlang-validator.js.map +1 -1
  14. package/out/language/error-reporter.d.ts +53 -0
  15. package/out/language/error-reporter.d.ts.map +1 -0
  16. package/out/language/error-reporter.js +879 -0
  17. package/out/language/error-reporter.js.map +1 -0
  18. package/out/language/generated/ast.d.ts +77 -2
  19. package/out/language/generated/ast.d.ts.map +1 -1
  20. package/out/language/generated/ast.js +60 -0
  21. package/out/language/generated/ast.js.map +1 -1
  22. package/out/language/generated/grammar.d.ts.map +1 -1
  23. package/out/language/generated/grammar.js +342 -206
  24. package/out/language/generated/grammar.js.map +1 -1
  25. package/out/language/main.cjs +901 -710
  26. package/out/language/main.cjs.map +3 -3
  27. package/out/language/parser.d.ts +4 -2
  28. package/out/language/parser.d.ts.map +1 -1
  29. package/out/language/parser.js +58 -99
  30. package/out/language/parser.js.map +1 -1
  31. package/out/language/syntax.d.ts +16 -0
  32. package/out/language/syntax.d.ts.map +1 -1
  33. package/out/language/syntax.js +66 -27
  34. package/out/language/syntax.js.map +1 -1
  35. package/out/runtime/api.d.ts +2 -0
  36. package/out/runtime/api.d.ts.map +1 -1
  37. package/out/runtime/api.js +25 -0
  38. package/out/runtime/api.js.map +1 -1
  39. package/out/runtime/datefns.d.ts +34 -0
  40. package/out/runtime/datefns.d.ts.map +1 -0
  41. package/out/runtime/datefns.js +82 -0
  42. package/out/runtime/datefns.js.map +1 -0
  43. package/out/runtime/defs.d.ts +1 -0
  44. package/out/runtime/defs.d.ts.map +1 -1
  45. package/out/runtime/defs.js +2 -1
  46. package/out/runtime/defs.js.map +1 -1
  47. package/out/runtime/document-retriever.d.ts +24 -0
  48. package/out/runtime/document-retriever.d.ts.map +1 -0
  49. package/out/runtime/document-retriever.js +258 -0
  50. package/out/runtime/document-retriever.js.map +1 -0
  51. package/out/runtime/embeddings/chunker.d.ts +18 -0
  52. package/out/runtime/embeddings/chunker.d.ts.map +1 -1
  53. package/out/runtime/embeddings/chunker.js +47 -15
  54. package/out/runtime/embeddings/chunker.js.map +1 -1
  55. package/out/runtime/embeddings/openai.d.ts.map +1 -1
  56. package/out/runtime/embeddings/openai.js +22 -9
  57. package/out/runtime/embeddings/openai.js.map +1 -1
  58. package/out/runtime/embeddings/provider.d.ts +1 -0
  59. package/out/runtime/embeddings/provider.d.ts.map +1 -1
  60. package/out/runtime/embeddings/provider.js +20 -1
  61. package/out/runtime/embeddings/provider.js.map +1 -1
  62. package/out/runtime/exec-graph.d.ts.map +1 -1
  63. package/out/runtime/exec-graph.js +22 -3
  64. package/out/runtime/exec-graph.js.map +1 -1
  65. package/out/runtime/integration-client.d.ts +21 -0
  66. package/out/runtime/integration-client.d.ts.map +1 -0
  67. package/out/runtime/integration-client.js +112 -0
  68. package/out/runtime/integration-client.js.map +1 -0
  69. package/out/runtime/integrations.d.ts.map +1 -1
  70. package/out/runtime/integrations.js +20 -9
  71. package/out/runtime/integrations.js.map +1 -1
  72. package/out/runtime/interpreter.d.ts +10 -0
  73. package/out/runtime/interpreter.d.ts.map +1 -1
  74. package/out/runtime/interpreter.js +221 -22
  75. package/out/runtime/interpreter.js.map +1 -1
  76. package/out/runtime/loader.d.ts.map +1 -1
  77. package/out/runtime/loader.js +70 -7
  78. package/out/runtime/loader.js.map +1 -1
  79. package/out/runtime/logger.d.ts.map +1 -1
  80. package/out/runtime/logger.js +8 -1
  81. package/out/runtime/logger.js.map +1 -1
  82. package/out/runtime/module.d.ts +18 -0
  83. package/out/runtime/module.d.ts.map +1 -1
  84. package/out/runtime/module.js +91 -3
  85. package/out/runtime/module.js.map +1 -1
  86. package/out/runtime/modules/ai.d.ts +16 -5
  87. package/out/runtime/modules/ai.d.ts.map +1 -1
  88. package/out/runtime/modules/ai.js +286 -88
  89. package/out/runtime/modules/ai.js.map +1 -1
  90. package/out/runtime/modules/core.d.ts.map +1 -1
  91. package/out/runtime/modules/core.js +5 -1
  92. package/out/runtime/modules/core.js.map +1 -1
  93. package/out/runtime/monitor.d.ts +6 -0
  94. package/out/runtime/monitor.d.ts.map +1 -1
  95. package/out/runtime/monitor.js +21 -1
  96. package/out/runtime/monitor.js.map +1 -1
  97. package/out/runtime/relgraph.d.ts.map +1 -1
  98. package/out/runtime/relgraph.js +7 -3
  99. package/out/runtime/relgraph.js.map +1 -1
  100. package/out/runtime/resolvers/interface.d.ts +7 -2
  101. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  102. package/out/runtime/resolvers/interface.js +17 -3
  103. package/out/runtime/resolvers/interface.js.map +1 -1
  104. package/out/runtime/resolvers/sqldb/database.d.ts +2 -0
  105. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  106. package/out/runtime/resolvers/sqldb/database.js +142 -126
  107. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  108. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  109. package/out/runtime/resolvers/sqldb/dbutil.js +25 -4
  110. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  111. package/out/runtime/resolvers/sqldb/impl.d.ts +2 -1
  112. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  113. package/out/runtime/resolvers/sqldb/impl.js +24 -7
  114. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  115. package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
  116. package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
  117. package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
  118. package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
  119. package/out/runtime/resolvers/vector/types.d.ts +32 -0
  120. package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
  121. package/out/runtime/resolvers/vector/types.js +2 -0
  122. package/out/runtime/resolvers/vector/types.js.map +1 -0
  123. package/out/runtime/services/documentFetcher.d.ts.map +1 -1
  124. package/out/runtime/services/documentFetcher.js +21 -6
  125. package/out/runtime/services/documentFetcher.js.map +1 -1
  126. package/out/runtime/state.d.ts +19 -1
  127. package/out/runtime/state.d.ts.map +1 -1
  128. package/out/runtime/state.js +36 -1
  129. package/out/runtime/state.js.map +1 -1
  130. package/out/runtime/util.d.ts +3 -2
  131. package/out/runtime/util.d.ts.map +1 -1
  132. package/out/runtime/util.js +13 -2
  133. package/out/runtime/util.js.map +1 -1
  134. package/out/syntaxes/agentlang.monarch.js +1 -1
  135. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  136. package/out/test-harness.d.ts +36 -0
  137. package/out/test-harness.d.ts.map +1 -0
  138. package/out/test-harness.js +341 -0
  139. package/out/test-harness.js.map +1 -0
  140. package/package.json +22 -19
  141. package/src/api/http.ts +336 -38
  142. package/src/cli/main.ts +3 -0
  143. package/src/language/agentlang-validator.ts +3 -0
  144. package/src/language/agentlang.langium +6 -2
  145. package/src/language/error-reporter.ts +1028 -0
  146. package/src/language/generated/ast.ts +94 -1
  147. package/src/language/generated/grammar.ts +342 -206
  148. package/src/language/parser.ts +64 -101
  149. package/src/language/syntax.ts +79 -24
  150. package/src/runtime/api.ts +36 -0
  151. package/src/runtime/datefns.ts +112 -0
  152. package/src/runtime/defs.ts +2 -1
  153. package/src/runtime/document-retriever.ts +311 -0
  154. package/src/runtime/embeddings/chunker.ts +52 -14
  155. package/src/runtime/embeddings/openai.ts +27 -9
  156. package/src/runtime/embeddings/provider.ts +22 -1
  157. package/src/runtime/exec-graph.ts +23 -2
  158. package/src/runtime/integration-client.ts +158 -0
  159. package/src/runtime/integrations.ts +20 -11
  160. package/src/runtime/interpreter.ts +221 -15
  161. package/src/runtime/loader.ts +83 -5
  162. package/src/runtime/logger.ts +12 -1
  163. package/src/runtime/module.ts +104 -3
  164. package/src/runtime/modules/ai.ts +341 -107
  165. package/src/runtime/modules/core.ts +5 -1
  166. package/src/runtime/monitor.ts +27 -1
  167. package/src/runtime/relgraph.ts +7 -3
  168. package/src/runtime/resolvers/interface.ts +23 -3
  169. package/src/runtime/resolvers/sqldb/database.ts +158 -130
  170. package/src/runtime/resolvers/sqldb/dbutil.ts +28 -6
  171. package/src/runtime/resolvers/sqldb/impl.ts +25 -7
  172. package/src/runtime/resolvers/vector/lancedb-store.ts +187 -0
  173. package/src/runtime/resolvers/vector/types.ts +39 -0
  174. package/src/runtime/services/documentFetcher.ts +21 -6
  175. package/src/runtime/state.ts +40 -1
  176. package/src/runtime/util.ts +19 -2
  177. package/src/syntaxes/agentlang.monarch.ts +1 -1
  178. package/src/test-harness.ts +423 -0
package/src/api/http.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import express, { Request, Response } from 'express';
2
+ import express, { Express, Request, Response } from 'express';
3
3
  import * as path from 'path';
4
4
  import {
5
5
  getAllChildRelationships,
@@ -8,6 +8,7 @@ import {
8
8
  Instance,
9
9
  InstanceAttributes,
10
10
  isBetweenRelationship,
11
+ getRelationship,
11
12
  makeInstance,
12
13
  objectAsInstanceAttributes,
13
14
  Relationship,
@@ -58,13 +59,15 @@ import {
58
59
  createFileRecord,
59
60
  deleteFileRecord,
60
61
  } from '../runtime/modules/files.js';
61
-
62
- export async function startServer(
63
- appSpec: ApplicationSpec,
64
- port: number,
65
- host?: string,
66
- config?: Config
67
- ) {
62
+ import {
63
+ getOAuthAuthorizeUrl,
64
+ exchangeOAuthCode,
65
+ getIntegrationAccessToken,
66
+ } from '../runtime/integration-client.js';
67
+ import * as XLSX from 'xlsx';
68
+ import { objectToQueryPattern } from '../language/parser.js';
69
+
70
+ export async function createApp(appSpec: ApplicationSpec, config?: Config): Promise<Express> {
68
71
  const app = express();
69
72
  app.use(express.json());
70
73
 
@@ -197,6 +200,87 @@ export async function startServer(
197
200
  handleMetaGet(req, res);
198
201
  });
199
202
 
203
+ app.post('/agentlang/QueryAsCsv', (req: Request, res: Response) => {
204
+ handleQueryAsCsvPost(req, res);
205
+ });
206
+
207
+ app.post('/agentlang/QueryAsXlsx', (req: Request, res: Response) => {
208
+ handleQueryAsXlsxPost(req, res);
209
+ });
210
+
211
+ // --- Built-in OAuth proxy ---
212
+ if (config?.integrations?.oauth) {
213
+ const connections = config.integrations.connections;
214
+
215
+ // Resolve provider key (e.g. 'google_drive') to integration entity name
216
+ // (e.g. 'google-drive') by extracting from the config path.
217
+ function resolveIntegrationName(provider: string): string {
218
+ const entry = connections[provider];
219
+ if (!entry) return provider;
220
+ const configPath = typeof entry === 'string' ? entry : entry.config;
221
+ if (!configPath) return provider;
222
+ return configPath.split('/')[0];
223
+ }
224
+
225
+ app.get('/agentlang/oauth/authorize-url', async (req: Request, res: Response) => {
226
+ try {
227
+ const provider = req.query.provider as string;
228
+ const redirectUri = req.query.redirectUri as string;
229
+ if (!provider || !redirectUri) {
230
+ res.status(400).json({ error: 'provider and redirectUri query params are required' });
231
+ return;
232
+ }
233
+ if (!connections[provider]) {
234
+ res.status(400).json({ error: `Unknown provider: ${provider}` });
235
+ return;
236
+ }
237
+ const result = await getOAuthAuthorizeUrl(resolveIntegrationName(provider), redirectUri);
238
+ res.json(result);
239
+ } catch (err: any) {
240
+ logger.error(`OAuth authorize-url error: ${err}`);
241
+ res.status(500).json({ error: err.message || 'Failed to get authorization URL' });
242
+ }
243
+ });
244
+
245
+ app.post('/agentlang/oauth/exchange', async (req: Request, res: Response) => {
246
+ try {
247
+ const { provider, code, state } = req.body;
248
+ if (!provider || !code || !state) {
249
+ res.status(400).json({ error: 'provider, code, and state are required in request body' });
250
+ return;
251
+ }
252
+ if (!connections[provider]) {
253
+ res.status(400).json({ error: `Unknown provider: ${provider}` });
254
+ return;
255
+ }
256
+ const result = await exchangeOAuthCode(resolveIntegrationName(provider), code, state);
257
+ res.json(result);
258
+ } catch (err: any) {
259
+ logger.error(`OAuth exchange error: ${err}`);
260
+ res.status(500).json({ error: err.message || 'Failed to exchange authorization code' });
261
+ }
262
+ });
263
+
264
+ app.get('/agentlang/oauth/access-token', async (req: Request, res: Response) => {
265
+ try {
266
+ const provider = req.query.provider as string;
267
+ if (!provider) {
268
+ res.status(400).json({ error: 'provider query param is required' });
269
+ return;
270
+ }
271
+ if (!connections[provider]) {
272
+ res.status(400).json({ error: `Unknown provider: ${provider}` });
273
+ return;
274
+ }
275
+ const result = await getIntegrationAccessToken(resolveIntegrationName(provider));
276
+ res.json(result);
277
+ } catch (err: any) {
278
+ logger.error(`OAuth access-token error: ${err}`);
279
+ res.status(500).json({ error: err.message || 'Failed to get access token' });
280
+ }
281
+ });
282
+ }
283
+
200
284
  if (isNodeEnv && upload && uploadDir) {
201
285
  app.post('/uploadFile', upload.single('file'), (req: Request, res: Response) => {
202
286
  handleFileUpload(req, res, config);
@@ -285,19 +369,6 @@ export async function startServer(
285
369
  });
286
370
  });
287
371
 
288
- const cb = () => {
289
- console.log(
290
- chalk.green(
291
- `Application ${chalk.bold(appName + ' version ' + appVersion)} started on port ${chalk.bold(port)}`
292
- )
293
- );
294
- };
295
- if (host) {
296
- app.listen(port, host, cb);
297
- } else {
298
- app.listen(port, cb);
299
- }
300
-
301
372
  setEventEndpointsUpdater((moduleName: string) => {
302
373
  const m = fetchModule(moduleName);
303
374
  const eventNames = m.getEventNames();
@@ -319,6 +390,39 @@ export async function startServer(
319
390
  addBetweenHandlers(moduleName, n);
320
391
  });
321
392
  });
393
+
394
+ return app;
395
+ }
396
+
397
+ export async function startServer(
398
+ appSpec: ApplicationSpec,
399
+ port: number,
400
+ host?: string,
401
+ config?: Config
402
+ ) {
403
+ const app = await createApp(appSpec, config);
404
+ const appName: string = appSpec.name;
405
+ const appVersion: string = appSpec.version;
406
+
407
+ // Expose port and host on globalThis so resolver code (e.g. integration-manager's
408
+ // auth-resolver) can make self-referencing HTTP calls to the correct address.
409
+ (globalThis as any).__agentlang_port = port;
410
+ if (host) {
411
+ (globalThis as any).__agentlang_host = host;
412
+ }
413
+
414
+ const cb = () => {
415
+ console.log(
416
+ chalk.green(
417
+ `Application ${chalk.bold(appName + ' version ' + appVersion)} started on port ${chalk.bold(port)}`
418
+ )
419
+ );
420
+ };
421
+ if (host) {
422
+ app.listen(port, host, cb);
423
+ } else {
424
+ app.listen(port, cb);
425
+ }
322
426
  }
323
427
 
324
428
  function ok(res: Response) {
@@ -346,23 +450,69 @@ function internalError(res: Response) {
346
450
  };
347
451
  }
348
452
 
349
- function patternFromAttributes(
453
+ function formatAttrValue(v: any, n: string): string {
454
+ let av = isString(v) ? `"${v}"` : v;
455
+ if (av instanceof Object) {
456
+ av = JSON.stringify(av);
457
+ }
458
+ if (isPathAttribute(n)) {
459
+ av = escapeSepInPath(av);
460
+ }
461
+ return `${n} ${av}`;
462
+ }
463
+
464
+ function buildChildPattern(otherFqName: string, childObj: { [key: string]: any }): string {
465
+ const childAttrs = Object.entries(childObj)
466
+ .map(([k, v]: [string, any]) => {
467
+ let av: any;
468
+ if (isString(v)) {
469
+ av = `"${v}"`;
470
+ } else if (v instanceof Object) {
471
+ av = JSON.stringify(v);
472
+ } else {
473
+ av = v;
474
+ }
475
+ return `${k} ${av}`;
476
+ })
477
+ .join(', ');
478
+ return `{${otherFqName} {${childAttrs}}, @upsert}`;
479
+ }
480
+
481
+ export function patternFromAttributes(
350
482
  moduleName: string,
351
483
  recName: string,
352
- attrs: InstanceAttributes
484
+ attrs: InstanceAttributes,
485
+ entityFqName?: string
353
486
  ): string {
354
487
  const attrsStrs = new Array<string>();
488
+ const relPatterns = new Array<string>();
489
+
355
490
  attrs.forEach((v: any, n: string) => {
356
- let av = isString(v) ? `"${v}"` : v;
357
- if (av instanceof Object) {
358
- av = JSON.stringify(av);
359
- }
360
- if (isPathAttribute(n)) {
361
- av = escapeSepInPath(av);
491
+ if (entityFqName) {
492
+ try {
493
+ if (isBetweenRelationship(n, moduleName)) {
494
+ const rel = getRelationship(n, moduleName);
495
+ const otherFqName =
496
+ rel.getParentFqName() === entityFqName ? rel.getChildFqName() : rel.getParentFqName();
497
+ const children = Array.isArray(v) ? v : [v];
498
+ const childPatterns = children
499
+ .map((child: any) => buildChildPattern(otherFqName, child))
500
+ .join(', ');
501
+ relPatterns.push(`${n} [${childPatterns}]`);
502
+ return;
503
+ }
504
+ } catch {
505
+ // Not a relationship — fall through to normal attribute handling
506
+ }
362
507
  }
363
- attrsStrs.push(`${n} ${av}`);
508
+ attrsStrs.push(formatAttrValue(v, n));
364
509
  });
365
- return `{${moduleName}/${recName} { ${attrsStrs.join(',\n')} }}`;
510
+
511
+ const entityPat = `{${moduleName}/${recName} {${attrsStrs.join(',\n')}}`;
512
+ if (relPatterns.length > 0) {
513
+ return `${entityPat}, ${relPatterns.join(', ')}}`;
514
+ }
515
+ return `${entityPat}}`;
366
516
  }
367
517
 
368
518
  function normalizeRequestPath(path: string[], moduleName: string): string[] {
@@ -423,7 +573,7 @@ async function handleEventPost(
423
573
  eventName,
424
574
  objectAsInstanceAttributes(req.body)
425
575
  ).setAuthContext(sessionInfo);
426
- evaluate(inst, ok(res)).catch(internalError(res));
576
+ evaluate(inst).then(ok(res)).catch(internalError(res));
427
577
  } catch (err: any) {
428
578
  logger.error(err);
429
579
  res.status(500).send(err.toString());
@@ -442,9 +592,15 @@ async function handleEntityPost(
442
592
  res.status(401).send('Authorization required');
443
593
  return;
444
594
  }
595
+ const entityFqName = makeFqName(moduleName, entityName);
445
596
  const pattern = req.params.path
446
597
  ? createChildPattern(moduleName, entityName, req)
447
- : patternFromAttributes(moduleName, entityName, objectAsInstanceAttributes(req.body));
598
+ : patternFromAttributes(
599
+ moduleName,
600
+ entityName,
601
+ objectAsInstanceAttributes(req.body),
602
+ entityFqName
603
+ );
448
604
  parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
449
605
  } catch (err: any) {
450
606
  logger.error(err);
@@ -483,15 +639,23 @@ const joinTags = new Map()
483
639
  .set('@leftJoinOn', '@left_join')
484
640
  .set('@rightJoinOn', '@right_join');
485
641
 
486
- function objectAsAttributesPattern(entityFqName: string, obj: object): [string, boolean] {
642
+ const paginationTags = new Set(['@limit', '@offset']);
643
+
644
+ function objectAsAttributesPattern(entityFqName: string, obj: object): [string, boolean, string] {
487
645
  const attrs = new Array<string>();
488
646
  let joinType: string | undefined;
489
647
  let joinOnAttr: string | undefined;
648
+ const paginationParts = new Array<string>();
490
649
  Object.keys(obj).forEach(key => {
491
650
  const s: string = obj[key as keyof object];
492
651
  if (joinTags.has(key)) {
493
652
  joinType = joinTags.get(key);
494
653
  joinOnAttr = s;
654
+ } else if (paginationTags.has(key)) {
655
+ const n = parseInt(s, 10);
656
+ if (!Number.isNaN(n) && Number.isInteger(n) && n >= 0) {
657
+ paginationParts.push(`${key}(${n})`);
658
+ }
495
659
  } else {
496
660
  let v = s;
497
661
  if (!s.startsWith('"')) {
@@ -519,12 +683,18 @@ function objectAsAttributesPattern(entityFqName: string, obj: object): [string,
519
683
  });
520
684
  const intoPat = `@into {${intoSpec.join(', ')}}`;
521
685
  joinOnAttr = reverseJoin ? splitRefs(joinOnAttr)[1] : joinOnAttr;
686
+ const paginationStr = paginationParts.length > 0 ? `,\n${paginationParts.join(',\n')}` : '';
522
687
  return [
523
- `${pat},\n${joinType} ${targetEntity} {${targetAttr}? ${entityFqName}.${joinOnAttr}}, \n${intoPat}`,
688
+ `${pat},\n${joinType} ${targetEntity} {${targetAttr}? ${entityFqName}.${joinOnAttr}}, \n${intoPat}${paginationStr}`,
524
689
  hasQueryAttrs,
690
+ '',
525
691
  ];
526
692
  } else {
527
- return [pat, hasQueryAttrs];
693
+ return [
694
+ pat,
695
+ hasQueryAttrs,
696
+ paginationParts.length > 0 ? `,\n${paginationParts.join(',\n')}` : '',
697
+ ];
528
698
  }
529
699
  }
530
700
 
@@ -537,9 +707,9 @@ function queryPatternFromPath(path: string, req: Request): string {
537
707
  const fqName = `${moduleName}/${entityName}`;
538
708
  if (parts.length == 2 && id === undefined) {
539
709
  if (req.query && Object.keys(req.query).length > 0) {
540
- const [pat, hasQueryAttrs] = objectAsAttributesPattern(fqName, req.query);
710
+ const [pat, hasQueryAttrs, paginationPat] = objectAsAttributesPattern(fqName, req.query);
541
711
  const n = hasQueryAttrs ? fqName : `${fqName}?`;
542
- return `{${n} ${pat}}`;
712
+ return `{${n} ${pat}${paginationPat}}`;
543
713
  } else {
544
714
  return `{${fqName}? {}}`;
545
715
  }
@@ -730,6 +900,69 @@ function normalizedResult(r: Result): Result {
730
900
  }
731
901
  }
732
902
 
903
+ /**
904
+ * Converts eval result to an array of flat row objects for CSV/XLSX.
905
+ * Handles:
906
+ * - [{ "EntityName": { "attr": "attrval" } }, ...] -> one row per item, columns = Entity + attrs
907
+ * - [{ "attr": "attrval" }, ...] -> one row per item, columns = attrs
908
+ * - [[ { "EntityName": { ... } }, ... ]] -> unwrap single outer array, then one row per inner item
909
+ */
910
+ function evalResultToRows(data: Result): { [key: string]: unknown }[] {
911
+ let items: Result[];
912
+ if (Array.isArray(data) && data.length === 1 && Array.isArray(data[0])) {
913
+ items = data[0] as Result[];
914
+ } else {
915
+ items = Array.isArray(data) ? (data as Result[]) : [data];
916
+ }
917
+ return items.map((item: Result) => {
918
+ if (item === null || typeof item !== 'object') {
919
+ return { value: item };
920
+ }
921
+ if (Array.isArray(item)) {
922
+ return { value: item };
923
+ }
924
+ const obj = item as { [key: string]: unknown };
925
+ const keys = Object.keys(obj);
926
+ if (keys.length === 1) {
927
+ const [k] = keys;
928
+ const v = obj[k];
929
+ if (
930
+ v !== null &&
931
+ typeof v === 'object' &&
932
+ !Array.isArray(v) &&
933
+ Object.prototype.toString.call(v) === '[object Object]'
934
+ ) {
935
+ return { ...(v as { [key: string]: unknown }) };
936
+ }
937
+ }
938
+ return { ...obj };
939
+ });
940
+ }
941
+
942
+ /**
943
+ * Ensures cell value is stringifiable for CSV/XLSX (nested objects/arrays become JSON string).
944
+ */
945
+ function cellValue(val: unknown): string | number | boolean | null {
946
+ if (val === null || val === undefined) return '';
947
+ if (typeof val === 'object') return JSON.stringify(val);
948
+ return val as string | number | boolean;
949
+ }
950
+
951
+ /**
952
+ * Parses column names from the first instance and returns an array-of-arrays
953
+ * for the sheet: first row = column names, following rows = data. This ensures
954
+ * CSV/XLSX output has proper headers (col1, col2, ...) not 0, 1, 2, ...
955
+ */
956
+ function rowsToSheetAoa(
957
+ rows: { [key: string]: unknown }[]
958
+ ): (string | number | boolean | null)[][] {
959
+ if (rows.length === 0) return [];
960
+ const columns = Object.keys(rows[0] as object);
961
+ const headerRow = columns;
962
+ const dataRows = rows.map(row => columns.map(col => cellValue(row[col])));
963
+ return [headerRow, ...dataRows];
964
+ }
965
+
733
966
  async function handleMetaGet(req: Request, res: Response): Promise<void> {
734
967
  try {
735
968
  const sessionInfo = await verifyAuth('', '', req.headers.authorization);
@@ -957,6 +1190,71 @@ async function handleMetaGet(req: Request, res: Response): Promise<void> {
957
1190
  }
958
1191
  }
959
1192
 
1193
+ async function handleQueryAsCsvPost(req: Request, res: Response): Promise<void> {
1194
+ try {
1195
+ const sessionInfo = await verifyAuth('', '', req.headers.authorization);
1196
+ if (isNoSession(sessionInfo)) {
1197
+ res.status(401).send('Authorization required');
1198
+ return;
1199
+ }
1200
+ const q = req.body?.q;
1201
+ if (q === undefined) {
1202
+ res.status(400).send('Missing or invalid pattern');
1203
+ return;
1204
+ }
1205
+ const qs = objectToQueryPattern(q);
1206
+ const result = await parseAndEvaluateStatement(qs, sessionInfo.userId);
1207
+ const normalized = normalizedResult(result);
1208
+ const rows = evalResultToRows(normalized);
1209
+ const aoa = rowsToSheetAoa(rows);
1210
+ const worksheet = XLSX.utils.aoa_to_sheet(aoa);
1211
+ const workbook = XLSX.utils.book_new();
1212
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
1213
+ const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'csv' });
1214
+ const filename = `eval-${Date.now()}.csv`;
1215
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
1216
+ res.contentType('text/csv');
1217
+ res.send(buffer);
1218
+ } catch (err: any) {
1219
+ logger.error(err);
1220
+ if (!res.headersSent) {
1221
+ res.status(statusFromErrorType(err)).send(err?.message ?? err?.toString());
1222
+ }
1223
+ }
1224
+ }
1225
+
1226
+ async function handleQueryAsXlsxPost(req: Request, res: Response): Promise<void> {
1227
+ try {
1228
+ const sessionInfo = await verifyAuth('', '', req.headers.authorization);
1229
+ if (isNoSession(sessionInfo)) {
1230
+ res.status(401).send('Authorization required');
1231
+ return;
1232
+ }
1233
+ const q = req.body?.q;
1234
+ if (q === undefined) {
1235
+ res.status(400).send('Missing or invalid pattern');
1236
+ return;
1237
+ }
1238
+ const qs = objectToQueryPattern(q);
1239
+ const result = await parseAndEvaluateStatement(qs, sessionInfo.userId);
1240
+ const normalized = normalizedResult(result);
1241
+ const rows = evalResultToRows(normalized);
1242
+ const aoa = rowsToSheetAoa(rows);
1243
+ const worksheet = XLSX.utils.aoa_to_sheet(aoa);
1244
+ const workbook = XLSX.utils.book_new();
1245
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
1246
+ const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
1247
+ const filename = `eval-${Date.now()}.xlsx`;
1248
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
1249
+ res.contentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
1250
+ res.send(buffer);
1251
+ } catch (err: any) {
1252
+ logger.error(err);
1253
+ if (!res.headersSent) {
1254
+ res.status(statusFromErrorType(err)).send(err?.message ?? err?.toString());
1255
+ }
1256
+ }
1257
+ }
960
1258
  async function handleFileUpload(
961
1259
  req: Request & { file?: Express.Multer.File },
962
1260
  res: Response,
package/src/cli/main.ts CHANGED
@@ -21,6 +21,7 @@ import { Instance, Module } from '../runtime/module.js';
21
21
  import { ModuleDefinition } from '../language/generated/ast.js';
22
22
  import { Config } from '../runtime/state.js';
23
23
  import { prepareIntegrations } from '../runtime/integrations.js';
24
+ import { configureIntegrationClient } from '../runtime/integration-client.js';
24
25
  import { isExecGraphEnabled, isNodeEnv } from '../utils/runtime.js';
25
26
  import { OpenAPIClientAxios } from 'openapi-client-axios';
26
27
  import { registerOpenApiModule } from '../runtime/openapi.js';
@@ -239,6 +240,8 @@ export const runModule = async (fileName: string, releaseDb: boolean = false): P
239
240
  config.integrations.password,
240
241
  config.integrations.connections
241
242
  );
243
+ // Configure the thin HTTP client to talk to integration-manager for auth
244
+ configureIntegrationClient(config.integrations.host);
242
245
  }
243
246
  if (config.openapi) {
244
247
  await loadOpenApiSpec(config.openapi);
@@ -25,6 +25,9 @@ export class AgentlangValidator {
25
25
  if (def.$type === 'PublicEventDefinition') {
26
26
  def = def.def;
27
27
  }
28
+ if (!def?.schema?.attributes) {
29
+ return;
30
+ }
28
31
  def.schema.attributes.forEach((a: AttributeDefinition) => {
29
32
  if (reported.has(a.name)) {
30
33
  accept('error', `'${def.name} " - attribute has non-unique name '${a.name}'.`, {
@@ -115,7 +115,9 @@ Statement: pattern=Pattern hints+=RuntimeHint*;
115
115
 
116
116
  IfWithAlias: '(' if=If ')';
117
117
 
118
- RuntimeHint: aliasSpec=AliasSpec | catchSpec=CatchSpec | thenSpec=ThenSpec;
118
+ RuntimeHint: aliasSpec=AliasSpec | catchSpec=CatchSpec | emptySpec=EmptySpec | thenSpec=ThenSpec;
119
+
120
+ EmptySpec: '@empty' stmt=Statement;
119
121
 
120
122
  AliasSpec: ('@as' (alias=ID | '[' ( aliases+=ID (',' aliases+=ID)*)+ ']'));
121
123
 
@@ -129,7 +131,7 @@ CrudMap: '{' (name=QueryId (':')? '{''}'
129
131
  '}';
130
132
 
131
133
  QueryOption: join=JoinSpec | into=SelectIntoSpec | where=WhereSpec | groupByClause=GroupByClause | orderByClause=OrderByClause
132
- | upsert='@upsert' | distinct='@distinct';
134
+ | limitClause=LimitClause | offsetClause=OffsetClause | upsert='@upsert' | distinct='@distinct';
133
135
 
134
136
  CrudMapBody: '{' (attributes+=SetAttribute (',' attributes+=SetAttribute)*)+ properties+=PropertyDefinition* '}';
135
137
 
@@ -146,6 +148,8 @@ WhereSpecClause: lhs=QueryId op=SqlComparisonOpr? rhs=Expr;
146
148
 
147
149
  GroupByClause: '@groupBy' ('(' (colNames+=QualifiedName (',' colNames+=QualifiedName)*)+ ')');
148
150
  OrderByClause: '@orderBy' ('(' (colNames+=QualifiedName (',' colNames+=QualifiedName)*)+ ')') (order=('@asc' | '@desc'))?;
151
+ LimitClause: '@limit' '(' value=INT ')';
152
+ OffsetClause: '@offset' '(' value=INT ')';
149
153
 
150
154
  FullTextSearch: '{' name=QueryId query=Literal options=MapLiteral? '}';
151
155