@exulu/backend 3.3.1 → 3.5.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.
@@ -3,7 +3,7 @@ import {
3
3
  __resetLiteLLMCatalogCacheForTesting,
4
4
  fetchLiteLLMCatalog,
5
5
  findLiteLLMModel
6
- } from "./chunk-7CCMW3IW.js";
6
+ } from "./chunk-4PDWNVNT.js";
7
7
  export {
8
8
  __resetLiteLLMCatalogCacheForTesting,
9
9
  fetchLiteLLMCatalog,
@@ -1,24 +1,73 @@
1
1
  import "dotenv/config";
2
2
 
3
+ // src/exulu/litellm/env.ts
4
+ var LiteLLMAdminError = class extends Error {
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ this.name = "LiteLLMAdminError";
9
+ }
10
+ };
11
+ function litellmBase() {
12
+ const host = process.env.LITELLM_HOST ?? "127.0.0.1";
13
+ const port = process.env.LITELLM_PORT ?? "4000";
14
+ const masterKey = process.env.LITELLM_MASTER_KEY;
15
+ if (!masterKey) {
16
+ throw new LiteLLMAdminError("LITELLM_MASTER_KEY is not configured.");
17
+ }
18
+ return { url: `http://${host}:${port}`, masterKey };
19
+ }
20
+ var _clientMode = false;
21
+ var isLiteLLMClientMode = () => _clientMode;
22
+ var setLiteLLMClientMode = (value) => {
23
+ _clientMode = value;
24
+ };
25
+ function resolveLiteLLMTarget() {
26
+ const rawBase = process.env.LITELLM_BASE_URL;
27
+ if (isLiteLLMClientMode() && rawBase && rawBase.trim().length > 0) {
28
+ const apiKey = process.env.EXULU_API_KEY;
29
+ if (!apiKey) {
30
+ throw new Error("EXULU_API_KEY is required when LITELLM_BASE_URL is set (remote LiteLLM client mode).");
31
+ }
32
+ return {
33
+ baseUrl: rawBase.trim().replace(/\/+$/, ""),
34
+ authHeaders: { "exulu-api-key": apiKey },
35
+ remote: true
36
+ };
37
+ }
38
+ const host = process.env.LITELLM_HOST ?? "127.0.0.1";
39
+ const port = process.env.LITELLM_PORT ?? "4000";
40
+ const masterKey = process.env.LITELLM_MASTER_KEY;
41
+ return {
42
+ baseUrl: `http://${host}:${port}`,
43
+ authHeaders: masterKey ? { Authorization: `Bearer ${masterKey}` } : {},
44
+ remote: false
45
+ };
46
+ }
47
+
3
48
  // src/exulu/litellm/catalog.ts
4
49
  var CACHE_TTL_MS = 3e4;
5
50
  var _cache;
6
51
  var __resetLiteLLMCatalogCacheForTesting = () => {
7
52
  _cache = void 0;
8
53
  };
