@exulu/backend 3.7.0 → 3.7.2
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.
|
@@ -96,6 +96,9 @@ var spawnLiteLLM = (cfg) => {
|
|
|
96
96
|
...rest,
|
|
97
97
|
DEBUG: "false"
|
|
98
98
|
};
|
|
99
|
+
if (!process.env.DISABLE_SCHEMA_UPDATE) {
|
|
100
|
+
childEnv.DISABLE_SCHEMA_UPDATE = "true";
|
|
101
|
+
}
|
|
99
102
|
if (LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
100
103
|
childEnv.SERVER_ROOT_PATH = LITELLM_UI_PATH;
|
|
101
104
|
}
|
|
@@ -1713,7 +1716,7 @@ var ExuluTool = class _ExuluTool {
|
|
|
1713
1716
|
if (!agent) {
|
|
1714
1717
|
throw new Error("Agent not found.");
|
|
1715
1718
|
}
|
|
1716
|
-
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-
|
|
1719
|
+
const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-UQSLJDXE.js");
|
|
1717
1720
|
const tools = await convertExuluToolsToAiSdkTools2(
|
|
1718
1721
|
[this],
|
|
1719
1722
|
[],
|
package/dist/index.cjs
CHANGED
|
@@ -1347,6 +1347,9 @@ var init_supervisor = __esm({
|
|
|
1347
1347
|
...rest,
|
|
1348
1348
|
DEBUG: "false"
|
|
1349
1349
|
};
|
|
1350
|
+
if (!process.env.DISABLE_SCHEMA_UPDATE) {
|
|
1351
|
+
childEnv.DISABLE_SCHEMA_UPDATE = "true";
|
|
1352
|
+
}
|
|
1350
1353
|
if (LITELLM_UI_PATH && !process.env.SERVER_ROOT_PATH) {
|
|
1351
1354
|
childEnv.SERVER_ROOT_PATH = LITELLM_UI_PATH;
|
|
1352
1355
|
}
|
|
@@ -10209,17 +10212,31 @@ function detectQueryLanguage(query, minLength = 10) {
|
|
|
10209
10212
|
}
|
|
10210
10213
|
function stemWord(word, languageCode) {
|
|
10211
10214
|
const stemmer = STEMMER_MAP[languageCode] || import_natural.default.PorterStemmer;
|
|
10212
|
-
const
|
|
10213
|
-
if (!
|
|
10215
|
+
const core = word.replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "").toLowerCase();
|
|
10216
|
+
if (!core) {
|
|
10214
10217
|
return word;
|
|
10215
10218
|
}
|
|
10219
|
+
if (!/^\p{L}+$/u.test(core)) {
|
|
10220
|
+
return core;
|
|
10221
|
+
}
|
|
10216
10222
|
try {
|
|
10217
|
-
return stemmer.stem(
|
|
10223
|
+
return stemmer.stem(core);
|
|
10218
10224
|
} catch (error) {
|
|
10219
10225
|
console.warn(`[EXULU] Error stemming word "${word}":`, error);
|
|
10220
|
-
return
|
|
10226
|
+
return core;
|
|
10221
10227
|
}
|
|
10222
10228
|
}
|
|
10229
|
+
function buildFullTextOrQuery(original, processed) {
|
|
10230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10231
|
+
const tokens = [];
|
|
10232
|
+
for (const raw of `${original} ${processed}`.split(/\s+/)) {
|
|
10233
|
+
const token = raw.replace(/["'`]/g, "").replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "").toLowerCase();
|
|
10234
|
+
if (!token || token === "or" || seen.has(token)) continue;
|
|
10235
|
+
seen.add(token);
|
|
10236
|
+
tokens.push(token);
|
|
10237
|
+
}
|
|
10238
|
+
return tokens.join(" or ");
|
|
10239
|
+
}
|
|
10223
10240
|
function preprocessQuery(query, options = {}) {
|
|
10224
10241
|
const {
|
|
10225
10242
|
enableStemming = true,
|
|
@@ -10254,6 +10271,18 @@ function preprocessQuery(query, options = {}) {
|
|
|
10254
10271
|
stemmed: true
|
|
10255
10272
|
};
|
|
10256
10273
|
}
|
|
10274
|
+
function resolveSearchQueryTexts(query) {
|
|
10275
|
+
const { processed } = preprocessQuery(query, {
|
|
10276
|
+
enableStemming: true,
|
|
10277
|
+
detectLanguage: true
|
|
10278
|
+
});
|
|
10279
|
+
const ftsText = processed || query;
|
|
10280
|
+
return {
|
|
10281
|
+
embedText: query,
|
|
10282
|
+
ftsText,
|
|
10283
|
+
hybridOrQuery: buildFullTextOrQuery(query, ftsText)
|
|
10284
|
+
};
|
|
10285
|
+
}
|
|
10257
10286
|
|
|
10258
10287
|
// src/graphql/resolvers/apply-sorting.ts
|
|
10259
10288
|
init_cjs_shims();
|
|
@@ -12732,17 +12761,15 @@ var vectorSearch = async ({
|
|
|
12732
12761
|
const _tBody = Date.now();
|
|
12733
12762
|
let _preMs = 0, _statMs = 0, _resolveMs = 0, _embedMs = 0;
|
|
12734
12763
|
let _embedSource = "none";
|
|
12764
|
+
let hybridOrQuery = "";
|
|
12735
12765
|
if (query) {
|
|
12736
12766
|
const _tp = Date.now();
|
|
12737
|
-
const
|
|
12738
|
-
enableStemming: true,
|
|
12739
|
-
detectLanguage: true
|
|
12740
|
-
});
|
|
12767
|
+
const texts = resolveSearchQueryTexts(query);
|
|
12741
12768
|
_preMs = Date.now() - _tp;
|
|
12742
|
-
console.log("[EXULU]
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12769
|
+
console.log("[EXULU] Search query texts:", texts);
|
|
12770
|
+
const embedText = texts.embedText;
|
|
12771
|
+
hybridOrQuery = texts.hybridOrQuery;
|
|
12772
|
+
query = texts.ftsText;
|
|
12746
12773
|
if (queryEmbedding && queryEmbedding.length) {
|
|
12747
12774
|
vector = queryEmbedding;
|
|
12748
12775
|
_embedSource = "reused";
|
|
@@ -12768,7 +12795,7 @@ var vectorSearch = async ({
|
|
|
12768
12795
|
});
|
|
12769
12796
|
_resolveMs = Date.now() - _tr;
|
|
12770
12797
|
const _te = Date.now();
|
|
12771
|
-
const [queryVector] = await resolved.embed([
|
|
12798
|
+
const [queryVector] = await resolved.embed([embedText], { inputType: "query" });
|
|
12772
12799
|
_embedMs = Date.now() - _te;
|
|
12773
12800
|
if (!queryVector?.length) {
|
|
12774
12801
|
throw new Error("No vector generated for query.");
|
|
@@ -12840,10 +12867,10 @@ var vectorSearch = async ({
|
|
|
12840
12867
|
const fullTextWeight = 2;
|
|
12841
12868
|
const semanticWeight = 1;
|
|
12842
12869
|
const rrfK = 50;
|
|
12843
|
-
const ftRankExpression = languages.map((lang) => `ts_rank(chunks.fts,
|
|
12844
|
-
const ftRankParams = languages.map(() =>
|
|
12845
|
-
const ftMatchExpression = languages.map((lang) => `chunks.fts @@
|
|
12846
|
-
const ftMatchParams = languages.map(() =>
|
|
12870
|
+
const ftRankExpression = languages.map((lang) => `ts_rank(chunks.fts, websearch_to_tsquery('${lang}', ?))`).join(", ");
|
|
12871
|
+
const ftRankParams = languages.map(() => hybridOrQuery);
|
|
12872
|
+
const ftMatchExpression = languages.map((lang) => `chunks.fts @@ websearch_to_tsquery('${lang}', ?)`).join(" OR ");
|
|
12873
|
+
const ftMatchParams = languages.map(() => hybridOrQuery);
|
|
12847
12874
|
let fullTextQuery = db2(chunksTable + " as chunks").select([
|
|
12848
12875
|
"chunks.id",
|
|
12849
12876
|
"chunks.source",
|
|
@@ -12883,8 +12910,8 @@ var vectorSearch = async ({
|
|
|
12883
12910
|
db2.raw('items."updatedAt" as item_updated_at'),
|
|
12884
12911
|
db2.raw('items."createdAt" as item_created_at'),
|
|
12885
12912
|
db2.raw(
|
|
12886
|
-
`GREATEST(${languages.map((lang) => `ts_rank(chunks.fts,
|
|
12887
|
-
languages.map(() =>
|
|
12913
|
+
`GREATEST(${languages.map((lang) => `ts_rank(chunks.fts, websearch_to_tsquery('${lang}', ?))`).join(", ")}) AS fts_rank`,
|
|
12914
|
+
languages.map(() => hybridOrQuery)
|
|
12888
12915
|
),
|
|
12889
12916
|
db2.raw(`(1 - (chunks.embedding <=> ${vectorExpr})) AS cosine_distance`),
|
|
12890
12917
|
db2.raw(
|
|
@@ -12909,8 +12936,8 @@ var vectorSearch = async ({
|
|
|
12909
12936
|
`,
|
|
12910
12937
|
[rrfK, fullTextWeight, rrfK, semanticWeight, cutoffs?.hybrid || 0]
|
|
12911
12938
|
).whereRaw(
|
|
12912
|
-
`(chunks.fts IS NULL OR GREATEST(${languages.map((lang) => `ts_rank(chunks.fts,
|
|
12913
|
-
[...languages.map(() =>
|
|
12939
|
+
`(chunks.fts IS NULL OR GREATEST(${languages.map((lang) => `ts_rank(chunks.fts, websearch_to_tsquery('${lang}', ?))`).join(", ")}) > ?)`,
|
|
12940
|
+
[...languages.map(() => hybridOrQuery), cutoffs?.tsvector || 0]
|
|
12914
12941
|
).whereRaw(`(chunks.embedding IS NULL OR (1 - (chunks.embedding <=> ${vectorExpr})) >= ?)`, [
|
|
12915
12942
|
cutoffs?.cosineDistance || 0
|
|
12916
12943
|
]).orderByRaw("hybrid_score DESC").limit(Math.min(matchCount, 250));
|
|
@@ -16655,6 +16682,37 @@ var bullmq = {
|
|
|
16655
16682
|
}
|
|
16656
16683
|
};
|
|
16657
16684
|
|
|
16685
|
+
// src/utils/serialize-error.ts
|
|
16686
|
+
init_cjs_shims();
|
|
16687
|
+
var MAX_CAUSE_DEPTH = 3;
|
|
16688
|
+
function serializeError(err, depth = 0) {
|
|
16689
|
+
if (err instanceof Error) {
|
|
16690
|
+
const out = { name: err.name, message: err.message };
|
|
16691
|
+
if (err.stack) out.stack = err.stack;
|
|
16692
|
+
for (const [key, value] of Object.entries(err)) {
|
|
16693
|
+
if (key !== "cause") out[key] = value;
|
|
16694
|
+
}
|
|
16695
|
+
const cause = err.cause;
|
|
16696
|
+
if (cause !== void 0 && depth < MAX_CAUSE_DEPTH) {
|
|
16697
|
+
out.cause = serializeError(cause, depth + 1);
|
|
16698
|
+
}
|
|
16699
|
+
return out;
|
|
16700
|
+
}
|
|
16701
|
+
if (typeof err === "string") {
|
|
16702
|
+
return { message: err };
|
|
16703
|
+
}
|
|
16704
|
+
if (err !== null && typeof err === "object") {
|
|
16705
|
+
let message;
|
|
16706
|
+
try {
|
|
16707
|
+
message = JSON.stringify(err);
|
|
16708
|
+
} catch {
|
|
16709
|
+
message = "[unserializable error object]";
|
|
16710
|
+
}
|
|
16711
|
+
return { ...err, message };
|
|
16712
|
+
}
|
|
16713
|
+
return { message: String(err) };
|
|
16714
|
+
}
|
|
16715
|
+
|
|
16658
16716
|
// src/utils/enabled-tools.ts
|
|
16659
16717
|
init_cjs_shims();
|
|
16660
16718
|
init_pipeline();
|
|
@@ -16673,6 +16731,62 @@ init_statistics();
|
|
|
16673
16731
|
|
|
16674
16732
|
// src/exulu/generate-stream.ts
|
|
16675
16733
|
init_cjs_shims();
|
|
16734
|
+
|
|
16735
|
+
// src/exulu/file-part.ts
|
|
16736
|
+
init_cjs_shims();
|
|
16737
|
+
var IMAGE_EXTENSION_MIME = {
|
|
16738
|
+
".png": "image/png",
|
|
16739
|
+
".jpeg": "image/jpeg",
|
|
16740
|
+
".jpg": "image/jpeg",
|
|
16741
|
+
".gif": "image/gif",
|
|
16742
|
+
".webp": "image/webp",
|
|
16743
|
+
// Windows bitmaps arrive as email logos / signature images and camera exports.
|
|
16744
|
+
// Left out, they were routed to OfficeParser and replaced by an error text.
|
|
16745
|
+
".bmp": "image/bmp",
|
|
16746
|
+
".dib": "image/bmp"
|
|
16747
|
+
};
|
|
16748
|
+
var MEDIA_TYPE_ALIASES = {
|
|
16749
|
+
"image/jpg": "image/jpeg",
|
|
16750
|
+
"image/x-ms-bmp": "image/bmp",
|
|
16751
|
+
"image/x-bmp": "image/bmp",
|
|
16752
|
+
"image/x-windows-bmp": "image/bmp"
|
|
16753
|
+
};
|
|
16754
|
+
function fileNameFromUrl(url) {
|
|
16755
|
+
if (!url) return void 0;
|
|
16756
|
+
let path4;
|
|
16757
|
+
try {
|
|
16758
|
+
path4 = new URL(url).pathname;
|
|
16759
|
+
} catch {
|
|
16760
|
+
path4 = url.split("?")[0] ?? "";
|
|
16761
|
+
}
|
|
16762
|
+
const last = path4.split("/").pop() ?? "";
|
|
16763
|
+
try {
|
|
16764
|
+
return decodeURIComponent(last) || void 0;
|
|
16765
|
+
} catch {
|
|
16766
|
+
return last || void 0;
|
|
16767
|
+
}
|
|
16768
|
+
}
|
|
16769
|
+
function classifyFilePart(part) {
|
|
16770
|
+
const filename = part.filename ?? fileNameFromUrl(part.url);
|
|
16771
|
+
const lowerName = (filename ?? "").toLowerCase();
|
|
16772
|
+
const extension = Object.keys(IMAGE_EXTENSION_MIME).find((ext) => lowerName.endsWith(ext));
|
|
16773
|
+
const declared = typeof part.mediaType === "string" ? part.mediaType.trim().toLowerCase() : "";
|
|
16774
|
+
const declaredIsImage = declared.startsWith("image/");
|
|
16775
|
+
if (declaredIsImage || extension) {
|
|
16776
|
+
const mediaType = declaredIsImage ? MEDIA_TYPE_ALIASES[declared] ?? declared : IMAGE_EXTENSION_MIME[extension];
|
|
16777
|
+
return { kind: "image", mediaType, filename };
|
|
16778
|
+
}
|
|
16779
|
+
return { kind: "document", filename: filename ?? "attachment" };
|
|
16780
|
+
}
|
|
16781
|
+
|
|
16782
|
+
// src/exulu/stored-messages.ts
|
|
16783
|
+
init_cjs_shims();
|
|
16784
|
+
function dropEmptyMessages(messages) {
|
|
16785
|
+
const kept = messages.filter((m) => Array.isArray(m.parts) && m.parts.length > 0);
|
|
16786
|
+
return kept.length === messages.length ? messages : kept;
|
|
16787
|
+
}
|
|
16788
|
+
|
|
16789
|
+
// src/exulu/generate-stream.ts
|
|
16676
16790
|
var import_ai6 = require("ai");
|
|
16677
16791
|
|
|
16678
16792
|
// src/exulu/context-guard.ts
|
|
@@ -16990,22 +17104,19 @@ var processFilePartsInMessages = async (messages, offloadCtx) => {
|
|
|
16990
17104
|
return part;
|
|
16991
17105
|
}
|
|
16992
17106
|
console.log(`[EXULU] Processing part`, part);
|
|
16993
|
-
const {
|
|
16994
|
-
|
|
16995
|
-
console.log(`[EXULU]
|
|
16996
|
-
|
|
16997
|
-
const imageTypes = [".png", ".jpeg", ".jpg", ".gif", ".webp"];
|
|
16998
|
-
const imageType = imageTypes.find(
|
|
16999
|
-
(type) => filename.toLowerCase().includes(type.toLowerCase())
|
|
17000
|
-
);
|
|
17001
|
-
if (imageType) {
|
|
17002
|
-
console.log(`[EXULU] Converting file part to image part: ${filename} `);
|
|
17107
|
+
const { url } = part;
|
|
17108
|
+
const classified = classifyFilePart(part);
|
|
17109
|
+
console.log(`[EXULU] File part classified as ${classified.kind}: ${classified.filename}`);
|
|
17110
|
+
if (classified.kind === "image") {
|
|
17003
17111
|
return {
|
|
17004
17112
|
type: "file",
|
|
17005
|
-
mediaType:
|
|
17006
|
-
url
|
|
17113
|
+
mediaType: classified.mediaType,
|
|
17114
|
+
url,
|
|
17115
|
+
// Keep the name so later turns/steps still know the file.
|
|
17116
|
+
...classified.filename ? { filename: classified.filename } : {}
|
|
17007
17117
|
};
|
|
17008
17118
|
}
|
|
17119
|
+
const filename = classified.filename;
|
|
17009
17120
|
console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
|
|
17010
17121
|
try {
|
|
17011
17122
|
const response = await fetch(url);
|
|
@@ -17055,7 +17166,7 @@ var saveChat = async ({
|
|
|
17055
17166
|
model
|
|
17056
17167
|
}) => {
|
|
17057
17168
|
const { db: db2 } = await postgresClient();
|
|
17058
|
-
for (const message of messages) {
|
|
17169
|
+
for (const message of dropEmptyMessages(messages)) {
|
|
17059
17170
|
const mutation = db2.from("agent_messages").insert({
|
|
17060
17171
|
session,
|
|
17061
17172
|
user,
|
|
@@ -17142,7 +17253,7 @@ var generateSync = async ({
|
|
|
17142
17253
|
);
|
|
17143
17254
|
messages = await (0, import_ai6.validateUIMessages)({
|
|
17144
17255
|
// append the new message to the previous messages:
|
|
17145
|
-
messages: [...previousMessagesContent, ...messages]
|
|
17256
|
+
messages: dropEmptyMessages([...previousMessagesContent, ...messages])
|
|
17146
17257
|
});
|
|
17147
17258
|
const contextBudget = deriveContextBudget(contextWindow);
|
|
17148
17259
|
const occupancy = contextOccupancy(messages);
|
|
@@ -17484,7 +17595,7 @@ var generateStream = async ({
|
|
|
17484
17595
|
const model = languageModel;
|
|
17485
17596
|
messages = await (0, import_ai6.validateUIMessages)({
|
|
17486
17597
|
// append the new message to the previous messages:
|
|
17487
|
-
messages: [...previousMessagesContent, message]
|
|
17598
|
+
messages: dropEmptyMessages([...previousMessagesContent, message])
|
|
17488
17599
|
});
|
|
17489
17600
|
const query = message.parts?.[0]?.type === "text" ? message.parts[0].text : void 0;
|
|
17490
17601
|
if (session && query) {
|
|
@@ -19288,7 +19399,8 @@ var createWorkers = async (queues2, config, contexts, evals, tools, tracer) => {
|
|
|
19288
19399
|
JOB_STATUS_ENUM.completed
|
|
19289
19400
|
]).update({
|
|
19290
19401
|
state: JOB_STATUS_ENUM.failed,
|
|
19291
|
-
|
|
19402
|
+
// A raw Error serialises to "{}" in jsonb — keep message/stack/cause.
|
|
19403
|
+
error: serializeError(error)
|
|
19292
19404
|
});
|
|
19293
19405
|
void maybePruneJobResults(db2);
|
|
19294
19406
|
return;
|
|
@@ -23818,7 +23930,7 @@ var import_fs3 = __toESM(require("fs"), 1);
|
|
|
23818
23930
|
var import_node_crypto15 = require("crypto");
|
|
23819
23931
|
var import_api2 = require("@opentelemetry/api");
|
|
23820
23932
|
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
23821
|
-
var
|
|
23933
|
+
var import_ai12 = require("ai");
|
|
23822
23934
|
var import_cookie_parser = __toESM(require("cookie-parser"), 1);
|
|
23823
23935
|
init_statistics2();
|
|
23824
23936
|
|
|
@@ -23977,7 +24089,9 @@ var compactSession = async ({
|
|
|
23977
24089
|
}) => {
|
|
23978
24090
|
const budget = deriveContextBudget(contextWindow);
|
|
23979
24091
|
const rows = await getAgentMessages({ session: sessionID, user: user.id });
|
|
23980
|
-
const all = await (0, import_ai10.validateUIMessages)({
|
|
24092
|
+
const all = await (0, import_ai10.validateUIMessages)({
|
|
24093
|
+
messages: dropEmptyMessages(rows.map((r) => JSON.parse(r.content)))
|
|
24094
|
+
});
|
|
23981
24095
|
const history = sliceHistoryAtCheckpoint(all);
|
|
23982
24096
|
const { head, tail } = splitTail(history, budget.compactionTailTokens);
|
|
23983
24097
|
if (head.length === 0) {
|
|
@@ -24031,6 +24145,25 @@ ${summary}` }],
|
|
|
24031
24145
|
return { checkpoint, occupancyEstimate, originalTokens, summaryTokens };
|
|
24032
24146
|
};
|
|
24033
24147
|
|
|
24148
|
+
// src/exulu/request-error.ts
|
|
24149
|
+
init_cjs_shims();
|
|
24150
|
+
var import_ai11 = require("ai");
|
|
24151
|
+
init_context_budget();
|
|
24152
|
+
function describeRequestError(err) {
|
|
24153
|
+
if (err instanceof ContextCompactionRequiredError) {
|
|
24154
|
+
return { status: 413, body: err.message };
|
|
24155
|
+
}
|
|
24156
|
+
if (import_ai11.TypeValidationError.isInstance(err)) {
|
|
24157
|
+
const cause = err.cause;
|
|
24158
|
+
const detail = cause instanceof Error ? cause.message : typeof cause === "string" ? cause : "";
|
|
24159
|
+
return {
|
|
24160
|
+
status: 422,
|
|
24161
|
+
body: `The stored conversation could not be loaded: ${detail.slice(0, 400) || "type validation failed"}`
|
|
24162
|
+
};
|
|
24163
|
+
}
|
|
24164
|
+
return { status: 500, body: err instanceof Error ? err.message : String(err) };
|
|
24165
|
+
}
|
|
24166
|
+
|
|
24034
24167
|
// src/exulu/transcribe.ts
|
|
24035
24168
|
init_cjs_shims();
|
|
24036
24169
|
init_env();
|
|
@@ -25636,11 +25769,13 @@ ${customInstructions}` : agent.instructions;
|
|
|
25636
25769
|
});
|
|
25637
25770
|
} catch (err) {
|
|
25638
25771
|
if (headers.session) clearStreamActive(headers.session);
|
|
25639
|
-
|
|
25640
|
-
|
|
25641
|
-
|
|
25642
|
-
|
|
25643
|
-
|
|
25772
|
+
console.error(
|
|
25773
|
+
"[EXULU] chat request failed before streaming.",
|
|
25774
|
+
err instanceof Error ? err.message.slice(0, 500) : err
|
|
25775
|
+
);
|
|
25776
|
+
const { status, body } = describeRequestError(err);
|
|
25777
|
+
res.status(status).send(body);
|
|
25778
|
+
return;
|
|
25644
25779
|
}
|
|
25645
25780
|
result.stream.consumeStream();
|
|
25646
25781
|
result.stream.pipeUIMessageStreamToResponse(res, {
|
|
@@ -25675,7 +25810,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
25675
25810
|
else message2 = JSON.stringify(error);
|
|
25676
25811
|
return mapStreamErrorMessage(message2);
|
|
25677
25812
|
},
|
|
25678
|
-
generateMessageId: (0,
|
|
25813
|
+
generateMessageId: (0, import_ai12.createIdGenerator)({
|
|
25679
25814
|
prefix: "msg_",
|
|
25680
25815
|
size: 16
|
|
25681
25816
|
}),
|
|
@@ -25779,11 +25914,13 @@ ${customInstructions}` : agent.instructions;
|
|
|
25779
25914
|
}
|
|
25780
25915
|
});
|
|
25781
25916
|
} catch (err) {
|
|
25782
|
-
|
|
25783
|
-
|
|
25784
|
-
|
|
25785
|
-
|
|
25786
|
-
|
|
25917
|
+
console.error(
|
|
25918
|
+
"[EXULU] run request failed.",
|
|
25919
|
+
err instanceof Error ? err.message.slice(0, 500) : err
|
|
25920
|
+
);
|
|
25921
|
+
const { status, body } = describeRequestError(err);
|
|
25922
|
+
res.status(status).send(body);
|
|
25923
|
+
return;
|
|
25787
25924
|
}
|
|
25788
25925
|
res.status(200).json(response);
|
|
25789
25926
|
return;
|
|
@@ -28107,6 +28244,7 @@ ${style.markdown}` : params.prompt;
|
|
|
28107
28244
|
".jpeg": "image/jpeg",
|
|
28108
28245
|
".gif": "image/gif",
|
|
28109
28246
|
".webp": "image/webp",
|
|
28247
|
+
".bmp": "image/bmp",
|
|
28110
28248
|
".svg": "image/svg+xml",
|
|
28111
28249
|
".pdf": "application/pdf",
|
|
28112
28250
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
@@ -29133,7 +29271,7 @@ init_cjs_shims();
|
|
|
29133
29271
|
|
|
29134
29272
|
// src/exulu/evals.ts
|
|
29135
29273
|
init_cjs_shims();
|
|
29136
|
-
var
|
|
29274
|
+
var import_ai13 = require("ai");
|
|
29137
29275
|
var ExuluEval = class {
|
|
29138
29276
|
id;
|
|
29139
29277
|
name;
|
|
@@ -29173,7 +29311,7 @@ var ExuluEval = class {
|
|
|
29173
29311
|
init_resolve_model();
|
|
29174
29312
|
init_singleton();
|
|
29175
29313
|
var import_zod20 = require("zod");
|
|
29176
|
-
var
|
|
29314
|
+
var import_ai14 = require("ai");
|
|
29177
29315
|
var llmAsJudgeEval = () => {
|
|
29178
29316
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
29179
29317
|
return new ExuluEval({
|
|
@@ -29216,13 +29354,13 @@ var llmAsJudgeEval = () => {
|
|
|
29216
29354
|
rbacBypass: true
|
|
29217
29355
|
});
|
|
29218
29356
|
console.log("[EXULU] prompt", prompt);
|
|
29219
|
-
const { output } = await (0,
|
|
29357
|
+
const { output } = await (0, import_ai14.generateText)({
|
|
29220
29358
|
temperature: 0,
|
|
29221
29359
|
model: resolved.languageModel,
|
|
29222
29360
|
system: "",
|
|
29223
29361
|
prompt,
|
|
29224
29362
|
maxRetries: 2,
|
|
29225
|
-
output:
|
|
29363
|
+
output: import_ai14.Output.object({
|
|
29226
29364
|
schema: import_zod20.z.object({
|
|
29227
29365
|
score: import_zod20.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
29228
29366
|
})
|
|
@@ -32836,7 +32974,7 @@ var MarkdownChunker = class {
|
|
|
32836
32974
|
init_cjs_shims();
|
|
32837
32975
|
var fs4 = __toESM(require("fs"), 1);
|
|
32838
32976
|
var path3 = __toESM(require("path"), 1);
|
|
32839
|
-
var
|
|
32977
|
+
var import_ai15 = require("ai");
|
|
32840
32978
|
var import_zod27 = require("zod");
|
|
32841
32979
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
32842
32980
|
var import_crypto3 = require("crypto");
|
|
@@ -33292,9 +33430,9 @@ If the page contains a flow-chart, schematic, technical drawing or control board
|
|
|
33292
33430
|
|
|
33293
33431
|
### 7. Only populate \`corrected_text\` when \`needs_correction\` is true. If the OCR output is accurate, return \`needs_correction: false\` and \`corrected_content: null\`.
|
|
33294
33432
|
`;
|
|
33295
|
-
const result = await (0,
|
|
33433
|
+
const result = await (0, import_ai15.generateText)({
|
|
33296
33434
|
model,
|
|
33297
|
-
output:
|
|
33435
|
+
output: import_ai15.Output.object({
|
|
33298
33436
|
schema: import_zod27.z.object({
|
|
33299
33437
|
needs_correction: import_zod27.z.boolean(),
|
|
33300
33438
|
corrected_text: import_zod27.z.string().nullable(),
|
package/dist/index.js
CHANGED
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
verifyCredentialNonce,
|
|
89
89
|
waitForLiteLLMReady,
|
|
90
90
|
withRetry
|
|
91
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-BNTL6LYY.js";
|
|
92
92
|
import {
|
|
93
93
|
LiteLLMAdminError,
|
|
94
94
|
findLiteLLMModel,
|
|
@@ -1347,17 +1347,31 @@ function detectQueryLanguage(query, minLength = 10) {
|
|
|
1347
1347
|
}
|
|
1348
1348
|
function stemWord(word, languageCode) {
|
|
1349
1349
|
const stemmer = STEMMER_MAP[languageCode] || natural.PorterStemmer;
|
|
1350
|
-
const
|
|
1351
|
-
if (!
|
|
1350
|
+
const core = word.replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "").toLowerCase();
|
|
1351
|
+
if (!core) {
|
|
1352
1352
|
return word;
|
|
1353
1353
|
}
|
|
1354
|
+
if (!/^\p{L}+$/u.test(core)) {
|
|
1355
|
+
return core;
|
|
1356
|
+
}
|
|
1354
1357
|
try {
|
|
1355
|
-
return stemmer.stem(
|
|
1358
|
+
return stemmer.stem(core);
|
|
1356
1359
|
} catch (error) {
|
|
1357
1360
|
console.warn(`[EXULU] Error stemming word "${word}":`, error);
|
|
1358
|
-
return
|
|
1361
|
+
return core;
|
|
1359
1362
|
}
|
|
1360
1363
|
}
|
|
1364
|
+
function buildFullTextOrQuery(original, processed) {
|
|
1365
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1366
|
+
const tokens = [];
|
|
1367
|
+
for (const raw of `${original} ${processed}`.split(/\s+/)) {
|
|
1368
|
+
const token = raw.replace(/["'`]/g, "").replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "").toLowerCase();
|
|
1369
|
+
if (!token || token === "or" || seen.has(token)) continue;
|
|
1370
|
+
seen.add(token);
|
|
1371
|
+
tokens.push(token);
|
|
1372
|
+
}
|
|
1373
|
+
return tokens.join(" or ");
|
|
1374
|
+
}
|
|
1361
1375
|
function preprocessQuery(query, options = {}) {
|
|
1362
1376
|
const {
|
|
1363
1377
|
enableStemming = true,
|
|
@@ -1392,6 +1406,18 @@ function preprocessQuery(query, options = {}) {
|
|
|
1392
1406
|
stemmed: true
|
|
1393
1407
|
};
|
|
1394
1408
|
}
|
|
1409
|
+
function resolveSearchQueryTexts(query) {
|
|
1410
|
+
const { processed } = preprocessQuery(query, {
|
|
1411
|
+
enableStemming: true,
|
|
1412
|
+
detectLanguage: true
|
|
1413
|
+
});
|
|
1414
|
+
const ftsText = processed || query;
|
|
1415
|
+
return {
|
|
1416
|
+
embedText: query,
|
|
1417
|
+
ftsText,
|
|
1418
|
+
hybridOrQuery: buildFullTextOrQuery(query, ftsText)
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1395
1421
|
|
|
1396
1422
|
// src/graphql/resolvers/field-allow-list.ts
|
|
1397
1423
|
var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
|
|
@@ -3828,17 +3854,15 @@ var vectorSearch = async ({
|
|
|
3828
3854
|
const _tBody = Date.now();
|
|
3829
3855
|
let _preMs = 0, _statMs = 0, _resolveMs = 0, _embedMs = 0;
|
|
3830
3856
|
let _embedSource = "none";
|
|
3857
|
+
let hybridOrQuery = "";
|
|
3831
3858
|
if (query) {
|
|
3832
3859
|
const _tp = Date.now();
|
|
3833
|
-
const
|
|
3834
|
-
enableStemming: true,
|
|
3835
|
-
detectLanguage: true
|
|
3836
|
-
});
|
|
3860
|
+
const texts = resolveSearchQueryTexts(query);
|
|
3837
3861
|
_preMs = Date.now() - _tp;
|
|
3838
|
-
console.log("[EXULU]
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3862
|
+
console.log("[EXULU] Search query texts:", texts);
|
|
3863
|
+
const embedText = texts.embedText;
|
|
3864
|
+
hybridOrQuery = texts.hybridOrQuery;
|
|
3865
|
+
query = texts.ftsText;
|
|
3842
3866
|
if (queryEmbedding && queryEmbedding.length) {
|
|
3843
3867
|
vector = queryEmbedding;
|
|
3844
3868
|
_embedSource = "reused";
|
|
@@ -3864,7 +3888,7 @@ var vectorSearch = async ({
|
|
|
3864
3888
|
});
|
|
3865
3889
|
_resolveMs = Date.now() - _tr;
|
|
3866
3890
|
const _te = Date.now();
|
|
3867
|
-
const [queryVector] = await resolved.embed([
|
|
3891
|
+
const [queryVector] = await resolved.embed([embedText], { inputType: "query" });
|
|
3868
3892
|
_embedMs = Date.now() - _te;
|
|
3869
3893
|
if (!queryVector?.length) {
|
|
3870
3894
|
throw new Error("No vector generated for query.");
|
|
@@ -3936,10 +3960,10 @@ var vectorSearch = async ({
|
|
|
3936
3960
|
const fullTextWeight = 2;
|
|
3937
3961
|
const semanticWeight = 1;
|
|
3938
3962
|
const rrfK = 50;
|
|
3939
|
-
const ftRankExpression = languages.map((lang) => `ts_rank(chunks.fts,
|
|
3940
|
-
const ftRankParams = languages.map(() =>
|
|
3941
|
-
const ftMatchExpression = languages.map((lang) => `chunks.fts @@
|
|
3942
|
-
const ftMatchParams = languages.map(() =>
|
|
3963
|
+
const ftRankExpression = languages.map((lang) => `ts_rank(chunks.fts, websearch_to_tsquery('${lang}', ?))`).join(", ");
|
|
3964
|
+
const ftRankParams = languages.map(() => hybridOrQuery);
|
|
3965
|
+
const ftMatchExpression = languages.map((lang) => `chunks.fts @@ websearch_to_tsquery('${lang}', ?)`).join(" OR ");
|
|
3966
|
+
const ftMatchParams = languages.map(() => hybridOrQuery);
|
|
3943
3967
|
let fullTextQuery = db(chunksTable + " as chunks").select([
|
|
3944
3968
|
"chunks.id",
|
|
3945
3969
|
"chunks.source",
|
|
@@ -3979,8 +4003,8 @@ var vectorSearch = async ({
|
|
|
3979
4003
|
db.raw('items."updatedAt" as item_updated_at'),
|
|
3980
4004
|
db.raw('items."createdAt" as item_created_at'),
|
|
3981
4005
|
db.raw(
|
|
3982
|
-
`GREATEST(${languages.map((lang) => `ts_rank(chunks.fts,
|
|
3983
|
-
languages.map(() =>
|
|
4006
|
+
`GREATEST(${languages.map((lang) => `ts_rank(chunks.fts, websearch_to_tsquery('${lang}', ?))`).join(", ")}) AS fts_rank`,
|
|
4007
|
+
languages.map(() => hybridOrQuery)
|
|
3984
4008
|
),
|
|
3985
4009
|
db.raw(`(1 - (chunks.embedding <=> ${vectorExpr})) AS cosine_distance`),
|
|
3986
4010
|
db.raw(
|
|
@@ -4005,8 +4029,8 @@ var vectorSearch = async ({
|
|
|
4005
4029
|
`,
|
|
4006
4030
|
[rrfK, fullTextWeight, rrfK, semanticWeight, cutoffs?.hybrid || 0]
|
|
4007
4031
|
).whereRaw(
|
|
4008
|
-
`(chunks.fts IS NULL OR GREATEST(${languages.map((lang) => `ts_rank(chunks.fts,
|
|
4009
|
-
[...languages.map(() =>
|
|
4032
|
+
`(chunks.fts IS NULL OR GREATEST(${languages.map((lang) => `ts_rank(chunks.fts, websearch_to_tsquery('${lang}', ?))`).join(", ")}) > ?)`,
|
|
4033
|
+
[...languages.map(() => hybridOrQuery), cutoffs?.tsvector || 0]
|
|
4010
4034
|
).whereRaw(`(chunks.embedding IS NULL OR (1 - (chunks.embedding <=> ${vectorExpr})) >= ?)`, [
|
|
4011
4035
|
cutoffs?.cosineDistance || 0
|
|
4012
4036
|
]).orderByRaw("hybrid_score DESC").limit(Math.min(matchCount, 250));
|
|
@@ -7687,9 +7711,91 @@ var bullmq = {
|
|
|
7687
7711
|
}
|
|
7688
7712
|
};
|
|
7689
7713
|
|
|
7714
|
+
// src/utils/serialize-error.ts
|
|
7715
|
+
var MAX_CAUSE_DEPTH = 3;
|
|
7716
|
+
function serializeError(err, depth = 0) {
|
|
7717
|
+
if (err instanceof Error) {
|
|
7718
|
+
const out = { name: err.name, message: err.message };
|
|
7719
|
+
if (err.stack) out.stack = err.stack;
|
|
7720
|
+
for (const [key, value] of Object.entries(err)) {
|
|
7721
|
+
if (key !== "cause") out[key] = value;
|
|
7722
|
+
}
|
|
7723
|
+
const cause = err.cause;
|
|
7724
|
+
if (cause !== void 0 && depth < MAX_CAUSE_DEPTH) {
|
|
7725
|
+
out.cause = serializeError(cause, depth + 1);
|
|
7726
|
+
}
|
|
7727
|
+
return out;
|
|
7728
|
+
}
|
|
7729
|
+
if (typeof err === "string") {
|
|
7730
|
+
return { message: err };
|
|
7731
|
+
}
|
|
7732
|
+
if (err !== null && typeof err === "object") {
|
|
7733
|
+
let message;
|
|
7734
|
+
try {
|
|
7735
|
+
message = JSON.stringify(err);
|
|
7736
|
+
} catch {
|
|
7737
|
+
message = "[unserializable error object]";
|
|
7738
|
+
}
|
|
7739
|
+
return { ...err, message };
|
|
7740
|
+
}
|
|
7741
|
+
return { message: String(err) };
|
|
7742
|
+
}
|
|
7743
|
+
|
|
7690
7744
|
// src/exulu/agent-as-tool.ts
|
|
7691
7745
|
import { z as z2 } from "zod";
|
|
7692
7746
|
|
|
7747
|
+
// src/exulu/file-part.ts
|
|
7748
|
+
var IMAGE_EXTENSION_MIME = {
|
|
7749
|
+
".png": "image/png",
|
|
7750
|
+
".jpeg": "image/jpeg",
|
|
7751
|
+
".jpg": "image/jpeg",
|
|
7752
|
+
".gif": "image/gif",
|
|
7753
|
+
".webp": "image/webp",
|
|
7754
|
+
// Windows bitmaps arrive as email logos / signature images and camera exports.
|
|
7755
|
+
// Left out, they were routed to OfficeParser and replaced by an error text.
|
|
7756
|
+
".bmp": "image/bmp",
|
|
7757
|
+
".dib": "image/bmp"
|
|
7758
|
+
};
|
|
7759
|
+
var MEDIA_TYPE_ALIASES = {
|
|
7760
|
+
"image/jpg": "image/jpeg",
|
|
7761
|
+
"image/x-ms-bmp": "image/bmp",
|
|
7762
|
+
"image/x-bmp": "image/bmp",
|
|
7763
|
+
"image/x-windows-bmp": "image/bmp"
|
|
7764
|
+
};
|
|
7765
|
+
function fileNameFromUrl(url) {
|
|
7766
|
+
if (!url) return void 0;
|
|
7767
|
+
let path2;
|
|
7768
|
+
try {
|
|
7769
|
+
path2 = new URL(url).pathname;
|
|
7770
|
+
} catch {
|
|
7771
|
+
path2 = url.split("?")[0] ?? "";
|
|
7772
|
+
}
|
|
7773
|
+
const last = path2.split("/").pop() ?? "";
|
|
7774
|
+
try {
|
|
7775
|
+
return decodeURIComponent(last) || void 0;
|
|
7776
|
+
} catch {
|
|
7777
|
+
return last || void 0;
|
|
7778
|
+
}
|
|
7779
|
+
}
|
|
7780
|
+
function classifyFilePart(part) {
|
|
7781
|
+
const filename = part.filename ?? fileNameFromUrl(part.url);
|
|
7782
|
+
const lowerName = (filename ?? "").toLowerCase();
|
|
7783
|
+
const extension = Object.keys(IMAGE_EXTENSION_MIME).find((ext) => lowerName.endsWith(ext));
|
|
7784
|
+
const declared = typeof part.mediaType === "string" ? part.mediaType.trim().toLowerCase() : "";
|
|
7785
|
+
const declaredIsImage = declared.startsWith("image/");
|
|
7786
|
+
if (declaredIsImage || extension) {
|
|
7787
|
+
const mediaType = declaredIsImage ? MEDIA_TYPE_ALIASES[declared] ?? declared : IMAGE_EXTENSION_MIME[extension];
|
|
7788
|
+
return { kind: "image", mediaType, filename };
|
|
7789
|
+
}
|
|
7790
|
+
return { kind: "document", filename: filename ?? "attachment" };
|
|
7791
|
+
}
|
|
7792
|
+
|
|
7793
|
+
// src/exulu/stored-messages.ts
|
|
7794
|
+
function dropEmptyMessages(messages) {
|
|
7795
|
+
const kept = messages.filter((m) => Array.isArray(m.parts) && m.parts.length > 0);
|
|
7796
|
+
return kept.length === messages.length ? messages : kept;
|
|
7797
|
+
}
|
|
7798
|
+
|
|
7693
7799
|
// src/exulu/generate-stream.ts
|
|
7694
7800
|
import {
|
|
7695
7801
|
convertToModelMessages,
|
|
@@ -7993,22 +8099,19 @@ var processFilePartsInMessages = async (messages, offloadCtx) => {
|
|
|
7993
8099
|
return part;
|
|
7994
8100
|
}
|
|
7995
8101
|
console.log(`[EXULU] Processing part`, part);
|
|
7996
|
-
const {
|
|
7997
|
-
|
|
7998
|
-
console.log(`[EXULU]
|
|
7999
|
-
|
|
8000
|
-
const imageTypes = [".png", ".jpeg", ".jpg", ".gif", ".webp"];
|
|
8001
|
-
const imageType = imageTypes.find(
|
|
8002
|
-
(type) => filename.toLowerCase().includes(type.toLowerCase())
|
|
8003
|
-
);
|
|
8004
|
-
if (imageType) {
|
|
8005
|
-
console.log(`[EXULU] Converting file part to image part: ${filename} `);
|
|
8102
|
+
const { url } = part;
|
|
8103
|
+
const classified = classifyFilePart(part);
|
|
8104
|
+
console.log(`[EXULU] File part classified as ${classified.kind}: ${classified.filename}`);
|
|
8105
|
+
if (classified.kind === "image") {
|
|
8006
8106
|
return {
|
|
8007
8107
|
type: "file",
|
|
8008
|
-
mediaType:
|
|
8009
|
-
url
|
|
8108
|
+
mediaType: classified.mediaType,
|
|
8109
|
+
url,
|
|
8110
|
+
// Keep the name so later turns/steps still know the file.
|
|
8111
|
+
...classified.filename ? { filename: classified.filename } : {}
|
|
8010
8112
|
};
|
|
8011
8113
|
}
|
|
8114
|
+
const filename = classified.filename;
|
|
8012
8115
|
console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
|
|
8013
8116
|
try {
|
|
8014
8117
|
const response = await fetch(url);
|
|
@@ -8058,7 +8161,7 @@ var saveChat = async ({
|
|
|
8058
8161
|
model
|
|
8059
8162
|
}) => {
|
|
8060
8163
|
const { db } = await postgresClient();
|
|
8061
|
-
for (const message of messages) {
|
|
8164
|
+
for (const message of dropEmptyMessages(messages)) {
|
|
8062
8165
|
const mutation = db.from("agent_messages").insert({
|
|
8063
8166
|
session,
|
|
8064
8167
|
user,
|
|
@@ -8145,7 +8248,7 @@ var generateSync = async ({
|
|
|
8145
8248
|
);
|
|
8146
8249
|
messages = await validateUIMessages({
|
|
8147
8250
|
// append the new message to the previous messages:
|
|
8148
|
-
messages: [...previousMessagesContent, ...messages]
|
|
8251
|
+
messages: dropEmptyMessages([...previousMessagesContent, ...messages])
|
|
8149
8252
|
});
|
|
8150
8253
|
const contextBudget = deriveContextBudget(contextWindow);
|
|
8151
8254
|
const occupancy = contextOccupancy(messages);
|
|
@@ -8487,7 +8590,7 @@ var generateStream = async ({
|
|
|
8487
8590
|
const model = languageModel;
|
|
8488
8591
|
messages = await validateUIMessages({
|
|
8489
8592
|
// append the new message to the previous messages:
|
|
8490
|
-
messages: [...previousMessagesContent, message]
|
|
8593
|
+
messages: dropEmptyMessages([...previousMessagesContent, message])
|
|
8491
8594
|
});
|
|
8492
8595
|
const query = message.parts?.[0]?.type === "text" ? message.parts[0].text : void 0;
|
|
8493
8596
|
if (session && query) {
|
|
@@ -10270,7 +10373,8 @@ var createWorkers = async (queues2, config, contexts, evals, tools, tracer) => {
|
|
|
10270
10373
|
JOB_STATUS_ENUM.completed
|
|
10271
10374
|
]).update({
|
|
10272
10375
|
state: JOB_STATUS_ENUM.failed,
|
|
10273
|
-
|
|
10376
|
+
// A raw Error serialises to "{}" in jsonb — keep message/stack/cause.
|
|
10377
|
+
error: serializeError(error)
|
|
10274
10378
|
});
|
|
10275
10379
|
void maybePruneJobResults(db);
|
|
10276
10380
|
return;
|
|
@@ -14914,7 +15018,9 @@ var compactSession = async ({
|
|
|
14914
15018
|
}) => {
|
|
14915
15019
|
const budget = deriveContextBudget(contextWindow);
|
|
14916
15020
|
const rows = await getAgentMessages({ session: sessionID, user: user.id });
|
|
14917
|
-
const all = await validateUIMessages2({
|
|
15021
|
+
const all = await validateUIMessages2({
|
|
15022
|
+
messages: dropEmptyMessages(rows.map((r) => JSON.parse(r.content)))
|
|
15023
|
+
});
|
|
14918
15024
|
const history = sliceHistoryAtCheckpoint(all);
|
|
14919
15025
|
const { head, tail } = splitTail(history, budget.compactionTailTokens);
|
|
14920
15026
|
if (head.length === 0) {
|
|
@@ -14968,6 +15074,23 @@ ${summary}` }],
|
|
|
14968
15074
|
return { checkpoint, occupancyEstimate, originalTokens, summaryTokens };
|
|
14969
15075
|
};
|
|
14970
15076
|
|
|
15077
|
+
// src/exulu/request-error.ts
|
|
15078
|
+
import { TypeValidationError } from "ai";
|
|
15079
|
+
function describeRequestError(err) {
|
|
15080
|
+
if (err instanceof ContextCompactionRequiredError) {
|
|
15081
|
+
return { status: 413, body: err.message };
|
|
15082
|
+
}
|
|
15083
|
+
if (TypeValidationError.isInstance(err)) {
|
|
15084
|
+
const cause = err.cause;
|
|
15085
|
+
const detail = cause instanceof Error ? cause.message : typeof cause === "string" ? cause : "";
|
|
15086
|
+
return {
|
|
15087
|
+
status: 422,
|
|
15088
|
+
body: `The stored conversation could not be loaded: ${detail.slice(0, 400) || "type validation failed"}`
|
|
15089
|
+
};
|
|
15090
|
+
}
|
|
15091
|
+
return { status: 500, body: err instanceof Error ? err.message : String(err) };
|
|
15092
|
+
}
|
|
15093
|
+
|
|
14971
15094
|
// src/exulu/transcribe.ts
|
|
14972
15095
|
var TRANSCRIBE_SYSTEM_PROMPT = "You are a speech-to-text transcription engine. Detect the language actually spoken and transcribe it word-for-word in that same language. Never translate. Output only the transcript text \u2014 no quotes, labels, or commentary. If there is no intelligible speech, output nothing.";
|
|
14973
15096
|
function isGeminiChatTranscriptionModel(entry) {
|
|
@@ -16524,11 +16647,13 @@ ${customInstructions}` : agent.instructions;
|
|
|
16524
16647
|
});
|
|
16525
16648
|
} catch (err) {
|
|
16526
16649
|
if (headers.session) clearStreamActive(headers.session);
|
|
16527
|
-
|
|
16528
|
-
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16650
|
+
console.error(
|
|
16651
|
+
"[EXULU] chat request failed before streaming.",
|
|
16652
|
+
err instanceof Error ? err.message.slice(0, 500) : err
|
|
16653
|
+
);
|
|
16654
|
+
const { status, body } = describeRequestError(err);
|
|
16655
|
+
res.status(status).send(body);
|
|
16656
|
+
return;
|
|
16532
16657
|
}
|
|
16533
16658
|
result.stream.consumeStream();
|
|
16534
16659
|
result.stream.pipeUIMessageStreamToResponse(res, {
|
|
@@ -16667,11 +16792,13 @@ ${customInstructions}` : agent.instructions;
|
|
|
16667
16792
|
}
|
|
16668
16793
|
});
|
|
16669
16794
|
} catch (err) {
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16795
|
+
console.error(
|
|
16796
|
+
"[EXULU] run request failed.",
|
|
16797
|
+
err instanceof Error ? err.message.slice(0, 500) : err
|
|
16798
|
+
);
|
|
16799
|
+
const { status, body } = describeRequestError(err);
|
|
16800
|
+
res.status(status).send(body);
|
|
16801
|
+
return;
|
|
16675
16802
|
}
|
|
16676
16803
|
res.status(200).json(response);
|
|
16677
16804
|
return;
|
|
@@ -18995,6 +19122,7 @@ ${style.markdown}` : params.prompt;
|
|
|
18995
19122
|
".jpeg": "image/jpeg",
|
|
18996
19123
|
".gif": "image/gif",
|
|
18997
19124
|
".webp": "image/webp",
|
|
19125
|
+
".bmp": "image/bmp",
|
|
18998
19126
|
".svg": "image/svg+xml",
|
|
18999
19127
|
".pdf": "application/pdf",
|
|
19000
19128
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
package/ee/workers.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { redisServer } from "@EE/queues/server.ts";
|
|
|
3
3
|
import { guardRedisStartup, logRedisErrors } from "@EE/queues/redis-startup.ts";
|
|
4
4
|
import { Job, Worker, type JobState } from "bullmq";
|
|
5
5
|
import { bullmq } from "@SRC/validators/bullmq.ts";
|
|
6
|
+
import { serializeError } from "@SRC/utils/serialize-error.ts";
|
|
6
7
|
import { getEnabledTools } from "@SRC/utils/enabled-tools.ts";
|
|
7
8
|
import { ExuluStorage } from "@SRC/exulu/storage.ts";
|
|
8
9
|
import type { ExuluAgent } from "@EXULU_TYPES/models/agent.ts";
|
|
@@ -1212,7 +1213,8 @@ export const createWorkers = async (
|
|
|
1212
1213
|
])
|
|
1213
1214
|
.update({
|
|
1214
1215
|
state: JOB_STATUS_ENUM.failed,
|
|
1215
|
-
|
|
1216
|
+
// A raw Error serialises to "{}" in jsonb — keep message/stack/cause.
|
|
1217
|
+
error: serializeError(error),
|
|
1216
1218
|
});
|
|
1217
1219
|
|
|
1218
1220
|
// Cap the table as rows become terminal (every Nth, idempotent).
|