@exulu/backend 1.61.3 → 1.62.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.
|
@@ -236,7 +236,7 @@ var resolveConfig = (packageRoot) => {
|
|
|
236
236
|
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
237
237
|
const port = process.env.LITELLM_PORT ?? "4000";
|
|
238
238
|
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
239
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(
|
|
239
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(process.cwd(), "./config.litellm.yaml");
|
|
240
240
|
const venvBin = resolve(packageRoot, "ee/python/.venv/bin");
|
|
241
241
|
const venvPython = resolve(venvBin, "python");
|
|
242
242
|
const litellmBin = resolve(venvBin, "litellm");
|
|
@@ -263,7 +263,13 @@ var spawnLiteLLM = (cfg) => {
|
|
|
263
263
|
`Spawning LiteLLM: ${cfg.litellmBin} --config ${cfg.configPath} --port ${cfg.port} --host ${cfg.host}`
|
|
264
264
|
);
|
|
265
265
|
const { DEBUG: _debug, ...rest } = process.env;
|
|
266
|
-
const childEnv = {
|
|
266
|
+
const childEnv = {
|
|
267
|
+
...rest,
|
|
268
|
+
DEBUG: "false"
|
|
269
|
+
};
|
|
270
|
+
if (process.env.EXULU_LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
271
|
+
childEnv.SERVER_ROOT_PATH = process.env.EXULU_LITELLM_UI_PATH;
|
|
272
|
+
}
|
|
267
273
|
const child = spawn(
|
|
268
274
|
cfg.litellmBin,
|
|
269
275
|
[
|
|
@@ -577,7 +583,17 @@ var getLiteLLMProvider = ({
|
|
|
577
583
|
name: "litellm",
|
|
578
584
|
baseURL: `http://${host}:${port}/v1`,
|
|
579
585
|
apiKey: masterKey,
|
|
580
|
-
fetch: createTaggedFetch(tags)
|
|
586
|
+
fetch: createTaggedFetch(tags),
|
|
587
|
+
// Without this flag the openai-compatible provider strips any
|
|
588
|
+
// responseFormat.schema before sending and warns
|
|
589
|
+
// "JSON response format schema is only supported with structuredOutputs".
|
|
590
|
+
// Models then return free-form JSON that fails Zod parsing in callers
|
|
591
|
+
// using `Output.object({ schema })`. LiteLLM forwards
|
|
592
|
+
// `response_format: { type: "json_schema", ... }` to every upstream it
|
|
593
|
+
// supports — including Vertex Gemini, which translates it into
|
|
594
|
+
// responseSchema/responseMimeType — so enabling this matches the actual
|
|
595
|
+
// proxy contract.
|
|
596
|
+
supportsStructuredOutputs: true
|
|
581
597
|
});
|
|
582
598
|
return _litellmProvider;
|
|
583
599
|
};
|
|
@@ -744,7 +760,7 @@ var ExuluTool = class {
|
|
|
744
760
|
});
|
|
745
761
|
providerapikey = resolved.apiKey;
|
|
746
762
|
}
|
|
747
|
-
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-
|
|
763
|
+
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-2HF7PPYW.js");
|
|
748
764
|
const tools = await convertExuluToolsToAiSdkTools2(
|
|
749
765
|
[this],
|
|
750
766
|
[],
|
package/dist/index.cjs
CHANGED
|
@@ -403,7 +403,7 @@ var init_supervisor = __esm({
|
|
|
403
403
|
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
404
404
|
const port = process.env.LITELLM_PORT ?? "4000";
|
|
405
405
|
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
406
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path.resolve)(
|
|
406
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
407
407
|
const venvBin = (0, import_node_path.resolve)(packageRoot, "ee/python/.venv/bin");
|
|
408
408
|
const venvPython = (0, import_node_path.resolve)(venvBin, "python");
|
|
409
409
|
const litellmBin = (0, import_node_path.resolve)(venvBin, "litellm");
|
|
@@ -430,7 +430,13 @@ var init_supervisor = __esm({
|
|
|
430
430
|
`Spawning LiteLLM: ${cfg.litellmBin} --config ${cfg.configPath} --port ${cfg.port} --host ${cfg.host}`
|
|
431
431
|
);
|
|
432
432
|
const { DEBUG: _debug, ...rest } = process.env;
|
|
433
|
-
const childEnv = {
|
|
433
|
+
const childEnv = {
|
|
434
|
+
...rest,
|
|
435
|
+
DEBUG: "false"
|
|
436
|
+
};
|
|
437
|
+
if (process.env.EXULU_LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
438
|
+
childEnv.SERVER_ROOT_PATH = process.env.EXULU_LITELLM_UI_PATH;
|
|
439
|
+
}
|
|
434
440
|
const child = (0, import_node_child_process.spawn)(
|
|
435
441
|
cfg.litellmBin,
|
|
436
442
|
[
|
|
@@ -1002,7 +1008,17 @@ var init_resolve_model = __esm({
|
|
|
1002
1008
|
name: "litellm",
|
|
1003
1009
|
baseURL: `http://${host}:${port}/v1`,
|
|
1004
1010
|
apiKey: masterKey,
|
|
1005
|
-
fetch: createTaggedFetch(tags)
|
|
1011
|
+
fetch: createTaggedFetch(tags),
|
|
1012
|
+
// Without this flag the openai-compatible provider strips any
|
|
1013
|
+
// responseFormat.schema before sending and warns
|
|
1014
|
+
// "JSON response format schema is only supported with structuredOutputs".
|
|
1015
|
+
// Models then return free-form JSON that fails Zod parsing in callers
|
|
1016
|
+
// using `Output.object({ schema })`. LiteLLM forwards
|
|
1017
|
+
// `response_format: { type: "json_schema", ... }` to every upstream it
|
|
1018
|
+
// supports — including Vertex Gemini, which translates it into
|
|
1019
|
+
// responseSchema/responseMimeType — so enabling this matches the actual
|
|
1020
|
+
// proxy contract.
|
|
1021
|
+
supportsStructuredOutputs: true
|
|
1006
1022
|
});
|
|
1007
1023
|
return _litellmProvider;
|
|
1008
1024
|
};
|
|
@@ -13034,30 +13050,6 @@ type LiteLLMModel {
|
|
|
13034
13050
|
pageInfo: PageInfo!
|
|
13035
13051
|
}
|
|
13036
13052
|
`;
|
|
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
13053
|
typeDefs += "}\n";
|
|
13062
13054
|
mutationDefs += "}\n";
|
|
13063
13055
|
const genericTypes = `
|
|
@@ -13272,15 +13264,6 @@ type StatisticsResult {
|
|
|
13272
13264
|
group: String!
|
|
13273
13265
|
count: Int!
|
|
13274
13266
|
}
|
|
13275
|
-
|
|
13276
|
-
type AgentWorldAgent {
|
|
13277
|
-
sessionId: ID!
|
|
13278
|
-
agentId: ID!
|
|
13279
|
-
agentName: String!
|
|
13280
|
-
agentImage: String
|
|
13281
|
-
currentTask: String
|
|
13282
|
-
lastActivityAt: String
|
|
13283
|
-
}
|
|
13284
13267
|
`;
|
|
13285
13268
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
13286
13269
|
const schema = (0, import_schema.makeExecutableSchema)({
|
|
@@ -13491,7 +13474,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
|
|
|
13491
13474
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
13492
13475
|
var import_crypto_js8 = __toESM(require("crypto-js"), 1);
|
|
13493
13476
|
var import_openai = __toESM(require("openai"), 1);
|
|
13494
|
-
var
|
|
13477
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
13495
13478
|
var import_node_crypto5 = require("crypto");
|
|
13496
13479
|
var import_api2 = require("@opentelemetry/api");
|
|
13497
13480
|
init_check_record_access();
|
|
@@ -13574,7 +13557,7 @@ async function clearSessionCurrentTask(session) {
|
|
|
13574
13557
|
}
|
|
13575
13558
|
|
|
13576
13559
|
// src/exulu/provider.ts
|
|
13577
|
-
var import_fs2 =
|
|
13560
|
+
var import_fs2 = require("fs");
|
|
13578
13561
|
var ExuluProvider = class {
|
|
13579
13562
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
13580
13563
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -14237,7 +14220,6 @@ ${extractedText}
|
|
|
14237
14220
|
// todo make this configurable?
|
|
14238
14221
|
page: 1
|
|
14239
14222
|
});
|
|
14240
|
-
import_fs2.default.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
|
|
14241
14223
|
if (result2?.chunks?.length) {
|
|
14242
14224
|
memoryItems = result2.chunks;
|
|
14243
14225
|
memoryContext = `
|
|
@@ -14888,212 +14870,6 @@ See docs/superpowers/specs/2026-05-31-in-chat-image-generation-design.md for the
|
|
|
14888
14870
|
|
|
14889
14871
|
// src/exulu/routes.ts
|
|
14890
14872
|
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
14873
|
init_tags();
|
|
15098
14874
|
var import_multer = __toESM(require("multer"), 1);
|
|
15099
14875
|
|
|
@@ -15769,7 +15545,7 @@ var REQUEST_SIZE_LIMIT = "50mb";
|
|
|
15769
15545
|
var getExuluVersionNumber = async () => {
|
|
15770
15546
|
try {
|
|
15771
15547
|
const path3 = process.cwd();
|
|
15772
|
-
const packageJson =
|
|
15548
|
+
const packageJson = import_fs3.default.readFileSync(path3 + "/package.json", "utf8");
|
|
15773
15549
|
const packageData = JSON.parse(packageJson);
|
|
15774
15550
|
const exuluVersion = packageData.dependencies["@exulu/backend"];
|
|
15775
15551
|
console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
|
|
@@ -16689,7 +16465,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
16689
16465
|
const imageModelsByName = (() => {
|
|
16690
16466
|
if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
|
|
16691
16467
|
try {
|
|
16692
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path5.resolve)(
|
|
16468
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path5.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
16693
16469
|
const models2 = parseImageGenerationModels(configPath);
|
|
16694
16470
|
return new Map(models2.map((m) => [m.model_name, m]));
|
|
16695
16471
|
} catch (err) {
|
|
@@ -17167,6 +16943,80 @@ ${style.markdown}` : params.prompt;
|
|
|
17167
16943
|
}));
|
|
17168
16944
|
res.status(200).json({ history });
|
|
17169
16945
|
});
|
|
16946
|
+
const litellmUiPath = "/litellm-admin";
|
|
16947
|
+
if (isLiteLLMEnabled() && litellmUiPath) {
|
|
16948
|
+
console.log("[EXULU] Registering LiteLLM UI at", litellmUiPath);
|
|
16949
|
+
app.use(litellmUiPath, async (req, res) => {
|
|
16950
|
+
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
16951
|
+
const port = process.env.LITELLM_PORT ?? "4000";
|
|
16952
|
+
const upstreamUrl = `http://${host}:${port}${litellmUiPath}${req.url}`;
|
|
16953
|
+
const upstreamHeaders = {};
|
|
16954
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
16955
|
+
if (value === void 0) continue;
|
|
16956
|
+
const lower = name.toLowerCase();
|
|
16957
|
+
if (lower === "host" || lower === "content-length" || lower === "connection" || lower === "transfer-encoding" || lower === "accept-encoding")
|
|
16958
|
+
continue;
|
|
16959
|
+
upstreamHeaders[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
16960
|
+
}
|
|
16961
|
+
const methodHasBody = !["GET", "HEAD"].includes(req.method);
|
|
16962
|
+
let body;
|
|
16963
|
+
if (methodHasBody && req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) {
|
|
16964
|
+
body = JSON.stringify(req.body);
|
|
16965
|
+
upstreamHeaders["content-type"] = "application/json";
|
|
16966
|
+
}
|
|
16967
|
+
try {
|
|
16968
|
+
const upstream = await fetch(upstreamUrl, {
|
|
16969
|
+
method: req.method,
|
|
16970
|
+
headers: upstreamHeaders,
|
|
16971
|
+
body,
|
|
16972
|
+
// Pass redirects through to the browser so LiteLLM's post-login
|
|
16973
|
+
// redirect lands the user on the right URL (the Location already
|
|
16974
|
+
// includes SERVER_ROOT_PATH).
|
|
16975
|
+
redirect: "manual"
|
|
16976
|
+
});
|
|
16977
|
+
res.status(upstream.status);
|
|
16978
|
+
const upstreamOrigin = `http://${host}:${port}`;
|
|
16979
|
+
upstream.headers.forEach((value, name) => {
|
|
16980
|
+
const lower = name.toLowerCase();
|
|
16981
|
+
if (lower === "content-encoding" || lower === "content-length" || lower === "transfer-encoding" || lower === "connection")
|
|
16982
|
+
return;
|
|
16983
|
+
if (lower === "location") {
|
|
16984
|
+
let loc = value.startsWith(upstreamOrigin) ? value.slice(upstreamOrigin.length) : value;
|
|
16985
|
+
if (loc.startsWith("/") && loc !== litellmUiPath && !loc.startsWith(`${litellmUiPath}/`)) {
|
|
16986
|
+
loc = `${litellmUiPath}${loc}`;
|
|
16987
|
+
}
|
|
16988
|
+
res.setHeader(name, loc);
|
|
16989
|
+
return;
|
|
16990
|
+
}
|
|
16991
|
+
res.setHeader(name, value);
|
|
16992
|
+
});
|
|
16993
|
+
if (!upstream.body) {
|
|
16994
|
+
res.end();
|
|
16995
|
+
return;
|
|
16996
|
+
}
|
|
16997
|
+
const reader = upstream.body.getReader();
|
|
16998
|
+
try {
|
|
16999
|
+
while (true) {
|
|
17000
|
+
const { done, value } = await reader.read();
|
|
17001
|
+
if (done) break;
|
|
17002
|
+
if (value) res.write(value);
|
|
17003
|
+
}
|
|
17004
|
+
} finally {
|
|
17005
|
+
reader.releaseLock();
|
|
17006
|
+
}
|
|
17007
|
+
res.end();
|
|
17008
|
+
} catch (err) {
|
|
17009
|
+
console.error("[EXULU] LiteLLM UI proxy failed", err);
|
|
17010
|
+
if (!res.headersSent) {
|
|
17011
|
+
res.status(502).json({
|
|
17012
|
+
detail: err instanceof Error ? err.message : "LiteLLM UI proxy failed."
|
|
17013
|
+
});
|
|
17014
|
+
} else {
|
|
17015
|
+
res.end();
|
|
17016
|
+
}
|
|
17017
|
+
}
|
|
17018
|
+
});
|
|
17019
|
+
}
|
|
17170
17020
|
app.use("/litellm/:project", async (req, res) => {
|
|
17171
17021
|
if (!isLiteLLMEnabled()) {
|
|
17172
17022
|
res.status(503).json({
|
|
@@ -20676,6 +20526,210 @@ init_entitlements();
|
|
|
20676
20526
|
init_system_dependencies();
|
|
20677
20527
|
init_supervisor();
|
|
20678
20528
|
|
|
20529
|
+
// src/utils/python-setup.ts
|
|
20530
|
+
init_cjs_shims();
|
|
20531
|
+
var import_child_process = require("child_process");
|
|
20532
|
+
var import_util = require("util");
|
|
20533
|
+
var import_path = require("path");
|
|
20534
|
+
var import_fs4 = require("fs");
|
|
20535
|
+
var import_url = require("url");
|
|
20536
|
+
var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
|
|
20537
|
+
function getPackageRoot() {
|
|
20538
|
+
const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
20539
|
+
let currentDir = (0, import_path.dirname)(currentFile);
|
|
20540
|
+
let attempts = 0;
|
|
20541
|
+
const maxAttempts = 10;
|
|
20542
|
+
while (attempts < maxAttempts) {
|
|
20543
|
+
const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
|
|
20544
|
+
if ((0, import_fs4.existsSync)(packageJsonPath)) {
|
|
20545
|
+
try {
|
|
20546
|
+
const packageJson = JSON.parse((0, import_fs4.readFileSync)(packageJsonPath, "utf-8"));
|
|
20547
|
+
if (packageJson.name === "@exulu/backend") {
|
|
20548
|
+
return currentDir;
|
|
20549
|
+
}
|
|
20550
|
+
} catch {
|
|
20551
|
+
}
|
|
20552
|
+
}
|
|
20553
|
+
const parentDir = (0, import_path.resolve)(currentDir, "..");
|
|
20554
|
+
if (parentDir === currentDir) {
|
|
20555
|
+
break;
|
|
20556
|
+
}
|
|
20557
|
+
currentDir = parentDir;
|
|
20558
|
+
attempts++;
|
|
20559
|
+
}
|
|
20560
|
+
const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
|
|
20561
|
+
return fallback;
|
|
20562
|
+
}
|
|
20563
|
+
function getSetupScriptPath(packageRoot) {
|
|
20564
|
+
return (0, import_path.resolve)(packageRoot, "ee/python/setup.sh");
|
|
20565
|
+
}
|
|
20566
|
+
function getVenvPath(packageRoot) {
|
|
20567
|
+
return (0, import_path.resolve)(packageRoot, "ee/python/.venv");
|
|
20568
|
+
}
|
|
20569
|
+
function isPythonEnvironmentSetup(packageRoot) {
|
|
20570
|
+
const root = packageRoot ?? getPackageRoot();
|
|
20571
|
+
const venvPath = getVenvPath(root);
|
|
20572
|
+
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
20573
|
+
return (0, import_fs4.existsSync)(venvPath) && (0, import_fs4.existsSync)(pythonPath);
|
|
20574
|
+
}
|
|
20575
|
+
async function setupPythonEnvironment(options = {}) {
|
|
20576
|
+
const {
|
|
20577
|
+
packageRoot = getPackageRoot(),
|
|
20578
|
+
force = false,
|
|
20579
|
+
verbose = false,
|
|
20580
|
+
timeout = 6e5
|
|
20581
|
+
// 10 minutes
|
|
20582
|
+
} = options;
|
|
20583
|
+
if (!force && isPythonEnvironmentSetup(packageRoot)) {
|
|
20584
|
+
if (verbose) {
|
|
20585
|
+
console.log("\u2713 Python environment already set up");
|
|
20586
|
+
}
|
|
20587
|
+
return {
|
|
20588
|
+
success: true,
|
|
20589
|
+
message: "Python environment already exists",
|
|
20590
|
+
alreadyExists: true
|
|
20591
|
+
};
|
|
20592
|
+
}
|
|
20593
|
+
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
20594
|
+
if (!(0, import_fs4.existsSync)(setupScriptPath)) {
|
|
20595
|
+
return {
|
|
20596
|
+
success: false,
|
|
20597
|
+
message: `Setup script not found at: ${setupScriptPath}`,
|
|
20598
|
+
alreadyExists: false
|
|
20599
|
+
};
|
|
20600
|
+
}
|
|
20601
|
+
try {
|
|
20602
|
+
if (verbose) {
|
|
20603
|
+
console.log("Setting up Python environment...");
|
|
20604
|
+
}
|
|
20605
|
+
const { stdout, stderr } = await execAsync4(`bash "${setupScriptPath}"`, {
|
|
20606
|
+
cwd: packageRoot,
|
|
20607
|
+
timeout,
|
|
20608
|
+
env: {
|
|
20609
|
+
...process.env,
|
|
20610
|
+
// Ensure script can write to the directory
|
|
20611
|
+
PYTHONDONTWRITEBYTECODE: "1"
|
|
20612
|
+
},
|
|
20613
|
+
maxBuffer: 10 * 1024 * 1024
|
|
20614
|
+
// 10MB buffer
|
|
20615
|
+
});
|
|
20616
|
+
const output = stdout + stderr;
|
|
20617
|
+
const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
20618
|
+
const pythonVersion = versionMatch ? versionMatch[1] : void 0;
|
|
20619
|
+
if (verbose) {
|
|
20620
|
+
console.log(output);
|
|
20621
|
+
}
|
|
20622
|
+
return {
|
|
20623
|
+
success: true,
|
|
20624
|
+
message: "Python environment set up successfully",
|
|
20625
|
+
alreadyExists: false,
|
|
20626
|
+
pythonVersion,
|
|
20627
|
+
output
|
|
20628
|
+
};
|
|
20629
|
+
} catch (error) {
|
|
20630
|
+
const errorOutput = error.stdout + error.stderr;
|
|
20631
|
+
return {
|
|
20632
|
+
success: false,
|
|
20633
|
+
message: `Setup failed: ${error.message}`,
|
|
20634
|
+
alreadyExists: false,
|
|
20635
|
+
output: errorOutput
|
|
20636
|
+
};
|
|
20637
|
+
}
|
|
20638
|
+
}
|
|
20639
|
+
function getPythonSetupInstructions() {
|
|
20640
|
+
return `
|
|
20641
|
+
Python environment not set up. Please run one of the following commands:
|
|
20642
|
+
|
|
20643
|
+
Option 1 (Automatic):
|
|
20644
|
+
import { setupPythonEnvironment } from '@exulu/backend';
|
|
20645
|
+
await setupPythonEnvironment();
|
|
20646
|
+
|
|
20647
|
+
Option 2 (Manual - for package consumers):
|
|
20648
|
+
npx @exulu/backend setup-python
|
|
20649
|
+
|
|
20650
|
+
Option 3 (Manual - for contributors):
|
|
20651
|
+
npm run python:setup
|
|
20652
|
+
|
|
20653
|
+
These commands will automatically create a Python virtual environment (.venv)
|
|
20654
|
+
in the @exulu/backend package and install all required dependencies.
|
|
20655
|
+
|
|
20656
|
+
Requirements:
|
|
20657
|
+
- Python 3.10 or higher must be installed
|
|
20658
|
+
- pip must be available
|
|
20659
|
+
- venv module must be available (for creating virtual environments)
|
|
20660
|
+
|
|
20661
|
+
If Python dependencies are not installed, install them first, then run one of the commands above:
|
|
20662
|
+
- macOS: brew install python@3.12
|
|
20663
|
+
- Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
|
|
20664
|
+
- Alpine Linux: apk add python3 py3-pip python3-dev
|
|
20665
|
+
- Windows: Download from https://www.python.org/downloads/
|
|
20666
|
+
|
|
20667
|
+
Note: In Docker containers, ensure you install all three components:
|
|
20668
|
+
Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
|
|
20669
|
+
Alpine: apk add python3 py3-pip python3-dev
|
|
20670
|
+
`.trim();
|
|
20671
|
+
}
|
|
20672
|
+
async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
20673
|
+
const root = packageRoot ?? getPackageRoot();
|
|
20674
|
+
const venvPath = getVenvPath(root);
|
|
20675
|
+
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
20676
|
+
if (!(0, import_fs4.existsSync)(venvPath)) {
|
|
20677
|
+
return {
|
|
20678
|
+
valid: false,
|
|
20679
|
+
message: getPythonSetupInstructions()
|
|
20680
|
+
};
|
|
20681
|
+
}
|
|
20682
|
+
if (!(0, import_fs4.existsSync)(pythonPath)) {
|
|
20683
|
+
return {
|
|
20684
|
+
valid: false,
|
|
20685
|
+
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
20686
|
+
};
|
|
20687
|
+
}
|
|
20688
|
+
try {
|
|
20689
|
+
await execAsync4(`"${pythonPath}" --version`, { cwd: root });
|
|
20690
|
+
} catch {
|
|
20691
|
+
return {
|
|
20692
|
+
valid: false,
|
|
20693
|
+
message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
20694
|
+
};
|
|
20695
|
+
}
|
|
20696
|
+
if (checkPackages) {
|
|
20697
|
+
const criticalPackages = ["docling", "transformers"];
|
|
20698
|
+
const missingPackages = [];
|
|
20699
|
+
for (const pkg of criticalPackages) {
|
|
20700
|
+
try {
|
|
20701
|
+
await execAsync4(`"${pythonPath}" -c "import ${pkg}"`, {
|
|
20702
|
+
cwd: root,
|
|
20703
|
+
timeout: 1e4
|
|
20704
|
+
// 10 second timeout per import check
|
|
20705
|
+
});
|
|
20706
|
+
} catch {
|
|
20707
|
+
missingPackages.push(pkg);
|
|
20708
|
+
}
|
|
20709
|
+
}
|
|
20710
|
+
if (missingPackages.length > 0) {
|
|
20711
|
+
return {
|
|
20712
|
+
valid: false,
|
|
20713
|
+
message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
|
|
20714
|
+
|
|
20715
|
+
This usually happens when:
|
|
20716
|
+
1. The .venv folder was copied but dependencies were not installed
|
|
20717
|
+
2. The package was installed via npm but setup script was not run
|
|
20718
|
+
|
|
20719
|
+
Please run:
|
|
20720
|
+
await setupPythonEnvironment({ force: true })
|
|
20721
|
+
|
|
20722
|
+
Or manually run the setup script:
|
|
20723
|
+
bash ` + getSetupScriptPath(root)
|
|
20724
|
+
};
|
|
20725
|
+
}
|
|
20726
|
+
}
|
|
20727
|
+
return {
|
|
20728
|
+
valid: true,
|
|
20729
|
+
message: "Python environment is valid"
|
|
20730
|
+
};
|
|
20731
|
+
}
|
|
20732
|
+
|
|
20679
20733
|
// src/templates/contexts/index.ts
|
|
20680
20734
|
init_cjs_shims();
|
|
20681
20735
|
|
|
@@ -20838,6 +20892,12 @@ var ExuluApp = class {
|
|
|
20838
20892
|
...providers ?? []
|
|
20839
20893
|
];
|
|
20840
20894
|
this._config = config;
|
|
20895
|
+
if (isLiteLLMEnabled()) {
|
|
20896
|
+
const uiPath = "/litellm-admin";
|
|
20897
|
+
if (uiPath) {
|
|
20898
|
+
process.env.EXULU_LITELLM_UI_PATH = uiPath;
|
|
20899
|
+
}
|
|
20900
|
+
}
|
|
20841
20901
|
const transcriptionTools = [];
|
|
20842
20902
|
if (process.env.TRANSCRIPTION_MODEL && config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
20843
20903
|
transcriptionTools.push(transcribeTool);
|
|
@@ -20845,7 +20905,7 @@ var ExuluApp = class {
|
|
|
20845
20905
|
const imageGenerationTools = [];
|
|
20846
20906
|
const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
|
|
20847
20907
|
if (isLiteLLMEnabled() && s3Configured) {
|
|
20848
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path7.resolve)(
|
|
20908
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path7.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
20849
20909
|
const imageModels = parseImageGenerationModels(configPath);
|
|
20850
20910
|
if (imageModels.length > 0) {
|
|
20851
20911
|
console.log(
|
|
@@ -23119,7 +23179,7 @@ ${WARNING_BANNER}`);
|
|
|
23119
23179
|
};
|
|
23120
23180
|
var log3 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
23121
23181
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
23122
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path8.resolve)(
|
|
23182
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path8.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
23123
23183
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
23124
23184
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
23125
23185
|
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-LYNLQWXC.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") {
|