@exulu/backend 1.68.0 → 1.69.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-VPSLTGZF.js → chunk-PJSDLFXL.js} +167 -48
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-CHQF36XW.js → convert-exulu-tools-to-ai-sdk-tools-MXLGIOCT.js} +1 -1
- package/dist/index.cjs +592 -113
- package/dist/index.d.cts +60 -2
- package/dist/index.d.ts +60 -2
- package/dist/index.js +394 -52
- package/ee/python/documents/processing/doc_processor.ts +61 -10
- package/ee/python/documents/processing/split_pdf.py +97 -0
- package/ee/queues/queues.ts +10 -0
- package/ee/queues/redis-startup.ts +121 -0
- package/ee/workers.ts +6 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -160,9 +160,9 @@ async function postgresClient() {
|
|
|
160
160
|
// Log pool events to help debug connection issues
|
|
161
161
|
afterCreate: (conn, done) => {
|
|
162
162
|
console.log("[EXULU] New database connection created");
|
|
163
|
-
conn.query("SET statement_timeout = 1800000", (err) => {
|
|
163
|
+
conn.query("SET statement_timeout = 1800000; SET hnsw.ef_search = 20", (err) => {
|
|
164
164
|
if (err) {
|
|
165
|
-
console.error("[EXULU] Error setting
|
|
165
|
+
console.error("[EXULU] Error setting connection parameters:", err);
|
|
166
166
|
}
|
|
167
167
|
done(err, conn);
|
|
168
168
|
});
|
|
@@ -1351,6 +1351,18 @@ var init_sanitize_name = __esm({
|
|
|
1351
1351
|
}
|
|
1352
1352
|
});
|
|
1353
1353
|
|
|
1354
|
+
// src/exulu/table-names.ts
|
|
1355
|
+
var getTableName, getChunksTableName;
|
|
1356
|
+
var init_table_names = __esm({
|
|
1357
|
+
"src/exulu/table-names.ts"() {
|
|
1358
|
+
"use strict";
|
|
1359
|
+
init_cjs_shims();
|
|
1360
|
+
init_sanitize_name();
|
|
1361
|
+
getTableName = (id) => sanitizeName(id) + "_items";
|
|
1362
|
+
getChunksTableName = (id) => sanitizeName(id) + "_chunks";
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1354
1366
|
// ee/tokenizer.ts
|
|
1355
1367
|
var import_lite, import_load, import_registry, import_model_to_encoding, ExuluTokenizer;
|
|
1356
1368
|
var init_tokenizer = __esm({
|
|
@@ -2052,7 +2064,7 @@ var init_chunker = __esm({
|
|
|
2052
2064
|
});
|
|
2053
2065
|
|
|
2054
2066
|
// src/exulu/litellm/supervisor.ts
|
|
2055
|
-
var import_node_child_process, import_node_fs3, import_node_path2, LITELLM_UI_PATH, MAX_CRASHES, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, READY_TIMEOUT_MS, WAIT_TIMEOUT_MS, READY_POLL_INTERVAL_MS, SHUTDOWN_GRACE_MS, internal, isLiteLLMEnabled, resolveConfig,
|
|
2067
|
+
var import_node_child_process, import_node_fs3, import_node_path2, LITELLM_UI_PATH, MAX_CRASHES, INITIAL_BACKOFF_MS, MAX_BACKOFF_MS, READY_TIMEOUT_MS, WAIT_TIMEOUT_MS, READY_POLL_INTERVAL_MS, SHUTDOWN_GRACE_MS, internal, isLiteLLMEnabled, resolveConfig, log2, pollHealth, spawnLiteLLM, supervise, _packageRoot, _clientMode, setLiteLLMPackageRoot, enableLiteLLMClientMode, startLiteLLMSupervisor, waitForLiteLLMReady, stopLiteLLM, shutdownHandlersRegistered, registerShutdownHandlers, getSupervisorState;
|
|
2056
2068
|
var init_supervisor = __esm({
|
|
2057
2069
|
"src/exulu/litellm/supervisor.ts"() {
|
|
2058
2070
|
"use strict";
|
|
@@ -2087,7 +2099,7 @@ var init_supervisor = __esm({
|
|
|
2087
2099
|
const litellmBin = (0, import_node_path2.resolve)(venvBin, "litellm");
|
|
2088
2100
|
return { host, port, masterKey, configPath, venvBin, venvPython, litellmBin };
|
|
2089
2101
|
};
|
|
2090
|
-
|
|
2102
|
+
log2 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
2091
2103
|
pollHealth = async (host, port) => {
|
|
2092
2104
|
const url = `http://${host}:${port}/health/liveliness`;
|
|
2093
2105
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
@@ -2104,7 +2116,7 @@ var init_supervisor = __esm({
|
|
|
2104
2116
|
);
|
|
2105
2117
|
};
|
|
2106
2118
|
spawnLiteLLM = (cfg) => {
|
|
2107
|
-
|
|
2119
|
+
log2(
|
|
2108
2120
|
`Spawning LiteLLM: ${cfg.litellmBin} --config ${cfg.configPath} --port ${cfg.port} --host ${cfg.host}`
|
|
2109
2121
|
);
|
|
2110
2122
|
const { DEBUG: _debug, ...rest } = process.env;
|
|
@@ -2131,10 +2143,10 @@ var init_supervisor = __esm({
|
|
|
2131
2143
|
}
|
|
2132
2144
|
);
|
|
2133
2145
|
child.stdout?.on("data", (chunk) => {
|
|
2134
|
-
chunk.toString().split("\n").filter((l) => l.length > 0).forEach((l) =>
|
|
2146
|
+
chunk.toString().split("\n").filter((l) => l.length > 0).forEach((l) => log2(l));
|
|
2135
2147
|
});
|
|
2136
2148
|
child.stderr?.on("data", (chunk) => {
|
|
2137
|
-
chunk.toString().split("\n").filter((l) => l.length > 0).forEach((l) =>
|
|
2149
|
+
chunk.toString().split("\n").filter((l) => l.length > 0).forEach((l) => log2(`stderr: ${l}`));
|
|
2138
2150
|
});
|
|
2139
2151
|
return child;
|
|
2140
2152
|
};
|
|
@@ -2151,7 +2163,7 @@ var init_supervisor = __esm({
|
|
|
2151
2163
|
exitPromise.then((code2) => ({ exited: code2 }))
|
|
2152
2164
|
]);
|
|
2153
2165
|
} catch (err) {
|
|
2154
|
-
|
|
2166
|
+
log2(`Readiness probe failed: ${err.message}`);
|
|
2155
2167
|
try {
|
|
2156
2168
|
internal.child?.kill("SIGTERM");
|
|
2157
2169
|
} catch {
|
|
@@ -2161,22 +2173,22 @@ var init_supervisor = __esm({
|
|
|
2161
2173
|
internal.state = "ready";
|
|
2162
2174
|
internal.crashCount = 0;
|
|
2163
2175
|
internal.backoffMs = INITIAL_BACKOFF_MS;
|
|
2164
|
-
|
|
2176
|
+
log2("LiteLLM is ready.");
|
|
2165
2177
|
}
|
|
2166
2178
|
const code = await exitPromise;
|
|
2167
2179
|
internal.state = "respawning";
|
|
2168
2180
|
internal.child = void 0;
|
|
2169
2181
|
if (internal.shutdownRequested) {
|
|
2170
|
-
|
|
2182
|
+
log2("Child exited during shutdown; supervisor stopping.");
|
|
2171
2183
|
internal.state = "stopped";
|
|
2172
2184
|
return;
|
|
2173
2185
|
}
|
|
2174
2186
|
internal.crashCount += 1;
|
|
2175
|
-
|
|
2187
|
+
log2(
|
|
2176
2188
|
`LiteLLM exited (code=${code}). Crash ${internal.crashCount}/${MAX_CRASHES}. Respawning in ${internal.backoffMs}ms.`
|
|
2177
2189
|
);
|
|
2178
2190
|
if (internal.crashCount >= MAX_CRASHES) {
|
|
2179
|
-
|
|
2191
|
+
log2(
|
|
2180
2192
|
"LiteLLM keeps crashing \u2014 fix the config and restart Exulu. Giving up on respawn."
|
|
2181
2193
|
);
|
|
2182
2194
|
internal.state = "given_up";
|
|
@@ -2212,14 +2224,14 @@ var init_supervisor = __esm({
|
|
|
2212
2224
|
}
|
|
2213
2225
|
const cfg = resolveConfig(packageRoot);
|
|
2214
2226
|
if (!(0, import_node_fs3.existsSync)(cfg.configPath)) {
|
|
2215
|
-
|
|
2227
|
+
log2(
|
|
2216
2228
|
`LiteLLM config not found at ${cfg.configPath}. Copy ee/python/.litellm/config.yaml.example to that path, edit it, and restart Exulu. LiteLLM will NOT be started until then.`
|
|
2217
2229
|
);
|
|
2218
2230
|
internal.state = "given_up";
|
|
2219
2231
|
return;
|
|
2220
2232
|
}
|
|
2221
2233
|
if (!(0, import_node_fs3.existsSync)(cfg.litellmBin)) {
|
|
2222
|
-
|
|
2234
|
+
log2(
|
|
2223
2235
|
`LiteLLM binary not found at ${cfg.litellmBin}. The Python venv may not be set up. Run setupPythonEnvironment() from @exulu/backend, then restart.`
|
|
2224
2236
|
);
|
|
2225
2237
|
internal.state = "given_up";
|
|
@@ -2735,13 +2747,31 @@ function durationToDays(duration) {
|
|
|
2735
2747
|
return n;
|
|
2736
2748
|
}
|
|
2737
2749
|
}
|
|
2750
|
+
function subtractDuration(date, duration) {
|
|
2751
|
+
const m = /^\s*(\d+(?:\.\d+)?)\s*(mo|[a-z]+)?\s*$/i.exec(String(duration ?? ""));
|
|
2752
|
+
const unit = m ? (m[2] ?? "d").toLowerCase() : "d";
|
|
2753
|
+
const n = m ? parseFloat(m[1]) : NaN;
|
|
2754
|
+
if (unit === "mo" && Number.isFinite(n) && n > 0) {
|
|
2755
|
+
const result = new Date(date);
|
|
2756
|
+
result.setUTCMonth(result.getUTCMonth() - Math.round(n));
|
|
2757
|
+
return result;
|
|
2758
|
+
}
|
|
2759
|
+
return new Date(date.getTime() - durationToDays(duration) * DAY_MS);
|
|
2760
|
+
}
|
|
2738
2761
|
function windowStartYmd(reset_at, duration) {
|
|
2739
|
-
const
|
|
2762
|
+
const now = Date.now();
|
|
2740
2763
|
const reset = reset_at ? new Date(reset_at) : null;
|
|
2741
|
-
const
|
|
2742
|
-
const trailingStart = new Date(
|
|
2743
|
-
|
|
2744
|
-
|
|
2764
|
+
const resetMs = reset && !Number.isNaN(reset.getTime()) ? reset.getTime() : null;
|
|
2765
|
+
const trailingStart = subtractDuration(new Date(now), duration);
|
|
2766
|
+
if (resetMs !== null) {
|
|
2767
|
+
if (resetMs > now) {
|
|
2768
|
+
const periodStart = subtractDuration(reset, duration);
|
|
2769
|
+
return ymd(periodStart > trailingStart ? periodStart : trailingStart);
|
|
2770
|
+
} else {
|
|
2771
|
+
return ymd(reset > trailingStart ? reset : trailingStart);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return ymd(trailingStart);
|
|
2745
2775
|
}
|
|
2746
2776
|
async function enrichSpendFromActivity(map) {
|
|
2747
2777
|
const names = Object.keys(map);
|
|
@@ -2752,7 +2782,10 @@ async function enrichSpendFromActivity(map) {
|
|
|
2752
2782
|
windows[name] = windowStartYmd(ti.budget_reset_at, ti.budget_duration);
|
|
2753
2783
|
}
|
|
2754
2784
|
try {
|
|
2755
|
-
const spendByTag = await getTagSpendByWindow(
|
|
2785
|
+
const spendByTag = await getTagSpendByWindow(
|
|
2786
|
+
windows,
|
|
2787
|
+
ymd(new Date(Date.now() + DAY_MS))
|
|
2788
|
+
);
|
|
2756
2789
|
for (const name of names) {
|
|
2757
2790
|
const spend = spendByTag[name];
|
|
2758
2791
|
if (typeof spend === "number" && Number.isFinite(spend)) {
|
|
@@ -2876,6 +2909,7 @@ async function getUserBudgetView(userId) {
|
|
|
2876
2909
|
readCache.set(tag, { expiry: Date.now() + READ_TTL_MS, view: null });
|
|
2877
2910
|
return null;
|
|
2878
2911
|
}
|
|
2912
|
+
await provisionDefaultUserBudget(userId);
|
|
2879
2913
|
const info = await tagInfo([tag]);
|
|
2880
2914
|
const ti = info[tag];
|
|
2881
2915
|
if (ti?.max_budget != null) {
|
|
@@ -3847,7 +3881,7 @@ var init_entitlements = __esm({
|
|
|
3847
3881
|
});
|
|
3848
3882
|
|
|
3849
3883
|
// src/postgres/core-schema.ts
|
|
3850
|
-
var agentMessagesSchema, agentSessionsSchema, skillsSchema, variablesSchema, projectsSchema, agentsSchema, modelsSchema, usersSchema, platformConfigurationsSchema, entityTypeSettingsSchema, promptLibrarySchema, promptFavoritesSchema, transcriptionJobsSchema, imageGenerationsSchema, oauthTokensSchema, contextPresetsSchema, addCoreFields, coreSchemas;
|
|
3884
|
+
var agentMessagesSchema, agentSessionsSchema, skillsSchema, variablesSchema, projectsSchema, agentsSchema, modelsSchema, usersSchema, platformConfigurationsSchema, entityTypeSettingsSchema, promptLibrarySchema, promptFavoritesSchema, transcriptionJobsSchema, imageGenerationsSchema, oauthTokensSchema, sharedArtifactsSchema, contextPresetsSchema, addCoreFields, coreSchemas;
|
|
3851
3885
|
var init_core_schema = __esm({
|
|
3852
3886
|
"src/postgres/core-schema.ts"() {
|
|
3853
3887
|
"use strict";
|
|
@@ -4112,6 +4146,11 @@ var init_core_schema = __esm({
|
|
|
4112
4146
|
{
|
|
4113
4147
|
name: "animation_responding",
|
|
4114
4148
|
type: "text"
|
|
4149
|
+
},
|
|
4150
|
+
{
|
|
4151
|
+
name: "sandbox_enabled",
|
|
4152
|
+
type: "boolean",
|
|
4153
|
+
default: false
|
|
4115
4154
|
}
|
|
4116
4155
|
]
|
|
4117
4156
|
};
|
|
@@ -4517,6 +4556,26 @@ var init_core_schema = __esm({
|
|
|
4517
4556
|
// null = non-expiring
|
|
4518
4557
|
]
|
|
4519
4558
|
};
|
|
4559
|
+
sharedArtifactsSchema = {
|
|
4560
|
+
type: "shared_artifacts",
|
|
4561
|
+
name: {
|
|
4562
|
+
plural: "shared_artifacts",
|
|
4563
|
+
singular: "shared_artifact"
|
|
4564
|
+
},
|
|
4565
|
+
// RBAC drives the "regular" auth_mode: rights_mode + the rbac table scope
|
|
4566
|
+
// who may view. public/password modes ignore rights_mode.
|
|
4567
|
+
RBAC: true,
|
|
4568
|
+
fields: [
|
|
4569
|
+
{ name: "name", type: "text", index: true, unique: true, required: true },
|
|
4570
|
+
{ name: "s3key", type: "text", required: true },
|
|
4571
|
+
{ name: "auth_mode", type: "text", default: "regular" },
|
|
4572
|
+
{ name: "password_hash", type: "text", required: false },
|
|
4573
|
+
// bcrypt; password mode only
|
|
4574
|
+
{ name: "expires_at", type: "date", required: false },
|
|
4575
|
+
// null = no expiry
|
|
4576
|
+
{ name: "content_type", type: "text", required: false }
|
|
4577
|
+
]
|
|
4578
|
+
};
|
|
4520
4579
|
contextPresetsSchema = {
|
|
4521
4580
|
type: "context_presets",
|
|
4522
4581
|
name: {
|
|
@@ -4609,6 +4668,7 @@ var init_core_schema = __esm({
|
|
|
4609
4668
|
promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
|
|
4610
4669
|
contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
|
|
4611
4670
|
oauthTokensSchema: () => addCoreFields(oauthTokensSchema),
|
|
4671
|
+
sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
|
|
4612
4672
|
transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
|
|
4613
4673
|
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
|
|
4614
4674
|
};
|
|
@@ -5105,7 +5165,12 @@ var init_resolve_model = __esm({
|
|
|
5105
5165
|
// supports — including Vertex Gemini, which translates it into
|
|
5106
5166
|
// responseSchema/responseMimeType — so enabling this matches the actual
|
|
5107
5167
|
// proxy contract.
|
|
5108
|
-
supportsStructuredOutputs: true
|
|
5168
|
+
supportsStructuredOutputs: true,
|
|
5169
|
+
// Request token usage on STREAMED responses. Without this the openai-compatible
|
|
5170
|
+
// provider omits `stream_options: { include_usage: true }`, so LiteLLM returns no
|
|
5171
|
+
// usage for streaming calls — which zeroes out the per-request token metrics and
|
|
5172
|
+
// the message-footer token count (both read the AI SDK `totalUsage`/finish-part usage).
|
|
5173
|
+
includeUsage: true
|
|
5109
5174
|
});
|
|
5110
5175
|
};
|
|
5111
5176
|
}
|
|
@@ -5768,7 +5833,8 @@ var init_vector_search = __esm({
|
|
|
5768
5833
|
trigger,
|
|
5769
5834
|
cutoffs,
|
|
5770
5835
|
expand,
|
|
5771
|
-
entityFilter
|
|
5836
|
+
entityFilter,
|
|
5837
|
+
queryEmbedding
|
|
5772
5838
|
}) => {
|
|
5773
5839
|
const table = convertContextToTableDefinition(context);
|
|
5774
5840
|
console.log("[EXULU] Called vector search.", {
|
|
@@ -5850,36 +5916,53 @@ var init_vector_search = __esm({
|
|
|
5850
5916
|
let vector = [];
|
|
5851
5917
|
let vectorStr = "";
|
|
5852
5918
|
let vectorExpr = "";
|
|
5919
|
+
const _tBody = Date.now();
|
|
5920
|
+
let _preMs = 0, _statMs = 0, _resolveMs = 0, _embedMs = 0;
|
|
5921
|
+
let _embedSource = "none";
|
|
5853
5922
|
if (query) {
|
|
5923
|
+
const _tp = Date.now();
|
|
5854
5924
|
const { processed: stemmedQuery } = preprocessQuery(query, {
|
|
5855
5925
|
enableStemming: true,
|
|
5856
5926
|
detectLanguage: true
|
|
5857
5927
|
});
|
|
5928
|
+
_preMs = Date.now() - _tp;
|
|
5858
5929
|
console.log("[EXULU] Stemmed query:", stemmedQuery);
|
|
5859
5930
|
if (stemmedQuery) {
|
|
5860
5931
|
query = stemmedQuery;
|
|
5861
5932
|
}
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5933
|
+
if (queryEmbedding && queryEmbedding.length) {
|
|
5934
|
+
vector = queryEmbedding;
|
|
5935
|
+
_embedSource = "reused";
|
|
5936
|
+
} else {
|
|
5937
|
+
const _ts = Date.now();
|
|
5938
|
+
await updateStatistic({
|
|
5939
|
+
name: "count",
|
|
5940
|
+
label: table.name.singular,
|
|
5941
|
+
type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
|
|
5942
|
+
trigger,
|
|
5943
|
+
count: 1,
|
|
5944
|
+
user: user?.id,
|
|
5945
|
+
role
|
|
5946
|
+
});
|
|
5947
|
+
_statMs = Date.now() - _ts;
|
|
5948
|
+
const _tr = Date.now();
|
|
5949
|
+
const resolved = await resolveEmbedder({
|
|
5950
|
+
model: embedder.model,
|
|
5951
|
+
contextId: context.id,
|
|
5952
|
+
contextName: context.name,
|
|
5953
|
+
user,
|
|
5954
|
+
roleId: role
|
|
5955
|
+
});
|
|
5956
|
+
_resolveMs = Date.now() - _tr;
|
|
5957
|
+
const _te = Date.now();
|
|
5958
|
+
const [queryVector] = await resolved.embed([query], { inputType: "query" });
|
|
5959
|
+
_embedMs = Date.now() - _te;
|
|
5960
|
+
if (!queryVector?.length) {
|
|
5961
|
+
throw new Error("No vector generated for query.");
|
|
5962
|
+
}
|
|
5963
|
+
vector = queryVector;
|
|
5964
|
+
_embedSource = "computed";
|
|
5881
5965
|
}
|
|
5882
|
-
vector = queryVector;
|
|
5883
5966
|
vectorStr = `ARRAY[${vector.join(",")}]`;
|
|
5884
5967
|
vectorExpr = `${vectorStr}::vector`;
|
|
5885
5968
|
}
|
|
@@ -5906,6 +5989,7 @@ var init_vector_search = __esm({
|
|
|
5906
5989
|
const languages = configuration.languages?.length ? configuration.languages : ["english"];
|
|
5907
5990
|
console.log("[EXULU] Vector search params:", { method, query, cutoffs, languages });
|
|
5908
5991
|
let resultChunks = [];
|
|
5992
|
+
const _tSql = Date.now();
|
|
5909
5993
|
switch (method) {
|
|
5910
5994
|
case "tsvector":
|
|
5911
5995
|
chunksQuery.limit(limit * 2);
|
|
@@ -6019,6 +6103,11 @@ var init_vector_search = __esm({
|
|
|
6019
6103
|
]).orderByRaw("hybrid_score DESC").limit(Math.min(matchCount, 250));
|
|
6020
6104
|
resultChunks = await hybridQuery;
|
|
6021
6105
|
}
|
|
6106
|
+
if (process.env.EXULU_VS_TIMING) {
|
|
6107
|
+
console.log(
|
|
6108
|
+
`[EXULU-VS] ctx=${context.id} method=${method} embed=${_embedSource} pre=${_preMs}ms stat=${_statMs}ms resolve=${_resolveMs}ms embedApi=${_embedMs}ms sql=${Date.now() - _tSql}ms total=${Date.now() - _tBody}ms`
|
|
6109
|
+
);
|
|
6110
|
+
}
|
|
6022
6111
|
console.log("[EXULU] Vector search chunk results:", resultChunks?.length);
|
|
6023
6112
|
let results = resultChunks.map((chunk) => ({
|
|
6024
6113
|
chunk_content: chunk.content,
|
|
@@ -6480,13 +6569,14 @@ var init_decorator = __esm({
|
|
|
6480
6569
|
});
|
|
6481
6570
|
|
|
6482
6571
|
// src/exulu/context.ts
|
|
6483
|
-
var import_knex5,
|
|
6572
|
+
var import_knex5, ExuluContext2;
|
|
6484
6573
|
var init_context = __esm({
|
|
6485
6574
|
"src/exulu/context.ts"() {
|
|
6486
6575
|
"use strict";
|
|
6487
6576
|
init_cjs_shims();
|
|
6488
6577
|
init_storage();
|
|
6489
6578
|
init_sanitize_name();
|
|
6579
|
+
init_table_names();
|
|
6490
6580
|
import_knex5 = __toESM(require("pgvector/knex"), 1);
|
|
6491
6581
|
init_chunker();
|
|
6492
6582
|
init_resolve_embedder();
|
|
@@ -6497,15 +6587,10 @@ var init_context = __esm({
|
|
|
6497
6587
|
init_vector_search();
|
|
6498
6588
|
init_convert_context_to_table_definition();
|
|
6499
6589
|
init_apply_filters();
|
|
6590
|
+
init_access_control();
|
|
6500
6591
|
init_map_types();
|
|
6501
6592
|
init_decorator();
|
|
6502
6593
|
init_entities();
|
|
6503
|
-
getTableName = (id) => {
|
|
6504
|
-
return sanitizeName(id) + "_items";
|
|
6505
|
-
};
|
|
6506
|
-
getChunksTableName = (id) => {
|
|
6507
|
-
return sanitizeName(id) + "_chunks";
|
|
6508
|
-
};
|
|
6509
6594
|
ExuluContext2 = class {
|
|
6510
6595
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
6511
6596
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -6999,12 +7084,18 @@ var init_context = __esm({
|
|
|
6999
7084
|
};
|
|
7000
7085
|
getItems = async ({
|
|
7001
7086
|
filters,
|
|
7002
|
-
fields
|
|
7087
|
+
fields,
|
|
7088
|
+
user,
|
|
7089
|
+
role
|
|
7003
7090
|
}) => {
|
|
7004
7091
|
const { db: db2 } = await postgresClient();
|
|
7005
|
-
let query = db2.from(getTableName(this.id)).select(fields || "*");
|
|
7006
7092
|
const tableDefinition = convertContextToTableDefinition(this);
|
|
7093
|
+
let query = db2.from(getTableName(this.id)).select(fields || "*");
|
|
7007
7094
|
query = applyFilters(query, filters || [], tableDefinition);
|
|
7095
|
+
if (user) {
|
|
7096
|
+
const acUser = role && (!user.role || user.role.id !== role) ? { ...user, role: { ...user.role ?? {}, id: role } } : user;
|
|
7097
|
+
query = applyAccessControl(tableDefinition, query, acUser);
|
|
7098
|
+
}
|
|
7008
7099
|
const items = await query;
|
|
7009
7100
|
return items;
|
|
7010
7101
|
};
|
|
@@ -7365,11 +7456,9 @@ async function resolveReranker(input) {
|
|
|
7365
7456
|
rerank_score: r.relevance_score ?? 0
|
|
7366
7457
|
}));
|
|
7367
7458
|
reranked.sort((a, b) => b.rerank_score - a.rerank_score);
|
|
7368
|
-
import_fs.default.writeFileSync("reranked.json", JSON.stringify(reranked, null, 2));
|
|
7369
7459
|
return reranked;
|
|
7370
7460
|
} catch (err) {
|
|
7371
7461
|
console.error("[EXULU] Error reranking:", err);
|
|
7372
|
-
import_fs.default.writeFileSync("reranked.json", JSON.stringify(err, null, 2));
|
|
7373
7462
|
return [];
|
|
7374
7463
|
}
|
|
7375
7464
|
};
|
|
@@ -7383,7 +7472,7 @@ var init_resolve_reranker = __esm({
|
|
|
7383
7472
|
init_supervisor();
|
|
7384
7473
|
init_budget_service();
|
|
7385
7474
|
init_tags();
|
|
7386
|
-
import_fs =
|
|
7475
|
+
import_fs = require("fs");
|
|
7387
7476
|
ResolveRerankerError = class extends Error {
|
|
7388
7477
|
constructor(code, message) {
|
|
7389
7478
|
super(message);
|
|
@@ -7984,9 +8073,21 @@ var init_memory_tool = __esm({
|
|
|
7984
8073
|
case "longText":
|
|
7985
8074
|
case "shortText":
|
|
7986
8075
|
case "code":
|
|
7987
|
-
case "enum":
|
|
7988
8076
|
fields[field.name] = import_zod4.z.string().describe("The " + field.name + " of the item to create");
|
|
7989
8077
|
break;
|
|
8078
|
+
case "enum":
|
|
8079
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
8080
|
+
const enumValues = field.enumValues;
|
|
8081
|
+
fields[field.name] = import_zod4.z.preprocess(
|
|
8082
|
+
(v) => typeof v === "string" ? v.toUpperCase() : v,
|
|
8083
|
+
import_zod4.z.enum(enumValues)
|
|
8084
|
+
).describe(
|
|
8085
|
+
"The " + field.name + " of the item to create. Must be one of: " + field.enumValues.join(", ")
|
|
8086
|
+
);
|
|
8087
|
+
} else {
|
|
8088
|
+
fields[field.name] = import_zod4.z.string().describe("The " + field.name + " of the item to create");
|
|
8089
|
+
}
|
|
8090
|
+
break;
|
|
7990
8091
|
case "json":
|
|
7991
8092
|
fields[field.name] = import_zod4.z.string({}).describe(
|
|
7992
8093
|
"The " + field.name + " of the item to create, it should be a valid JSON string."
|
|
@@ -8012,6 +8113,9 @@ var init_memory_tool = __esm({
|
|
|
8012
8113
|
break;
|
|
8013
8114
|
}
|
|
8014
8115
|
}
|
|
8116
|
+
fields["visibility"] = import_zod4.z.enum(["private", "public"]).optional().describe(
|
|
8117
|
+
"Whether this memory is private to the user or shared (public). Ask the user if unknown."
|
|
8118
|
+
);
|
|
8015
8119
|
const toolName = "create_" + sanitizeName(context.name) + "_memory_item";
|
|
8016
8120
|
return new ExuluTool({
|
|
8017
8121
|
id: toolName,
|
|
@@ -8021,14 +8125,36 @@ var init_memory_tool = __esm({
|
|
|
8021
8125
|
type: "function",
|
|
8022
8126
|
inputSchema: import_zod4.z.object(fields),
|
|
8023
8127
|
config: [],
|
|
8024
|
-
execute: async (
|
|
8128
|
+
execute: async (params) => {
|
|
8129
|
+
const { name, description, surroundingContext, information, visibility, exuluConfig, user } = params;
|
|
8025
8130
|
let result = { result: "" };
|
|
8131
|
+
if (!visibility) {
|
|
8132
|
+
return {
|
|
8133
|
+
result: `Before saving this memory, ask the user whether it should be PRIVATE (visible only to them) or PUBLIC (shared with the team), then call \`${toolName}\` again with \`visibility\` set.`
|
|
8134
|
+
};
|
|
8135
|
+
}
|
|
8026
8136
|
try {
|
|
8137
|
+
const extraFields = {};
|
|
8138
|
+
for (const field of context.fields ?? []) {
|
|
8139
|
+
if (field.type === "enum" && field.enumValues && field.enumValues.length > 0) {
|
|
8140
|
+
const raw = params[field.name];
|
|
8141
|
+
if (raw !== void 0 && raw !== null && raw !== "") {
|
|
8142
|
+
const rawStr = String(raw);
|
|
8143
|
+
const canonical = field.enumValues.find(
|
|
8144
|
+
(v) => v.toUpperCase() === rawStr.toUpperCase()
|
|
8145
|
+
);
|
|
8146
|
+
if (canonical !== void 0) {
|
|
8147
|
+
extraFields[field.name] = canonical;
|
|
8148
|
+
}
|
|
8149
|
+
}
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8027
8152
|
const newItem = {
|
|
8028
8153
|
name,
|
|
8029
8154
|
description: "Description: " + description + "\n\nSurrounding Context: " + surroundingContext,
|
|
8030
8155
|
information: "Information: " + information,
|
|
8031
|
-
rights_mode: "public"
|
|
8156
|
+
rights_mode: visibility === "private" ? "private" : "public",
|
|
8157
|
+
...extraFields
|
|
8032
8158
|
};
|
|
8033
8159
|
const { item: createdItem, job: createdJob } = await context.createItem(
|
|
8034
8160
|
newItem,
|
|
@@ -8841,7 +8967,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
8841
8967
|
contexts = [];
|
|
8842
8968
|
}
|
|
8843
8969
|
let sharedSessionSandbox;
|
|
8844
|
-
if (sessionID && exuluConfig) {
|
|
8970
|
+
if (sessionID && exuluConfig && agent?.sandbox_enabled === true) {
|
|
8845
8971
|
try {
|
|
8846
8972
|
sharedSessionSandbox = await createSessionSandbox(
|
|
8847
8973
|
sessionID,
|
|
@@ -10922,11 +11048,13 @@ __export(index_exports, {
|
|
|
10922
11048
|
ExuluProvider: () => ExuluProvider,
|
|
10923
11049
|
ExuluPython: () => ExuluPython,
|
|
10924
11050
|
ExuluQueues: () => queues,
|
|
11051
|
+
ExuluReadApi: () => ExuluReadApi,
|
|
10925
11052
|
ExuluReranker: () => ExuluReranker,
|
|
10926
11053
|
ExuluTool: () => ExuluTool,
|
|
10927
11054
|
ExuluTrajectoryRegistry: () => trajectoryRegistry,
|
|
10928
11055
|
ExuluVariables: () => ExuluVariables,
|
|
10929
|
-
defaultChunker: () => defaultChunker
|
|
11056
|
+
defaultChunker: () => defaultChunker,
|
|
11057
|
+
postgresClient: () => postgresClient
|
|
10930
11058
|
});
|
|
10931
11059
|
module.exports = __toCommonJS(index_exports);
|
|
10932
11060
|
init_cjs_shims();
|
|
@@ -10945,6 +11073,73 @@ var redisServer = {
|
|
|
10945
11073
|
username: process.env.REDIS_USER || ""
|
|
10946
11074
|
};
|
|
10947
11075
|
|
|
11076
|
+
// ee/queues/redis-startup.ts
|
|
11077
|
+
init_cjs_shims();
|
|
11078
|
+
var REDIS_STARTUP_TIMEOUT_MS = 6e4;
|
|
11079
|
+
var WATCHDOG_INTERVAL_MS = 1e4;
|
|
11080
|
+
var ERROR_LOG_THROTTLE_MS = 3e4;
|
|
11081
|
+
var log = (line) => console.log(`[EXULU-REDIS] ${line}`);
|
|
11082
|
+
var warn = (line) => console.warn(`[EXULU-REDIS] ${line}`);
|
|
11083
|
+
var errorLog = (line) => console.error(`[EXULU-REDIS] ${line}`);
|
|
11084
|
+
var redisAddress = () => `${redisServer.host || "(unset)"}:${redisServer.port || "(unset)"}`;
|
|
11085
|
+
var describeError = (e) => {
|
|
11086
|
+
const any = e;
|
|
11087
|
+
const head = any?.message ? String(any.message).split("\n")[0] : void 0;
|
|
11088
|
+
if (any?.code) return head && head !== any.code ? `${any.code} (${head})` : `${any.code}`;
|
|
11089
|
+
return head ?? String(e);
|
|
11090
|
+
};
|
|
11091
|
+
function logRedisErrors(source, label) {
|
|
11092
|
+
let count = 0;
|
|
11093
|
+
let lastLoggedAt = 0;
|
|
11094
|
+
source.on("error", (err) => {
|
|
11095
|
+
count += 1;
|
|
11096
|
+
const now = Date.now();
|
|
11097
|
+
if (count === 1 || now - lastLoggedAt >= ERROR_LOG_THROTTLE_MS) {
|
|
11098
|
+
errorLog(`${label} connection error (${redisAddress()}): ${describeError(err)}${count > 1 ? ` (x${count})` : ""}`);
|
|
11099
|
+
lastLoggedAt = now;
|
|
11100
|
+
}
|
|
11101
|
+
});
|
|
11102
|
+
}
|
|
11103
|
+
async function guardRedisStartup(label, run, source) {
|
|
11104
|
+
const addr = redisAddress();
|
|
11105
|
+
log(`Connecting to Redis (${addr}) for ${label}\u2026`);
|
|
11106
|
+
const startedAt = Date.now();
|
|
11107
|
+
let lastError;
|
|
11108
|
+
const onError = (err) => {
|
|
11109
|
+
lastError = err;
|
|
11110
|
+
};
|
|
11111
|
+
source?.on("error", onError);
|
|
11112
|
+
const watchdog = setInterval(() => {
|
|
11113
|
+
const secs = Math.round((Date.now() - startedAt) / 1e3);
|
|
11114
|
+
warn(
|
|
11115
|
+
`\u26A0 Still waiting for Redis at ${addr} after ${secs}s \u2014 ${label} startup is blocked. Is Redis running? (aborting at ${REDIS_STARTUP_TIMEOUT_MS / 1e3}s)`
|
|
11116
|
+
);
|
|
11117
|
+
}, WATCHDOG_INTERVAL_MS);
|
|
11118
|
+
watchdog.unref?.();
|
|
11119
|
+
let timer2;
|
|
11120
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
11121
|
+
timer2 = setTimeout(() => {
|
|
11122
|
+
reject(
|
|
11123
|
+
new Error(
|
|
11124
|
+
`[EXULU-REDIS] Redis unreachable at ${addr} after ${REDIS_STARTUP_TIMEOUT_MS / 1e3}s \u2014 aborting ${label} startup. Last error: ${lastError ? describeError(lastError) : "none surfaced"}. Check REDIS_HOST/REDIS_PORT and that a Redis server is reachable at ${addr}.`
|
|
11125
|
+
)
|
|
11126
|
+
);
|
|
11127
|
+
}, REDIS_STARTUP_TIMEOUT_MS);
|
|
11128
|
+
});
|
|
11129
|
+
const runPromise = Promise.resolve().then(run);
|
|
11130
|
+
runPromise.catch(() => {
|
|
11131
|
+
});
|
|
11132
|
+
try {
|
|
11133
|
+
const result = await Promise.race([runPromise, timeout]);
|
|
11134
|
+
log(`Redis ready; ${label} initialized (${addr}, ${((Date.now() - startedAt) / 1e3).toFixed(1)}s).`);
|
|
11135
|
+
return result;
|
|
11136
|
+
} finally {
|
|
11137
|
+
clearInterval(watchdog);
|
|
11138
|
+
if (timer2) clearTimeout(timer2);
|
|
11139
|
+
source?.off?.("error", onError);
|
|
11140
|
+
}
|
|
11141
|
+
}
|
|
11142
|
+
|
|
10948
11143
|
// src/redis/client.ts
|
|
10949
11144
|
var client = {};
|
|
10950
11145
|
async function redisClient() {
|
|
@@ -10962,9 +11157,11 @@ async function redisClient() {
|
|
|
10962
11157
|
client["exulu"] = (0, import_redis.createClient)({
|
|
10963
11158
|
url
|
|
10964
11159
|
});
|
|
10965
|
-
|
|
11160
|
+
logRedisErrors(client["exulu"], "client");
|
|
11161
|
+
await guardRedisStartup("client", () => client["exulu"].connect().then(() => void 0), client["exulu"]);
|
|
10966
11162
|
} catch (error) {
|
|
10967
11163
|
console.error(`[EXULU] error connecting to redis:`, error);
|
|
11164
|
+
delete client["exulu"];
|
|
10968
11165
|
return { client: null };
|
|
10969
11166
|
}
|
|
10970
11167
|
}
|
|
@@ -10989,7 +11186,8 @@ init_client();
|
|
|
10989
11186
|
init_auth();
|
|
10990
11187
|
var requestValidators = {
|
|
10991
11188
|
authenticate: async (req) => {
|
|
10992
|
-
const
|
|
11189
|
+
const rawApiKey = req.headers["exulu-api-key"] || req.headers["x-api-key"];
|
|
11190
|
+
const apikey = rawApiKey?.replace(/^Bearer\s+/i, "") || null;
|
|
10993
11191
|
const { db: db2 } = await postgresClient();
|
|
10994
11192
|
let authtoken = null;
|
|
10995
11193
|
if (typeof apikey !== "string") {
|
|
@@ -11082,7 +11280,7 @@ var requestValidators = {
|
|
|
11082
11280
|
init_statistics();
|
|
11083
11281
|
init_client();
|
|
11084
11282
|
var import_express5 = __toESM(require("express"), 1);
|
|
11085
|
-
var
|
|
11283
|
+
var import_server5 = require("@apollo/server");
|
|
11086
11284
|
var import_cors = __toESM(require("cors"), 1);
|
|
11087
11285
|
var import_reflect_metadata = require("reflect-metadata");
|
|
11088
11286
|
|
|
@@ -11275,6 +11473,14 @@ var ExuluQueues = class {
|
|
|
11275
11473
|
},
|
|
11276
11474
|
telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
|
|
11277
11475
|
});
|
|
11476
|
+
logRedisErrors(newQueue, `queue "${name}"`);
|
|
11477
|
+
try {
|
|
11478
|
+
await guardRedisStartup(`queue "${name}"`, () => newQueue.waitUntilReady(), newQueue);
|
|
11479
|
+
} catch (err) {
|
|
11480
|
+
void newQueue.close().catch(() => {
|
|
11481
|
+
});
|
|
11482
|
+
throw err;
|
|
11483
|
+
}
|
|
11278
11484
|
await newQueue.setGlobalConcurrency(queueConcurrency);
|
|
11279
11485
|
this.queues.push({
|
|
11280
11486
|
queue: newQueue,
|
|
@@ -13436,6 +13642,8 @@ var createWorkers = async (providers, queues2, config, contexts, evals, tools, t
|
|
|
13436
13642
|
},
|
|
13437
13643
|
maxRetriesPerRequest: null
|
|
13438
13644
|
});
|
|
13645
|
+
logRedisErrors(redisConnection, "worker");
|
|
13646
|
+
await guardRedisStartup("workers", () => redisConnection.ping().then(() => void 0), redisConnection);
|
|
13439
13647
|
}
|
|
13440
13648
|
const workers = queues2.map((queue) => {
|
|
13441
13649
|
console.log(`[EXULU] creating worker for queue ${queue.queue.name}.`);
|
|
@@ -14615,7 +14823,7 @@ var renderTranscript = (segments, speakers) => {
|
|
|
14615
14823
|
|
|
14616
14824
|
// src/exulu/transcription/service.ts
|
|
14617
14825
|
var TABLE2 = "transcription_jobs";
|
|
14618
|
-
var
|
|
14826
|
+
var log3 = (msg) => console.log(`[EXULU-TRANSCRIPTION] ${msg}`);
|
|
14619
14827
|
var parseJsonField = (v) => {
|
|
14620
14828
|
if (v == null) return null;
|
|
14621
14829
|
if (typeof v === "string") {
|
|
@@ -14686,7 +14894,7 @@ var transcriptionService = {
|
|
|
14686
14894
|
error: err.message,
|
|
14687
14895
|
updatedAt: /* @__PURE__ */ new Date()
|
|
14688
14896
|
}).returning("*");
|
|
14689
|
-
|
|
14897
|
+
log3(`Failed to dispatch job ${row.id}: ${err.message}`);
|
|
14690
14898
|
return this._rowFromDb(failed);
|
|
14691
14899
|
}
|
|
14692
14900
|
},
|
|
@@ -14714,9 +14922,9 @@ var transcriptionService = {
|
|
|
14714
14922
|
updatedAt: /* @__PURE__ */ new Date()
|
|
14715
14923
|
});
|
|
14716
14924
|
} else if (err instanceof TranscriptionServerUnavailable) {
|
|
14717
|
-
|
|
14925
|
+
log3(`Whisper server unreachable while polling ${row.id}; will retry`);
|
|
14718
14926
|
} else {
|
|
14719
|
-
|
|
14927
|
+
log3(`Error polling job ${row.id}: ${err.message}`);
|
|
14720
14928
|
}
|
|
14721
14929
|
}
|
|
14722
14930
|
}
|
|
@@ -14766,7 +14974,7 @@ var transcriptionService = {
|
|
|
14766
14974
|
} catch (err) {
|
|
14767
14975
|
const code = err.code;
|
|
14768
14976
|
if (code !== "JOB_NOT_FOUND") {
|
|
14769
|
-
|
|
14977
|
+
log3(`Best-effort cancel of whisper job failed: ${err.message}`);
|
|
14770
14978
|
}
|
|
14771
14979
|
}
|
|
14772
14980
|
}
|
|
@@ -14852,7 +15060,7 @@ var transcriptionService = {
|
|
|
14852
15060
|
[]
|
|
14853
15061
|
);
|
|
14854
15062
|
} catch (err) {
|
|
14855
|
-
|
|
15063
|
+
log3(`RBAC update failed for item ${itemId}: ${err.message}`);
|
|
14856
15064
|
}
|
|
14857
15065
|
}
|
|
14858
15066
|
const projectId = input.project_id ?? row.project_id ?? null;
|
|
@@ -15149,7 +15357,7 @@ var durationFromSegments = (segments) => {
|
|
|
15149
15357
|
// src/exulu/recall/service.ts
|
|
15150
15358
|
var TABLE3 = "transcription_jobs";
|
|
15151
15359
|
var DEFAULT_BOT_NAME = "Exulu Notetaker";
|
|
15152
|
-
var
|
|
15360
|
+
var log4 = (msg) => console.log(`[EXULU-RECALL] ${msg}`);
|
|
15153
15361
|
var parseJson = (v) => {
|
|
15154
15362
|
if (v == null) return null;
|
|
15155
15363
|
if (typeof v === "string") {
|
|
@@ -15246,7 +15454,7 @@ var recallService = {
|
|
|
15246
15454
|
error: err.message,
|
|
15247
15455
|
updatedAt: /* @__PURE__ */ new Date()
|
|
15248
15456
|
}).returning("*");
|
|
15249
|
-
|
|
15457
|
+
log4(`createBot failed for job ${inserted.id}: ${err.message}`);
|
|
15250
15458
|
return this._row(failed);
|
|
15251
15459
|
}
|
|
15252
15460
|
},
|
|
@@ -15260,7 +15468,7 @@ var recallService = {
|
|
|
15260
15468
|
const name = event?.event ?? "";
|
|
15261
15469
|
const job = await this._findJob(ids.botId, ids.recordingId, ids.transcriptId);
|
|
15262
15470
|
if (!job) {
|
|
15263
|
-
|
|
15471
|
+
log4(`No job for event ${name} (bot=${ids.botId}); ignoring.`);
|
|
15264
15472
|
return;
|
|
15265
15473
|
}
|
|
15266
15474
|
if (name.startsWith("bot.")) {
|
|
@@ -15285,12 +15493,12 @@ var recallService = {
|
|
|
15285
15493
|
await this._fail(job.id, ids.subCode || ids.code || "transcript failed");
|
|
15286
15494
|
return;
|
|
15287
15495
|
default:
|
|
15288
|
-
|
|
15496
|
+
log4(`Unhandled event ${name} for job ${job.id}`);
|
|
15289
15497
|
}
|
|
15290
15498
|
},
|
|
15291
15499
|
async _onRecordingDone(jobId, recordingId) {
|
|
15292
15500
|
if (!recordingId) {
|
|
15293
|
-
|
|
15501
|
+
log4(`recording.done for job ${jobId} had no recording id`);
|
|
15294
15502
|
return;
|
|
15295
15503
|
}
|
|
15296
15504
|
const { db: db2 } = await postgresClient();
|
|
@@ -15300,7 +15508,7 @@ var recallService = {
|
|
|
15300
15508
|
updatedAt: /* @__PURE__ */ new Date()
|
|
15301
15509
|
});
|
|
15302
15510
|
if (!claimed) {
|
|
15303
|
-
|
|
15511
|
+
log4(`recording.done for job ${jobId} already handled; skipping.`);
|
|
15304
15512
|
return;
|
|
15305
15513
|
}
|
|
15306
15514
|
try {
|
|
@@ -15320,7 +15528,7 @@ var recallService = {
|
|
|
15320
15528
|
if (!dbRow) return;
|
|
15321
15529
|
const job = this._row(dbRow);
|
|
15322
15530
|
if ((job.status === "awaiting_review" || job.status === "saved") && job.raw_segments && job.raw_segments.length > 0) {
|
|
15323
|
-
|
|
15531
|
+
log4(`transcript.done for job ${jobId} already processed; skipping.`);
|
|
15324
15532
|
return;
|
|
15325
15533
|
}
|
|
15326
15534
|
try {
|
|
@@ -15339,7 +15547,7 @@ var recallService = {
|
|
|
15339
15547
|
const recDuration = recordingDurationSeconds(rec);
|
|
15340
15548
|
if (recDuration != null) duration = recDuration;
|
|
15341
15549
|
} catch (err) {
|
|
15342
|
-
|
|
15550
|
+
log4(`could not fetch recording duration for job ${jobId}: ${err.message}`);
|
|
15343
15551
|
}
|
|
15344
15552
|
}
|
|
15345
15553
|
await this._update(jobId, {
|
|
@@ -15379,11 +15587,11 @@ var recallService = {
|
|
|
15379
15587
|
const prompts = job.post_processing_prompts ?? [];
|
|
15380
15588
|
if (prompts.length === 0) return [];
|
|
15381
15589
|
if (job.post_processing_outputs && job.post_processing_outputs.length > 0) {
|
|
15382
|
-
|
|
15590
|
+
log4(`post-processing for job ${jobId} already ran; skipping.`);
|
|
15383
15591
|
return job.post_processing_outputs;
|
|
15384
15592
|
}
|
|
15385
15593
|
if (!job.raw_segments || job.raw_segments.length === 0) {
|
|
15386
|
-
|
|
15594
|
+
log4(`post-processing for job ${jobId} skipped: no transcript.`);
|
|
15387
15595
|
return [];
|
|
15388
15596
|
}
|
|
15389
15597
|
const outputs = [];
|
|
@@ -15456,7 +15664,7 @@ ${transcriptText}`,
|
|
|
15456
15664
|
ran_at: ranAt
|
|
15457
15665
|
};
|
|
15458
15666
|
} catch (err) {
|
|
15459
|
-
|
|
15667
|
+
log4(`post-processing prompt ${promptId} failed for job ${job.id}: ${err.message}`);
|
|
15460
15668
|
return {
|
|
15461
15669
|
prompt_id: promptId,
|
|
15462
15670
|
agent_id: agentId,
|
|
@@ -19655,6 +19863,53 @@ var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()
|
|
|
19655
19863
|
return passed ? { ok: true } : { ok: false, reason: "signature mismatch" };
|
|
19656
19864
|
};
|
|
19657
19865
|
|
|
19866
|
+
// src/exulu/shared-artifacts.ts
|
|
19867
|
+
init_cjs_shims();
|
|
19868
|
+
var import_bcryptjs4 = __toESM(require("bcryptjs"), 1);
|
|
19869
|
+
var normalizeS3Key = (key, bucket) => {
|
|
19870
|
+
const segments = key.split("/").filter((s, i) => !(i === 0 && s === "")).map((s) => decodeURIComponent(s));
|
|
19871
|
+
if (segments[0] === bucket) segments.shift();
|
|
19872
|
+
return segments.join("/");
|
|
19873
|
+
};
|
|
19874
|
+
var isHtmlKey = (key) => /\.html?$/i.test(key);
|
|
19875
|
+
var deriveFilename = (key) => {
|
|
19876
|
+
const base = key.split("/").pop() ?? key;
|
|
19877
|
+
return base.split("_EXULU_").pop() ?? base;
|
|
19878
|
+
};
|
|
19879
|
+
var slugifyShareName = (input) => deriveFilename(input).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
19880
|
+
var isExpired = (expiresAt, now) => {
|
|
19881
|
+
if (!expiresAt) return false;
|
|
19882
|
+
return new Date(expiresAt).getTime() <= now.getTime();
|
|
19883
|
+
};
|
|
19884
|
+
var validateCreateInput = (input, now) => {
|
|
19885
|
+
if (!input.s3key) return { ok: false, message: "s3key is required." };
|
|
19886
|
+
if (!input.name) return { ok: false, message: "name is required." };
|
|
19887
|
+
const mode = input.auth_mode;
|
|
19888
|
+
if (mode !== "public" && mode !== "password" && mode !== "regular") {
|
|
19889
|
+
return { ok: false, message: "auth_mode must be public, password, or regular." };
|
|
19890
|
+
}
|
|
19891
|
+
if (mode === "password" && !input.password) {
|
|
19892
|
+
return { ok: false, message: "A password is required for password mode." };
|
|
19893
|
+
}
|
|
19894
|
+
if (input.expires_at && Number.isNaN(new Date(input.expires_at).getTime())) {
|
|
19895
|
+
return { ok: false, message: "expires_at is not a valid date." };
|
|
19896
|
+
}
|
|
19897
|
+
if (input.expires_at && isExpired(input.expires_at, now)) {
|
|
19898
|
+
return { ok: false, message: "expires_at must be in the future." };
|
|
19899
|
+
}
|
|
19900
|
+
return { ok: true };
|
|
19901
|
+
};
|
|
19902
|
+
var hashSharePassword = (password) => import_bcryptjs4.default.hash(password, 10);
|
|
19903
|
+
var verifySharePassword = (password, hash) => import_bcryptjs4.default.compare(password, hash);
|
|
19904
|
+
var contentHeadersFor = (key, contentType, filename) => {
|
|
19905
|
+
if (isHtmlKey(key)) return { contentType: "text/html; charset=utf-8" };
|
|
19906
|
+
return {
|
|
19907
|
+
contentType: contentType || "application/octet-stream",
|
|
19908
|
+
disposition: `attachment; filename="${filename.replace(/"/g, "")}"`
|
|
19909
|
+
};
|
|
19910
|
+
};
|
|
19911
|
+
var getSharedArtifactByName = (db2, name) => db2("shared_artifacts").where({ name }).first();
|
|
19912
|
+
|
|
19658
19913
|
// src/exulu/routes.ts
|
|
19659
19914
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
19660
19915
|
var getExuluVersionNumber = async () => {
|
|
@@ -19771,7 +20026,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
|
|
|
19771
20026
|
config,
|
|
19772
20027
|
evals
|
|
19773
20028
|
);
|
|
19774
|
-
const server = new
|
|
20029
|
+
const server = new import_server5.ApolloServer({
|
|
19775
20030
|
cache: new import_utils5.InMemoryLRUCache(),
|
|
19776
20031
|
schema,
|
|
19777
20032
|
introspection: true
|
|
@@ -22689,6 +22944,126 @@ ${style.markdown}` : params.prompt;
|
|
|
22689
22944
|
}
|
|
22690
22945
|
res.status(204).send();
|
|
22691
22946
|
});
|
|
22947
|
+
app.post("/shared-artifacts", async (req, res) => {
|
|
22948
|
+
const { db: db2 } = await postgresClient();
|
|
22949
|
+
const auth = await requestValidators.authenticate(req);
|
|
22950
|
+
if (!auth.user?.id) {
|
|
22951
|
+
res.status(401).json({ detail: "Authentication required." });
|
|
22952
|
+
return;
|
|
22953
|
+
}
|
|
22954
|
+
const now = /* @__PURE__ */ new Date();
|
|
22955
|
+
const valid = validateCreateInput(req.body, now);
|
|
22956
|
+
if (!valid.ok) {
|
|
22957
|
+
res.status(400).json({ detail: valid.message });
|
|
22958
|
+
return;
|
|
22959
|
+
}
|
|
22960
|
+
const bucket = config.fileUploads?.s3Bucket ?? "";
|
|
22961
|
+
const s3key = normalizeS3Key(req.body.s3key, bucket);
|
|
22962
|
+
const name = slugifyShareName(req.body.name);
|
|
22963
|
+
if (!name) {
|
|
22964
|
+
res.status(400).json({ detail: "name must contain url-safe characters." });
|
|
22965
|
+
return;
|
|
22966
|
+
}
|
|
22967
|
+
const existing = await getSharedArtifactByName(db2, name);
|
|
22968
|
+
if (existing) {
|
|
22969
|
+
res.status(409).json({ detail: "That share name is already taken." });
|
|
22970
|
+
return;
|
|
22971
|
+
}
|
|
22972
|
+
const auth_mode = req.body.auth_mode;
|
|
22973
|
+
const password_hash = auth_mode === "password" ? await hashSharePassword(req.body.password) : null;
|
|
22974
|
+
const rights_mode = auth_mode === "regular" ? req.body.rights_mode ?? "private" : "public";
|
|
22975
|
+
const [row] = await db2("shared_artifacts").insert({
|
|
22976
|
+
name,
|
|
22977
|
+
s3key,
|
|
22978
|
+
auth_mode,
|
|
22979
|
+
password_hash,
|
|
22980
|
+
expires_at: req.body.expires_at ?? null,
|
|
22981
|
+
content_type: req.body.content_type ?? null,
|
|
22982
|
+
rights_mode,
|
|
22983
|
+
created_by: auth.user.id
|
|
22984
|
+
}).returning("*");
|
|
22985
|
+
if (auth_mode === "regular" && req.body.rbac) {
|
|
22986
|
+
await handleRBACUpdate(db2, "shared_artifact", row.id, req.body.rbac, []);
|
|
22987
|
+
}
|
|
22988
|
+
res.status(201).json({ name: row.name });
|
|
22989
|
+
});
|
|
22990
|
+
app.get("/shared-artifacts/:name/meta", async (req, res) => {
|
|
22991
|
+
const { db: db2 } = await postgresClient();
|
|
22992
|
+
const row = await getSharedArtifactByName(db2, req.params.name ?? "");
|
|
22993
|
+
if (!row) {
|
|
22994
|
+
res.status(404).json({ detail: "Not found." });
|
|
22995
|
+
return;
|
|
22996
|
+
}
|
|
22997
|
+
if (isExpired(row.expires_at, /* @__PURE__ */ new Date())) {
|
|
22998
|
+
res.status(410).json({ detail: "This link has expired." });
|
|
22999
|
+
return;
|
|
23000
|
+
}
|
|
23001
|
+
res.json({
|
|
23002
|
+
auth_mode: row.auth_mode,
|
|
23003
|
+
expires_at: row.expires_at,
|
|
23004
|
+
filename: deriveFilename(row.s3key),
|
|
23005
|
+
content_type: row.content_type,
|
|
23006
|
+
is_html: /\.html?$/i.test(row.s3key)
|
|
23007
|
+
});
|
|
23008
|
+
});
|
|
23009
|
+
app.get("/shared-artifacts/:name/content", async (req, res) => {
|
|
23010
|
+
const { db: db2 } = await postgresClient();
|
|
23011
|
+
const row = await getSharedArtifactByName(db2, req.params.name ?? "");
|
|
23012
|
+
if (!row) {
|
|
23013
|
+
res.status(404).json({ detail: "Not found." });
|
|
23014
|
+
return;
|
|
23015
|
+
}
|
|
23016
|
+
if (isExpired(row.expires_at, /* @__PURE__ */ new Date())) {
|
|
23017
|
+
res.status(410).json({ detail: "This link has expired." });
|
|
23018
|
+
return;
|
|
23019
|
+
}
|
|
23020
|
+
if (row.auth_mode === "password") {
|
|
23021
|
+
const pw = req.headers["x-share-password"] || "";
|
|
23022
|
+
if (!row.password_hash || !await verifySharePassword(pw, row.password_hash)) {
|
|
23023
|
+
res.status(401).json({ detail: "Incorrect password." });
|
|
23024
|
+
return;
|
|
23025
|
+
}
|
|
23026
|
+
} else if (row.auth_mode === "regular") {
|
|
23027
|
+
const viewer = await requestValidators.authenticate(req);
|
|
23028
|
+
if (!viewer.user?.id) {
|
|
23029
|
+
res.status(401).json({ detail: "Authentication required." });
|
|
23030
|
+
return;
|
|
23031
|
+
}
|
|
23032
|
+
const rbac = await RBACResolver(
|
|
23033
|
+
db2,
|
|
23034
|
+
"shared_artifact",
|
|
23035
|
+
row.id,
|
|
23036
|
+
row.rights_mode || "private"
|
|
23037
|
+
);
|
|
23038
|
+
const ok = await checkRecordAccess({ ...row, RBAC: rbac }, "read", viewer.user);
|
|
23039
|
+
if (!ok) {
|
|
23040
|
+
res.status(403).json({ detail: "You don't have access to this artifact." });
|
|
23041
|
+
return;
|
|
23042
|
+
}
|
|
23043
|
+
}
|
|
23044
|
+
let bytes;
|
|
23045
|
+
try {
|
|
23046
|
+
bytes = await getS3ObjectBytes(row.s3key, config);
|
|
23047
|
+
} catch (e) {
|
|
23048
|
+
if (e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.$metadata?.httpStatusCode === 404) {
|
|
23049
|
+
res.status(404).json({ detail: "Artifact file not found." });
|
|
23050
|
+
return;
|
|
23051
|
+
}
|
|
23052
|
+
console.error("[EXULU] shared-artifact content read failed", e);
|
|
23053
|
+
res.status(500).json({ detail: "Failed to read artifact." });
|
|
23054
|
+
return;
|
|
23055
|
+
}
|
|
23056
|
+
const headers = contentHeadersFor(
|
|
23057
|
+
row.s3key,
|
|
23058
|
+
row.content_type,
|
|
23059
|
+
deriveFilename(row.s3key)
|
|
23060
|
+
);
|
|
23061
|
+
res.setHeader("Content-Type", headers.contentType);
|
|
23062
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
23063
|
+
res.setHeader("Cache-Control", "private, no-store");
|
|
23064
|
+
if (headers.disposition) res.setHeader("Content-Disposition", headers.disposition);
|
|
23065
|
+
res.send(bytes);
|
|
23066
|
+
});
|
|
22692
23067
|
app.use(import_express5.default.static("public"));
|
|
22693
23068
|
await registerOpenAIGatewayRoutes(app, providers, tools, contexts, config);
|
|
22694
23069
|
return app;
|
|
@@ -26668,6 +27043,83 @@ var RecursiveChunker = class _RecursiveChunker extends BaseChunker {
|
|
|
26668
27043
|
// src/index.ts
|
|
26669
27044
|
init_chunker();
|
|
26670
27045
|
init_context();
|
|
27046
|
+
|
|
27047
|
+
// src/exulu/read-api.ts
|
|
27048
|
+
init_cjs_shims();
|
|
27049
|
+
init_table_names();
|
|
27050
|
+
init_types();
|
|
27051
|
+
init_client();
|
|
27052
|
+
init_access_control();
|
|
27053
|
+
init_convert_context_to_table_definition();
|
|
27054
|
+
init_resolve_embedder();
|
|
27055
|
+
var authorizedRead = async (context, user, role, opts = {}) => {
|
|
27056
|
+
if (!opts.itemIds?.length && !opts.externalIds?.length) {
|
|
27057
|
+
throw new Error("authorizedRead requires itemIds or externalIds to constrain the read.");
|
|
27058
|
+
}
|
|
27059
|
+
const { db: db2 } = await postgresClient();
|
|
27060
|
+
const table = convertContextToTableDefinition(context);
|
|
27061
|
+
const itemsTable = getTableName(context.id);
|
|
27062
|
+
const chunksTable = getChunksTableName(context.id);
|
|
27063
|
+
const acUser = role && (!user.role || user.role.id !== role) ? { ...user, role: { ...user.role ?? {}, id: role } } : user;
|
|
27064
|
+
let q = db2(chunksTable + " as chunks").select([
|
|
27065
|
+
"chunks.id as chunk_id",
|
|
27066
|
+
"chunks.source as chunk_source",
|
|
27067
|
+
"chunks.content as chunk_content",
|
|
27068
|
+
"chunks.chunk_index",
|
|
27069
|
+
"chunks.metadata as chunk_metadata",
|
|
27070
|
+
db2.raw('chunks."createdAt" as chunk_created_at'),
|
|
27071
|
+
db2.raw('chunks."updatedAt" as chunk_updated_at'),
|
|
27072
|
+
"items.id as item_id",
|
|
27073
|
+
"items.name as item_name",
|
|
27074
|
+
"items.external_id as item_external_id",
|
|
27075
|
+
db2.raw('items."createdAt" as item_created_at'),
|
|
27076
|
+
db2.raw('items."updatedAt" as item_updated_at')
|
|
27077
|
+
]);
|
|
27078
|
+
q = q.leftJoin(itemsTable + " as items", "chunks.source", "items.id");
|
|
27079
|
+
if (opts.itemIds?.length) q = q.whereIn("items.id", opts.itemIds);
|
|
27080
|
+
if (opts.externalIds?.length) q = q.whereIn("items.external_id", opts.externalIds);
|
|
27081
|
+
if (opts.chunkIndexRange) {
|
|
27082
|
+
const { from, to } = opts.chunkIndexRange;
|
|
27083
|
+
if (typeof from === "number") q = q.where("chunks.chunk_index", ">=", from);
|
|
27084
|
+
if (typeof to === "number") q = q.where("chunks.chunk_index", "<=", to);
|
|
27085
|
+
}
|
|
27086
|
+
q = applyAccessControl(table, q, acUser, "items");
|
|
27087
|
+
q = q.orderBy("chunks.source").orderBy("chunks.chunk_index");
|
|
27088
|
+
return await q;
|
|
27089
|
+
};
|
|
27090
|
+
var entitiesAvailable = async (context) => {
|
|
27091
|
+
if (!context.entities) return false;
|
|
27092
|
+
const { db: db2 } = await postgresClient();
|
|
27093
|
+
const table = getChunkEntitiesTableName(context.id);
|
|
27094
|
+
const res = await db2.raw(
|
|
27095
|
+
"SELECT to_regclass(?) IS NOT NULL AS exists",
|
|
27096
|
+
[table]
|
|
27097
|
+
);
|
|
27098
|
+
return res.rows?.[0]?.exists === true;
|
|
27099
|
+
};
|
|
27100
|
+
var embedQuery = async (context, text, opts = {}) => {
|
|
27101
|
+
const resolved = await resolveEmbedder({
|
|
27102
|
+
model: context.embedder.model,
|
|
27103
|
+
contextId: context.id,
|
|
27104
|
+
contextName: context.name,
|
|
27105
|
+
user: opts.user,
|
|
27106
|
+
roleId: opts.role
|
|
27107
|
+
});
|
|
27108
|
+
const [vector] = await resolved.embed([text], { inputType: opts.inputType ?? "query" });
|
|
27109
|
+
return vector ?? [];
|
|
27110
|
+
};
|
|
27111
|
+
var ExuluReadApi = {
|
|
27112
|
+
getTableName,
|
|
27113
|
+
getChunksTableName,
|
|
27114
|
+
getEntitiesTableName,
|
|
27115
|
+
getChunkEntitiesTableName,
|
|
27116
|
+
entitiesAvailable,
|
|
27117
|
+
authorizedRead,
|
|
27118
|
+
embedQuery
|
|
27119
|
+
};
|
|
27120
|
+
|
|
27121
|
+
// src/index.ts
|
|
27122
|
+
init_client();
|
|
26671
27123
|
init_tool();
|
|
26672
27124
|
init_sentence2();
|
|
26673
27125
|
|
|
@@ -26704,7 +27156,8 @@ var {
|
|
|
26704
27156
|
promptFavoritesSchema: promptFavoritesSchema3,
|
|
26705
27157
|
transcriptionJobsSchema: transcriptionJobsSchema3,
|
|
26706
27158
|
imageGenerationsSchema: imageGenerationsSchema2,
|
|
26707
|
-
oauthTokensSchema: oauthTokensSchema2
|
|
27159
|
+
oauthTokensSchema: oauthTokensSchema2,
|
|
27160
|
+
sharedArtifactsSchema: sharedArtifactsSchema2
|
|
26708
27161
|
} = coreSchemas.get();
|
|
26709
27162
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
26710
27163
|
for (const field of fields) {
|
|
@@ -26748,6 +27201,7 @@ var up = async function(knex) {
|
|
|
26748
27201
|
transcriptionJobsSchema3(),
|
|
26749
27202
|
imageGenerationsSchema2(),
|
|
26750
27203
|
oauthTokensSchema2(),
|
|
27204
|
+
sharedArtifactsSchema2(),
|
|
26751
27205
|
rbacSchema3(),
|
|
26752
27206
|
agentsSchema3(),
|
|
26753
27207
|
feedbackSchema3(),
|
|
@@ -27038,7 +27492,7 @@ var checkLiteLLMDatabaseSafety = (configPath) => {
|
|
|
27038
27492
|
|
|
27039
27493
|
// src/exulu/litellm/db-init.ts
|
|
27040
27494
|
var WARNING_BANNER = "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550";
|
|
27041
|
-
var
|
|
27495
|
+
var warn2 = (lines) => {
|
|
27042
27496
|
console.warn(`
|
|
27043
27497
|
${WARNING_BANNER}`);
|
|
27044
27498
|
console.warn("\u26A0 [EXULU-LITELLM] CONFIGURATION WARNING");
|
|
@@ -27046,13 +27500,13 @@ ${WARNING_BANNER}`);
|
|
|
27046
27500
|
console.warn(`${WARNING_BANNER}
|
|
27047
27501
|
`);
|
|
27048
27502
|
};
|
|
27049
|
-
var
|
|
27503
|
+
var log5 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
27050
27504
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
27051
27505
|
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path9.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
27052
27506
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
27053
27507
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
27054
27508
|
if (!safety.ok && safety.reason === "unparseable-url") {
|
|
27055
|
-
|
|
27509
|
+
warn2([
|
|
27056
27510
|
`LiteLLM's database_url is not a valid postgres URL:`,
|
|
27057
27511
|
` ${safety.rawUrl}`,
|
|
27058
27512
|
`Expected postgres://user:pass@host:port/database. Skipping setup.`
|
|
@@ -27061,7 +27515,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27061
27515
|
}
|
|
27062
27516
|
if (!safety.ok && safety.reason === "shared-with-exulu") {
|
|
27063
27517
|
const { exuluTarget } = safety;
|
|
27064
|
-
|
|
27518
|
+
warn2([
|
|
27065
27519
|
`LiteLLM's database_url points to the SAME database Exulu uses:`,
|
|
27066
27520
|
` ${exuluTarget.host}:${exuluTarget.port}/${exuluTarget.database}`,
|
|
27067
27521
|
``,
|
|
@@ -27080,7 +27534,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27080
27534
|
return;
|
|
27081
27535
|
}
|
|
27082
27536
|
const target = "litellmTarget" in safety ? safety.litellmTarget : void 0;
|
|
27083
|
-
|
|
27537
|
+
log5(
|
|
27084
27538
|
`LiteLLM database mode detected (${target?.host}:${target?.port}/${target?.database}).`
|
|
27085
27539
|
);
|
|
27086
27540
|
const ensureDatabaseExists2 = async () => {
|
|
@@ -27092,7 +27546,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27092
27546
|
} catch (err) {
|
|
27093
27547
|
const code = err?.code;
|
|
27094
27548
|
if (code !== "3D000") {
|
|
27095
|
-
|
|
27549
|
+
warn2([
|
|
27096
27550
|
`Could not connect to LiteLLM's target database:`,
|
|
27097
27551
|
` ${err instanceof Error ? err.message : String(err)}`,
|
|
27098
27552
|
``,
|
|
@@ -27104,13 +27558,13 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27104
27558
|
const url = new URL(litellmUrl);
|
|
27105
27559
|
const targetDbName = url.pathname.replace(/^\//, "");
|
|
27106
27560
|
if (!targetDbName) {
|
|
27107
|
-
|
|
27561
|
+
warn2([`LiteLLM database_url has no database name; cannot auto-create.`]);
|
|
27108
27562
|
return false;
|
|
27109
27563
|
}
|
|
27110
27564
|
url.pathname = "/postgres";
|
|
27111
|
-
|
|
27565
|
+
log5(`Target database "${targetDbName}" does not exist; creating it\u2026`);
|
|
27112
27566
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(targetDbName)) {
|
|
27113
|
-
|
|
27567
|
+
warn2([
|
|
27114
27568
|
`Refusing to auto-create database "${targetDbName}" \u2014 name`,
|
|
27115
27569
|
`contains characters that would require quoting. Create it`,
|
|
27116
27570
|
`manually: createdb -h ${url.hostname} -U ${url.username} ${targetDbName}`
|
|
@@ -27121,10 +27575,10 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27121
27575
|
try {
|
|
27122
27576
|
await admin.connect();
|
|
27123
27577
|
await admin.query(`CREATE DATABASE "${targetDbName}"`);
|
|
27124
|
-
|
|
27578
|
+
log5(`\u2713 Created database "${targetDbName}".`);
|
|
27125
27579
|
return true;
|
|
27126
27580
|
} catch (createErr) {
|
|
27127
|
-
|
|
27581
|
+
warn2([
|
|
27128
27582
|
`Failed to auto-create database "${targetDbName}":`,
|
|
27129
27583
|
` ${createErr instanceof Error ? createErr.message : String(createErr)}`,
|
|
27130
27584
|
``,
|
|
@@ -27143,7 +27597,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27143
27597
|
}
|
|
27144
27598
|
};
|
|
27145
27599
|
if (!await ensureDatabaseExists2()) return;
|
|
27146
|
-
|
|
27600
|
+
log5("Checking that the target database is safe to push into\u2026");
|
|
27147
27601
|
const client2 = new import_pg.Client({ connectionString: litellmUrl });
|
|
27148
27602
|
let foreignTables = [];
|
|
27149
27603
|
try {
|
|
@@ -27159,7 +27613,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27159
27613
|
);
|
|
27160
27614
|
foreignTables = res.rows.map((r) => r.table_name);
|
|
27161
27615
|
} catch (err) {
|
|
27162
|
-
|
|
27616
|
+
warn2([
|
|
27163
27617
|
`Could not query LiteLLM's target database to verify it is safe`,
|
|
27164
27618
|
`to push into:`,
|
|
27165
27619
|
` ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -27175,7 +27629,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27175
27629
|
}
|
|
27176
27630
|
}
|
|
27177
27631
|
if (foreignTables.length > 0) {
|
|
27178
|
-
|
|
27632
|
+
warn2([
|
|
27179
27633
|
`LiteLLM's target database contains ${foreignTables.length} table(s) that are NOT`,
|
|
27180
27634
|
`part of LiteLLM's schema:`,
|
|
27181
27635
|
...foreignTables.slice(0, 10).map((t) => ` - ${t}`),
|
|
@@ -27191,7 +27645,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27191
27645
|
const venvLibDir = (0, import_node_path9.resolve)(packageRoot, "ee/python/.venv/lib");
|
|
27192
27646
|
const pythonVersionDir = (0, import_node_fs9.existsSync)(venvLibDir) ? (0, import_node_fs9.readdirSync)(venvLibDir).find((entry) => /^python3\.\d+$/.test(entry)) : void 0;
|
|
27193
27647
|
if (!pythonVersionDir) {
|
|
27194
|
-
|
|
27648
|
+
warn2([
|
|
27195
27649
|
`Could not find a python3.* directory under ${venvLibDir}.`,
|
|
27196
27650
|
`Run \`npm run python:setup\` to create the venv.`,
|
|
27197
27651
|
`Skipping LiteLLM database setup.`
|
|
@@ -27205,7 +27659,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27205
27659
|
);
|
|
27206
27660
|
const schemaPath = (0, import_node_path9.resolve)(litellmProxyDir, "schema.prisma");
|
|
27207
27661
|
if (!(0, import_node_fs9.existsSync)(prismaCli)) {
|
|
27208
|
-
|
|
27662
|
+
warn2([
|
|
27209
27663
|
`Prisma CLI not found at ${prismaCli}.`,
|
|
27210
27664
|
`Run \`npm run python:setup\` to create the venv and install prisma.`,
|
|
27211
27665
|
`Skipping LiteLLM database setup.`
|
|
@@ -27213,13 +27667,13 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27213
27667
|
return;
|
|
27214
27668
|
}
|
|
27215
27669
|
if (!(0, import_node_fs9.existsSync)(schemaPath)) {
|
|
27216
|
-
|
|
27670
|
+
warn2([
|
|
27217
27671
|
`LiteLLM Prisma schema not found at ${schemaPath}.`,
|
|
27218
27672
|
`Re-run \`npm run python:setup\`. Skipping LiteLLM database setup.`
|
|
27219
27673
|
]);
|
|
27220
27674
|
return;
|
|
27221
27675
|
}
|
|
27222
|
-
|
|
27676
|
+
log5("Running `prisma db push` against LiteLLM's schema\u2026");
|
|
27223
27677
|
const result = (0, import_node_child_process5.spawnSync)(prismaCli, ["db", "push", "--skip-generate"], {
|
|
27224
27678
|
cwd: litellmProxyDir,
|
|
27225
27679
|
env: {
|
|
@@ -27233,14 +27687,14 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27233
27687
|
encoding: "utf8"
|
|
27234
27688
|
});
|
|
27235
27689
|
if (result.error) {
|
|
27236
|
-
|
|
27690
|
+
warn2([
|
|
27237
27691
|
`Failed to launch prisma: ${result.error.message}`,
|
|
27238
27692
|
`Skipping LiteLLM database setup.`
|
|
27239
27693
|
]);
|
|
27240
27694
|
return;
|
|
27241
27695
|
}
|
|
27242
27696
|
if (result.status !== 0) {
|
|
27243
|
-
|
|
27697
|
+
warn2([
|
|
27244
27698
|
`prisma db push exited with status ${result.status}.`,
|
|
27245
27699
|
`stdout:`,
|
|
27246
27700
|
...(result.stdout || "(empty)").split("\n").map((l) => ` ${l}`),
|
|
@@ -27249,7 +27703,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27249
27703
|
]);
|
|
27250
27704
|
return;
|
|
27251
27705
|
}
|
|
27252
|
-
|
|
27706
|
+
log5("\u2713 LiteLLM database ready.");
|
|
27253
27707
|
};
|
|
27254
27708
|
|
|
27255
27709
|
// src/postgres/init-litellm-db.ts
|
|
@@ -28532,14 +28986,37 @@ ${setupResult.output || ""}`);
|
|
|
28532
28986
|
model: config.processor.model ?? "mistral-ocr",
|
|
28533
28987
|
...config.attribution
|
|
28534
28988
|
});
|
|
28535
|
-
|
|
28536
|
-
const
|
|
28537
|
-
const
|
|
28538
|
-
|
|
28539
|
-
|
|
28540
|
-
|
|
28541
|
-
|
|
28542
|
-
|
|
28989
|
+
const maxPagesPerChunk = config.processor.maxPagesPerChunk ?? 25;
|
|
28990
|
+
const chunksDir = path2.join(path2.dirname(paths.json), "ocr_chunks");
|
|
28991
|
+
const splitResult = await executePythonScript({
|
|
28992
|
+
scriptPath: "ee/python/documents/processing/split_pdf.py",
|
|
28993
|
+
args: [paths.source, chunksDir, "--chunk-size", String(maxPagesPerChunk)],
|
|
28994
|
+
timeout: 5 * 60 * 1e3
|
|
28995
|
+
});
|
|
28996
|
+
const pdfChunks = JSON.parse(splitResult.stdout);
|
|
28997
|
+
console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
|
|
28998
|
+
const chunkLimit = (0, import_p_limit.default)(3);
|
|
28999
|
+
const chunkResults = await Promise.all(
|
|
29000
|
+
pdfChunks.map(
|
|
29001
|
+
(chunk, i) => chunkLimit(async () => {
|
|
29002
|
+
await new Promise((resolve8) => setImmediate(resolve8));
|
|
29003
|
+
await new Promise((resolve8) => setTimeout(resolve8, Math.floor(Math.random() * 1e3) + 200));
|
|
29004
|
+
console.log(`[EXULU] OCR chunk ${i + 1}/${pdfChunks.length}: pages ${chunk.start_page}\u2013${chunk.end_page - 1}`);
|
|
29005
|
+
const chunkBuffer = await fs5.promises.readFile(chunk.path);
|
|
29006
|
+
const chunkBase64 = chunkBuffer.toString("base64");
|
|
29007
|
+
const chunkResponse = await withRetry(async () => {
|
|
29008
|
+
return await resolved.ocr({
|
|
29009
|
+
type: "document_url",
|
|
29010
|
+
document_url: "data:application/pdf;base64," + chunkBase64
|
|
29011
|
+
}, { includeImageBase64: false });
|
|
29012
|
+
}, 10);
|
|
29013
|
+
return { pages: chunkResponse.pages, offset: chunk.start_page };
|
|
29014
|
+
})
|
|
29015
|
+
)
|
|
29016
|
+
);
|
|
29017
|
+
const mergedPages = chunkResults.sort((a, b) => a.offset - b.offset).flatMap(
|
|
29018
|
+
({ pages, offset }) => pages.map((p) => ({ ...p, index: p.index + offset }))
|
|
29019
|
+
);
|
|
28543
29020
|
const parser = new import_liteparse.LiteParse();
|
|
28544
29021
|
const screenshots = await parser.screenshot(paths.source, void 0);
|
|
28545
29022
|
await fs5.promises.mkdir(paths.images, { recursive: true });
|
|
@@ -28553,7 +29030,7 @@ ${setupResult.output || ""}`);
|
|
|
28553
29030
|
);
|
|
28554
29031
|
screenshot.imagePath = path2.join(paths.images, `${screenshot.pageNum}.png`);
|
|
28555
29032
|
}
|
|
28556
|
-
json =
|
|
29033
|
+
json = mergedPages.map((page) => ({
|
|
28557
29034
|
page: page.index + 1,
|
|
28558
29035
|
content: page.markdown,
|
|
28559
29036
|
image: screenshots.find((s) => s.pageNum === page.index + 1)?.imagePath,
|
|
@@ -28887,9 +29364,11 @@ var ExuluPython = {
|
|
|
28887
29364
|
ExuluProvider,
|
|
28888
29365
|
ExuluPython,
|
|
28889
29366
|
ExuluQueues,
|
|
29367
|
+
ExuluReadApi,
|
|
28890
29368
|
ExuluReranker,
|
|
28891
29369
|
ExuluTool,
|
|
28892
29370
|
ExuluTrajectoryRegistry,
|
|
28893
29371
|
ExuluVariables,
|
|
28894
|
-
defaultChunker
|
|
29372
|
+
defaultChunker,
|
|
29373
|
+
postgresClient
|
|
28895
29374
|
});
|