@exulu/backend 1.62.0 → 1.63.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.
@@ -2,7 +2,7 @@ import {
2
2
  __resetLiteLLMCatalogCacheForTesting,
3
3
  fetchLiteLLMCatalog,
4
4
  findLiteLLMModel
5
- } from "./chunk-ILAHW4UT.js";
5
+ } from "./chunk-YCE44CMU.js";
6
6
  export {
7
7
  __resetLiteLLMCatalogCacheForTesting,
8
8
  fetchLiteLLMCatalog,
@@ -164,6 +164,7 @@ var checkRecordAccess = async (record, request, user) => {
164
164
  const isPublic = record.rights_mode === "public";
165
165
  const byUsers = record.rights_mode === "users";
166
166
  const byRoles = record.rights_mode === "roles";
167
+ const byTeams = record.rights_mode === "teams";
167
168
  const createdBy = typeof record.created_by === "string" ? record.created_by : record.created_by?.toString();
168
169
  const isCreator = user ? createdBy === user.id.toString() : false;
169
170
  const isAdmin = user ? user.super_admin : false;
@@ -209,6 +210,23 @@ var checkRecordAccess = async (record, request, user) => {
209
210
  return true;
210
211
  }
211
212
  }
213
+ if (byTeams) {
214
+ if (!user) {
215
+ setRecordAccessCache(false);
216
+ return false;
217
+ }
218
+ hasAccess = record.RBAC?.teams?.find((x) => x.id === user.team?.id)?.rights || "none";
219
+ if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
220
+ console.error(
221
+ `[EXULU] Your current team ${user.team?.name} does not have access to this record, current access type is: ${hasAccess}.`
222
+ );
223
+ setRecordAccessCache(false);
224
+ return false;
225
+ } else {
226
+ setRecordAccessCache(true);
227
+ return true;
228
+ }
229
+ }
212
230
  setRecordAccessCache(false);
213
231
  return false;
214
232
  };
@@ -220,7 +238,8 @@ import { resolve } from "path";
220
238
  var MAX_CRASHES = 5;
221
239
  var INITIAL_BACKOFF_MS = 1e3;
222
240
  var MAX_BACKOFF_MS = 3e4;
223
- var READY_TIMEOUT_MS = 3e4;
241
+ var READY_TIMEOUT_MS = 9e4;
242
+ var WAIT_TIMEOUT_MS = 6e4;
224
243
  var READY_POLL_INTERVAL_MS = 200;
225
244
  var SHUTDOWN_GRACE_MS = 5e3;
