@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.
- package/dist/{catalog-UGTDNMDM.js → catalog-3QLU2G7R.js} +1 -1
- package/dist/{chunk-7CCMW3IW.js → chunk-4PDWNVNT.js} +67 -9
- package/dist/{chunk-5FTX543Z.js → chunk-7AZH4ETH.js} +31 -58
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-WQWYMU7G.js → convert-exulu-tools-to-ai-sdk-tools-TZ2UKWR4.js} +2 -2
- package/dist/index.cjs +233 -162
- package/dist/index.js +99 -62
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1233,8 +1233,60 @@ var init_table_names = __esm({
|
|
|
1233
1233
|
}
|
|
1234
1234
|
});
|
|
1235
1235
|
|
|
1236
|
+
// src/exulu/litellm/env.ts
|
|
1237
|
+
function litellmBase() {
|
|
1238
|
+
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
1239
|
+
const port = process.env.LITELLM_PORT ?? "4000";
|
|
1240
|
+
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
1241
|
+
if (!masterKey) {
|
|
1242
|
+
throw new LiteLLMAdminError("LITELLM_MASTER_KEY is not configured.");
|
|
1243
|
+
}
|
|
1244
|
+
return { url: `http://${host}:${port}`, masterKey };
|
|
1245
|
+
}
|
|
1246
|
+
function resolveLiteLLMTarget() {
|
|
1247
|
+
const rawBase = process.env.LITELLM_BASE_URL;
|
|
1248
|
+
if (isLiteLLMClientMode() && rawBase && rawBase.trim().length > 0) {
|
|
1249
|
+
const apiKey = process.env.EXULU_API_KEY;
|
|
1250
|
+
if (!apiKey) {
|
|
1251
|
+
throw new Error("EXULU_API_KEY is required when LITELLM_BASE_URL is set (remote LiteLLM client mode).");
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
baseUrl: rawBase.trim().replace(/\/+$/, ""),
|
|
1255
|
+
authHeaders: { "exulu-api-key": apiKey },
|
|
1256
|
+
remote: true
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
1260
|
+
const port = process.env.LITELLM_PORT ?? "4000";
|
|
1261
|
+
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
1262
|
+
return {
|
|
1263
|
+
baseUrl: `http://${host}:${port}`,
|
|
1264
|
+
authHeaders: masterKey ? { Authorization: `Bearer ${masterKey}` } : {},
|
|
1265
|
+
remote: false
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
var LiteLLMAdminError, _clientMode, isLiteLLMClientMode, setLiteLLMClientMode;
|
|
1269
|
+
var init_env = __esm({
|
|
1270
|
+
"src/exulu/litellm/env.ts"() {
|
|
1271
|
+
"use strict";
|
|
1272
|
+
init_cjs_shims();
|
|
1273
|
+
LiteLLMAdminError = class extends Error {
|
|
1274
|
+
constructor(message, status) {
|
|
1275
|
+
super(message);
|
|
1276
|
+
this.status = status;
|
|
1277
|
+
this.name = "LiteLLMAdminError";
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
_clientMode = false;
|
|
1281
|
+
isLiteLLMClientMode = () => _clientMode;
|
|
1282
|
+
setLiteLLMClientMode = (value) => {
|
|
1283
|
+
_clientMode = value;
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1236
1288
|
// src/exulu/litellm/supervisor.ts
|
|
1237
|
-
var import_node_child_process, import_node_fs3, import_node_path2, LITELLM_UI_PATH, MAX_CRASHES, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, READY_TIMEOUT_MS, WAIT_TIMEOUT_MS, READY_POLL_INTERVAL_MS, SHUTDOWN_GRACE_MS, internal, isLiteLLMEnabled, resolveConfig, log2, pollHealth, spawnLiteLLM, supervise, _packageRoot,
|
|
1289
|
+
var import_node_child_process, import_node_fs3, import_node_path2, LITELLM_UI_PATH, MAX_CRASHES, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, READY_TIMEOUT_MS, WAIT_TIMEOUT_MS, READY_POLL_INTERVAL_MS, SHUTDOWN_GRACE_MS, internal, isLiteLLMEnabled, resolveConfig, log2, pollHealth, spawnLiteLLM, supervise, _packageRoot, setLiteLLMPackageRoot, enableLiteLLMClientMode, startLiteLLMSupervisor, waitForLiteLLMReady, stopLiteLLM, shutdownHandlersRegistered, registerShutdownHandlers, getSupervisorState;
|
|
1238
1290
|
var init_supervisor = __esm({
|
|
1239
1291
|
"src/exulu/litellm/supervisor.ts"() {
|
|
1240
1292
|
"use strict";
|
|
@@ -1242,6 +1294,7 @@ var init_supervisor = __esm({
|
|
|
1242
1294
|
import_node_child_process = require("child_process");
|
|
1243
1295
|
import_node_fs3 = require("fs");
|
|
1244
1296
|
import_node_path2 = require("path");
|
|
1297
|
+
init_env();
|
|
1245
1298
|
LITELLM_UI_PATH = "/litellm-admin";
|
|
1246
1299
|
MAX_CRASHES = 5;
|
|
1247
1300
|
INITIAL_BACKOFF_MS = 1e3;
|
|
@@ -1368,13 +1421,12 @@ var init_supervisor = __esm({
|
|
|
1368
1421
|
internal.backoffMs = Math.min(internal.backoffMs * 2, MAX_BACKOFF_MS);
|
|
1369
1422
|
}
|
|
1370
1423
|
};
|
|
1371
|
-
_clientMode = false;
|
|
1372
1424
|
setLiteLLMPackageRoot = (root) => {
|
|
1373
1425
|
_packageRoot = root;
|
|
1374
1426
|
};
|
|
1375
1427
|
enableLiteLLMClientMode = () => {
|
|
1376
1428
|
if (internal.readyPromise) return;
|
|
1377
|
-
|
|
1429
|
+
setLiteLLMClientMode(true);
|
|
1378
1430
|
};
|
|
1379
1431
|
startLiteLLMSupervisor = async (options = {}) => {
|
|
1380
1432
|
if (!isLiteLLMEnabled()) return;
|
|
@@ -1424,14 +1476,13 @@ var init_supervisor = __esm({
|
|
|
1424
1476
|
};
|
|
1425
1477
|
waitForLiteLLMReady = async () => {
|
|
1426
1478
|
if (!isLiteLLMEnabled()) return;
|
|
1427
|
-
if (
|
|
1479
|
+
if (isLiteLLMClientMode()) {
|
|
1428
1480
|
if (internal.state === "ready") return;
|
|
1429
|
-
const
|
|
1430
|
-
const
|
|
1431
|
-
const url = `http://${host}:${port}/health/liveliness`;
|
|
1481
|
+
const { baseUrl, authHeaders, remote } = resolveLiteLLMTarget();
|
|
1482
|
+
const url = remote ? `${baseUrl}/v1/models` : `${baseUrl}/health/liveliness`;
|
|
1432
1483
|
let res;
|
|
1433
1484
|
try {
|
|
1434
|
-
res = await fetch(url, { method: "GET" });
|
|
1485
|
+
res = await fetch(url, { method: "GET", headers: remote ? authHeaders : {} });
|
|
1435
1486
|
} catch (err) {
|
|
1436
1487
|
throw new Error(
|
|
1437
1488
|
`LiteLLM proxy not reachable at ${url} (is the Exulu server process running?): ${err.message}`
|
|
@@ -1625,31 +1676,6 @@ var init_tags = __esm({
|
|
|
1625
1676
|
}
|
|
1626
1677
|
});
|
|
1627
1678
|
|
|
1628
|
-
// src/exulu/litellm/env.ts
|
|
1629
|
-
function litellmBase() {
|
|
1630
|
-
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
1631
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
1632
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
1633
|
-
if (!masterKey) {
|
|
1634
|
-
throw new LiteLLMAdminError("LITELLM_MASTER_KEY is not configured.");
|
|
1635
|
-
}
|
|
1636
|
-
return { url: `http://${host}:${port}`, masterKey };
|
|
1637
|
-
}
|
|
1638
|
-
var LiteLLMAdminError;
|
|
1639
|
-
var init_env = __esm({
|
|
1640
|
-
"src/exulu/litellm/env.ts"() {
|
|
1641
|
-
"use strict";
|
|
1642
|
-
init_cjs_shims();
|
|
1643
|
-
LiteLLMAdminError = class extends Error {
|
|
1644
|
-
constructor(message, status) {
|
|
1645
|
-
super(message);
|
|
1646
|
-
this.status = status;
|
|
1647
|
-
this.name = "LiteLLMAdminError";
|
|
1648
|
-
}
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
});
|
|
1652
|
-
|
|
1653
1679
|
// src/exulu/litellm/admin-client.ts
|
|
1654
1680
|
async function call(path4, body) {
|
|
1655
1681
|
const { url, masterKey } = litellmBase();
|
|
@@ -2285,6 +2311,7 @@ var init_resolve_model = __esm({
|
|
|
2285
2311
|
init_supervisor();
|
|
2286
2312
|
init_tags();
|
|
2287
2313
|
init_budget_service();
|
|
2314
|
+
init_env();
|
|
2288
2315
|
ResolveModelError = class extends Error {
|
|
2289
2316
|
constructor(code, message) {
|
|
2290
2317
|
super(message);
|
|
@@ -2300,9 +2327,7 @@ var init_resolve_model = __esm({
|
|
|
2300
2327
|
team,
|
|
2301
2328
|
routine
|
|
2302
2329
|
}) => {
|
|
2303
|
-
const
|
|
2304
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
2305
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
2330
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
2306
2331
|
const tags = buildTags({
|
|
2307
2332
|
user_id: user?.id,
|
|
2308
2333
|
role_id: role?.id,
|
|
@@ -2317,16 +2342,13 @@ var init_resolve_model = __esm({
|
|
|
2317
2342
|
routine_id: routine?.id,
|
|
2318
2343
|
routine_name: routine?.name
|
|
2319
2344
|
});
|
|
2320
|
-
if (!masterKey) {
|
|
2321
|
-
throw new ResolveModelError(
|
|
2322
|
-
"LITELLM_NOT_CONFIGURED",
|
|
2323
|
-
"LITELLM_MASTER_KEY is required when EXULU_USE_LITELLM=true"
|
|
2324
|
-
);
|
|
2325
|
-
}
|
|
2326
2345
|
return (0, import_openai_compatible.createOpenAICompatible)({
|
|
2327
2346
|
name: "litellm",
|
|
2328
|
-
baseURL:
|
|
2329
|
-
apiKey
|
|
2347
|
+
baseURL: `${baseUrl}/v1`,
|
|
2348
|
+
// createOpenAICompatible requires a non-empty apiKey; real auth for remote mode is the
|
|
2349
|
+
// exulu-api-key header (added via `headers`). The passthrough strips/ignores Authorization.
|
|
2350
|
+
apiKey: process.env.LITELLM_MASTER_KEY ?? process.env.EXULU_API_KEY ?? "x",
|
|
2351
|
+
headers: authHeaders,
|
|
2330
2352
|
fetch: createTaggedFetch(tags),
|
|
2331
2353
|
// Without this flag the openai-compatible provider strips any
|
|
2332
2354
|
// responseFormat.schema before sending and warns
|
|
@@ -3320,6 +3342,9 @@ var init_check_item_write_access = __esm({
|
|
|
3320
3342
|
if (record.rights_mode === "private") {
|
|
3321
3343
|
return record.created_by != null && String(record.created_by) === String(user.id);
|
|
3322
3344
|
}
|
|
3345
|
+
if (record.created_by != null && String(record.created_by) === String(user.id)) {
|
|
3346
|
+
return true;
|
|
3347
|
+
}
|
|
3323
3348
|
const validRightsModes = ["users", "roles", "teams"];
|
|
3324
3349
|
if (!validRightsModes.includes(record.rights_mode)) {
|
|
3325
3350
|
return false;
|
|
@@ -4958,28 +4983,33 @@ __export(catalog_exports, {
|
|
|
4958
4983
|
fetchLiteLLMCatalog: () => fetchLiteLLMCatalog,
|
|
4959
4984
|
findLiteLLMModel: () => findLiteLLMModel
|
|
4960
4985
|
});
|
|
4961
|
-
var CACHE_TTL_MS, _cache, __resetLiteLLMCatalogCacheForTesting, fetchLiteLLMCatalog, findLiteLLMModel;
|
|
4986
|
+
var CACHE_TTL_MS, _cache, __resetLiteLLMCatalogCacheForTesting, fetchFullCatalog, fetchLiteLLMCatalog, findLiteLLMModel;
|
|
4962
4987
|
var init_catalog = __esm({
|
|
4963
4988
|
"src/exulu/litellm/catalog.ts"() {
|
|
4964
4989
|
"use strict";
|
|
4965
4990
|
init_cjs_shims();
|
|
4991
|
+
init_env();
|
|
4966
4992
|
CACHE_TTL_MS = 3e4;
|
|
4967
4993
|
__resetLiteLLMCatalogCacheForTesting = () => {
|
|
4968
4994
|
_cache = void 0;
|
|
4969
4995
|
};
|
|
4970
|
-
|
|
4996
|
+
fetchFullCatalog = async () => {
|
|
4971
4997
|
if (process.env.EXULU_USE_LITELLM !== "true") return [];
|
|
4972
4998
|
if (_cache && _cache.expiresAt > Date.now()) {
|
|
4973
4999
|
return _cache.items;
|
|
4974
5000
|
}
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
5001
|
+
let baseUrl;
|
|
5002
|
+
let authHeaders;
|
|
5003
|
+
try {
|
|
5004
|
+
({ baseUrl, authHeaders } = resolveLiteLLMTarget());
|
|
5005
|
+
} catch {
|
|
5006
|
+
return [];
|
|
5007
|
+
}
|
|
5008
|
+
if (Object.keys(authHeaders).length === 0) return [];
|
|
4979
5009
|
try {
|
|
4980
|
-
const res = await fetch(
|
|
5010
|
+
const res = await fetch(`${baseUrl}/model/info`, {
|
|
4981
5011
|
method: "GET",
|
|
4982
|
-
headers:
|
|
5012
|
+
headers: authHeaders
|
|
4983
5013
|
});
|
|
4984
5014
|
if (!res.ok) {
|
|
4985
5015
|
console.error(
|
|
@@ -5022,15 +5052,19 @@ var init_catalog = __esm({
|
|
|
5022
5052
|
}
|
|
5023
5053
|
const uniqueItems = Array.from(map.values());
|
|
5024
5054
|
_cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
|
|
5025
|
-
return uniqueItems
|
|
5055
|
+
return uniqueItems;
|
|
5026
5056
|
} catch (err) {
|
|
5027
5057
|
console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
|
|
5028
5058
|
return [];
|
|
5029
5059
|
}
|
|
5030
5060
|
};
|
|
5061
|
+
fetchLiteLLMCatalog = async () => {
|
|
5062
|
+
const items = await fetchFullCatalog();
|
|
5063
|
+
return items.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
|
|
5064
|
+
};
|
|
5031
5065
|
findLiteLLMModel = async (modelName) => {
|
|
5032
5066
|
if (!modelName) return void 0;
|
|
5033
|
-
const items = await
|
|
5067
|
+
const items = await fetchFullCatalog();
|
|
5034
5068
|
return items.find((m) => m.model_name === modelName);
|
|
5035
5069
|
};
|
|
5036
5070
|
}
|
|
@@ -6564,15 +6598,7 @@ async function resolveReranker(input) {
|
|
|
6564
6598
|
`LiteLLM is not ready: ${err.message}`
|
|
6565
6599
|
);
|
|
6566
6600
|
}
|
|
6567
|
-
const
|
|
6568
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
6569
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
6570
|
-
if (!masterKey) {
|
|
6571
|
-
throw new ResolveRerankerError(
|
|
6572
|
-
"LITELLM_NOT_CONFIGURED",
|
|
6573
|
-
"LITELLM_MASTER_KEY is required when EXULU_USE_LITELLM=true"
|
|
6574
|
-
);
|
|
6575
|
-
}
|
|
6601
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
6576
6602
|
const resolvedUserId = user?.id ?? userId;
|
|
6577
6603
|
if (resolvedUserId) await provisionDefaultUserBudget(resolvedUserId);
|
|
6578
6604
|
const role = user?.role;
|
|
@@ -6592,7 +6618,7 @@ async function resolveReranker(input) {
|
|
|
6592
6618
|
routine_name: routine?.name,
|
|
6593
6619
|
context_name: contextName
|
|
6594
6620
|
});
|
|
6595
|
-
const endpoint =
|
|
6621
|
+
const endpoint = `${baseUrl}/v1/rerank`;
|
|
6596
6622
|
const rerank2 = async (query, chunks, opts) => {
|
|
6597
6623
|
try {
|
|
6598
6624
|
if (chunks.length === 0) return [];
|
|
@@ -6602,7 +6628,7 @@ async function resolveReranker(input) {
|
|
|
6602
6628
|
const res = await fetch(endpoint, {
|
|
6603
6629
|
method: "POST",
|
|
6604
6630
|
headers: {
|
|
6605
|
-
|
|
6631
|
+
...authHeaders,
|
|
6606
6632
|
"Content-Type": "application/json"
|
|
6607
6633
|
},
|
|
6608
6634
|
body: JSON.stringify({
|
|
@@ -6643,7 +6669,7 @@ async function resolveReranker(input) {
|
|
|
6643
6669
|
};
|
|
6644
6670
|
return { model, rerank: rerank2 };
|
|
6645
6671
|
}
|
|
6646
|
-
var
|
|
6672
|
+
var ResolveRerankerError;
|
|
6647
6673
|
var init_resolve_reranker = __esm({
|
|
6648
6674
|
"src/exulu/resolve-reranker.ts"() {
|
|
6649
6675
|
"use strict";
|
|
@@ -6651,7 +6677,7 @@ var init_resolve_reranker = __esm({
|
|
|
6651
6677
|
init_supervisor();
|
|
6652
6678
|
init_budget_service();
|
|
6653
6679
|
init_tags();
|
|
6654
|
-
|
|
6680
|
+
init_env();
|
|
6655
6681
|
ResolveRerankerError = class extends Error {
|
|
6656
6682
|
constructor(code, message) {
|
|
6657
6683
|
super(message);
|
|
@@ -10019,6 +10045,7 @@ init_cjs_shims();
|
|
|
10019
10045
|
init_supervisor();
|
|
10020
10046
|
init_budget_service();
|
|
10021
10047
|
init_tags();
|
|
10048
|
+
init_env();
|
|
10022
10049
|
var ResolveEmbedderError = class extends Error {
|
|
10023
10050
|
constructor(code, message) {
|
|
10024
10051
|
super(message);
|
|
@@ -10042,15 +10069,7 @@ async function resolveEmbedder(input) {
|
|
|
10042
10069
|
`LiteLLM is not ready: ${err.message}`
|
|
10043
10070
|
);
|
|
10044
10071
|
}
|
|
10045
|
-
const
|
|
10046
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
10047
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
10048
|
-
if (!masterKey) {
|
|
10049
|
-
throw new ResolveEmbedderError(
|
|
10050
|
-
"LITELLM_NOT_CONFIGURED",
|
|
10051
|
-
"LITELLM_MASTER_KEY is required when EXULU_USE_LITELLM=true"
|
|
10052
|
-
);
|
|
10053
|
-
}
|
|
10072
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
10054
10073
|
const resolvedUserId = user?.id ?? userId;
|
|
10055
10074
|
if (resolvedUserId) await provisionDefaultUserBudget(resolvedUserId);
|
|
10056
10075
|
const { dimensionality, maxChunkSize, maxBatchSize } = getEmbeddingModelInfo(model);
|
|
@@ -10071,12 +10090,12 @@ async function resolveEmbedder(input) {
|
|
|
10071
10090
|
routine_name: routine?.name,
|
|
10072
10091
|
context_name: contextName
|
|
10073
10092
|
});
|
|
10074
|
-
const endpoint =
|
|
10093
|
+
const endpoint = `${baseUrl}/v1/embeddings`;
|
|
10075
10094
|
const embedBatch = async (batch) => {
|
|
10076
10095
|
const res = await fetch(endpoint, {
|
|
10077
10096
|
method: "POST",
|
|
10078
10097
|
headers: {
|
|
10079
|
-
|
|
10098
|
+
...authHeaders,
|
|
10080
10099
|
"Content-Type": "application/json"
|
|
10081
10100
|
},
|
|
10082
10101
|
body: JSON.stringify({
|
|
@@ -15120,20 +15139,25 @@ async function encryptString(string) {
|
|
|
15120
15139
|
const hash = await import_bcryptjs2.default.hash(string, SALT_ROUNDS);
|
|
15121
15140
|
return hash;
|
|
15122
15141
|
}
|
|
15123
|
-
var generateApiKey = async (name, email) => {
|
|
15142
|
+
var generateApiKey = async (name, email, options) => {
|
|
15124
15143
|
const { db: db2 } = await postgresClient();
|
|
15125
15144
|
email = String(email).trim().toLowerCase();
|
|
15145
|
+
const superAdmin = options?.superAdmin ?? true;
|
|
15146
|
+
const roleName = options?.roleName ?? "admin";
|
|
15147
|
+
const rolePermissions = options?.rolePermissions ?? {
|
|
15148
|
+
agents: "write",
|
|
15149
|
+
workflows: "write",
|
|
15150
|
+
variables: "write",
|
|
15151
|
+
users: "write"
|
|
15152
|
+
};
|
|
15126
15153
|
console.log("[EXULU] Inserting default user and admin role.");
|
|
15127
|
-
const existingRole = await db2.from("roles").where({ name:
|
|
15154
|
+
const existingRole = await db2.from("roles").where({ name: roleName }).first();
|
|
15128
15155
|
let roleId;
|
|
15129
15156
|
if (!existingRole) {
|
|
15130
|
-
console.log(
|
|
15157
|
+
console.log(`[EXULU] Creating default ${roleName} role.`);
|
|
15131
15158
|
const role = await db2.from("roles").insert({
|
|
15132
|
-
name:
|
|
15133
|
-
|
|
15134
|
-
workflows: "write",
|
|
15135
|
-
variables: "write",
|
|
15136
|
-
users: "write"
|
|
15159
|
+
name: roleName,
|
|
15160
|
+
...rolePermissions
|
|
15137
15161
|
}).returning("id");
|
|
15138
15162
|
roleId = role[0].id;
|
|
15139
15163
|
} else {
|
|
@@ -15149,7 +15173,7 @@ var generateApiKey = async (name, email) => {
|
|
|
15149
15173
|
await db2.from("users").insert({
|
|
15150
15174
|
name,
|
|
15151
15175
|
email,
|
|
15152
|
-
super_admin:
|
|
15176
|
+
super_admin: superAdmin,
|
|
15153
15177
|
createdAt: /* @__PURE__ */ new Date(),
|
|
15154
15178
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
15155
15179
|
type: "api",
|
|
@@ -15735,6 +15759,9 @@ function createMutations(table, contexts, tools, config) {
|
|
|
15735
15759
|
}
|
|
15736
15760
|
throw new Error("Only the creator can edit this private record");
|
|
15737
15761
|
}
|
|
15762
|
+
if (record.created_by != null && String(record.created_by) === String(user.id)) {
|
|
15763
|
+
return true;
|
|
15764
|
+
}
|
|
15738
15765
|
if (record.rights_mode === "users") {
|
|
15739
15766
|
const rbacRecord = await db2.from("rbac").where({
|
|
15740
15767
|
entity: table.name.singular,
|
|
@@ -19681,7 +19708,7 @@ var mapRoutineRunRow = (row, routineById) => {
|
|
|
19681
19708
|
|
|
19682
19709
|
// src/graphql/schemas/index.ts
|
|
19683
19710
|
init_entitlements();
|
|
19684
|
-
var
|
|
19711
|
+
var import_fs2 = require("fs");
|
|
19685
19712
|
|
|
19686
19713
|
// src/exulu/transcription/service.ts
|
|
19687
19714
|
init_cjs_shims();
|
|
@@ -23636,7 +23663,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
|
|
|
23636
23663
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
23637
23664
|
var import_crypto_js7 = require("crypto-js");
|
|
23638
23665
|
var import_openai = require("openai");
|
|
23639
|
-
var
|
|
23666
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
23640
23667
|
var import_node_crypto15 = require("crypto");
|
|
23641
23668
|
var import_api2 = require("@opentelemetry/api");
|
|
23642
23669
|
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
@@ -23855,6 +23882,19 @@ ${summary}` }],
|
|
|
23855
23882
|
|
|
23856
23883
|
// src/exulu/transcribe.ts
|
|
23857
23884
|
init_cjs_shims();
|
|
23885
|
+
init_env();
|
|
23886
|
+
init_catalog();
|
|
23887
|
+
var TRANSCRIBE_SYSTEM_PROMPT = "You are a speech-to-text transcription engine. Detect the language actually spoken and transcribe it word-for-word in that same language. Never translate. Output only the transcript text \u2014 no quotes, labels, or commentary. If there is no intelligible speech, output nothing.";
|
|
23888
|
+
function isGeminiChatTranscriptionModel(entry) {
|
|
23889
|
+
const upstream = entry?.upstream_model ?? "";
|
|
23890
|
+
return /^vertex_ai\//i.test(upstream) && /gemini/i.test(upstream);
|
|
23891
|
+
}
|
|
23892
|
+
function cleanTranscript(raw) {
|
|
23893
|
+
let text = (raw ?? "").trim();
|
|
23894
|
+
const wrapped = text.match(/^(["'`])([\s\S]*)\1$/);
|
|
23895
|
+
if (wrapped) text = (wrapped[2] ?? "").trim();
|
|
23896
|
+
return text;
|
|
23897
|
+
}
|
|
23858
23898
|
var TranscriptionError = class extends Error {
|
|
23859
23899
|
constructor(upstreamStatus, message) {
|
|
23860
23900
|
super(message);
|
|
@@ -23863,12 +23903,16 @@ var TranscriptionError = class extends Error {
|
|
|
23863
23903
|
}
|
|
23864
23904
|
};
|
|
23865
23905
|
async function transcribeAudio(args) {
|
|
23866
|
-
const
|
|
23867
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
23868
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
23906
|
+
const target = resolveLiteLLMTarget();
|
|
23869
23907
|
const model = process.env.TRANSCRIPTION_MODEL;
|
|
23870
|
-
if (!masterKey) throw new Error("LITELLM_MASTER_KEY is not set");
|
|
23871
23908
|
if (!model) throw new Error("TRANSCRIPTION_MODEL is not set");
|
|
23909
|
+
const entry = await findLiteLLMModel(model).catch(() => void 0);
|
|
23910
|
+
if (isGeminiChatTranscriptionModel(entry)) {
|
|
23911
|
+
return transcribeViaChat(args, target, model);
|
|
23912
|
+
}
|
|
23913
|
+
return transcribeViaAudioEndpoint(args, target, model);
|
|
23914
|
+
}
|
|
23915
|
+
async function transcribeViaAudioEndpoint(args, target, model) {
|
|
23872
23916
|
const form = new FormData();
|
|
23873
23917
|
form.append(
|
|
23874
23918
|
"file",
|
|
@@ -23877,9 +23921,9 @@ async function transcribeAudio(args) {
|
|
|
23877
23921
|
);
|
|
23878
23922
|
form.append("model", model);
|
|
23879
23923
|
if (args.language) form.append("language", args.language);
|
|
23880
|
-
const res = await fetch(
|
|
23924
|
+
const res = await fetch(`${target.baseUrl}/v1/audio/transcriptions`, {
|
|
23881
23925
|
method: "POST",
|
|
23882
|
-
headers: {
|
|
23926
|
+
headers: { ...target.authHeaders },
|
|
23883
23927
|
body: form
|
|
23884
23928
|
});
|
|
23885
23929
|
if (!res.ok) {
|
|
@@ -23892,9 +23936,46 @@ async function transcribeAudio(args) {
|
|
|
23892
23936
|
const json = await res.json();
|
|
23893
23937
|
return { text: typeof json.text === "string" ? json.text : "" };
|
|
23894
23938
|
}
|
|
23939
|
+
async function transcribeViaChat(args, target, model) {
|
|
23940
|
+
const subtype = args.file.mimetype.replace(/^audio\//, "").split(";")[0];
|
|
23941
|
+
const format = (subtype && subtype.length > 0 ? subtype : "wav").toLowerCase();
|
|
23942
|
+
const body = {
|
|
23943
|
+
model,
|
|
23944
|
+
temperature: 0,
|
|
23945
|
+
reasoning_effort: "disable",
|
|
23946
|
+
messages: [
|
|
23947
|
+
{ role: "system", content: TRANSCRIBE_SYSTEM_PROMPT },
|
|
23948
|
+
{
|
|
23949
|
+
role: "user",
|
|
23950
|
+
content: [
|
|
23951
|
+
{ type: "text", text: "Transcribe this audio." },
|
|
23952
|
+
{
|
|
23953
|
+
type: "input_audio",
|
|
23954
|
+
input_audio: { data: args.file.buffer.toString("base64"), format }
|
|
23955
|
+
}
|
|
23956
|
+
]
|
|
23957
|
+
}
|
|
23958
|
+
]
|
|
23959
|
+
};
|
|
23960
|
+
const res = await fetch(`${target.baseUrl}/v1/chat/completions`, {
|
|
23961
|
+
method: "POST",
|
|
23962
|
+
headers: { ...target.authHeaders, "Content-Type": "application/json" },
|
|
23963
|
+
body: JSON.stringify(body)
|
|
23964
|
+
});
|
|
23965
|
+
if (!res.ok) {
|
|
23966
|
+
const errBody = await res.text().catch(() => "");
|
|
23967
|
+
throw new TranscriptionError(
|
|
23968
|
+
res.status,
|
|
23969
|
+
`LiteLLM transcription failed (status ${res.status}): ${errBody}`.trim()
|
|
23970
|
+
);
|
|
23971
|
+
}
|
|
23972
|
+
const json = await res.json();
|
|
23973
|
+
return { text: cleanTranscript(json.choices?.[0]?.message?.content) };
|
|
23974
|
+
}
|
|
23895
23975
|
|
|
23896
23976
|
// src/exulu/speech.ts
|
|
23897
23977
|
init_cjs_shims();
|
|
23978
|
+
init_env();
|
|
23898
23979
|
var SpeechError = class extends Error {
|
|
23899
23980
|
constructor(upstreamStatus, message) {
|
|
23900
23981
|
super(message);
|
|
@@ -23903,12 +23984,9 @@ var SpeechError = class extends Error {
|
|
|
23903
23984
|
}
|
|
23904
23985
|
};
|
|
23905
23986
|
async function synthesizeSpeech(args) {
|
|
23906
|
-
const
|
|
23907
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
23908
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
23987
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
23909
23988
|
const model = process.env.TTS_MODEL;
|
|
23910
23989
|
const voice = process.env.TTS_VOICE;
|
|
23911
|
-
if (!masterKey) throw new Error("LITELLM_MASTER_KEY is not set");
|
|
23912
23990
|
if (!model) throw new Error("TTS_MODEL is not set");
|
|
23913
23991
|
if (!voice) throw new Error("TTS_VOICE is not set");
|
|
23914
23992
|
const body = {
|
|
@@ -23917,10 +23995,10 @@ async function synthesizeSpeech(args) {
|
|
|
23917
23995
|
voice,
|
|
23918
23996
|
response_format: "mp3"
|
|
23919
23997
|
};
|
|
23920
|
-
const res = await fetch(
|
|
23998
|
+
const res = await fetch(`${baseUrl}/v1/audio/speech`, {
|
|
23921
23999
|
method: "POST",
|
|
23922
24000
|
headers: {
|
|
23923
|
-
|
|
24001
|
+
...authHeaders,
|
|
23924
24002
|
"Content-Type": "application/json"
|
|
23925
24003
|
},
|
|
23926
24004
|
body: JSON.stringify(body)
|
|
@@ -23938,6 +24016,7 @@ async function synthesizeSpeech(args) {
|
|
|
23938
24016
|
|
|
23939
24017
|
// src/exulu/image-generation.ts
|
|
23940
24018
|
init_cjs_shims();
|
|
24019
|
+
init_env();
|
|
23941
24020
|
var ImageGenerationError = class extends Error {
|
|
23942
24021
|
constructor(upstreamStatus, message) {
|
|
23943
24022
|
super(message);
|
|
@@ -23945,13 +24024,6 @@ var ImageGenerationError = class extends Error {
|
|
|
23945
24024
|
this.name = "ImageGenerationError";
|
|
23946
24025
|
}
|
|
23947
24026
|
};
|
|
23948
|
-
var resolveProxyConfig = () => {
|
|
23949
|
-
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
23950
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
23951
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
23952
|
-
if (!masterKey) throw new Error("LITELLM_MASTER_KEY is not set");
|
|
23953
|
-
return { host, port, masterKey };
|
|
23954
|
-
};
|
|
23955
24027
|
var normalizeDataEntries = async (data) => {
|
|
23956
24028
|
const out = [];
|
|
23957
24029
|
for (const entry of data) {
|
|
@@ -23988,7 +24060,7 @@ var normalizeDataEntries = async (data) => {
|
|
|
23988
24060
|
async function generateImage(args) {
|
|
23989
24061
|
if (!args.model) throw new Error("model is required");
|
|
23990
24062
|
if (!args.prompt) throw new Error("prompt is required");
|
|
23991
|
-
const
|
|
24063
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
23992
24064
|
const body = {
|
|
23993
24065
|
model: args.model,
|
|
23994
24066
|
prompt: args.prompt
|
|
@@ -23996,10 +24068,10 @@ async function generateImage(args) {
|
|
|
23996
24068
|
if (args.size) body.size = args.size;
|
|
23997
24069
|
if (args.quality) body.quality = args.quality;
|
|
23998
24070
|
if (args.n) body.n = args.n;
|
|
23999
|
-
const res = await fetch(
|
|
24071
|
+
const res = await fetch(`${baseUrl}/v1/images/generations`, {
|
|
24000
24072
|
method: "POST",
|
|
24001
24073
|
headers: {
|
|
24002
|
-
|
|
24074
|
+
...authHeaders,
|
|
24003
24075
|
"Content-Type": "application/json"
|
|
24004
24076
|
},
|
|
24005
24077
|
body: JSON.stringify(body),
|
|
@@ -24027,7 +24099,7 @@ async function editImage(args) {
|
|
|
24027
24099
|
if (!args.references || args.references.length === 0) {
|
|
24028
24100
|
throw new Error("at least one reference image is required");
|
|
24029
24101
|
}
|
|
24030
|
-
const
|
|
24102
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
24031
24103
|
const form = new FormData();
|
|
24032
24104
|
form.append("model", args.model);
|
|
24033
24105
|
form.append("prompt", args.prompt);
|
|
@@ -24049,9 +24121,9 @@ async function editImage(args) {
|
|
|
24049
24121
|
args.mask.filename
|
|
24050
24122
|
);
|
|
24051
24123
|
}
|
|
24052
|
-
const res = await fetch(
|
|
24124
|
+
const res = await fetch(`${baseUrl}/v1/images/edits`, {
|
|
24053
24125
|
method: "POST",
|
|
24054
|
-
headers: {
|
|
24126
|
+
headers: { ...authHeaders },
|
|
24055
24127
|
body: form,
|
|
24056
24128
|
signal: args.signal
|
|
24057
24129
|
});
|
|
@@ -24201,6 +24273,12 @@ var import_node_path9 = require("path");
|
|
|
24201
24273
|
init_tags();
|
|
24202
24274
|
init_admin_client();
|
|
24203
24275
|
init_env();
|
|
24276
|
+
|
|
24277
|
+
// src/exulu/litellm/passthrough-allowlist.ts
|
|
24278
|
+
init_cjs_shims();
|
|
24279
|
+
var isLiteLLMPassthroughPathAllowed = (path4) => path4.startsWith("/v1/") || path4 === "/model/info";
|
|
24280
|
+
|
|
24281
|
+
// src/exulu/routes.ts
|
|
24204
24282
|
init_activity_client();
|
|
24205
24283
|
init_budget_service();
|
|
24206
24284
|
|
|
@@ -24901,7 +24979,7 @@ var REQUEST_SIZE_LIMIT = "50mb";
|
|
|
24901
24979
|
var getExuluVersionNumber = async () => {
|
|
24902
24980
|
try {
|
|
24903
24981
|
const path4 = process.cwd();
|
|
24904
|
-
const packageJson =
|
|
24982
|
+
const packageJson = import_fs3.default.readFileSync(path4 + "/package.json", "utf8");
|
|
24905
24983
|
const packageData = JSON.parse(packageJson);
|
|
24906
24984
|
const exuluVersion = packageData.dependencies["@exulu/backend"];
|
|
24907
24985
|
console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
|
|
@@ -26453,7 +26531,7 @@ ${style.markdown}` : params.prompt;
|
|
|
26453
26531
|
return;
|
|
26454
26532
|
}
|
|
26455
26533
|
const user = authenticationResult.user;
|
|
26456
|
-
if (!req.path
|
|
26534
|
+
if (!isLiteLLMPassthroughPathAllowed(req.path)) {
|
|
26457
26535
|
res.status(403).json({
|
|
26458
26536
|
detail: `Path ${req.path} is not exposed through the Exulu LiteLLM proxy.`
|
|
26459
26537
|
});
|
|
@@ -29883,7 +29961,7 @@ init_cjs_shims();
|
|
|
29883
29961
|
var import_child_process = require("child_process");
|
|
29884
29962
|
var import_util = require("util");
|
|
29885
29963
|
var import_path3 = require("path");
|
|
29886
|
-
var
|
|
29964
|
+
var import_fs4 = require("fs");
|
|
29887
29965
|
var import_url = require("url");
|
|
29888
29966
|
var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
|
|
29889
29967
|
function getPackageRoot() {
|
|
@@ -29893,9 +29971,9 @@ function getPackageRoot() {
|
|
|
29893
29971
|
const maxAttempts = 10;
|
|
29894
29972
|
while (attempts < maxAttempts) {
|
|
29895
29973
|
const packageJsonPath = (0, import_path3.join)(currentDir, "package.json");
|
|
29896
|
-
if ((0,
|
|
29974
|
+
if ((0, import_fs4.existsSync)(packageJsonPath)) {
|
|
29897
29975
|
try {
|
|
29898
|
-
const packageJson = JSON.parse((0,
|
|
29976
|
+
const packageJson = JSON.parse((0, import_fs4.readFileSync)(packageJsonPath, "utf-8"));
|
|
29899
29977
|
if (packageJson.name === "@exulu/backend") {
|
|
29900
29978
|
return currentDir;
|
|
29901
29979
|
}
|
|
@@ -29922,7 +30000,7 @@ function isPythonEnvironmentSetup(packageRoot) {
|
|
|
29922
30000
|
const root = packageRoot ?? getPackageRoot();
|
|
29923
30001
|
const venvPath = getVenvPath(root);
|
|
29924
30002
|
const pythonPath = (0, import_path3.join)(venvPath, "bin", "python");
|
|
29925
|
-
return (0,
|
|
30003
|
+
return (0, import_fs4.existsSync)(venvPath) && (0, import_fs4.existsSync)(pythonPath);
|
|
29926
30004
|
}
|
|
29927
30005
|
async function setupPythonEnvironment(options = {}) {
|
|
29928
30006
|
const {
|
|
@@ -29943,7 +30021,7 @@ async function setupPythonEnvironment(options = {}) {
|
|
|
29943
30021
|
};
|
|
29944
30022
|
}
|
|
29945
30023
|
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
29946
|
-
if (!(0,
|
|
30024
|
+
if (!(0, import_fs4.existsSync)(setupScriptPath)) {
|
|
29947
30025
|
return {
|
|
29948
30026
|
success: false,
|
|
29949
30027
|
message: `Setup script not found at: ${setupScriptPath}`,
|
|
@@ -30025,13 +30103,13 @@ async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
|
30025
30103
|
const root = packageRoot ?? getPackageRoot();
|
|
30026
30104
|
const venvPath = getVenvPath(root);
|
|
30027
30105
|
const pythonPath = (0, import_path3.join)(venvPath, "bin", "python");
|
|
30028
|
-
if (!(0,
|
|
30106
|
+
if (!(0, import_fs4.existsSync)(venvPath)) {
|
|
30029
30107
|
return {
|
|
30030
30108
|
valid: false,
|
|
30031
30109
|
message: getPythonSetupInstructions()
|
|
30032
30110
|
};
|
|
30033
30111
|
}
|
|
30034
|
-
if (!(0,
|
|
30112
|
+
if (!(0, import_fs4.existsSync)(pythonPath)) {
|
|
30035
30113
|
return {
|
|
30036
30114
|
valid: false,
|
|
30037
30115
|
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
@@ -32581,7 +32659,7 @@ var MarkdownChunker = class {
|
|
|
32581
32659
|
|
|
32582
32660
|
// ee/python/documents/processing/doc_processor.ts
|
|
32583
32661
|
init_cjs_shims();
|
|
32584
|
-
var
|
|
32662
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
32585
32663
|
var path3 = __toESM(require("path"), 1);
|
|
32586
32664
|
var import_ai14 = require("ai");
|
|
32587
32665
|
var import_zod27 = require("zod");
|
|
@@ -32599,7 +32677,7 @@ init_cjs_shims();
|
|
|
32599
32677
|
var import_child_process2 = require("child_process");
|
|
32600
32678
|
var import_util3 = require("util");
|
|
32601
32679
|
var import_path4 = require("path");
|
|
32602
|
-
var
|
|
32680
|
+
var import_fs5 = require("fs");
|
|
32603
32681
|
var import_url2 = require("url");
|
|
32604
32682
|
var execAsync5 = (0, import_util3.promisify)(import_child_process2.exec);
|
|
32605
32683
|
function getPackageRoot2() {
|
|
@@ -32609,9 +32687,9 @@ function getPackageRoot2() {
|
|
|
32609
32687
|
const maxAttempts = 10;
|
|
32610
32688
|
while (attempts < maxAttempts) {
|
|
32611
32689
|
const packageJsonPath = (0, import_path4.join)(currentDir, "package.json");
|
|
32612
|
-
if ((0,
|
|
32690
|
+
if ((0, import_fs5.existsSync)(packageJsonPath)) {
|
|
32613
32691
|
try {
|
|
32614
|
-
const packageJson = JSON.parse((0,
|
|
32692
|
+
const packageJson = JSON.parse((0, import_fs5.readFileSync)(packageJsonPath, "utf-8"));
|
|
32615
32693
|
if (packageJson.name === "@exulu/backend") {
|
|
32616
32694
|
return currentDir;
|
|
32617
32695
|
}
|
|
@@ -32673,7 +32751,7 @@ async function executePythonScript(config) {
|
|
|
32673
32751
|
await validatePythonEnvironmentForExecution(packageRoot);
|
|
32674
32752
|
}
|
|
32675
32753
|
const resolvedScriptPath = (0, import_path4.resolve)(packageRoot, scriptPath);
|
|
32676
|
-
if (!(0,
|
|
32754
|
+
if (!(0, import_fs5.existsSync)(resolvedScriptPath)) {
|
|
32677
32755
|
throw new PythonExecutionError(
|
|
32678
32756
|
`Python script not found: ${resolvedScriptPath}`,
|
|
32679
32757
|
"",
|
|
@@ -32743,6 +32821,7 @@ init_cjs_shims();
|
|
|
32743
32821
|
init_supervisor();
|
|
32744
32822
|
init_budget_service();
|
|
32745
32823
|
init_tags();
|
|
32824
|
+
init_env();
|
|
32746
32825
|
var ResolveOcrError = class extends Error {
|
|
32747
32826
|
constructor(code, message) {
|
|
32748
32827
|
super(message);
|
|
@@ -32766,15 +32845,7 @@ async function resolveOcr(input) {
|
|
|
32766
32845
|
`LiteLLM is not ready: ${err.message}`
|
|
32767
32846
|
);
|
|
32768
32847
|
}
|
|
32769
|
-
const
|
|
32770
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
32771
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
32772
|
-
if (!masterKey) {
|
|
32773
|
-
throw new ResolveOcrError(
|
|
32774
|
-
"LITELLM_NOT_CONFIGURED",
|
|
32775
|
-
"LITELLM_MASTER_KEY is required when EXULU_USE_LITELLM=true"
|
|
32776
|
-
);
|
|
32777
|
-
}
|
|
32848
|
+
const { baseUrl, authHeaders } = resolveLiteLLMTarget();
|
|
32778
32849
|
const resolvedUserId = user?.id ?? userId;
|
|
32779
32850
|
if (resolvedUserId) await provisionDefaultUserBudget(resolvedUserId);
|
|
32780
32851
|
const role = user?.role;
|
|
@@ -32794,12 +32865,12 @@ async function resolveOcr(input) {
|
|
|
32794
32865
|
routine_name: routine?.name,
|
|
32795
32866
|
context_name: contextName
|
|
32796
32867
|
});
|
|
32797
|
-
const endpoint =
|
|
32868
|
+
const endpoint = `${baseUrl}/v1/ocr`;
|
|
32798
32869
|
const ocr = async (document2, opts) => {
|
|
32799
32870
|
const res = await fetch(endpoint, {
|
|
32800
32871
|
method: "POST",
|
|
32801
32872
|
headers: {
|
|
32802
|
-
|
|
32873
|
+
...authHeaders,
|
|
32803
32874
|
"Content-Type": "application/json"
|
|
32804
32875
|
},
|
|
32805
32876
|
body: JSON.stringify({
|
|
@@ -32877,9 +32948,9 @@ async function resolveVlmModel(config) {
|
|
|
32877
32948
|
}
|
|
32878
32949
|
async function processImage(buffer, paths, config, verbose = false) {
|
|
32879
32950
|
try {
|
|
32880
|
-
await
|
|
32951
|
+
await fs4.promises.mkdir(paths.images, { recursive: true });
|
|
32881
32952
|
const imagePath = path3.join(paths.images, "1.png");
|
|
32882
|
-
await
|
|
32953
|
+
await fs4.promises.writeFile(imagePath, buffer);
|
|
32883
32954
|
console.log(`[EXULU] Image saved to: ${imagePath}`);
|
|
32884
32955
|
let json = [{
|
|
32885
32956
|
page: 1,
|
|
@@ -32897,7 +32968,7 @@ async function processImage(buffer, paths, config, verbose = false) {
|
|
|
32897
32968
|
verbose,
|
|
32898
32969
|
config.vlm.concurrency
|
|
32899
32970
|
);
|
|
32900
|
-
await
|
|
32971
|
+
await fs4.promises.writeFile(
|
|
32901
32972
|
paths.json,
|
|
32902
32973
|
JSON.stringify(json, null, 2),
|
|
32903
32974
|
"utf-8"
|
|
@@ -32908,14 +32979,14 @@ async function processImage(buffer, paths, config, verbose = false) {
|
|
|
32908
32979
|
} else {
|
|
32909
32980
|
console.log("[EXULU] No VLM configured, image saved without content extraction");
|
|
32910
32981
|
console.log("[EXULU] Note: Enable VLM in config to extract text/content from images");
|
|
32911
|
-
await
|
|
32982
|
+
await fs4.promises.writeFile(
|
|
32912
32983
|
paths.json,
|
|
32913
32984
|
JSON.stringify(json, null, 2),
|
|
32914
32985
|
"utf-8"
|
|
32915
32986
|
);
|
|
32916
32987
|
}
|
|
32917
32988
|
const markdown = json.map((p) => p.vlm_corrected_text ?? p.content).join("\n\n\n<!-- END_OF_PAGE -->\n\n\n");
|
|
32918
|
-
await
|
|
32989
|
+
await fs4.promises.writeFile(paths.markdown, markdown, "utf-8");
|
|
32919
32990
|
return {
|
|
32920
32991
|
markdown,
|
|
32921
32992
|
json
|
|
@@ -32968,7 +33039,7 @@ function reconstructHeadings(correctedText, headingsHierarchy) {
|
|
|
32968
33039
|
return result;
|
|
32969
33040
|
}
|
|
32970
33041
|
async function validatePageWithVLM(page, imagePath, model) {
|
|
32971
|
-
const imageBuffer = await
|
|
33042
|
+
const imageBuffer = await fs4.promises.readFile(imagePath);
|
|
32972
33043
|
const imageBase64 = imageBuffer.toString("base64");
|
|
32973
33044
|
const mimeType = "image/png";
|
|
32974
33045
|
const prompt = `You are a document validation assistant. Your task is to analyze a page image and correct the output of an OCR/parsing pipeline. The content may include tables, technical diagrams, schematics, and structured text.
|
|
@@ -33303,7 +33374,7 @@ ${setupResult.output || ""}`);
|
|
|
33303
33374
|
if (!result.success) {
|
|
33304
33375
|
throw new Error(`Document processing failed: ${result.stderr}`);
|
|
33305
33376
|
}
|
|
33306
|
-
const jsonContent = await
|
|
33377
|
+
const jsonContent = await fs4.promises.readFile(paths.json, "utf-8");
|
|
33307
33378
|
json = JSON.parse(jsonContent);
|
|
33308
33379
|
} else if (config?.processor.name === "officeparser") {
|
|
33309
33380
|
const text = await (0, import_officeparser3.parseOfficeAsync)(buffer, {
|
|
@@ -33336,7 +33407,7 @@ ${setupResult.output || ""}`);
|
|
|
33336
33407
|
await new Promise((resolve8) => setImmediate(resolve8));
|
|
33337
33408
|
await new Promise((resolve8) => setTimeout(resolve8, Math.floor(Math.random() * 1e3) + 200));
|
|
33338
33409
|
console.log(`[EXULU] OCR chunk ${i + 1}/${pdfChunks.length}: pages ${chunk.start_page}\u2013${chunk.end_page - 1}`);
|
|
33339
|
-
const chunkBuffer = await
|
|
33410
|
+
const chunkBuffer = await fs4.promises.readFile(chunk.path);
|
|
33340
33411
|
const chunkBase64 = chunkBuffer.toString("base64");
|
|
33341
33412
|
const chunkResponse = await withRetry(async () => {
|
|
33342
33413
|
return await resolved.ocr({
|
|
@@ -33353,9 +33424,9 @@ ${setupResult.output || ""}`);
|
|
|
33353
33424
|
);
|
|
33354
33425
|
const parser = new import_liteparse.LiteParse();
|
|
33355
33426
|
const screenshots = await parser.screenshot(paths.source, void 0);
|
|
33356
|
-
await
|
|
33427
|
+
await fs4.promises.mkdir(paths.images, { recursive: true });
|
|
33357
33428
|
for (const screenshot of screenshots) {
|
|
33358
|
-
await
|
|
33429
|
+
await fs4.promises.writeFile(
|
|
33359
33430
|
path3.join(
|
|
33360
33431
|
paths.images,
|
|
33361
33432
|
`${screenshot.pageNum}.png`
|
|
@@ -33370,15 +33441,15 @@ ${setupResult.output || ""}`);
|
|
|
33370
33441
|
image: screenshots.find((s) => s.pageNum === page.index + 1)?.imagePath,
|
|
33371
33442
|
headings: []
|
|
33372
33443
|
}));
|
|
33373
|
-
|
|
33444
|
+
fs4.writeFileSync(paths.json, JSON.stringify(json, null, 2));
|
|
33374
33445
|
} else if (config?.processor.name === "liteparse") {
|
|
33375
33446
|
const parser = new import_liteparse.LiteParse();
|
|
33376
33447
|
const result = await parser.parse(paths.source);
|
|
33377
33448
|
const screenshots = await parser.screenshot(paths.source, void 0);
|
|
33378
33449
|
console.log(`[EXULU] Liteparse screenshots: ${JSON.stringify(screenshots)}`);
|
|
33379
|
-
await
|
|
33450
|
+
await fs4.promises.mkdir(paths.images, { recursive: true });
|
|
33380
33451
|
for (const screenshot of screenshots) {
|
|
33381
|
-
await
|
|
33452
|
+
await fs4.promises.writeFile(path3.join(paths.images, `${screenshot.pageNum}.png`), screenshot.imageBuffer);
|
|
33382
33453
|
screenshot.imagePath = path3.join(paths.images, `${screenshot.pageNum}.png`);
|
|
33383
33454
|
}
|
|
33384
33455
|
json = result.pages.map((page) => ({
|
|
@@ -33386,7 +33457,7 @@ ${setupResult.output || ""}`);
|
|
|
33386
33457
|
content: page.text,
|
|
33387
33458
|
image: screenshots.find((s) => s.pageNum === page.pageNum)?.imagePath
|
|
33388
33459
|
}));
|
|
33389
|
-
|
|
33460
|
+
fs4.writeFileSync(paths.json, JSON.stringify(json, null, 2));
|
|
33390
33461
|
}
|
|
33391
33462
|
console.log(`[EXULU]
|
|
33392
33463
|
\u2713 Document processing completed successfully`);
|
|
@@ -33418,13 +33489,13 @@ ${setupResult.output || ""}`);
|
|
|
33418
33489
|
console.log(`[EXULU] Corrected: ${page.vlm_corrected_text.substring(0, 150)}...`);
|
|
33419
33490
|
});
|
|
33420
33491
|
}
|
|
33421
|
-
await
|
|
33492
|
+
await fs4.promises.writeFile(
|
|
33422
33493
|
paths.json,
|
|
33423
33494
|
JSON.stringify(json, null, 2),
|
|
33424
33495
|
"utf-8"
|
|
33425
33496
|
);
|
|
33426
33497
|
}
|
|
33427
|
-
const markdownStream =
|
|
33498
|
+
const markdownStream = fs4.createWriteStream(paths.markdown, { encoding: "utf-8" });
|
|
33428
33499
|
for (let i = 0; i < json.length; i++) {
|
|
33429
33500
|
const p = json[i];
|
|
33430
33501
|
if (!p) continue;
|
|
@@ -33440,7 +33511,7 @@ ${setupResult.output || ""}`);
|
|
|
33440
33511
|
});
|
|
33441
33512
|
console.log(`[EXULU] Validated output saved to: ${paths.json}`);
|
|
33442
33513
|
console.log(`[EXULU] Validated markdown saved to: ${paths.markdown}`);
|
|
33443
|
-
const markdown = await
|
|
33514
|
+
const markdown = await fs4.promises.readFile(paths.markdown, "utf-8");
|
|
33444
33515
|
const processedJson = json.map((e) => {
|
|
33445
33516
|
const finalContent = e.vlm_corrected_text ?? e.content;
|
|
33446
33517
|
return {
|
|
@@ -33471,7 +33542,7 @@ var loadFile = async (file, name, tempDir) => {
|
|
|
33471
33542
|
let buffer;
|
|
33472
33543
|
if (Buffer.isBuffer(file)) {
|
|
33473
33544
|
filePath = path3.join(tempDir, `${UUID}.${fileType}`);
|
|
33474
|
-
await
|
|
33545
|
+
await fs4.promises.writeFile(filePath, file);
|
|
33475
33546
|
buffer = file;
|
|
33476
33547
|
} else {
|
|
33477
33548
|
filePath = filePath.trim();
|
|
@@ -33479,11 +33550,11 @@ var loadFile = async (file, name, tempDir) => {
|
|
|
33479
33550
|
const response = await fetch(filePath);
|
|
33480
33551
|
const array = await response.arrayBuffer();
|
|
33481
33552
|
const tempFilePath = path3.join(tempDir, `${UUID}.${fileType}`);
|
|
33482
|
-
await
|
|
33553
|
+
await fs4.promises.writeFile(tempFilePath, Buffer.from(array));
|
|
33483
33554
|
buffer = Buffer.from(array);
|
|
33484
33555
|
filePath = tempFilePath;
|
|
33485
33556
|
} else {
|
|
33486
|
-
buffer = await
|
|
33557
|
+
buffer = await fs4.promises.readFile(file);
|
|
33487
33558
|
}
|
|
33488
33559
|
}
|
|
33489
33560
|
return { filePath, fileType, buffer };
|
|
@@ -33501,9 +33572,9 @@ async function documentProcessor({
|
|
|
33501
33572
|
const tempDir = path3.join(process.cwd(), "temp", uuid);
|
|
33502
33573
|
const localFilesAndFoldersToDelete = [tempDir];
|
|
33503
33574
|
console.log(`[EXULU] Temporary directory for processing document ${name}: ${tempDir}`);
|
|
33504
|
-
await
|
|
33575
|
+
await fs4.promises.mkdir(tempDir, { recursive: true });
|
|
33505
33576
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
33506
|
-
await
|
|
33577
|
+
await fs4.promises.writeFile(path3.join(tempDir, "created_at.txt"), timestamp);
|
|
33507
33578
|
try {
|
|
33508
33579
|
const {
|
|
33509
33580
|
filePath,
|
|
@@ -33544,7 +33615,7 @@ async function documentProcessor({
|
|
|
33544
33615
|
if (config?.debugging?.deleteTempFiles !== false) {
|
|
33545
33616
|
for (const file2 of localFilesAndFoldersToDelete) {
|
|
33546
33617
|
try {
|
|
33547
|
-
await
|
|
33618
|
+
await fs4.promises.rm(file2, { recursive: true });
|
|
33548
33619
|
console.log(`[EXULU] Deleted file or folder: ${file2}`);
|
|
33549
33620
|
} catch (error) {
|
|
33550
33621
|
console.error(`[EXULU] Error deleting file or folder: ${file2}`, error);
|