@exulu/backend 1.68.0 → 1.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-VPSLTGZF.js → chunk-IVC2M56U.js} +157 -47
- package/dist/{convert-exulu-tools-to-ai-sdk-tools-CHQF36XW.js → convert-exulu-tools-to-ai-sdk-tools-ET6UI7YG.js} +1 -1
- package/dist/index.cjs +582 -112
- 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";
|
|
@@ -2737,11 +2749,20 @@ function durationToDays(duration) {
|
|
|
2737
2749
|
}
|
|
2738
2750
|
function windowStartYmd(reset_at, duration) {
|
|
2739
2751
|
const days = durationToDays(duration);
|
|
2752
|
+
const windowMs = days * DAY_MS;
|
|
2753
|
+
const now = Date.now();
|
|
2740
2754
|
const reset = reset_at ? new Date(reset_at) : null;
|
|
2741
|
-
const
|
|
2742
|
-
const trailingStart = new Date(
|
|
2743
|
-
|
|
2744
|
-
|
|
2755
|
+
const resetMs = reset && !Number.isNaN(reset.getTime()) ? reset.getTime() : null;
|
|
2756
|
+
const trailingStart = new Date(now - windowMs);
|
|
2757
|
+
if (resetMs !== null) {
|
|
2758
|
+
if (resetMs > now) {
|
|
2759
|
+
const periodStart = new Date(resetMs - windowMs);
|
|
2760
|
+
return ymd(periodStart > trailingStart ? periodStart : trailingStart);
|
|
2761
|
+
} else {
|
|
2762
|
+
return ymd(reset > trailingStart ? reset : trailingStart);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return ymd(trailingStart);
|
|
2745
2766
|
}
|
|
2746
2767
|
async function enrichSpendFromActivity(map) {
|
|
2747
2768
|
const names = Object.keys(map);
|
|
@@ -2752,7 +2773,10 @@ async function enrichSpendFromActivity(map) {
|
|
|
2752
2773
|
windows[name] = windowStartYmd(ti.budget_reset_at, ti.budget_duration);
|
|
2753
2774
|
}
|
|
2754
2775
|
try {
|
|
2755
|
-
const spendByTag = await getTagSpendByWindow(
|
|
2776
|
+
const spendByTag = await getTagSpendByWindow(
|
|
2777
|
+
windows,
|
|
2778
|
+
ymd(new Date(Date.now() + DAY_MS))
|
|
2779
|
+
);
|
|
2756
2780
|
for (const name of names) {
|
|
2757
2781
|
const spend = spendByTag[name];
|
|
2758
2782
|
if (typeof spend === "number" && Number.isFinite(spend)) {
|
|
@@ -2876,6 +2900,7 @@ async function getUserBudgetView(userId) {
|
|
|
2876
2900
|
readCache.set(tag, { expiry: Date.now() + READ_TTL_MS, view: null });
|
|
2877
2901
|
return null;
|
|
2878
2902
|
}
|
|
2903
|
+
await provisionDefaultUserBudget(userId);
|
|
2879
2904
|
const info = await tagInfo([tag]);
|
|
2880
2905
|
const ti = info[tag];
|
|
2881
2906
|
if (ti?.max_budget != null) {
|
|
@@ -3847,7 +3872,7 @@ var init_entitlements = __esm({
|
|
|
3847
3872
|
});
|
|
3848
3873
|
|
|
3849
3874
|
// 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;
|
|
3875
|
+
var agentMessagesSchema, agentSessionsSchema, skillsSchema, variablesSchema, projectsSchema, agentsSchema, modelsSchema, usersSchema, platformConfigurationsSchema, entityTypeSettingsSchema, promptLibrarySchema, promptFavoritesSchema, transcriptionJobsSchema, imageGenerationsSchema, oauthTokensSchema, sharedArtifactsSchema, contextPresetsSchema, addCoreFields, coreSchemas;
|
|
3851
3876
|
var init_core_schema = __esm({
|
|
3852
3877
|
"src/postgres/core-schema.ts"() {
|
|
3853
3878
|
"use strict";
|
|
@@ -4112,6 +4137,11 @@ var init_core_schema = __esm({
|
|
|
4112
4137
|
{
|
|
4113
4138
|
name: "animation_responding",
|
|
4114
4139
|
type: "text"
|
|
4140
|
+
},
|
|
4141
|
+
{
|
|
4142
|
+
name: "sandbox_enabled",
|
|
4143
|
+
type: "boolean",
|
|
4144
|
+
default: false
|
|
4115
4145
|
}
|
|
4116
4146
|
]
|
|
4117
4147
|
};
|
|
@@ -4517,6 +4547,26 @@ var init_core_schema = __esm({
|
|
|
4517
4547
|
// null = non-expiring
|
|
4518
4548
|
]
|
|
4519
4549
|
};
|
|
4550
|
+
sharedArtifactsSchema = {
|
|
4551
|
+
type: "shared_artifacts",
|
|
4552
|
+
name: {
|
|
4553
|
+
plural: "shared_artifacts",
|
|
4554
|
+
singular: "shared_artifact"
|
|
4555
|
+
},
|
|
4556
|
+
// RBAC drives the "regular" auth_mode: rights_mode + the rbac table scope
|
|
4557
|
+
// who may view. public/password modes ignore rights_mode.
|
|
4558
|
+
RBAC: true,
|
|
4559
|
+
fields: [
|
|
4560
|
+
{ name: "name", type: "text", index: true, unique: true, required: true },
|
|
4561
|
+
{ name: "s3key", type: "text", required: true },
|
|
4562
|
+
{ name: "auth_mode", type: "text", default: "regular" },
|
|
4563
|
+
{ name: "password_hash", type: "text", required: false },
|
|
4564
|
+
// bcrypt; password mode only
|
|
4565
|
+
{ name: "expires_at", type: "date", required: false },
|
|
4566
|
+
// null = no expiry
|
|
4567
|
+
{ name: "content_type", type: "text", required: false }
|
|
4568
|
+
]
|
|
4569
|
+
};
|
|
4520
4570
|
contextPresetsSchema = {
|
|
4521
4571
|
type: "context_presets",
|
|
4522
4572
|
name: {
|
|
@@ -4609,6 +4659,7 @@ var init_core_schema = __esm({
|
|
|
4609
4659
|
promptFavoritesSchema: () => addCoreFields(promptFavoritesSchema),
|
|
4610
4660
|
contextPresetsSchema: () => addCoreFields(contextPresetsSchema),
|
|
4611
4661
|
oauthTokensSchema: () => addCoreFields(oauthTokensSchema),
|
|
4662
|
+
sharedArtifactsSchema: () => addCoreFields(sharedArtifactsSchema),
|
|
4612
4663
|
transcriptionJobsSchema: () => addCoreFields(transcriptionJobsSchema),
|
|
4613
4664
|
imageGenerationsSchema: () => addCoreFields(imageGenerationsSchema)
|
|
4614
4665
|
};
|
|
@@ -5105,7 +5156,12 @@ var init_resolve_model = __esm({
|
|
|
5105
5156
|
// supports — including Vertex Gemini, which translates it into
|
|
5106
5157
|
// responseSchema/responseMimeType — so enabling this matches the actual
|
|
5107
5158
|
// proxy contract.
|
|
5108
|
-
supportsStructuredOutputs: true
|
|
5159
|
+
supportsStructuredOutputs: true,
|
|
5160
|
+
// Request token usage on STREAMED responses. Without this the openai-compatible
|
|
5161
|
+
// provider omits `stream_options: { include_usage: true }`, so LiteLLM returns no
|
|
5162
|
+
// usage for streaming calls — which zeroes out the per-request token metrics and
|
|
5163
|
+
// the message-footer token count (both read the AI SDK `totalUsage`/finish-part usage).
|
|
5164
|
+
includeUsage: true
|
|
5109
5165
|
});
|
|
5110
5166
|
};
|
|
5111
5167
|
}
|
|
@@ -5768,7 +5824,8 @@ var init_vector_search = __esm({
|
|
|
5768
5824
|
trigger,
|
|
5769
5825
|
cutoffs,
|
|
5770
5826
|
expand,
|
|
5771
|
-
entityFilter
|
|
5827
|
+
entityFilter,
|
|
5828
|
+
queryEmbedding
|
|
5772
5829
|
}) => {
|
|
5773
5830
|
const table = convertContextToTableDefinition(context);
|
|
5774
5831
|
console.log("[EXULU] Called vector search.", {
|
|
@@ -5850,36 +5907,53 @@ var init_vector_search = __esm({
|
|
|
5850
5907
|
let vector = [];
|
|
5851
5908
|
let vectorStr = "";
|
|
5852
5909
|
let vectorExpr = "";
|
|
5910
|
+
const _tBody = Date.now();
|
|
5911
|
+
let _preMs = 0, _statMs = 0, _resolveMs = 0, _embedMs = 0;
|
|
5912
|
+
let _embedSource = "none";
|
|
5853
5913
|
if (query) {
|
|
5914
|
+
const _tp = Date.now();
|
|
5854
5915
|
const { processed: stemmedQuery } = preprocessQuery(query, {
|
|
5855
5916
|
enableStemming: true,
|
|
5856
5917
|
detectLanguage: true
|
|
5857
5918
|
});
|
|
5919
|
+
_preMs = Date.now() - _tp;
|
|
5858
5920
|
console.log("[EXULU] Stemmed query:", stemmedQuery);
|
|
5859
5921
|
if (stemmedQuery) {
|
|
5860
5922
|
query = stemmedQuery;
|
|
5861
5923
|
}
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5924
|
+
if (queryEmbedding && queryEmbedding.length) {
|
|
5925
|
+
vector = queryEmbedding;
|
|
5926
|
+
_embedSource = "reused";
|
|
5927
|
+
} else {
|
|
5928
|
+
const _ts = Date.now();
|
|
5929
|
+
await updateStatistic({
|
|
5930
|
+
name: "count",
|
|
5931
|
+
label: table.name.singular,
|
|
5932
|
+
type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
|
|
5933
|
+
trigger,
|
|
5934
|
+
count: 1,
|
|
5935
|
+
user: user?.id,
|
|
5936
|
+
role
|
|
5937
|
+
});
|
|
5938
|
+
_statMs = Date.now() - _ts;
|
|
5939
|
+
const _tr = Date.now();
|
|
5940
|
+
const resolved = await resolveEmbedder({
|
|
5941
|
+
model: embedder.model,
|
|
5942
|
+
contextId: context.id,
|
|
5943
|
+
contextName: context.name,
|
|
5944
|
+
user,
|
|
5945
|
+
roleId: role
|
|
5946
|
+
});
|
|
5947
|
+
_resolveMs = Date.now() - _tr;
|
|
5948
|
+
const _te = Date.now();
|
|
5949
|
+
const [queryVector] = await resolved.embed([query], { inputType: "query" });
|
|
5950
|
+
_embedMs = Date.now() - _te;
|
|
5951
|
+
if (!queryVector?.length) {
|
|
5952
|
+
throw new Error("No vector generated for query.");
|
|
5953
|
+
}
|
|
5954
|
+
vector = queryVector;
|
|
5955
|
+
_embedSource = "computed";
|
|
5881
5956
|
}
|
|
5882
|
-
vector = queryVector;
|
|
5883
5957
|
vectorStr = `ARRAY[${vector.join(",")}]`;
|
|
5884
5958
|
vectorExpr = `${vectorStr}::vector`;
|
|
5885
5959
|
}
|
|
@@ -5906,6 +5980,7 @@ var init_vector_search = __esm({
|
|
|
5906
5980
|
const languages = configuration.languages?.length ? configuration.languages : ["english"];
|
|
5907
5981
|
console.log("[EXULU] Vector search params:", { method, query, cutoffs, languages });
|
|
5908
5982
|
let resultChunks = [];
|
|
5983
|
+
const _tSql = Date.now();
|
|
5909
5984
|
switch (method) {
|
|
5910
5985
|
case "tsvector":
|
|
5911
5986
|
chunksQuery.limit(limit * 2);
|
|
@@ -6019,6 +6094,11 @@ var init_vector_search = __esm({
|
|
|
6019
6094
|
]).orderByRaw("hybrid_score DESC").limit(Math.min(matchCount, 250));
|
|
6020
6095
|
resultChunks = await hybridQuery;
|
|
6021
6096
|
}
|
|
6097
|
+
if (process.env.EXULU_VS_TIMING) {
|
|
6098
|
+
console.log(
|
|
6099
|
+
`[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`
|
|
6100
|
+
);
|
|
6101
|
+
}
|
|
6022
6102
|
console.log("[EXULU] Vector search chunk results:", resultChunks?.length);
|
|
6023
6103
|
let results = resultChunks.map((chunk) => ({
|
|
6024
6104
|
chunk_content: chunk.content,
|
|
@@ -6480,13 +6560,14 @@ var init_decorator = __esm({
|
|
|
6480
6560
|
});
|
|
6481
6561
|
|
|
6482
6562
|
// src/exulu/context.ts
|
|
6483
|
-
var import_knex5,
|
|
6563
|
+
var import_knex5, ExuluContext2;
|
|
6484
6564
|
var init_context = __esm({
|
|
6485
6565
|
"src/exulu/context.ts"() {
|
|
6486
6566
|
"use strict";
|
|
6487
6567
|
init_cjs_shims();
|
|
6488
6568
|
init_storage();
|
|
6489
6569
|
init_sanitize_name();
|
|
6570
|
+
init_table_names();
|
|
6490
6571
|
import_knex5 = __toESM(require("pgvector/knex"), 1);
|
|
6491
6572
|
init_chunker();
|
|
6492
6573
|
init_resolve_embedder();
|
|
@@ -6497,15 +6578,10 @@ var init_context = __esm({
|
|
|
6497
6578
|
init_vector_search();
|
|
6498
6579
|
init_convert_context_to_table_definition();
|
|
6499
6580
|
init_apply_filters();
|
|
6581
|
+
init_access_control();
|
|
6500
6582
|
init_map_types();
|
|
6501
6583
|
init_decorator();
|
|
6502
6584
|
init_entities();
|
|
6503
|
-
getTableName = (id) => {
|
|
6504
|
-
return sanitizeName(id) + "_items";
|
|
6505
|
-
};
|
|
6506
|
-
getChunksTableName = (id) => {
|
|
6507
|
-
return sanitizeName(id) + "_chunks";
|
|
6508
|
-
};
|
|
6509
6585
|
ExuluContext2 = class {
|
|
6510
6586
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
6511
6587
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -6999,12 +7075,18 @@ var init_context = __esm({
|
|
|
6999
7075
|
};
|
|
7000
7076
|
getItems = async ({
|
|
7001
7077
|
filters,
|
|
7002
|
-
fields
|
|
7078
|
+
fields,
|
|
7079
|
+
user,
|
|
7080
|
+
role
|
|
7003
7081
|
}) => {
|
|
7004
7082
|
const { db: db2 } = await postgresClient();
|
|
7005
|
-
let query = db2.from(getTableName(this.id)).select(fields || "*");
|
|
7006
7083
|
const tableDefinition = convertContextToTableDefinition(this);
|
|
7084
|
+
let query = db2.from(getTableName(this.id)).select(fields || "*");
|
|
7007
7085
|
query = applyFilters(query, filters || [], tableDefinition);
|
|
7086
|
+
if (user) {
|
|
7087
|
+
const acUser = role && (!user.role || user.role.id !== role) ? { ...user, role: { ...user.role ?? {}, id: role } } : user;
|
|
7088
|
+
query = applyAccessControl(tableDefinition, query, acUser);
|
|
7089
|
+
}
|
|
7008
7090
|
const items = await query;
|
|
7009
7091
|
return items;
|
|
7010
7092
|
};
|
|
@@ -7365,11 +7447,9 @@ async function resolveReranker(input) {
|
|
|
7365
7447
|
rerank_score: r.relevance_score ?? 0
|
|
7366
7448
|
}));
|
|
7367
7449
|
reranked.sort((a, b) => b.rerank_score - a.rerank_score);
|
|
7368
|
-
import_fs.default.writeFileSync("reranked.json", JSON.stringify(reranked, null, 2));
|
|
7369
7450
|
return reranked;
|
|
7370
7451
|
} catch (err) {
|
|
7371
7452
|
console.error("[EXULU] Error reranking:", err);
|
|
7372
|
-
import_fs.default.writeFileSync("reranked.json", JSON.stringify(err, null, 2));
|
|
7373
7453
|
return [];
|
|
7374
7454
|
}
|
|
7375
7455
|
};
|
|
@@ -7383,7 +7463,7 @@ var init_resolve_reranker = __esm({
|
|
|
7383
7463
|
init_supervisor();
|
|
7384
7464
|
init_budget_service();
|
|
7385
7465
|
init_tags();
|
|
7386
|
-
import_fs =
|
|
7466
|
+
import_fs = require("fs");
|
|
7387
7467
|
ResolveRerankerError = class extends Error {
|
|
7388
7468
|
constructor(code, message) {
|
|
7389
7469
|
super(message);
|
|
@@ -7984,9 +8064,21 @@ var init_memory_tool = __esm({
|
|
|
7984
8064
|
case "longText":
|
|
7985
8065
|
case "shortText":
|
|
7986
8066
|
case "code":
|
|
7987
|
-
case "enum":
|
|
7988
8067
|
fields[field.name] = import_zod4.z.string().describe("The " + field.name + " of the item to create");
|
|
7989
8068
|
break;
|
|
8069
|
+
case "enum":
|
|
8070
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
8071
|
+
const enumValues = field.enumValues;
|
|
8072
|
+
fields[field.name] = import_zod4.z.preprocess(
|
|
8073
|
+
(v) => typeof v === "string" ? v.toUpperCase() : v,
|
|
8074
|
+
import_zod4.z.enum(enumValues)
|
|
8075
|
+
).describe(
|
|
8076
|
+
"The " + field.name + " of the item to create. Must be one of: " + field.enumValues.join(", ")
|
|
8077
|
+
);
|
|
8078
|
+
} else {
|
|
8079
|
+
fields[field.name] = import_zod4.z.string().describe("The " + field.name + " of the item to create");
|
|
8080
|
+
}
|
|
8081
|
+
break;
|
|
7990
8082
|
case "json":
|
|
7991
8083
|
fields[field.name] = import_zod4.z.string({}).describe(
|
|
7992
8084
|
"The " + field.name + " of the item to create, it should be a valid JSON string."
|
|
@@ -8012,6 +8104,9 @@ var init_memory_tool = __esm({
|
|
|
8012
8104
|
break;
|
|
8013
8105
|
}
|
|
8014
8106
|
}
|
|
8107
|
+
fields["visibility"] = import_zod4.z.enum(["private", "public"]).optional().describe(
|
|
8108
|
+
"Whether this memory is private to the user or shared (public). Ask the user if unknown."
|
|
8109
|
+
);
|
|
8015
8110
|
const toolName = "create_" + sanitizeName(context.name) + "_memory_item";
|
|
8016
8111
|
return new ExuluTool({
|
|
8017
8112
|
id: toolName,
|
|
@@ -8021,14 +8116,36 @@ var init_memory_tool = __esm({
|
|
|
8021
8116
|
type: "function",
|
|
8022
8117
|
inputSchema: import_zod4.z.object(fields),
|
|
8023
8118
|
config: [],
|
|
8024
|
-
execute: async (
|
|
8119
|
+
execute: async (params) => {
|
|
8120
|
+
const { name, description, surroundingContext, information, visibility, exuluConfig, user } = params;
|
|
8025
8121
|
let result = { result: "" };
|
|
8122
|
+
if (!visibility) {
|
|
8123
|
+
return {
|
|
8124
|
+
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.`
|
|
8125
|
+
};
|
|
8126
|
+
}
|
|
8026
8127
|
try {
|
|
8128
|
+
const extraFields = {};
|
|
8129
|
+
for (const field of context.fields ?? []) {
|
|
8130
|
+
if (field.type === "enum" && field.enumValues && field.enumValues.length > 0) {
|
|
8131
|
+
const raw = params[field.name];
|
|
8132
|
+
if (raw !== void 0 && raw !== null && raw !== "") {
|
|
8133
|
+
const rawStr = String(raw);
|
|
8134
|
+
const canonical = field.enumValues.find(
|
|
8135
|
+
(v) => v.toUpperCase() === rawStr.toUpperCase()
|
|
8136
|
+
);
|
|
8137
|
+
if (canonical !== void 0) {
|
|
8138
|
+
extraFields[field.name] = canonical;
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
}
|
|
8142
|
+
}
|
|
8027
8143
|
const newItem = {
|
|
8028
8144
|
name,
|
|
8029
8145
|
description: "Description: " + description + "\n\nSurrounding Context: " + surroundingContext,
|
|
8030
8146
|
information: "Information: " + information,
|
|
8031
|
-
rights_mode: "public"
|
|
8147
|
+
rights_mode: visibility === "private" ? "private" : "public",
|
|
8148
|
+
...extraFields
|
|
8032
8149
|
};
|
|
8033
8150
|
const { item: createdItem, job: createdJob } = await context.createItem(
|
|
8034
8151
|
newItem,
|
|
@@ -8841,7 +8958,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
8841
8958
|
contexts = [];
|
|
8842
8959
|
}
|
|
8843
8960
|
let sharedSessionSandbox;
|
|
8844
|
-
if (sessionID && exuluConfig) {
|
|
8961
|
+
if (sessionID && exuluConfig && agent?.sandbox_enabled === true) {
|
|
8845
8962
|
try {
|
|
8846
8963
|
sharedSessionSandbox = await createSessionSandbox(
|
|
8847
8964
|
sessionID,
|
|
@@ -10922,11 +11039,13 @@ __export(index_exports, {
|
|
|
10922
11039
|
ExuluProvider: () => ExuluProvider,
|
|
10923
11040
|
ExuluPython: () => ExuluPython,
|
|
10924
11041
|
ExuluQueues: () => queues,
|
|
11042
|
+
ExuluReadApi: () => ExuluReadApi,
|
|
10925
11043
|
ExuluReranker: () => ExuluReranker,
|
|
10926
11044
|
ExuluTool: () => ExuluTool,
|
|
10927
11045
|
ExuluTrajectoryRegistry: () => trajectoryRegistry,
|
|
10928
11046
|
ExuluVariables: () => ExuluVariables,
|
|
10929
|
-
defaultChunker: () => defaultChunker
|
|
11047
|
+
defaultChunker: () => defaultChunker,
|
|
11048
|
+
postgresClient: () => postgresClient
|
|
10930
11049
|
});
|
|
10931
11050
|
module.exports = __toCommonJS(index_exports);
|
|
10932
11051
|
init_cjs_shims();
|
|
@@ -10945,6 +11064,73 @@ var redisServer = {
|
|
|
10945
11064
|
username: process.env.REDIS_USER || ""
|
|
10946
11065
|
};
|
|
10947
11066
|
|
|
11067
|
+
// ee/queues/redis-startup.ts
|
|
11068
|
+
init_cjs_shims();
|
|
11069
|
+
var REDIS_STARTUP_TIMEOUT_MS = 6e4;
|
|
11070
|
+
var WATCHDOG_INTERVAL_MS = 1e4;
|
|
11071
|
+
var ERROR_LOG_THROTTLE_MS = 3e4;
|
|
11072
|
+
var log = (line) => console.log(`[EXULU-REDIS] ${line}`);
|
|
11073
|
+
var warn = (line) => console.warn(`[EXULU-REDIS] ${line}`);
|
|
11074
|
+
var errorLog = (line) => console.error(`[EXULU-REDIS] ${line}`);
|
|
11075
|
+
var redisAddress = () => `${redisServer.host || "(unset)"}:${redisServer.port || "(unset)"}`;
|
|
11076
|
+
var describeError = (e) => {
|
|
11077
|
+
const any = e;
|
|
11078
|
+
const head = any?.message ? String(any.message).split("\n")[0] : void 0;
|
|
11079
|
+
if (any?.code) return head && head !== any.code ? `${any.code} (${head})` : `${any.code}`;
|
|
11080
|
+
return head ?? String(e);
|
|
11081
|
+
};
|
|
11082
|
+
function logRedisErrors(source, label) {
|
|
11083
|
+
let count = 0;
|
|
11084
|
+
let lastLoggedAt = 0;
|
|
11085
|
+
source.on("error", (err) => {
|
|
11086
|
+
count += 1;
|
|
11087
|
+
const now = Date.now();
|
|
11088
|
+
if (count === 1 || now - lastLoggedAt >= ERROR_LOG_THROTTLE_MS) {
|
|
11089
|
+
errorLog(`${label} connection error (${redisAddress()}): ${describeError(err)}${count > 1 ? ` (x${count})` : ""}`);
|
|
11090
|
+
lastLoggedAt = now;
|
|
11091
|
+
}
|
|
11092
|
+
});
|
|
11093
|
+
}
|
|
11094
|
+
async function guardRedisStartup(label, run, source) {
|
|
11095
|
+
const addr = redisAddress();
|
|
11096
|
+
log(`Connecting to Redis (${addr}) for ${label}\u2026`);
|
|
11097
|
+
const startedAt = Date.now();
|
|
11098
|
+
let lastError;
|
|
11099
|
+
const onError = (err) => {
|
|
11100
|
+
lastError = err;
|
|
11101
|
+
};
|
|
11102
|
+
source?.on("error", onError);
|
|
11103
|
+
const watchdog = setInterval(() => {
|
|
11104
|
+
const secs = Math.round((Date.now() - startedAt) / 1e3);
|
|
11105
|
+
warn(
|
|
11106
|
+
`\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)`
|
|
11107
|
+
);
|
|
11108
|
+
}, WATCHDOG_INTERVAL_MS);
|
|
11109
|
+
watchdog.unref?.();
|
|
11110
|
+
let timer2;
|
|
11111
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
11112
|
+
timer2 = setTimeout(() => {
|
|
11113
|
+
reject(
|
|
11114
|
+
new Error(
|
|
11115
|
+
`[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}.`
|
|
11116
|
+
)
|
|
11117
|
+
);
|
|
11118
|
+
}, REDIS_STARTUP_TIMEOUT_MS);
|
|
11119
|
+
});
|
|
11120
|
+
const runPromise = Promise.resolve().then(run);
|
|
11121
|
+
runPromise.catch(() => {
|
|
11122
|
+
});
|
|
11123
|
+
try {
|
|
11124
|
+
const result = await Promise.race([runPromise, timeout]);
|
|
11125
|
+
log(`Redis ready; ${label} initialized (${addr}, ${((Date.now() - startedAt) / 1e3).toFixed(1)}s).`);
|
|
11126
|
+
return result;
|
|
11127
|
+
} finally {
|
|
11128
|
+
clearInterval(watchdog);
|
|
11129
|
+
if (timer2) clearTimeout(timer2);
|
|
11130
|
+
source?.off?.("error", onError);
|
|
11131
|
+
}
|
|
11132
|
+
}
|
|
11133
|
+
|
|
10948
11134
|
// src/redis/client.ts
|
|
10949
11135
|
var client = {};
|
|
10950
11136
|
async function redisClient() {
|
|
@@ -10962,9 +11148,11 @@ async function redisClient() {
|
|
|
10962
11148
|
client["exulu"] = (0, import_redis.createClient)({
|
|
10963
11149
|
url
|
|
10964
11150
|
});
|
|
10965
|
-
|
|
11151
|
+
logRedisErrors(client["exulu"], "client");
|
|
11152
|
+
await guardRedisStartup("client", () => client["exulu"].connect().then(() => void 0), client["exulu"]);
|
|
10966
11153
|
} catch (error) {
|
|
10967
11154
|
console.error(`[EXULU] error connecting to redis:`, error);
|
|
11155
|
+
delete client["exulu"];
|
|
10968
11156
|
return { client: null };
|
|
10969
11157
|
}
|
|
10970
11158
|
}
|
|
@@ -10989,7 +11177,8 @@ init_client();
|
|
|
10989
11177
|
init_auth();
|
|
10990
11178
|
var requestValidators = {
|
|
10991
11179
|
authenticate: async (req) => {
|
|
10992
|
-
const
|
|
11180
|
+
const rawApiKey = req.headers["exulu-api-key"] || req.headers["x-api-key"];
|
|
11181
|
+
const apikey = rawApiKey?.replace(/^Bearer\s+/i, "") || null;
|
|
10993
11182
|
const { db: db2 } = await postgresClient();
|
|
10994
11183
|
let authtoken = null;
|
|
10995
11184
|
if (typeof apikey !== "string") {
|
|
@@ -11082,7 +11271,7 @@ var requestValidators = {
|
|
|
11082
11271
|
init_statistics();
|
|
11083
11272
|
init_client();
|
|
11084
11273
|
var import_express5 = __toESM(require("express"), 1);
|
|
11085
|
-
var
|
|
11274
|
+
var import_server5 = require("@apollo/server");
|
|
11086
11275
|
var import_cors = __toESM(require("cors"), 1);
|
|
11087
11276
|
var import_reflect_metadata = require("reflect-metadata");
|
|
11088
11277
|
|
|
@@ -11275,6 +11464,14 @@ var ExuluQueues = class {
|
|
|
11275
11464
|
},
|
|
11276
11465
|
telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
|
|
11277
11466
|
});
|
|
11467
|
+
logRedisErrors(newQueue, `queue "${name}"`);
|
|
11468
|
+
try {
|
|
11469
|
+
await guardRedisStartup(`queue "${name}"`, () => newQueue.waitUntilReady(), newQueue);
|
|
11470
|
+
} catch (err) {
|
|
11471
|
+
void newQueue.close().catch(() => {
|
|
11472
|
+
});
|
|
11473
|
+
throw err;
|
|
11474
|
+
}
|
|
11278
11475
|
await newQueue.setGlobalConcurrency(queueConcurrency);
|
|
11279
11476
|
this.queues.push({
|
|
11280
11477
|
queue: newQueue,
|
|
@@ -13436,6 +13633,8 @@ var createWorkers = async (providers, queues2, config, contexts, evals, tools, t
|
|
|
13436
13633
|
},
|
|
13437
13634
|
maxRetriesPerRequest: null
|
|
13438
13635
|
});
|
|
13636
|
+
logRedisErrors(redisConnection, "worker");
|
|
13637
|
+
await guardRedisStartup("workers", () => redisConnection.ping().then(() => void 0), redisConnection);
|
|
13439
13638
|
}
|
|
13440
13639
|
const workers = queues2.map((queue) => {
|
|
13441
13640
|
console.log(`[EXULU] creating worker for queue ${queue.queue.name}.`);
|
|
@@ -14615,7 +14814,7 @@ var renderTranscript = (segments, speakers) => {
|
|
|
14615
14814
|
|
|
14616
14815
|
// src/exulu/transcription/service.ts
|
|
14617
14816
|
var TABLE2 = "transcription_jobs";
|
|
14618
|
-
var
|
|
14817
|
+
var log3 = (msg) => console.log(`[EXULU-TRANSCRIPTION] ${msg}`);
|
|
14619
14818
|
var parseJsonField = (v) => {
|
|
14620
14819
|
if (v == null) return null;
|
|
14621
14820
|
if (typeof v === "string") {
|
|
@@ -14686,7 +14885,7 @@ var transcriptionService = {
|
|
|
14686
14885
|
error: err.message,
|
|
14687
14886
|
updatedAt: /* @__PURE__ */ new Date()
|
|
14688
14887
|
}).returning("*");
|
|
14689
|
-
|
|
14888
|
+
log3(`Failed to dispatch job ${row.id}: ${err.message}`);
|
|
14690
14889
|
return this._rowFromDb(failed);
|
|
14691
14890
|
}
|
|
14692
14891
|
},
|
|
@@ -14714,9 +14913,9 @@ var transcriptionService = {
|
|
|
14714
14913
|
updatedAt: /* @__PURE__ */ new Date()
|
|
14715
14914
|
});
|
|
14716
14915
|
} else if (err instanceof TranscriptionServerUnavailable) {
|
|
14717
|
-
|
|
14916
|
+
log3(`Whisper server unreachable while polling ${row.id}; will retry`);
|
|
14718
14917
|
} else {
|
|
14719
|
-
|
|
14918
|
+
log3(`Error polling job ${row.id}: ${err.message}`);
|
|
14720
14919
|
}
|
|
14721
14920
|
}
|
|
14722
14921
|
}
|
|
@@ -14766,7 +14965,7 @@ var transcriptionService = {
|
|
|
14766
14965
|
} catch (err) {
|
|
14767
14966
|
const code = err.code;
|
|
14768
14967
|
if (code !== "JOB_NOT_FOUND") {
|
|
14769
|
-
|
|
14968
|
+
log3(`Best-effort cancel of whisper job failed: ${err.message}`);
|
|
14770
14969
|
}
|
|
14771
14970
|
}
|
|
14772
14971
|
}
|
|
@@ -14852,7 +15051,7 @@ var transcriptionService = {
|
|
|
14852
15051
|
[]
|
|
14853
15052
|
);
|
|
14854
15053
|
} catch (err) {
|
|
14855
|
-
|
|
15054
|
+
log3(`RBAC update failed for item ${itemId}: ${err.message}`);
|
|
14856
15055
|
}
|
|
14857
15056
|
}
|
|
14858
15057
|
const projectId = input.project_id ?? row.project_id ?? null;
|
|
@@ -15149,7 +15348,7 @@ var durationFromSegments = (segments) => {
|
|
|
15149
15348
|
// src/exulu/recall/service.ts
|
|
15150
15349
|
var TABLE3 = "transcription_jobs";
|
|
15151
15350
|
var DEFAULT_BOT_NAME = "Exulu Notetaker";
|
|
15152
|
-
var
|
|
15351
|
+
var log4 = (msg) => console.log(`[EXULU-RECALL] ${msg}`);
|
|
15153
15352
|
var parseJson = (v) => {
|
|
15154
15353
|
if (v == null) return null;
|
|
15155
15354
|
if (typeof v === "string") {
|
|
@@ -15246,7 +15445,7 @@ var recallService = {
|
|
|
15246
15445
|
error: err.message,
|
|
15247
15446
|
updatedAt: /* @__PURE__ */ new Date()
|
|
15248
15447
|
}).returning("*");
|
|
15249
|
-
|
|
15448
|
+
log4(`createBot failed for job ${inserted.id}: ${err.message}`);
|
|
15250
15449
|
return this._row(failed);
|
|
15251
15450
|
}
|
|
15252
15451
|
},
|
|
@@ -15260,7 +15459,7 @@ var recallService = {
|
|
|
15260
15459
|
const name = event?.event ?? "";
|
|
15261
15460
|
const job = await this._findJob(ids.botId, ids.recordingId, ids.transcriptId);
|
|
15262
15461
|
if (!job) {
|
|
15263
|
-
|
|
15462
|
+
log4(`No job for event ${name} (bot=${ids.botId}); ignoring.`);
|
|
15264
15463
|
return;
|
|
15265
15464
|
}
|
|
15266
15465
|
if (name.startsWith("bot.")) {
|
|
@@ -15285,12 +15484,12 @@ var recallService = {
|
|
|
15285
15484
|
await this._fail(job.id, ids.subCode || ids.code || "transcript failed");
|
|
15286
15485
|
return;
|
|
15287
15486
|
default:
|
|
15288
|
-
|
|
15487
|
+
log4(`Unhandled event ${name} for job ${job.id}`);
|
|
15289
15488
|
}
|
|
15290
15489
|
},
|
|
15291
15490
|
async _onRecordingDone(jobId, recordingId) {
|
|
15292
15491
|
if (!recordingId) {
|
|
15293
|
-
|
|
15492
|
+
log4(`recording.done for job ${jobId} had no recording id`);
|
|
15294
15493
|
return;
|
|
15295
15494
|
}
|
|
15296
15495
|
const { db: db2 } = await postgresClient();
|
|
@@ -15300,7 +15499,7 @@ var recallService = {
|
|
|
15300
15499
|
updatedAt: /* @__PURE__ */ new Date()
|
|
15301
15500
|
});
|
|
15302
15501
|
if (!claimed) {
|
|
15303
|
-
|
|
15502
|
+
log4(`recording.done for job ${jobId} already handled; skipping.`);
|
|
15304
15503
|
return;
|
|
15305
15504
|
}
|
|
15306
15505
|
try {
|
|
@@ -15320,7 +15519,7 @@ var recallService = {
|
|
|
15320
15519
|
if (!dbRow) return;
|
|
15321
15520
|
const job = this._row(dbRow);
|
|
15322
15521
|
if ((job.status === "awaiting_review" || job.status === "saved") && job.raw_segments && job.raw_segments.length > 0) {
|
|
15323
|
-
|
|
15522
|
+
log4(`transcript.done for job ${jobId} already processed; skipping.`);
|
|
15324
15523
|
return;
|
|
15325
15524
|
}
|
|
15326
15525
|
try {
|
|
@@ -15339,7 +15538,7 @@ var recallService = {
|
|
|
15339
15538
|
const recDuration = recordingDurationSeconds(rec);
|
|
15340
15539
|
if (recDuration != null) duration = recDuration;
|
|
15341
15540
|
} catch (err) {
|
|
15342
|
-
|
|
15541
|
+
log4(`could not fetch recording duration for job ${jobId}: ${err.message}`);
|
|
15343
15542
|
}
|
|
15344
15543
|
}
|
|
15345
15544
|
await this._update(jobId, {
|
|
@@ -15379,11 +15578,11 @@ var recallService = {
|
|
|
15379
15578
|
const prompts = job.post_processing_prompts ?? [];
|
|
15380
15579
|
if (prompts.length === 0) return [];
|
|
15381
15580
|
if (job.post_processing_outputs && job.post_processing_outputs.length > 0) {
|
|
15382
|
-
|
|
15581
|
+
log4(`post-processing for job ${jobId} already ran; skipping.`);
|
|
15383
15582
|
return job.post_processing_outputs;
|
|
15384
15583
|
}
|
|
15385
15584
|
if (!job.raw_segments || job.raw_segments.length === 0) {
|
|
15386
|
-
|
|
15585
|
+
log4(`post-processing for job ${jobId} skipped: no transcript.`);
|
|
15387
15586
|
return [];
|
|
15388
15587
|
}
|
|
15389
15588
|
const outputs = [];
|
|
@@ -15456,7 +15655,7 @@ ${transcriptText}`,
|
|
|
15456
15655
|
ran_at: ranAt
|
|
15457
15656
|
};
|
|
15458
15657
|
} catch (err) {
|
|
15459
|
-
|
|
15658
|
+
log4(`post-processing prompt ${promptId} failed for job ${job.id}: ${err.message}`);
|
|
15460
15659
|
return {
|
|
15461
15660
|
prompt_id: promptId,
|
|
15462
15661
|
agent_id: agentId,
|
|
@@ -19655,6 +19854,53 @@ var verifyRecallRequest = (headers, rawBody, secret = recallVerificationSecret()
|
|
|
19655
19854
|
return passed ? { ok: true } : { ok: false, reason: "signature mismatch" };
|
|
19656
19855
|
};
|
|
19657
19856
|
|
|
19857
|
+
// src/exulu/shared-artifacts.ts
|
|
19858
|
+
init_cjs_shims();
|
|
19859
|
+
var import_bcryptjs4 = __toESM(require("bcryptjs"), 1);
|
|
19860
|
+
var normalizeS3Key = (key, bucket) => {
|
|
19861
|
+
const segments = key.split("/").filter((s, i) => !(i === 0 && s === "")).map((s) => decodeURIComponent(s));
|
|
19862
|
+
if (segments[0] === bucket) segments.shift();
|
|
19863
|
+
return segments.join("/");
|
|
19864
|
+
};
|
|
19865
|
+
var isHtmlKey = (key) => /\.html?$/i.test(key);
|
|
19866
|
+
var deriveFilename = (key) => {
|
|
19867
|
+
const base = key.split("/").pop() ?? key;
|
|
19868
|
+
return base.split("_EXULU_").pop() ?? base;
|
|
19869
|
+
};
|
|
19870
|
+
var slugifyShareName = (input) => deriveFilename(input).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
19871
|
+
var isExpired = (expiresAt, now) => {
|
|
19872
|
+
if (!expiresAt) return false;
|
|
19873
|
+
return new Date(expiresAt).getTime() <= now.getTime();
|
|
19874
|
+
};
|
|
19875
|
+
var validateCreateInput = (input, now) => {
|
|
19876
|
+
if (!input.s3key) return { ok: false, message: "s3key is required." };
|
|
19877
|
+
if (!input.name) return { ok: false, message: "name is required." };
|
|
19878
|
+
const mode = input.auth_mode;
|
|
19879
|
+
if (mode !== "public" && mode !== "password" && mode !== "regular") {
|
|
19880
|
+
return { ok: false, message: "auth_mode must be public, password, or regular." };
|
|
19881
|
+
}
|
|
19882
|
+
if (mode === "password" && !input.password) {
|
|
19883
|
+
return { ok: false, message: "A password is required for password mode." };
|
|
19884
|
+
}
|
|
19885
|
+
if (input.expires_at && Number.isNaN(new Date(input.expires_at).getTime())) {
|
|
19886
|
+
return { ok: false, message: "expires_at is not a valid date." };
|
|
19887
|
+
}
|
|
19888
|
+
if (input.expires_at && isExpired(input.expires_at, now)) {
|
|
19889
|
+
return { ok: false, message: "expires_at must be in the future." };
|
|
19890
|
+
}
|
|
19891
|
+
return { ok: true };
|
|
19892
|
+
};
|
|
19893
|
+
var hashSharePassword = (password) => import_bcryptjs4.default.hash(password, 10);
|
|
19894
|
+
var verifySharePassword = (password, hash) => import_bcryptjs4.default.compare(password, hash);
|
|
19895
|
+
var contentHeadersFor = (key, contentType, filename) => {
|
|
19896
|
+
if (isHtmlKey(key)) return { contentType: "text/html; charset=utf-8" };
|
|
19897
|
+
return {
|
|
19898
|
+
contentType: contentType || "application/octet-stream",
|
|
19899
|
+
disposition: `attachment; filename="${filename.replace(/"/g, "")}"`
|
|
19900
|
+
};
|
|
19901
|
+
};
|
|
19902
|
+
var getSharedArtifactByName = (db2, name) => db2("shared_artifacts").where({ name }).first();
|
|
19903
|
+
|
|
19658
19904
|
// src/exulu/routes.ts
|
|
19659
19905
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
19660
19906
|
var getExuluVersionNumber = async () => {
|
|
@@ -19771,7 +20017,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
|
|
|
19771
20017
|
config,
|
|
19772
20018
|
evals
|
|
19773
20019
|
);
|
|
19774
|
-
const server = new
|
|
20020
|
+
const server = new import_server5.ApolloServer({
|
|
19775
20021
|
cache: new import_utils5.InMemoryLRUCache(),
|
|
19776
20022
|
schema,
|
|
19777
20023
|
introspection: true
|
|
@@ -22689,6 +22935,126 @@ ${style.markdown}` : params.prompt;
|
|
|
22689
22935
|
}
|
|
22690
22936
|
res.status(204).send();
|
|
22691
22937
|
});
|
|
22938
|
+
app.post("/shared-artifacts", async (req, res) => {
|
|
22939
|
+
const { db: db2 } = await postgresClient();
|
|
22940
|
+
const auth = await requestValidators.authenticate(req);
|
|
22941
|
+
if (!auth.user?.id) {
|
|
22942
|
+
res.status(401).json({ detail: "Authentication required." });
|
|
22943
|
+
return;
|
|
22944
|
+
}
|
|
22945
|
+
const now = /* @__PURE__ */ new Date();
|
|
22946
|
+
const valid = validateCreateInput(req.body, now);
|
|
22947
|
+
if (!valid.ok) {
|
|
22948
|
+
res.status(400).json({ detail: valid.message });
|
|
22949
|
+
return;
|
|
22950
|
+
}
|
|
22951
|
+
const bucket = config.fileUploads?.s3Bucket ?? "";
|
|
22952
|
+
const s3key = normalizeS3Key(req.body.s3key, bucket);
|
|
22953
|
+
const name = slugifyShareName(req.body.name);
|
|
22954
|
+
if (!name) {
|
|
22955
|
+
res.status(400).json({ detail: "name must contain url-safe characters." });
|
|
22956
|
+
return;
|
|
22957
|
+
}
|
|
22958
|
+
const existing = await getSharedArtifactByName(db2, name);
|
|
22959
|
+
if (existing) {
|
|
22960
|
+
res.status(409).json({ detail: "That share name is already taken." });
|
|
22961
|
+
return;
|
|
22962
|
+
}
|
|
22963
|
+
const auth_mode = req.body.auth_mode;
|
|
22964
|
+
const password_hash = auth_mode === "password" ? await hashSharePassword(req.body.password) : null;
|
|
22965
|
+
const rights_mode = auth_mode === "regular" ? req.body.rights_mode ?? "private" : "public";
|
|
22966
|
+
const [row] = await db2("shared_artifacts").insert({
|
|
22967
|
+
name,
|
|
22968
|
+
s3key,
|
|
22969
|
+
auth_mode,
|
|
22970
|
+
password_hash,
|
|
22971
|
+
expires_at: req.body.expires_at ?? null,
|
|
22972
|
+
content_type: req.body.content_type ?? null,
|
|
22973
|
+
rights_mode,
|
|
22974
|
+
created_by: auth.user.id
|
|
22975
|
+
}).returning("*");
|
|
22976
|
+
if (auth_mode === "regular" && req.body.rbac) {
|
|
22977
|
+
await handleRBACUpdate(db2, "shared_artifact", row.id, req.body.rbac, []);
|
|
22978
|
+
}
|
|
22979
|
+
res.status(201).json({ name: row.name });
|
|
22980
|
+
});
|
|
22981
|
+
app.get("/shared-artifacts/:name/meta", async (req, res) => {
|
|
22982
|
+
const { db: db2 } = await postgresClient();
|
|
22983
|
+
const row = await getSharedArtifactByName(db2, req.params.name ?? "");
|
|
22984
|
+
if (!row) {
|
|
22985
|
+
res.status(404).json({ detail: "Not found." });
|
|
22986
|
+
return;
|
|
22987
|
+
}
|
|
22988
|
+
if (isExpired(row.expires_at, /* @__PURE__ */ new Date())) {
|
|
22989
|
+
res.status(410).json({ detail: "This link has expired." });
|
|
22990
|
+
return;
|
|
22991
|
+
}
|
|
22992
|
+
res.json({
|
|
22993
|
+
auth_mode: row.auth_mode,
|
|
22994
|
+
expires_at: row.expires_at,
|
|
22995
|
+
filename: deriveFilename(row.s3key),
|
|
22996
|
+
content_type: row.content_type,
|
|
22997
|
+
is_html: /\.html?$/i.test(row.s3key)
|
|
22998
|
+
});
|
|
22999
|
+
});
|
|
23000
|
+
app.get("/shared-artifacts/:name/content", async (req, res) => {
|
|
23001
|
+
const { db: db2 } = await postgresClient();
|
|
23002
|
+
const row = await getSharedArtifactByName(db2, req.params.name ?? "");
|
|
23003
|
+
if (!row) {
|
|
23004
|
+
res.status(404).json({ detail: "Not found." });
|
|
23005
|
+
return;
|
|
23006
|
+
}
|
|
23007
|
+
if (isExpired(row.expires_at, /* @__PURE__ */ new Date())) {
|
|
23008
|
+
res.status(410).json({ detail: "This link has expired." });
|
|
23009
|
+
return;
|
|
23010
|
+
}
|
|
23011
|
+
if (row.auth_mode === "password") {
|
|
23012
|
+
const pw = req.headers["x-share-password"] || "";
|
|
23013
|
+
if (!row.password_hash || !await verifySharePassword(pw, row.password_hash)) {
|
|
23014
|
+
res.status(401).json({ detail: "Incorrect password." });
|
|
23015
|
+
return;
|
|
23016
|
+
}
|
|
23017
|
+
} else if (row.auth_mode === "regular") {
|
|
23018
|
+
const viewer = await requestValidators.authenticate(req);
|
|
23019
|
+
if (!viewer.user?.id) {
|
|
23020
|
+
res.status(401).json({ detail: "Authentication required." });
|
|
23021
|
+
return;
|
|
23022
|
+
}
|
|
23023
|
+
const rbac = await RBACResolver(
|
|
23024
|
+
db2,
|
|
23025
|
+
"shared_artifact",
|
|
23026
|
+
row.id,
|
|
23027
|
+
row.rights_mode || "private"
|
|
23028
|
+
);
|
|
23029
|
+
const ok = await checkRecordAccess({ ...row, RBAC: rbac }, "read", viewer.user);
|
|
23030
|
+
if (!ok) {
|
|
23031
|
+
res.status(403).json({ detail: "You don't have access to this artifact." });
|
|
23032
|
+
return;
|
|
23033
|
+
}
|
|
23034
|
+
}
|
|
23035
|
+
let bytes;
|
|
23036
|
+
try {
|
|
23037
|
+
bytes = await getS3ObjectBytes(row.s3key, config);
|
|
23038
|
+
} catch (e) {
|
|
23039
|
+
if (e?.name === "NoSuchKey" || e?.name === "NotFound" || e?.$metadata?.httpStatusCode === 404) {
|
|
23040
|
+
res.status(404).json({ detail: "Artifact file not found." });
|
|
23041
|
+
return;
|
|
23042
|
+
}
|
|
23043
|
+
console.error("[EXULU] shared-artifact content read failed", e);
|
|
23044
|
+
res.status(500).json({ detail: "Failed to read artifact." });
|
|
23045
|
+
return;
|
|
23046
|
+
}
|
|
23047
|
+
const headers = contentHeadersFor(
|
|
23048
|
+
row.s3key,
|
|
23049
|
+
row.content_type,
|
|
23050
|
+
deriveFilename(row.s3key)
|
|
23051
|
+
);
|
|
23052
|
+
res.setHeader("Content-Type", headers.contentType);
|
|
23053
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
23054
|
+
res.setHeader("Cache-Control", "private, no-store");
|
|
23055
|
+
if (headers.disposition) res.setHeader("Content-Disposition", headers.disposition);
|
|
23056
|
+
res.send(bytes);
|
|
23057
|
+
});
|
|
22692
23058
|
app.use(import_express5.default.static("public"));
|
|
22693
23059
|
await registerOpenAIGatewayRoutes(app, providers, tools, contexts, config);
|
|
22694
23060
|
return app;
|
|
@@ -26668,6 +27034,83 @@ var RecursiveChunker = class _RecursiveChunker extends BaseChunker {
|
|
|
26668
27034
|
// src/index.ts
|
|
26669
27035
|
init_chunker();
|
|
26670
27036
|
init_context();
|
|
27037
|
+
|
|
27038
|
+
// src/exulu/read-api.ts
|
|
27039
|
+
init_cjs_shims();
|
|
27040
|
+
init_table_names();
|
|
27041
|
+
init_types();
|
|
27042
|
+
init_client();
|
|
27043
|
+
init_access_control();
|
|
27044
|
+
init_convert_context_to_table_definition();
|
|
27045
|
+
init_resolve_embedder();
|
|
27046
|
+
var authorizedRead = async (context, user, role, opts = {}) => {
|
|
27047
|
+
if (!opts.itemIds?.length && !opts.externalIds?.length) {
|
|
27048
|
+
throw new Error("authorizedRead requires itemIds or externalIds to constrain the read.");
|
|
27049
|
+
}
|
|
27050
|
+
const { db: db2 } = await postgresClient();
|
|
27051
|
+
const table = convertContextToTableDefinition(context);
|
|
27052
|
+
const itemsTable = getTableName(context.id);
|
|
27053
|
+
const chunksTable = getChunksTableName(context.id);
|
|
27054
|
+
const acUser = role && (!user.role || user.role.id !== role) ? { ...user, role: { ...user.role ?? {}, id: role } } : user;
|
|
27055
|
+
let q = db2(chunksTable + " as chunks").select([
|
|
27056
|
+
"chunks.id as chunk_id",
|
|
27057
|
+
"chunks.source as chunk_source",
|
|
27058
|
+
"chunks.content as chunk_content",
|
|
27059
|
+
"chunks.chunk_index",
|
|
27060
|
+
"chunks.metadata as chunk_metadata",
|
|
27061
|
+
db2.raw('chunks."createdAt" as chunk_created_at'),
|
|
27062
|
+
db2.raw('chunks."updatedAt" as chunk_updated_at'),
|
|
27063
|
+
"items.id as item_id",
|
|
27064
|
+
"items.name as item_name",
|
|
27065
|
+
"items.external_id as item_external_id",
|
|
27066
|
+
db2.raw('items."createdAt" as item_created_at'),
|
|
27067
|
+
db2.raw('items."updatedAt" as item_updated_at')
|
|
27068
|
+
]);
|
|
27069
|
+
q = q.leftJoin(itemsTable + " as items", "chunks.source", "items.id");
|
|
27070
|
+
if (opts.itemIds?.length) q = q.whereIn("items.id", opts.itemIds);
|
|
27071
|
+
if (opts.externalIds?.length) q = q.whereIn("items.external_id", opts.externalIds);
|
|
27072
|
+
if (opts.chunkIndexRange) {
|
|
27073
|
+
const { from, to } = opts.chunkIndexRange;
|
|
27074
|
+
if (typeof from === "number") q = q.where("chunks.chunk_index", ">=", from);
|
|
27075
|
+
if (typeof to === "number") q = q.where("chunks.chunk_index", "<=", to);
|
|
27076
|
+
}
|
|
27077
|
+
q = applyAccessControl(table, q, acUser, "items");
|
|
27078
|
+
q = q.orderBy("chunks.source").orderBy("chunks.chunk_index");
|
|
27079
|
+
return await q;
|
|
27080
|
+
};
|
|
27081
|
+
var entitiesAvailable = async (context) => {
|
|
27082
|
+
if (!context.entities) return false;
|
|
27083
|
+
const { db: db2 } = await postgresClient();
|
|
27084
|
+
const table = getChunkEntitiesTableName(context.id);
|
|
27085
|
+
const res = await db2.raw(
|
|
27086
|
+
"SELECT to_regclass(?) IS NOT NULL AS exists",
|
|
27087
|
+
[table]
|
|
27088
|
+
);
|
|
27089
|
+
return res.rows?.[0]?.exists === true;
|
|
27090
|
+
};
|
|
27091
|
+
var embedQuery = async (context, text, opts = {}) => {
|
|
27092
|
+
const resolved = await resolveEmbedder({
|
|
27093
|
+
model: context.embedder.model,
|
|
27094
|
+
contextId: context.id,
|
|
27095
|
+
contextName: context.name,
|
|
27096
|
+
user: opts.user,
|
|
27097
|
+
roleId: opts.role
|
|
27098
|
+
});
|
|
27099
|
+
const [vector] = await resolved.embed([text], { inputType: opts.inputType ?? "query" });
|
|
27100
|
+
return vector ?? [];
|
|
27101
|
+
};
|
|
27102
|
+
var ExuluReadApi = {
|
|
27103
|
+
getTableName,
|
|
27104
|
+
getChunksTableName,
|
|
27105
|
+
getEntitiesTableName,
|
|
27106
|
+
getChunkEntitiesTableName,
|
|
27107
|
+
entitiesAvailable,
|
|
27108
|
+
authorizedRead,
|
|
27109
|
+
embedQuery
|
|
27110
|
+
};
|
|
27111
|
+
|
|
27112
|
+
// src/index.ts
|
|
27113
|
+
init_client();
|
|
26671
27114
|
init_tool();
|
|
26672
27115
|
init_sentence2();
|
|
26673
27116
|
|
|
@@ -26704,7 +27147,8 @@ var {
|
|
|
26704
27147
|
promptFavoritesSchema: promptFavoritesSchema3,
|
|
26705
27148
|
transcriptionJobsSchema: transcriptionJobsSchema3,
|
|
26706
27149
|
imageGenerationsSchema: imageGenerationsSchema2,
|
|
26707
|
-
oauthTokensSchema: oauthTokensSchema2
|
|
27150
|
+
oauthTokensSchema: oauthTokensSchema2,
|
|
27151
|
+
sharedArtifactsSchema: sharedArtifactsSchema2
|
|
26708
27152
|
} = coreSchemas.get();
|
|
26709
27153
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
26710
27154
|
for (const field of fields) {
|
|
@@ -26748,6 +27192,7 @@ var up = async function(knex) {
|
|
|
26748
27192
|
transcriptionJobsSchema3(),
|
|
26749
27193
|
imageGenerationsSchema2(),
|
|
26750
27194
|
oauthTokensSchema2(),
|
|
27195
|
+
sharedArtifactsSchema2(),
|
|
26751
27196
|
rbacSchema3(),
|
|
26752
27197
|
agentsSchema3(),
|
|
26753
27198
|
feedbackSchema3(),
|
|
@@ -27038,7 +27483,7 @@ var checkLiteLLMDatabaseSafety = (configPath) => {
|
|
|
27038
27483
|
|
|
27039
27484
|
// src/exulu/litellm/db-init.ts
|
|
27040
27485
|
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
|
|
27486
|
+
var warn2 = (lines) => {
|
|
27042
27487
|
console.warn(`
|
|
27043
27488
|
${WARNING_BANNER}`);
|
|
27044
27489
|
console.warn("\u26A0 [EXULU-LITELLM] CONFIGURATION WARNING");
|
|
@@ -27046,13 +27491,13 @@ ${WARNING_BANNER}`);
|
|
|
27046
27491
|
console.warn(`${WARNING_BANNER}
|
|
27047
27492
|
`);
|
|
27048
27493
|
};
|
|
27049
|
-
var
|
|
27494
|
+
var log5 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
27050
27495
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
27051
27496
|
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path9.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
27052
27497
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
27053
27498
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
27054
27499
|
if (!safety.ok && safety.reason === "unparseable-url") {
|
|
27055
|
-
|
|
27500
|
+
warn2([
|
|
27056
27501
|
`LiteLLM's database_url is not a valid postgres URL:`,
|
|
27057
27502
|
` ${safety.rawUrl}`,
|
|
27058
27503
|
`Expected postgres://user:pass@host:port/database. Skipping setup.`
|
|
@@ -27061,7 +27506,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27061
27506
|
}
|
|
27062
27507
|
if (!safety.ok && safety.reason === "shared-with-exulu") {
|
|
27063
27508
|
const { exuluTarget } = safety;
|
|
27064
|
-
|
|
27509
|
+
warn2([
|
|
27065
27510
|
`LiteLLM's database_url points to the SAME database Exulu uses:`,
|
|
27066
27511
|
` ${exuluTarget.host}:${exuluTarget.port}/${exuluTarget.database}`,
|
|
27067
27512
|
``,
|
|
@@ -27080,7 +27525,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27080
27525
|
return;
|
|
27081
27526
|
}
|
|
27082
27527
|
const target = "litellmTarget" in safety ? safety.litellmTarget : void 0;
|
|
27083
|
-
|
|
27528
|
+
log5(
|
|
27084
27529
|
`LiteLLM database mode detected (${target?.host}:${target?.port}/${target?.database}).`
|
|
27085
27530
|
);
|
|
27086
27531
|
const ensureDatabaseExists2 = async () => {
|
|
@@ -27092,7 +27537,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27092
27537
|
} catch (err) {
|
|
27093
27538
|
const code = err?.code;
|
|
27094
27539
|
if (code !== "3D000") {
|
|
27095
|
-
|
|
27540
|
+
warn2([
|
|
27096
27541
|
`Could not connect to LiteLLM's target database:`,
|
|
27097
27542
|
` ${err instanceof Error ? err.message : String(err)}`,
|
|
27098
27543
|
``,
|
|
@@ -27104,13 +27549,13 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27104
27549
|
const url = new URL(litellmUrl);
|
|
27105
27550
|
const targetDbName = url.pathname.replace(/^\//, "");
|
|
27106
27551
|
if (!targetDbName) {
|
|
27107
|
-
|
|
27552
|
+
warn2([`LiteLLM database_url has no database name; cannot auto-create.`]);
|
|
27108
27553
|
return false;
|
|
27109
27554
|
}
|
|
27110
27555
|
url.pathname = "/postgres";
|
|
27111
|
-
|
|
27556
|
+
log5(`Target database "${targetDbName}" does not exist; creating it\u2026`);
|
|
27112
27557
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(targetDbName)) {
|
|
27113
|
-
|
|
27558
|
+
warn2([
|
|
27114
27559
|
`Refusing to auto-create database "${targetDbName}" \u2014 name`,
|
|
27115
27560
|
`contains characters that would require quoting. Create it`,
|
|
27116
27561
|
`manually: createdb -h ${url.hostname} -U ${url.username} ${targetDbName}`
|
|
@@ -27121,10 +27566,10 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27121
27566
|
try {
|
|
27122
27567
|
await admin.connect();
|
|
27123
27568
|
await admin.query(`CREATE DATABASE "${targetDbName}"`);
|
|
27124
|
-
|
|
27569
|
+
log5(`\u2713 Created database "${targetDbName}".`);
|
|
27125
27570
|
return true;
|
|
27126
27571
|
} catch (createErr) {
|
|
27127
|
-
|
|
27572
|
+
warn2([
|
|
27128
27573
|
`Failed to auto-create database "${targetDbName}":`,
|
|
27129
27574
|
` ${createErr instanceof Error ? createErr.message : String(createErr)}`,
|
|
27130
27575
|
``,
|
|
@@ -27143,7 +27588,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27143
27588
|
}
|
|
27144
27589
|
};
|
|
27145
27590
|
if (!await ensureDatabaseExists2()) return;
|
|
27146
|
-
|
|
27591
|
+
log5("Checking that the target database is safe to push into\u2026");
|
|
27147
27592
|
const client2 = new import_pg.Client({ connectionString: litellmUrl });
|
|
27148
27593
|
let foreignTables = [];
|
|
27149
27594
|
try {
|
|
@@ -27159,7 +27604,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27159
27604
|
);
|
|
27160
27605
|
foreignTables = res.rows.map((r) => r.table_name);
|
|
27161
27606
|
} catch (err) {
|
|
27162
|
-
|
|
27607
|
+
warn2([
|
|
27163
27608
|
`Could not query LiteLLM's target database to verify it is safe`,
|
|
27164
27609
|
`to push into:`,
|
|
27165
27610
|
` ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -27175,7 +27620,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27175
27620
|
}
|
|
27176
27621
|
}
|
|
27177
27622
|
if (foreignTables.length > 0) {
|
|
27178
|
-
|
|
27623
|
+
warn2([
|
|
27179
27624
|
`LiteLLM's target database contains ${foreignTables.length} table(s) that are NOT`,
|
|
27180
27625
|
`part of LiteLLM's schema:`,
|
|
27181
27626
|
...foreignTables.slice(0, 10).map((t) => ` - ${t}`),
|
|
@@ -27191,7 +27636,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27191
27636
|
const venvLibDir = (0, import_node_path9.resolve)(packageRoot, "ee/python/.venv/lib");
|
|
27192
27637
|
const pythonVersionDir = (0, import_node_fs9.existsSync)(venvLibDir) ? (0, import_node_fs9.readdirSync)(venvLibDir).find((entry) => /^python3\.\d+$/.test(entry)) : void 0;
|
|
27193
27638
|
if (!pythonVersionDir) {
|
|
27194
|
-
|
|
27639
|
+
warn2([
|
|
27195
27640
|
`Could not find a python3.* directory under ${venvLibDir}.`,
|
|
27196
27641
|
`Run \`npm run python:setup\` to create the venv.`,
|
|
27197
27642
|
`Skipping LiteLLM database setup.`
|
|
@@ -27205,7 +27650,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27205
27650
|
);
|
|
27206
27651
|
const schemaPath = (0, import_node_path9.resolve)(litellmProxyDir, "schema.prisma");
|
|
27207
27652
|
if (!(0, import_node_fs9.existsSync)(prismaCli)) {
|
|
27208
|
-
|
|
27653
|
+
warn2([
|
|
27209
27654
|
`Prisma CLI not found at ${prismaCli}.`,
|
|
27210
27655
|
`Run \`npm run python:setup\` to create the venv and install prisma.`,
|
|
27211
27656
|
`Skipping LiteLLM database setup.`
|
|
@@ -27213,13 +27658,13 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27213
27658
|
return;
|
|
27214
27659
|
}
|
|
27215
27660
|
if (!(0, import_node_fs9.existsSync)(schemaPath)) {
|
|
27216
|
-
|
|
27661
|
+
warn2([
|
|
27217
27662
|
`LiteLLM Prisma schema not found at ${schemaPath}.`,
|
|
27218
27663
|
`Re-run \`npm run python:setup\`. Skipping LiteLLM database setup.`
|
|
27219
27664
|
]);
|
|
27220
27665
|
return;
|
|
27221
27666
|
}
|
|
27222
|
-
|
|
27667
|
+
log5("Running `prisma db push` against LiteLLM's schema\u2026");
|
|
27223
27668
|
const result = (0, import_node_child_process5.spawnSync)(prismaCli, ["db", "push", "--skip-generate"], {
|
|
27224
27669
|
cwd: litellmProxyDir,
|
|
27225
27670
|
env: {
|
|
@@ -27233,14 +27678,14 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27233
27678
|
encoding: "utf8"
|
|
27234
27679
|
});
|
|
27235
27680
|
if (result.error) {
|
|
27236
|
-
|
|
27681
|
+
warn2([
|
|
27237
27682
|
`Failed to launch prisma: ${result.error.message}`,
|
|
27238
27683
|
`Skipping LiteLLM database setup.`
|
|
27239
27684
|
]);
|
|
27240
27685
|
return;
|
|
27241
27686
|
}
|
|
27242
27687
|
if (result.status !== 0) {
|
|
27243
|
-
|
|
27688
|
+
warn2([
|
|
27244
27689
|
`prisma db push exited with status ${result.status}.`,
|
|
27245
27690
|
`stdout:`,
|
|
27246
27691
|
...(result.stdout || "(empty)").split("\n").map((l) => ` ${l}`),
|
|
@@ -27249,7 +27694,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
27249
27694
|
]);
|
|
27250
27695
|
return;
|
|
27251
27696
|
}
|
|
27252
|
-
|
|
27697
|
+
log5("\u2713 LiteLLM database ready.");
|
|
27253
27698
|
};
|
|
27254
27699
|
|
|
27255
27700
|
// src/postgres/init-litellm-db.ts
|
|
@@ -28532,14 +28977,37 @@ ${setupResult.output || ""}`);
|
|
|
28532
28977
|
model: config.processor.model ?? "mistral-ocr",
|
|
28533
28978
|
...config.attribution
|
|
28534
28979
|
});
|
|
28535
|
-
|
|
28536
|
-
const
|
|
28537
|
-
const
|
|
28538
|
-
|
|
28539
|
-
|
|
28540
|
-
|
|
28541
|
-
|
|
28542
|
-
|
|
28980
|
+
const maxPagesPerChunk = config.processor.maxPagesPerChunk ?? 25;
|
|
28981
|
+
const chunksDir = path2.join(path2.dirname(paths.json), "ocr_chunks");
|
|
28982
|
+
const splitResult = await executePythonScript({
|
|
28983
|
+
scriptPath: "ee/python/documents/processing/split_pdf.py",
|
|
28984
|
+
args: [paths.source, chunksDir, "--chunk-size", String(maxPagesPerChunk)],
|
|
28985
|
+
timeout: 5 * 60 * 1e3
|
|
28986
|
+
});
|
|
28987
|
+
const pdfChunks = JSON.parse(splitResult.stdout);
|
|
28988
|
+
console.log(`[EXULU] PDF split into ${pdfChunks.length} chunk(s) for OCR (max ${maxPagesPerChunk} pages each)`);
|
|
28989
|
+
const chunkLimit = (0, import_p_limit.default)(3);
|
|
28990
|
+
const chunkResults = await Promise.all(
|
|
28991
|
+
pdfChunks.map(
|
|
28992
|
+
(chunk, i) => chunkLimit(async () => {
|
|
28993
|
+
await new Promise((resolve8) => setImmediate(resolve8));
|
|
28994
|
+
await new Promise((resolve8) => setTimeout(resolve8, Math.floor(Math.random() * 1e3) + 200));
|
|
28995
|
+
console.log(`[EXULU] OCR chunk ${i + 1}/${pdfChunks.length}: pages ${chunk.start_page}\u2013${chunk.end_page - 1}`);
|
|
28996
|
+
const chunkBuffer = await fs5.promises.readFile(chunk.path);
|
|
28997
|
+
const chunkBase64 = chunkBuffer.toString("base64");
|
|
28998
|
+
const chunkResponse = await withRetry(async () => {
|
|
28999
|
+
return await resolved.ocr({
|
|
29000
|
+
type: "document_url",
|
|
29001
|
+
document_url: "data:application/pdf;base64," + chunkBase64
|
|
29002
|
+
}, { includeImageBase64: false });
|
|
29003
|
+
}, 10);
|
|
29004
|
+
return { pages: chunkResponse.pages, offset: chunk.start_page };
|
|
29005
|
+
})
|
|
29006
|
+
)
|
|
29007
|
+
);
|
|
29008
|
+
const mergedPages = chunkResults.sort((a, b) => a.offset - b.offset).flatMap(
|
|
29009
|
+
({ pages, offset }) => pages.map((p) => ({ ...p, index: p.index + offset }))
|
|
29010
|
+
);
|
|
28543
29011
|
const parser = new import_liteparse.LiteParse();
|
|
28544
29012
|
const screenshots = await parser.screenshot(paths.source, void 0);
|
|
28545
29013
|
await fs5.promises.mkdir(paths.images, { recursive: true });
|
|
@@ -28553,7 +29021,7 @@ ${setupResult.output || ""}`);
|
|
|
28553
29021
|
);
|
|
28554
29022
|
screenshot.imagePath = path2.join(paths.images, `${screenshot.pageNum}.png`);
|
|
28555
29023
|
}
|
|
28556
|
-
json =
|
|
29024
|
+
json = mergedPages.map((page) => ({
|
|
28557
29025
|
page: page.index + 1,
|
|
28558
29026
|
content: page.markdown,
|
|
28559
29027
|
image: screenshots.find((s) => s.pageNum === page.index + 1)?.imagePath,
|
|
@@ -28887,9 +29355,11 @@ var ExuluPython = {
|
|
|
28887
29355
|
ExuluProvider,
|
|
28888
29356
|
ExuluPython,
|
|
28889
29357
|
ExuluQueues,
|
|
29358
|
+
ExuluReadApi,
|
|
28890
29359
|
ExuluReranker,
|
|
28891
29360
|
ExuluTool,
|
|
28892
29361
|
ExuluTrajectoryRegistry,
|
|
28893
29362
|
ExuluVariables,
|
|
28894
|
-
defaultChunker
|
|
29363
|
+
defaultChunker,
|
|
29364
|
+
postgresClient
|
|
28895
29365
|
});
|