agentlang 0.0.4 → 0.0.5

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 (43) hide show
  1. package/out/api/http.js +27 -19
  2. package/out/api/http.js.map +1 -1
  3. package/out/language/generated/ast.d.ts +20 -3
  4. package/out/language/generated/ast.d.ts.map +1 -1
  5. package/out/language/generated/ast.js +26 -1
  6. package/out/language/generated/ast.js.map +1 -1
  7. package/out/language/generated/grammar.d.ts.map +1 -1
  8. package/out/language/generated/grammar.js +208 -106
  9. package/out/language/generated/grammar.js.map +1 -1
  10. package/out/language/main.cjs +228 -107
  11. package/out/language/main.cjs.map +2 -2
  12. package/out/runtime/loader.d.ts.map +1 -1
  13. package/out/runtime/loader.js +25 -9
  14. package/out/runtime/loader.js.map +1 -1
  15. package/out/runtime/module.d.ts +1 -0
  16. package/out/runtime/module.d.ts.map +1 -1
  17. package/out/runtime/module.js +14 -0
  18. package/out/runtime/module.js.map +1 -1
  19. package/out/runtime/modules/auth.d.ts.map +1 -1
  20. package/out/runtime/modules/auth.js +13 -2
  21. package/out/runtime/modules/auth.js.map +1 -1
  22. package/out/runtime/resolvers/sqldb/database.d.ts +4 -2
  23. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  24. package/out/runtime/resolvers/sqldb/database.js +43 -4
  25. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  26. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  27. package/out/runtime/resolvers/sqldb/impl.js +7 -3
  28. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  29. package/out/runtime/util.d.ts +1 -0
  30. package/out/runtime/util.d.ts.map +1 -1
  31. package/out/runtime/util.js +11 -0
  32. package/out/runtime/util.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/api/http.ts +26 -17
  35. package/src/language/agentlang.langium +5 -1
  36. package/src/language/generated/ast.ts +48 -4
  37. package/src/language/generated/grammar.ts +208 -106
  38. package/src/runtime/loader.ts +23 -10
  39. package/src/runtime/module.ts +14 -0
  40. package/src/runtime/modules/auth.ts +13 -2
  41. package/src/runtime/resolvers/sqldb/database.ts +55 -4
  42. package/src/runtime/resolvers/sqldb/impl.ts +8 -7
  43. package/src/runtime/util.ts +12 -0
@@ -19,11 +19,10 @@ import {
19
19
  SchemaDefinition,
20
20
  isAgentDefinition,
21
21
  AgentDefinition,
22
- SetAttribute,
23
- isLiteral,
24
22
  isResolverDefinition,
25
23
  ResolverDefinition,
26
24
  ResolverMethodSpec,
25
+ AgentPropertyDef,
27
26
  } from '../language/generated/ast.js';
