@exulu/backend 3.7.2 → 3.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-T6JVFT7L.js → chunk-27K2CO47.js} +5 -0
- package/dist/{chunk-BNTL6LYY.js → chunk-AWMU6QXB.js} +109 -22
- package/dist/cli/start-whisper.js +1 -1
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-UQSLJDXE.js → convert-exulu-tools-to-ai-sdk-tools-XNQ6Q3X6.js} +1 -1
- package/dist/index.cjs +439 -248
- package/dist/index.d.cts +0 -5
- package/dist/index.d.ts +0 -5
- package/dist/index.js +66 -5
- package/dist/python-setup-JZGHWQCG.js +17 -0
- package/ee/invoke-skills/artifact-filter.test.ts +49 -0
- package/ee/invoke-skills/artifact-filter.ts +38 -0
- package/ee/invoke-skills/create-sandbox.ts +56 -4
- package/ee/python/requirements.txt +5 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3727,6 +3727,40 @@ Current item: ${JSON.stringify(summary)}`
|
|
|
3727
3727
|
}
|
|
3728
3728
|
});
|
|
3729
3729
|
|
|
3730
|
+
// ee/invoke-skills/artifact-filter.ts
|
|
3731
|
+
function isIgnoredArtifactPath(relativePath) {
|
|
3732
|
+
return relativePath.split(/[\\/]+/).some((segment) => IGNORED_SEGMENTS.has(segment));
|
|
3733
|
+
}
|
|
3734
|
+
function capArtifacts(artifacts, max = DEFAULT_ARTIFACT_CAP) {
|
|
3735
|
+
if (artifacts.length <= max) return { kept: artifacts, omitted: 0 };
|
|
3736
|
+
return { kept: artifacts.slice(0, max), omitted: artifacts.length - max };
|
|
3737
|
+
}
|
|
3738
|
+
function needsDownload(localSize, remoteSize) {
|
|
3739
|
+
return localSize === void 0 || localSize !== remoteSize;
|
|
3740
|
+
}
|
|
3741
|
+
var IGNORED_SEGMENTS, DEFAULT_ARTIFACT_CAP;
|
|
3742
|
+
var init_artifact_filter = __esm({
|
|
3743
|
+
"ee/invoke-skills/artifact-filter.ts"() {
|
|
3744
|
+
"use strict";
|
|
3745
|
+
init_cjs_shims();
|
|
3746
|
+
IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
3747
|
+
"venv",
|
|
3748
|
+
".venv",
|
|
3749
|
+
"env",
|
|
3750
|
+
"node_modules",
|
|
3751
|
+
"__pycache__",
|
|
3752
|
+
"site-packages",
|
|
3753
|
+
"dist-packages",
|
|
3754
|
+
".cache",
|
|
3755
|
+
".git",
|
|
3756
|
+
".pytest_cache",
|
|
3757
|
+
".mypy_cache",
|
|
3758
|
+
".ipynb_checkpoints"
|
|
3759
|
+
]);
|
|
3760
|
+
DEFAULT_ARTIFACT_CAP = 50;
|
|
3761
|
+
}
|
|
3762
|
+
});
|
|
3763
|
+
|
|
3730
3764
|
// src/exulu/system-dependencies.ts
|
|
3731
3765
|
async function getNpmGlobalRoot() {
|
|
3732
3766
|
if (cachedNpmGlobalRoot !== void 0) return cachedNpmGlobalRoot;
|
|
@@ -3856,6 +3890,229 @@ var init_variable = __esm({
|
|
|
3856
3890
|
}
|
|
3857
3891
|
});
|
|
3858
3892
|
|
|
3893
|
+
// src/utils/python-setup.ts
|
|
3894
|
+
var python_setup_exports = {};
|
|
3895
|
+
__export(python_setup_exports, {
|
|
3896
|
+
getPackageRoot: () => getPackageRoot,
|
|
3897
|
+
getPythonSetupInstructions: () => getPythonSetupInstructions,
|
|
3898
|
+
getPythonVenvPath: () => getPythonVenvPath,
|
|
3899
|
+
isPythonEnvironmentSetup: () => isPythonEnvironmentSetup,
|
|
3900
|
+
setupPythonEnvironment: () => setupPythonEnvironment,
|
|
3901
|
+
validatePythonEnvironment: () => validatePythonEnvironment
|
|
3902
|
+
});
|
|
3903
|
+
function getPackageRoot() {
|
|
3904
|
+
const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
3905
|
+
let currentDir = (0, import_path.dirname)(currentFile);
|
|
3906
|
+
let attempts = 0;
|
|
3907
|
+
const maxAttempts = 10;
|
|
3908
|
+
while (attempts < maxAttempts) {
|
|
3909
|
+
const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
|
|
3910
|
+
if ((0, import_fs.existsSync)(packageJsonPath)) {
|
|
3911
|
+
try {
|
|
3912
|
+
const packageJson = JSON.parse((0, import_fs.readFileSync)(packageJsonPath, "utf-8"));
|
|
3913
|
+
if (packageJson.name === "@exulu/backend") {
|
|
3914
|
+
return currentDir;
|
|
3915
|
+
}
|
|
3916
|
+
} catch {
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
const parentDir = (0, import_path.resolve)(currentDir, "..");
|
|
3920
|
+
if (parentDir === currentDir) {
|
|
3921
|
+
break;
|
|
3922
|
+
}
|
|
3923
|
+
currentDir = parentDir;
|
|
3924
|
+
attempts++;
|
|
3925
|
+
}
|
|
3926
|
+
const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
|
|
3927
|
+
return fallback;
|
|
3928
|
+
}
|
|
3929
|
+
function getSetupScriptPath(packageRoot) {
|
|
3930
|
+
return (0, import_path.resolve)(packageRoot, "ee/python/setup.sh");
|
|
3931
|
+
}
|
|
3932
|
+
function getVenvPath(packageRoot) {
|
|
3933
|
+
return (0, import_path.resolve)(packageRoot, "ee/python/.venv");
|
|
3934
|
+
}
|
|
3935
|
+
function getPythonVenvPath(packageRoot) {
|
|
3936
|
+
const root = packageRoot ?? getPackageRoot();
|
|
3937
|
+
return isPythonEnvironmentSetup(root) ? getVenvPath(root) : void 0;
|
|
3938
|
+
}
|
|
3939
|
+
function isPythonEnvironmentSetup(packageRoot) {
|
|
3940
|
+
const root = packageRoot ?? getPackageRoot();
|
|
3941
|
+
const venvPath = getVenvPath(root);
|
|
3942
|
+
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
3943
|
+
return (0, import_fs.existsSync)(venvPath) && (0, import_fs.existsSync)(pythonPath);
|
|
3944
|
+
}
|
|
3945
|
+
async function setupPythonEnvironment(options = {}) {
|
|
3946
|
+
const {
|
|
3947
|
+
packageRoot = getPackageRoot(),
|
|
3948
|
+
force = false,
|
|
3949
|
+
verbose = false,
|
|
3950
|
+
timeout = 6e5
|
|
3951
|
+
// 10 minutes
|
|
3952
|
+
} = options;
|
|
3953
|
+
if (!force && isPythonEnvironmentSetup(packageRoot)) {
|
|
3954
|
+
if (verbose) {
|
|
3955
|
+
console.log("\u2713 Python environment already set up");
|
|
3956
|
+
}
|
|
3957
|
+
return {
|
|
3958
|
+
success: true,
|
|
3959
|
+
message: "Python environment already exists",
|
|
3960
|
+
alreadyExists: true
|
|
3961
|
+
};
|
|
3962
|
+
}
|
|
3963
|
+
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
3964
|
+
if (!(0, import_fs.existsSync)(setupScriptPath)) {
|
|
3965
|
+
return {
|
|
3966
|
+
success: false,
|
|
3967
|
+
message: `Setup script not found at: ${setupScriptPath}`,
|
|
3968
|
+
alreadyExists: false
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
try {
|
|
3972
|
+
if (verbose) {
|
|
3973
|
+
console.log("Setting up Python environment...");
|
|
3974
|
+
}
|
|
3975
|
+
const { stdout, stderr } = await execAsync2(`bash "${setupScriptPath}"`, {
|
|
3976
|
+
cwd: packageRoot,
|
|
3977
|
+
timeout,
|
|
3978
|
+
env: {
|
|
3979
|
+
...process.env,
|
|
3980
|
+
// Ensure script can write to the directory
|
|
3981
|
+
PYTHONDONTWRITEBYTECODE: "1"
|
|
3982
|
+
},
|
|
3983
|
+
maxBuffer: 10 * 1024 * 1024
|
|
3984
|
+
// 10MB buffer
|
|
3985
|
+
});
|
|
3986
|
+
const output = stdout + stderr;
|
|
3987
|
+
const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
3988
|
+
const pythonVersion = versionMatch ? versionMatch[1] : void 0;
|
|
3989
|
+
if (verbose) {
|
|
3990
|
+
console.log(output);
|
|
3991
|
+
}
|
|
3992
|
+
return {
|
|
3993
|
+
success: true,
|
|
3994
|
+
message: "Python environment set up successfully",
|
|
3995
|
+
alreadyExists: false,
|
|
3996
|
+
pythonVersion,
|
|
3997
|
+
output
|
|
3998
|
+
};
|
|
3999
|
+
} catch (error) {
|
|
4000
|
+
const errorOutput = error.stdout + error.stderr;
|
|
4001
|
+
return {
|
|
4002
|
+
success: false,
|
|
4003
|
+
message: `Setup failed: ${error.message}`,
|
|
4004
|
+
alreadyExists: false,
|
|
4005
|
+
output: errorOutput
|
|
4006
|
+
};
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
function getPythonSetupInstructions() {
|
|
4010
|
+
return `
|
|
4011
|
+
Python environment not set up. Please run one of the following commands:
|
|
4012
|
+
|
|
4013
|
+
Option 1 (Automatic):
|
|
4014
|
+
import { setupPythonEnvironment } from '@exulu/backend';
|
|
4015
|
+
await setupPythonEnvironment();
|
|
4016
|
+
|
|
4017
|
+
Option 2 (Manual - for package consumers):
|
|
4018
|
+
npx @exulu/backend setup-python
|
|
4019
|
+
|
|
4020
|
+
Option 3 (Manual - for contributors):
|
|
4021
|
+
npm run python:setup
|
|
4022
|
+
|
|
4023
|
+
These commands will automatically create a Python virtual environment (.venv)
|
|
4024
|
+
in the @exulu/backend package and install all required dependencies.
|
|
4025
|
+
|
|
4026
|
+
Requirements:
|
|
4027
|
+
- Python 3.10 or higher must be installed
|
|
4028
|
+
- pip must be available
|
|
4029
|
+
- venv module must be available (for creating virtual environments)
|
|
4030
|
+
|
|
4031
|
+
If Python dependencies are not installed, install them first, then run one of the commands above:
|
|
4032
|
+
- macOS: brew install python@3.12
|
|
4033
|
+
- Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
|
|
4034
|
+
- Alpine Linux: apk add python3 py3-pip python3-dev
|
|
4035
|
+
- Windows: Download from https://www.python.org/downloads/
|
|
4036
|
+
|
|
4037
|
+
Note: In Docker containers, ensure you install all three components:
|
|
4038
|
+
Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
|
|
4039
|
+
Alpine: apk add python3 py3-pip python3-dev
|
|
4040
|
+
`.trim();
|
|
4041
|
+
}
|
|
4042
|
+
async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
4043
|
+
const root = packageRoot ?? getPackageRoot();
|
|
4044
|
+
const venvPath = getVenvPath(root);
|
|
4045
|
+
const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
|
|
4046
|
+
if (!(0, import_fs.existsSync)(venvPath)) {
|
|
4047
|
+
return {
|
|
4048
|
+
valid: false,
|
|
4049
|
+
message: getPythonSetupInstructions()
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
if (!(0, import_fs.existsSync)(pythonPath)) {
|
|
4053
|
+
return {
|
|
4054
|
+
valid: false,
|
|
4055
|
+
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
4056
|
+
};
|
|
4057
|
+
}
|
|
4058
|
+
try {
|
|
4059
|
+
await execAsync2(`"${pythonPath}" --version`, { cwd: root });
|
|
4060
|
+
} catch {
|
|
4061
|
+
return {
|
|
4062
|
+
valid: false,
|
|
4063
|
+
message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
if (checkPackages) {
|
|
4067
|
+
const criticalPackages = ["docling", "transformers"];
|
|
4068
|
+
const missingPackages = [];
|
|
4069
|
+
for (const pkg of criticalPackages) {
|
|
4070
|
+
try {
|
|
4071
|
+
await execAsync2(`"${pythonPath}" -c "import ${pkg}"`, {
|
|
4072
|
+
cwd: root,
|
|
4073
|
+
timeout: 1e4
|
|
4074
|
+
// 10 second timeout per import check
|
|
4075
|
+
});
|
|
4076
|
+
} catch {
|
|
4077
|
+
missingPackages.push(pkg);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
if (missingPackages.length > 0) {
|
|
4081
|
+
return {
|
|
4082
|
+
valid: false,
|
|
4083
|
+
message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
|
|
4084
|
+
|
|
4085
|
+
This usually happens when:
|
|
4086
|
+
1. The .venv folder was copied but dependencies were not installed
|
|
4087
|
+
2. The package was installed via npm but setup script was not run
|
|
4088
|
+
|
|
4089
|
+
Please run:
|
|
4090
|
+
await setupPythonEnvironment({ force: true })
|
|
4091
|
+
|
|
4092
|
+
Or manually run the setup script:
|
|
4093
|
+
bash ` + getSetupScriptPath(root)
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
return {
|
|
4098
|
+
valid: true,
|
|
4099
|
+
message: "Python environment is valid"
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
var import_child_process, import_util, import_path, import_fs, import_url, execAsync2;
|
|
4103
|
+
var init_python_setup = __esm({
|
|
4104
|
+
"src/utils/python-setup.ts"() {
|
|
4105
|
+
"use strict";
|
|
4106
|
+
init_cjs_shims();
|
|
4107
|
+
import_child_process = require("child_process");
|
|
4108
|
+
import_util = require("util");
|
|
4109
|
+
import_path = require("path");
|
|
4110
|
+
import_fs = require("fs");
|
|
4111
|
+
import_url = require("url");
|
|
4112
|
+
execAsync2 = (0, import_util.promisify)(import_child_process.exec);
|
|
4113
|
+
}
|
|
4114
|
+
});
|
|
4115
|
+
|
|
3859
4116
|
// ee/invoke-skills/create-sandbox.ts
|
|
3860
4117
|
function probeSandboxSupport() {
|
|
3861
4118
|
if (sandboxProbePromise) return sandboxProbePromise;
|
|
@@ -3941,7 +4198,7 @@ function resolveSessionPath(inputPath, sessionDir) {
|
|
|
3941
4198
|
}
|
|
3942
4199
|
return resolved;
|
|
3943
4200
|
}
|
|
3944
|
-
async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config) {
|
|
4201
|
+
async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config, opts = {}) {
|
|
3945
4202
|
const userPrefix = `user_${userId}/sessions/${sessionId}/`;
|
|
3946
4203
|
let objects;
|
|
3947
4204
|
try {
|
|
@@ -3961,7 +4218,17 @@ async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config) {
|
|
|
3961
4218
|
const idx = obj.key.indexOf(userPrefix);
|
|
3962
4219
|
const relativePath = idx >= 0 ? obj.key.slice(idx + userPrefix.length) : "";
|
|
3963
4220
|
if (!relativePath) continue;
|
|
4221
|
+
if (isIgnoredArtifactPath(relativePath)) continue;
|
|
3964
4222
|
const localPath = (0, import_node_path4.join)(sessionDir, relativePath);
|
|
4223
|
+
if (opts.onlyMissing) {
|
|
4224
|
+
let localSize;
|
|
4225
|
+
try {
|
|
4226
|
+
localSize = (await (0, import_promises.stat)(localPath)).size;
|
|
4227
|
+
} catch {
|
|
4228
|
+
localSize = void 0;
|
|
4229
|
+
}
|
|
4230
|
+
if (!needsDownload(localSize, obj.size)) continue;
|
|
4231
|
+
}
|
|
3965
4232
|
try {
|
|
3966
4233
|
const bytes = await getS3ObjectBytes(obj.key, config);
|
|
3967
4234
|
await (0, import_promises.mkdir)((0, import_node_path4.dirname)(localPath), { recursive: true });
|
|
@@ -3995,6 +4262,15 @@ async function downloadKeyIntoSandbox(opts) {
|
|
|
3995
4262
|
await (0, import_promises.writeFile)(localPath, bytes);
|
|
3996
4263
|
return { written: true, localPath };
|
|
3997
4264
|
}
|
|
4265
|
+
async function resolvePythonVenvPath() {
|
|
4266
|
+
try {
|
|
4267
|
+
const { getPythonVenvPath: getPythonVenvPath2 } = await Promise.resolve().then(() => (init_python_setup(), python_setup_exports));
|
|
4268
|
+
return getPythonVenvPath2();
|
|
4269
|
+
} catch (err) {
|
|
4270
|
+
console.warn("[SKILLS] Could not resolve the Python venv for the session sandbox; skill scripts fall back to the system python.", err);
|
|
4271
|
+
return void 0;
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
3998
4274
|
async function createSessionSandbox(sessionId, skills, config, userId) {
|
|
3999
4275
|
const cached = sandboxCache.get(sessionId);
|
|
4000
4276
|
if (cached) {
|
|
@@ -4008,6 +4284,13 @@ async function createSessionSandbox(sessionId, skills, config, userId) {
|
|
|
4008
4284
|
await downloadSkill(skill, skillsDirectory2, config);
|
|
4009
4285
|
cached.installedSkills.set(skill.id, skill.current_version);
|
|
4010
4286
|
}
|
|
4287
|
+
if (userId && config.fileUploads) {
|
|
4288
|
+
try {
|
|
4289
|
+
await restoreArtifactsFromS3(cached.handle.sessionDir, sessionId, userId, config, { onlyMissing: true });
|
|
4290
|
+
} catch (err) {
|
|
4291
|
+
console.error(`[SKILLS] Failed to re-sync S3 session files for session ${sessionId}; continuing.`, err);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4011
4294
|
return cached.handle;
|
|
4012
4295
|
}
|
|
4013
4296
|
const sessionDir = (0, import_node_path4.join)("/tmp", "exulu-sessions", sessionId);
|
|
@@ -4025,8 +4308,8 @@ async function createSessionSandbox(sessionId, skills, config, userId) {
|
|
|
4025
4308
|
`[SKILLS] S3 artifact persistence disabled for session ${sessionId} (userId=${userId ?? "missing"}, fileUploads=${config.fileUploads ? "configured" : "missing"})`
|
|
4026
4309
|
);
|
|
4027
4310
|
}
|
|
4028
|
-
if (userId && config.fileUploads
|
|
4029
|
-
await restoreArtifactsFromS3(sessionDir, sessionId, userId, config);
|
|
4311
|
+
if (userId && config.fileUploads) {
|
|
4312
|
+
await restoreArtifactsFromS3(sessionDir, sessionId, userId, config, { onlyMissing: dirExisted });
|
|
4030
4313
|
}
|
|
4031
4314
|
const probe = await probeSandboxSupport();
|
|
4032
4315
|
const useDirectExec = !probe.canSandbox;
|
|
@@ -4061,6 +4344,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
4061
4344
|
await import_sandbox_runtime.SandboxManager.initialize(baselineSandboxConfig);
|
|
4062
4345
|
}
|
|
4063
4346
|
const npmGlobalRoot = await getNpmGlobalRoot();
|
|
4347
|
+
const pythonVenvPath = await resolvePythonVenvPath();
|
|
4064
4348
|
const sessionSandboxConfig = {
|
|
4065
4349
|
network: {
|
|
4066
4350
|
allowedDomains: [],
|
|
@@ -4075,7 +4359,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
4075
4359
|
// Allow Node to read globally-installed packages from inside
|
|
4076
4360
|
// the sandbox. Without this, `require('docx')` fails with
|
|
4077
4361
|
// EPERM even when NODE_PATH points the resolver here.
|
|
4078
|
-
...npmGlobalRoot ? [npmGlobalRoot] : []
|
|
4362
|
+
...npmGlobalRoot ? [npmGlobalRoot] : [],
|
|
4363
|
+
...pythonVenvPath ? [pythonVenvPath] : []
|
|
4079
4364
|
],
|
|
4080
4365
|
allowWrite: [sessionDir],
|
|
4081
4366
|
denyWrite: []
|
|
@@ -4093,7 +4378,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
4093
4378
|
const sandboxedExecEnv = {
|
|
4094
4379
|
...configuredVariables,
|
|
4095
4380
|
...process.env,
|
|
4096
|
-
...npmGlobalRoot ? { NODE_PATH: npmGlobalRoot } : {}
|
|
4381
|
+
...npmGlobalRoot ? { NODE_PATH: npmGlobalRoot } : {},
|
|
4382
|
+
...pythonVenvPath ? { PATH: `${(0, import_node_path4.join)(pythonVenvPath, "bin")}:${process.env.PATH ?? ""}`, VIRTUAL_ENV: pythonVenvPath } : {}
|
|
4097
4383
|
};
|
|
4098
4384
|
const wrapIfNeeded = async (command) => {
|
|
4099
4385
|
if (useDirectExec) return command;
|
|
@@ -4102,7 +4388,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
4102
4388
|
const runWrapped = async (command) => {
|
|
4103
4389
|
const wrapped = await wrapIfNeeded(command);
|
|
4104
4390
|
try {
|
|
4105
|
-
const { stdout, stderr } = await
|
|
4391
|
+
const { stdout, stderr } = await execAsync3(wrapped, {
|
|
4106
4392
|
maxBuffer: EXEC_MAX_BUFFER,
|
|
4107
4393
|
shell: "/bin/bash",
|
|
4108
4394
|
env: sandboxedExecEnv
|
|
@@ -4218,6 +4504,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
4218
4504
|
for (const entry of entries) {
|
|
4219
4505
|
const full = (0, import_node_path4.join)(dir, entry.name);
|
|
4220
4506
|
if (full === skillsDir) continue;
|
|
4507
|
+
if (isIgnoredArtifactPath((0, import_node_path4.relative)(sessionDir, full))) continue;
|
|
4221
4508
|
if (entry.isDirectory()) {
|
|
4222
4509
|
await walk(full);
|
|
4223
4510
|
} else if (entry.isFile()) {
|
|
@@ -4313,19 +4600,24 @@ Probe error: ${probe.reason ?? "(no detail)"}`
|
|
|
4313
4600
|
}
|
|
4314
4601
|
}
|
|
4315
4602
|
let stdout = result?.stdout ?? "";
|
|
4316
|
-
const
|
|
4603
|
+
const { kept, omitted } = capArtifacts(artifacts);
|
|
4604
|
+
const withUrls = kept.filter((a) => a.url);
|
|
4317
4605
|
if (withUrls.length > 0) {
|
|
4318
4606
|
const lines = ["", "[exulu-artifacts]"];
|
|
4319
4607
|
for (const a of withUrls) {
|
|
4320
4608
|
lines.push(` ${a.relativePath}: ${a.url}`);
|
|
4321
4609
|
}
|
|
4610
|
+
if (omitted > 0) {
|
|
4611
|
+
lines.push(` \u2026 ${omitted} more file(s) were created and mirrored but are not listed here.`);
|
|
4612
|
+
}
|
|
4322
4613
|
stdout = `${stdout}
|
|
4323
4614
|
${lines.join("\n")}`;
|
|
4324
4615
|
}
|
|
4325
4616
|
return {
|
|
4326
4617
|
...result,
|
|
4327
4618
|
stdout,
|
|
4328
|
-
artifacts
|
|
4619
|
+
artifacts: kept,
|
|
4620
|
+
...omitted > 0 ? { artifactsOmitted: omitted } : {}
|
|
4329
4621
|
};
|
|
4330
4622
|
}
|
|
4331
4623
|
});
|
|
@@ -4345,7 +4637,7 @@ ${lines.join("\n")}`;
|
|
|
4345
4637
|
sandboxCache.set(sessionId, { handle, installedSkills });
|
|
4346
4638
|
return handle;
|
|
4347
4639
|
}
|
|
4348
|
-
var import_sandbox_runtime, import_promises, import_node_fs5, import_node_path4, import_node_child_process3, import_node_util2, import_bash_tool, import_ai2, import_zod6, import_crypto_js3, getAllExuluVariables,
|
|
4640
|
+
var import_sandbox_runtime, import_promises, import_node_fs5, import_node_path4, import_node_child_process3, import_node_util2, import_bash_tool, import_ai2, import_zod6, import_crypto_js3, getAllExuluVariables, execAsync3, EXEC_MAX_BUFFER, sandboxProbePromise, SANDBOX_FALLBACK_INSTRUCTIONS, degradedModeLogged, sandboxCache;
|
|
4349
4641
|
var init_create_sandbox = __esm({
|
|
4350
4642
|
"ee/invoke-skills/create-sandbox.ts"() {
|
|
4351
4643
|
"use strict";
|
|
@@ -4357,6 +4649,7 @@ var init_create_sandbox = __esm({
|
|
|
4357
4649
|
import_node_child_process3 = require("child_process");
|
|
4358
4650
|
import_node_util2 = require("util");
|
|
4359
4651
|
init_uppy();
|
|
4652
|
+
init_artifact_filter();
|
|
4360
4653
|
init_system_dependencies();
|
|
4361
4654
|
import_bash_tool = require("bash-tool");
|
|
4362
4655
|
import_ai2 = require("ai");
|
|
@@ -4390,7 +4683,7 @@ var init_create_sandbox = __esm({
|
|
|
4390
4683
|
}
|
|
4391
4684
|
return out;
|
|
4392
4685
|
};
|
|
4393
|
-
|
|
4686
|
+
execAsync3 = (0, import_node_util2.promisify)(import_node_child_process3.exec);
|
|
4394
4687
|
EXEC_MAX_BUFFER = 32 * 1024 * 1024;
|
|
4395
4688
|
SANDBOX_FALLBACK_INSTRUCTIONS = 'Skill sandboxing is running in DEGRADED mode: bwrap cannot create user namespaces on this host, so commands\nexecute directly. The container remains the isolation boundary and resolveSessionPath still scopes\nreadFile/writeFile to the session directory at the JS layer, but bash commands are NOT kernel-sandboxed.\n\nTo restore full sandboxing on Ubuntu 23.10+ / 24.04+ hosts (kernel 6.5+):\n sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0\n echo "kernel.apparmor_restrict_unprivileged_userns=0" | sudo tee /etc/sysctl.d/60-userns.conf\n sudo sysctl --system\n\nOn Debian hosts where the same symptom appears:\n sudo sysctl -w kernel.unprivileged_userns_clone=1\n\nSet EXULU_REQUIRE_SANDBOX=1 to fail startup instead of degrading.';
|
|
4396
4689
|
degradedModeLogged = false;
|
|
@@ -4629,6 +4922,18 @@ var init_auth_tool_model_output = __esm({
|
|
|
4629
4922
|
}
|
|
4630
4923
|
});
|
|
4631
4924
|
|
|
4925
|
+
// src/exulu/session-files.ts
|
|
4926
|
+
function sessionFilePrefix(ownerId, sessionId, s3prefix) {
|
|
4927
|
+
const general = s3prefix ? `${s3prefix.replace(/\/+$/, "")}/` : "";
|
|
4928
|
+
return `${general}user_${ownerId}/sessions/${sessionId}/`;
|
|
4929
|
+
}
|
|
4930
|
+
var init_session_files = __esm({
|
|
4931
|
+
"src/exulu/session-files.ts"() {
|
|
4932
|
+
"use strict";
|
|
4933
|
+
init_cjs_shims();
|
|
4934
|
+
}
|
|
4935
|
+
});
|
|
4936
|
+
|
|
4632
4937
|
// src/templates/tools/session-file-read-tool.ts
|
|
4633
4938
|
var import_zod7, DEFAULT_LIMIT, MAX_CONTENT_CHARS, createSessionFileReadTool;
|
|
4634
4939
|
var init_session_file_read_tool = __esm({
|
|
@@ -4636,6 +4941,7 @@ var init_session_file_read_tool = __esm({
|
|
|
4636
4941
|
"use strict";
|
|
4637
4942
|
init_cjs_shims();
|
|
4638
4943
|
import_zod7 = require("zod");
|
|
4944
|
+
init_session_files();
|
|
4639
4945
|
init_tool();
|
|
4640
4946
|
init_uppy();
|
|
4641
4947
|
DEFAULT_LIMIT = 250;
|
|
@@ -4643,7 +4949,8 @@ var init_session_file_read_tool = __esm({
|
|
|
4643
4949
|
createSessionFileReadTool = ({
|
|
4644
4950
|
sessionID,
|
|
4645
4951
|
user,
|
|
4646
|
-
exuluConfig
|
|
4952
|
+
exuluConfig,
|
|
4953
|
+
ownerId
|
|
4647
4954
|
}) => {
|
|
4648
4955
|
if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
|
|
4649
4956
|
const readSessionFileExecute = async ({ filename, offset, limit }) => {
|
|
@@ -4654,8 +4961,7 @@ var init_session_file_read_tool = __esm({
|
|
|
4654
4961
|
};
|
|
4655
4962
|
}
|
|
4656
4963
|
const uploads = exuluConfig.fileUploads;
|
|
4657
|
-
const
|
|
4658
|
-
const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
|
|
4964
|
+
const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
|
|
4659
4965
|
try {
|
|
4660
4966
|
const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
|
|
4661
4967
|
const res = await fetch(url);
|
|
@@ -4759,6 +5065,18 @@ var init_document_render_helpers = __esm({
|
|
|
4759
5065
|
});
|
|
4760
5066
|
|
|
4761
5067
|
// src/templates/tools/parse-document-tool.ts
|
|
5068
|
+
function looksLikeGarbledTextLayer(text) {
|
|
5069
|
+
let control = 0;
|
|
5070
|
+
let visible = 0;
|
|
5071
|
+
for (const ch of text) {
|
|
5072
|
+
const code = ch.charCodeAt(0);
|
|
5073
|
+
if (code === 9 || code === 10 || code === 12 || code === 13 || code === 32) continue;
|
|
5074
|
+
visible++;
|
|
5075
|
+
if (code < 32 || code === 127) control++;
|
|
5076
|
+
}
|
|
5077
|
+
if (visible < 40) return false;
|
|
5078
|
+
return control / visible > 0.01;
|
|
5079
|
+
}
|
|
4762
5080
|
var import_zod8, import_node_path6, import_officeparser, DEFAULT_LIMIT2, MAX_CONTENT_CHARS2, MIN_CHARS_PER_PAGE, OFFICE_EXTENSIONS, pagesPattern, createParseDocumentTool;
|
|
4763
5081
|
var init_parse_document_tool = __esm({
|
|
4764
5082
|
"src/templates/tools/parse-document-tool.ts"() {
|
|
@@ -4770,6 +5088,7 @@ var init_parse_document_tool = __esm({
|
|
|
4770
5088
|
init_tool();
|
|
4771
5089
|
init_uppy();
|
|
4772
5090
|
init_document_render_helpers();
|
|
5091
|
+
init_session_files();
|
|
4773
5092
|
DEFAULT_LIMIT2 = 250;
|
|
4774
5093
|
MAX_CONTENT_CHARS2 = 16e3;
|
|
4775
5094
|
MIN_CHARS_PER_PAGE = 20;
|
|
@@ -4789,7 +5108,8 @@ var init_parse_document_tool = __esm({
|
|
|
4789
5108
|
createParseDocumentTool = ({
|
|
4790
5109
|
sessionID,
|
|
4791
5110
|
user,
|
|
4792
|
-
exuluConfig
|
|
5111
|
+
exuluConfig,
|
|
5112
|
+
ownerId
|
|
4793
5113
|
}) => {
|
|
4794
5114
|
if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
|
|
4795
5115
|
const parseDocumentExecute = async ({
|
|
@@ -4814,8 +5134,7 @@ var init_parse_document_tool = __esm({
|
|
|
4814
5134
|
return { error: `The pages option is only supported for PDF files \u2014 "${ext}" documents are extracted whole.` };
|
|
4815
5135
|
}
|
|
4816
5136
|
const uploads = exuluConfig.fileUploads;
|
|
4817
|
-
const
|
|
4818
|
-
const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
|
|
5137
|
+
const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
|
|
4819
5138
|
try {
|
|
4820
5139
|
const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
|
|
4821
5140
|
const res = await fetch(url);
|
|
@@ -4830,6 +5149,11 @@ var init_parse_document_tool = __esm({
|
|
|
4830
5149
|
const pageTexts = raw.replace(/\f$/, "").split("\f");
|
|
4831
5150
|
totalPages = pageTexts.length;
|
|
4832
5151
|
const nonWhitespace = raw.replace(/\s/g, "").length;
|
|
5152
|
+
if (looksLikeGarbledTextLayer(raw)) {
|
|
5153
|
+
return {
|
|
5154
|
+
error: `"${safeName}" has a text layer that is unreadable (its font encoding maps glyphs to the wrong characters, so words and especially numbers come out wrong or vanish). Do not use extracted text from this file. Use view_document_page to read the pages visually, or suggest the user add the document to a knowledge base with a document processor for full OCR.`
|
|
5155
|
+
};
|
|
5156
|
+
}
|
|
4833
5157
|
if (nonWhitespace < totalPages * MIN_CHARS_PER_PAGE) {
|
|
4834
5158
|
return {
|
|
4835
5159
|
error: `"${safeName}" has no extractable text layer (likely a scan or image-based PDF). Use view_document_page to look at pages visually, or suggest the user add the document to a knowledge base with a document processor for full OCR.`
|
|
@@ -4928,7 +5252,7 @@ async function getPdfPreviewBytes(opts) {
|
|
|
4928
5252
|
const bytes = await getS3ObjectBytes(sourceKey, config);
|
|
4929
5253
|
await (0, import_promises3.writeFile)(inputPath, bytes);
|
|
4930
5254
|
try {
|
|
4931
|
-
await
|
|
5255
|
+
await execAsync4(
|
|
4932
5256
|
`soffice --headless --convert-to pdf "${inputPath}" --outdir "${CACHE_OUT}"`,
|
|
4933
5257
|
{ timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
|
|
4934
5258
|
);
|
|
@@ -4954,7 +5278,7 @@ async function getPdfPreviewBytes(opts) {
|
|
|
4954
5278
|
inFlight.set(safeEtag, promise);
|
|
4955
5279
|
return promise;
|
|
4956
5280
|
}
|
|
4957
|
-
var import_node_child_process5, import_node_fs6, import_promises3, import_node_path7, import_node_util4,
|
|
5281
|
+
var import_node_child_process5, import_node_fs6, import_promises3, import_node_path7, import_node_util4, execAsync4, CACHE_ROOT, CACHE_IN, CACHE_OUT, inFlight, PreviewRenderError;
|
|
4958
5282
|
var init_pdf_preview_cache = __esm({
|
|
4959
5283
|
"src/sessions/pdf-preview-cache.ts"() {
|
|
4960
5284
|
"use strict";
|
|
@@ -4965,7 +5289,7 @@ var init_pdf_preview_cache = __esm({
|
|
|
4965
5289
|
import_node_path7 = require("path");
|
|
4966
5290
|
import_node_util4 = require("util");
|
|
4967
5291
|
init_uppy();
|
|
4968
|
-
|
|
5292
|
+
execAsync4 = (0, import_node_util4.promisify)(import_node_child_process5.exec);
|
|
4969
5293
|
CACHE_ROOT = "/tmp/exulu-pdf-cache";
|
|
4970
5294
|
CACHE_IN = (0, import_node_path7.join)(CACHE_ROOT, "_in");
|
|
4971
5295
|
CACHE_OUT = (0, import_node_path7.join)(CACHE_ROOT, "_out");
|
|
@@ -5165,6 +5489,7 @@ var init_view_document_page_tool = __esm({
|
|
|
5165
5489
|
"use strict";
|
|
5166
5490
|
init_cjs_shims();
|
|
5167
5491
|
import_zod9 = require("zod");
|
|
5492
|
+
init_session_files();
|
|
5168
5493
|
import_node_path8 = require("path");
|
|
5169
5494
|
init_tool();
|
|
5170
5495
|
init_uppy();
|
|
@@ -5197,7 +5522,8 @@ var init_view_document_page_tool = __esm({
|
|
|
5197
5522
|
createViewDocumentPageTool = ({
|
|
5198
5523
|
sessionID,
|
|
5199
5524
|
user,
|
|
5200
|
-
exuluConfig
|
|
5525
|
+
exuluConfig,
|
|
5526
|
+
ownerId
|
|
5201
5527
|
}) => {
|
|
5202
5528
|
if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
|
|
5203
5529
|
const viewDocumentPageExecute = async ({ filename, page, model }, options) => {
|
|
@@ -5230,8 +5556,7 @@ var init_view_document_page_tool = __esm({
|
|
|
5230
5556
|
}
|
|
5231
5557
|
}
|
|
5232
5558
|
const uploads = exuluConfig.fileUploads;
|
|
5233
|
-
const
|
|
5234
|
-
const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
|
|
5559
|
+
const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
|
|
5235
5560
|
const pageNumber = page ?? 1;
|
|
5236
5561
|
try {
|
|
5237
5562
|
let imageBytes;
|
|
@@ -5305,13 +5630,13 @@ var init_view_document_page_tool = __esm({
|
|
|
5305
5630
|
});
|
|
5306
5631
|
|
|
5307
5632
|
// src/exulu/audit/config.ts
|
|
5308
|
-
var import_os,
|
|
5633
|
+
var import_os, import_path2, normalizePrefix, hasAllS3Fields, resolveAuditConfig;
|
|
5309
5634
|
var init_config = __esm({
|
|
5310
5635
|
"src/exulu/audit/config.ts"() {
|
|
5311
5636
|
"use strict";
|
|
5312
5637
|
init_cjs_shims();
|
|
5313
5638
|
import_os = __toESM(require("os"), 1);
|
|
5314
|
-
|
|
5639
|
+
import_path2 = __toESM(require("path"), 1);
|
|
5315
5640
|
normalizePrefix = (p) => {
|
|
5316
5641
|
const raw = (p ?? "audit").trim().replace(/^\/+|\/+$/g, "");
|
|
5317
5642
|
return `${raw || "audit"}/`;
|
|
@@ -5343,7 +5668,7 @@ var init_config = __esm({
|
|
|
5343
5668
|
retentionDays: a.retentionDays,
|
|
5344
5669
|
manageLifecycle: a.manageLifecycle ?? !usingSharedFileUploadsBucket,
|
|
5345
5670
|
usingSharedFileUploadsBucket,
|
|
5346
|
-
spoolDir: a.spoolDir ??
|
|
5671
|
+
spoolDir: a.spoolDir ?? import_path2.default.join(import_os.default.tmpdir(), "exulu-audit-spool"),
|
|
5347
5672
|
flush: {
|
|
5348
5673
|
maxRecords: a.flush?.maxRecords ?? 100,
|
|
5349
5674
|
maxIntervalMs: a.flush?.maxIntervalMs ?? 5e3
|
|
@@ -5464,29 +5789,29 @@ ${JSON.stringify(config, null, 2)}`
|
|
|
5464
5789
|
});
|
|
5465
5790
|
|
|
5466
5791
|
// src/exulu/audit/sink.ts
|
|
5467
|
-
var import_crypto2,
|
|
5792
|
+
var import_crypto2, import_fs2, import_path3, createFsSpoolStore, pad, AuditSink;
|
|
5468
5793
|
var init_sink = __esm({
|
|
5469
5794
|
"src/exulu/audit/sink.ts"() {
|
|
5470
5795
|
"use strict";
|
|
5471
5796
|
init_cjs_shims();
|
|
5472
5797
|
import_crypto2 = require("crypto");
|
|
5473
|
-
|
|
5474
|
-
|
|
5798
|
+
import_fs2 = require("fs");
|
|
5799
|
+
import_path3 = __toESM(require("path"), 1);
|
|
5475
5800
|
createFsSpoolStore = (dir) => ({
|
|
5476
5801
|
write: async (name, body) => {
|
|
5477
|
-
await
|
|
5478
|
-
await
|
|
5802
|
+
await import_fs2.promises.mkdir(dir, { recursive: true });
|
|
5803
|
+
await import_fs2.promises.writeFile(import_path3.default.join(dir, name), body, "utf8");
|
|
5479
5804
|
},
|
|
5480
5805
|
list: async () => {
|
|
5481
5806
|
try {
|
|
5482
|
-
return (await
|
|
5807
|
+
return (await import_fs2.promises.readdir(dir)).filter((f) => f.endsWith(".ndjson"));
|
|
5483
5808
|
} catch {
|
|
5484
5809
|
return [];
|
|
5485
5810
|
}
|
|
5486
5811
|
},
|
|
5487
|
-
read: async (name) =>
|
|
5812
|
+
read: async (name) => import_fs2.promises.readFile(import_path3.default.join(dir, name), "utf8"),
|
|
5488
5813
|
remove: async (name) => {
|
|
5489
|
-
await
|
|
5814
|
+
await import_fs2.promises.rm(import_path3.default.join(dir, name), { force: true });
|
|
5490
5815
|
}
|
|
5491
5816
|
});
|
|
5492
5817
|
pad = (n) => String(n).padStart(2, "0");
|
|
@@ -6032,7 +6357,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
6032
6357
|
await Promise.all(promises2);
|
|
6033
6358
|
return tool4;
|
|
6034
6359
|
};
|
|
6035
|
-
convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools) => {
|
|
6360
|
+
convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools, sessionOwnerId) => {
|
|
6036
6361
|
if (!currentTools) return {};
|
|
6037
6362
|
if (!allExuluTools) {
|
|
6038
6363
|
allExuluTools = [];
|
|
@@ -6049,7 +6374,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
6049
6374
|
sessionID,
|
|
6050
6375
|
currentSkills || [],
|
|
6051
6376
|
exuluConfig,
|
|
6052
|
-
user?.id
|
|
6377
|
+
sessionOwnerId ?? user?.id
|
|
6053
6378
|
);
|
|
6054
6379
|
} catch (err) {
|
|
6055
6380
|
console.error(
|
|
@@ -6114,15 +6439,15 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
6114
6439
|
currentTools.push(sessionItemsRetrievalTool);
|
|
6115
6440
|
}
|
|
6116
6441
|
}
|
|
6117
|
-
const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig });
|
|
6442
|
+
const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
|
|
6118
6443
|
if (sessionFileReadTool && !disabled.has(sessionFileReadTool.id)) {
|
|
6119
6444
|
currentTools.push(sessionFileReadTool);
|
|
6120
6445
|
}
|
|
6121
|
-
const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig });
|
|
6446
|
+
const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
|
|
6122
6447
|
if (parseDocumentTool && !disabled.has(parseDocumentTool.id)) {
|
|
6123
6448
|
currentTools.push(parseDocumentTool);
|
|
6124
6449
|
}
|
|
6125
|
-
const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig });
|
|
6450
|
+
const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
|
|
6126
6451
|
if (viewDocumentPageTool && !disabled.has(viewDocumentPageTool.id)) {
|
|
6127
6452
|
currentTools.push(viewDocumentPageTool);
|
|
6128
6453
|
}
|
|
@@ -16786,7 +17111,59 @@ function dropEmptyMessages(messages) {
|
|
|
16786
17111
|
return kept.length === messages.length ? messages : kept;
|
|
16787
17112
|
}
|
|
16788
17113
|
|
|
17114
|
+
// src/exulu/stored-file-url.ts
|
|
17115
|
+
init_cjs_shims();
|
|
17116
|
+
var stripTrailingSlash = (s) => s.replace(/\/+$/, "");
|
|
17117
|
+
function decodeKey(rawPath) {
|
|
17118
|
+
const key = rawPath.replace(/^\/+/, "");
|
|
17119
|
+
try {
|
|
17120
|
+
return decodeURIComponent(key);
|
|
17121
|
+
} catch {
|
|
17122
|
+
return key;
|
|
17123
|
+
}
|
|
17124
|
+
}
|
|
17125
|
+
function parseStoredFileUrl(url, store) {
|
|
17126
|
+
if (!url || !store.bucket) return void 0;
|
|
17127
|
+
let parsed;
|
|
17128
|
+
try {
|
|
17129
|
+
parsed = new URL(url);
|
|
17130
|
+
} catch {
|
|
17131
|
+
return void 0;
|
|
17132
|
+
}
|
|
17133
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
|
|
17134
|
+
if (store.endpoint) {
|
|
17135
|
+
let endpoint;
|
|
17136
|
+
try {
|
|
17137
|
+
endpoint = new URL(store.endpoint);
|
|
17138
|
+
} catch {
|
|
17139
|
+
return void 0;
|
|
17140
|
+
}
|
|
17141
|
+
if (parsed.host !== endpoint.host) return void 0;
|
|
17142
|
+
const base = stripTrailingSlash(endpoint.pathname);
|
|
17143
|
+
const prefix = `${base}/${store.bucket}/`;
|
|
17144
|
+
if (!parsed.pathname.startsWith(prefix)) return void 0;
|
|
17145
|
+
const key2 = decodeKey(parsed.pathname.slice(prefix.length));
|
|
17146
|
+
return key2 ? { bucket: store.bucket, key: key2 } : void 0;
|
|
17147
|
+
}
|
|
17148
|
+
if (!parsed.host.startsWith(`${store.bucket}.s3`) || !parsed.host.endsWith(".amazonaws.com")) {
|
|
17149
|
+
return void 0;
|
|
17150
|
+
}
|
|
17151
|
+
const key = decodeKey(parsed.pathname);
|
|
17152
|
+
return key ? { bucket: store.bucket, key } : void 0;
|
|
17153
|
+
}
|
|
17154
|
+
async function resolveFreshFileUrl(url, opts) {
|
|
17155
|
+
const location = parseStoredFileUrl(url, opts);
|
|
17156
|
+
if (!location) return url;
|
|
17157
|
+
try {
|
|
17158
|
+
return await opts.sign(location.bucket, location.key);
|
|
17159
|
+
} catch (err) {
|
|
17160
|
+
console.warn(`[EXULU] could not re-sign stored file URL for key "${location.key}":`, err);
|
|
17161
|
+
return url;
|
|
17162
|
+
}
|
|
17163
|
+
}
|
|
17164
|
+
|
|
16789
17165
|
// src/exulu/generate-stream.ts
|
|
17166
|
+
init_uppy();
|
|
16790
17167
|
var import_ai6 = require("ai");
|
|
16791
17168
|
|
|
16792
17169
|
// src/exulu/context-guard.ts
|
|
@@ -17104,7 +17481,12 @@ var processFilePartsInMessages = async (messages, offloadCtx) => {
|
|
|
17104
17481
|
return part;
|
|
17105
17482
|
}
|
|
17106
17483
|
console.log(`[EXULU] Processing part`, part);
|
|
17107
|
-
const
|
|
17484
|
+
const uploads = offloadCtx.exuluConfig?.fileUploads;
|
|
17485
|
+
const url = uploads?.s3Bucket ? await resolveFreshFileUrl(part.url, {
|
|
17486
|
+
endpoint: uploads.s3endpoint,
|
|
17487
|
+
bucket: uploads.s3Bucket,
|
|
17488
|
+
sign: (bucket, key) => getPresignedUrl(bucket, key, offloadCtx.exuluConfig)
|
|
17489
|
+
}) : part.url;
|
|
17108
17490
|
const classified = classifyFilePart(part);
|
|
17109
17491
|
console.log(`[EXULU] File part classified as ${classified.kind}: ${classified.filename}`);
|
|
17110
17492
|
if (classified.kind === "image") {
|
|
@@ -17235,10 +17617,12 @@ var generateSync = async ({
|
|
|
17235
17617
|
}
|
|
17236
17618
|
let project;
|
|
17237
17619
|
let sessionItems;
|
|
17620
|
+
let sessionOwnerId;
|
|
17238
17621
|
if (session) {
|
|
17239
17622
|
const sessionData = await getSession({ sessionID: session });
|
|
17240
17623
|
sessionItems = sessionData.session_items;
|
|
17241
17624
|
project = sessionData.project;
|
|
17625
|
+
sessionOwnerId = sessionData.user ?? void 0;
|
|
17242
17626
|
}
|
|
17243
17627
|
const model = languageModel;
|
|
17244
17628
|
console.log("[EXULU] Model created for generating sync.");
|
|
@@ -17342,7 +17726,8 @@ var generateSync = async ({
|
|
|
17342
17726
|
agent,
|
|
17343
17727
|
memoryItems,
|
|
17344
17728
|
contextWindow,
|
|
17345
|
-
disabledTools
|
|
17729
|
+
disabledTools,
|
|
17730
|
+
sessionOwnerId
|
|
17346
17731
|
);
|
|
17347
17732
|
const agenticEntry = currentTools?.find((t) => t.id === "agentic_context_search");
|
|
17348
17733
|
const agenticToolKey = agenticEntry ? sanitizeToolName(agenticEntry.name) : void 0;
|
|
@@ -17577,10 +17962,12 @@ var generateStream = async ({
|
|
|
17577
17962
|
let previousMessagesContent = previousMessages || [];
|
|
17578
17963
|
let project;
|
|
17579
17964
|
let sessionItems;
|
|
17965
|
+
let sessionOwnerId;
|
|
17580
17966
|
if (session) {
|
|
17581
17967
|
const sessionData = await getSession({ sessionID: session });
|
|
17582
17968
|
project = sessionData.project;
|
|
17583
17969
|
sessionItems = sessionData.session_items;
|
|
17970
|
+
sessionOwnerId = sessionData.user ?? void 0;
|
|
17584
17971
|
console.log("[EXULU] loading previous messages from session: " + session);
|
|
17585
17972
|
const previousMessages2 = await getAgentMessages({
|
|
17586
17973
|
session,
|
|
@@ -17760,7 +18147,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
17760
18147
|
agent,
|
|
17761
18148
|
memoryItems,
|
|
17762
18149
|
contextWindow,
|
|
17763
|
-
disabledTools
|
|
18150
|
+
disabledTools,
|
|
18151
|
+
sessionOwnerId
|
|
17764
18152
|
);
|
|
17765
18153
|
console.log("[EXULU] Converted tools", Object.keys(tools));
|
|
17766
18154
|
const includesContextSearchTool = currentTools?.some(
|
|
@@ -19920,7 +20308,7 @@ var mapRoutineRunRow = (row, routineById) => {
|
|
|
19920
20308
|
|
|
19921
20309
|
// src/graphql/schemas/index.ts
|
|
19922
20310
|
init_entitlements();
|
|
19923
|
-
var
|
|
20311
|
+
var import_fs3 = require("fs");
|
|
19924
20312
|
|
|
19925
20313
|
// src/exulu/transcription/service.ts
|
|
19926
20314
|
init_cjs_shims();
|
|
@@ -23926,7 +24314,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
|
|
|
23926
24314
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
23927
24315
|
var import_crypto_js7 = require("crypto-js");
|
|
23928
24316
|
var import_openai = require("openai");
|
|
23929
|
-
var
|
|
24317
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
23930
24318
|
var import_node_crypto15 = require("crypto");
|
|
23931
24319
|
var import_api2 = require("@opentelemetry/api");
|
|
23932
24320
|
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
@@ -25263,7 +25651,7 @@ var REQUEST_SIZE_LIMIT = "50mb";
|
|
|
25263
25651
|
var getExuluVersionNumber = async () => {
|
|
25264
25652
|
try {
|
|
25265
25653
|
const path4 = process.cwd();
|
|
25266
|
-
const packageJson =
|
|
25654
|
+
const packageJson = import_fs4.default.readFileSync(path4 + "/package.json", "utf8");
|
|
25267
25655
|
const packageData = JSON.parse(packageJson);
|
|
25268
25656
|
const exuluVersion = packageData.dependencies["@exulu/backend"];
|
|
25269
25657
|
console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
|
|
@@ -30244,210 +30632,7 @@ init_singleton();
|
|
|
30244
30632
|
init_entitlements();
|
|
30245
30633
|
init_system_dependencies();
|
|
30246
30634
|
init_supervisor();
|
|
30247
|
-
|
|
30248
|
-
// src/utils/python-setup.ts
|
|
30249
|
-
init_cjs_shims();
|
|
30250
|
-
var import_child_process = require("child_process");
|
|
30251
|
-
var import_util = require("util");
|
|
30252
|
-
var import_path3 = require("path");
|
|
30253
|
-
var import_fs4 = require("fs");
|
|
30254
|
-
var import_url = require("url");
|
|
30255
|
-
var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
|
|
30256
|
-
function getPackageRoot() {
|
|
30257
|
-
const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
30258
|
-
let currentDir = (0, import_path3.dirname)(currentFile);
|
|
30259
|
-
let attempts = 0;
|
|
30260
|
-
const maxAttempts = 10;
|
|
30261
|
-
while (attempts < maxAttempts) {
|
|
30262
|
-
const packageJsonPath = (0, import_path3.join)(currentDir, "package.json");
|
|
30263
|
-
if ((0, import_fs4.existsSync)(packageJsonPath)) {
|
|
30264
|
-
try {
|
|
30265
|
-
const packageJson = JSON.parse((0, import_fs4.readFileSync)(packageJsonPath, "utf-8"));
|
|
30266
|
-
if (packageJson.name === "@exulu/backend") {
|
|
30267
|
-
return currentDir;
|
|
30268
|
-
}
|
|
30269
|
-
} catch {
|
|
30270
|
-
}
|
|
30271
|
-
}
|
|
30272
|
-
const parentDir = (0, import_path3.resolve)(currentDir, "..");
|
|
30273
|
-
if (parentDir === currentDir) {
|
|
30274
|
-
break;
|
|
30275
|
-
}
|
|
30276
|
-
currentDir = parentDir;
|
|
30277
|
-
attempts++;
|
|
30278
|
-
}
|
|
30279
|
-
const fallback = (0, import_path3.resolve)((0, import_path3.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
|
|
30280
|
-
return fallback;
|
|
30281
|
-
}
|
|
30282
|
-
function getSetupScriptPath(packageRoot) {
|
|
30283
|
-
return (0, import_path3.resolve)(packageRoot, "ee/python/setup.sh");
|
|
30284
|
-
}
|
|
30285
|
-
function getVenvPath(packageRoot) {
|
|
30286
|
-
return (0, import_path3.resolve)(packageRoot, "ee/python/.venv");
|
|
30287
|
-
}
|
|
30288
|
-
function isPythonEnvironmentSetup(packageRoot) {
|
|
30289
|
-
const root = packageRoot ?? getPackageRoot();
|
|
30290
|
-
const venvPath = getVenvPath(root);
|
|
30291
|
-
const pythonPath = (0, import_path3.join)(venvPath, "bin", "python");
|
|
30292
|
-
return (0, import_fs4.existsSync)(venvPath) && (0, import_fs4.existsSync)(pythonPath);
|
|
30293
|
-
}
|
|
30294
|
-
async function setupPythonEnvironment(options = {}) {
|
|
30295
|
-
const {
|
|
30296
|
-
packageRoot = getPackageRoot(),
|
|
30297
|
-
force = false,
|
|
30298
|
-
verbose = false,
|
|
30299
|
-
timeout = 6e5
|
|
30300
|
-
// 10 minutes
|
|
30301
|
-
} = options;
|
|
30302
|
-
if (!force && isPythonEnvironmentSetup(packageRoot)) {
|
|
30303
|
-
if (verbose) {
|
|
30304
|
-
console.log("\u2713 Python environment already set up");
|
|
30305
|
-
}
|
|
30306
|
-
return {
|
|
30307
|
-
success: true,
|
|
30308
|
-
message: "Python environment already exists",
|
|
30309
|
-
alreadyExists: true
|
|
30310
|
-
};
|
|
30311
|
-
}
|
|
30312
|
-
const setupScriptPath = getSetupScriptPath(packageRoot);
|
|
30313
|
-
if (!(0, import_fs4.existsSync)(setupScriptPath)) {
|
|
30314
|
-
return {
|
|
30315
|
-
success: false,
|
|
30316
|
-
message: `Setup script not found at: ${setupScriptPath}`,
|
|
30317
|
-
alreadyExists: false
|
|
30318
|
-
};
|
|
30319
|
-
}
|
|
30320
|
-
try {
|
|
30321
|
-
if (verbose) {
|
|
30322
|
-
console.log("Setting up Python environment...");
|
|
30323
|
-
}
|
|
30324
|
-
const { stdout, stderr } = await execAsync4(`bash "${setupScriptPath}"`, {
|
|
30325
|
-
cwd: packageRoot,
|
|
30326
|
-
timeout,
|
|
30327
|
-
env: {
|
|
30328
|
-
...process.env,
|
|
30329
|
-
// Ensure script can write to the directory
|
|
30330
|
-
PYTHONDONTWRITEBYTECODE: "1"
|
|
30331
|
-
},
|
|
30332
|
-
maxBuffer: 10 * 1024 * 1024
|
|
30333
|
-
// 10MB buffer
|
|
30334
|
-
});
|
|
30335
|
-
const output = stdout + stderr;
|
|
30336
|
-
const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
30337
|
-
const pythonVersion = versionMatch ? versionMatch[1] : void 0;
|
|
30338
|
-
if (verbose) {
|
|
30339
|
-
console.log(output);
|
|
30340
|
-
}
|
|
30341
|
-
return {
|
|
30342
|
-
success: true,
|
|
30343
|
-
message: "Python environment set up successfully",
|
|
30344
|
-
alreadyExists: false,
|
|
30345
|
-
pythonVersion,
|
|
30346
|
-
output
|
|
30347
|
-
};
|
|
30348
|
-
} catch (error) {
|
|
30349
|
-
const errorOutput = error.stdout + error.stderr;
|
|
30350
|
-
return {
|
|
30351
|
-
success: false,
|
|
30352
|
-
message: `Setup failed: ${error.message}`,
|
|
30353
|
-
alreadyExists: false,
|
|
30354
|
-
output: errorOutput
|
|
30355
|
-
};
|
|
30356
|
-
}
|
|
30357
|
-
}
|
|
30358
|
-
function getPythonSetupInstructions() {
|
|
30359
|
-
return `
|
|
30360
|
-
Python environment not set up. Please run one of the following commands:
|
|
30361
|
-
|
|
30362
|
-
Option 1 (Automatic):
|
|
30363
|
-
import { setupPythonEnvironment } from '@exulu/backend';
|
|
30364
|
-
await setupPythonEnvironment();
|
|
30365
|
-
|
|
30366
|
-
Option 2 (Manual - for package consumers):
|
|
30367
|
-
npx @exulu/backend setup-python
|
|
30368
|
-
|
|
30369
|
-
Option 3 (Manual - for contributors):
|
|
30370
|
-
npm run python:setup
|
|
30371
|
-
|
|
30372
|
-
These commands will automatically create a Python virtual environment (.venv)
|
|
30373
|
-
in the @exulu/backend package and install all required dependencies.
|
|
30374
|
-
|
|
30375
|
-
Requirements:
|
|
30376
|
-
- Python 3.10 or higher must be installed
|
|
30377
|
-
- pip must be available
|
|
30378
|
-
- venv module must be available (for creating virtual environments)
|
|
30379
|
-
|
|
30380
|
-
If Python dependencies are not installed, install them first, then run one of the commands above:
|
|
30381
|
-
- macOS: brew install python@3.12
|
|
30382
|
-
- Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
|
|
30383
|
-
- Alpine Linux: apk add python3 py3-pip python3-dev
|
|
30384
|
-
- Windows: Download from https://www.python.org/downloads/
|
|
30385
|
-
|
|
30386
|
-
Note: In Docker containers, ensure you install all three components:
|
|
30387
|
-
Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
|
|
30388
|
-
Alpine: apk add python3 py3-pip python3-dev
|
|
30389
|
-
`.trim();
|
|
30390
|
-
}
|
|
30391
|
-
async function validatePythonEnvironment(packageRoot, checkPackages = true) {
|
|
30392
|
-
const root = packageRoot ?? getPackageRoot();
|
|
30393
|
-
const venvPath = getVenvPath(root);
|
|
30394
|
-
const pythonPath = (0, import_path3.join)(venvPath, "bin", "python");
|
|
30395
|
-
if (!(0, import_fs4.existsSync)(venvPath)) {
|
|
30396
|
-
return {
|
|
30397
|
-
valid: false,
|
|
30398
|
-
message: getPythonSetupInstructions()
|
|
30399
|
-
};
|
|
30400
|
-
}
|
|
30401
|
-
if (!(0, import_fs4.existsSync)(pythonPath)) {
|
|
30402
|
-
return {
|
|
30403
|
-
valid: false,
|
|
30404
|
-
message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
30405
|
-
};
|
|
30406
|
-
}
|
|
30407
|
-
try {
|
|
30408
|
-
await execAsync4(`"${pythonPath}" --version`, { cwd: root });
|
|
30409
|
-
} catch {
|
|
30410
|
-
return {
|
|
30411
|
-
valid: false,
|
|
30412
|
-
message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
|
|
30413
|
-
};
|
|
30414
|
-
}
|
|
30415
|
-
if (checkPackages) {
|
|
30416
|
-
const criticalPackages = ["docling", "transformers"];
|
|
30417
|
-
const missingPackages = [];
|
|
30418
|
-
for (const pkg of criticalPackages) {
|
|
30419
|
-
try {
|
|
30420
|
-
await execAsync4(`"${pythonPath}" -c "import ${pkg}"`, {
|
|
30421
|
-
cwd: root,
|
|
30422
|
-
timeout: 1e4
|
|
30423
|
-
// 10 second timeout per import check
|
|
30424
|
-
});
|
|
30425
|
-
} catch {
|
|
30426
|
-
missingPackages.push(pkg);
|
|
30427
|
-
}
|
|
30428
|
-
}
|
|
30429
|
-
if (missingPackages.length > 0) {
|
|
30430
|
-
return {
|
|
30431
|
-
valid: false,
|
|
30432
|
-
message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
|
|
30433
|
-
|
|
30434
|
-
This usually happens when:
|
|
30435
|
-
1. The .venv folder was copied but dependencies were not installed
|
|
30436
|
-
2. The package was installed via npm but setup script was not run
|
|
30437
|
-
|
|
30438
|
-
Please run:
|
|
30439
|
-
await setupPythonEnvironment({ force: true })
|
|
30440
|
-
|
|
30441
|
-
Or manually run the setup script:
|
|
30442
|
-
bash ` + getSetupScriptPath(root)
|
|
30443
|
-
};
|
|
30444
|
-
}
|
|
30445
|
-
}
|
|
30446
|
-
return {
|
|
30447
|
-
valid: true,
|
|
30448
|
-
message: "Python environment is valid"
|
|
30449
|
-
};
|
|
30450
|
-
}
|
|
30635
|
+
init_python_setup();
|
|
30451
30636
|
|
|
30452
30637
|
// src/templates/contexts/index.ts
|
|
30453
30638
|
init_cjs_shims();
|
|
@@ -32436,6 +32621,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
32436
32621
|
};
|
|
32437
32622
|
|
|
32438
32623
|
// src/postgres/init-litellm-db.ts
|
|
32624
|
+
init_python_setup();
|
|
32439
32625
|
var initLitellmDb = async () => {
|
|
32440
32626
|
await initLiteLLMDatabase(getPackageRoot());
|
|
32441
32627
|
console.log("[EXULU] LiteLLM database initialized.");
|
|
@@ -32970,6 +33156,9 @@ var MarkdownChunker = class {
|
|
|
32970
33156
|
}
|
|
32971
33157
|
};
|
|
32972
33158
|
|
|
33159
|
+
// src/index.ts
|
|
33160
|
+
init_python_setup();
|
|
33161
|
+
|
|
32973
33162
|
// ee/python/documents/processing/doc_processor.ts
|
|
32974
33163
|
init_cjs_shims();
|
|
32975
33164
|
var fs4 = __toESM(require("fs"), 1);
|
|
@@ -32992,6 +33181,7 @@ var import_util3 = require("util");
|
|
|
32992
33181
|
var import_path4 = require("path");
|
|
32993
33182
|
var import_fs5 = require("fs");
|
|
32994
33183
|
var import_url2 = require("url");
|
|
33184
|
+
init_python_setup();
|
|
32995
33185
|
var execAsync5 = (0, import_util3.promisify)(import_child_process2.exec);
|
|
32996
33186
|
function getPackageRoot2() {
|
|
32997
33187
|
const currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
|
|
@@ -33127,6 +33317,7 @@ ${command}`;
|
|
|
33127
33317
|
}
|
|
33128
33318
|
|
|
33129
33319
|
// ee/python/documents/processing/doc_processor.ts
|
|
33320
|
+
init_python_setup();
|
|
33130
33321
|
var import_liteparse = require("@llamaindex/liteparse");
|
|
33131
33322
|
|
|
33132
33323
|
// src/exulu/resolve-ocr.ts
|