@exulu/backend 2.1.0 → 2.3.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/index.cjs
CHANGED
|
@@ -415,7 +415,12 @@ function getS3Client(config) {
|
|
|
415
415
|
credentials: {
|
|
416
416
|
accessKeyId: config.fileUploads.s3key,
|
|
417
417
|
secretAccessKey: config.fileUploads.s3secret
|
|
418
|
-
}
|
|
418
|
+
},
|
|
419
|
+
// AWS SDK >= 3.729 injects x-amz-checksum-crc32 (of an empty body) into
|
|
420
|
+
// presigned PUT URLs, which S3-compatible stores like MinIO reject on
|
|
421
|
+
// upload with a checksum mismatch. WHEN_REQUIRED disables that default.
|
|
422
|
+
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
423
|
+
responseChecksumValidation: "WHEN_REQUIRED"
|
|
419
424
|
});
|
|
420
425
|
return s3Client;
|
|
421
426
|
}
|
|
@@ -1667,6 +1672,9 @@ async function tagUpdate(input) {
|
|
|
1667
1672
|
budget_duration: input.budget_duration
|
|
1668
1673
|
});
|
|
1669
1674
|
}
|
|
1675
|
+
async function budgetUpdate(budget_id, patch) {
|
|
1676
|
+
await call("/budget/update", { budget_id, ...patch });
|
|
1677
|
+
}
|
|
1670
1678
|
async function tagDelete(name) {
|
|
1671
1679
|
await call("/tag/delete", { name });
|
|
1672
1680
|
}
|
|
@@ -1675,8 +1683,9 @@ function extractBudget(raw) {
|
|
|
1675
1683
|
const max_budget = bt.max_budget ?? raw?.max_budget ?? null;
|
|
1676
1684
|
const budget_duration = bt.budget_duration ?? raw?.budget_duration ?? null;
|
|
1677
1685
|
const budget_reset_at = bt.budget_reset_at ?? raw?.budget_reset_at ?? null;
|
|
1686
|
+
const budget_id = bt.budget_id ?? raw?.budget_id ?? null;
|
|
1678
1687
|
const spend = typeof raw?.spend === "number" ? raw.spend : typeof bt.spend === "number" ? bt.spend : 0;
|
|
1679
|
-
return { max_budget, budget_duration, budget_reset_at, spend };
|
|
1688
|
+
return { max_budget, budget_duration, budget_reset_at, budget_id, spend };
|
|
1680
1689
|
}
|
|
1681
1690
|
async function listTags() {
|
|
1682
1691
|
const { url, masterKey } = litellmBase();
|
|
@@ -1709,7 +1718,8 @@ async function listTagBudgets() {
|
|
|
1709
1718
|
spend: b.spend,
|
|
1710
1719
|
max_budget: b.max_budget,
|
|
1711
1720
|
budget_duration: b.budget_duration,
|
|
1712
|
-
budget_reset_at: b.budget_reset_at
|
|
1721
|
+
budget_reset_at: b.budget_reset_at,
|
|
1722
|
+
budget_id: b.budget_id
|
|
1713
1723
|
};
|
|
1714
1724
|
}
|
|
1715
1725
|
const names = Object.keys(map);
|
|
@@ -1755,7 +1765,8 @@ async function tagInfo(names) {
|
|
|
1755
1765
|
spend: b.spend,
|
|
1756
1766
|
max_budget: b.max_budget,
|
|
1757
1767
|
budget_duration: b.budget_duration,
|
|
1758
|
-
budget_reset_at: b.budget_reset_at
|
|
1768
|
+
budget_reset_at: b.budget_reset_at,
|
|
1769
|
+
budget_id: b.budget_id
|
|
1759
1770
|
};
|
|
1760
1771
|
}
|
|
1761
1772
|
return out;
|
|
@@ -1986,7 +1997,16 @@ async function setBudgetSettings(settings) {
|
|
|
1986
1997
|
}).onConflict("config_key").merge({ config_value: JSON.stringify(settings) });
|
|
1987
1998
|
return settings;
|
|
1988
1999
|
}
|
|
1989
|
-
|
|
2000
|
+
function parseResetAt(raw) {
|
|
2001
|
+
if (raw === void 0 || raw === null || raw === "") {
|
|
2002
|
+
return { valid: true, value: void 0 };
|
|
2003
|
+
}
|
|
2004
|
+
if (typeof raw !== "string") return { valid: false };
|
|
2005
|
+
const t = Date.parse(raw);
|
|
2006
|
+
if (Number.isNaN(t)) return { valid: false };
|
|
2007
|
+
return { valid: true, value: new Date(t).toISOString() };
|
|
2008
|
+
}
|
|
2009
|
+
async function upsertBudget(tag, max_budget, budget_duration, budget_reset_at) {
|
|
1990
2010
|
const info = await tagInfo([tag]);
|
|
1991
2011
|
try {
|
|
1992
2012
|
if (info[tag]) {
|
|
@@ -2001,6 +2021,15 @@ async function upsertBudget(tag, max_budget, budget_duration) {
|
|
|
2001
2021
|
await tagUpdate({ name: tag, max_budget, budget_duration });
|
|
2002
2022
|
}
|
|
2003
2023
|
}
|
|
2024
|
+
if (budget_reset_at) {
|
|
2025
|
+
const after = await tagInfo([tag]);
|
|
2026
|
+
const budgetId = after[tag]?.budget_id ?? null;
|
|
2027
|
+
if (budgetId) {
|
|
2028
|
+
await budgetUpdate(budgetId, { budget_reset_at });
|
|
2029
|
+
} else {
|
|
2030
|
+
console.warn(`[EXULU] upsertBudget: no budget_id for ${tag}; reset date not applied`);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2004
2033
|
invalidateBudgetCaches(tag);
|
|
2005
2034
|
}
|
|
2006
2035
|
function invalidateBudgetCaches(tag) {
|
|
@@ -3303,6 +3332,15 @@ var init_system_dependencies = __esm({
|
|
|
3303
3332
|
macos: "brew install poppler"
|
|
3304
3333
|
}
|
|
3305
3334
|
},
|
|
3335
|
+
{
|
|
3336
|
+
check: { kind: "binary", binary: "pdftotext" },
|
|
3337
|
+
displayName: "Poppler (pdftotext)",
|
|
3338
|
+
purpose: "parse_document tool: extracting page-marked text from PDFs",
|
|
3339
|
+
installHints: {
|
|
3340
|
+
debian: "apt-get install -y poppler-utils",
|
|
3341
|
+
macos: "brew install poppler"
|
|
3342
|
+
}
|
|
3343
|
+
},
|
|
3306
3344
|
{
|
|
3307
3345
|
check: { kind: "npm-global", packageName: "docx" },
|
|
3308
3346
|
displayName: "docx (npm global)",
|
|
@@ -4140,6 +4178,595 @@ var init_session_file_read_tool = __esm({
|
|
|
4140
4178
|
}
|
|
4141
4179
|
});
|
|
4142
4180
|
|
|
4181
|
+
// src/templates/tools/document-render-helpers.ts
|
|
4182
|
+
async function pdfToText(pdf) {
|
|
4183
|
+
const dir = await (0, import_promises2.mkdtemp)((0, import_node_path5.join)((0, import_node_os.tmpdir)(), "exulu-parse-"));
|
|
4184
|
+
try {
|
|
4185
|
+
const inputPath = (0, import_node_path5.join)(dir, "input.pdf");
|
|
4186
|
+
await (0, import_promises2.writeFile)(inputPath, pdf);
|
|
4187
|
+
const { stdout } = await execFileAsync("pdftotext", ["-layout", inputPath, "-"], {
|
|
4188
|
+
timeout: 6e4,
|
|
4189
|
+
maxBuffer: MAX_STDOUT_BYTES
|
|
4190
|
+
});
|
|
4191
|
+
return stdout;
|
|
4192
|
+
} finally {
|
|
4193
|
+
await (0, import_promises2.rm)(dir, { recursive: true, force: true });
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
async function renderPdfPageToPng(pdf, page, scaleTo) {
|
|
4197
|
+
const dir = await (0, import_promises2.mkdtemp)((0, import_node_path5.join)((0, import_node_os.tmpdir)(), "exulu-render-"));
|
|
4198
|
+
try {
|
|
4199
|
+
const inputPath = (0, import_node_path5.join)(dir, "input.pdf");
|
|
4200
|
+
await (0, import_promises2.writeFile)(inputPath, pdf);
|
|
4201
|
+
try {
|
|
4202
|
+
await execFileAsync(
|
|
4203
|
+
"pdftoppm",
|
|
4204
|
+
["-png", "-f", String(page), "-l", String(page), "-scale-to", String(scaleTo), inputPath, (0, import_node_path5.join)(dir, "page")],
|
|
4205
|
+
{ timeout: 6e4, maxBuffer: MAX_STDOUT_BYTES }
|
|
4206
|
+
);
|
|
4207
|
+
} catch (err) {
|
|
4208
|
+
if (err?.code === 99) return null;
|
|
4209
|
+
throw err;
|
|
4210
|
+
}
|
|
4211
|
+
const produced = (await (0, import_promises2.readdir)(dir)).find((f) => f.startsWith("page") && f.endsWith(".png"));
|
|
4212
|
+
if (!produced) return null;
|
|
4213
|
+
return await (0, import_promises2.readFile)((0, import_node_path5.join)(dir, produced));
|
|
4214
|
+
} finally {
|
|
4215
|
+
await (0, import_promises2.rm)(dir, { recursive: true, force: true });
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
var import_node_child_process4, import_node_util3, import_promises2, import_node_os, import_node_path5, execFileAsync, MAX_STDOUT_BYTES;
|
|
4219
|
+
var init_document_render_helpers = __esm({
|
|
4220
|
+
"src/templates/tools/document-render-helpers.ts"() {
|
|
4221
|
+
"use strict";
|
|
4222
|
+
init_cjs_shims();
|
|
4223
|
+
import_node_child_process4 = require("child_process");
|
|
4224
|
+
import_node_util3 = require("util");
|
|
4225
|
+
import_promises2 = require("fs/promises");
|
|
4226
|
+
import_node_os = require("os");
|
|
4227
|
+
import_node_path5 = require("path");
|
|
4228
|
+
execFileAsync = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
|
|
4229
|
+
MAX_STDOUT_BYTES = 64 * 1024 * 1024;
|
|
4230
|
+
}
|
|
4231
|
+
});
|
|
4232
|
+
|
|
4233
|
+
// src/templates/tools/parse-document-tool.ts
|
|
4234
|
+
var import_zod6, import_node_path6, import_officeparser, DEFAULT_LIMIT2, MAX_CONTENT_CHARS2, MIN_CHARS_PER_PAGE, OFFICE_EXTENSIONS, pagesPattern, createParseDocumentTool;
|
|
4235
|
+
var init_parse_document_tool = __esm({
|
|
4236
|
+
"src/templates/tools/parse-document-tool.ts"() {
|
|
4237
|
+
"use strict";
|
|
4238
|
+
init_cjs_shims();
|
|
4239
|
+
import_zod6 = require("zod");
|
|
4240
|
+
import_node_path6 = require("path");
|
|
4241
|
+
import_officeparser = require("officeparser");
|
|
4242
|
+
init_tool();
|
|
4243
|
+
init_uppy();
|
|
4244
|
+
init_document_render_helpers();
|
|
4245
|
+
DEFAULT_LIMIT2 = 250;
|
|
4246
|
+
MAX_CONTENT_CHARS2 = 16e3;
|
|
4247
|
+
MIN_CHARS_PER_PAGE = 20;
|
|
4248
|
+
OFFICE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4249
|
+
".docx",
|
|
4250
|
+
".doc",
|
|
4251
|
+
".xlsx",
|
|
4252
|
+
".xls",
|
|
4253
|
+
".pptx",
|
|
4254
|
+
".ppt",
|
|
4255
|
+
".odt",
|
|
4256
|
+
".ods",
|
|
4257
|
+
".odp",
|
|
4258
|
+
".rtf"
|
|
4259
|
+
]);
|
|
4260
|
+
pagesPattern = /^(\d+)(?:-(\d+))?$/;
|
|
4261
|
+
createParseDocumentTool = ({
|
|
4262
|
+
sessionID,
|
|
4263
|
+
user,
|
|
4264
|
+
exuluConfig
|
|
4265
|
+
}) => {
|
|
4266
|
+
if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
|
|
4267
|
+
const parseDocumentExecute = async ({
|
|
4268
|
+
filename,
|
|
4269
|
+
pages,
|
|
4270
|
+
offset,
|
|
4271
|
+
limit
|
|
4272
|
+
}) => {
|
|
4273
|
+
const safeName = String(filename ?? "").trim();
|
|
4274
|
+
if (!safeName || safeName.includes("..") || safeName.includes("/") || safeName.includes("\\")) {
|
|
4275
|
+
return {
|
|
4276
|
+
error: "Invalid filename \u2014 pass the bare file name exactly as listed in the session files (no paths)."
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4279
|
+
const ext = (0, import_node_path6.extname)(safeName).toLowerCase();
|
|
4280
|
+
if (ext !== ".pdf" && !OFFICE_EXTENSIONS.has(ext)) {
|
|
4281
|
+
return {
|
|
4282
|
+
error: `Unsupported extension "${ext}" \u2014 parse_document handles PDF and Office formats. For plain-text files use read_session_file.`
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
if (pages && ext !== ".pdf") {
|
|
4286
|
+
return { error: `The pages option is only supported for PDF files \u2014 "${ext}" documents are extracted whole.` };
|
|
4287
|
+
}
|
|
4288
|
+
const uploads = exuluConfig.fileUploads;
|
|
4289
|
+
const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
|
|
4290
|
+
const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
|
|
4291
|
+
try {
|
|
4292
|
+
const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
|
|
4293
|
+
const res = await fetch(url);
|
|
4294
|
+
if (!res.ok) {
|
|
4295
|
+
return { error: `Could not read session file "${safeName}" (status ${res.status}). Check the exact file name.` };
|
|
4296
|
+
}
|
|
4297
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
4298
|
+
let fullText;
|
|
4299
|
+
let totalPages;
|
|
4300
|
+
if (ext === ".pdf") {
|
|
4301
|
+
const raw = await pdfToText(bytes);
|
|
4302
|
+
const pageTexts = raw.replace(/\f$/, "").split("\f");
|
|
4303
|
+
totalPages = pageTexts.length;
|
|
4304
|
+
const nonWhitespace = raw.replace(/\s/g, "").length;
|
|
4305
|
+
if (nonWhitespace < totalPages * MIN_CHARS_PER_PAGE) {
|
|
4306
|
+
return {
|
|
4307
|
+
error: `"${safeName}" has no extractable text layer (likely a scan or image-based PDF). Use view_document_page to look at pages visually, or suggest the user add the document to a knowledge base with a document processor for full OCR.`
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
let range = [1, totalPages];
|
|
4311
|
+
if (pages) {
|
|
4312
|
+
const m = pagesPattern.exec(pages.trim());
|
|
4313
|
+
if (!m) return { error: `Invalid pages "${pages}" \u2014 use "3" or "2-5".` };
|
|
4314
|
+
range = [Number(m[1]), Number(m[2] ?? m[1])];
|
|
4315
|
+
if (range[0] < 1 || range[0] > range[1]) {
|
|
4316
|
+
return { error: `Invalid pages "${pages}" \u2014 start must be at least 1 and not greater than the end.` };
|
|
4317
|
+
}
|
|
4318
|
+
if (range[0] > totalPages) {
|
|
4319
|
+
return { error: `Page range starts at ${range[0]} but "${safeName}" has only ${totalPages} page${totalPages === 1 ? "" : "s"}.` };
|
|
4320
|
+
}
|
|
4321
|
+
}
|
|
4322
|
+
fullText = pageTexts.map((text, i) => ({ page: i + 1, text })).filter(({ page }) => page >= range[0] && page <= range[1]).map(({ page, text }) => `--- page ${page} ---
|
|
4323
|
+
${text.trim()}`).join("\n");
|
|
4324
|
+
} else {
|
|
4325
|
+
const extracted = await (0, import_officeparser.parseOfficeAsync)(bytes, {
|
|
4326
|
+
outputErrorToConsole: false,
|
|
4327
|
+
newlineDelimiter: "\n"
|
|
4328
|
+
});
|
|
4329
|
+
fullText = String(extracted);
|
|
4330
|
+
}
|
|
4331
|
+
const lines = fullText.split("\n");
|
|
4332
|
+
const start = (offset ?? 1) - 1;
|
|
4333
|
+
const requested = limit ?? DEFAULT_LIMIT2;
|
|
4334
|
+
const sliced = lines.slice(start, start + requested);
|
|
4335
|
+
let content = sliced.join("\n");
|
|
4336
|
+
let linesReturned = sliced.length;
|
|
4337
|
+
if (content.length > MAX_CONTENT_CHARS2) {
|
|
4338
|
+
content = content.slice(0, MAX_CONTENT_CHARS2);
|
|
4339
|
+
linesReturned = Math.max(1, content.split("\n").length - 1);
|
|
4340
|
+
content = content + "\n[slice truncated \u2014 request fewer lines]";
|
|
4341
|
+
}
|
|
4342
|
+
return {
|
|
4343
|
+
content,
|
|
4344
|
+
...totalPages !== void 0 ? { totalPages } : {},
|
|
4345
|
+
totalLines: lines.length,
|
|
4346
|
+
offset: start + 1,
|
|
4347
|
+
linesReturned
|
|
4348
|
+
};
|
|
4349
|
+
} catch (err) {
|
|
4350
|
+
return { error: `Failed to parse "${safeName}": ${err instanceof Error ? err.message : "unknown error"}` };
|
|
4351
|
+
}
|
|
4352
|
+
};
|
|
4353
|
+
return ExuluTool.internal({
|
|
4354
|
+
id: "parse_document",
|
|
4355
|
+
name: "parse_document",
|
|
4356
|
+
needsApproval: false,
|
|
4357
|
+
description: `Extract the text of an uploaded PDF or Office document from this session's files, with "--- page N ---" markers for PDFs so you can locate content by page. Free and fast (no OCR): works only on documents with a real text layer. To SEE a page or an image inside a document, use view_document_page.`,
|
|
4358
|
+
inputSchema: import_zod6.z.object({
|
|
4359
|
+
filename: import_zod6.z.string().describe('Exact session file name, e.g. "report.pdf"'),
|
|
4360
|
+
pages: import_zod6.z.string().optional().describe('PDF page or range to extract, e.g. "2" or "1-5" (default: all pages) (PDF only)'),
|
|
4361
|
+
offset: import_zod6.z.number().int().min(1).optional().describe("1-based first output line to read (default 1)"),
|
|
4362
|
+
limit: import_zod6.z.number().int().min(1).max(1e3).optional().describe(`Number of lines to read (default ${DEFAULT_LIMIT2})`)
|
|
4363
|
+
}),
|
|
4364
|
+
type: "function",
|
|
4365
|
+
category: "session",
|
|
4366
|
+
config: [],
|
|
4367
|
+
// Same shape mismatch as read_session_file / memory-tool: internal utility
|
|
4368
|
+
// tools return richer objects than ExuluTool's retrieval-flavored execute
|
|
4369
|
+
// type; the AI SDK passes the object through verbatim.
|
|
4370
|
+
execute: parseDocumentExecute
|
|
4371
|
+
});
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
});
|
|
4375
|
+
|
|
4376
|
+
// src/sessions/pdf-preview-cache.ts
|
|
4377
|
+
function sanitizeEtag(raw) {
|
|
4378
|
+
return raw.replace(/^"|"$/g, "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4379
|
+
}
|
|
4380
|
+
async function getPdfPreviewBytes(opts) {
|
|
4381
|
+
const { sourceKey, etag, config } = opts;
|
|
4382
|
+
const safeEtag = sanitizeEtag(etag);
|
|
4383
|
+
if (!safeEtag) {
|
|
4384
|
+
throw new PreviewRenderError(`Invalid ETag for ${sourceKey}`);
|
|
4385
|
+
}
|
|
4386
|
+
const cachedPath = (0, import_node_path7.join)(CACHE_ROOT, `${safeEtag}.pdf`);
|
|
4387
|
+
if ((0, import_node_fs6.existsSync)(cachedPath)) {
|
|
4388
|
+
return (0, import_promises3.readFile)(cachedPath);
|
|
4389
|
+
}
|
|
4390
|
+
const existing = inFlight.get(safeEtag);
|
|
4391
|
+
if (existing) return existing;
|
|
4392
|
+
const promise = (async () => {
|
|
4393
|
+
try {
|
|
4394
|
+
await (0, import_promises3.mkdir)(CACHE_IN, { recursive: true });
|
|
4395
|
+
await (0, import_promises3.mkdir)(CACHE_OUT, { recursive: true });
|
|
4396
|
+
const ext = ((0, import_node_path7.extname)(sourceKey) || ".docx").toLowerCase();
|
|
4397
|
+
const inputPath = (0, import_node_path7.join)(CACHE_IN, `${safeEtag}${ext}`);
|
|
4398
|
+
const outputPath = (0, import_node_path7.join)(CACHE_OUT, `${safeEtag}.pdf`);
|
|
4399
|
+
try {
|
|
4400
|
+
const bytes = await getS3ObjectBytes(sourceKey, config);
|
|
4401
|
+
await (0, import_promises3.writeFile)(inputPath, bytes);
|
|
4402
|
+
try {
|
|
4403
|
+
await execAsync3(
|
|
4404
|
+
`soffice --headless --convert-to pdf "${inputPath}" --outdir "${CACHE_OUT}"`,
|
|
4405
|
+
{ timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
|
|
4406
|
+
);
|
|
4407
|
+
} catch (err) {
|
|
4408
|
+
throw new PreviewRenderError(
|
|
4409
|
+
`LibreOffice conversion failed for ${sourceKey} (etag ${etag}): ${err?.stderr ?? err?.message ?? "unknown error"}`
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4412
|
+
if (!(0, import_node_fs6.existsSync)(outputPath)) {
|
|
4413
|
+
throw new PreviewRenderError(
|
|
4414
|
+
`LibreOffice produced no output for ${sourceKey} (etag ${etag})`
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
await (0, import_promises3.rename)(outputPath, cachedPath);
|
|
4418
|
+
return await (0, import_promises3.readFile)(cachedPath);
|
|
4419
|
+
} finally {
|
|
4420
|
+
await (0, import_promises3.rm)(inputPath, { force: true });
|
|
4421
|
+
}
|
|
4422
|
+
} finally {
|
|
4423
|
+
inFlight.delete(safeEtag);
|
|
4424
|
+
}
|
|
4425
|
+
})();
|
|
4426
|
+
inFlight.set(safeEtag, promise);
|
|
4427
|
+
return promise;
|
|
4428
|
+
}
|
|
4429
|
+
var import_node_child_process5, import_node_fs6, import_promises3, import_node_path7, import_node_util4, execAsync3, CACHE_ROOT, CACHE_IN, CACHE_OUT, inFlight, PreviewRenderError;
|
|
4430
|
+
var init_pdf_preview_cache = __esm({
|
|
4431
|
+
"src/sessions/pdf-preview-cache.ts"() {
|
|
4432
|
+
"use strict";
|
|
4433
|
+
init_cjs_shims();
|
|
4434
|
+
import_node_child_process5 = require("child_process");
|
|
4435
|
+
import_node_fs6 = require("fs");
|
|
4436
|
+
import_promises3 = require("fs/promises");
|
|
4437
|
+
import_node_path7 = require("path");
|
|
4438
|
+
import_node_util4 = require("util");
|
|
4439
|
+
init_uppy();
|
|
4440
|
+
execAsync3 = (0, import_node_util4.promisify)(import_node_child_process5.exec);
|
|
4441
|
+
CACHE_ROOT = "/tmp/exulu-pdf-cache";
|
|
4442
|
+
CACHE_IN = (0, import_node_path7.join)(CACHE_ROOT, "_in");
|
|
4443
|
+
CACHE_OUT = (0, import_node_path7.join)(CACHE_ROOT, "_out");
|
|
4444
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
4445
|
+
PreviewRenderError = class extends Error {
|
|
4446
|
+
constructor(message) {
|
|
4447
|
+
super(message);
|
|
4448
|
+
this.name = "PreviewRenderError";
|
|
4449
|
+
}
|
|
4450
|
+
};
|
|
4451
|
+
}
|
|
4452
|
+
});
|
|
4453
|
+
|
|
4454
|
+
// src/exulu/litellm/catalog.ts
|
|
4455
|
+
var catalog_exports = {};
|
|
4456
|
+
__export(catalog_exports, {
|
|
4457
|
+
__resetLiteLLMCatalogCacheForTesting: () => __resetLiteLLMCatalogCacheForTesting,
|
|
4458
|
+
fetchLiteLLMCatalog: () => fetchLiteLLMCatalog,
|
|
4459
|
+
findLiteLLMModel: () => findLiteLLMModel
|
|
4460
|
+
});
|
|
4461
|
+
var CACHE_TTL_MS, _cache, __resetLiteLLMCatalogCacheForTesting, fetchLiteLLMCatalog, findLiteLLMModel;
|
|
4462
|
+
var init_catalog = __esm({
|
|
4463
|
+
"src/exulu/litellm/catalog.ts"() {
|
|
4464
|
+
"use strict";
|
|
4465
|
+
init_cjs_shims();
|
|
4466
|
+
CACHE_TTL_MS = 3e4;
|
|
4467
|
+
__resetLiteLLMCatalogCacheForTesting = () => {
|
|
4468
|
+
_cache = void 0;
|
|
4469
|
+
};
|
|
4470
|
+
fetchLiteLLMCatalog = async () => {
|
|
4471
|
+
if (process.env.EXULU_USE_LITELLM !== "true") return [];
|
|
4472
|
+
if (_cache && _cache.expiresAt > Date.now()) {
|
|
4473
|
+
return _cache.items;
|
|
4474
|
+
}
|
|
4475
|
+
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
4476
|
+
const port = process.env.LITELLM_PORT ?? "4000";
|
|
4477
|
+
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
4478
|
+
if (!masterKey) return [];
|
|
4479
|
+
try {
|
|
4480
|
+
const res = await fetch(`http://${host}:${port}/model/info`, {
|
|
4481
|
+
method: "GET",
|
|
4482
|
+
headers: { Authorization: `Bearer ${masterKey}` }
|
|
4483
|
+
});
|
|
4484
|
+
if (!res.ok) {
|
|
4485
|
+
console.error(
|
|
4486
|
+
`[EXULU] litellmCatalog: LiteLLM /model/info returned ${res.status}`
|
|
4487
|
+
);
|
|
4488
|
+
return [];
|
|
4489
|
+
}
|
|
4490
|
+
const json = await res.json();
|
|
4491
|
+
const items = (Array.isArray(json?.data) ? json.data : []).map((m) => ({
|
|
4492
|
+
// filter out trailing * from model_name
|
|
4493
|
+
model_name: m.model_name.replace(/\*$/, ""),
|
|
4494
|
+
upstream_model: m.litellm_params?.model ?? null,
|
|
4495
|
+
tags: Array.isArray(m.model_info?.tags) ? m.model_info.tags : [],
|
|
4496
|
+
brand: m.model_info?.brand ?? null,
|
|
4497
|
+
type: m.model_info?.type ?? null,
|
|
4498
|
+
region: m.model_info?.region ?? null,
|
|
4499
|
+
max_tokens: m.model_info?.max_tokens ?? null,
|
|
4500
|
+
max_input_tokens: m.model_info?.max_input_tokens ?? null,
|
|
4501
|
+
input_cost_per_million_tokens: m.model_info?.input_cost_per_token * 1e6,
|
|
4502
|
+
output_cost_per_million_tokens: m.model_info?.output_cost_per_token * 1e6,
|
|
4503
|
+
active: m.model_info?.active ?? true,
|
|
4504
|
+
max_output_tokens: m.model_info?.max_output_tokens ?? null,
|
|
4505
|
+
supports_vision: !!m.model_info?.supports_vision,
|
|
4506
|
+
supports_function_calling: !!m.model_info?.supports_function_calling,
|
|
4507
|
+
supports_pdf_input: !!m.model_info?.supports_pdf_input,
|
|
4508
|
+
supports_audio_input: !!m.model_info?.supports_audio_input,
|
|
4509
|
+
sizes: Array.isArray(m.model_info?.sizes) ? m.model_info.sizes : null,
|
|
4510
|
+
qualities: Array.isArray(m.model_info?.qualities) ? m.model_info.qualities : null,
|
|
4511
|
+
supports_edit: !!m.model_info?.supports_edit,
|
|
4512
|
+
max_n: typeof m.model_info?.max_n === "number" ? m.model_info.max_n : null
|
|
4513
|
+
}));
|
|
4514
|
+
const map = /* @__PURE__ */ new Map();
|
|
4515
|
+
for (const item of items) {
|
|
4516
|
+
const key = `${item.model_name}-${item.upstream_model}`;
|
|
4517
|
+
if (map.has(key)) {
|
|
4518
|
+
map.get(key).tags.push(...item.tags);
|
|
4519
|
+
} else {
|
|
4520
|
+
map.set(key, item);
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
const uniqueItems = Array.from(map.values());
|
|
4524
|
+
_cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
|
|
4525
|
+
return uniqueItems.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
|
|
4526
|
+
} catch (err) {
|
|
4527
|
+
console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
|
|
4528
|
+
return [];
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
findLiteLLMModel = async (modelName) => {
|
|
4532
|
+
if (!modelName) return void 0;
|
|
4533
|
+
const items = await fetchLiteLLMCatalog();
|
|
4534
|
+
return items.find((m) => m.model_name === modelName);
|
|
4535
|
+
};
|
|
4536
|
+
}
|
|
4537
|
+
});
|
|
4538
|
+
|
|
4539
|
+
// src/exulu/tool-image-attachments.ts
|
|
4540
|
+
function sweep() {
|
|
4541
|
+
const cutoff = Date.now() - TTL_MS;
|
|
4542
|
+
for (const [id, entry] of stash) {
|
|
4543
|
+
if (entry.stashedAt < cutoff) stash.delete(id);
|
|
4544
|
+
}
|
|
4545
|
+
while (stash.size > MAX_ENTRIES) {
|
|
4546
|
+
const oldest = stash.keys().next().value;
|
|
4547
|
+
if (oldest === void 0) break;
|
|
4548
|
+
stash.delete(oldest);
|
|
4549
|
+
}
|
|
4550
|
+
let totalBytes = 0;
|
|
4551
|
+
for (const entry of stash.values()) totalBytes += entry.data.length;
|
|
4552
|
+
while (totalBytes > MAX_TOTAL_BYTES) {
|
|
4553
|
+
const oldest = stash.keys().next().value;
|
|
4554
|
+
if (oldest === void 0) break;
|
|
4555
|
+
totalBytes -= stash.get(oldest).data.length;
|
|
4556
|
+
stash.delete(oldest);
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
function stashToolImage(toolCallId, image) {
|
|
4560
|
+
stash.set(toolCallId, { ...image, stashedAt: Date.now() });
|
|
4561
|
+
sweep();
|
|
4562
|
+
}
|
|
4563
|
+
function stashedIdsInMessage(message) {
|
|
4564
|
+
if (message?.role !== "tool" || !Array.isArray(message.content)) return [];
|
|
4565
|
+
return message.content.filter(
|
|
4566
|
+
(p) => p?.type === "tool-result" && typeof p.toolCallId === "string" && p.toolName === STASH_TOOL_NAME && stash.has(p.toolCallId)
|
|
4567
|
+
).map((p) => p.toolCallId);
|
|
4568
|
+
}
|
|
4569
|
+
function injectedTextFor(id, label) {
|
|
4570
|
+
return `${INJECTED_IMAGE_PREFIX}${id}: ${label}]`;
|
|
4571
|
+
}
|
|
4572
|
+
function firstTextPart(message) {
|
|
4573
|
+
if (message?.role !== "user" || !Array.isArray(message.content)) return void 0;
|
|
4574
|
+
const first = message.content[0];
|
|
4575
|
+
return first?.type === "text" ? first.text : void 0;
|
|
4576
|
+
}
|
|
4577
|
+
function imageAttachmentGuard() {
|
|
4578
|
+
return ({ messages }) => {
|
|
4579
|
+
sweep();
|
|
4580
|
+
if (!Array.isArray(messages) || messages.length === 0 || stash.size === 0) return void 0;
|
|
4581
|
+
let changed = false;
|
|
4582
|
+
const next = [];
|
|
4583
|
+
for (let i = 0; i < messages.length; i++) {
|
|
4584
|
+
const message = messages[i];
|
|
4585
|
+
next.push(message);
|
|
4586
|
+
const ids = stashedIdsInMessage(message);
|
|
4587
|
+
if (ids.length === 0) continue;
|
|
4588
|
+
const alreadyInjected = [];
|
|
4589
|
+
for (let j = i + 1; j < messages.length; j++) {
|
|
4590
|
+
const text = firstTextPart(messages[j]);
|
|
4591
|
+
if (text === void 0 || !text.startsWith(INJECTED_IMAGE_PREFIX)) break;
|
|
4592
|
+
alreadyInjected.push(text);
|
|
4593
|
+
}
|
|
4594
|
+
for (const id of ids) {
|
|
4595
|
+
if (alreadyInjected.some((text) => text.startsWith(`${INJECTED_IMAGE_PREFIX}${id}:`))) continue;
|
|
4596
|
+
const image = stash.get(id);
|
|
4597
|
+
changed = true;
|
|
4598
|
+
next.push({
|
|
4599
|
+
role: "user",
|
|
4600
|
+
content: [
|
|
4601
|
+
{ type: "text", text: injectedTextFor(id, image.label) },
|
|
4602
|
+
{ type: "image", image: image.data, mediaType: image.mediaType }
|
|
4603
|
+
]
|
|
4604
|
+
});
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
return changed ? { messages: next } : void 0;
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
var INJECTED_IMAGE_PREFIX, stash, MAX_ENTRIES, TTL_MS, MAX_TOTAL_BYTES, STASH_TOOL_NAME;
|
|
4611
|
+
var init_tool_image_attachments = __esm({
|
|
4612
|
+
"src/exulu/tool-image-attachments.ts"() {
|
|
4613
|
+
"use strict";
|
|
4614
|
+
init_cjs_shims();
|
|
4615
|
+
INJECTED_IMAGE_PREFIX = "[Image attached from tool call ";
|
|
4616
|
+
stash = /* @__PURE__ */ new Map();
|
|
4617
|
+
MAX_ENTRIES = 100;
|
|
4618
|
+
TTL_MS = 30 * 60 * 1e3;
|
|
4619
|
+
MAX_TOTAL_BYTES = 1e8;
|
|
4620
|
+
STASH_TOOL_NAME = "view_document_page";
|
|
4621
|
+
}
|
|
4622
|
+
});
|
|
4623
|
+
|
|
4624
|
+
// src/templates/tools/view-document-page-tool.ts
|
|
4625
|
+
var import_zod7, import_node_path8, MAX_IMAGE_BYTES, SCALE_PRIMARY, SCALE_FALLBACK, IMAGE_MEDIA_TYPES, OFFICE_EXTENSIONS2, createViewDocumentPageTool;
|
|
4626
|
+
var init_view_document_page_tool = __esm({
|
|
4627
|
+
"src/templates/tools/view-document-page-tool.ts"() {
|
|
4628
|
+
"use strict";
|
|
4629
|
+
init_cjs_shims();
|
|
4630
|
+
import_zod7 = require("zod");
|
|
4631
|
+
import_node_path8 = require("path");
|
|
4632
|
+
init_tool();
|
|
4633
|
+
init_uppy();
|
|
4634
|
+
init_pdf_preview_cache();
|
|
4635
|
+
init_catalog();
|
|
4636
|
+
init_tool_image_attachments();
|
|
4637
|
+
init_document_render_helpers();
|
|
4638
|
+
MAX_IMAGE_BYTES = 375e4;
|
|
4639
|
+
SCALE_PRIMARY = 1568;
|
|
4640
|
+
SCALE_FALLBACK = 1024;
|
|
4641
|
+
IMAGE_MEDIA_TYPES = {
|
|
4642
|
+
".png": "image/png",
|
|
4643
|
+
".jpg": "image/jpeg",
|
|
4644
|
+
".jpeg": "image/jpeg",
|
|
4645
|
+
".gif": "image/gif",
|
|
4646
|
+
".webp": "image/webp"
|
|
4647
|
+
};
|
|
4648
|
+
OFFICE_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
4649
|
+
".docx",
|
|
4650
|
+
".doc",
|
|
4651
|
+
".xlsx",
|
|
4652
|
+
".xls",
|
|
4653
|
+
".pptx",
|
|
4654
|
+
".ppt",
|
|
4655
|
+
".odt",
|
|
4656
|
+
".ods",
|
|
4657
|
+
".odp",
|
|
4658
|
+
".rtf"
|
|
4659
|
+
]);
|
|
4660
|
+
createViewDocumentPageTool = ({
|
|
4661
|
+
sessionID,
|
|
4662
|
+
user,
|
|
4663
|
+
exuluConfig
|
|
4664
|
+
}) => {
|
|
4665
|
+
if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
|
|
4666
|
+
const viewDocumentPageExecute = async ({ filename, page, model }, options) => {
|
|
4667
|
+
const safeName = String(filename ?? "").trim();
|
|
4668
|
+
if (!safeName || safeName.includes("..") || safeName.includes("/") || safeName.includes("\\")) {
|
|
4669
|
+
return {
|
|
4670
|
+
error: "Invalid filename \u2014 pass the bare file name exactly as listed in the session files (no paths)."
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4673
|
+
const ext = (0, import_node_path8.extname)(safeName).toLowerCase();
|
|
4674
|
+
const isImage = ext in IMAGE_MEDIA_TYPES;
|
|
4675
|
+
const isPdf = ext === ".pdf";
|
|
4676
|
+
const isOffice = OFFICE_EXTENSIONS2.has(ext);
|
|
4677
|
+
if (!isImage && !isPdf && !isOffice) {
|
|
4678
|
+
return { error: `Unsupported extension "${ext}" \u2014 view_document_page handles PDF, Office, and image files.` };
|
|
4679
|
+
}
|
|
4680
|
+
const modelId = typeof model === "string" ? model : model?.modelId;
|
|
4681
|
+
if (!modelId) {
|
|
4682
|
+
console.warn("[EXULU] view_document_page: no model id available for vision gating \u2014 proceeding optimistically.");
|
|
4683
|
+
}
|
|
4684
|
+
if (modelId) {
|
|
4685
|
+
try {
|
|
4686
|
+
const entry = await findLiteLLMModel(modelId);
|
|
4687
|
+
if (entry && entry.supports_vision === false) {
|
|
4688
|
+
return {
|
|
4689
|
+
error: `The current model "${modelId}" does not support images, so this page cannot be shown to you. Use parse_document for the text, or tell the user a vision-capable model is required.`
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4692
|
+
} catch {
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
const uploads = exuluConfig.fileUploads;
|
|
4696
|
+
const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
|
|
4697
|
+
const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
|
|
4698
|
+
const pageNumber = page ?? 1;
|
|
4699
|
+
try {
|
|
4700
|
+
let imageBytes;
|
|
4701
|
+
let mediaType = "image/png";
|
|
4702
|
+
if (isImage) {
|
|
4703
|
+
imageBytes = await getS3ObjectBytes(key, exuluConfig);
|
|
4704
|
+
mediaType = IMAGE_MEDIA_TYPES[ext];
|
|
4705
|
+
if (imageBytes.length > MAX_IMAGE_BYTES) {
|
|
4706
|
+
return {
|
|
4707
|
+
error: `"${safeName}" is too large to attach (${imageBytes.length} bytes, max ${MAX_IMAGE_BYTES}). Ask the user for a smaller version of the image.`
|
|
4708
|
+
};
|
|
4709
|
+
}
|
|
4710
|
+
} else {
|
|
4711
|
+
let pdfBytes;
|
|
4712
|
+
if (isPdf) {
|
|
4713
|
+
pdfBytes = await getS3ObjectBytes(key, exuluConfig);
|
|
4714
|
+
} else {
|
|
4715
|
+
const etag = await getS3ObjectEtag(key, exuluConfig);
|
|
4716
|
+
if (!etag) {
|
|
4717
|
+
return { error: `Could not read session file "${safeName}". Check the exact file name.` };
|
|
4718
|
+
}
|
|
4719
|
+
pdfBytes = await getPdfPreviewBytes({ sourceKey: key, etag, config: exuluConfig });
|
|
4720
|
+
}
|
|
4721
|
+
let rendered = await renderPdfPageToPng(pdfBytes, pageNumber, SCALE_PRIMARY);
|
|
4722
|
+
if (!rendered) {
|
|
4723
|
+
return { error: `Could not render page ${pageNumber} of "${safeName}" \u2014 the document may have fewer pages.` };
|
|
4724
|
+
}
|
|
4725
|
+
if (rendered.length > MAX_IMAGE_BYTES) {
|
|
4726
|
+
rendered = await renderPdfPageToPng(pdfBytes, pageNumber, SCALE_FALLBACK);
|
|
4727
|
+
}
|
|
4728
|
+
if (!rendered || rendered.length > MAX_IMAGE_BYTES) {
|
|
4729
|
+
return { error: `Page ${pageNumber} of "${safeName}" is too complex to attach within the image size limit.` };
|
|
4730
|
+
}
|
|
4731
|
+
imageBytes = rendered;
|
|
4732
|
+
}
|
|
4733
|
+
if (!options?.toolCallId) {
|
|
4734
|
+
return { error: "Internal error: missing toolCallId \u2014 the image cannot be attached." };
|
|
4735
|
+
}
|
|
4736
|
+
stashToolImage(options.toolCallId, {
|
|
4737
|
+
data: imageBytes.toString("base64"),
|
|
4738
|
+
mediaType,
|
|
4739
|
+
label: isImage ? safeName : `${safeName} page ${pageNumber}`
|
|
4740
|
+
});
|
|
4741
|
+
return {
|
|
4742
|
+
attached: true,
|
|
4743
|
+
filename: safeName,
|
|
4744
|
+
page: pageNumber,
|
|
4745
|
+
note: "The rendered image follows this tool result as an attached user message \u2014 analyze it there. If no image message follows, the attachment has expired; call this tool again to re-render it."
|
|
4746
|
+
};
|
|
4747
|
+
} catch (err) {
|
|
4748
|
+
return { error: `Failed to render "${safeName}": ${err instanceof Error ? err.message : "unknown error"}` };
|
|
4749
|
+
}
|
|
4750
|
+
};
|
|
4751
|
+
return ExuluTool.internal({
|
|
4752
|
+
id: "view_document_page",
|
|
4753
|
+
name: "view_document_page",
|
|
4754
|
+
needsApproval: false,
|
|
4755
|
+
description: "LOOK at a page of an uploaded PDF/Office document, or at an uploaded image, from this session's files. The rendered image is attached as a user message directly after this tool result so you can visually analyze photos, charts, scans, and layouts. Use parse_document first to find which page you need. Requires a vision-capable model.",
|
|
4756
|
+
inputSchema: import_zod7.z.object({
|
|
4757
|
+
filename: import_zod7.z.string().describe('Exact session file name, e.g. "report.pdf" or "screenshot.png"'),
|
|
4758
|
+
page: import_zod7.z.number().int().min(1).optional().describe("Page number to render (default 1; ignored for image files)")
|
|
4759
|
+
}),
|
|
4760
|
+
type: "function",
|
|
4761
|
+
category: "session",
|
|
4762
|
+
config: [],
|
|
4763
|
+
// Same execute-shape cast as read_session_file / parse_document.
|
|
4764
|
+
execute: viewDocumentPageExecute
|
|
4765
|
+
});
|
|
4766
|
+
};
|
|
4767
|
+
}
|
|
4768
|
+
});
|
|
4769
|
+
|
|
4143
4770
|
// src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
|
|
4144
4771
|
var convert_exulu_tools_to_ai_sdk_tools_exports = {};
|
|
4145
4772
|
__export(convert_exulu_tools_to_ai_sdk_tools_exports, {
|
|
@@ -4168,6 +4795,8 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4168
4795
|
init_truncate_tool_output();
|
|
4169
4796
|
init_tool_output_offload();
|
|
4170
4797
|
init_session_file_read_tool();
|
|
4798
|
+
init_parse_document_tool();
|
|
4799
|
+
init_view_document_page_tool();
|
|
4171
4800
|
init_context_budget();
|
|
4172
4801
|
OUTPUT_OFFLOAD_EXEMPT_TOOL_IDS = /* @__PURE__ */ new Set(["agentic_context_search"]);
|
|
4173
4802
|
generateS3Key = (filename) => `${(0, import_node_crypto4.randomUUID)()}-${filename}`;
|
|
@@ -4348,6 +4977,14 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4348
4977
|
if (sessionFileReadTool && !disabled.has(sessionFileReadTool.id)) {
|
|
4349
4978
|
currentTools.push(sessionFileReadTool);
|
|
4350
4979
|
}
|
|
4980
|
+
const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig });
|
|
4981
|
+
if (parseDocumentTool && !disabled.has(parseDocumentTool.id)) {
|
|
4982
|
+
currentTools.push(parseDocumentTool);
|
|
4983
|
+
}
|
|
4984
|
+
const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig });
|
|
4985
|
+
if (viewDocumentPageTool && !disabled.has(viewDocumentPageTool.id)) {
|
|
4986
|
+
currentTools.push(viewDocumentPageTool);
|
|
4987
|
+
}
|
|
4351
4988
|
console.log("[EXULU] Creating agentic search tool", contexts?.length, model);
|
|
4352
4989
|
if (contexts?.length && model && !disabled.has("agentic_context_search")) {
|
|
4353
4990
|
const index = currentTools.findIndex((tool4) => tool4.id === "agentic_context_search");
|
|
@@ -4615,13 +5252,13 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
|
|
|
4615
5252
|
});
|
|
4616
5253
|
|
|
4617
5254
|
// src/exulu/tool.ts
|
|
4618
|
-
var import_ai3,
|
|
5255
|
+
var import_ai3, import_zod8, import_node_crypto5, PUBLIC_TOOL_TYPES, ExuluTool;
|
|
4619
5256
|
var init_tool = __esm({
|
|
4620
5257
|
"src/exulu/tool.ts"() {
|
|
4621
5258
|
"use strict";
|
|
4622
5259
|
init_cjs_shims();
|
|
4623
5260
|
import_ai3 = require("ai");
|
|
4624
|
-
|
|
5261
|
+
import_zod8 = require("zod");
|
|
4625
5262
|
init_sanitize_name();
|
|
4626
5263
|
import_node_crypto5 = require("crypto");
|
|
4627
5264
|
init_singleton();
|
|
@@ -4678,7 +5315,7 @@ var init_tool = __esm({
|
|
|
4678
5315
|
this.type = type;
|
|
4679
5316
|
this.tool = (0, import_ai3.tool)({
|
|
4680
5317
|
description,
|
|
4681
|
-
inputSchema: inputSchema ||
|
|
5318
|
+
inputSchema: inputSchema || import_zod8.z.object({}),
|
|
4682
5319
|
execute: oauth ? wrapExecuteWithOauth(id, oauth, execute2) : execute2
|
|
4683
5320
|
});
|
|
4684
5321
|
}
|
|
@@ -4971,62 +5608,62 @@ function effectiveKbSettings(profile, ctx) {
|
|
|
4971
5608
|
keywordPrefilter: preset.keywordPrefilter
|
|
4972
5609
|
};
|
|
4973
5610
|
}
|
|
4974
|
-
var
|
|
5611
|
+
var import_zod9, KB_KINDS, DEFAULT_PREFILTER_CUTOFF, RRF_K, CHUNK_GROUP_MAX, kbProfileSchema, knowledgeBasesSchema, routingRuleSchema, routingSchema, identifierSetSchema, vocabularySchema, memorySchema, tuningSchema, boolVal, strVal, KIND_PRESETS;
|
|
4975
5612
|
var init_config = __esm({
|
|
4976
5613
|
"ee/agentic-retrieval/pipeline/config.ts"() {
|
|
4977
5614
|
"use strict";
|
|
4978
5615
|
init_cjs_shims();
|
|
4979
|
-
|
|
5616
|
+
import_zod9 = require("zod");
|
|
4980
5617
|
KB_KINDS = ["documents", "conversations", "records"];
|
|
4981
5618
|
DEFAULT_PREFILTER_CUTOFF = 2.5;
|
|
4982
5619
|
RRF_K = 60;
|
|
4983
5620
|
CHUNK_GROUP_MAX = 10;
|
|
4984
|
-
kbProfileSchema =
|
|
4985
|
-
enabled:
|
|
4986
|
-
kind:
|
|
4987
|
-
instructions:
|
|
4988
|
-
overrides:
|
|
4989
|
-
limit:
|
|
4990
|
-
expand:
|
|
4991
|
-
multiQuery:
|
|
4992
|
-
hyde:
|
|
5621
|
+
kbProfileSchema = import_zod9.z.object({
|
|
5622
|
+
enabled: import_zod9.z.boolean().default(true),
|
|
5623
|
+
kind: import_zod9.z.enum(KB_KINDS).default("documents"),
|
|
5624
|
+
instructions: import_zod9.z.string().default(""),
|
|
5625
|
+
overrides: import_zod9.z.object({
|
|
5626
|
+
limit: import_zod9.z.number().int().positive().optional(),
|
|
5627
|
+
expand: import_zod9.z.number().int().min(0).optional(),
|
|
5628
|
+
multiQuery: import_zod9.z.boolean().optional(),
|
|
5629
|
+
hyde: import_zod9.z.boolean().optional()
|
|
4993
5630
|
}).default({})
|
|
4994
5631
|
});
|
|
4995
|
-
knowledgeBasesSchema =
|
|
4996
|
-
routingRuleSchema =
|
|
4997
|
-
id:
|
|
4998
|
-
label:
|
|
4999
|
-
description:
|
|
5000
|
-
main:
|
|
5001
|
-
fallback:
|
|
5632
|
+
knowledgeBasesSchema = import_zod9.z.record(import_zod9.z.string(), kbProfileSchema);
|
|
5633
|
+
routingRuleSchema = import_zod9.z.object({
|
|
5634
|
+
id: import_zod9.z.string(),
|
|
5635
|
+
label: import_zod9.z.string(),
|
|
5636
|
+
description: import_zod9.z.string(),
|
|
5637
|
+
main: import_zod9.z.array(import_zod9.z.string()),
|
|
5638
|
+
fallback: import_zod9.z.array(import_zod9.z.string()).default([])
|
|
5002
5639
|
});
|
|
5003
|
-
routingSchema =
|
|
5004
|
-
identifierSetSchema =
|
|
5005
|
-
name:
|
|
5006
|
-
description:
|
|
5007
|
-
examples:
|
|
5008
|
-
strategy:
|
|
5009
|
-
contexts:
|
|
5640
|
+
routingSchema = import_zod9.z.object({ rules: import_zod9.z.array(routingRuleSchema).default([]) });
|
|
5641
|
+
identifierSetSchema = import_zod9.z.object({
|
|
5642
|
+
name: import_zod9.z.string(),
|
|
5643
|
+
description: import_zod9.z.string().default(""),
|
|
5644
|
+
examples: import_zod9.z.array(import_zod9.z.string()).default([]),
|
|
5645
|
+
strategy: import_zod9.z.enum(["fuzzy", "exact"]),
|
|
5646
|
+
contexts: import_zod9.z.array(import_zod9.z.string()).default([])
|
|
5010
5647
|
});
|
|
5011
|
-
vocabularySchema =
|
|
5012
|
-
glossary:
|
|
5013
|
-
identifiers:
|
|
5014
|
-
rewrites:
|
|
5015
|
-
styleHint:
|
|
5648
|
+
vocabularySchema = import_zod9.z.object({
|
|
5649
|
+
glossary: import_zod9.z.array(import_zod9.z.object({ term: import_zod9.z.string(), meaning: import_zod9.z.string() })).default([]),
|
|
5650
|
+
identifiers: import_zod9.z.array(identifierSetSchema).default([]),
|
|
5651
|
+
rewrites: import_zod9.z.array(import_zod9.z.object({ find: import_zod9.z.string(), replace: import_zod9.z.string() })).default([]),
|
|
5652
|
+
styleHint: import_zod9.z.string().default("")
|
|
5016
5653
|
});
|
|
5017
|
-
memorySchema =
|
|
5018
|
-
enabled:
|
|
5019
|
-
override:
|
|
5020
|
-
filePrioritization:
|
|
5021
|
-
queryAugmentation:
|
|
5654
|
+
memorySchema = import_zod9.z.object({
|
|
5655
|
+
enabled: import_zod9.z.boolean().default(true),
|
|
5656
|
+
override: import_zod9.z.boolean().default(false),
|
|
5657
|
+
filePrioritization: import_zod9.z.boolean().default(false),
|
|
5658
|
+
queryAugmentation: import_zod9.z.boolean().default(true)
|
|
5022
5659
|
});
|
|
5023
|
-
tuningSchema =
|
|
5024
|
-
topK:
|
|
5025
|
-
fallbackThreshold:
|
|
5026
|
-
pinBoost:
|
|
5027
|
-
identifierBoost:
|
|
5028
|
-
pageWindow:
|
|
5029
|
-
maxQueriesPerContext:
|
|
5660
|
+
tuningSchema = import_zod9.z.object({
|
|
5661
|
+
topK: import_zod9.z.number().int().positive().default(5),
|
|
5662
|
+
fallbackThreshold: import_zod9.z.number().min(0).max(1).default(0.95),
|
|
5663
|
+
pinBoost: import_zod9.z.number().min(0).max(1).default(0.15),
|
|
5664
|
+
identifierBoost: import_zod9.z.number().min(0).max(1).default(0.15),
|
|
5665
|
+
pageWindow: import_zod9.z.number().int().min(0).default(1),
|
|
5666
|
+
maxQueriesPerContext: import_zod9.z.number().int().positive().default(5)
|
|
5030
5667
|
});
|
|
5031
5668
|
boolVal = (v) => v === true || v === "true" || v === 1;
|
|
5032
5669
|
strVal = (v, fallback) => typeof v === "string" && v.length > 0 ? v : fallback;
|
|
@@ -5273,9 +5910,9 @@ async function resolveIdentifierPins({
|
|
|
5273
5910
|
system: set.strategy === "exact" ? EXACT_EXTRACTION_PROMPT(set) : FUZZY_EXTRACTION_PROMPT(set),
|
|
5274
5911
|
messages: [{ role: "user", content: question }],
|
|
5275
5912
|
output: import_ai4.Output.object({
|
|
5276
|
-
schema:
|
|
5277
|
-
hasMatches:
|
|
5278
|
-
matches:
|
|
5913
|
+
schema: import_zod10.z.object({
|
|
5914
|
+
hasMatches: import_zod10.z.boolean(),
|
|
5915
|
+
matches: import_zod10.z.array(import_zod10.z.string()).optional()
|
|
5279
5916
|
})
|
|
5280
5917
|
}),
|
|
5281
5918
|
maxOutputTokens: 300
|
|
@@ -5323,14 +5960,14 @@ async function resolveIdentifierPins({
|
|
|
5323
5960
|
);
|
|
5324
5961
|
return { pinsByContext, exactPinsByContext, steps };
|
|
5325
5962
|
}
|
|
5326
|
-
var import_fuse, import_ai4,
|
|
5963
|
+
var import_fuse, import_ai4, import_zod10, itemCaches, ensureItemsCache, FUZZY_EXTRACTION_PROMPT, EXACT_EXTRACTION_PROMPT;
|
|
5327
5964
|
var init_prefilter = __esm({
|
|
5328
5965
|
"ee/agentic-retrieval/pipeline/prefilter.ts"() {
|
|
5329
5966
|
"use strict";
|
|
5330
5967
|
init_cjs_shims();
|
|
5331
5968
|
import_fuse = __toESM(require("fuse.js"), 1);
|
|
5332
5969
|
import_ai4 = require("ai");
|
|
5333
|
-
|
|
5970
|
+
import_zod10 = require("zod");
|
|
5334
5971
|
init_with_retry();
|
|
5335
5972
|
init_text_utils();
|
|
5336
5973
|
init_config();
|
|
@@ -5415,11 +6052,11 @@ If explicit, return the knowledge base ids. If not, return an empty array.`;
|
|
|
5415
6052
|
system: buildDocPagePrompt(knownIdentifiers),
|
|
5416
6053
|
messages: [{ role: "user", content: question }],
|
|
5417
6054
|
output: import_ai5.Output.object({
|
|
5418
|
-
schema:
|
|
5419
|
-
hasFilenameHint:
|
|
5420
|
-
filenameHints:
|
|
5421
|
-
hasPageHint:
|
|
5422
|
-
pageNumber:
|
|
6055
|
+
schema: import_zod11.z.object({
|
|
6056
|
+
hasFilenameHint: import_zod11.z.boolean(),
|
|
6057
|
+
filenameHints: import_zod11.z.array(import_zod11.z.string()).optional(),
|
|
6058
|
+
hasPageHint: import_zod11.z.boolean(),
|
|
6059
|
+
pageNumber: import_zod11.z.number().int().nullable().optional()
|
|
5423
6060
|
})
|
|
5424
6061
|
}),
|
|
5425
6062
|
maxOutputTokens: 300
|
|
@@ -5446,9 +6083,9 @@ If explicit, return the knowledge base ids. If not, return an empty array.`;
|
|
|
5446
6083
|
temperature: 0,
|
|
5447
6084
|
system: kbSystemPrompt,
|
|
5448
6085
|
output: import_ai5.Output.object({
|
|
5449
|
-
schema:
|
|
5450
|
-
explicitlyRequestedKnowledgeBases:
|
|
5451
|
-
|
|
6086
|
+
schema: import_zod11.z.object({
|
|
6087
|
+
explicitlyRequestedKnowledgeBases: import_zod11.z.array(
|
|
6088
|
+
import_zod11.z.enum(enabledContexts.map((c) => c.id))
|
|
5452
6089
|
)
|
|
5453
6090
|
})
|
|
5454
6091
|
}),
|
|
@@ -5546,9 +6183,9 @@ ${extraInstructions}
|
|
|
5546
6183
|
system: classifyPrompt,
|
|
5547
6184
|
messages: [{ role: "user", content: question }],
|
|
5548
6185
|
output: import_ai5.Output.object({
|
|
5549
|
-
schema:
|
|
5550
|
-
ruleId:
|
|
5551
|
-
reason:
|
|
6186
|
+
schema: import_zod11.z.object({
|
|
6187
|
+
ruleId: import_zod11.z.enum(ruleIds),
|
|
6188
|
+
reason: import_zod11.z.string()
|
|
5552
6189
|
})
|
|
5553
6190
|
}),
|
|
5554
6191
|
maxOutputTokens: 200
|
|
@@ -5607,13 +6244,13 @@ ${extraInstructions}
|
|
|
5607
6244
|
};
|
|
5608
6245
|
}
|
|
5609
6246
|
}
|
|
5610
|
-
var import_ai5,
|
|
6247
|
+
var import_ai5, import_zod11, MAX_USER_PIN_MATCHES, buildDocPagePrompt;
|
|
5611
6248
|
var init_routing = __esm({
|
|
5612
6249
|
"ee/agentic-retrieval/pipeline/routing.ts"() {
|
|
5613
6250
|
"use strict";
|
|
5614
6251
|
init_cjs_shims();
|
|
5615
6252
|
import_ai5 = require("ai");
|
|
5616
|
-
|
|
6253
|
+
import_zod11 = require("zod");
|
|
5617
6254
|
init_with_retry();
|
|
5618
6255
|
init_prefilter();
|
|
5619
6256
|
init_text_utils();
|
|
@@ -5885,8 +6522,8 @@ async function runMemoryPhase({
|
|
|
5885
6522
|
}
|
|
5886
6523
|
],
|
|
5887
6524
|
output: import_ai6.Output.object({
|
|
5888
|
-
schema:
|
|
5889
|
-
relevantChunkIds:
|
|
6525
|
+
schema: import_zod12.z.object({
|
|
6526
|
+
relevantChunkIds: import_zod12.z.array(import_zod12.z.string()).describe(
|
|
5890
6527
|
"The chunk_ids (UUIDs at the start of each bullet) of chunks containing information relevant to the user's question. Empty array if none are relevant."
|
|
5891
6528
|
)
|
|
5892
6529
|
})
|
|
@@ -6002,17 +6639,17 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6002
6639
|
}
|
|
6003
6640
|
],
|
|
6004
6641
|
output: import_ai6.Output.object({
|
|
6005
|
-
schema:
|
|
6006
|
-
overrides:
|
|
6642
|
+
schema: import_zod12.z.object({
|
|
6643
|
+
overrides: import_zod12.z.boolean().describe(
|
|
6007
6644
|
"True ONLY if a memory chunk directly and sufficiently answers the user's question and should be authoritative over the documents. Be strict; when unsure, false."
|
|
6008
6645
|
),
|
|
6009
|
-
confidence:
|
|
6646
|
+
confidence: import_zod12.z.enum(["high", "medium", "low"]).describe(
|
|
6010
6647
|
"Confidence that the selected memory chunk(s) fully and directly answer the question."
|
|
6011
6648
|
),
|
|
6012
|
-
authoritativeChunkIds:
|
|
6649
|
+
authoritativeChunkIds: import_zod12.z.array(import_zod12.z.string()).describe(
|
|
6013
6650
|
"The chunk_ids of the memory chunk(s) that directly answer the question. Empty if overrides is false."
|
|
6014
6651
|
),
|
|
6015
|
-
reason:
|
|
6652
|
+
reason: import_zod12.z.string().describe(
|
|
6016
6653
|
"One short sentence: why this memory does or does not directly answer the question."
|
|
6017
6654
|
)
|
|
6018
6655
|
})
|
|
@@ -6043,9 +6680,9 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6043
6680
|
system: "You are a helpful assistant that will strictly follow the user's instructions.",
|
|
6044
6681
|
messages: [{ role: "user", content: PROMPT_EXTRACT_PRIORITIZED_FILES }],
|
|
6045
6682
|
output: import_ai6.Output.object({
|
|
6046
|
-
schema:
|
|
6047
|
-
shouldPrioritizeFiles:
|
|
6048
|
-
fileNameHints:
|
|
6683
|
+
schema: import_zod12.z.object({
|
|
6684
|
+
shouldPrioritizeFiles: import_zod12.z.boolean(),
|
|
6685
|
+
fileNameHints: import_zod12.z.array(import_zod12.z.string()).optional()
|
|
6049
6686
|
})
|
|
6050
6687
|
}),
|
|
6051
6688
|
maxOutputTokens: 300
|
|
@@ -6064,10 +6701,10 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6064
6701
|
system: "You are a helpful assistant that will strictly follow the user's instructions.",
|
|
6065
6702
|
messages: [{ role: "user", content: QUERY_AUGMENTATION_PROMPT }],
|
|
6066
6703
|
output: import_ai6.Output.object({
|
|
6067
|
-
schema:
|
|
6068
|
-
updatedUserQuestion:
|
|
6069
|
-
updatedRelevantKeywords:
|
|
6070
|
-
updatedImportantKeyword:
|
|
6704
|
+
schema: import_zod12.z.object({
|
|
6705
|
+
updatedUserQuestion: import_zod12.z.string(),
|
|
6706
|
+
updatedRelevantKeywords: import_zod12.z.array(import_zod12.z.string()),
|
|
6707
|
+
updatedImportantKeyword: import_zod12.z.string()
|
|
6071
6708
|
})
|
|
6072
6709
|
}),
|
|
6073
6710
|
maxOutputTokens: 600
|
|
@@ -6153,13 +6790,13 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
|
|
|
6153
6790
|
return neutralResult(question, keywords, importantKeyword);
|
|
6154
6791
|
}
|
|
6155
6792
|
}
|
|
6156
|
-
var import_ai6,
|
|
6793
|
+
var import_ai6, import_zod12, MEMORY_OVERRIDE_MIN_CONFIDENCE, MEMORY_SYNTHETIC_RERANK_SCORE, ITEM_CACHE_TTL_MS, memoryItemCache;
|
|
6157
6794
|
var init_memory = __esm({
|
|
6158
6795
|
"ee/agentic-retrieval/pipeline/memory.ts"() {
|
|
6159
6796
|
"use strict";
|
|
6160
6797
|
init_cjs_shims();
|
|
6161
6798
|
import_ai6 = require("ai");
|
|
6162
|
-
|
|
6799
|
+
import_zod12 = require("zod");
|
|
6163
6800
|
init_with_retry();
|
|
6164
6801
|
init_multi_query();
|
|
6165
6802
|
init_prefilter();
|
|
@@ -6655,11 +7292,11 @@ function createAgenticRetrievalTool(opts) {
|
|
|
6655
7292
|
default: '{"topK":5,"fallbackThreshold":0.95,"pinBoost":0.15,"identifierBoost":0.15,"pageWindow":1,"maxQueriesPerContext":5}'
|
|
6656
7293
|
}
|
|
6657
7294
|
],
|
|
6658
|
-
inputSchema:
|
|
6659
|
-
userQuery:
|
|
6660
|
-
relevantKeywords:
|
|
6661
|
-
importantKeyword:
|
|
6662
|
-
confirmedContextIds:
|
|
7295
|
+
inputSchema: import_zod13.z.object({
|
|
7296
|
+
userQuery: import_zod13.z.string().describe("The original unaltered question from the user"),
|
|
7297
|
+
relevantKeywords: import_zod13.z.array(import_zod13.z.string()).describe("Keywords extracted from the user's question relevant to the search"),
|
|
7298
|
+
importantKeyword: import_zod13.z.string().describe("The single most important keyword from the user's question"),
|
|
7299
|
+
confirmedContextIds: import_zod13.z.array(import_zod13.z.string()).optional().describe(
|
|
6663
7300
|
"Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. When present, only searches these contexts."
|
|
6664
7301
|
)
|
|
6665
7302
|
}),
|
|
@@ -7050,12 +7687,12 @@ Verified answer:
|
|
|
7050
7687
|
}
|
|
7051
7688
|
});
|
|
7052
7689
|
}
|
|
7053
|
-
var
|
|
7690
|
+
var import_zod13;
|
|
7054
7691
|
var init_pipeline = __esm({
|
|
7055
7692
|
"ee/agentic-retrieval/pipeline/index.ts"() {
|
|
7056
7693
|
"use strict";
|
|
7057
7694
|
init_cjs_shims();
|
|
7058
|
-
|
|
7695
|
+
import_zod13 = require("zod");
|
|
7059
7696
|
init_tool();
|
|
7060
7697
|
init_entitlements();
|
|
7061
7698
|
init_resolve_reranker();
|
|
@@ -7073,91 +7710,6 @@ var init_pipeline = __esm({
|
|
|
7073
7710
|
}
|
|
7074
7711
|
});
|
|
7075
7712
|
|
|
7076
|
-
// src/exulu/litellm/catalog.ts
|
|
7077
|
-
var catalog_exports = {};
|
|
7078
|
-
__export(catalog_exports, {
|
|
7079
|
-
__resetLiteLLMCatalogCacheForTesting: () => __resetLiteLLMCatalogCacheForTesting,
|
|
7080
|
-
fetchLiteLLMCatalog: () => fetchLiteLLMCatalog,
|
|
7081
|
-
findLiteLLMModel: () => findLiteLLMModel
|
|
7082
|
-
});
|
|
7083
|
-
var CACHE_TTL_MS, _cache, __resetLiteLLMCatalogCacheForTesting, fetchLiteLLMCatalog, findLiteLLMModel;
|
|
7084
|
-
var init_catalog = __esm({
|
|
7085
|
-
"src/exulu/litellm/catalog.ts"() {
|
|
7086
|
-
"use strict";
|
|
7087
|
-
init_cjs_shims();
|
|
7088
|
-
CACHE_TTL_MS = 3e4;
|
|
7089
|
-
__resetLiteLLMCatalogCacheForTesting = () => {
|
|
7090
|
-
_cache = void 0;
|
|
7091
|
-
};
|
|
7092
|
-
fetchLiteLLMCatalog = async () => {
|
|
7093
|
-
if (process.env.EXULU_USE_LITELLM !== "true") return [];
|
|
7094
|
-
if (_cache && _cache.expiresAt > Date.now()) {
|
|
7095
|
-
return _cache.items;
|
|
7096
|
-
}
|
|
7097
|
-
const host = process.env.LITELLM_HOST ?? "127.0.0.1";
|
|
7098
|
-
const port = process.env.LITELLM_PORT ?? "4000";
|
|
7099
|
-
const masterKey = process.env.LITELLM_MASTER_KEY;
|
|
7100
|
-
if (!masterKey) return [];
|
|
7101
|
-
try {
|
|
7102
|
-
const res = await fetch(`http://${host}:${port}/model/info`, {
|
|
7103
|
-
method: "GET",
|
|
7104
|
-
headers: { Authorization: `Bearer ${masterKey}` }
|
|
7105
|
-
});
|
|
7106
|
-
if (!res.ok) {
|
|
7107
|
-
console.error(
|
|
7108
|
-
`[EXULU] litellmCatalog: LiteLLM /model/info returned ${res.status}`
|
|
7109
|
-
);
|
|
7110
|
-
return [];
|
|
7111
|
-
}
|
|
7112
|
-
const json = await res.json();
|
|
7113
|
-
const items = (Array.isArray(json?.data) ? json.data : []).map((m) => ({
|
|
7114
|
-
// filter out trailing * from model_name
|
|
7115
|
-
model_name: m.model_name.replace(/\*$/, ""),
|
|
7116
|
-
upstream_model: m.litellm_params?.model ?? null,
|
|
7117
|
-
tags: Array.isArray(m.model_info?.tags) ? m.model_info.tags : [],
|
|
7118
|
-
brand: m.model_info?.brand ?? null,
|
|
7119
|
-
type: m.model_info?.type ?? null,
|
|
7120
|
-
region: m.model_info?.region ?? null,
|
|
7121
|
-
max_tokens: m.model_info?.max_tokens ?? null,
|
|
7122
|
-
max_input_tokens: m.model_info?.max_input_tokens ?? null,
|
|
7123
|
-
input_cost_per_million_tokens: m.model_info?.input_cost_per_token * 1e6,
|
|
7124
|
-
output_cost_per_million_tokens: m.model_info?.output_cost_per_token * 1e6,
|
|
7125
|
-
active: m.model_info?.active ?? true,
|
|
7126
|
-
max_output_tokens: m.model_info?.max_output_tokens ?? null,
|
|
7127
|
-
supports_vision: !!m.model_info?.supports_vision,
|
|
7128
|
-
supports_function_calling: !!m.model_info?.supports_function_calling,
|
|
7129
|
-
supports_pdf_input: !!m.model_info?.supports_pdf_input,
|
|
7130
|
-
supports_audio_input: !!m.model_info?.supports_audio_input,
|
|
7131
|
-
sizes: Array.isArray(m.model_info?.sizes) ? m.model_info.sizes : null,
|
|
7132
|
-
qualities: Array.isArray(m.model_info?.qualities) ? m.model_info.qualities : null,
|
|
7133
|
-
supports_edit: !!m.model_info?.supports_edit,
|
|
7134
|
-
max_n: typeof m.model_info?.max_n === "number" ? m.model_info.max_n : null
|
|
7135
|
-
}));
|
|
7136
|
-
const map = /* @__PURE__ */ new Map();
|
|
7137
|
-
for (const item of items) {
|
|
7138
|
-
const key = `${item.model_name}-${item.upstream_model}`;
|
|
7139
|
-
if (map.has(key)) {
|
|
7140
|
-
map.get(key).tags.push(...item.tags);
|
|
7141
|
-
} else {
|
|
7142
|
-
map.set(key, item);
|
|
7143
|
-
}
|
|
7144
|
-
}
|
|
7145
|
-
const uniqueItems = Array.from(map.values());
|
|
7146
|
-
_cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
|
|
7147
|
-
return uniqueItems.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
|
|
7148
|
-
} catch (err) {
|
|
7149
|
-
console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
|
|
7150
|
-
return [];
|
|
7151
|
-
}
|
|
7152
|
-
};
|
|
7153
|
-
findLiteLLMModel = async (modelName) => {
|
|
7154
|
-
if (!modelName) return void 0;
|
|
7155
|
-
const items = await fetchLiteLLMCatalog();
|
|
7156
|
-
return items.find((m) => m.model_name === modelName);
|
|
7157
|
-
};
|
|
7158
|
-
}
|
|
7159
|
-
});
|
|
7160
|
-
|
|
7161
7713
|
// src/index.ts
|
|
7162
7714
|
var index_exports = {};
|
|
7163
7715
|
__export(index_exports, {
|
|
@@ -18415,28 +18967,14 @@ var BundleValidationError = class extends Error {
|
|
|
18415
18967
|
function isUnsafePath(path2) {
|
|
18416
18968
|
if (!path2) return true;
|
|
18417
18969
|
if (path2.startsWith("/")) return true;
|
|
18418
|
-
return path2.split("/").some((segment) => segment === "..");
|
|
18419
|
-
}
|
|
18420
|
-
function isOsJunkPath(path2) {
|
|
18421
|
-
if (path2.startsWith("__MACOSX/")) return true;
|
|
18422
|
-
const basename = path2.split("/").pop() ?? "";
|
|
18423
|
-
return basename === ".DS_Store" || basename === "Thumbs.db" || basename === "desktop.ini";
|
|
18424
|
-
}
|
|
18425
|
-
async function
|
|
18426
|
-
const { bytes, skillId, isZip, config } = opts;
|
|
18427
|
-
if (!isZip) {
|
|
18428
|
-
await uploadFile(
|
|
18429
|
-
bytes,
|
|
18430
|
-
`skills/${skillId}/v1/SKILL.md`,
|
|
18431
|
-
config,
|
|
18432
|
-
{ contentType: "text/markdown" },
|
|
18433
|
-
void 0,
|
|
18434
|
-
void 0,
|
|
18435
|
-
true
|
|
18436
|
-
// global=true so the key isn't user-prefixed (skill files are shared)
|
|
18437
|
-
);
|
|
18438
|
-
return { filesCount: 1 };
|
|
18439
|
-
}
|
|
18970
|
+
return path2.split("/").some((segment) => segment === "..");
|
|
18971
|
+
}
|
|
18972
|
+
function isOsJunkPath(path2) {
|
|
18973
|
+
if (path2.startsWith("__MACOSX/")) return true;
|
|
18974
|
+
const basename = path2.split("/").pop() ?? "";
|
|
18975
|
+
return basename === ".DS_Store" || basename === "Thumbs.db" || basename === "desktop.ini";
|
|
18976
|
+
}
|
|
18977
|
+
async function extractZipToPrefix(bytes, prefix, config) {
|
|
18440
18978
|
let zip;
|
|
18441
18979
|
try {
|
|
18442
18980
|
zip = await import_jszip.default.loadAsync(bytes);
|
|
@@ -18500,7 +19038,7 @@ async function extractBundleToS3(opts) {
|
|
|
18500
19038
|
}
|
|
18501
19039
|
let filesCount = 0;
|
|
18502
19040
|
for (const { relPath, content } of prepared) {
|
|
18503
|
-
const s3Key =
|
|
19041
|
+
const s3Key = `${prefix}${relPath}`;
|
|
18504
19042
|
await uploadFile(
|
|
18505
19043
|
content,
|
|
18506
19044
|
s3Key,
|
|
@@ -18509,86 +19047,80 @@ async function extractBundleToS3(opts) {
|
|
|
18509
19047
|
void 0,
|
|
18510
19048
|
void 0,
|
|
18511
19049
|
true
|
|
18512
|
-
// global=true —
|
|
19050
|
+
// global=true — skill files are shared across users
|
|
18513
19051
|
);
|
|
18514
19052
|
filesCount += 1;
|
|
18515
19053
|
}
|
|
18516
19054
|
return { filesCount };
|
|
18517
19055
|
}
|
|
19056
|
+
async function extractBundleToS3(opts) {
|
|
19057
|
+
const { bytes, skillId, isZip, config } = opts;
|
|
19058
|
+
if (!isZip) {
|
|
19059
|
+
await uploadFile(
|
|
19060
|
+
bytes,
|
|
19061
|
+
`skills/${skillId}/v1/SKILL.md`,
|
|
19062
|
+
config,
|
|
19063
|
+
{ contentType: "text/markdown" },
|
|
19064
|
+
void 0,
|
|
19065
|
+
void 0,
|
|
19066
|
+
true
|
|
19067
|
+
// global=true so the key isn't user-prefixed (skill files are shared)
|
|
19068
|
+
);
|
|
19069
|
+
return { filesCount: 1 };
|
|
19070
|
+
}
|
|
19071
|
+
return extractZipToPrefix(bytes, `skills/${skillId}/v1/`, config);
|
|
19072
|
+
}
|
|
19073
|
+
async function extractBundleToVersion(opts) {
|
|
19074
|
+
const { bytes, skillId, version, config } = opts;
|
|
19075
|
+
return extractZipToPrefix(bytes, `skills/${skillId}/v${version}/`, config);
|
|
19076
|
+
}
|
|
18518
19077
|
|
|
18519
|
-
// src/
|
|
19078
|
+
// src/skills/frontmatter.ts
|
|
18520
19079
|
init_cjs_shims();
|
|
18521
|
-
var
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
19080
|
+
var import_jszip2 = __toESM(require("jszip"), 1);
|
|
19081
|
+
function parseFrontmatter(md) {
|
|
19082
|
+
const match = /^?---\r?\n([\s\S]*?)\r?\n---/.exec(md);
|
|
19083
|
+
if (!match) return {};
|
|
19084
|
+
const block = match[1];
|
|
19085
|
+
const out = {};
|
|
19086
|
+
for (const line of block.split(/\r?\n/)) {
|
|
19087
|
+
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
19088
|
+
if (!m) continue;
|
|
19089
|
+
const key = m[1];
|
|
19090
|
+
let v = m[2].trim();
|
|
19091
|
+
if (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'")) {
|
|
19092
|
+
v = v.slice(1, -1);
|
|
19093
|
+
}
|
|
19094
|
+
out[key] = v;
|
|
18536
19095
|
}
|
|
18537
|
-
|
|
18538
|
-
function sanitizeEtag(raw) {
|
|
18539
|
-
return raw.replace(/^"|"$/g, "").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
19096
|
+
return out;
|
|
18540
19097
|
}
|
|
18541
|
-
async function
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
const cachedPath = (0, import_node_path5.join)(CACHE_ROOT, `${safeEtag}.pdf`);
|
|
18548
|
-
if ((0, import_node_fs6.existsSync)(cachedPath)) {
|
|
18549
|
-
return (0, import_promises2.readFile)(cachedPath);
|
|
19098
|
+
async function parseSkillFrontmatter(zipBytes) {
|
|
19099
|
+
let zip;
|
|
19100
|
+
try {
|
|
19101
|
+
zip = await import_jszip2.default.loadAsync(zipBytes);
|
|
19102
|
+
} catch {
|
|
19103
|
+
return {};
|
|
18550
19104
|
}
|
|
18551
|
-
const
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
|
|
18558
|
-
|
|
18559
|
-
|
|
18560
|
-
|
|
18561
|
-
|
|
18562
|
-
|
|
18563
|
-
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
{ timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
|
|
18567
|
-
);
|
|
18568
|
-
} catch (err) {
|
|
18569
|
-
throw new PreviewRenderError(
|
|
18570
|
-
`LibreOffice conversion failed for ${sourceKey} (etag ${etag}): ${err?.stderr ?? err?.message ?? "unknown error"}`
|
|
18571
|
-
);
|
|
18572
|
-
}
|
|
18573
|
-
if (!(0, import_node_fs6.existsSync)(outputPath)) {
|
|
18574
|
-
throw new PreviewRenderError(
|
|
18575
|
-
`LibreOffice produced no output for ${sourceKey} (etag ${etag})`
|
|
18576
|
-
);
|
|
18577
|
-
}
|
|
18578
|
-
await (0, import_promises2.rename)(outputPath, cachedPath);
|
|
18579
|
-
return await (0, import_promises2.readFile)(cachedPath);
|
|
18580
|
-
} finally {
|
|
18581
|
-
await (0, import_promises2.rm)(inputPath, { force: true });
|
|
18582
|
-
}
|
|
18583
|
-
} finally {
|
|
18584
|
-
inFlight.delete(safeEtag);
|
|
18585
|
-
}
|
|
18586
|
-
})();
|
|
18587
|
-
inFlight.set(safeEtag, promise);
|
|
18588
|
-
return promise;
|
|
19105
|
+
const paths = [];
|
|
19106
|
+
zip.forEach((p, entry) => {
|
|
19107
|
+
if (!entry.dir) paths.push(p);
|
|
19108
|
+
});
|
|
19109
|
+
const heads = new Set(paths.map((p) => p.split("/")[0]).filter(Boolean));
|
|
19110
|
+
let strip = (p) => p;
|
|
19111
|
+
if (heads.size === 1) {
|
|
19112
|
+
const head = [...heads][0] + "/";
|
|
19113
|
+
if (paths.every((p) => p.startsWith(head))) strip = (p) => p.slice(head.length);
|
|
19114
|
+
}
|
|
19115
|
+
const skillPath = paths.find((p) => strip(p) === "SKILL.md");
|
|
19116
|
+
if (!skillPath) return {};
|
|
19117
|
+
const md = await zip.file(skillPath).async("string");
|
|
19118
|
+
const fm = parseFrontmatter(md);
|
|
19119
|
+
return { name: fm.name, description: fm.description };
|
|
18589
19120
|
}
|
|
18590
19121
|
|
|
18591
19122
|
// src/exulu/routes.ts
|
|
19123
|
+
init_pdf_preview_cache();
|
|
18592
19124
|
init_create_sandbox();
|
|
18593
19125
|
var import_utils5 = require("@apollo/utils.keyvaluecache");
|
|
18594
19126
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
@@ -18598,7 +19130,7 @@ var import_fs3 = __toESM(require("fs"), 1);
|
|
|
18598
19130
|
var import_node_crypto9 = require("crypto");
|
|
18599
19131
|
var import_api2 = require("@opentelemetry/api");
|
|
18600
19132
|
init_check_record_access();
|
|
18601
|
-
var
|
|
19133
|
+
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
18602
19134
|
var import_ai15 = require("ai");
|
|
18603
19135
|
var import_cookie_parser = __toESM(require("cookie-parser"), 1);
|
|
18604
19136
|
init_statistics2();
|
|
@@ -18734,6 +19266,7 @@ function composePrepareSteps(...guards) {
|
|
|
18734
19266
|
}
|
|
18735
19267
|
|
|
18736
19268
|
// src/exulu/provider.ts
|
|
19269
|
+
init_tool_image_attachments();
|
|
18737
19270
|
init_sanitize_tool_name();
|
|
18738
19271
|
|
|
18739
19272
|
// src/exulu/auto-decline-stale-approvals.ts
|
|
@@ -18765,7 +19298,7 @@ var autoDeclineStaleApprovals = (messages) => {
|
|
|
18765
19298
|
};
|
|
18766
19299
|
|
|
18767
19300
|
// src/exulu/provider.ts
|
|
18768
|
-
var
|
|
19301
|
+
var import_zod14 = require("zod");
|
|
18769
19302
|
init_tool();
|
|
18770
19303
|
init_resolve_model();
|
|
18771
19304
|
init_statistics2();
|
|
@@ -18785,7 +19318,7 @@ init_check_record_access();
|
|
|
18785
19318
|
init_client();
|
|
18786
19319
|
init_statistics();
|
|
18787
19320
|
init_convert_exulu_tools_to_ai_sdk_tools();
|
|
18788
|
-
var
|
|
19321
|
+
var import_officeparser2 = require("officeparser");
|
|
18789
19322
|
init_singleton();
|
|
18790
19323
|
init_entitlements();
|
|
18791
19324
|
|
|
@@ -18914,9 +19447,9 @@ var ExuluProvider = class {
|
|
|
18914
19447
|
name: `${agent.name}`,
|
|
18915
19448
|
type: "agent",
|
|
18916
19449
|
category: "agents",
|
|
18917
|
-
inputSchema:
|
|
18918
|
-
prompt:
|
|
18919
|
-
information:
|
|
19450
|
+
inputSchema: import_zod14.z.object({
|
|
19451
|
+
prompt: import_zod14.z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
|
|
19452
|
+
information: import_zod14.z.string().describe("A summary of relevant context / information from the current session")
|
|
18920
19453
|
}),
|
|
18921
19454
|
description: `This tool calls an agent named: ${agent.name}. The agent does the following: ${agent.description}.`,
|
|
18922
19455
|
config: [],
|
|
@@ -19223,7 +19756,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19223
19756
|
// Stop after the image_generation tool fires — the widget IS the
|
|
19224
19757
|
// assistant's response, no follow-up text turn is wanted (same
|
|
19225
19758
|
// reasoning as question_ask: the UI artifact is the message).
|
|
19226
|
-
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
|
|
19759
|
+
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
19227
19760
|
stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
|
|
19228
19761
|
});
|
|
19229
19762
|
console.log("[EXULU] Output: " + JSON.stringify(output, null, 2));
|
|
@@ -19286,7 +19819,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19286
19819
|
}),
|
|
19287
19820
|
maxRetries: 2,
|
|
19288
19821
|
tools,
|
|
19289
|
-
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
|
|
19822
|
+
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
19290
19823
|
stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
|
|
19291
19824
|
});
|
|
19292
19825
|
if (statistics) {
|
|
@@ -19380,7 +19913,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19380
19913
|
};
|
|
19381
19914
|
}
|
|
19382
19915
|
const arrayBuffer = await response.arrayBuffer();
|
|
19383
|
-
const extractedText = await (0,
|
|
19916
|
+
const extractedText = await (0, import_officeparser2.parseOfficeAsync)(arrayBuffer, {
|
|
19384
19917
|
outputErrorToConsole: false,
|
|
19385
19918
|
newlineDelimiter: "\n"
|
|
19386
19919
|
});
|
|
@@ -19701,7 +20234,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
|
|
|
19701
20234
|
);
|
|
19702
20235
|
},
|
|
19703
20236
|
// todo allow configuring the step budget per skill
|
|
19704
|
-
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
|
|
20237
|
+
prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
19705
20238
|
stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
|
|
19706
20239
|
});
|
|
19707
20240
|
return {
|
|
@@ -19756,12 +20289,12 @@ var saveChat = async ({
|
|
|
19756
20289
|
// src/exulu/suggestions.ts
|
|
19757
20290
|
init_cjs_shims();
|
|
19758
20291
|
var import_ai12 = require("ai");
|
|
19759
|
-
var
|
|
20292
|
+
var import_zod15 = require("zod");
|
|
19760
20293
|
var SUGGESTIONS_SYSTEM_PROMPT = "You generate short follow-up message suggestions for the user. You are NOT continuing the conversation as the assistant \u2014 you are predicting what the user might want to say next. Suggest up to 3 short follow-up questions or messages the user might want to send next. Each suggestion must be written from the user's perspective (first person) and be 12 words or fewer. You MUST submit your answer by calling the `submit_suggestions` tool exactly once. Do not emit any plain text \u2014 only the tool call.";
|
|
19761
20294
|
var submitSuggestionsTool = (0, import_ai12.tool)({
|
|
19762
20295
|
description: "Submit the final list of follow-up message suggestions for the user. Must be called exactly once. Each suggestion is written from the user's perspective (first person) and is 12 words or fewer.",
|
|
19763
|
-
inputSchema:
|
|
19764
|
-
suggestions:
|
|
20296
|
+
inputSchema: import_zod15.z.object({
|
|
20297
|
+
suggestions: import_zod15.z.array(import_zod15.z.string()).max(3)
|
|
19765
20298
|
})
|
|
19766
20299
|
});
|
|
19767
20300
|
var MAX_CHARS_PER_MESSAGE = 1e4;
|
|
@@ -20323,7 +20856,7 @@ See docs/superpowers/specs/2026-05-31-in-chat-image-generation-design.md for the
|
|
|
20323
20856
|
};
|
|
20324
20857
|
|
|
20325
20858
|
// src/exulu/routes.ts
|
|
20326
|
-
var
|
|
20859
|
+
var import_node_path9 = require("path");
|
|
20327
20860
|
init_tags();
|
|
20328
20861
|
init_admin_client();
|
|
20329
20862
|
init_env();
|
|
@@ -20452,6 +20985,7 @@ init_resolve_model();
|
|
|
20452
20985
|
init_sanitize_tool_name();
|
|
20453
20986
|
init_context_budget();
|
|
20454
20987
|
init_supervisor();
|
|
20988
|
+
init_tool_image_attachments();
|
|
20455
20989
|
function convertOpenAIToolsToAiSdkTools(tools) {
|
|
20456
20990
|
return Object.fromEntries(
|
|
20457
20991
|
tools.map((t) => {
|
|
@@ -20858,7 +21392,7 @@ ${project.description}` : ""}` : "",
|
|
|
20858
21392
|
messages: coreMessages,
|
|
20859
21393
|
tools: hasTools ? activeTools : void 0,
|
|
20860
21394
|
maxRetries: 2,
|
|
20861
|
-
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
|
|
21395
|
+
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
20862
21396
|
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai14.stepCountIs)(turnBudget)],
|
|
20863
21397
|
onError: (error) => {
|
|
20864
21398
|
console.error("[OPENAI GATEWAY] stream error:", error);
|
|
@@ -20898,7 +21432,7 @@ ${project.description}` : ""}` : "",
|
|
|
20898
21432
|
messages: coreMessages,
|
|
20899
21433
|
tools: hasTools ? activeTools : void 0,
|
|
20900
21434
|
maxRetries: 2,
|
|
20901
|
-
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
|
|
21435
|
+
prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
|
|
20902
21436
|
stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai14.stepCountIs)(turnBudget)]
|
|
20903
21437
|
});
|
|
20904
21438
|
res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
|
|
@@ -21113,6 +21647,158 @@ var contentHeadersFor = (key, contentType, filename) => {
|
|
|
21113
21647
|
};
|
|
21114
21648
|
var getSharedArtifactByName = (db2, name) => db2("shared_artifacts").where({ name }).first();
|
|
21115
21649
|
|
|
21650
|
+
// src/skills/skill-access.ts
|
|
21651
|
+
init_cjs_shims();
|
|
21652
|
+
init_check_record_access();
|
|
21653
|
+
async function resolveSkillByName(db2, name) {
|
|
21654
|
+
const row = await db2("skills").where({ name }).first();
|
|
21655
|
+
return row ?? null;
|
|
21656
|
+
}
|
|
21657
|
+
async function canAccessSkill(db2, skill, action, user) {
|
|
21658
|
+
const rbac = await RBACResolver(db2, "skill", skill.id, skill.rights_mode || "private");
|
|
21659
|
+
return checkRecordAccess({ ...skill, RBAC: rbac }, action, user);
|
|
21660
|
+
}
|
|
21661
|
+
async function filterReadableSkills(db2, skills, user) {
|
|
21662
|
+
const out = [];
|
|
21663
|
+
for (const s of skills) {
|
|
21664
|
+
if (await canAccessSkill(db2, s, "read", user)) out.push(s);
|
|
21665
|
+
}
|
|
21666
|
+
return out;
|
|
21667
|
+
}
|
|
21668
|
+
|
|
21669
|
+
// src/skills/bootstrap/imp-skills.ts
|
|
21670
|
+
init_cjs_shims();
|
|
21671
|
+
|
|
21672
|
+
// src/skills/bootstrap/clients.ts
|
|
21673
|
+
init_cjs_shims();
|
|
21674
|
+
var CLIENT_MANIFEST = [
|
|
21675
|
+
{ id: "agents", dir: ".agents/skills" },
|
|
21676
|
+
// cross-agent standard (symlink canonical store)
|
|
21677
|
+
{ id: "claude", dir: ".claude/skills" },
|
|
21678
|
+
{ id: "windsurf", dir: ".windsurf/skills" },
|
|
21679
|
+
{ id: "continue", dir: ".continue/skills" },
|
|
21680
|
+
{ id: "roo", dir: ".roo/skills" },
|
|
21681
|
+
{ id: "kilocode", dir: ".kilocode/skills" },
|
|
21682
|
+
{ id: "crush", dir: ".crush/skills" },
|
|
21683
|
+
{ id: "goose", dir: ".goose/skills" },
|
|
21684
|
+
{ id: "qwen", dir: ".qwen/skills" },
|
|
21685
|
+
{ id: "iflow", dir: ".iflow/skills" },
|
|
21686
|
+
{ id: "junie", dir: ".junie/skills" },
|
|
21687
|
+
{ id: "kiro", dir: ".kiro/skills" },
|
|
21688
|
+
{ id: "trae", dir: ".trae/skills" },
|
|
21689
|
+
{ id: "augment", dir: ".augment/skills" },
|
|
21690
|
+
{ id: "factory", dir: ".factory/skills" },
|
|
21691
|
+
{ id: "devin", dir: ".devin/skills" },
|
|
21692
|
+
{ id: "openhands", dir: ".openhands/skills" },
|
|
21693
|
+
{ id: "pi", dir: ".pi/skills" },
|
|
21694
|
+
{ id: "cortex", dir: ".cortex/skills" },
|
|
21695
|
+
{ id: "zencoder", dir: ".zencoder/skills" },
|
|
21696
|
+
{ id: "codebuddy", dir: ".codebuddy/skills" },
|
|
21697
|
+
{ id: "codestudio", dir: ".codestudio/skills" },
|
|
21698
|
+
{ id: "commandcode", dir: ".commandcode/skills" },
|
|
21699
|
+
{ id: "codemaker", dir: ".codemaker/skills" },
|
|
21700
|
+
{ id: "codeartsdoer", dir: ".codeartsdoer/skills" },
|
|
21701
|
+
{ id: "lingma", dir: ".lingma/skills" },
|
|
21702
|
+
{ id: "qoder", dir: ".qoder/skills" },
|
|
21703
|
+
{ id: "rovodev", dir: ".rovodev/skills" },
|
|
21704
|
+
{ id: "moxby", dir: ".moxby/skills" },
|
|
21705
|
+
{ id: "mux", dir: ".mux/skills" },
|
|
21706
|
+
{ id: "neovate", dir: ".neovate/skills" },
|
|
21707
|
+
{ id: "ona", dir: ".ona/skills" },
|
|
21708
|
+
{ id: "pochi", dir: ".pochi/skills" },
|
|
21709
|
+
{ id: "reasonix", dir: ".reasonix/skills" },
|
|
21710
|
+
{ id: "terramind", dir: ".terramind/skills" },
|
|
21711
|
+
{ id: "tinycloud", dir: ".tinycloud/skills" },
|
|
21712
|
+
{ id: "vibe", dir: ".vibe/skills" },
|
|
21713
|
+
{ id: "adal", dir: ".adal/skills" },
|
|
21714
|
+
{ id: "aider-desk", dir: ".aider-desk/skills" },
|
|
21715
|
+
{ id: "autohand", dir: ".autohand/skills" },
|
|
21716
|
+
{ id: "bob", dir: ".bob/skills" },
|
|
21717
|
+
{ id: "hermes", dir: ".hermes/skills" },
|
|
21718
|
+
{ id: "inferencesh", dir: ".inferencesh/skills" },
|
|
21719
|
+
{ id: "jazz", dir: ".jazz/skills" },
|
|
21720
|
+
{ id: "kode", dir: ".kode/skills" },
|
|
21721
|
+
{ id: "mcpjam", dir: ".mcpjam/skills" },
|
|
21722
|
+
{ id: "forge", dir: ".forge/skills" },
|
|
21723
|
+
{ id: "tabnine", dir: ".tabnine/agent/skills" }
|
|
21724
|
+
// exception: nested under agent/
|
|
21725
|
+
];
|
|
21726
|
+
|
|
21727
|
+
// src/skills/bootstrap/imp-sh.generated.ts
|
|
21728
|
+
init_cjs_shims();
|
|
21729
|
+
var IMP_SH_B64 = "IyEvYmluL3NoCiMgaW1wIOKAlCBoZWxwZXIgZm9yIHRoZSBjZW50cmFsIHNraWxsIGxpYnJhcnkuIFRoZSBhZ2VudCBpbnZva2VzIHRoaXMKIyAobmV2ZXIgcmF3IGN1cmwpOiB0aGUgdG9rZW4gaXMgcmVhZCBmcm9tIHRoZSBjb25maWcgZmlsZSBoZXJlIGFuZCBzZW50IHZpYQojIGB4LWFwaS1rZXk6IEJlYXJlcmAsIHNvIGl0IG5ldmVyIGVudGVycyB0aGUgbW9kZWwgY29udGV4dC4gQ2xpZW50IGZhbi1vdXQKIyAoY29weS9zeW1saW5rIGFjcm9zcyBhZ2VudCBjbGllbnRzKSBpcyBkZXRlcm1pbmlzdGljLgpzZXQgLWV1CgpDT05GSUdfRElSPSIkSE9NRS8uY29uZmlnL2ltcCIKQ09ORklHX0ZJTEU9IiRDT05GSUdfRElSL3NraWxscy5qc29uIgoKZGllKCkgeyBwcmludGYgJ2ltcDogJXNcbicgIiQqIiA+JjI7IGV4aXQgMTsgfQppbmZvKCkgeyBwcmludGYgJyVzXG4nICIkKiIgPiYyOyB9Cgpqc29uX3N0cigpIHsgIyBqc29uX3N0ciA8a2V5PiA8ZmlsZT4g4oCUIGZsYXQgImtleSI6InZhbHVlIgogIHNlZCAtbiAicy8uKlwiJDFcIltbOnNwYWNlOl1dKjpbWzpzcGFjZTpdXSpcIlxcKFteXCJdKlxcKVwiLiovXFwxL3AiICIkMiIgfCBoZWFkIC1uMQp9CgpbIC1mICIkQ09ORklHX0ZJTEUiIF0gfHwgZGllICJub3QgY29uZmlndXJlZCDigJQgcnVuOiBjdXJsIC1mc1NMIDxiYXNlX3VybD4vYXBpL3NraWxscy9pbnN0YWxsLnNoIHwgc2giCgpCQVNFX1VSTD0iJChqc29uX3N0ciBiYXNlX3VybCAiJENPTkZJR19GSUxFIikiCkJBQ0tFTkQ9IiQoanNvbl9zdHIgYmFja2VuZCAiJENPTkZJR19GSUxFIikiCkFQSV9LRVk9IiQoanNvbl9zdHIgYXBpX2tleSAiJENPTkZJR19GSUxFIikiCkxJTktfTU9ERT0iJChqc29uX3N0ciBsaW5rX21vZGUgIiRDT05GSUdfRklMRSIpIgpTQ09QRT0iJChqc29uX3N0ciBzY29wZSAiJENPTkZJR19GSUxFIikiCkNMSUVOVFM9IiQoc2VkIC1uICdzLy4qImNsaWVudHMiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlxbXChbXl1dKlwpXF0uKi9cMS9wJyAiJENPTkZJR19GSUxFIiB8IHRyICcsJyAnICcgfCB0ciAtZCAnIicgfCB0ciAtcyAnICcpIgoKWyAtbiAiJENMSUVOVFMiIF0gfHwgQ0xJRU5UUz0iYWdlbnRzIgpbIC1uICIkTElOS19NT0RFIiBdIHx8IExJTktfTU9ERT0iY29weSIKWyAtbiAiJFNDT1BFIiBdIHx8IFNDT1BFPSJwcm9qZWN0IgoKaWYgWyAteiAiJEJBQ0tFTkQiIF07IHRoZW4KICBbIC1uICIkQkFTRV9VUkwiIF0gfHwgZGllICJjb25maWcgaGFzIG5vIGJhY2tlbmQgYW5kIG5vIGJhc2VfdXJsIgogIEJBQ0tFTkQ9IiQoY3VybCAtZnNTTCAiJEJBU0VfVVJML2FwaS9jb25maWciIHwgc2VkIC1uICdzLy4qImJhY2tlbmQiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKiJcKFteIl0qXCkiLiovXDEvcCcgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJEJBQ0tFTkQiIF0gfHwgZGllICJjb3VsZCBub3QgcmVzb2x2ZSBiYWNrZW5kIGZyb20gJEJBU0VfVVJML2FwaS9jb25maWciCmZpCkJBQ0tFTkQ9IiQocHJpbnRmICclcycgIiRCQUNLRU5EIiB8IHNlZCAnczovKiQ6OicpIgoKUk9PVD0iJFBXRCIKWyAiJFNDT1BFIiA9ICJob21lIiBdICYmIFJPT1Q9IiRIT01FIgoKIyBkaXJfZm9yIENMSUVOVF9JRCAtPiByZWxhdGl2ZSBza2lsbCBkaXIgKGdlbmVyYXRlZCBmcm9tIHRoZSBtYW5pZmVzdCBpbiB0aGUKIyByZWFsIGJ1aWxkOyBhIHJlcHJlc2VudGF0aXZlIHN1YnNldCBoZXJlIGZvciBsb2NhbCB0ZXN0aW5nKS4KZGlyX2ZvcigpIHsKICBjYXNlICIkMSIgaW4KX19ESVJfRk9SX0NBU0VTX18KICAgICopIHJldHVybiAxIDs7CiAgZXNhYwp9CgojIE9uZSAiLi4vIiBwZXIgcGF0aCBjb21wb25lbnQgb2YgJDEgKGEgY2xpZW50IGRpciByZWxhdGl2ZSB0byBST09UKSwgc28KIyBzeW1saW5rcyBhcmUgcmVsYXRpdmUgYW5kIHN1cnZpdmUgYmVpbmcgY29tbWl0dGVkIGFuZCBjaGVja2VkIG91dCBlbHNld2hlcmUuCnJlbF90b19yb290KCkgewogIF91cD0iIjsgX29sZGlmcz0kSUZTOyBJRlM9LwogIGZvciBfc2VnIGluICQxOyBkbyBbIC1uICIkX3NlZyIgXSAmJiBfdXA9Ii4uLyRfdXAiOyBkb25lCiAgSUZTPSRfb2xkaWZzOyBwcmludGYgJyVzJyAiJF91cCIKfQoKYXBpKCkgeyAjIGFwaSA8TUVUSE9EPiA8cGF0aD4gW2V4dHJhIGN1cmwgYXJncy4uLl0gLT4gYm9keSBvbiBzdGRvdXQKICBtPSIkMSI7IHA9IiQyIjsgc2hpZnQgMgogIGN1cmwgLWZzUyAtWCAiJG0iICIkQkFDS0VORCRwIiAtSCAieC1hcGkta2V5OiBCZWFyZXIgJEFQSV9LRVkiICIkQCIKfQoKbWV0YV92ZXJzaW9uKCkgeyAjIG1ldGFfdmVyc2lvbiA8bmFtZT4gLT4gY3VycmVudF92ZXJzaW9uIGZyb20gcmVnaXN0cnkKICBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyQxIiAyPi9kZXYvbnVsbCBcCiAgICB8IHNlZCAtbiAncy8uKiJjdXJyZW50X3ZlcnNpb24iW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlwoWzAtOV1bMC05XSpcKS4qL1wxL3AnIHwgaGVhZCAtbjEKfQoKX21hbmFnZWQoKSB7ICMgX21hbmFnZWQgPGRpcj4gLT4gMCBpZiBzYWZlIHRvIG92ZXJ3cml0ZSAob3VycyBvciBzeW1saW5rIG9yIGFic2VudCkKICBkPSIkMSIKICBpZiBbIC1lICIkZCIgXSAmJiBbICEgLUwgIiRkIiBdICYmIFsgISAtZiAiJGQvLmltcC1za2lsbC5qc29uIiBdOyB0aGVuCiAgICBpbmZvICJza2lwICRkIChleGlzdHMsIG5vdCBtYW5hZ2VkIGJ5IGltcCkiOyByZXR1cm4gMQogIGZpCiAgcmV0dXJuIDAKfQoKX3B1dF9yZWFsKCkgeyAjIF9wdXRfcmVhbCA8ZGVzdD4gPHNyY2Rpcj4gPG1hcmtlci1qc29uPgogIF9tYW5hZ2VkICIkMSIgfHwgcmV0dXJuIDAKICBybSAtcmYgIiQxIjsgbWtkaXIgLXAgIiQxIjsgY3AgLVIgIiQyLy4iICIkMS8iCiAgcHJpbnRmICclc1xuJyAiJDMiID4gIiQxLy5pbXAtc2tpbGwuanNvbiIKfQoKcGxhY2Vfc2tpbGwoKSB7ICMgcGxhY2Vfc2tpbGwgPG5hbWU+IDxzcmNkaXI+IDx2ZXJzaW9uPgogIHBuYW1lPSIkMSI7IHBzcmM9IiQyIjsgcHZlcj0iJHszOi0xfSIKICBtYXJrZXI9J3sgIm5hbWUiOiAiJyIkcG5hbWUiJyIsICJ2ZXJzaW9uIjogJyIkcHZlciInLCAic291cmNlIjogIiciJEJBQ0tFTkQiJyIgfScKICBjYW5vbj0iJFJPT1QvLmFnZW50cy9za2lsbHMvJHBuYW1lIgogIFsgIiRMSU5LX01PREUiID0gInN5bWxpbmsiIF0gJiYgX3B1dF9yZWFsICIkY2Fub24iICIkcHNyYyIgIiRtYXJrZXIiCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgeyBpbmZvICJ1bmtub3duIGNsaWVudDogJGlkIjsgY29udGludWU7IH0KICAgIHBhcmVudD0iJFJPT1QvJGQiOyBkZXN0PSIkcGFyZW50LyRwbmFtZSIKICAgIGlmIFsgIiRMSU5LX01PREUiID0gInN5bWxpbmsiIF0gJiYgWyAiJGlkIiAhPSAiYWdlbnRzIiBdOyB0aGVuCiAgICAgIF9tYW5hZ2VkICIkZGVzdCIgfHwgY29udGludWUKICAgICAgbWtkaXIgLXAgIiRwYXJlbnQiOyBybSAtcmYgIiRkZXN0IgogICAgICByZWw9IiQocmVsX3RvX3Jvb3QgIiRkIikuYWdlbnRzL3NraWxscy8kcG5hbWUiCiAgICAgIGlmIGxuIC1zICIkcmVsIiAiJGRlc3QiIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAibGlua2VkICRkZXN0IC0+ICRyZWwiCiAgICAgIGVsc2UKICAgICAgICBpbmZvICJzeW1saW5rIHVuc3VwcG9ydGVkIGF0ICRkZXN0OyBjb3B5aW5nIgogICAgICAgIF9wdXRfcmVhbCAiJGRlc3QiICIkcHNyYyIgIiRtYXJrZXIiCiAgICAgIGZpCiAgICBlbGlmIFsgIiRMSU5LX01PREUiID0gImNvcHkiIF07IHRoZW4KICAgICAgX3B1dF9yZWFsICIkZGVzdCIgIiRwc3JjIiAiJG1hcmtlciIKICAgIGZpCiAgICAjIHN5bWxpbmsgKyBhZ2VudHM6IGFscmVhZHkgcGxhY2VkIGFzIHRoZSBjYW5vbmljYWwgc3RvcmUgYWJvdmUuCiAgZG9uZQp9CgppbnN0YWxsZWRfbmFtZXMoKSB7ICMgdW5pcXVlIHNraWxsIG5hbWVzIHRoYXQgY2Fycnkgb3VyIG1hcmtlciB1bmRlciBST09UCiAgeyBmaW5kICIkUk9PVC8uYWdlbnRzL3NraWxscyIgLW1heGRlcHRoIDIgLW5hbWUgLmltcC1za2lsbC5qc29uIDI+L2Rldi9udWxsCiAgICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICAgIGQ9IiQoZGlyX2ZvciAiJGlkIikiIHx8IGNvbnRpbnVlCiAgICAgIGZpbmQgIiRST09ULyRkIiAtbWF4ZGVwdGggMiAtbmFtZSAuaW1wLXNraWxsLmpzb24gMj4vZGV2L251bGwKICAgIGRvbmUKICB9IHwgd2hpbGUgcmVhZCAtciBtOyBkbyBqc29uX3N0ciBuYW1lICIkbSI7IGRvbmUgfCBzb3J0IC11Cn0KCm1hcmtlcl92ZXJzaW9uKCkgeyAjIG1hcmtlcl92ZXJzaW9uIDxuYW1lPgogIGZvciBiYXNlIGluICIkUk9PVC8uYWdlbnRzL3NraWxscyI7IGRvCiAgICBbIC1mICIkYmFzZS8kMS8uaW1wLXNraWxsLmpzb24iIF0gJiYgeyBqc29uX3N0ciB2ZXJzaW9uICIkYmFzZS8kMS8uaW1wLXNraWxsLmpzb24iOyByZXR1cm47IH0KICBkb25lCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgY29udGludWUKICAgIGY9IiRST09ULyRkLyQxLy5pbXAtc2tpbGwuanNvbiIKICAgIFsgLWYgIiRmIiBdICYmIHsganNvbl9zdHIgdmVyc2lvbiAiJGYiOyByZXR1cm47IH0KICBkb25lCn0KCmRvX2luc3RhbGwoKSB7ICMgZG9faW5zdGFsbCA8bmFtZT4KICBuYW1lPSIkMSIKICBUTVA9IiQobWt0ZW1wIC1kKSIKICBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyRuYW1lL2Rvd25sb2FkIiAtLW91dHB1dCAiJFRNUC9za2lsbC56aXAiIFwKICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJkb3dubG9hZCBmYWlsZWQgZm9yICckbmFtZScgKDQwMyA9IG5vIGFjY2VzcywgNDA0ID0gdW5rbm93bikiOyB9CiAgbWtkaXIgLXAgIiRUTVAveCIKICB1bnppcCAtcSAiJFRNUC9za2lsbC56aXAiIC1kICIkVE1QL3giIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJiYWQgYXJjaGl2ZSBmb3IgJyRuYW1lJyI7IH0KICBzcmM9IiQoZmluZCAiJFRNUC94IiAtbWluZGVwdGggMSAtbWF4ZGVwdGggMSAtdHlwZSBkIHwgaGVhZCAtbjEpIgogIFsgLW4gIiRzcmMiIF0gfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgInVuZXhwZWN0ZWQgYXJjaGl2ZSBsYXlvdXQgZm9yICckbmFtZSciOyB9CiAgdmVyPSIkKG1ldGFfdmVyc2lvbiAiJG5hbWUiKSIKICBwbGFjZV9za2lsbCAiJG5hbWUiICIkc3JjIiAiJHt2ZXI6LTF9IgogIHJtIC1yZiAiJFRNUCIKICBpbmZvICJpbnN0YWxsZWQgJG5hbWUgKHYke3ZlcjotMX0pIFskTElOS19NT0RFXSBpbnRvOiAkQ0xJRU5UUyIKfQoKdXNhZ2UoKSB7CiAgY2F0ID4mMiA8PEVPRgppbXAg4oCUIHNraWxsIGxpYnJhcnkgaGVscGVyCiAgaW1wIGxpc3QgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0IHNraWxscyB5b3UgY2FuIGFjY2VzcyAoSlNPTikKICBpbXAgZ2V0IDxuYW1lPiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3cgb25lIHNraWxsJ3MgbWV0YWRhdGEgKEpTT04pCiAgaW1wIGluc3RhbGwgPG5hbWU+ICAgICAgICAgICAgICAgICAgICAgICBpbnN0YWxsL3JlZnJlc2ggYSBza2lsbCBpbnRvIHlvdXIgYWdlbnQgY2xpZW50cwogIGltcCB1cGRhdGUgWzxuYW1lPl0gICAgICAgICAgICAgICAgICAgICAgdXBkYXRlIGluc3RhbGxlZCBza2lsbHMgKGFsbCwgb3Igb25lKSB0byBsYXRlc3QKICBpbXAgcHVibGlzaCA8bmFtZT4gPGRpcj4gPHB1YmxpY3xwcml2YXRlPiBwdWJsaXNoIGEgbG9jYWwgc2tpbGwgZm9sZGVyIGFzIDxuYW1lPgogIGltcCBjb25maWcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvdyByZXNvbHZlZCBiYWNrZW5kIC8gc2NvcGUgLyBjbGllbnRzIChubyBzZWNyZXRzKQpFT0YKfQoKY21kPSIkezE6LWhlbHB9IgpbICQjIC1ndCAwIF0gJiYgc2hpZnQgfHwgdHJ1ZQoKY2FzZSAiJGNtZCIgaW4KICBsaXN0KSBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5IiA7OwogIGdldCkgWyAkIyAtZ2UgMSBdIHx8IGRpZSAidXNhZ2U6IGltcCBnZXQgPG5hbWU+IjsgYXBpIEdFVCAiL3NraWxscy9yZWdpc3RyeS8kMSIgOzsKICBpbnN0YWxsKSBbICQjIC1nZSAxIF0gfHwgZGllICJ1c2FnZTogaW1wIGluc3RhbGwgPG5hbWU+IjsgZG9faW5zdGFsbCAiJDEiIDs7CiAgdXBkYXRlKQogICAgaWYgWyAkIyAtZ2UgMSBdOyB0aGVuIG5hbWVzPSIkMSI7IGVsc2UgbmFtZXM9IiQoaW5zdGFsbGVkX25hbWVzKSI7IGZpCiAgICBbIC1uICIkbmFtZXMiIF0gfHwgeyBpbmZvICJubyBpbXAtbWFuYWdlZCBza2lsbHMgZm91bmQgdW5kZXIgJFJPT1QiOyBleGl0IDA7IH0KICAgIGZvciBuIGluICRuYW1lczsgZG8KICAgICAgY3VyPSIkKG1hcmtlcl92ZXJzaW9uICIkbiIpIgogICAgICBsYXRlc3Q9IiQobWV0YV92ZXJzaW9uICIkbiIpIgogICAgICBbIC1uICIkbGF0ZXN0IiBdIHx8IHsgaW5mbyAic2tpcCAkbiAobm90IGluIHJlZ2lzdHJ5KSI7IGNvbnRpbnVlOyB9CiAgICAgIGlmIFsgLXogIiRjdXIiIF0gfHwgWyAiJGxhdGVzdCIgLWd0ICIkY3VyIiBdIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAidXBkYXRpbmcgJG46IHYke2N1cjotP30gLT4gdiRsYXRlc3QiOyBkb19pbnN0YWxsICIkbiIKICAgICAgZWxzZQogICAgICAgIGluZm8gIiRuIHVwIHRvIGRhdGUgKHYkY3VyKSIKICAgICAgZmkKICAgIGRvbmUKICAgIDs7CiAgcHVibGlzaCkKICAgIFsgJCMgLWdlIDMgXSB8fCBkaWUgInVzYWdlOiBpbXAgcHVibGlzaCA8bmFtZT4gPGRpcj4gPHB1YmxpY3xwcml2YXRlPiIKICAgIG5hbWU9IiQxIjsgZm9sZGVyPSIkMiI7IHZpc2liaWxpdHk9IiQzIgogICAgY2FzZSAiJHZpc2liaWxpdHkiIGluCiAgICAgIHB1YmxpY3xwcml2YXRlKSA7OwogICAgICAqKSBkaWUgInZpc2liaWxpdHkgbXVzdCBiZSAncHVibGljJyBvciAncHJpdmF0ZScgKGFzayB0aGUgdXNlciB3aGljaCB0aGV5IHdhbnQpIiA7OwogICAgZXNhYwogICAgWyAtZCAiJGZvbGRlciIgXSB8fCBkaWUgIm5vIHN1Y2ggZm9sZGVyOiAkZm9sZGVyIgogICAgWyAtZiAiJGZvbGRlci9TS0lMTC5tZCIgXSB8fCBkaWUgIiRmb2xkZXIgaGFzIG5vIFNLSUxMLm1kIGF0IGl0cyByb290IgogICAgY29tbWFuZCAtdiB6aXAgPi9kZXYvbnVsbCAyPiYxIHx8IGRpZSAidGhlICd6aXAnIGNvbW1hbmQgaXMgcmVxdWlyZWQgdG8gcHVibGlzaCIKICAgIFRNUD0iJChta3RlbXAgLWQpIgogICAgKCBjZCAiJChkaXJuYW1lICIkZm9sZGVyIikiIFwKICAgICAgJiYgemlwIC1xIC1yIC1YICIkVE1QL3NraWxsLnppcCIgIiQoYmFzZW5hbWUgIiRmb2xkZXIiKSIgXAogICAgICAgICAgIC14ICcqLy5pbXAtc2tpbGwuanNvbicgJyovLmdpdC8qJyAnKi5EU19TdG9yZScgJyovX19NQUNPU1gvKicgKSBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJjb3VsZCBub3QgemlwICRmb2xkZXIiOyB9CiAgICBhcGkgUE9TVCAiL3NraWxscy9yZWdpc3RyeS8kbmFtZT92aXNpYmlsaXR5PSR2aXNpYmlsaXR5IiAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi96aXAiIFwKICAgICAgLS1kYXRhLWJpbmFyeSBAIiRUTVAvc2tpbGwuemlwIiBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJwdWJsaXNoIGZhaWxlZCAoNDAzID0gbm8gd3JpdGUgYWNjZXNzLCA0MDkgPSBuYW1lIHRha2VuKSI7IH0KICAgIHJtIC1yZiAiJFRNUCIKICAgIGluZm8gInB1Ymxpc2hlZCAkbmFtZSAoJHZpc2liaWxpdHkpIgogICAgOzsKICBjb25maWcpCiAgICBwcmludGYgJ2JhY2tlbmQ9JXNcbnNjb3BlPSVzXG5saW5rX21vZGU9JXNcbmNsaWVudHM9JXNcbmFwaV9rZXk9JXNcbicgXAogICAgICAiJEJBQ0tFTkQiICIkU0NPUEUiICIkTElOS19NT0RFIiAiJENMSUVOVFMiIFwKICAgICAgIiQoWyAtbiAiJEFQSV9LRVkiIF0gJiYgZWNobyBzZXQgfHwgZWNobyBNSVNTSU5HKSIKICAgIDs7CiAgaGVscHwtLWhlbHB8LWgpIHVzYWdlIDs7CiAgKikgaW5mbyAidW5rbm93biBjb21tYW5kOiAkY21kIjsgdXNhZ2U7IGV4aXQgMiA7Owplc2FjCg==";
|
|
21730
|
+
|
|
21731
|
+
// src/skills/bootstrap/imp-skills.ts
|
|
21732
|
+
var BOOTSTRAP_CLIENTS_JSON = JSON.stringify(CLIENT_MANIFEST, null, 2);
|
|
21733
|
+
var DIR_FOR_CASES = CLIENT_MANIFEST.map(
|
|
21734
|
+
(c) => ` ${c.id}) printf '%s' '${c.dir}' ;;`
|
|
21735
|
+
).join("\n");
|
|
21736
|
+
var BOOTSTRAP_IMP_SH = Buffer.from(IMP_SH_B64, "base64").toString("utf8").replace("__DIR_FOR_CASES__", DIR_FOR_CASES);
|
|
21737
|
+
var BOOTSTRAP_SKILL_MD = `---
|
|
21738
|
+
name: imp-skills
|
|
21739
|
+
description: Install, update, and publish skills from this instance's central skill library. Use when the user asks to install a skill, get the latest version of a skill, list IMP skills, or publish a skill to IMP.
|
|
21740
|
+
---
|
|
21741
|
+
|
|
21742
|
+
# IMP Skills
|
|
21743
|
+
|
|
21744
|
+
Bridge to the IMP central skill library. **All operations go through the
|
|
21745
|
+
bundled helper script \u2014 do not hand-write curl or copy files yourself.** The
|
|
21746
|
+
script reads the API token from config (keeping it out of this conversation) and
|
|
21747
|
+
handles the multi-client copy/symlink fan-out deterministically.
|
|
21748
|
+
|
|
21749
|
+
## The helper
|
|
21750
|
+
|
|
21751
|
+
Run the script next to this file, \`scripts/imp\`, with \`sh\` and the absolute
|
|
21752
|
+
path of this skill's directory:
|
|
21753
|
+
|
|
21754
|
+
\`\`\`
|
|
21755
|
+
sh "<this-skill-dir>/scripts/imp" <command>
|
|
21756
|
+
\`\`\`
|
|
21757
|
+
|
|
21758
|
+
Commands:
|
|
21759
|
+
- \`list\` \u2014 skills you can access (JSON on stdout)
|
|
21760
|
+
- \`get <name>\` \u2014 one skill's metadata (JSON)
|
|
21761
|
+
- \`install <name>\` \u2014 install/refresh a skill into the user's agent clients
|
|
21762
|
+
- \`update [<name>]\` \u2014 update every installed skill, or just \`<name>\`, to latest
|
|
21763
|
+
- \`publish <name> <folder> <public|private>\` \u2014 publish a local skill folder as \`<name>\`
|
|
21764
|
+
- \`config\` \u2014 show resolved backend / scope / clients (prints no secrets)
|
|
21765
|
+
|
|
21766
|
+
The token, backend URL, target clients, copy-vs-symlink mode, and scope all come
|
|
21767
|
+
from \`~/.config/imp/skills.json\` (written by the installer). Never print the
|
|
21768
|
+
\`api_key\` or read it into your reply \u2014 the script uses it internally.
|
|
21769
|
+
|
|
21770
|
+
## Requests \u2192 commands
|
|
21771
|
+
|
|
21772
|
+
- "list / search skills" \u2192 \`imp list\`, then filter the JSON for the user.
|
|
21773
|
+
- "install skill X" / "add the X skill" \u2192 \`imp install X\`.
|
|
21774
|
+
- "update / get the latest version [of X]" \u2192 \`imp update [X]\`.
|
|
21775
|
+
- "publish / upload this skill as X" \u2192 confirm the target name with the user; for
|
|
21776
|
+
an existing skill run \`imp get X\` first and confirm a new version is intended.
|
|
21777
|
+
For a NEW skill you MUST ask the user whether it should be \`public\` (visible
|
|
21778
|
+
to everyone on the instance) or \`private\` (only them) \u2014 never pick a
|
|
21779
|
+
visibility yourself; then \`imp publish X <folder> <public|private>\`.
|
|
21780
|
+
|
|
21781
|
+
## Not configured yet?
|
|
21782
|
+
|
|
21783
|
+
If \`imp config\` reports it's not configured (or \`~/.config/imp/skills.json\`
|
|
21784
|
+
is missing), tell the user to run the installer \u2014 it sets everything up
|
|
21785
|
+
interactively (base URL, API key, target clients, copy/symlink):
|
|
21786
|
+
|
|
21787
|
+
\`\`\`
|
|
21788
|
+
curl -fsSL <base_url>/api/skills/install.sh | sh
|
|
21789
|
+
\`\`\`
|
|
21790
|
+
|
|
21791
|
+
\`<base_url>\` is their instance's frontend URL (e.g. https://ai.open.de). They
|
|
21792
|
+
can create an API key at \`<base_url>/token\`.
|
|
21793
|
+
|
|
21794
|
+
## Errors
|
|
21795
|
+
|
|
21796
|
+
- install: \`403\` = no access to that skill; \`404\` = unknown name.
|
|
21797
|
+
- publish: \`400\` = missing/invalid visibility (new skills need public|private);
|
|
21798
|
+
\`403\` = you can see it but lack write access; \`409\` = the name is taken by a
|
|
21799
|
+
skill you can't access.
|
|
21800
|
+
`;
|
|
21801
|
+
|
|
21116
21802
|
// src/exulu/routes.ts
|
|
21117
21803
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
21118
21804
|
var getExuluVersionNumber = async () => {
|
|
@@ -21186,6 +21872,7 @@ var createExpressRoutes = async (app, providers, tools, contexts, config, evals,
|
|
|
21186
21872
|
}
|
|
21187
21873
|
next();
|
|
21188
21874
|
});
|
|
21875
|
+
const rawZip = import_express5.default.raw({ type: ["application/zip", "application/octet-stream", "application/x-zip-compressed", "application/x-zip"], limit: "50mb" });
|
|
21189
21876
|
console.log(`
|
|
21190
21877
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
|
|
21191
21878
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
@@ -22140,7 +22827,7 @@ ${customInstructions}` : agent.instructions;
|
|
|
22140
22827
|
const imageModelsByName = (() => {
|
|
22141
22828
|
if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
|
|
22142
22829
|
try {
|
|
22143
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0,
|
|
22830
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path9.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
22144
22831
|
const models2 = parseImageGenerationModels(configPath);
|
|
22145
22832
|
return new Map(models2.map((m) => [m.model_name, m]));
|
|
22146
22833
|
} catch (err) {
|
|
@@ -22838,7 +23525,9 @@ ${style.markdown}` : params.prompt;
|
|
|
22838
23525
|
const budget_duration = String(body?.budget_duration ?? "");
|
|
22839
23526
|
if (!Number.isFinite(max_budget) || max_budget <= 0) return null;
|
|
22840
23527
|
if (!BUDGET_ALLOWED_DURATIONS.has(budget_duration)) return null;
|
|
22841
|
-
|
|
23528
|
+
const reset = parseResetAt(body?.budget_reset_at);
|
|
23529
|
+
if (!reset.valid) return null;
|
|
23530
|
+
return { max_budget, budget_duration, budget_reset_at: reset.value };
|
|
22842
23531
|
};
|
|
22843
23532
|
const parseBudgetSettingsBody = (body) => {
|
|
22844
23533
|
if (!body || typeof body !== "object") return null;
|
|
@@ -22908,7 +23597,7 @@ ${style.markdown}` : params.prompt;
|
|
|
22908
23597
|
}
|
|
22909
23598
|
const body = parseBudgetBody(req.body);
|
|
22910
23599
|
if (!body) {
|
|
22911
|
-
res.status(400).json({ detail: "Invalid budget (max_budget, budget_duration)." });
|
|
23600
|
+
res.status(400).json({ detail: "Invalid budget (max_budget, budget_duration, budget_reset_at)." });
|
|
22912
23601
|
return;
|
|
22913
23602
|
}
|
|
22914
23603
|
const entityIds = Array.isArray(req.body?.entityIds) ? req.body.entityIds : [];
|
|
@@ -22920,7 +23609,7 @@ ${style.markdown}` : params.prompt;
|
|
|
22920
23609
|
for (const id of entityIds) {
|
|
22921
23610
|
const tag = budgetTagFor(entityType, id);
|
|
22922
23611
|
try {
|
|
22923
|
-
await upsertBudget(tag, body.max_budget, body.budget_duration);
|
|
23612
|
+
await upsertBudget(tag, body.max_budget, body.budget_duration, body.budget_reset_at);
|
|
22924
23613
|
results.push({ entityId: String(id), ok: true });
|
|
22925
23614
|
} catch (err) {
|
|
22926
23615
|
results.push({
|
|
@@ -22945,12 +23634,12 @@ ${style.markdown}` : params.prompt;
|
|
|
22945
23634
|
}
|
|
22946
23635
|
const body = parseBudgetBody(req.body);
|
|
22947
23636
|
if (!body) {
|
|
22948
|
-
res.status(400).json({ detail: "Invalid budget (max_budget, budget_duration)." });
|
|
23637
|
+
res.status(400).json({ detail: "Invalid budget (max_budget, budget_duration, budget_reset_at)." });
|
|
22949
23638
|
return;
|
|
22950
23639
|
}
|
|
22951
23640
|
const tag = budgetTagFor(entityType, req.params.entityId ?? "");
|
|
22952
23641
|
try {
|
|
22953
|
-
await upsertBudget(tag, body.max_budget, body.budget_duration);
|
|
23642
|
+
await upsertBudget(tag, body.max_budget, body.budget_duration, body.budget_reset_at);
|
|
22954
23643
|
const info = await tagInfo([tag]);
|
|
22955
23644
|
res.status(200).json({ budget: info[tag] ?? null });
|
|
22956
23645
|
} catch (err) {
|
|
@@ -23413,6 +24102,206 @@ ${style.markdown}` : params.prompt;
|
|
|
23413
24102
|
}
|
|
23414
24103
|
return root;
|
|
23415
24104
|
}
|
|
24105
|
+
app.get("/skills/agent/bootstrap", async (_req, res) => {
|
|
24106
|
+
try {
|
|
24107
|
+
const zip = new import_jszip3.default();
|
|
24108
|
+
zip.file("imp-skills/SKILL.md", BOOTSTRAP_SKILL_MD);
|
|
24109
|
+
zip.file("imp-skills/references/clients.json", BOOTSTRAP_CLIENTS_JSON);
|
|
24110
|
+
zip.file("imp-skills/scripts/imp", BOOTSTRAP_IMP_SH, {
|
|
24111
|
+
unixPermissions: 493
|
|
24112
|
+
});
|
|
24113
|
+
const buffer = await zip.generateAsync({
|
|
24114
|
+
type: "nodebuffer",
|
|
24115
|
+
platform: "UNIX"
|
|
24116
|
+
});
|
|
24117
|
+
res.setHeader("Content-Type", "application/zip");
|
|
24118
|
+
res.setHeader("Content-Disposition", 'attachment; filename="imp-skills.zip"');
|
|
24119
|
+
res.send(buffer);
|
|
24120
|
+
} catch (err) {
|
|
24121
|
+
console.error("[SKILLS] Failed to build bootstrap zip", err);
|
|
24122
|
+
res.status(500).json({ detail: "Failed to build bootstrap skill." });
|
|
24123
|
+
}
|
|
24124
|
+
});
|
|
24125
|
+
app.get("/skills/registry", async (req, res) => {
|
|
24126
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24127
|
+
if (!authResult.user?.id) {
|
|
24128
|
+
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24129
|
+
return;
|
|
24130
|
+
}
|
|
24131
|
+
const { db: db2 } = await postgresClient();
|
|
24132
|
+
const tag = typeof req.query.tag === "string" ? req.query.tag : void 0;
|
|
24133
|
+
const all = await db2("skills").select("*");
|
|
24134
|
+
const readable = await filterReadableSkills(db2, all, authResult.user);
|
|
24135
|
+
const skills = readable.filter((s) => {
|
|
24136
|
+
if (!tag) return true;
|
|
24137
|
+
const tags = Array.isArray(s.tags) ? s.tags : [];
|
|
24138
|
+
return tags.includes(tag);
|
|
24139
|
+
}).map((s) => ({
|
|
24140
|
+
name: s.name,
|
|
24141
|
+
description: s.description ?? "",
|
|
24142
|
+
tags: Array.isArray(s.tags) ? s.tags : [],
|
|
24143
|
+
current_version: s.current_version ?? 1,
|
|
24144
|
+
updated_at: s.updatedAt ?? s.updated_at ?? null
|
|
24145
|
+
}));
|
|
24146
|
+
res.json({ skills });
|
|
24147
|
+
});
|
|
24148
|
+
app.get("/skills/registry/:name/download", async (req, res) => {
|
|
24149
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24150
|
+
if (!authResult.user?.id) {
|
|
24151
|
+
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24152
|
+
return;
|
|
24153
|
+
}
|
|
24154
|
+
const { db: db2 } = await postgresClient();
|
|
24155
|
+
const skill = await resolveSkillByName(db2, req.params.name);
|
|
24156
|
+
if (!skill) {
|
|
24157
|
+
res.status(404).json({ detail: "Skill not found." });
|
|
24158
|
+
return;
|
|
24159
|
+
}
|
|
24160
|
+
if (!await canAccessSkill(db2, skill, "read", authResult.user)) {
|
|
24161
|
+
res.status(403).json({ detail: "You don't have access to this skill." });
|
|
24162
|
+
return;
|
|
24163
|
+
}
|
|
24164
|
+
const vQuery = req.query.version;
|
|
24165
|
+
const version = !vQuery || vQuery === "latest" ? skill.current_version ?? 1 : Number(vQuery);
|
|
24166
|
+
if (!Number.isFinite(version) || version < 1) {
|
|
24167
|
+
res.status(400).json({ detail: "Invalid version." });
|
|
24168
|
+
return;
|
|
24169
|
+
}
|
|
24170
|
+
const safeName = String(skill.name ?? "skill").replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "") || "skill";
|
|
24171
|
+
const versionPrefix = `skills/${skill.id}/v${version}/`;
|
|
24172
|
+
const files = await listS3ObjectsByPrefix(versionPrefix, config);
|
|
24173
|
+
if (files.length === 0) {
|
|
24174
|
+
res.status(404).json({ detail: `Version v${version} has no files.` });
|
|
24175
|
+
return;
|
|
24176
|
+
}
|
|
24177
|
+
const zip = new import_jszip3.default();
|
|
24178
|
+
for (const file of files) {
|
|
24179
|
+
const idx = file.key.indexOf(versionPrefix);
|
|
24180
|
+
const rel = idx >= 0 ? file.key.slice(idx + versionPrefix.length) : file.key;
|
|
24181
|
+
if (!rel) continue;
|
|
24182
|
+
const bytes = await getS3ObjectBytes(file.key, config);
|
|
24183
|
+
zip.file(`${safeName}/${rel}`, bytes);
|
|
24184
|
+
}
|
|
24185
|
+
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
24186
|
+
res.setHeader("Content-Type", "application/zip");
|
|
24187
|
+
res.setHeader("Content-Disposition", `attachment; filename="${safeName}.skill"`);
|
|
24188
|
+
res.send(buffer);
|
|
24189
|
+
});
|
|
24190
|
+
app.post("/skills/registry/:name", rawZip, async (req, res) => {
|
|
24191
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24192
|
+
if (!authResult.user?.id) {
|
|
24193
|
+
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24194
|
+
return;
|
|
24195
|
+
}
|
|
24196
|
+
const name = req.params.name;
|
|
24197
|
+
const bytes = req.body;
|
|
24198
|
+
if (!Buffer.isBuffer(bytes) || bytes.length === 0) {
|
|
24199
|
+
res.status(400).json({ detail: "Empty body. Send the skill as a zip/.skill payload." });
|
|
24200
|
+
return;
|
|
24201
|
+
}
|
|
24202
|
+
const { db: db2 } = await postgresClient();
|
|
24203
|
+
const existing = await resolveSkillByName(db2, name);
|
|
24204
|
+
if (existing) {
|
|
24205
|
+
const canWrite = await canAccessSkill(db2, existing, "write", authResult.user);
|
|
24206
|
+
if (canWrite) {
|
|
24207
|
+
const nextVersion = (existing.current_version ?? 1) + 1;
|
|
24208
|
+
try {
|
|
24209
|
+
await extractBundleToVersion({ bytes, skillId: existing.id, version: nextVersion, config });
|
|
24210
|
+
} catch (err) {
|
|
24211
|
+
if (err instanceof BundleValidationError) {
|
|
24212
|
+
res.status(400).json({ detail: err.message });
|
|
24213
|
+
return;
|
|
24214
|
+
}
|
|
24215
|
+
console.error("[SKILLS] publish (new version) failed", err);
|
|
24216
|
+
res.status(500).json({ detail: "Failed to publish new version." });
|
|
24217
|
+
return;
|
|
24218
|
+
}
|
|
24219
|
+
const history = Array.isArray(existing.history) ? existing.history : [];
|
|
24220
|
+
await db2("skills").where({ id: existing.id }).update({
|
|
24221
|
+
current_version: nextVersion,
|
|
24222
|
+
history: JSON.stringify([
|
|
24223
|
+
...history,
|
|
24224
|
+
{ version: nextVersion, created_at: (/* @__PURE__ */ new Date()).toISOString(), label: "Published from agent" }
|
|
24225
|
+
])
|
|
24226
|
+
});
|
|
24227
|
+
res.json({ name, version: nextVersion, created: false });
|
|
24228
|
+
return;
|
|
24229
|
+
} else {
|
|
24230
|
+
const canRead = await canAccessSkill(db2, existing, "read", authResult.user);
|
|
24231
|
+
if (!canRead) {
|
|
24232
|
+
res.status(409).json({ detail: "That name is unavailable." });
|
|
24233
|
+
return;
|
|
24234
|
+
}
|
|
24235
|
+
res.status(403).json({ detail: "You don't have write access to this skill." });
|
|
24236
|
+
return;
|
|
24237
|
+
}
|
|
24238
|
+
}
|
|
24239
|
+
const visibility = req.query.visibility;
|
|
24240
|
+
if (visibility !== "public" && visibility !== "private") {
|
|
24241
|
+
res.status(400).json({
|
|
24242
|
+
detail: "Missing or invalid 'visibility'. New skills require ?visibility=public or ?visibility=private \u2014 ask the user which they want."
|
|
24243
|
+
});
|
|
24244
|
+
return;
|
|
24245
|
+
}
|
|
24246
|
+
const meta = await parseSkillFrontmatter(bytes);
|
|
24247
|
+
const skillId = (0, import_node_crypto9.randomUUID)();
|
|
24248
|
+
try {
|
|
24249
|
+
await extractBundleToVersion({ bytes, skillId, version: 1, config });
|
|
24250
|
+
} catch (err) {
|
|
24251
|
+
if (err instanceof BundleValidationError) {
|
|
24252
|
+
res.status(400).json({ detail: err.message });
|
|
24253
|
+
return;
|
|
24254
|
+
}
|
|
24255
|
+
console.error("[SKILLS] publish (create) failed", err);
|
|
24256
|
+
res.status(500).json({ detail: "Failed to publish skill." });
|
|
24257
|
+
return;
|
|
24258
|
+
}
|
|
24259
|
+
try {
|
|
24260
|
+
await db2("skills").insert({
|
|
24261
|
+
id: skillId,
|
|
24262
|
+
name,
|
|
24263
|
+
description: meta.description ?? "",
|
|
24264
|
+
s3folder: `skills/${skillId}`,
|
|
24265
|
+
tags: JSON.stringify([]),
|
|
24266
|
+
usage_count: 0,
|
|
24267
|
+
favorite_count: 0,
|
|
24268
|
+
current_version: 1,
|
|
24269
|
+
history: JSON.stringify([
|
|
24270
|
+
{ version: 1, created_at: (/* @__PURE__ */ new Date()).toISOString(), label: "Published from agent" }
|
|
24271
|
+
]),
|
|
24272
|
+
rights_mode: visibility,
|
|
24273
|
+
created_by: authResult.user.id
|
|
24274
|
+
});
|
|
24275
|
+
} catch (err) {
|
|
24276
|
+
res.status(409).json({ detail: "That name is unavailable." });
|
|
24277
|
+
return;
|
|
24278
|
+
}
|
|
24279
|
+
res.json({ name, version: 1, created: true });
|
|
24280
|
+
});
|
|
24281
|
+
app.get("/skills/registry/:name", async (req, res) => {
|
|
24282
|
+
const authResult = await requestValidators.authenticate(req);
|
|
24283
|
+
if (!authResult.user?.id) {
|
|
24284
|
+
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24285
|
+
return;
|
|
24286
|
+
}
|
|
24287
|
+
const { db: db2 } = await postgresClient();
|
|
24288
|
+
const skill = await resolveSkillByName(db2, req.params.name);
|
|
24289
|
+
if (!skill) {
|
|
24290
|
+
res.status(404).json({ detail: "Skill not found." });
|
|
24291
|
+
return;
|
|
24292
|
+
}
|
|
24293
|
+
if (!await canAccessSkill(db2, skill, "read", authResult.user)) {
|
|
24294
|
+
res.status(403).json({ detail: "You don't have access to this skill." });
|
|
24295
|
+
return;
|
|
24296
|
+
}
|
|
24297
|
+
res.json({
|
|
24298
|
+
name: skill.name,
|
|
24299
|
+
description: skill.description ?? "",
|
|
24300
|
+
tags: Array.isArray(skill.tags) ? skill.tags : [],
|
|
24301
|
+
current_version: skill.current_version ?? 1,
|
|
24302
|
+
history: Array.isArray(skill.history) ? skill.history : []
|
|
24303
|
+
});
|
|
24304
|
+
});
|
|
23416
24305
|
app.post("/skills/:skillId/init", async (req, res) => {
|
|
23417
24306
|
const authResult = await requestValidators.authenticate(req);
|
|
23418
24307
|
if (!authResult.user?.id) {
|
|
@@ -23460,8 +24349,8 @@ ${style.markdown}` : params.prompt;
|
|
|
23460
24349
|
}
|
|
23461
24350
|
const { skillId } = req.params;
|
|
23462
24351
|
const { extension, contentType } = req.body ?? {};
|
|
23463
|
-
if (extension !== ".zip" && extension !== ".md") {
|
|
23464
|
-
res.status(400).json({ detail: 'extension must be ".zip" or ".
|
|
24352
|
+
if (extension !== ".zip" && extension !== ".md" && extension !== ".skill") {
|
|
24353
|
+
res.status(400).json({ detail: 'extension must be ".zip", ".md", or ".skill".' });
|
|
23465
24354
|
return;
|
|
23466
24355
|
}
|
|
23467
24356
|
if (!contentType || typeof contentType !== "string") {
|
|
@@ -23600,19 +24489,22 @@ ${style.markdown}` : params.prompt;
|
|
|
23600
24489
|
}
|
|
23601
24490
|
const versionPrefix = `skills/${skillId}/v${version}/`;
|
|
23602
24491
|
const files = await listS3ObjectsByPrefix(versionPrefix, config);
|
|
23603
|
-
const
|
|
24492
|
+
const asSkill = req.query.format === "skill";
|
|
24493
|
+
const safeName = String(skill.name ?? "skill").replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "") || "skill";
|
|
24494
|
+
const zip = new import_jszip3.default();
|
|
23604
24495
|
let fileCount = 0;
|
|
23605
24496
|
for (const file of files) {
|
|
23606
24497
|
const prefixIndex = file.key.indexOf(versionPrefix);
|
|
23607
24498
|
const relativePath = prefixIndex >= 0 ? file.key.slice(prefixIndex + versionPrefix.length) : file.key;
|
|
23608
24499
|
if (!relativePath) continue;
|
|
23609
24500
|
const bytes = await getS3ObjectBytes(file.key, config);
|
|
23610
|
-
|
|
24501
|
+
const archivePath = asSkill ? `${safeName}/${relativePath}` : relativePath;
|
|
24502
|
+
zip.file(archivePath, bytes);
|
|
23611
24503
|
fileCount += 1;
|
|
23612
24504
|
}
|
|
23613
24505
|
const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
23614
24506
|
zip.file(
|
|
23615
|
-
"version.txt",
|
|
24507
|
+
asSkill ? `${safeName}/version.txt` : "version.txt",
|
|
23616
24508
|
[
|
|
23617
24509
|
`Skill: ${skill.name ?? skillId}`,
|
|
23618
24510
|
`Skill id: ${skillId}`,
|
|
@@ -23623,13 +24515,9 @@ ${style.markdown}` : params.prompt;
|
|
|
23623
24515
|
].join("\n")
|
|
23624
24516
|
);
|
|
23625
24517
|
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
23626
|
-
const
|
|
23627
|
-
const filename = `${safeName}-v${version}.zip`;
|
|
24518
|
+
const filename = asSkill ? `${safeName}.skill` : `${safeName}-v${version}.zip`;
|
|
23628
24519
|
res.setHeader("Content-Type", "application/zip");
|
|
23629
|
-
res.setHeader(
|
|
23630
|
-
"Content-Disposition",
|
|
23631
|
-
`attachment; filename="${filename}"`
|
|
23632
|
-
);
|
|
24520
|
+
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
|
23633
24521
|
res.send(buffer);
|
|
23634
24522
|
});
|
|
23635
24523
|
app.post("/skills/:skillId/sign", async (req, res) => {
|
|
@@ -23929,6 +24817,12 @@ ${style.markdown}` : params.prompt;
|
|
|
23929
24817
|
const fullSessionPrefix = `${generalPrefix}${userSessionPrefix}`;
|
|
23930
24818
|
return { userSessionPrefix, fullSessionPrefix };
|
|
23931
24819
|
}
|
|
24820
|
+
const loadSessionFilesAuth = async (req, res, sessionId, rights) => {
|
|
24821
|
+
const authed = await loadAuthedSession(req, res, sessionId, rights);
|
|
24822
|
+
if (!authed) return null;
|
|
24823
|
+
const ownerId = authed.session.user ?? authed.user.id;
|
|
24824
|
+
return { ...authed, ownerId, ...buildSessionPrefixes(ownerId, sessionId) };
|
|
24825
|
+
};
|
|
23932
24826
|
function sanitizeFilename(name) {
|
|
23933
24827
|
const trimmed = name.trim();
|
|
23934
24828
|
if (!trimmed) return "";
|
|
@@ -23937,11 +24831,6 @@ ${style.markdown}` : params.prompt;
|
|
|
23937
24831
|
return trimmed.replace(/[\\/]/g, "_");
|
|
23938
24832
|
}
|
|
23939
24833
|
app.get("/sessions/:sessionId/files", async (req, res) => {
|
|
23940
|
-
const authResult = await requestValidators.authenticate(req);
|
|
23941
|
-
if (!authResult.user?.id) {
|
|
23942
|
-
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
23943
|
-
return;
|
|
23944
|
-
}
|
|
23945
24834
|
const sessionId = req.params.sessionId;
|
|
23946
24835
|
if (!sessionId) {
|
|
23947
24836
|
res.status(400).json({ detail: "Missing sessionId in path." });
|
|
@@ -23951,10 +24840,9 @@ ${style.markdown}` : params.prompt;
|
|
|
23951
24840
|
res.status(500).json({ detail: "File uploads are not configured." });
|
|
23952
24841
|
return;
|
|
23953
24842
|
}
|
|
23954
|
-
const
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
);
|
|
24843
|
+
const authed = await loadSessionFilesAuth(req, res, sessionId, "read");
|
|
24844
|
+
if (!authed) return;
|
|
24845
|
+
const { userSessionPrefix } = authed;
|
|
23958
24846
|
let objects;
|
|
23959
24847
|
try {
|
|
23960
24848
|
objects = await listS3ObjectsByPrefix(userSessionPrefix, config);
|
|
@@ -23984,11 +24872,6 @@ ${style.markdown}` : params.prompt;
|
|
|
23984
24872
|
app.post(
|
|
23985
24873
|
"/sessions/:sessionId/files/upload-sign",
|
|
23986
24874
|
async (req, res) => {
|
|
23987
|
-
const authResult = await requestValidators.authenticate(req);
|
|
23988
|
-
if (!authResult.user?.id) {
|
|
23989
|
-
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
23990
|
-
return;
|
|
23991
|
-
}
|
|
23992
24875
|
const sessionId = req.params.sessionId;
|
|
23993
24876
|
if (!sessionId) {
|
|
23994
24877
|
res.status(400).json({ detail: "Missing sessionId in path." });
|
|
@@ -23998,6 +24881,8 @@ ${style.markdown}` : params.prompt;
|
|
|
23998
24881
|
res.status(500).json({ detail: "File uploads are not configured." });
|
|
23999
24882
|
return;
|
|
24000
24883
|
}
|
|
24884
|
+
const authed = await loadSessionFilesAuth(req, res, sessionId, "write");
|
|
24885
|
+
if (!authed) return;
|
|
24001
24886
|
const { filename, contentType } = req.body ?? {};
|
|
24002
24887
|
if (!filename || typeof filename !== "string") {
|
|
24003
24888
|
res.status(400).json({ detail: "Missing filename in request body." });
|
|
@@ -24012,11 +24897,7 @@ ${style.markdown}` : params.prompt;
|
|
|
24012
24897
|
res.status(400).json({ detail: "Missing contentType in request body." });
|
|
24013
24898
|
return;
|
|
24014
24899
|
}
|
|
24015
|
-
const
|
|
24016
|
-
authResult.user.id,
|
|
24017
|
-
sessionId
|
|
24018
|
-
);
|
|
24019
|
-
const fullKey = `${fullSessionPrefix}${safeName}`;
|
|
24900
|
+
const fullKey = `${authed.fullSessionPrefix}${safeName}`;
|
|
24020
24901
|
const uploadUrl = await getS3SignedUploadUrl(fullKey, contentType, config);
|
|
24021
24902
|
res.json({ uploadUrl, key: fullKey });
|
|
24022
24903
|
}
|
|
@@ -24024,11 +24905,6 @@ ${style.markdown}` : params.prompt;
|
|
|
24024
24905
|
app.post(
|
|
24025
24906
|
"/sessions/:sessionId/files/sync-to-sandbox",
|
|
24026
24907
|
async (req, res) => {
|
|
24027
|
-
const authResult = await requestValidators.authenticate(req);
|
|
24028
|
-
if (!authResult.user?.id) {
|
|
24029
|
-
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24030
|
-
return;
|
|
24031
|
-
}
|
|
24032
24908
|
const sessionId = req.params.sessionId;
|
|
24033
24909
|
if (!sessionId) {
|
|
24034
24910
|
res.status(400).json({ detail: "Missing sessionId in path." });
|
|
@@ -24039,18 +24915,16 @@ ${style.markdown}` : params.prompt;
|
|
|
24039
24915
|
res.status(400).json({ detail: "Missing key in request body." });
|
|
24040
24916
|
return;
|
|
24041
24917
|
}
|
|
24042
|
-
const
|
|
24043
|
-
|
|
24044
|
-
|
|
24045
|
-
);
|
|
24046
|
-
if (!key.startsWith(fullSessionPrefix)) {
|
|
24918
|
+
const authed = await loadSessionFilesAuth(req, res, sessionId, "write");
|
|
24919
|
+
if (!authed) return;
|
|
24920
|
+
if (!key.startsWith(authed.fullSessionPrefix)) {
|
|
24047
24921
|
res.status(403).json({ detail: "Key does not belong to this session." });
|
|
24048
24922
|
return;
|
|
24049
24923
|
}
|
|
24050
24924
|
try {
|
|
24051
24925
|
const result = await downloadKeyIntoSandbox({
|
|
24052
24926
|
sessionId,
|
|
24053
|
-
userId:
|
|
24927
|
+
userId: authed.ownerId,
|
|
24054
24928
|
fullS3Key: key,
|
|
24055
24929
|
config
|
|
24056
24930
|
});
|
|
@@ -24064,11 +24938,6 @@ ${style.markdown}` : params.prompt;
|
|
|
24064
24938
|
app.delete(
|
|
24065
24939
|
"/sessions/:sessionId/files",
|
|
24066
24940
|
async (req, res) => {
|
|
24067
|
-
const authResult = await requestValidators.authenticate(req);
|
|
24068
|
-
if (!authResult.user?.id) {
|
|
24069
|
-
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24070
|
-
return;
|
|
24071
|
-
}
|
|
24072
24941
|
const sessionId = req.params.sessionId;
|
|
24073
24942
|
if (!sessionId) {
|
|
24074
24943
|
res.status(400).json({ detail: "Missing sessionId in path." });
|
|
@@ -24079,11 +24948,9 @@ ${style.markdown}` : params.prompt;
|
|
|
24079
24948
|
res.status(400).json({ detail: "Missing or invalid 'key' query parameter." });
|
|
24080
24949
|
return;
|
|
24081
24950
|
}
|
|
24082
|
-
const
|
|
24083
|
-
|
|
24084
|
-
|
|
24085
|
-
);
|
|
24086
|
-
if (!key.startsWith(fullSessionPrefix)) {
|
|
24951
|
+
const authed = await loadSessionFilesAuth(req, res, sessionId, "write");
|
|
24952
|
+
if (!authed) return;
|
|
24953
|
+
if (!key.startsWith(authed.fullSessionPrefix)) {
|
|
24087
24954
|
res.status(403).json({ detail: "Key does not belong to this session." });
|
|
24088
24955
|
return;
|
|
24089
24956
|
}
|
|
@@ -24102,11 +24969,6 @@ ${style.markdown}` : params.prompt;
|
|
|
24102
24969
|
if (!req.headers.authorization && typeof req.query.auth === "string") {
|
|
24103
24970
|
req.headers.authorization = `Bearer ${req.query.auth}`;
|
|
24104
24971
|
}
|
|
24105
|
-
const authResult = await requestValidators.authenticate(req);
|
|
24106
|
-
if (!authResult.user?.id) {
|
|
24107
|
-
res.status(authResult.code ?? 401).json({ detail: authResult.message });
|
|
24108
|
-
return;
|
|
24109
|
-
}
|
|
24110
24972
|
const sessionId = req.params.sessionId;
|
|
24111
24973
|
if (!sessionId) {
|
|
24112
24974
|
res.status(400).json({ detail: "Missing sessionId in path." });
|
|
@@ -24117,11 +24979,9 @@ ${style.markdown}` : params.prompt;
|
|
|
24117
24979
|
res.status(400).json({ detail: "Missing or invalid 'key' query parameter." });
|
|
24118
24980
|
return;
|
|
24119
24981
|
}
|
|
24120
|
-
const
|
|
24121
|
-
|
|
24122
|
-
|
|
24123
|
-
);
|
|
24124
|
-
if (!key.startsWith(fullSessionPrefix)) {
|
|
24982
|
+
const authed = await loadSessionFilesAuth(req, res, sessionId, "read");
|
|
24983
|
+
if (!authed) return;
|
|
24984
|
+
if (!key.startsWith(authed.fullSessionPrefix)) {
|
|
24125
24985
|
res.status(403).json({ detail: "Key does not belong to this session." });
|
|
24126
24986
|
return;
|
|
24127
24987
|
}
|
|
@@ -24225,7 +25085,7 @@ ${style.markdown}` : params.prompt;
|
|
|
24225
25085
|
` - tracking.json tracking events linked to the user`,
|
|
24226
25086
|
``
|
|
24227
25087
|
].join("\n");
|
|
24228
|
-
const zip = new
|
|
25088
|
+
const zip = new import_jszip3.default();
|
|
24229
25089
|
zip.file("README.txt", readme);
|
|
24230
25090
|
zip.file("user_data.json", JSON.stringify(userExport, null, 2));
|
|
24231
25091
|
zip.file("sessions.json", JSON.stringify(sessionsWithMessages, null, 2));
|
|
@@ -24516,7 +25376,7 @@ init_check_record_access();
|
|
|
24516
25376
|
init_resolve_model();
|
|
24517
25377
|
init_supervisor();
|
|
24518
25378
|
init_client();
|
|
24519
|
-
var
|
|
25379
|
+
var import_zod16 = require("zod");
|
|
24520
25380
|
init_convert_exulu_tools_to_ai_sdk_tools();
|
|
24521
25381
|
init_singleton();
|
|
24522
25382
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
@@ -24590,7 +25450,7 @@ var ExuluMCP = class {
|
|
|
24590
25450
|
title: tool4.name + " agent",
|
|
24591
25451
|
description: tool4.description,
|
|
24592
25452
|
inputSchema: {
|
|
24593
|
-
inputs: tool4.inputSchema ||
|
|
25453
|
+
inputs: tool4.inputSchema || import_zod16.z.object({})
|
|
24594
25454
|
}
|
|
24595
25455
|
},
|
|
24596
25456
|
async ({ inputs }, args) => {
|
|
@@ -24642,7 +25502,7 @@ var ExuluMCP = class {
|
|
|
24642
25502
|
title: "Get List of Prompt Templates",
|
|
24643
25503
|
description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
|
|
24644
25504
|
inputSchema: {
|
|
24645
|
-
inputs:
|
|
25505
|
+
inputs: import_zod16.z.object({})
|
|
24646
25506
|
}
|
|
24647
25507
|
},
|
|
24648
25508
|
async ({ inputs }, args) => {
|
|
@@ -24688,8 +25548,8 @@ var ExuluMCP = class {
|
|
|
24688
25548
|
title: "Get Prompt Template Details",
|
|
24689
25549
|
description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
|
|
24690
25550
|
inputSchema: {
|
|
24691
|
-
inputs:
|
|
24692
|
-
id:
|
|
25551
|
+
inputs: import_zod16.z.object({
|
|
25552
|
+
id: import_zod16.z.string().describe("The ID of the prompt template to retrieve")
|
|
24693
25553
|
})
|
|
24694
25554
|
}
|
|
24695
25555
|
},
|
|
@@ -25804,7 +26664,7 @@ var ExuluEval = class {
|
|
|
25804
26664
|
// src/templates/evals/index.ts
|
|
25805
26665
|
init_resolve_model();
|
|
25806
26666
|
init_singleton();
|
|
25807
|
-
var
|
|
26667
|
+
var import_zod17 = require("zod");
|
|
25808
26668
|
var import_ai17 = require("ai");
|
|
25809
26669
|
var llmAsJudgeEval = () => {
|
|
25810
26670
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
@@ -25857,8 +26717,8 @@ var llmAsJudgeEval = () => {
|
|
|
25857
26717
|
prompt,
|
|
25858
26718
|
maxRetries: 2,
|
|
25859
26719
|
output: import_ai17.Output.object({
|
|
25860
|
-
schema:
|
|
25861
|
-
score:
|
|
26720
|
+
schema: import_zod17.z.object({
|
|
26721
|
+
score: import_zod17.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
25862
26722
|
})
|
|
25863
26723
|
})
|
|
25864
26724
|
});
|
|
@@ -26089,15 +26949,15 @@ Usage:
|
|
|
26089
26949
|
- If no todos exist yet, an empty list will be returned`;
|
|
26090
26950
|
|
|
26091
26951
|
// src/templates/tools/todo/todo.ts
|
|
26092
|
-
var
|
|
26952
|
+
var import_zod18 = __toESM(require("zod"), 1);
|
|
26093
26953
|
init_tool();
|
|
26094
26954
|
init_check_record_access();
|
|
26095
26955
|
init_client();
|
|
26096
|
-
var TodoSchema =
|
|
26097
|
-
content:
|
|
26098
|
-
status:
|
|
26099
|
-
priority:
|
|
26100
|
-
id:
|
|
26956
|
+
var TodoSchema = import_zod18.default.object({
|
|
26957
|
+
content: import_zod18.default.string().describe("Brief description of the task"),
|
|
26958
|
+
status: import_zod18.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
|
|
26959
|
+
priority: import_zod18.default.string().describe("Priority level of the task: high, medium, low"),
|
|
26960
|
+
id: import_zod18.default.string().describe("Unique identifier for the todo item")
|
|
26101
26961
|
});
|
|
26102
26962
|
var TodoWriteTool = new ExuluTool({
|
|
26103
26963
|
id: "todo_write",
|
|
@@ -26113,8 +26973,8 @@ var TodoWriteTool = new ExuluTool({
|
|
|
26113
26973
|
default: todowrite_default
|
|
26114
26974
|
}
|
|
26115
26975
|
],
|
|
26116
|
-
inputSchema:
|
|
26117
|
-
todos:
|
|
26976
|
+
inputSchema: import_zod18.default.object({
|
|
26977
|
+
todos: import_zod18.default.array(TodoSchema).describe("The updated todo list")
|
|
26118
26978
|
}),
|
|
26119
26979
|
execute: async (inputs) => {
|
|
26120
26980
|
const { sessionID, todos, user } = inputs;
|
|
@@ -26149,7 +27009,7 @@ var TodoReadTool = new ExuluTool({
|
|
|
26149
27009
|
id: "todo_read",
|
|
26150
27010
|
name: "Todo Read",
|
|
26151
27011
|
description: "Use this tool to read your todo list",
|
|
26152
|
-
inputSchema:
|
|
27012
|
+
inputSchema: import_zod18.default.object({}),
|
|
26153
27013
|
type: "function",
|
|
26154
27014
|
category: "todo",
|
|
26155
27015
|
config: [
|
|
@@ -26194,7 +27054,7 @@ init_cjs_shims();
|
|
|
26194
27054
|
var questionread_default = 'Use this tool to read questions you\'ve asked and check if they\'ve been answered by the user. This tool helps you track the status of questions and retrieve the user\'s selected answers.\n\n## When to Use This Tool\n\nUse this tool proactively in these situations:\n- After asking a question to check if the user has responded\n- To retrieve the user\'s answer before proceeding with implementation\n- To review all questions and answers in the current session\n- When you need to reference a previous answer\n\n## How It Works\n\n- This tool takes no parameters (leave the input blank or empty)\n- Returns an array of all questions in the session\n- Each question includes:\n - `id`: Unique identifier for the question\n - `question`: The question text\n - `answerOptions`: Array of answer options with their IDs and text\n - `status`: Either "pending" (not answered) or "answered"\n - `selectedAnswerId`: The ID of the chosen answer (only present if answered)\n\n## Usage Pattern\n\nTypically you\'ll:\n1. Use Question Ask to pose a question\n2. Wait for the user to respond\n3. Use Question Read to check the answer\n4. Find the selected answer by matching the `selectedAnswerId` with an option in `answerOptions`\n5. Proceed with implementation based on the user\'s choice\n\n## Example Response\n\n```json\n[\n {\n "id": "question123",\n "question": "Which authentication method would you like to implement?",\n "answerOptions": [\n { "id": "ans1", "text": "JWT tokens" },\n { "id": "ans2", "text": "OAuth 2.0" },\n { "id": "ans3", "text": "Session-based auth" },\n { "id": "ans4", "text": "None of the above..." }\n ],\n "status": "answered",\n "selectedAnswerId": "ans1"\n }\n]\n```\n\nIn this example, the user selected "JWT tokens" (id: ans1).\n\n## Important Notes\n\n- If no questions exist in the session, an empty array will be returned\n- Questions remain in the session even after being answered for reference\n- Use the `selectedAnswerId` to find which answer option the user chose by matching it against the `id` field in `answerOptions`\n';
|
|
26195
27055
|
|
|
26196
27056
|
// src/templates/tools/question/question.ts
|
|
26197
|
-
var
|
|
27057
|
+
var import_zod20 = __toESM(require("zod"), 1);
|
|
26198
27058
|
init_tool();
|
|
26199
27059
|
init_client();
|
|
26200
27060
|
|
|
@@ -26286,21 +27146,21 @@ After asking a question, use the Question Read tool to check if the user has ans
|
|
|
26286
27146
|
`;
|
|
26287
27147
|
|
|
26288
27148
|
// src/templates/tools/question/question-ask.ts
|
|
26289
|
-
var
|
|
27149
|
+
var import_zod19 = __toESM(require("zod"), 1);
|
|
26290
27150
|
init_tool();
|
|
26291
27151
|
init_check_record_access();
|
|
26292
27152
|
init_client();
|
|
26293
27153
|
var import_node_crypto11 = require("crypto");
|
|
26294
|
-
var AnswerOptionSchema =
|
|
26295
|
-
id:
|
|
26296
|
-
text:
|
|
27154
|
+
var AnswerOptionSchema = import_zod19.default.object({
|
|
27155
|
+
id: import_zod19.default.string().describe("Unique identifier for the answer option"),
|
|
27156
|
+
text: import_zod19.default.string().describe("The text of the answer option")
|
|
26297
27157
|
});
|
|
26298
|
-
var _QuestionSchema =
|
|
26299
|
-
id:
|
|
26300
|
-
question:
|
|
26301
|
-
answerOptions:
|
|
26302
|
-
selectedAnswerId:
|
|
26303
|
-
status:
|
|
27158
|
+
var _QuestionSchema = import_zod19.default.object({
|
|
27159
|
+
id: import_zod19.default.string().describe("Unique identifier for the question"),
|
|
27160
|
+
question: import_zod19.default.string().describe("The question to ask the user"),
|
|
27161
|
+
answerOptions: import_zod19.default.array(AnswerOptionSchema).describe("Array of possible answer options"),
|
|
27162
|
+
selectedAnswerId: import_zod19.default.string().optional().describe("The ID of the answer option selected by the user"),
|
|
27163
|
+
status: import_zod19.default.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
|
|
26304
27164
|
});
|
|
26305
27165
|
var QuestionAskTool = new ExuluTool({
|
|
26306
27166
|
id: "question_ask",
|
|
@@ -26317,9 +27177,9 @@ var QuestionAskTool = new ExuluTool({
|
|
|
26317
27177
|
default: questionask_default
|
|
26318
27178
|
}
|
|
26319
27179
|
],
|
|
26320
|
-
inputSchema:
|
|
26321
|
-
question:
|
|
26322
|
-
answerOptions:
|
|
27180
|
+
inputSchema: import_zod19.default.object({
|
|
27181
|
+
question: import_zod19.default.string().describe("The question to ask the user"),
|
|
27182
|
+
answerOptions: import_zod19.default.array(import_zod19.default.string()).describe("Array of possible answer options (strings)")
|
|
26323
27183
|
}),
|
|
26324
27184
|
execute: async (inputs) => {
|
|
26325
27185
|
const { sessionID, question, answerOptions, user } = inputs;
|
|
@@ -26392,7 +27252,7 @@ var QuestionReadTool = new ExuluTool({
|
|
|
26392
27252
|
name: "Question Read",
|
|
26393
27253
|
needsApproval: false,
|
|
26394
27254
|
description: "Use this tool to read questions and their answers",
|
|
26395
|
-
inputSchema:
|
|
27255
|
+
inputSchema: import_zod20.default.object({}),
|
|
26396
27256
|
type: "function",
|
|
26397
27257
|
category: "question",
|
|
26398
27258
|
config: [
|
|
@@ -26424,15 +27284,15 @@ var questionTools = [QuestionAskTool, QuestionReadTool];
|
|
|
26424
27284
|
// src/templates/tools/perplexity.ts
|
|
26425
27285
|
init_cjs_shims();
|
|
26426
27286
|
init_tool();
|
|
26427
|
-
var
|
|
27287
|
+
var import_zod21 = __toESM(require("zod"), 1);
|
|
26428
27288
|
var import_perplexity_ai = __toESM(require("@perplexity-ai/perplexity_ai"), 1);
|
|
26429
27289
|
var internetSearchTool = new ExuluTool({
|
|
26430
27290
|
id: "internet_search",
|
|
26431
27291
|
name: "Internet Search",
|
|
26432
27292
|
description: "Search the internet for information.",
|
|
26433
|
-
inputSchema:
|
|
26434
|
-
query:
|
|
26435
|
-
search_recency_filter:
|
|
27293
|
+
inputSchema: import_zod21.default.object({
|
|
27294
|
+
query: import_zod21.default.string().describe("The query to the tool."),
|
|
27295
|
+
search_recency_filter: import_zod21.default.enum(["day", "week", "month", "year"]).optional().describe("The recency filter for the search, can be day, week, month or year.")
|
|
26436
27296
|
}),
|
|
26437
27297
|
category: "internet_search",
|
|
26438
27298
|
type: "web_search",
|
|
@@ -26527,7 +27387,7 @@ var perplexityTools = [internetSearchTool];
|
|
|
26527
27387
|
init_cjs_shims();
|
|
26528
27388
|
init_tool();
|
|
26529
27389
|
var nodemailer = __toESM(require("nodemailer"), 1);
|
|
26530
|
-
var
|
|
27390
|
+
var import_zod22 = require("zod");
|
|
26531
27391
|
var transporter = null;
|
|
26532
27392
|
function getTransporter(config) {
|
|
26533
27393
|
if (!transporter) {
|
|
@@ -26551,11 +27411,11 @@ var emailTool = new ExuluTool({
|
|
|
26551
27411
|
id: "email",
|
|
26552
27412
|
name: "Email",
|
|
26553
27413
|
description: "Send an email.",
|
|
26554
|
-
inputSchema:
|
|
26555
|
-
recipient:
|
|
26556
|
-
subject:
|
|
26557
|
-
html:
|
|
26558
|
-
text:
|
|
27414
|
+
inputSchema: import_zod22.z.object({
|
|
27415
|
+
recipient: import_zod22.z.string().describe("The recipient of the email."),
|
|
27416
|
+
subject: import_zod22.z.string().describe("The subject of the email."),
|
|
27417
|
+
html: import_zod22.z.string().describe("The HTML body of the email."),
|
|
27418
|
+
text: import_zod22.z.string().describe("The text body of the email.")
|
|
26559
27419
|
}),
|
|
26560
27420
|
type: "function",
|
|
26561
27421
|
config: [{
|
|
@@ -26626,7 +27486,7 @@ init_tool();
|
|
|
26626
27486
|
init_supervisor();
|
|
26627
27487
|
init_client();
|
|
26628
27488
|
init_check_record_access();
|
|
26629
|
-
var
|
|
27489
|
+
var import_zod23 = require("zod");
|
|
26630
27490
|
var _cachedImageModels;
|
|
26631
27491
|
var setCachedImageModels = (models2) => {
|
|
26632
27492
|
_cachedImageModels = models2;
|
|
@@ -26687,8 +27547,8 @@ var createImageGenerationWidgetTool = (models2) => {
|
|
|
26687
27547
|
needsApproval: false,
|
|
26688
27548
|
type: "function",
|
|
26689
27549
|
config: [],
|
|
26690
|
-
inputSchema:
|
|
26691
|
-
prompt:
|
|
27550
|
+
inputSchema: import_zod23.z.object({
|
|
27551
|
+
prompt: import_zod23.z.string().describe(
|
|
26692
27552
|
"Initial image prompt. The user can edit it before generating."
|
|
26693
27553
|
)
|
|
26694
27554
|
}),
|
|
@@ -26727,7 +27587,7 @@ var createImageGenerationWidgetTool = (models2) => {
|
|
|
26727
27587
|
};
|
|
26728
27588
|
|
|
26729
27589
|
// src/exulu/app/index.ts
|
|
26730
|
-
var
|
|
27590
|
+
var import_node_path10 = require("path");
|
|
26731
27591
|
|
|
26732
27592
|
// src/validators/postgres-name.ts
|
|
26733
27593
|
init_cjs_shims();
|
|
@@ -27113,7 +27973,7 @@ var ExuluApp = class {
|
|
|
27113
27973
|
const imageGenerationTools = [];
|
|
27114
27974
|
const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
|
|
27115
27975
|
if (isLiteLLMEnabled() && s3Configured) {
|
|
27116
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0,
|
|
27976
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path10.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
27117
27977
|
const imageModels = parseImageGenerationModels(configPath);
|
|
27118
27978
|
if (imageModels.length > 0) {
|
|
27119
27979
|
console.log(
|
|
@@ -28573,8 +29433,8 @@ init_cjs_shims();
|
|
|
28573
29433
|
// src/exulu/litellm/db-init.ts
|
|
28574
29434
|
init_cjs_shims();
|
|
28575
29435
|
var import_node_fs9 = require("fs");
|
|
28576
|
-
var
|
|
28577
|
-
var
|
|
29436
|
+
var import_node_path11 = require("path");
|
|
29437
|
+
var import_node_child_process6 = require("child_process");
|
|
28578
29438
|
var import_pg = require("pg");
|
|
28579
29439
|
|
|
28580
29440
|
// src/exulu/litellm/db-setup-check.ts
|
|
@@ -28643,7 +29503,7 @@ ${WARNING_BANNER}`);
|
|
|
28643
29503
|
};
|
|
28644
29504
|
var log5 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
|
|
28645
29505
|
var initLiteLLMDatabase = async (packageRoot) => {
|
|
28646
|
-
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0,
|
|
29506
|
+
const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path11.resolve)(process.cwd(), "./config.litellm.yaml");
|
|
28647
29507
|
const safety = checkLiteLLMDatabaseSafety(configPath);
|
|
28648
29508
|
if (safety.ok && safety.reason === "no-litellm-db-mode") return;
|
|
28649
29509
|
if (!safety.ok && safety.reason === "unparseable-url") {
|
|
@@ -28781,9 +29641,9 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
28781
29641
|
]);
|
|
28782
29642
|
return;
|
|
28783
29643
|
}
|
|
28784
|
-
const venvBin = (0,
|
|
28785
|
-
const prismaCli = (0,
|
|
28786
|
-
const venvLibDir = (0,
|
|
29644
|
+
const venvBin = (0, import_node_path11.resolve)(packageRoot, "ee/python/.venv/bin");
|
|
29645
|
+
const prismaCli = (0, import_node_path11.resolve)(venvBin, "prisma");
|
|
29646
|
+
const venvLibDir = (0, import_node_path11.resolve)(packageRoot, "ee/python/.venv/lib");
|
|
28787
29647
|
const pythonVersionDir = (0, import_node_fs9.existsSync)(venvLibDir) ? (0, import_node_fs9.readdirSync)(venvLibDir).find((entry) => /^python3\.\d+$/.test(entry)) : void 0;
|
|
28788
29648
|
if (!pythonVersionDir) {
|
|
28789
29649
|
warn2([
|
|
@@ -28793,12 +29653,12 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
28793
29653
|
]);
|
|
28794
29654
|
return;
|
|
28795
29655
|
}
|
|
28796
|
-
const litellmProxyDir = (0,
|
|
29656
|
+
const litellmProxyDir = (0, import_node_path11.resolve)(
|
|
28797
29657
|
venvLibDir,
|
|
28798
29658
|
pythonVersionDir,
|
|
28799
29659
|
"site-packages/litellm/proxy"
|
|
28800
29660
|
);
|
|
28801
|
-
const schemaPath = (0,
|
|
29661
|
+
const schemaPath = (0, import_node_path11.resolve)(litellmProxyDir, "schema.prisma");
|
|
28802
29662
|
if (!(0, import_node_fs9.existsSync)(prismaCli)) {
|
|
28803
29663
|
warn2([
|
|
28804
29664
|
`Prisma CLI not found at ${prismaCli}.`,
|
|
@@ -28815,7 +29675,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
|
|
|
28815
29675
|
return;
|
|
28816
29676
|
}
|
|
28817
29677
|
log5("Running `prisma db push` against LiteLLM's schema\u2026");
|
|
28818
|
-
const result = (0,
|
|
29678
|
+
const result = (0, import_node_child_process6.spawnSync)(prismaCli, ["db", "push", "--skip-generate"], {
|
|
28819
29679
|
cwd: litellmProxyDir,
|
|
28820
29680
|
env: {
|
|
28821
29681
|
...process.env,
|
|
@@ -29387,14 +30247,14 @@ init_cjs_shims();
|
|
|
29387
30247
|
var fs4 = __toESM(require("fs"), 1);
|
|
29388
30248
|
var path = __toESM(require("path"), 1);
|
|
29389
30249
|
var import_ai18 = require("ai");
|
|
29390
|
-
var
|
|
30250
|
+
var import_zod24 = require("zod");
|
|
29391
30251
|
var import_p_limit = __toESM(require("p-limit"), 1);
|
|
29392
30252
|
var import_crypto2 = require("crypto");
|
|
29393
30253
|
init_with_retry();
|
|
29394
30254
|
var mammoth = __toESM(require("mammoth"), 1);
|
|
29395
30255
|
var import_turndown = __toESM(require("turndown"), 1);
|
|
29396
30256
|
var import_word_extractor = __toESM(require("word-extractor"), 1);
|
|
29397
|
-
var
|
|
30257
|
+
var import_officeparser3 = require("officeparser");
|
|
29398
30258
|
init_entitlements();
|
|
29399
30259
|
|
|
29400
30260
|
// src/utils/python-executor.ts
|
|
@@ -29854,15 +30714,15 @@ If the page contains a flow-chart, schematic, technical drawing or control board
|
|
|
29854
30714
|
const result = await (0, import_ai18.generateText)({
|
|
29855
30715
|
model,
|
|
29856
30716
|
output: import_ai18.Output.object({
|
|
29857
|
-
schema:
|
|
29858
|
-
needs_correction:
|
|
29859
|
-
corrected_text:
|
|
29860
|
-
current_page_table:
|
|
29861
|
-
headers:
|
|
29862
|
-
is_continuation:
|
|
30717
|
+
schema: import_zod24.z.object({
|
|
30718
|
+
needs_correction: import_zod24.z.boolean(),
|
|
30719
|
+
corrected_text: import_zod24.z.string().nullable(),
|
|
30720
|
+
current_page_table: import_zod24.z.object({
|
|
30721
|
+
headers: import_zod24.z.array(import_zod24.z.string()),
|
|
30722
|
+
is_continuation: import_zod24.z.boolean()
|
|
29863
30723
|
}).nullable(),
|
|
29864
|
-
confidence:
|
|
29865
|
-
reasoning:
|
|
30724
|
+
confidence: import_zod24.z.enum(["high", "medium", "low"]),
|
|
30725
|
+
reasoning: import_zod24.z.string()
|
|
29866
30726
|
})
|
|
29867
30727
|
}),
|
|
29868
30728
|
messages: [
|
|
@@ -30111,7 +30971,7 @@ ${setupResult.output || ""}`);
|
|
|
30111
30971
|
const jsonContent = await fs4.promises.readFile(paths.json, "utf-8");
|
|
30112
30972
|
json = JSON.parse(jsonContent);
|
|
30113
30973
|
} else if (config?.processor.name === "officeparser") {
|
|
30114
|
-
const text = await (0,
|
|
30974
|
+
const text = await (0, import_officeparser3.parseOfficeAsync)(buffer, {
|
|
30115
30975
|
outputErrorToConsole: false,
|
|
30116
30976
|
newlineDelimiter: "\n"
|
|
30117
30977
|
});
|