9
- var fetchLiteLLMCatalog = async () => {
54
+ var fetchFullCatalog = async () => {
10
55
  if (process.env.EXULU_USE_LITELLM !== "true") return [];
11
56
  if (_cache && _cache.expiresAt > Date.now()) {
12
57
  return _cache.items;
13
58
  }
14
- const host = process.env.LITELLM_HOST ?? "127.0.0.1";
15
- const port = process.env.LITELLM_PORT ?? "4000";
16
- const masterKey = process.env.LITELLM_MASTER_KEY;
17
- if (!masterKey) return [];
59
+ let baseUrl;
60
+ let authHeaders;
18
61
  try {
19
- const res = await fetch(`http://${host}:${port}/model/info`, {
62
+ ({ baseUrl, authHeaders } = resolveLiteLLMTarget());
63
+ } catch {
64
+ return [];
65
+ }
66
+ if (Object.keys(authHeaders).length === 0) return [];
67
+ try {
68
+ const res = await fetch(`${baseUrl}/model/info`, {
20
69
  method: "GET",
21
- headers: { Authorization: `Bearer ${masterKey}` }
70
+ headers: authHeaders
22
71
  });
23
72
  if (!res.ok) {
24
73
  console.error(
@@ -61,19 +110,28 @@ var fetchLiteLLMCatalog = async () => {
61
110
  }
62
111
  const uniqueItems = Array.from(map.values());
63
112
  _cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
64
- return uniqueItems.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
113
+ return uniqueItems;
65
114
  } catch (err) {
66
115
  console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
67
116
  return [];
68
117
  }
69
118
  };
119
+ var fetchLiteLLMCatalog = async () => {
120
+ const items = await fetchFullCatalog();
121
+ return items.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
122
+ };
70
123
  var findLiteLLMModel = async (modelName) => {
71
124
  if (!modelName) return void 0;
72
- const items = await fetchLiteLLMCatalog();
125
+ const items = await fetchFullCatalog();
73
126
  return items.find((m) => m.model_name === modelName);
74
127
  };
75
128
 
76
129
  export {
130
+ LiteLLMAdminError,
131
+ litellmBase,
132
+ isLiteLLMClientMode,
133
+ setLiteLLMClientMode,
134
+ resolveLiteLLMTarget,
77
135
  __resetLiteLLMCatalogCacheForTesting,
78
136
  fetchLiteLLMCatalog,
79
137
  findLiteLLMModel
@@ -1,7 +1,12 @@
1
1
  import "dotenv/config";
2
2
  import {
3
- findLiteLLMModel
4
- } from "./chunk-7CCMW3IW.js";
3
+ LiteLLMAdminError,
4
+ findLiteLLMModel,
5
+ isLiteLLMClientMode,
6
+ litellmBase,
7
+ resolveLiteLLMTarget,
8
+ setLiteLLMClientMode
9
+ } from "./chunk-4PDWNVNT.js";
5
10
 
6
11
  // src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
7
12
  import { S3Client as S3Client3, PutObjectCommand as PutObjectCommand3, S3ServiceException } from "@aws-sdk/client-s3";
@@ -166,13 +171,12 @@ var supervise = async (cfg) => {
166
171
  }
167
172
  };
168
173
  var _packageRoot;
169
- var _clientMode = false;
170
174
  var setLiteLLMPackageRoot = (root) => {
171
175
  _packageRoot = root;
172
176
  };
173
177
  var enableLiteLLMClientMode = () => {
174
178
  if (internal.readyPromise) return;
175
- _clientMode = true;
179
+ setLiteLLMClientMode(true);
176
180
  };
177
181
  var startLiteLLMSupervisor = async (options = {}) => {
178
182
  if (!isLiteLLMEnabled()) return;
@@ -222,14 +226,13 @@ var startLiteLLMSupervisor = async (options = {}) => {
222
226
  };
223
227
  var waitForLiteLLMReady = async () => {
224
228
  if (!isLiteLLMEnabled()) return;
225
- if (_clientMode) {
229
+ if (isLiteLLMClientMode()) {
226
230
  if (internal.state === "ready") return;
227
- const host = process.env.LITELLM_HOST ?? "127.0.0.1";
228
- const port = process.env.LITELLM_PORT ?? "4000";
229
- const url = `http://${host}:${port}/health/liveliness`;
231
+ const { baseUrl, authHeaders, remote } = resolveLiteLLMTarget();
232
+ const url = remote ? `${baseUrl}/v1/models` : `${baseUrl}/health/liveliness`;
230
233
  let res;
231
234
  try {
232
- res = await fetch(url, { method: "GET" });
235
+ res = await fetch(url, { method: "GET", headers: remote ? authHeaders : {} });
233
236
  } catch (err) {
234
237
  throw new Error(
235
238
  `LiteLLM proxy not reachable at ${url} (is the Exulu server process running?): ${err.message}`
@@ -531,24 +534,6 @@ async function postgresClient() {
531
534
  };
532
535
  }
533
536
 
534
- // src/exulu/litellm/env.ts
535
- var LiteLLMAdminError = class extends Error {
536
- constructor(message, status) {
537
- super(message);
538
- this.status = status;
539
- this.name = "LiteLLMAdminError";
540
- }
541
- };
542
- function litellmBase() {
543
- const host = process.env.LITELLM_HOST ?? "127.0.0.1";
544
- const port = process.env.LITELLM_PORT ?? "4000";
545
- const masterKey = process.env.LITELLM_MASTER_KEY;
546
- if (!masterKey) {
547
- throw new LiteLLMAdminError("LITELLM_MASTER_KEY is not configured.");
548
- }
549
- return { url: `http://${host}:${port}`, masterKey };
550
- }
551
-
552
537
  // src/exulu/litellm/admin-client.ts
553
538
  async function call(path3, body) {
554
539
  const { url, masterKey } = litellmBase();
@@ -1042,9 +1027,7 @@ var getLiteLLMProvider = ({
1042
1027
  team,
1043
1028
  routine
1044
1029
  }) => {
1045
- const host = process.env.LITELLM_HOST ?? "127.0.0.1";
1046
- const port = process.env.LITELLM_PORT ?? "4000";
1047
- const masterKey = process.env.LITELLM_MASTER_KEY;
1030
+ const { baseUrl, authHeaders } = resolveLiteLLMTarget();
1048
1031
  const tags = buildTags({
1049
1032
  user_id: user?.id,
1050
1033
  role_id: role?.id,
@@ -1059,16 +1042,13 @@ var getLiteLLMProvider = ({
1059
1042
  routine_id: routine?.id,
1060
1043
  routine_name: routine?.name
1061
1044
  });
1062
- if (!masterKey) {
1063
- throw new ResolveModelError(
1064
- "LITELLM_NOT_CONFIGURED",
1065
- "LITELLM_MASTER_KEY is required when EXULU_USE_LITELLM=true"
1066
- );
1067
- }
1068
1045
  return createOpenAICompatible({
1069
1046
  name: "litellm",
1070
- baseURL: `http://${host}:${port}/v1`,
1071
- apiKey: masterKey,
1047
+ baseURL: `${baseUrl}/v1`,
1048
+ // createOpenAICompatible requires a non-empty apiKey; real auth for remote mode is the
1049
+ // exulu-api-key header (added via `headers`). The passthrough strips/ignores Authorization.
1050
+ apiKey: process.env.LITELLM_MASTER_KEY ?? process.env.EXULU_API_KEY ?? "x",
1051
+ headers: authHeaders,
1072
1052
  fetch: createTaggedFetch(tags),
1073
1053
  // Without this flag the openai-compatible provider strips any
1074
1054
  // responseFormat.schema before sending and warns
@@ -1733,7 +1713,7 @@ var ExuluTool = class _ExuluTool {
1733
1713
  if (!agent) {
1734
1714
  throw new Error("Agent not found.");
1735
1715
  }
1736
- const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-WQWYMU7G.js");
1716
+ const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-TZ2UKWR4.js");
1737
1717
  const tools = await convertExuluToolsToAiSdkTools2(
1738
1718
  [this],
1739
1719
  [],
@@ -1939,7 +1919,6 @@ var checkLicense = () => {
1939
1919
  };
1940
1920
 
1941
1921
  // src/exulu/resolve-reranker.ts
1942
- import "fs";
1943
1922
  var ResolveRerankerError = class extends Error {
1944
1923
  constructor(code, message) {
1945
1924
  super(message);
@@ -1963,15 +1942,7 @@ async function resolveReranker(input) {
1963
1942
  `LiteLLM is not ready: ${err.message}`
1964
1943
  );
1965
1944
  }
1966
- const host = process.env.LITELLM_HOST ?? "127.0.0.1";
1967
- const port = process.env.LITELLM_PORT ?? "4000";
1968
- const masterKey = process.env.LITELLM_MASTER_KEY;
1969
- if (!masterKey) {
1970
- throw new ResolveRerankerError(
1971
- "LITELLM_NOT_CONFIGURED",
1972
- "LITELLM_MASTER_KEY is required when EXULU_USE_LITELLM=true"
1973
- );
1974
- }
1945
+ const { baseUrl, authHeaders } = resolveLiteLLMTarget();
1975
1946
  const resolvedUserId = user?.id ?? userId;
1976
1947
  if (resolvedUserId) await provisionDefaultUserBudget(resolvedUserId);
1977
1948
  const role = user?.role;
@@ -1991,7 +1962,7 @@ async function resolveReranker(input) {
1991
1962
  routine_name: routine?.name,
1992
1963
  context_name: contextName
1993
1964
  });
1994
- const endpoint = `http://${host}:${port}/v1/rerank`;
1965
+ const endpoint = `${baseUrl}/v1/rerank`;
1995
1966
  const rerank = async (query, chunks, opts) => {
1996
1967
  try {
1997
1968
  if (chunks.length === 0) return [];
@@ -2001,7 +1972,7 @@ async function resolveReranker(input) {
2001
1972
  const res = await fetch(endpoint, {
2002
1973
  method: "POST",
2003
1974
  headers: {
2004
- Authorization: `Bearer ${masterKey}`,
1975
+ ...authHeaders,
2005
1976
  "Content-Type": "application/json"
2006
1977
  },
2007
1978
  body: JSON.stringify({
@@ -4371,6 +4342,9 @@ var checkItemWriteAccess = async (context, record, user) => {
4371
4342
  if (record.rights_mode === "private") {
4372
4343
  return record.created_by != null && String(record.created_by) === String(user.id);
4373
4344
  }
4345
+ if (record.created_by != null && String(record.created_by) === String(user.id)) {
4346
+ return true;
4347
+ }
4374
4348
  const validRightsModes = ["users", "roles", "teams"];
4375
4349
  if (!validRightsModes.includes(record.rights_mode)) {
4376
4350
  return false;
@@ -7258,23 +7232,23 @@ ${JSON.stringify(config, null, 2)}`
7258
7232
 
7259
7233
  // src/exulu/audit/sink.ts
7260
7234
  import { randomUUID as randomUUID4 } from "crypto";
7261
- import { promises as fs2 } from "fs";
7235
+ import { promises as fs } from "fs";
7262
7236
  import path2 from "path";
7263
7237
  var createFsSpoolStore = (dir) => ({
7264
7238
  write: async (name, body) => {
7265
- await fs2.mkdir(dir, { recursive: true });
7266
- await fs2.writeFile(path2.join(dir, name), body, "utf8");
7239
+ await fs.mkdir(dir, { recursive: true });
7240
+ await fs.writeFile(path2.join(dir, name), body, "utf8");
7267
7241
  },
7268
7242
  list: async () => {
7269
7243
  try {
7270
- return (await fs2.readdir(dir)).filter((f) => f.endsWith(".ndjson"));
7244
+ return (await fs.readdir(dir)).filter((f) => f.endsWith(".ndjson"));
7271
7245
  } catch {
7272
7246
  return [];
7273
7247
  }
7274
7248
  },
7275
- read: async (name) => fs2.readFile(path2.join(dir, name), "utf8"),
7249
+ read: async (name) => fs.readFile(path2.join(dir, name), "utf8"),
7276
7250
  remove: async (name) => {
7277
- await fs2.rm(path2.join(dir, name), { force: true });
7251
+ await fs.rm(path2.join(dir, name), { force: true });
7278
7252
  }
7279
7253
  });
7280
7254
  var pad = (n) => String(n).padStart(2, "0");
@@ -8155,7 +8129,6 @@ export {
8155
8129
  buildTags,
8156
8130
  budgetTagFor,
8157
8131
  createTaggedFetch,
8158
- LiteLLMAdminError,
8159
8132
  tagDelete,
8160
8133
  tagInfo,
8161
8134
  getTagDailyActivity,
@@ -2,8 +2,8 @@ import "dotenv/config";
2
2
  import {
3
3
  convertExuluToolsToAiSdkTools,
4
4
  hydrateVariables
5
- } from "./chunk-5FTX543Z.js";
6
- import "./chunk-7CCMW3IW.js";
5
+ } from "./chunk-7AZH4ETH.js";
6
+ import "./chunk-4PDWNVNT.js";
7
7
  export {
8
8
  convertExuluToolsToAiSdkTools,
9
9
  hydrateVariables