28
27
  import {
29
28
  addEntity,
@@ -417,27 +416,41 @@ async function addAgentDefinition(def: AgentDefinition, moduleName: string) {
417
416
  const attrsStrs = new Array<string>();
418
417
  attrsStrs.push(`name "${name}"`);
419
418
  const attrs = newInstanceAttributes();
420
- def.body?.attributes.forEach((sa: SetAttribute) => {
419
+ def.body?.attributes.forEach((apdef: AgentPropertyDef) => {
421
420
  let v: any = undefined;
422
- if (isLiteral(sa.value)) {
423
- v = sa.value.str || sa.value.id || sa.value.num;
421
+ if (apdef.value.array) {
422
+ v = apdef.value.array.vals
423
+ .map((stmt: Statement) => {
424
+ if (stmt.pattern.literal) {
425
+ const s = stmt.pattern.literal.str;
426
+ if (s == undefined) {
427
+ throw new Error(`Only arrays of string-literals are to be passed to agent ${name}`);
428
+ }
429
+ return s;
430
+ } else {
431
+ throw new Error(`Invalid value in array passed to agent ${name}`);
432
+ }
433
+ })
434
+ .join(',');
435
+ } else {
436
+ v = apdef.value.str || apdef.value.id || apdef.value.num;
424
437
  if (v == undefined) {
425
- v = sa.value.bool;
438
+ v = apdef.value.bool;
426
439
  }
427
440
  }
428
441
  if (v == undefined) {
429
442
  throw new Error(`Cannot initialize agent ${name}, only literals can be set for attributes`);
430
443
  }
431
- if (llmName == undefined && sa.name == 'llm') {
444
+ if (llmName == undefined && apdef.name == 'llm') {
432
445
  llmName = v;
433
446
  hasUserLlm = true;
434
447
  }
435
448
  const ov = v;
436
- if (isLiteral(sa.value) && (sa.value.str || sa.value.id)) {
449
+ if (apdef.value.str || apdef.value.id || apdef.value.array) {
437
450
  v = `"${v}"`;
438
451
  }
439
- attrsStrs.push(`${sa.name} ${v}`);
440
- attrs.set(sa.name, ov);
452
+ attrsStrs.push(`${apdef.name} ${v}`);
453
+ attrs.set(apdef.name, ov);
441
454
  });
442
455
  if (!attrs.has('llm')) {
443
456
  llmName = `${name}_llm`;
@@ -2419,3 +2419,17 @@ export function instanceToObject<Type>(inst: Instance, obj: any): Type {
2419
2419
  });
2420
2420
  return obj as Type;
2421
2421
  }
2422
+
2423
+ export function getEntityRbacRules(entityFqName: string): RbacSpecification[] | undefined {
2424
+ const p = splitFqName(entityFqName);
2425
+ const mn = p.getModuleName();
2426
+ const en = p.getEntryName();
2427
+ const m = isModule(mn) && fetchModule(mn);
2428
+ if (m && m.isEntity(en)) {
2429
+ const entity = getEntity(en, mn);
2430
+ return entity.getRbacSpecifications()?.filter((spec: RbacSpecification) => {
2431
+ return spec.expression != undefined;
2432
+ });
2433
+ }
2434
+ return undefined;
2435
+ }
@@ -35,7 +35,13 @@ entity User {
35
35
  id UUID @id @default(uuid()),
36
36
  email Email @unique @indexed,
37
37
  firstName String,
38
- lastName String
38
+ lastName String,
39
+ @rbac [(allow: [read, delete, update, create], where: auth.user = this.id)],
40
+ @after {delete AfterDeleteUser}
41
+ }
42
+
43
+ workflow AfterDeleteUser {
44
+ {RemoveUserSession {id AfterDeleteUser.User.id}}
39
45
  }
40
46
 
41
47
  workflow CreateUser {
@@ -124,7 +130,8 @@ entity Session {
124
130
  id UUID @id,
125
131
  userId UUID @indexed,
126
132
  authToken String @optional,
127
- isActive Boolean
133
+ isActive Boolean,
134
+ @rbac [(allow: [read, delete, update, create], where: auth.user = this.userId)]
128
135
  }
129
136
 
130
137
 
@@ -147,6 +154,10 @@ workflow RemoveSession {
147
154
  purge {Session {id? RemoveSession.id}}
148
155
  }
149
156
 
157
+ workflow RemoveUserSession {
158
+ {Session {userId? RemoveUserSession.id}} as [session];
159
+ purge {Session {id? session.id}}
160
+ }
150
161
 
151
162
  workflow signup {
152
163
  await Auth.signUpUser(signup.email, signup.password, signup.userData)
@@ -14,6 +14,7 @@ import {
14
14
  InstanceAttributes,
15
15
  newInstanceAttributes,
16
16
  RbacPermissionFlag,
17
+ RbacSpecification,
17
18
  Relationship,
18
19
  } from '../../module.js';
19
20
  import pgvector from 'pgvector';
@@ -29,13 +30,15 @@ export class DbContext {
29
30
  resourceFqName: string;
30
31
  activeEnv: Environment;
31
32
  private needAuthCheckFlag: boolean = true;
33
+ rbacRules: RbacSpecification[] | undefined;
32
34
 
33
35
  constructor(
34
36
  resourceFqName: string,
35
37
  authInfo: ResolverAuthInfo,
36
38
  activeEnv: Environment,
37
39
  txnId?: string,
38
- inKernelMode?: boolean
40
+ inKernelMode?: boolean,
41
+ rbacRules?: RbacSpecification[]
39
42
  ) {
40
43
  this.resourceFqName = resourceFqName;
41
44
  this.authInfo = authInfo;
@@ -44,6 +47,7 @@ export class DbContext {
44
47
  if (inKernelMode != undefined) {
45
48
  this.inKernelMode = inKernelMode;
46
49
  }
50
+ this.rbacRules = rbacRules;
47
51
  }
48
52
  private static GlobalDbContext: DbContext | undefined;
49
53
 
@@ -93,6 +97,12 @@ export class DbContext {
93
97
  return this;
94
98
  }
95
99
 
100
+ switchAuthCheck(flag: boolean): boolean {
101
+ const old = this.needAuthCheckFlag;
102
+ this.needAuthCheckFlag = flag;
103
+ return old;
104
+ }
105
+
96
106
  isPermitted(): boolean {
97
107
  return this.inKernelMode || !this.needAuthCheckFlag;
98
108
  }
@@ -379,8 +389,29 @@ export async function insertRows(
379
389
  }
380
390
  if (hasPerm) {
381
391
  await insertRowsHelper(tableName, rows, ctx, doUpsert);
382
- if (!ctx.isInKernelMode() && !doUpsert) {
383
- await createOwnership(tableName, rows, ctx);
392
+ if (!doUpsert) {
393
+ if (!ctx.isInKernelMode()) {
394
+ await createOwnership(tableName, rows, ctx);
395
+ }
396
+ if (ctx.rbacRules) {
397
+ for (let i = 0; i < ctx.rbacRules.length; ++i) {
398
+ const rbacRule = ctx.rbacRules[i];
399
+ const e = rbacRule.expression;
400
+ if (e) {
401
+ const [selfRef, userRef] = e.lhs.startsWith('this.') ? [e.lhs, e.rhs] : [e.rhs, e.lhs];
402
+ if (userRef == 'auth.user') {
403
+ const attr = selfRef.split('.')[1];
404
+ for (let j = 0; j < rows.length; ++j) {
405
+ const r: any = rows[j];
406
+ const userId = r[attr];
407
+ if (userId) {
408
+ await createLimitedOwnership(tableName, [r], userId, rbacRule.permissions, ctx);
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
384
415
  }
385
416
  } else {
386
417
  throw new UnauthorisedError({ opr: 'insert', entity: tableName });
@@ -430,13 +461,33 @@ export async function insertBetweenRow(
430
461
 
431
462
  const PathKey = PathAttributeName as keyof object;
432
463
 
464
+ const AllPerms = new Set<RbacPermissionFlag>()
465
+ .add(RbacPermissionFlag.CREATE)
466
+ .add(RbacPermissionFlag.READ)
467
+ .add(RbacPermissionFlag.UPDATE)
468
+ .add(RbacPermissionFlag.DELETE);
469
+
433
470
  async function createOwnership(tableName: string, rows: object[], ctx: DbContext): Promise<void> {
471
+ await createLimitedOwnership(tableName, rows, ctx.authInfo.userId, AllPerms, ctx);
472
+ }
473
+
474
+ async function createLimitedOwnership(
475
+ tableName: string,
476
+ rows: object[],
477
+ userId: string,
478
+ perms: Set<RbacPermissionFlag>,
479
+ ctx: DbContext
480
+ ): Promise<void> {
434
481
  const ownerRows: object[] = [];
435
482
  rows.forEach((r: object) => {
436
483
  ownerRows.push({
437
484
  id: crypto.randomUUID(),
438
485
  path: r[PathKey],
439
- user_id: ctx.authInfo.userId,
486
+ user_id: userId,
487
+ c: perms.has(RbacPermissionFlag.CREATE),
488
+ r: perms.has(RbacPermissionFlag.READ),
489
+ d: perms.has(RbacPermissionFlag.DELETE),
490
+ u: perms.has(RbacPermissionFlag.UPDATE),
440
491
  });
441
492
  });
442
493
  const tname = ownersTable(tableName);
@@ -6,6 +6,7 @@ import {
6
6
  getAllBetweenRelationships,
7
7
  getAllOneToOneRelationshipsForEntity,
8
8
  getBetweenInstanceNodeValues,
9
+ getEntityRbacRules,
9
10
  Instance,
10
11
  InstanceAttributes,
11
12
  isBetweenRelationship,
@@ -72,7 +73,8 @@ export class SqlDbResolver extends Resolver {
72
73
  this.authInfo,
73
74
  activeEnv,
74
75
  this.txnId,
75
- activeEnv.isInKernelMode()
76
+ activeEnv.isInKernelMode(),
77
+ getEntityRbacRules(resourceFqName)
76
78
  );
77
79
  }
78
80
 
@@ -151,12 +153,11 @@ export class SqlDbResolver extends Resolver {
151
153
  let result = SqlDbResolver.EmptyResultSet;
152
154
 
153
155
  const tableName = asTableName(inst.moduleName, inst.name);
154
- const rslt: any = await getMany(
155
- tableName,
156
- queryAll ? undefined : inst.queryAttributesAsObject(),
157
- queryAll ? undefined : inst.queryAttributeValuesAsObject(),
158
- this.getDbContext(inst.getFqName())
159
- );
156
+ const fqName = inst.getFqName();
157
+ const ctx = this.getDbContext(fqName);
158
+ const qattrs: any = queryAll ? undefined : inst.queryAttributesAsObject();
159
+ const qvals: any = queryAll ? undefined : inst.queryAttributeValuesAsObject();
160
+ const rslt: any = await getMany(tableName, qattrs, qvals, ctx);
160
161
  if (rslt instanceof Array) {
161
162
  result = new Array<Instance>();
162
163
  rslt.forEach((r: object) => {
@@ -528,3 +528,15 @@ export function walkDownInstancePath(path: string): [string, string, string | un
528
528
  }
529
529
  return [moduleName, entryName, undefined, parts];
530
530
  }
531
+
532
+ export function areSetsEqual<T>(set1: Set<T>, set2: Set<T>): boolean {
533
+ if (set1.size !== set2.size) {
534
+ return false;
535
+ }
536
+ for (const item of set1) {
537
+ if (!set2.has(item)) {
538
+ return false;
539
+ }
540
+ }
541
+ return true;
542
+ }