@exulu/backend 1.61.3 → 1.62.1
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.
|
@@ -220,7 +220,8 @@ import { resolve } from "path";
|
|
|
220
220
|
var MAX_CRASHES = 5;
|
|
221
221
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
222
222
|
var MAX_BACKOFF_MS = 3e4;
|
|
223
|
-
var READY_TIMEOUT_MS =
|
|
223
|
+
var READY_TIMEOUT_MS = 9e4;
|
|
224
|
+
var WAIT_TIMEOUT_MS = 6e4;
|
|
224
225
|
var READY_POLL_INTERVAL_MS = 200;
|
|
225
226
|
var SHUTDOWN_GRACE_MS = 5e3;
|
|
226
227
|
var internal = {
|
|
@@ -236,7 +237,7 @@ var resolveConfig = (packageRoot) => {
|
|
|
236
237
|
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
237
238
|
const port = process.env.LITELLM_PORT ?? "4000";
|
|
238
239
|
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
239
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(
|
|
240
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(process.cwd(), "./config.litellm.yaml");
|
|
240
241
|
const venvBin = resolve(packageRoot, "ee/python/.venv/bin");
|
|
241
242
|
const venvPython = resolve(venvBin, "python");
|
|
242
243
|
const litellmBin = resolve(venvBin, "litellm");
|
|
@@ -263,7 +264,13 @@ var spawnLiteLLM = (cfg) => {
|
|
|
263
264
|
`Spawning LiteLLM: ${cfg.litellmBin} --config ${cfg.configPath} --port ${cfg.port} --host ${cfg.host}`
|
|
264
265
|
);
|
|
265
266
|
const { DEBUG: _debug, ...rest } = process.env;
|
|
266
|
-
const childEnv = {
|
|
267
|
+
const childEnv = {
|
|
268
|
+
...rest,
|
|
269
|
+
DEBUG: "false"
|
|
270
|
+
};
|
|
271
|
+
if (process.env.EXULU_LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
272
|
+
childEnv.SERVER_ROOT_PATH = process.env.EXULU_LITELLM_UI_PATH;
|
|
273
|
+
}
|
|
267
274
|
const child = spawn(
|
|
268
275
|
cfg.litellmBin,
|
|
269
276
|
[
|
|
@@ -388,10 +395,18 @@ var startLiteLLMSupervisor = async (options = {}) => {
|
|
|
388
395
|
var waitForLiteLLMReady = async () => {
|
|
389
396
|
if (!isLiteLLMEnabled()) return;
|
|
390
397
|
if (!internal.readyPromise) {
|
|
391
|
-
|
|
392
|
-
return;
|
|
398
|
+
return startLiteLLMSupervisor();
|
|
393
399
|
}
|
|
394
|
-
|
|
400
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
401
|
+
while (Date.now() < deadline) {
|
|
402
|
+
const s = getSupervisorState();
|
|
403
|
+
if (s === "ready") return;
|
|
404
|
+
if (s === "given_up") {
|
|
405
|
+
throw new Error("LiteLLM supervisor has given up.");
|
|
406
|
+
}
|
|
407
|
+
await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
|
|
408
|
+
}
|
|
409
|
+
throw new Error("Timed out waiting for LiteLLM to become ready.");
|
|
395
410
|
};
|
|
396
411
|
var stopLiteLLM = (signal = "SIGTERM") => {
|
|
397
412
|
internal.shutdownRequested = true;
|
|
@@ -418,6 +433,7 @@ var registerShutdownHandlers = () => {
|
|
|
418
433
|
process.on("SIGTERM", () => stopLiteLLM("SIGTERM"));
|
|
419
434
|
process.on("exit", () => stopLiteLLM("SIGTERM"));
|
|
420
435
|
};
|
|
436
|
+
var getSupervisorState = () => internal.state;
|
|
421
437
|
|
|
422
438
|
// src/exulu/tags.ts
|
|
423
439
|
var MAX_LEN = 63;
|
|
@@ -577,7 +593,17 @@ var getLiteLLMProvider = ({
|
|
|
577
593
|
name: "litellm",
|
|
578
594
|
baseURL: `http://${host}:${port}/v1`,
|
|
579
595
|
apiKey: masterKey,
|
|
580
|
-
fetch: createTaggedFetch(tags)
|
|
596
|
+
fetch: createTaggedFetch(tags),
|
|
597
|
+
// Without this flag the openai-compatible provider strips any
|
|
598
|
+
// responseFormat.schema before sending and warns
|
|
599
|
+
// "JSON response format schema is only supported with structuredOutputs".
|
|
600
|
+
// Models then return free-form JSON that fails Zod parsing in callers
|
|
601
|
+
// using `Output.object({ schema })`. LiteLLM forwards
|
|
602
|
+
// `response_format: { type: "json_schema", ... }` to every upstream it
|
|
603
|
+
// supports — including Vertex Gemini, which translates it into
|
|
604
|
+
// responseSchema/responseMimeType — so enabling this matches the actual
|
|
605
|
+
// proxy contract.
|
|
606
|
+
supportsStructuredOutputs: true
|
|
581
607
|
});
|
|
582
608
|
return _litellmProvider;
|
|
583
609
|
};
|
|
@@ -744,7 +770,7 @@ var ExuluTool = class {
|
|
|
744
770
|
});
|
|
745
771
|
providerapikey = resolved.apiKey;
|
|
746
772
|
}
|
|
747
|
-
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-
|
|
773
|
+
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-FZ4ZCVBZ.js");
|
|
748
774
|
const tools = await convertExuluToolsToAiSdkTools2(
|
|
749
775
|
[this],
|
|
750
776
|
[],
|
package/dist/index.cjs
CHANGED
|
@@ -376,7 +376,7 @@ var init_statistics = __esm({
|
|
|
376
376
|
});
|
|
377
377
|
|
|
378
378
|
// src/exulu/litellm/supervisor.ts
|
|
379
|
-
var import_node_child_process, import_node_fs, import_node_path, MAX_CRASHES, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, READY_TIMEOUT_MS, READY_POLL_INTERVAL_MS, SHUTDOWN_GRACE_MS, internal, isLiteLLMEnabled, resolveConfig, log, pollHealth, spawnLiteLLM, supervise, _packageRoot, setLiteLLMPackageRoot, startLiteLLMSupervisor, waitForLiteLLMReady, stopLiteLLM, shutdownHandlersRegistered, registerShutdownHandlers;
|
|
379
|
+
var import_node_child_process, import_node_fs, import_node_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, log, pollHealth, spawnLiteLLM, supervise, _packageRoot, setLiteLLMPackageRoot, startLiteLLMSupervisor, waitForLiteLLMReady, stopLiteLLM, shutdownHandlersRegistered, registerShutdownHandlers, getSupervisorState;
|
|
380
380
|
var init_supervisor = __esm({
|
|
381
381
|
"src/exulu/litellm/supervisor.ts"() {
|
|
382
382
|
"use strict";
|
|
@@ -387,7 +387,8 @@ var init_supervisor = __esm({
|
|
|
387
387
|
MAX_CRASHES = 5;
|
|
388
388
|
INITIAL_BACKOFF_MS = 1e3;
|
|
389
389
|
MAX_BACKOFF_MS = 3e4;
|
|
390
|
-
READY_TIMEOUT_MS =
|
|
390
|
+
READY_TIMEOUT_MS = 9e4;
|
|
391
|
+
WAIT_TIMEOUT_MS = 6e4;
|
|
391
392
|
READY_POLL_INTERVAL_MS = 200;
|
|
392
393
|
SHUTDOWN_GRACE_MS = 5e3;
|
|
393
394
|
internal = {
|
|
@@ -403,7 +404,7 @@ var init_supervisor = __esm({
|
|
|
403
404
|
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
404
405
|
const port = process.env.LITELLM_PORT ?? "4000";
|
|
405
406
|
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
406
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path.resolve)(
|
|
407
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
407
408
|
const venvBin = (0, import_node_path.resolve)(packageRoot, "ee/python/.venv/bin");
|
|
408
409
|
const venvPython = (0, import_node_path.resolve)(venvBin, "python");
|
|
409
410
|
const litellmBin = (0, import_node_path.resolve)(venvBin, "litellm");
|
|
@@ -430,7 +431,13 @@ var init_supervisor = __esm({
|
|
|
430
431
|
`Spawning LiteLLM: ${cfg.litellmBin} --config ${cfg.configPath} --port ${cfg.port} --host ${cfg.host}`
|
|
431
432
|
);
|
|
432
433
|
const { DEBUG: _debug, ...rest } = process.env;
|
|
433
|
-
const childEnv = {
|
|
434
|
+
const childEnv = {
|
|
435
|
+
...rest,
|
|
436
|
+
DEBUG: "false"
|
|
437
|
+
};
|
|
438
|
+
if (process.env.EXULU_LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
439
|
+
childEnv.SERVER_ROOT_PATH = process.env.EXULU_LITELLM_UI_PATH;
|
|
440
|
+
}
|
|
434
441
|
const child = (0, import_node_child_process.spawn)(
|
|
435
442
|
cfg.litellmBin,
|
|
436
443
|
[
|
|
@@ -554,10 +561,18 @@ var init_supervisor = __esm({
|
|
|
554
561
|
waitForLiteLLMReady = async () => {
|
|
555
562
|
if (!isLiteLLMEnabled()) return;
|
|
556
563
|
if (!internal.readyPromise) {
|
|
557
|
-
|
|
558
|
-
return;
|
|
564
|
+
return startLiteLLMSupervisor();
|
|
559
565
|
}
|
|
560
|
-
|
|
566
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
567
|
+
while (Date.now() < deadline) {
|
|
568
|
+
const s = getSupervisorState();
|
|
569
|
+
if (s === "ready") return;
|
|
570
|
+
if (s === "given_up") {
|
|
571
|
+
throw new Error("LiteLLM supervisor has given up.");
|
|
572
|
+
}
|
|
573
|
+
await new Promise((r) => setTimeout(r, READY_POLL_INTERVAL_MS));
|
|
574
|
+
}
|
|
575
|
+
throw new Error("Timed out waiting for LiteLLM to become ready.");
|
|
561
576
|
};
|
|
562
577
|
stopLiteLLM = (signal = "SIGTERM") => {
|
|
563
578
|
internal.shutdownRequested = true;
|
|
@@ -584,6 +599,7 @@ var init_supervisor = __esm({
|
|
|
584
599
|
process.on("SIGTERM", () => stopLiteLLM("SIGTERM"));
|
|
585
600
|
process.on("exit", () => stopLiteLLM("SIGTERM"));
|
|
586
601
|
};
|
|
602
|
+
getSupervisorState = () => internal.state;
|
|
587
603
|
}
|
|
588
604
|
});
|
|
589
605
|
|
|
@@ -1002,7 +1018,17 @@ var init_resolve_model = __esm({
|
|
|
1002
1018
|
name: "litellm",
|
|
1003
1019
|
baseURL: `http://${host}:${port}/v1`,
|
|
1004
1020
|
apiKey: masterKey,
|
|
1005
|
-
fetch: createTaggedFetch(tags)
|
|
1021
|
+
fetch: createTaggedFetch(tags),
|
|
1022
|
+
// Without this flag the openai-compatible provider strips any
|
|
1023
|
+
// responseFormat.schema before sending and warns
|
|
1024
|
+
// "JSON response format schema is only supported with structuredOutputs".
|
|
1025
|
+
// Models then return free-form JSON that fails Zod parsing in callers
|
|
1026
|
+
// using `Output.object({ schema })`. LiteLLM forwards
|
|
1027
|
+
// `response_format: { type: "json_schema", ... }` to every upstream it
|
|
1028
|
+
// supports — including Vertex Gemini, which translates it into
|
|
1029
|
+
// responseSchema/responseMimeType — so enabling this matches the actual
|
|
1030
|
+
// proxy contract.
|
|
1031
|
+
supportsStructuredOutputs: true
|
|
1006
1032
|
});
|
|
1007
1033
|
return _litellmProvider;
|
|
1008
1034
|
};
|
|
@@ -13034,30 +13060,6 @@ type LiteLLMModel {
|
|
|
13034
13060
|
pageInfo: PageInfo!
|
|
13035
13061
|
}
|
|
13036
13062
|
`;
|
|
13037
|
-
typeDefs += `
|
|
13038
|
-
agentWorldAgents: [AgentWorldAgent!]!
|
|
13039
|
-
`;
|
|
13040
|
-
resolvers.Query["agentWorldAgents"] = async (_, _args, context) => {
|
|
13041
|
-
const { db: db2 } = context;
|
|
13042
|
-
const sessions = await db2("agent_sessions as s").select([
|
|
13043
|
-
"s.id as sessionId",
|
|
13044
|
-
"s.agent as agentId",
|
|
13045
|
-
"s.currenttask as currentTask",
|
|
13046
|
-
"s.createdAt as lastActivityAt",
|
|
13047
|
-
"s.created_by as userId"
|
|
13048
|
-
]).whereNotNull("s.currenttask").limit(20).orderBy("s.createdAt", "desc");
|
|
13049
|
-
return sessions.map((row) => {
|
|
13050
|
-
const agent = providers.find((p) => p.id === row.agentId);
|
|
13051
|
-
return {
|
|
13052
|
-
sessionId: row.sessionId,
|
|
13053
|
-
agentId: row.agentId ?? "",
|
|
13054
|
-
agentName: agent?.name ?? row.agentId ?? "Agent",
|
|
13055
|
-
agentImage: agent?.image ?? null,
|
|
13056
|
-
currentTask: row.currentTask,
|
|
13057
|
-
lastActivityAt: row.lastActivityAt
|
|
13058
|
-
};
|
|
13059
|
-
});
|
|
13060
|
-
};
|
|
13061
13063
|
typeDefs += "}\n";
|
|
13062
13064
|
mutationDefs += "}\n";
|
|
13063
13065
|
const genericTypes = `
|
|
@@ -13272,15 +13274,6 @@ type StatisticsResult {
|
|
|
13272
13274
|
group: String!
|
|
13273
13275
|
count: Int!
|
|
13274
13276
|
}
|
|
13275
|
-
|
|
13276
|
-
type AgentWorldAgent {
|
|
13277
|
-
sessionId: ID!
|
|
13278
|
-
agentId: ID!
|
|
13279
|
-
agentName: String!
|
|
13280
|
-
agentImage: String
|
|
13281
|
-
currentTask: String
|
|
13282
|
-
lastActivityAt: String
|
|
13283
|
-
}
|
|
13284
13277
|
`;
|
|
13285
13278
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
13286
13279
|
const schema = (0, import_schema.makeExecutableSchema)({
|
|
@@ -13491,7 +13484,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
|
|
|
13491
13484
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
13492
13485
|
var import_crypto_js8 = __toESM(require("crypto-js"), 1);
|
|
13493
13486
|
var import_openai = __toESM(require("openai"), 1);
|
|
13494
|
-
var
|
|
13487
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
13495
13488
|
var import_node_crypto5 = require("crypto");
|
|
13496
13489
|
var import_api2 = require("@opentelemetry/api");
|
|
13497
13490
|
init_check_record_access();
|
|
@@ -13574,7 +13567,7 @@ async function clearSessionCurrentTask(session) {
|
|
|
13574
13567
|
}
|
|
13575
13568
|
|
|
13576
13569
|
// src/exulu/provider.ts
|
|
13577
|
-
var import_fs2 =
|
|
13570
|
+
var import_fs2 = require("fs");
|
|
13578
13571
|
var ExuluProvider = class {
|
|
13579
13572
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
13580
13573
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -14237,7 +14230,6 @@ ${extractedText}
|
|
|
14237
14230
|
// todo make this configurable?
|
|
14238
14231
|
page: 1
|
|
14239
14232
|
});
|
|
14240
|
-
import_fs2.default.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
|
|
14241
14233
|
if (result2?.chunks?.length) {
|
|
14242
14234
|
memoryItems = result2.chunks;
|
|
14243
14235
|
memoryContext = `
|
|
@@ -14888,212 +14880,6 @@ See docs/superpowers/specs/2026-05-31-in-chat-image-generation-design.md for the
|
|
|
14888
14880
|
|
|
14889
14881
|
// src/exulu/routes.ts
|
|
14890
14882
|
var import_node_path5 = require("path");
|
|
14891
|
-
|
|
14892
|
-
// src/utils/python-setup.ts
|
|
14893
|
-
init_cjs_shims();
|
|
14894
|
-
var import_child_process = require("child_process");
|
|
14895
|
-
var import_util = require("util");
|
|
14896
|
-
var import_path = require("path");
|
|
14897
|
-
var import_fs3 = require("fs");
|
|
14898
|
-
var import_url = require("url");
|
|
14899
|
-
var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
|
|
14900
|
-
function getPackageRoot() {
|
|
14901
|
-
const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
14902
|
-
let currentDir = (0, import_path.dirname)(currentFile);
|
|
14903
|
-
let attempts = 0;
|
|
14904
|
-
const maxAttempts = 10;
|
|
14905
|
-
while (attempts < maxAttempts) {
|
|
14906
|
-
const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
|
|
14907
|
-
if ((0, import_fs3.existsSync)(packageJsonPath)) {
|
|
14908
|
-
try {
|
|
14909
|
-
const packageJson = JSON.parse((0, import_fs3.readFileSync)(packageJsonPath, "utf-8"));
|
|
14910
|
-
if (packageJson.name === "@exulu/backend") {
|
|
14911
|
-
return currentDir;
|
|
14912
|
-
}
|
|
14913
|
-
} catch {
|
|
14914
|
-
}
|
|
14915
|
-
}
|
|
14916
|
-
const parentDir = (0, import_path.resolve)(currentDir, "..");
|
|
14917
|
-
if (parentDir === currentDir) {
|
|
14918
|
-
break;
|
|
14919
|
-
}
|
|
14920
|
-
currentDir = parentDir;
|
|
14921
|
-
attempts++;
|
|
14922
|
-
}
|
|
14923
|
-
const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
|
|
14924
|
-
return fallback;
|
|
14925
|
-
}
|
|
14926
|
-
function getSetupScriptPath(packageRoot) {
|
|
14927
|
-
return (0, import_path.resolve)(packageRoot, "ee/python/setup.sh");
|
|
14928
|
-
}
|
|
14929
|
-
function getVenvPath(packageRoot) {
|
|
14930
|
-
return (0, import_path.resolve)(packageRoot, "ee/python/.venv");
|
|
14931
|
-
}
|
|
14932
|
-
function isPythonEnvironmentSetup(packageRoot) {
|
|
14933
|
-
const root = packageRoot ?? getPackageRoot();
|
|
14934
|
-
const venvPath = getVenvPath(root);
|
|
14935
|
-
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
14936
|
-
return (0, import_fs3.existsSync)(venvPath) && (0, import_fs3.existsSync)(pythonPath);
|
|
14937
|
-
}
|
|
14938
|
-
async function setupPythonEnvironment(options = {}) {
|
|
14939
|
-
const {
|
|
14940
|
-
packageRoot = getPackageRoot(),
|
|
14941
|
-
force = false,
|
|
14942
|
-
verbose = false,
|
|
14943
|
-
timeout = 6e5
|
|
14944
|
-
// 10 minutes
|
|
14945
|
-
} = options;
|
|
14946
|
-
if (!force && isPythonEnvironmentSetup(packageRoot)) {
|
|
14947
|
-
if (verbose) {
|
|
14948
|
-
console.log("\u2713 Python environment already set up");
|
|
14949
|
-
}
|
|
14950
|
-
return {
|
|
14951
|
-
success: true,
|
|
14952
|
-
message: "Python environment already exists",
|
|
14953
|
-
alreadyExists: true
|
|
14954
|
-
};
|
|
14955
|
-
}
|
|
14956
|
-
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
14957
|
-
if (!(0, import_fs3.existsSync)(setupScriptPath)) {
|
|
14958
|
-
return {
|
|
14959
|
-
success: false,
|
|
14960
|
-
message: `Setup script not found at: ${setupScriptPath}`,
|
|
14961
|
-
alreadyExists: false
|
|
14962
|
-
};
|
|
14963
|
-
}
|
|
14964
|
-
try {
|
|
14965
|
-
if (verbose) {
|
|
14966
|
-
console.log("Setting up Python environment...");
|
|
14967
|
-
}
|
|
14968
|
-
const { stdout, stderr } = await execAsync4(`bash "${setupScriptPath}"`, {
|
|
14969
|
-
cwd: packageRoot,
|
|
14970
|
-
timeout,
|
|
14971
|
-
env: {
|
|
14972
|
-
...process.env,
|
|
14973
|
-
// Ensure script can write to the directory
|
|
14974
|
-
PYTHONDONTWRITEBYTECODE: "1"
|
|
14975
|
-
},
|
|
14976
|
-
maxBuffer: 10 * 1024 * 1024
|
|
14977
|
-
// 10MB buffer
|
|
14978
|
-
});
|
|
14979
|
-
const output = stdout + stderr;
|
|
14980
|
-
const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
14981
|
-
const pythonVersion = versionMatch ? versionMatch[1] : void 0;
|
|
14982
|
-
if (verbose) {
|
|
14983
|
-
console.log(output);
|
|
14984
|
-
}
|
|
14985
|
-
return {
|
|
14986
|
-
success: true,
|
|
14987
|
-
message: "Python environment set up successfully",
|
|
14988
|
-
alreadyExists: false,
|
|
14989
|
-
pythonVersion,
|
|
14990
|
-
output
|
|
14991
|
-
};
|
|
14992
|
-
} catch (error) {
|
|
14993
|
-
const errorOutput = error.stdout + error.stderr;
|
|
14994
|
-
return {
|
|
14995
|
-
success: false,
|
|
14996
|
-
message: `Setup failed: ${error.message}`,
|
|
14997
|
-
alreadyExists: false,
|
|
14998
|
-
output: errorOutput
|
|
14999
|
-
};
|
|
15000
|
-
}
|
|
15001
|
-
}
|
|
15002
|
-
function getPythonSetupInstructions() {
|
|
15003
|
-
return `
|
|
15004
|
-
Python environment not set up. Please run one of the following commands:
|
|
15005
|
-
|
|
15006
|
-
Option 1 (Automatic):
|
|
15007
|
-
import { setupPythonEnvironment } from '@exulu/backend';
|
|
15008
|
-
await setupPythonEnvironment();
|
|
15009
|
-
|
|
15010
|
-
Option 2 (Manual - for package consumers):
|
|
15011
|
-
npx @exulu/backend setup-python
|
|
15012
|
-
|
|
15013
|
-
Option 3 (Manual - for contributors):
|
|
15014
|
-
npm run python:setup
|
|
15015
|
-
|
|
15016
|
-
These commands will automatically create a Python virtual environment (.venv)
|
|
15017
|
-
in the @exulu/backend package and install all required dependencies.
|
|
15018
|
-
|
|
15019
|
-
Requirements:
|
|
15020
|
-
- Python 3.10 or higher must be installed
|
|
15021
|
-
- pip must be available
|
|
15022
|
-
- venv module must be available (for creating virtual environments)
|
|
15023
|
-
|
|
15024
|
-
If Python dependencies are not installed, install them first, then run one of the commands above:
|
|
15025
|
-
- macOS: brew install python@3.12
|
|
15026
|
-
- Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
|
|
15027
|
-
- Alpine Linux: apk add python3 py3-pip python3-dev
|
|
15028
|
-
- Windows: Download from https://www.python.org/downloads/
|
|
15029
|
-
|
|
15030
|
-
Note: In Docker containers, ensure you install all three components:
|
|
15031
|
-
Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
|
|
15032
|
-
Alpine: apk add python3 py3-pip python3-dev
|
|
15033
|
-
`.trim();
|
|
15034
|
-
}
|
|
15035
|
-
async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
15036
|
-
const root = packageRoot ?? getPackageRoot();
|
|
15037
|
-
const venvPath = getVenvPath(root);
|
|
15038
|
-
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
15039
|
-
if (!(0, import_fs3.existsSync)(venvPath)) {
|
|
15040
|
-
return {
|
|
15041
|
-
valid: false,
|
|
15042
|
-
message: getPythonSetupInstructions()
|
|
15043
|
-
};
|
|
15044
|
-
}
|
|
15045
|
-
if (!(0, import_fs3.existsSync)(pythonPath)) {
|
|
15046
|
-
return {
|
|
15047
|
-
valid: false,
|
|
15048
|
-
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
15049
|
-
};
|
|
15050
|
-
}
|
|
15051
|
-
try {
|
|
15052
|
-
await execAsync4(`"${pythonPath}" --version`, { cwd: root });
|
|
15053
|
-
} catch {
|
|
15054
|
-
return {
|
|
15055
|
-
valid: false,
|
|
15056
|
-
message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
15057
|
-
};
|
|
15058
|
-
}
|
|
15059
|
-
if (checkPackages) {
|
|
15060
|
-
const criticalPackages = ["docling", "transformers"];
|
|
15061
|
-
const missingPackages = [];
|
|
15062
|
-
for (const pkg of criticalPackages) {
|
|
15063
|
-
try {
|
|
15064
|
-
await execAsync4(`"${pythonPath}" -c "import ${pkg}"`, {
|
|
15065
|
-
cwd: root,
|
|
15066
|
-
timeout: 1e4
|
|
15067
|
-
// 10 second timeout per import check
|
|
15068
|
-
});
|
|
15069
|
-
} catch {
|
|
15070
|
-
missingPackages.push(pkg);
|
|
15071
|
-
}
|
|
15072
|
-
}
|
|
15073
|
-
if (missingPackages.length > 0) {
|
|
15074
|
-
return {
|
|
15075
|
-
valid: false,
|
|
15076
|
-
message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
|
|
15077
|
-
|
|
15078
|
-
This usually happens when:
|
|
15079
|
-
1. The .venv folder was copied but dependencies were not installed
|
|
15080
|
-
2. The package was installed via npm but setup script was not run
|
|
15081
|
-
|
|
15082
|
-
Please run:
|
|
15083
|
-
await setupPythonEnvironment({ force: true })
|
|
15084
|
-
|
|
15085
|
-
Or manually run the setup script:
|
|
15086
|
-
bash ` + getSetupScriptPath(root)
|
|
15087
|
-
};
|
|
15088
|
-
}
|
|
15089
|
-
}
|
|
15090
|
-
return {
|
|
15091
|
-
valid: true,
|
|
15092
|
-
message: "Python environment is valid"
|
|
15093
|
-
};
|
|
15094
|
-
}
|
|
15095
|
-
|
|
15096
|
-
// src/exulu/routes.ts
|
|
15097
14883
|
init_tags();
|
|
15098
14884
|
var import_multer = __toESM(require("multer"), 1);
|
|
15099
14885
|
|
|
@@ -15769,7 +15555,7 @@ var REQUEST_SIZE_LIMIT = "50mb";
|
|
|
15769
15555
|
var getExuluVersionNumber = async () => {
|
|
15770
15556
|
try {
|
|
15771
15557
|
const path3 = process.cwd();
|
|
15772
|
-
const packageJson =
|
|
15558
|
+
const packageJson = import_fs3.default.readFileSync(path3 + "/package.json", "utf8");
|
|
15773
15559
|
const packageData = JSON.parse(packageJson);
|
|
15774
15560
|
const exuluVersion = packageData.dependencies["@exulu/backend"];
|
|
15775
15561
|
console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
|
|
@@ -16689,7 +16475,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
16689
16475
|
const imageModelsByName = (() => {
|
|
16690
16476
|
if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
|
|
16691
16477
|
try {
|
|
16692
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path5.resolve)(
|
|
16478
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path5.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
16693
16479
|
const models2 = parseImageGenerationModels(configPath);
|
|
16694
16480
|
return new Map(models2.map((m) => [m.model_name, m]));
|
|
16695
16481
|
} catch (err) {
|
|
@@ -17167,6 +16953,80 @@ ${style.markdown}` : params.prompt;
|
|
|
17167
16953
|
}));
|
|
17168
16954
|
res.status(200).json({ history });
|
|
17169
16955
|
});
|
|
16956
|
+
const litellmUiPath = "/litellm-admin";
|
|
16957
|
+
if (isLiteLLMEnabled() && litellmUiPath) {
|
|
16958
|
+
console.log("[EXULU] Registering LiteLLM UI at", litellmUiPath);
|
|
16959
|
+
app.use(litellmUiPath, async (req, res) => {
|
|
16960
|
+
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
16961
|
+
const port = process.env.LITELLM_PORT ?? "4000";
|
|
16962
|
+
const upstreamUrl = `http://${host}:${port}${litellmUiPath}${req.url}`;
|
|
16963
|
+
const upstreamHeaders = {};
|
|
16964
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
16965
|
+
if (value === void 0) continue;
|
|
16966
|
+
const lower = name.toLowerCase();
|
|
16967
|
+
if (lower === "host" || lower === "content-length" || lower === "connection" || lower === "transfer-encoding" || lower === "accept-encoding")
|
|
16968
|
+
continue;
|
|
16969
|
+
upstreamHeaders[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
16970
|
+
}
|
|
16971
|
+
const methodHasBody = !["GET", "HEAD"].includes(req.method);
|
|
16972
|
+
let body;
|
|
16973
|
+
if (methodHasBody && req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) {
|
|
16974
|
+
body = JSON.stringify(req.body);
|
|
16975
|
+
upstreamHeaders["content-type"] = "application/json";
|
|
16976
|
+
}
|
|
16977
|
+
try {
|
|
16978
|
+
const upstream = await fetch(upstreamUrl, {
|
|
16979
|
+
method: req.method,
|
|
16980
|
+
headers: upstreamHeaders,
|
|
16981
|
+
body,
|
|
16982
|
+
// Pass redirects through to the browser so LiteLLM's post-login
|
|
16983
|
+
// redirect lands the user on the right URL (the Location already
|
|
16984
|
+
// includes SERVER_ROOT_PATH).
|
|
16985
|
+
redirect: "manual"
|
|
16986
|
+
});
|
|
16987
|
+
res.status(upstream.status);
|
|
16988
|
+
const upstreamOrigin = `http://${host}:${port}`;
|
|
16989
|
+
upstream.headers.forEach((value, name) => {
|
|
16990
|
+
const lower = name.toLowerCase();
|
|
16991
|
+
if (lower === "content-encoding" || lower === "content-length" || lower === "transfer-encoding" || lower === "connection")
|
|
16992
|
+
return;
|
|
16993
|
+
if (lower === "location") {
|
|
16994
|
+
let loc = value.startsWith(upstreamOrigin) ? value.slice(upstreamOrigin.length) : value;
|
|
16995
|
+
if (loc.startsWith("/") && loc !== litellmUiPath && !loc.startsWith(`${litellmUiPath}/`)) {
|
|
16996
|
+
loc = `${litellmUiPath}${loc}`;
|
|
16997
|
+
}
|
|
16998
|
+
res.setHeader(name, loc);
|
|
16999
|
+
return;
|
|
17000
|
+
}
|
|
17001
|
+
res.setHeader(name, value);
|
|
17002
|
+
});
|
|
17003
|
+
if (!upstream.body) {
|
|
17004
|
+
res.end();
|
|
17005
|
+
return;
|
|
17006
|
+
}
|
|
17007
|
+
const reader = upstream.body.getReader();
|
|
17008
|
+
try {
|
|
17009
|
+
while (true) {
|
|
17010
|
+
const { done, value } = await reader.read();
|
|
17011
|
+
if (done) break;
|
|
17012
|
+
if (value) res.write(value);
|
|
17013
|
+
}
|
|
17014
|
+
} finally {
|
|
17015
|
+
reader.releaseLock();
|
|
17016
|
+
}
|
|
17017
|
+
res.end();
|
|
17018
|
+
} catch (err) {
|
|
17019
|
+
console.error("[EXULU] LiteLLM UI proxy failed", err);
|
|
17020
|
+
if (!res.headersSent) {
|
|
17021
|
+
res.status(502).json({
|
|
17022
|
+
detail: err instanceof Error ? err.message : "LiteLLM UI proxy failed."
|
|
17023
|
+
});
|
|
17024
|
+
} else {
|
|
17025
|
+
res.end();
|
|
17026
|
+
}
|
|
17027
|
+
}
|
|
17028
|
+
});
|
|
17029
|
+
}
|
|
17170
17030
|
app.use("/litellm/:project", async (req, res) => {
|
|
17171
17031
|
if (!isLiteLLMEnabled()) {
|
|
17172
17032
|
res.status(503).json({
|
|
@@ -20676,6 +20536,210 @@ init_entitlements();
|
|
|
20676
20536
|
init_system_dependencies();
|
|
20677
20537
|
init_supervisor();
|
|
20678
20538
|
|
|
20539
|
+
// src/utils/python-setup.ts
|
|
20540
|
+
init_cjs_shims();
|
|
20541
|
+
var import_child_process = require("child_process");
|
|
20542
|
+
var import_util = require("util");
|
|
20543
|
+
var import_path = require("path");
|
|
20544
|
+
var import_fs4 = require("fs");
|
|
20545
|
+
var import_url = require("url");
|
|
20546
|
+
var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
|
|
20547
|
+
function getPackageRoot() {
|
|
20548
|
+
const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
20549
|
+
let currentDir = (0, import_path.dirname)(currentFile);
|
|
20550
|
+
let attempts = 0;
|
|
20551
|
+
const maxAttempts = 10;
|
|
20552
|
+
while (attempts < maxAttempts) {
|
|
20553
|
+
const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
|
|
20554
|
+
if ((0, import_fs4.existsSync)(packageJsonPath)) {
|
|
20555
|
+
try {
|
|
20556
|
+
const packageJson = JSON.parse((0, import_fs4.readFileSync)(packageJsonPath, "utf-8"));
|
|
20557
|
+
if (packageJson.name === "@exulu/backend") {
|
|
20558
|
+
return currentDir;
|
|
20559
|
+
}
|
|
20560
|
+
} catch {
|
|
20561
|
+
}
|
|
20562
|
+
}
|
|
20563
|
+
const parentDir = (0, import_path.resolve)(currentDir, "..");
|
|
20564
|
+
if (parentDir === currentDir) {
|
|
20565
|
+
break;
|
|
20566
|
+
}
|
|
20567
|
+
currentDir = parentDir;
|
|
20568
|
+
attempts++;
|
|
20569
|
+
}
|
|
20570
|
+
const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
|
|
20571
|
+
return fallback;
|
|
20572
|
+
}
|
|
20573
|
+
function getSetupScriptPath(packageRoot) {
|
|
20574
|
+
return (0, import_path.resolve)(packageRoot, "ee/python/setup.sh");
|
|
20575
|
+
}
|
|
20576
|
+
function getVenvPath(packageRoot) {
|
|
20577
|
+
return (0, import_path.resolve)(packageRoot, "ee/python/.venv");
|
|
20578
|
+
}
|
|
20579
|
+
function isPythonEnvironmentSetup(packageRoot) {
|
|
20580
|
+
const root = packageRoot ?? getPackageRoot();
|
|
20581
|
+
const venvPath = getVenvPath(root);
|
|
20582
|
+
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
20583
|
+
return (0, import_fs4.existsSync)(venvPath) && (0, import_fs4.existsSync)(pythonPath);
|
|
20584
|
+
}
|
|
20585
|
+
async function setupPythonEnvironment(options = {}) {
|
|
20586
|
+
const {
|
|
20587
|
+
packageRoot = getPackageRoot(),
|
|
20588
|
+
force = false,
|
|
20589
|
+
verbose = false,
|
|
20590
|
+
timeout = 6e5
|
|
20591
|
+
// 10 minutes
|
|
20592
|
+
} = options;
|
|
20593
|
+
if (!force && isPythonEnvironmentSetup(packageRoot)) {
|
|
20594
|
+
if (verbose) {
|
|
20595
|
+
console.log("\u2713 Python environment already set up");
|
|
20596
|
+
}
|
|
20597
|
+
return {
|
|
20598
|
+
success: true,
|
|
20599
|
+
message: "Python environment already exists",
|
|
20600
|
+
alreadyExists: true
|
|
20601
|
+
};
|
|
20602
|
+
}
|
|
20603
|
+
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
20604
|
+
if (!(0, import_fs4.existsSync)(setupScriptPath)) {
|
|
20605
|
+
return {
|
|
20606
|
+
success: false,
|
|
20607
|
+
message: `Setup script not found at: ${setupScriptPath}`,
|
|
20608
|
+
alreadyExists: false
|
|
20609
|
+
};
|
|
20610
|
+
}
|
|
20611
|
+
try {
|
|
20612
|
+
if (verbose) {
|
|
20613
|
+
console.log("Setting up Python environment...");
|
|
20614
|
+
}
|
|
20615
|
+
const { stdout, stderr } = await execAsync4(`bash "${setupScriptPath}"`, {
|
|
20616
|
+
cwd: packageRoot,
|
|
20617
|
+
timeout,
|
|
20618
|
+
env: {
|
|
20619
|
+
...process.env,
|
|
20620
|
+
// Ensure script can write to the directory
|
|
20621
|
+
PYTHONDONTWRITEBYTECODE: "1"
|
|
20622
|
+
},
|
|
20623
|
+
maxBuffer: 10 * 1024 * 1024
|
|
20624
|
+
// 10MB buffer
|
|
20625
|
+
});
|
|
20626
|
+
const output = stdout + stderr;
|
|
20627
|
+
const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
20628
|
+
const pythonVersion = versionMatch ? versionMatch[1] : void 0;
|
|
20629
|
+
if (verbose) {
|
|
20630
|
+
console.log(output);
|
|
20631
|
+
}
|
|
20632
|
+
return {
|
|
20633
|
+
success: true,
|
|
20634
|
+
message: "Python environment set up successfully",
|
|
20635
|
+
alreadyExists: false,
|
|
20636
|
+
pythonVersion,
|
|
20637
|
+
output
|
|
20638
|
+
};
|
|
20639
|
+
} catch (error) {
|
|
20640
|
+
const errorOutput = error.stdout + error.stderr;
|
|
20641
|
+
return {
|
|
20642
|
+
success: false,
|
|
20643
|
+
message: `Setup failed: ${error.message}`,
|
|
20644
|
+
alreadyExists: false,
|
|
20645
|
+
output: errorOutput
|
|
20646
|
+
};
|
|
20647
|
+
}
|
|
20648
|
+
}
|
|
20649
|
+
function getPythonSetupInstructions() {
|
|
20650
|
+
return `
|
|
20651
|
+
Python environment not set up. Please run one of the following commands:
|
|
20652
|
+
|
|
20653
|
+
Option 1 (Automatic):
|
|
20654
|
+
import { setupPythonEnvironment } from '@exulu/backend';
|
|
20655
|
+
await setupPythonEnvironment();
|
|
20656
|
+
|
|
20657
|
+
Option 2 (Manual - for package consumers):
|
|
20658
|
+
npx @exulu/backend setup-python
|
|
20659
|
+
|
|
20660
|
+
Option 3 (Manual - for contributors):
|
|
20661
|
+
npm run python:setup
|
|
20662
|
+
|
|
20663
|
+
These commands will automatically create a Python virtual environment (.venv)
|
|
20664
|
+
in the @exulu/backend package and install all required dependencies.
|
|
20665
|
+
|
|
20666
|
+
Requirements:
|
|
20667
|
+
- Python 3.10 or higher must be installed
|
|
20668
|
+
- pip must be available
|
|
20669
|
+
- venv module must be available (for creating virtual environments)
|
|
20670
|
+
|
|
20671
|
+
If Python dependencies are not installed, install them first, then run one of the commands above:
|
|
20672
|
+
- macOS: brew install python@3.12
|
|
20673
|
+
- Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
|
|
20674
|
+
- Alpine Linux: apk add python3 py3-pip python3-dev
|
|
20675
|
+
- Windows: Download from https://www.python.org/downloads/
|
|
20676
|
+
|
|
20677
|
+
Note: In Docker containers, ensure you install all three components:
|
|
20678
|
+
Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
|
|
20679
|
+
Alpine: apk add python3 py3-pip python3-dev
|
|
20680
|
+
`.trim();
|
|
20681
|
+
}
|
|
20682
|
+
async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
20683
|
+
const root = packageRoot ?? getPackageRoot();
|
|
20684
|
+
const venvPath = getVenvPath(root);
|
|
20685
|
+
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
20686
|
+
if (!(0, import_fs4.existsSync)(venvPath)) {
|
|
20687
|
+
return {
|
|
20688
|
+
valid: false,
|
|
20689
|
+
message: getPythonSetupInstructions()
|
|
20690
|
+
};
|
|
20691
|
+
}
|
|
20692
|
+
if (!(0, import_fs4.existsSync)(pythonPath)) {
|
|
20693
|
+
return {
|
|
20694
|
+
valid: false,
|
|
20695
|
+
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
20696
|
+
};
|
|
20697
|
+
}
|
|
20698
|
+
try {
|
|
20699
|
+
await execAsync4(`"${pythonPath}" --version`, { cwd: root });
|
|
20700
|
+
} catch {
|
|
20701
|
+
return {
|
|
20702
|
+
valid: false,
|
|
20703
|
+
message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
20704
|
+
};
|
|
20705
|
+
}
|
|
20706
|
+
if (checkPackages) {
|
|
20707
|
+
const criticalPackages = ["docling", "transformers"];
|
|
20708
|
+
const missingPackages = [];
|
|
20709
|
+
for (const pkg of criticalPackages) {
|
|
20710
|
+
try {
|
|
20711
|
+
await execAsync4(`"${pythonPath}" -c "import ${pkg}"`, {
|
|
20712
|
+
cwd: root,
|
|
20713
|
+
timeout: 1e4
|
|
20714
|
+
// 10 second timeout per import check
|
|
20715
|
+
});
|
|
20716
|
+
} catch {
|
|
20717
|
+
missingPackages.push(pkg);
|
|
20718
|
+
}
|
|
20719
|
+
}
|
|
20720
|
+
if (missingPackages.length > 0) {
|
|
20721
|
+
return {
|
|
20722
|
+
valid: false,
|
|
20723
|
+
message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
|
|
20724
|
+
|
|
20725
|
+
This usually happens when:
|
|
20726
|
+
1. The .venv folder was copied but dependencies were not installed
|
|
20727
|
+
2. The package was installed via npm but setup script was not run
|
|
20728
|
+
|
|
20729
|
+
Please run:
|
|
20730
|
+
await setupPythonEnvironment({ force: true })
|
|
20731
|
+
|
|
20732
|
+
Or manually run the setup script:
|
|
20733
|
+
bash ` + getSetupScriptPath(root)
|
|
20734
|
+
};
|
|
20735
|
+
}
|
|
20736
|
+
}
|
|
20737
|
+
return {
|
|
20738
|
+
valid: true,
|
|
20739
|
+
message: "Python environment is valid"
|
|
20740
|
+
};
|
|
20741
|
+
}
|
|
20742
|
+
|
|
20679
20743
|
// src/templates/contexts/index.ts
|
|
20680
20744
|
init_cjs_shims();
|
|
20681
20745
|
|
|
@@ -20838,6 +20902,12 @@ var ExuluApp = class {
|
|
|
20838
20902
|
...providers ?? []
|
|
20839
20903
|
];
|
|
20840
20904
|
this._config = config;
|
|
20905
|
+
if (isLiteLLMEnabled()) {
|
|
20906
|
+
const uiPath = "/litellm-admin";
|
|
20907
|
+
if (uiPath) {
|
|
20908
|
+
process.env.EXULU_LITELLM_UI_PATH = uiPath;
|
|
20909
|
+
}
|
|
20910
|
+
}
|
|
20841
20911
|
const transcriptionTools = [];
|
|
20842
20912
|
if (process.env.TRANSCRIPTION_MODEL && config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
20843
20913
|
transcriptionTools.push(transcribeTool);
|
|
@@ -20845,7 +20915,7 @@ var ExuluApp = class {
|
|
|
20845
20915
|
const imageGenerationTools = [];
|
|
20846
20916
|
const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
|
|
20847
20917
|
if (isLiteLLMEnabled() && s3Configured) {
|
|
20848
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path7.resolve)(
|
|
20918
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path7.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
20849
20919
|
const imageModels = parseImageGenerationModels(configPath);
|
|
20850
20920
|
if (imageModels.length > 0) {
|
|
20851
20921
|
console.log(
|
|
@@ -23119,7 +23189,7 @@ ${WARNING_BANNER}`);
|
|
|
23119
23189
|
};
|
|
23120
23190
|
var log3 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
23121
23191
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
23122
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path8.resolve)(
|
|
23192
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path8.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
23123
23193
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
23124
23194
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
23125
23195
|
if (!safety.ok && safety.reason === "unparseable-url") {
|
package/dist/index.js
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
vectorSearch,
|
|
54
54
|
waitForLiteLLMReady,
|
|
55
55
|
withRetry
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-4TCN467I.js";
|
|
57
57
|
import {
|
|
58
58
|
findLiteLLMModel
|
|
59
59
|
} from "./chunk-ILAHW4UT.js";
|
|
@@ -5182,30 +5182,6 @@ type LiteLLMModel {
|
|
|
5182
5182
|
pageInfo: PageInfo!
|
|
5183
5183
|
}
|
|
5184
5184
|
`;
|
|
5185
|
-
typeDefs += `
|
|
5186
|
-
agentWorldAgents: [AgentWorldAgent!]!
|
|
5187
|
-
`;
|
|
5188
|
-
resolvers.Query["agentWorldAgents"] = async (_, _args, context) => {
|
|
5189
|
-
const { db } = context;
|
|
5190
|
-
const sessions = await db("agent_sessions as s").select([
|
|
5191
|
-
"s.id as sessionId",
|
|
5192
|
-
"s.agent as agentId",
|
|
5193
|
-
"s.currenttask as currentTask",
|
|
5194
|
-
"s.createdAt as lastActivityAt",
|
|
5195
|
-
"s.created_by as userId"
|
|
5196
|
-
]).whereNotNull("s.currenttask").limit(20).orderBy("s.createdAt", "desc");
|
|
5197
|
-
return sessions.map((row) => {
|
|
5198
|
-
const agent = providers.find((p) => p.id === row.agentId);
|
|
5199
|
-
return {
|
|
5200
|
-
sessionId: row.sessionId,
|
|
5201
|
-
agentId: row.agentId ?? "",
|
|
5202
|
-
agentName: agent?.name ?? row.agentId ?? "Agent",
|
|
5203
|
-
agentImage: agent?.image ?? null,
|
|
5204
|
-
currentTask: row.currentTask,
|
|
5205
|
-
lastActivityAt: row.lastActivityAt
|
|
5206
|
-
};
|
|
5207
|
-
});
|
|
5208
|
-
};
|
|
5209
5185
|
typeDefs += "}\n";
|
|
5210
5186
|
mutationDefs += "}\n";
|
|
5211
5187
|
const genericTypes = `
|
|
@@ -5420,15 +5396,6 @@ type StatisticsResult {
|
|
|
5420
5396
|
group: String!
|
|
5421
5397
|
count: Int!
|
|
5422
5398
|
}
|
|
5423
|
-
|
|
5424
|
-
type AgentWorldAgent {
|
|
5425
|
-
sessionId: ID!
|
|
5426
|
-
agentId: ID!
|
|
5427
|
-
agentName: String!
|
|
5428
|
-
agentImage: String
|
|
5429
|
-
currentTask: String
|
|
5430
|
-
lastActivityAt: String
|
|
5431
|
-
}
|
|
5432
5399
|
`;
|
|
5433
5400
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
5434
5401
|
const schema = makeExecutableSchema({
|
|
@@ -5703,7 +5670,7 @@ async function clearSessionCurrentTask(session) {
|
|
|
5703
5670
|
}
|
|
5704
5671
|
|
|
5705
5672
|
// src/exulu/provider.ts
|
|
5706
|
-
import
|
|
5673
|
+
import "fs";
|
|
5707
5674
|
var ExuluProvider = class {
|
|
5708
5675
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
5709
5676
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -6366,7 +6333,6 @@ ${extractedText}
|
|
|
6366
6333
|
// todo make this configurable?
|
|
6367
6334
|
page: 1
|
|
6368
6335
|
});
|
|
6369
|
-
fs2.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
|
|
6370
6336
|
if (result2?.chunks?.length) {
|
|
6371
6337
|
memoryItems = result2.chunks;
|
|
6372
6338
|
memoryContext = `
|
|
@@ -8594,7 +8560,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
8594
8560
|
const imageModelsByName = (() => {
|
|
8595
8561
|
if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
|
|
8596
8562
|
try {
|
|
8597
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolvePath(
|
|
8563
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolvePath(process.cwd(), "./config.litellm.yaml");
|
|
8598
8564
|
const models2 = parseImageGenerationModels(configPath);
|
|
8599
8565
|
return new Map(models2.map((m) => [m.model_name, m]));
|
|
8600
8566
|
} catch (err) {
|
|
@@ -9072,6 +9038,80 @@ ${style.markdown}` : params.prompt;
|
|
|
9072
9038
|
}));
|
|
9073
9039
|
res.status(200).json({ history });
|
|
9074
9040
|
});
|
|
9041
|
+
const litellmUiPath = "/litellm-admin";
|
|
9042
|
+
if (isLiteLLMEnabled() && litellmUiPath) {
|
|
9043
|
+
console.log("[EXULU] Registering LiteLLM UI at", litellmUiPath);
|
|
9044
|
+
app.use(litellmUiPath, async (req, res) => {
|
|
9045
|
+
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
9046
|
+
const port = process.env.LITELLM_PORT ?? "4000";
|
|
9047
|
+
const upstreamUrl = `http://${host}:${port}${litellmUiPath}${req.url}`;
|
|
9048
|
+
const upstreamHeaders = {};
|
|
9049
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
9050
|
+
if (value === void 0) continue;
|
|
9051
|
+
const lower = name.toLowerCase();
|
|
9052
|
+
if (lower === "host" || lower === "content-length" || lower === "connection" || lower === "transfer-encoding" || lower === "accept-encoding")
|
|
9053
|
+
continue;
|
|
9054
|
+
upstreamHeaders[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
9055
|
+
}
|
|
9056
|
+
const methodHasBody = !["GET", "HEAD"].includes(req.method);
|
|
9057
|
+
let body;
|
|
9058
|
+
if (methodHasBody && req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) {
|
|
9059
|
+
body = JSON.stringify(req.body);
|
|
9060
|
+
upstreamHeaders["content-type"] = "application/json";
|
|
9061
|
+
}
|
|
9062
|
+
try {
|
|
9063
|
+
const upstream = await fetch(upstreamUrl, {
|
|
9064
|
+
method: req.method,
|
|
9065
|
+
headers: upstreamHeaders,
|
|
9066
|
+
body,
|
|
9067
|
+
// Pass redirects through to the browser so LiteLLM's post-login
|
|
9068
|
+
// redirect lands the user on the right URL (the Location already
|
|
9069
|
+
// includes SERVER_ROOT_PATH).
|
|
9070
|
+
redirect: "manual"
|
|
9071
|
+
});
|
|
9072
|
+
res.status(upstream.status);
|
|
9073
|
+
const upstreamOrigin = `http://${host}:${port}`;
|
|
9074
|
+
upstream.headers.forEach((value, name) => {
|
|
9075
|
+
const lower = name.toLowerCase();
|
|
9076
|
+
if (lower === "content-encoding" || lower === "content-length" || lower === "transfer-encoding" || lower === "connection")
|
|
9077
|
+
return;
|
|
9078
|
+
if (lower === "location") {
|
|
9079
|
+
let loc = value.startsWith(upstreamOrigin) ? value.slice(upstreamOrigin.length) : value;
|
|
9080
|
+
if (loc.startsWith("/") && loc !== litellmUiPath && !loc.startsWith(`${litellmUiPath}/`)) {
|
|
9081
|
+
loc = `${litellmUiPath}${loc}`;
|
|
9082
|
+
}
|
|
9083
|
+
res.setHeader(name, loc);
|
|
9084
|
+
return;
|
|
9085
|
+
}
|
|
9086
|
+
res.setHeader(name, value);
|
|
9087
|
+
});
|
|
9088
|
+
if (!upstream.body) {
|
|
9089
|
+
res.end();
|
|
9090
|
+
return;
|
|
9091
|
+
}
|
|
9092
|
+
const reader = upstream.body.getReader();
|
|
9093
|
+
try {
|
|
9094
|
+
while (true) {
|
|
9095
|
+
const { done, value } = await reader.read();
|
|
9096
|
+
if (done) break;
|
|
9097
|
+
if (value) res.write(value);
|
|
9098
|
+
}
|
|
9099
|
+
} finally {
|
|
9100
|
+
reader.releaseLock();
|
|
9101
|
+
}
|
|
9102
|
+
res.end();
|
|
9103
|
+
} catch (err) {
|
|
9104
|
+
console.error("[EXULU] LiteLLM UI proxy failed", err);
|
|
9105
|
+
if (!res.headersSent) {
|
|
9106
|
+
res.status(502).json({
|
|
9107
|
+
detail: err instanceof Error ? err.message : "LiteLLM UI proxy failed."
|
|
9108
|
+
});
|
|
9109
|
+
} else {
|
|
9110
|
+
res.end();
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
9113
|
+
});
|
|
9114
|
+
}
|
|
9075
9115
|
app.use("/litellm/:project", async (req, res) => {
|
|
9076
9116
|
if (!isLiteLLMEnabled()) {
|
|
9077
9117
|
res.status(503).json({
|
|
@@ -12677,6 +12717,12 @@ var ExuluApp = class {
|
|
|
12677
12717
|
...providers ?? []
|
|
12678
12718
|
];
|
|
12679
12719
|
this._config = config;
|
|
12720
|
+
if (isLiteLLMEnabled()) {
|
|
12721
|
+
const uiPath = "/litellm-admin";
|
|
12722
|
+
if (uiPath) {
|
|
12723
|
+
process.env.EXULU_LITELLM_UI_PATH = uiPath;
|
|
12724
|
+
}
|
|
12725
|
+
}
|
|
12680
12726
|
const transcriptionTools = [];
|
|
12681
12727
|
if (process.env.TRANSCRIPTION_MODEL && config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
12682
12728
|
transcriptionTools.push(transcribeTool);
|
|
@@ -12684,7 +12730,7 @@ var ExuluApp = class {
|
|
|
12684
12730
|
const imageGenerationTools = [];
|
|
12685
12731
|
const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
|
|
12686
12732
|
if (isLiteLLMEnabled() && s3Configured) {
|
|
12687
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(
|
|
12733
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(process.cwd(), "./config.litellm.yaml");
|
|
12688
12734
|
const imageModels = parseImageGenerationModels(configPath);
|
|
12689
12735
|
if (imageModels.length > 0) {
|
|
12690
12736
|
console.log(
|
|
@@ -14919,7 +14965,7 @@ ${WARNING_BANNER}`);
|
|
|
14919
14965
|
};
|
|
14920
14966
|
var log2 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
14921
14967
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
14922
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve2(
|
|
14968
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve2(process.cwd(), "./config.litellm.yaml");
|
|
14923
14969
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
14924
14970
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
14925
14971
|
if (!safety.ok && safety.reason === "unparseable-url") {
|