@exulu/backend 1.61.2 → 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
|
[],
|
|
@@ -1767,22 +1783,45 @@ var createUppyRoutes = async (app, config) => {
|
|
|
1767
1783
|
} else {
|
|
1768
1784
|
prefix += "global";
|
|
1769
1785
|
}
|
|
1786
|
+
console.log("[EXULU] bucket", config.fileUploads.s3Bucket);
|
|
1770
1787
|
console.log("[EXULU] prefix", prefix);
|
|
1771
|
-
|
|
1772
|
-
Bucket: config.fileUploads.s3Bucket,
|
|
1773
|
-
Prefix: prefix,
|
|
1774
|
-
MaxKeys: 9,
|
|
1775
|
-
...req.query.continuationToken && {
|
|
1776
|
-
ContinuationToken: req.query.continuationToken
|
|
1777
|
-
}
|
|
1778
|
-
});
|
|
1779
|
-
const response = await client.send(command);
|
|
1788
|
+
let response;
|
|
1780
1789
|
if (req.query.search) {
|
|
1781
|
-
const search = req.query.search;
|
|
1790
|
+
const search = req.query.search.toLowerCase();
|
|
1782
1791
|
console.log("[EXULU] Filtering files by search query", req.query.search);
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1792
|
+
const matched = [];
|
|
1793
|
+
let token;
|
|
1794
|
+
do {
|
|
1795
|
+
const page = await client.send(
|
|
1796
|
+
new ListObjectsV2Command({
|
|
1797
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
1798
|
+
Prefix: prefix,
|
|
1799
|
+
...token && { ContinuationToken: token }
|
|
1800
|
+
})
|
|
1801
|
+
);
|
|
1802
|
+
for (const obj of page.Contents ?? []) {
|
|
1803
|
+
if (obj.Key?.toLowerCase().includes(search)) {
|
|
1804
|
+
matched.push(obj);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
token = page.IsTruncated ? page.NextContinuationToken : void 0;
|
|
1808
|
+
} while (token);
|
|
1809
|
+
response = {
|
|
1810
|
+
$metadata: {},
|
|
1811
|
+
Contents: matched,
|
|
1812
|
+
KeyCount: matched.length,
|
|
1813
|
+
IsTruncated: false
|
|
1814
|
+
};
|
|
1815
|
+
} else {
|
|
1816
|
+
const command = new ListObjectsV2Command({
|
|
1817
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
1818
|
+
Prefix: prefix,
|
|
1819
|
+
MaxKeys: 9,
|
|
1820
|
+
...req.query.continuationToken && {
|
|
1821
|
+
ContinuationToken: req.query.continuationToken
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
response = await client.send(command);
|
|
1786
1825
|
}
|
|
1787
1826
|
res.json({
|
|
1788
1827
|
...response,
|
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
|
};
|
|
@@ -1847,22 +1863,45 @@ var init_uppy = __esm({
|
|
|
1847
1863
|
} else {
|
|
1848
1864
|
prefix += "global";
|
|
1849
1865
|
}
|
|
1866
|
+
console.log("[EXULU] bucket", config.fileUploads.s3Bucket);
|
|
1850
1867
|
console.log("[EXULU] prefix", prefix);
|
|
1851
|
-
|
|
1852
|
-
Bucket: config.fileUploads.s3Bucket,
|
|
1853
|
-
Prefix: prefix,
|
|
1854
|
-
MaxKeys: 9,
|
|
1855
|
-
...req.query.continuationToken && {
|
|
1856
|
-
ContinuationToken: req.query.continuationToken
|
|
1857
|
-
}
|
|
1858
|
-
});
|
|
1859
|
-
const response = await client2.send(command);
|
|
1868
|
+
let response;
|
|
1860
1869
|
if (req.query.search) {
|
|
1861
|
-
const search = req.query.search;
|
|
1870
|
+
const search = req.query.search.toLowerCase();
|
|
1862
1871
|
console.log("[EXULU] Filtering files by search query", req.query.search);
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1872
|
+
const matched = [];
|
|
1873
|
+
let token;
|
|
1874
|
+
do {
|
|
1875
|
+
const page = await client2.send(
|
|
1876
|
+
new import_client_s3.ListObjectsV2Command({
|
|
1877
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
1878
|
+
Prefix: prefix,
|
|
1879
|
+
...token && { ContinuationToken: token }
|
|
1880
|
+
})
|
|
1881
|
+
);
|
|
1882
|
+
for (const obj of page.Contents ?? []) {
|
|
1883
|
+
if (obj.Key?.toLowerCase().includes(search)) {
|
|
1884
|
+
matched.push(obj);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
token = page.IsTruncated ? page.NextContinuationToken : void 0;
|
|
1888
|
+
} while (token);
|
|
1889
|
+
response = {
|
|
1890
|
+
$metadata: {},
|
|
1891
|
+
Contents: matched,
|
|
1892
|
+
KeyCount: matched.length,
|
|
1893
|
+
IsTruncated: false
|
|
1894
|
+
};
|
|
1895
|
+
} else {
|
|
1896
|
+
const command = new import_client_s3.ListObjectsV2Command({
|
|
1897
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
1898
|
+
Prefix: prefix,
|
|
1899
|
+
MaxKeys: 9,
|
|
1900
|
+
...req.query.continuationToken && {
|
|
1901
|
+
ContinuationToken: req.query.continuationToken
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
response = await client2.send(command);
|
|
1866
1905
|
}
|
|
1867
1906
|
res.json({
|
|
1868
1907
|
...response,
|
|
@@ -13011,30 +13050,6 @@ type LiteLLMModel {
|
|
|
13011
13050
|
pageInfo: PageInfo!
|
|
13012
13051
|
}
|
|
13013
13052
|
`;
|
|
13014
|
-
typeDefs += `
|
|
13015
|
-
agentWorldAgents: [AgentWorldAgent!]!
|
|
13016
|
-
`;
|
|
13017
|
-
resolvers.Query["agentWorldAgents"] = async (_, _args, context) => {
|
|
13018
|
-
const { db: db2 } = context;
|
|
13019
|
-
const sessions = await db2("agent_sessions as s").select([
|
|
13020
|
-
"s.id as sessionId",
|
|
13021
|
-
"s.agent as agentId",
|
|
13022
|
-
"s.currenttask as currentTask",
|
|
13023
|
-
"s.createdAt as lastActivityAt",
|
|
13024
|
-
"s.created_by as userId"
|
|
13025
|
-
]).whereNotNull("s.currenttask").limit(20).orderBy("s.createdAt", "desc");
|
|
13026
|
-
return sessions.map((row) => {
|
|
13027
|
-
const agent = providers.find((p) => p.id === row.agentId);
|
|
13028
|
-
return {
|
|
13029
|
-
sessionId: row.sessionId,
|
|
13030
|
-
agentId: row.agentId ?? "",
|
|
13031
|
-
agentName: agent?.name ?? row.agentId ?? "Agent",
|
|
13032
|
-
agentImage: agent?.image ?? null,
|
|
13033
|
-
currentTask: row.currentTask,
|
|
13034
|
-
lastActivityAt: row.lastActivityAt
|
|
13035
|
-
};
|
|
13036
|
-
});
|
|
13037
|
-
};
|
|
13038
13053
|
typeDefs += "}\n";
|
|
13039
13054
|
mutationDefs += "}\n";
|
|
13040
13055
|
const genericTypes = `
|
|
@@ -13249,15 +13264,6 @@ type StatisticsResult {
|
|
|
13249
13264
|
group: String!
|
|
13250
13265
|
count: Int!
|
|
13251
13266
|
}
|
|
13252
|
-
|
|
13253
|
-
type AgentWorldAgent {
|
|
13254
|
-
sessionId: ID!
|
|
13255
|
-
agentId: ID!
|
|
13256
|
-
agentName: String!
|
|
13257
|
-
agentImage: String
|
|
13258
|
-
currentTask: String
|
|
13259
|
-
lastActivityAt: String
|
|
13260
|
-
}
|
|
13261
13267
|
`;
|
|
13262
13268
|
const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
|
|
13263
13269
|
const schema = (0, import_schema.makeExecutableSchema)({
|
|
@@ -13468,7 +13474,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
|
|
|
13468
13474
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
13469
13475
|
var import_crypto_js8 = __toESM(require("crypto-js"), 1);
|
|
13470
13476
|
var import_openai = __toESM(require("openai"), 1);
|
|
13471
|
-
var
|
|
13477
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
13472
13478
|
var import_node_crypto5 = require("crypto");
|
|
13473
13479
|
var import_api2 = require("@opentelemetry/api");
|
|
13474
13480
|
init_check_record_access();
|
|
@@ -13551,7 +13557,7 @@ async function clearSessionCurrentTask(session) {
|
|
|
13551
13557
|
}
|
|
13552
13558
|
|
|
13553
13559
|
// src/exulu/provider.ts
|
|
13554
|
-
var import_fs2 =
|
|
13560
|
+
var import_fs2 = require("fs");
|
|
13555
13561
|
var ExuluProvider = class {
|
|
13556
13562
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
13557
13563
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -14214,7 +14220,6 @@ ${extractedText}
|
|
|
14214
14220
|
// todo make this configurable?
|
|
14215
14221
|
page: 1
|
|
14216
14222
|
});
|
|
14217
|
-
import_fs2.default.writeFileSync("pre-fetched-relevant-information.json", JSON.stringify(result2, null, 2));
|
|
14218
14223
|
if (result2?.chunks?.length) {
|
|
14219
14224
|
memoryItems = result2.chunks;
|
|
14220
14225
|
memoryContext = `
|
|
@@ -14345,7 +14350,6 @@ ${skillsList}
|
|
|
14345
14350
|
|
|
14346
14351
|
When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
|
|
14347
14352
|
'Inform the user that the action was not performed.`;
|
|
14348
|
-
import_fs2.default.writeFileSync("system-prompt.txt", system);
|
|
14349
14353
|
console.log("[EXULU] Tools", currentTools?.map((x) => x.name));
|
|
14350
14354
|
console.log("[EXULU] Skills", currentSkills?.map((x) => x.name));
|
|
14351
14355
|
const tools = await convertExuluToolsToAiSdkTools(
|
|
@@ -14866,212 +14870,6 @@ See docs/superpowers/specs/2026-05-31-in-chat-image-generation-design.md for the
|
|
|
14866
14870
|
|
|
14867
14871
|
// src/exulu/routes.ts
|
|
14868
14872
|
var import_node_path5 = require("path");
|
|
14869
|
-
|
|
14870
|
-
// src/utils/python-setup.ts
|
|
14871
|
-
init_cjs_shims();
|
|
14872
|
-
var import_child_process = require("child_process");
|
|
14873
|
-
var import_util = require("util");
|
|
14874
|
-
var import_path = require("path");
|
|
14875
|
-
var import_fs3 = require("fs");
|
|
14876
|
-
var import_url = require("url");
|
|
14877
|
-
var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
|
|
14878
|
-
function getPackageRoot() {
|
|
14879
|
-
const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
14880
|
-
let currentDir = (0, import_path.dirname)(currentFile);
|
|
14881
|
-
let attempts = 0;
|
|
14882
|
-
const maxAttempts = 10;
|
|
14883
|
-
while (attempts < maxAttempts) {
|
|
14884
|
-
const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
|
|
14885
|
-
if ((0, import_fs3.existsSync)(packageJsonPath)) {
|
|
14886
|
-
try {
|
|
14887
|
-
const packageJson = JSON.parse((0, import_fs3.readFileSync)(packageJsonPath, "utf-8"));
|
|
14888
|
-
if (packageJson.name === "@exulu/backend") {
|
|
14889
|
-
return currentDir;
|
|
14890
|
-
}
|
|
14891
|
-
} catch {
|
|
14892
|
-
}
|
|
14893
|
-
}
|
|
14894
|
-
const parentDir = (0, import_path.resolve)(currentDir, "..");
|
|
14895
|
-
if (parentDir === currentDir) {
|
|
14896
|
-
break;
|
|
14897
|
-
}
|
|
14898
|
-
currentDir = parentDir;
|
|
14899
|
-
attempts++;
|
|
14900
|
-
}
|
|
14901
|
-
const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
|
|
14902
|
-
return fallback;
|
|
14903
|
-
}
|
|
14904
|
-
function getSetupScriptPath(packageRoot) {
|
|
14905
|
-
return (0, import_path.resolve)(packageRoot, "ee/python/setup.sh");
|
|
14906
|
-
}
|
|
14907
|
-
function getVenvPath(packageRoot) {
|
|
14908
|
-
return (0, import_path.resolve)(packageRoot, "ee/python/.venv");
|
|
14909
|
-
}
|
|
14910
|
-
function isPythonEnvironmentSetup(packageRoot) {
|
|
14911
|
-
const root = packageRoot ?? getPackageRoot();
|
|
14912
|
-
const venvPath = getVenvPath(root);
|
|
14913
|
-
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
14914
|
-
return (0, import_fs3.existsSync)(venvPath) && (0, import_fs3.existsSync)(pythonPath);
|
|
14915
|
-
}
|
|
14916
|
-
async function setupPythonEnvironment(options = {}) {
|
|
14917
|
-
const {
|
|
14918
|
-
packageRoot = getPackageRoot(),
|
|
14919
|
-
force = false,
|
|
14920
|
-
verbose = false,
|
|
14921
|
-
timeout = 6e5
|
|
14922
|
-
// 10 minutes
|
|
14923
|
-
} = options;
|
|
14924
|
-
if (!force && isPythonEnvironmentSetup(packageRoot)) {
|
|
14925
|
-
if (verbose) {
|
|
14926
|
-
console.log("\u2713 Python environment already set up");
|
|
14927
|
-
}
|
|
14928
|
-
return {
|
|
14929
|
-
success: true,
|
|
14930
|
-
message: "Python environment already exists",
|
|
14931
|
-
alreadyExists: true
|
|
14932
|
-
};
|
|
14933
|
-
}
|
|
14934
|
-
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
14935
|
-
if (!(0, import_fs3.existsSync)(setupScriptPath)) {
|
|
14936
|
-
return {
|
|
14937
|
-
success: false,
|
|
14938
|
-
message: `Setup script not found at: ${setupScriptPath}`,
|
|
14939
|
-
alreadyExists: false
|
|
14940
|
-
};
|
|
14941
|
-
}
|
|
14942
|
-
try {
|
|
14943
|
-
if (verbose) {
|
|
14944
|
-
console.log("Setting up Python environment...");
|
|
14945
|
-
}
|
|
14946
|
-
const { stdout, stderr } = await execAsync4(`bash "${setupScriptPath}"`, {
|
|
14947
|
-
cwd: packageRoot,
|
|
14948
|
-
timeout,
|
|
14949
|
-
env: {
|
|
14950
|
-
...process.env,
|
|
14951
|
-
// Ensure script can write to the directory
|
|
14952
|
-
PYTHONDONTWRITEBYTECODE: "1"
|
|
14953
|
-
},
|
|
14954
|
-
maxBuffer: 10 * 1024 * 1024
|
|
14955
|
-
// 10MB buffer
|
|
14956
|
-
});
|
|
14957
|
-
const output = stdout + stderr;
|
|
14958
|
-
const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
14959
|
-
const pythonVersion = versionMatch ? versionMatch[1] : void 0;
|
|
14960
|
-
if (verbose) {
|
|
14961
|
-
console.log(output);
|
|
14962
|
-
}
|
|
14963
|
-
return {
|
|
14964
|
-
success: true,
|
|
14965
|
-
message: "Python environment set up successfully",
|
|
14966
|
-
alreadyExists: false,
|
|
14967
|
-
pythonVersion,
|
|
14968
|
-
output
|
|
14969
|
-
};
|
|
14970
|
-
} catch (error) {
|
|
14971
|
-
const errorOutput = error.stdout + error.stderr;
|
|
14972
|
-
return {
|
|
14973
|
-
success: false,
|
|
14974
|
-
message: `Setup failed: ${error.message}`,
|
|
14975
|
-
alreadyExists: false,
|
|
14976
|
-
output: errorOutput
|
|
14977
|
-
};
|
|
14978
|
-
}
|
|
14979
|
-
}
|
|
14980
|
-
function getPythonSetupInstructions() {
|
|
14981
|
-
return `
|
|
14982
|
-
Python environment not set up. Please run one of the following commands:
|
|
14983
|
-
|
|
14984
|
-
Option 1 (Automatic):
|
|
14985
|
-
import { setupPythonEnvironment } from '@exulu/backend';
|
|
14986
|
-
await setupPythonEnvironment();
|
|
14987
|
-
|
|
14988
|
-
Option 2 (Manual - for package consumers):
|
|
14989
|
-
npx @exulu/backend setup-python
|
|
14990
|
-
|
|
14991
|
-
Option 3 (Manual - for contributors):
|
|
14992
|
-
npm run python:setup
|
|
14993
|
-
|
|
14994
|
-
These commands will automatically create a Python virtual environment (.venv)
|
|
14995
|
-
in the @exulu/backend package and install all required dependencies.
|
|
14996
|
-
|
|
14997
|
-
Requirements:
|
|
14998
|
-
- Python 3.10 or higher must be installed
|
|
14999
|
-
- pip must be available
|
|
15000
|
-
- venv module must be available (for creating virtual environments)
|
|
15001
|
-
|
|
15002
|
-
If Python dependencies are not installed, install them first, then run one of the commands above:
|
|
15003
|
-
- macOS: brew install python@3.12
|
|
15004
|
-
- Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
|
|
15005
|
-
- Alpine Linux: apk add python3 py3-pip python3-dev
|
|
15006
|
-
- Windows: Download from https://www.python.org/downloads/
|
|
15007
|
-
|
|
15008
|
-
Note: In Docker containers, ensure you install all three components:
|
|
15009
|
-
Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
|
|
15010
|
-
Alpine: apk add python3 py3-pip python3-dev
|
|
15011
|
-
`.trim();
|
|
15012
|
-
}
|
|
15013
|
-
async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
15014
|
-
const root = packageRoot ?? getPackageRoot();
|
|
15015
|
-
const venvPath = getVenvPath(root);
|
|
15016
|
-
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
15017
|
-
if (!(0, import_fs3.existsSync)(venvPath)) {
|
|
15018
|
-
return {
|
|
15019
|
-
valid: false,
|
|
15020
|
-
message: getPythonSetupInstructions()
|
|
15021
|
-
};
|
|
15022
|
-
}
|
|
15023
|
-
if (!(0, import_fs3.existsSync)(pythonPath)) {
|
|
15024
|
-
return {
|
|
15025
|
-
valid: false,
|
|
15026
|
-
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
15027
|
-
};
|
|
15028
|
-
}
|
|
15029
|
-
try {
|
|
15030
|
-
await execAsync4(`"${pythonPath}" --version`, { cwd: root });
|
|
15031
|
-
} catch {
|
|
15032
|
-
return {
|
|
15033
|
-
valid: false,
|
|
15034
|
-
message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
15035
|
-
};
|
|
15036
|
-
}
|
|
15037
|
-
if (checkPackages) {
|
|
15038
|
-
const criticalPackages = ["docling", "transformers"];
|
|
15039
|
-
const missingPackages = [];
|
|
15040
|
-
for (const pkg of criticalPackages) {
|
|
15041
|
-
try {
|
|
15042
|
-
await execAsync4(`"${pythonPath}" -c "import ${pkg}"`, {
|
|
15043
|
-
cwd: root,
|
|
15044
|
-
timeout: 1e4
|
|
15045
|
-
// 10 second timeout per import check
|
|
15046
|
-
});
|
|
15047
|
-
} catch {
|
|
15048
|
-
missingPackages.push(pkg);
|
|
15049
|
-
}
|
|
15050
|
-
}
|
|
15051
|
-
if (missingPackages.length > 0) {
|
|
15052
|
-
return {
|
|
15053
|
-
valid: false,
|
|
15054
|
-
message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
|
|
15055
|
-
|
|
15056
|
-
This usually happens when:
|
|
15057
|
-
1. The .venv folder was copied but dependencies were not installed
|
|
15058
|
-
2. The package was installed via npm but setup script was not run
|
|
15059
|
-
|
|
15060
|
-
Please run:
|
|
15061
|
-
await setupPythonEnvironment({ force: true })
|
|
15062
|
-
|
|
15063
|
-
Or manually run the setup script:
|
|
15064
|
-
bash ` + getSetupScriptPath(root)
|
|
15065
|
-
};
|
|
15066
|
-
}
|
|
15067
|
-
}
|
|
15068
|
-
return {
|
|
15069
|
-
valid: true,
|
|
15070
|
-
message: "Python environment is valid"
|
|
15071
|
-
};
|
|
15072
|
-
}
|
|
15073
|
-
|
|
15074
|
-
// src/exulu/routes.ts
|
|
15075
14873
|
init_tags();
|
|
15076
14874
|
var import_multer = __toESM(require("multer"), 1);
|
|
15077
14875
|
|
|
@@ -15747,7 +15545,7 @@ var REQUEST_SIZE_LIMIT = "50mb";
|
|
|
15747
15545
|
var getExuluVersionNumber = async () => {
|
|
15748
15546
|
try {
|
|
15749
15547
|
const path3 = process.cwd();
|
|
15750
|
-
const packageJson =
|
|
15548
|
+
const packageJson = import_fs3.default.readFileSync(path3 + "/package.json", "utf8");
|
|
15751
15549
|
const packageData = JSON.parse(packageJson);
|
|
15752
15550
|
const exuluVersion = packageData.dependencies["@exulu/backend"];
|
|
15753
15551
|
console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
|
|
@@ -16667,7 +16465,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
16667
16465
|
const imageModelsByName = (() => {
|
|
16668
16466
|
if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
|
|
16669
16467
|
try {
|
|
16670
|
-
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");
|
|
16671
16469
|
const models2 = parseImageGenerationModels(configPath);
|
|
16672
16470
|
return new Map(models2.map((m) => [m.model_name, m]));
|
|
16673
16471
|
} catch (err) {
|
|
@@ -17145,6 +16943,80 @@ ${style.markdown}` : params.prompt;
|
|
|
17145
16943
|
}));
|
|
17146
16944
|
res.status(200).json({ history });
|
|
17147
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
|
+
}
|
|
17148
17020
|
app.use("/litellm/:project", async (req, res) => {
|
|
17149
17021
|
if (!isLiteLLMEnabled()) {
|
|
17150
17022
|
res.status(503).json({
|
|
@@ -20654,6 +20526,210 @@ init_entitlements();
|
|
|
20654
20526
|
init_system_dependencies();
|
|
20655
20527
|
init_supervisor();
|
|
20656
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
|
+
|
|
20657
20733
|
// src/templates/contexts/index.ts
|
|
20658
20734
|
init_cjs_shims();
|
|
20659
20735
|
|
|
@@ -20816,6 +20892,12 @@ var ExuluApp = class {
|
|
|
20816
20892
|
...providers ?? []
|
|
20817
20893
|
];
|
|
20818
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
|
+
}
|
|
20819
20901
|
const transcriptionTools = [];
|
|
20820
20902
|
if (process.env.TRANSCRIPTION_MODEL && config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
20821
20903
|
transcriptionTools.push(transcribeTool);
|
|
@@ -20823,7 +20905,7 @@ var ExuluApp = class {
|
|
|
20823
20905
|
const imageGenerationTools = [];
|
|
20824
20906
|
const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
|
|
20825
20907
|
if (isLiteLLMEnabled() && s3Configured) {
|
|
20826
|
-
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");
|
|
20827
20909
|
const imageModels = parseImageGenerationModels(configPath);
|
|
20828
20910
|
if (imageModels.length > 0) {
|
|
20829
20911
|
console.log(
|
|
@@ -23097,7 +23179,7 @@ ${WARNING_BANNER}`);
|
|
|
23097
23179
|
};
|
|
23098
23180
|
var log3 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
23099
23181
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
23100
|
-
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");
|
|
23101
23183
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
23102
23184
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
23103
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 = `
|
|
@@ -6497,7 +6463,6 @@ ${skillsList}
|
|
|
6497
6463
|
|
|
6498
6464
|
When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
|
|
6499
6465
|
'Inform the user that the action was not performed.`;
|
|
6500
|
-
fs2.writeFileSync("system-prompt.txt", system);
|
|
6501
6466
|
console.log("[EXULU] Tools", currentTools?.map((x) => x.name));
|
|
6502
6467
|
console.log("[EXULU] Skills", currentSkills?.map((x) => x.name));
|
|
6503
6468
|
const tools = await convertExuluToolsToAiSdkTools(
|
|
@@ -8595,7 +8560,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
8595
8560
|
const imageModelsByName = (() => {
|
|
8596
8561
|
if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
|
|
8597
8562
|
try {
|
|
8598
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolvePath(
|
|
8563
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolvePath(process.cwd(), "./config.litellm.yaml");
|
|
8599
8564
|
const models2 = parseImageGenerationModels(configPath);
|
|
8600
8565
|
return new Map(models2.map((m) => [m.model_name, m]));
|
|
8601
8566
|
} catch (err) {
|
|
@@ -9073,6 +9038,80 @@ ${style.markdown}` : params.prompt;
|
|
|
9073
9038
|
}));
|
|
9074
9039
|
res.status(200).json({ history });
|
|
9075
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
|
+
}
|
|
9076
9115
|
app.use("/litellm/:project", async (req, res) => {
|
|
9077
9116
|
if (!isLiteLLMEnabled()) {
|
|
9078
9117
|
res.status(503).json({
|
|
@@ -12678,6 +12717,12 @@ var ExuluApp = class {
|
|
|
12678
12717
|
...providers ?? []
|
|
12679
12718
|
];
|
|
12680
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
|
+
}
|
|
12681
12726
|
const transcriptionTools = [];
|
|
12682
12727
|
if (process.env.TRANSCRIPTION_MODEL && config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
|
|
12683
12728
|
transcriptionTools.push(transcribeTool);
|
|
@@ -12685,7 +12730,7 @@ var ExuluApp = class {
|
|
|
12685
12730
|
const imageGenerationTools = [];
|
|
12686
12731
|
const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
|
|
12687
12732
|
if (isLiteLLMEnabled() && s3Configured) {
|
|
12688
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(
|
|
12733
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve(process.cwd(), "./config.litellm.yaml");
|
|
12689
12734
|
const imageModels = parseImageGenerationModels(configPath);
|
|
12690
12735
|
if (imageModels.length > 0) {
|
|
12691
12736
|
console.log(
|
|
@@ -14920,7 +14965,7 @@ ${WARNING_BANNER}`);
|
|
|
14920
14965
|
};
|
|
14921
14966
|
var log2 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
14922
14967
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
14923
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve2(
|
|
14968
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? resolve2(process.cwd(), "./config.litellm.yaml");
|
|
14924
14969
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
14925
14970
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
14926
14971
|
if (!safety.ok && safety.reason === "unparseable-url") {
|