226
245
  var internal = {
@@ -394,10 +413,18 @@ var startLiteLLMSupervisor = async (options = {}) => {
394
413
  var waitForLiteLLMReady = async () => {
395
414
  if (!isLiteLLMEnabled()) return;
396
415
  if (!internal.readyPromise) {
397
- await startLiteLLMSupervisor();
398
- return;
416
+ return startLiteLLMSupervisor();
399
417
  }
400
- return internal.readyPromise;
418
+ const deadline = Date.now() + WAIT_TIMEOUT_MS;
419
+ while (Date.now() < deadline) {
420
+ const s = getSupervisorState();
421
+ if (s === "ready") return;
422
+ if (s === "given_up") {
423
+ throw new Error("LiteLLM supervisor has given up.");
424
+ }
425
+ await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
426
+ }
427
+ throw new Error("Timed out waiting for LiteLLM to become ready.");
401
428
  };
402
429
  var stopLiteLLM = (signal = "SIGTERM") => {
403
430
  internal.shutdownRequested = true;
@@ -424,6 +451,7 @@ var registerShutdownHandlers = () => {
424
451
  process.on("SIGTERM", () => stopLiteLLM("SIGTERM"));
425
452
  process.on("exit", () => stopLiteLLM("SIGTERM"));
426
453
  };
454
+ var getSupervisorState = () => internal.state;
427
455
 
428
456
  // src/exulu/tags.ts
429
457
  var MAX_LEN = 63;
@@ -458,6 +486,12 @@ function buildTags(input) {
458
486
  if (input.agent_name) {
459
487
  candidates.push("agent_name_" + input.agent_name);
460
488
  }
489
+ if (input.team_id) {
490
+ candidates.push("team_id_" + input.team_id);
491
+ }
492
+ if (input.team_name) {
493
+ candidates.push("team_name_" + input.team_name);
494
+ }
461
495
  console.log("[EXULU] Candidates", candidates);
462
496
  const out = [];
463
497
  for (const candidate of candidates) {
@@ -561,17 +595,24 @@ var getLiteLLMProvider = ({
561
595
  user,
562
596
  role,
563
597
  project,
564
- agent
598
+ agent,
599
+ team
565
600
  }) => {
566
601
  if (_litellmProvider) return _litellmProvider;
567
602
  const host = process.env.LITELLM_HOST ?? "127.0.0.1";
568
603
  const port = process.env.LITELLM_PORT ?? "4000";
569
604
  const masterKey = process.env.LITELLM_MASTER_KEY;
570
605
  const tags = buildTags({
571
- user,
572
- role,
573
- project,
574
- agent
606
+ user_id: user?.id,
607
+ role_id: role?.id,
608
+ project_id: project?.id,
609
+ agent_id: agent?.id,
610
+ user_name: !user ? void 0 : user.type === "api" ? user.firstname ?? user.email : user.email,
611
+ role_name: role?.name,
612
+ project_name: project?.name,
613
+ agent_name: agent?.name,
614
+ team_id: team?.id,
615
+ team_name: team?.name
575
616
  });
576
617
  if (!masterKey) {
577
618
  throw new ResolveModelError(
@@ -610,10 +651,11 @@ async function resolveModel(input) {
610
651
  );
611
652
  }
612
653
  const litellm = getLiteLLMProvider({
613
- user: user?.id,
614
- role: user?.role?.id,
615
- project: project?.id,
616
- agent: agent?.id
654
+ user,
655
+ role: user?.role,
656
+ project,
657
+ agent,
658
+ team: user?.team
617
659
  });
618
660
  const languageModel2 = litellm(modelId);
619
661
  const syntheticModel = {
@@ -755,12 +797,12 @@ var ExuluTool = class {
755
797
  modelId: agent.model,
756
798
  user,
757
799
  providers,
758
- agent: { id: agent.id },
800
+ agent,
759
801
  rbacBypass: true
760
802
  });
761
803
  providerapikey = resolved.apiKey;
762
804
  }
763
- const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-2HF7PPYW.js");
805
+ const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-THDKPKF3.js");
764
806
  const tools = await convertExuluToolsToAiSdkTools2(
765
807
  [this],
766
808
  [],
@@ -1223,6 +1265,12 @@ var authentication = async ({
1223
1265
  user.role = role;
1224
1266
  }
1225
1267
  }
1268
+ if (user?.team) {
1269
+ const team = await db2.from("teams").select("*").where("id", user?.team).first();
1270
+ if (team) {
1271
+ user.team = team;
1272
+ }
1273
+ }
1226
1274
  if (!user) {
1227
1275
  return {
1228
1276
  error: true,
@@ -2324,6 +2372,16 @@ var applyAccessControl = (table, query, user, field_prefix) => {
2324
2372
  });
2325
2373
  });
2326
2374
  }
2375
+ if (user?.team) {
2376
+ const userTeamId = user.team.id;
2377
+ this.orWhere(function() {
2378
+ this.where(`${prefix}rights_mode`, "teams").whereExists(function() {
2379
+ this.select("*").from("rbac").whereRaw(
2380
+ "rbac.target_resource_id = " + (prefix ? prefix.slice(0, -1) : tableNamePlural) + ".id"
2381
+ ).where("rbac.entity", table.name.singular).where("rbac.access_type", "Team").where("rbac.team_id", userTeamId);
2382
+ });
2383
+ });
2384
+ }
2327
2385
  });
2328
2386
  } catch (error) {
2329
2387
  console.error("Access control error:", error);
@@ -2536,6 +2594,26 @@ var rolesSchema = {
2536
2594
  }
2537
2595
  ]
2538
2596
  };
2597
+ var teamsSchema = {
2598
+ type: "teams",
2599
+ name: {
2600
+ plural: "teams",
2601
+ singular: "team"
2602
+ },
2603
+ fields: [
2604
+ {
2605
+ name: "name",
2606
+ type: "text",
2607
+ index: true,
2608
+ unique: true,
2609
+ required: true
2610
+ },
2611
+ {
2612
+ name: "description",
2613
+ type: "text"
2614
+ }
2615
+ ]
2616
+ };
2539
2617
  var statisticsSchema = {
2540
2618
  type: "tracking",
2541
2619
  name: {
@@ -2756,6 +2834,10 @@ var rbacSchema = {
2756
2834
  name: "role_id",
2757
2835
  type: "uuid"
2758
2836
  },
2837
+ {
2838
+ name: "team_id",
2839
+ type: "uuid"
2840
+ },
2759
2841
  {
2760
2842
  name: "user_id",
2761
2843
  type: "number"
@@ -3214,6 +3296,10 @@ var usersSchema = {
3214
3296
  {
3215
3297
  name: "role",
3216
3298
  type: "uuid"
3299
+ },
3300
+ {
3301
+ name: "team",
3302
+ type: "uuid"
3217
3303
  }
3218
3304
  ]
3219
3305
  };
@@ -3492,6 +3578,7 @@ var coreSchemas = {
3492
3578
  }
3493
3579
  if (license["rbac"]) {
3494
3580
  schemas.rolesSchema = () => addCoreFields(rolesSchema);
3581
+ schemas.teamsSchema = () => addCoreFields(teamsSchema);
3495
3582
  schemas.rbacSchema = () => addCoreFields(rbacSchema);
3496
3583
  }
3497
3584
  if (license["evals"]) {
@@ -35,6 +35,8 @@ var fetchLiteLLMCatalog = async () => {
35
35
  region: m.model_info?.region ?? null,
36
36
  max_tokens: m.model_info?.max_tokens ?? null,
37
37
  max_input_tokens: m.model_info?.max_input_tokens ?? null,
38
+ input_cost_per_million_tokens: m.model_info?.input_cost_per_token * 1e6,
39
+ output_cost_per_million_tokens: m.model_info?.output_cost_per_token * 1e6,
38
40
  active: m.model_info?.active ?? true,
39
41
  max_output_tokens: m.model_info?.max_output_tokens ?? null,
40
42
  supports_vision: !!m.model_info?.supports_vision,
@@ -46,8 +48,18 @@ var fetchLiteLLMCatalog = async () => {
46
48
  supports_edit: !!m.model_info?.supports_edit,
47
49
  max_n: typeof m.model_info?.max_n === "number" ? m.model_info.max_n : null
48
50
  }));
49
- _cache = { expiresAt: Date.now() + CACHE_TTL_MS, items };
50
- return items.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
51
+ const map = /* @__PURE__ */ new Map();
52
+ for (const item of items) {
53
+ const key = `${item.model_name}-${item.upstream_model}`;
54
+ if (map.has(key)) {
55
+ map.get(key).tags.push(...item.tags);
56
+ } else {
57
+ map.set(key, item);
58
+ }
59
+ }
60
+ const uniqueItems = Array.from(map.values());
61
+ _cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
62
+ return uniqueItems.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
51
63
  } catch (err) {
52
64
  console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
53
65
  return [];
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  convertExuluToolsToAiSdkTools
3
- } from "./chunk-LYNLQWXC.js";
3
+ } from "./chunk-IZOD2X2F.js";
4
4
  export {
5
5
  convertExuluToolsToAiSdkTools
6
6
  };