@infuro/cms-core 1.0.19 → 1.0.20

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/api.cjs CHANGED
@@ -29,6 +29,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  mod
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+ var __decorateClass = (decorators, target, key, kind) => {
33
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
34
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
35
+ if (decorator = decorators[i])
36
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
37
+ if (kind && result) __defProp(target, key, result);
38
+ return result;
39
+ };
32
40
 
33
41
  // src/plugins/erp/erp-queue.ts
34
42
  async function queueErp(cms, payload) {
@@ -432,6 +440,7 @@ __export(api_exports, {
432
440
  createForgotPasswordHandler: () => createForgotPasswordHandler,
433
441
  createFormBySlugHandler: () => createFormBySlugHandler,
434
442
  createInviteAcceptHandler: () => createInviteAcceptHandler,
443
+ createLlmAgentKnowledgeHandlers: () => createLlmAgentKnowledgeHandlers,
435
444
  createMediaZipExtractHandler: () => createMediaZipExtractHandler,
436
445
  createSetPasswordHandler: () => createSetPasswordHandler,
437
446
  createSettingsApiHandlers: () => createSettingsApiHandlers,
@@ -441,7 +450,11 @@ __export(api_exports, {
441
450
  createUserAvatarHandler: () => createUserAvatarHandler,
442
451
  createUserProfileHandler: () => createUserProfileHandler,
443
452
  createUsersApiHandlers: () => createUsersApiHandlers,
444
- getPublicSettingsGroup: () => getPublicSettingsGroup
453
+ getPublicSettingsGroup: () => getPublicSettingsGroup,
454
+ mergeGuardrailsIntoSystemPrompt: () => mergeGuardrailsIntoSystemPrompt,
455
+ parseLlmAgentValidationRules: () => parseLlmAgentValidationRules,
456
+ validateUserMessageAgainstAgentRules: () => validateUserMessageAgainstAgentRules,
457
+ validateUserMessageAgainstStructuredRules: () => validateUserMessageAgainstStructuredRules
445
458
  });
446
459
  module.exports = __toCommonJS(api_exports);
447
460
 
@@ -1364,7 +1377,7 @@ function createUserAuthApiRouter(config) {
1364
1377
  }
1365
1378
 
1366
1379
  // src/api/cms-handlers.ts
1367
- var import_typeorm4 = require("typeorm");
1380
+ var import_typeorm5 = require("typeorm");
1368
1381
  init_email_queue();
1369
1382
  init_erp_queue();
1370
1383
 
@@ -1384,6 +1397,86 @@ async function assertCaptchaOk(getCms, body, req, json) {
1384
1397
  return json({ error: result.message }, { status: result.status });
1385
1398
  }
1386
1399
 
1400
+ // src/entities/llm-agent.entity.ts
1401
+ var import_typeorm3 = require("typeorm");
1402
+ var LlmAgent = class {
1403
+ id;
1404
+ name;
1405
+ slug;
1406
+ systemInstruction;
1407
+ model;
1408
+ temperature;
1409
+ maxTokens;
1410
+ validationRules;
1411
+ enabled;
1412
+ createdAt;
1413
+ updatedAt;
1414
+ deletedAt;
1415
+ deleted;
1416
+ createdBy;
1417
+ updatedBy;
1418
+ deletedBy;
1419
+ };
1420
+ __decorateClass([
1421
+ (0, import_typeorm3.PrimaryGeneratedColumn)()
1422
+ ], LlmAgent.prototype, "id", 2);
1423
+ __decorateClass([
1424
+ (0, import_typeorm3.Column)("varchar")
1425
+ ], LlmAgent.prototype, "name", 2);
1426
+ __decorateClass([
1427
+ (0, import_typeorm3.Column)("varchar")
1428
+ ], LlmAgent.prototype, "slug", 2);
1429
+ __decorateClass([
1430
+ (0, import_typeorm3.Column)("text", { name: "system_instruction", default: "" })
1431
+ ], LlmAgent.prototype, "systemInstruction", 2);
1432
+ __decorateClass([
1433
+ (0, import_typeorm3.Column)("varchar", { nullable: true })
1434
+ ], LlmAgent.prototype, "model", 2);
1435
+ __decorateClass([
1436
+ (0, import_typeorm3.Column)("double precision", { name: "temperature", nullable: true })
1437
+ ], LlmAgent.prototype, "temperature", 2);
1438
+ __decorateClass([
1439
+ (0, import_typeorm3.Column)("int", { name: "max_tokens", nullable: true })
1440
+ ], LlmAgent.prototype, "maxTokens", 2);
1441
+ __decorateClass([
1442
+ (0, import_typeorm3.Column)("text", { name: "validation_rules", nullable: true })
1443
+ ], LlmAgent.prototype, "validationRules", 2);
1444
+ __decorateClass([
1445
+ (0, import_typeorm3.Column)("boolean", { default: true })
1446
+ ], LlmAgent.prototype, "enabled", 2);
1447
+ __decorateClass([
1448
+ (0, import_typeorm3.Column)({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1449
+ ], LlmAgent.prototype, "createdAt", 2);
1450
+ __decorateClass([
1451
+ (0, import_typeorm3.Column)({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
1452
+ ], LlmAgent.prototype, "updatedAt", 2);
1453
+ __decorateClass([
1454
+ (0, import_typeorm3.Column)({ type: "timestamp", nullable: true })
1455
+ ], LlmAgent.prototype, "deletedAt", 2);
1456
+ __decorateClass([
1457
+ (0, import_typeorm3.Column)("boolean", { default: false })
1458
+ ], LlmAgent.prototype, "deleted", 2);
1459
+ __decorateClass([
1460
+ (0, import_typeorm3.Column)("int", { nullable: true })
1461
+ ], LlmAgent.prototype, "createdBy", 2);
1462
+ __decorateClass([
1463
+ (0, import_typeorm3.Column)("int", { nullable: true })
1464
+ ], LlmAgent.prototype, "updatedBy", 2);
1465
+ __decorateClass([
1466
+ (0, import_typeorm3.Column)("int", { nullable: true })
1467
+ ], LlmAgent.prototype, "deletedBy", 2);
1468
+ LlmAgent = __decorateClass([
1469
+ (0, import_typeorm3.Entity)("llm_agents")
1470
+ ], LlmAgent);
1471
+ function llmAgentToChatAgentOptions(agent) {
1472
+ return {
1473
+ systemPrompt: agent.systemInstruction?.trim() || void 0,
1474
+ model: agent.model?.trim() || void 0,
1475
+ temperature: agent.temperature ?? void 0,
1476
+ max_tokens: agent.maxTokens ?? void 0
1477
+ };
1478
+ }
1479
+
1387
1480
  // src/lib/media-folder-path.ts
1388
1481
  function sanitizeMediaFolderPath(input) {
1389
1482
  if (input == null) return "";
@@ -1415,7 +1508,7 @@ async function relativePathFromMediaParentId(dataSource, entityMap, parentId) {
1415
1508
  }
1416
1509
 
1417
1510
  // src/lib/media-zip-extract.ts
1418
- var import_typeorm3 = require("typeorm");
1511
+ var import_typeorm4 = require("typeorm");
1419
1512
  var ZIP_MIME_TYPES = /* @__PURE__ */ new Set(["application/zip", "application/x-zip-compressed"]);
1420
1513
  var MAX_ENTRIES = 2e3;
1421
1514
  var MAX_TOTAL_UNCOMPRESSED = 80 * 1024 * 1024;
@@ -1466,7 +1559,7 @@ function guessMimeType(fileName) {
1466
1559
  async function findOrCreateFolder(dataSource, entityMap, parentId, name) {
1467
1560
  const safe = sanitizeStorageSegment(name);
1468
1561
  const repo = dataSource.getRepository(entityMap.media);
1469
- const where = parentId == null ? { kind: "folder", filename: safe, parentId: (0, import_typeorm3.IsNull)() } : { kind: "folder", filename: safe, parentId };
1562
+ const where = parentId == null ? { kind: "folder", filename: safe, parentId: (0, import_typeorm4.IsNull)() } : { kind: "folder", filename: safe, parentId };
1470
1563
  const existing = await repo.findOne({ where });
1471
1564
  if (existing) return existing.id;
1472
1565
  const row = await repo.save(
@@ -1573,6 +1666,82 @@ async function extractZipMediaIntoParentTree(opts) {
1573
1666
  }
1574
1667
 
1575
1668
  // src/api/cms-handlers.ts
1669
+ function historyBeforeCurrentUser(history, currentUserContent) {
1670
+ const last = history[history.length - 1];
1671
+ if (last?.role === "user" && last.content === currentUserContent) {
1672
+ return history.slice(0, -1);
1673
+ }
1674
+ return [...history];
1675
+ }
1676
+ function pickStructuredRules(rules) {
1677
+ return {
1678
+ maxUserChars: rules.maxUserChars,
1679
+ maxMessageLength: rules.maxMessageLength,
1680
+ minUserChars: rules.minUserChars,
1681
+ minMessageLength: rules.minMessageLength,
1682
+ blockedSubstrings: rules.blockedSubstrings
1683
+ };
1684
+ }
1685
+ function guardrailsTextFromJsonObject(rules) {
1686
+ const g = typeof rules.guardrails === "string" && rules.guardrails.trim() || typeof rules.outputRules === "string" && rules.outputRules.trim() || typeof rules.outputInstructions === "string" && rules.outputInstructions.trim() || "";
1687
+ return g || null;
1688
+ }
1689
+ function parseLlmAgentValidationRules(validationRulesText) {
1690
+ const raw = validationRulesText?.trim();
1691
+ if (!raw) return { structured: {}, guardrailsForPrompt: null };
1692
+ try {
1693
+ const parsed = JSON.parse(raw);
1694
+ if (typeof parsed === "string") {
1695
+ const s = parsed.trim();
1696
+ return { structured: {}, guardrailsForPrompt: s || null };
1697
+ }
1698
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1699
+ return { structured: {}, guardrailsForPrompt: raw };
1700
+ }
1701
+ const rules = parsed;
1702
+ return {
1703
+ structured: pickStructuredRules(rules),
1704
+ guardrailsForPrompt: guardrailsTextFromJsonObject(rules)
1705
+ };
1706
+ } catch {
1707
+ return { structured: {}, guardrailsForPrompt: raw };
1708
+ }
1709
+ }
1710
+ function validateUserMessageAgainstStructuredRules(message, structured) {
1711
+ const maxLen = structured.maxUserChars ?? structured.maxMessageLength;
1712
+ if (typeof maxLen === "number" && Number.isFinite(maxLen) && maxLen >= 0 && message.length > maxLen) {
1713
+ return { ok: false, error: `Message exceeds maximum length (${maxLen} characters)` };
1714
+ }
1715
+ const minLen = structured.minUserChars ?? structured.minMessageLength;
1716
+ if (typeof minLen === "number" && Number.isFinite(minLen) && minLen > 0 && message.length < minLen) {
1717
+ return { ok: false, error: `Message is shorter than minimum length (${minLen} characters)` };
1718
+ }
1719
+ const blocked = structured.blockedSubstrings;
1720
+ if (Array.isArray(blocked) && blocked.length > 0) {
1721
+ const lower = message.toLowerCase();
1722
+ for (const s of blocked) {
1723
+ if (typeof s !== "string" || !s.trim()) continue;
1724
+ if (lower.includes(s.toLowerCase())) {
1725
+ return { ok: false, error: "Message contains disallowed content" };
1726
+ }
1727
+ }
1728
+ }
1729
+ return { ok: true };
1730
+ }
1731
+ function validateUserMessageAgainstAgentRules(message, validationRulesText) {
1732
+ const { structured } = parseLlmAgentValidationRules(validationRulesText);
1733
+ return validateUserMessageAgainstStructuredRules(message, structured);
1734
+ }
1735
+ function mergeGuardrailsIntoSystemPrompt(baseSystem, guardrailsForPrompt) {
1736
+ const g = guardrailsForPrompt?.trim();
1737
+ const b = (baseSystem ?? "").trim();
1738
+ if (!g) return b;
1739
+ const block = `### Output guardrails (follow in every reply)
1740
+ ${g}`;
1741
+ return b ? `${b}
1742
+
1743
+ ${block}` : block;
1744
+ }
1576
1745
  function createDashboardStatsHandler(config) {
1577
1746
  const { dataSource, entityMap, json, requireAuth, requirePermission, requireEntityPermission } = config;
1578
1747
  return async function GET(req) {
@@ -1595,8 +1764,8 @@ function createDashboardStatsHandler(config) {
1595
1764
  repo("form_submissions")?.count() ?? 0,
1596
1765
  repo("users")?.count({ where: { deleted: false } }) ?? 0,
1597
1766
  repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
1598
- repo("contacts")?.count({ where: { createdAt: (0, import_typeorm4.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
1599
- repo("form_submissions")?.count({ where: { createdAt: (0, import_typeorm4.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
1767
+ repo("contacts")?.count({ where: { createdAt: (0, import_typeorm5.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
1768
+ repo("form_submissions")?.count({ where: { createdAt: (0, import_typeorm5.MoreThanOrEqual)(sevenDaysAgo) } }) ?? 0,
1600
1769
  repo("contacts")?.createQueryBuilder("c").select("COALESCE(NULLIF(TRIM(c.type), ''), 'unknown')", "type").addSelect("COUNT(*)", "count").where("c.deleted = :deleted", { deleted: false }).groupBy("COALESCE(NULLIF(TRIM(c.type), ''), 'unknown')").getRawMany() ?? []
1601
1770
  ]);
1602
1771
  return json({
@@ -1645,19 +1814,19 @@ function createEcommerceAnalyticsHandler(config) {
1645
1814
  const productRepo = dataSource.getRepository(entityMap.products);
1646
1815
  const [salesOrders, returnOrders, replacementOrders, payments, products] = await Promise.all([
1647
1816
  orderRepo.find({
1648
- where: { deleted: false, createdAt: (0, import_typeorm4.MoreThanOrEqual)(start), orderKind: "sale", status: (0, import_typeorm4.In)(["confirmed", "processing", "completed"]) },
1817
+ where: { deleted: false, createdAt: (0, import_typeorm5.MoreThanOrEqual)(start), orderKind: "sale", status: (0, import_typeorm5.In)(["confirmed", "processing", "completed"]) },
1649
1818
  select: ["id", "contactId", "createdAt", "subtotal", "discount", "tax", "total", "status"]
1650
1819
  }),
1651
1820
  orderRepo.find({
1652
- where: { deleted: false, createdAt: (0, import_typeorm4.MoreThanOrEqual)(start), orderKind: "return" },
1821
+ where: { deleted: false, createdAt: (0, import_typeorm5.MoreThanOrEqual)(start), orderKind: "return" },
1653
1822
  select: ["id", "createdAt", "total"]
1654
1823
  }),
1655
1824
  orderRepo.find({
1656
- where: { deleted: false, createdAt: (0, import_typeorm4.MoreThanOrEqual)(start), orderKind: "replacement" },
1825
+ where: { deleted: false, createdAt: (0, import_typeorm5.MoreThanOrEqual)(start), orderKind: "replacement" },
1657
1826
  select: ["id", "createdAt", "total"]
1658
1827
  }),
1659
1828
  paymentRepo.find({
1660
- where: { deleted: false, createdAt: (0, import_typeorm4.MoreThanOrEqual)(start) },
1829
+ where: { deleted: false, createdAt: (0, import_typeorm5.MoreThanOrEqual)(start) },
1661
1830
  select: ["id", "status", "method", "amount", "createdAt"]
1662
1831
  }),
1663
1832
  productRepo.find({
@@ -1667,7 +1836,7 @@ function createEcommerceAnalyticsHandler(config) {
1667
1836
  ]);
1668
1837
  const saleOrderIds = salesOrders.map((o) => o.id);
1669
1838
  const orderItems = saleOrderIds.length ? await itemRepo.find({
1670
- where: { orderId: (0, import_typeorm4.In)(saleOrderIds) },
1839
+ where: { orderId: (0, import_typeorm5.In)(saleOrderIds) },
1671
1840
  select: ["id", "orderId", "productId", "quantity", "total"]
1672
1841
  }) : [];
1673
1842
  const grossSales = salesOrders.reduce((sum, o) => sum + toNum(o.subtotal), 0);
@@ -2355,8 +2524,8 @@ function createUsersApiHandlers(config) {
2355
2524
  const sortOrder = url.searchParams.get("sortOrder") === "desc" ? "DESC" : "ASC";
2356
2525
  const search = url.searchParams.get("search");
2357
2526
  const where = search ? [
2358
- { name: (0, import_typeorm4.ILike)(`%${search}%`), deleted: false },
2359
- { email: (0, import_typeorm4.ILike)(`%${search}%`), deleted: false }
2527
+ { name: (0, import_typeorm5.ILike)(`%${search}%`), deleted: false },
2528
+ { email: (0, import_typeorm5.ILike)(`%${search}%`), deleted: false }
2360
2529
  ] : { deleted: false };
2361
2530
  const [data, total] = await userRepo().findAndCount({
2362
2531
  skip,
@@ -2654,9 +2823,24 @@ function createSettingsApiHandlers(config) {
2654
2823
  }
2655
2824
  var KB_CHUNK_LIMIT = 10;
2656
2825
  var KB_CONTEXT_MAX_CHARS = 4e3;
2826
+ var RAG_LOG = "[rag-context]";
2657
2827
  function getQueryTerms(message) {
2658
2828
  return message.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 6);
2659
2829
  }
2830
+ function normalizeChatModeSetting(raw) {
2831
+ if (raw === "external" || raw === "llm") return raw;
2832
+ return "whatsapp";
2833
+ }
2834
+ async function loadLlmSettingsMap(dataSource, entityMap) {
2835
+ if (!entityMap.configs) return {};
2836
+ const repo = dataSource.getRepository(entityMap.configs);
2837
+ const rows = await repo.find({ where: { settings: "llm", deleted: false } });
2838
+ const out = {};
2839
+ for (const row of rows) {
2840
+ out[row.key] = row.value;
2841
+ }
2842
+ return out;
2843
+ }
2660
2844
  function createChatHandlers(config) {
2661
2845
  const { dataSource, entityMap, json, getCms } = config;
2662
2846
  const contactRepo = () => dataSource.getRepository(entityMap.contacts);
@@ -2664,6 +2848,26 @@ function createChatHandlers(config) {
2664
2848
  const msgRepo = () => dataSource.getRepository(entityMap.chat_messages);
2665
2849
  const chunkRepo = () => dataSource.getRepository(entityMap.knowledge_base_chunks);
2666
2850
  return {
2851
+ async publicConfig(_req) {
2852
+ try {
2853
+ const map = await loadLlmSettingsMap(dataSource, entityMap);
2854
+ const mode = normalizeChatModeSetting(map.chatMode);
2855
+ const body = {
2856
+ enabled: map.enabled !== "false",
2857
+ chatMode: mode,
2858
+ agentSlug: mode === "llm" ? (map.attachedAgentSlug ?? "").trim() : "",
2859
+ botName: map.botName ?? "",
2860
+ icon: map.icon ?? "",
2861
+ iconImageUrl: map.iconImageUrl ?? "",
2862
+ iconBackgroundColor: map.iconBackgroundColor ?? "#6366f1",
2863
+ headerColor: map.headerColor ?? "#6366f1",
2864
+ whatsappPhone: map.whatsappPhone ?? ""
2865
+ };
2866
+ return json(body);
2867
+ } catch {
2868
+ return json({ error: "Failed to load chat config" }, { status: 500 });
2869
+ }
2870
+ },
2667
2871
  async identify(req) {
2668
2872
  try {
2669
2873
  const body = await req.json();
@@ -2711,20 +2915,78 @@ function createChatHandlers(config) {
2711
2915
  relations: ["messages"]
2712
2916
  });
2713
2917
  if (!conv) return json({ error: "Conversation not found" }, { status: 404 });
2714
- const msgRepoInst = msgRepo();
2715
- await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "user", content: message }));
2716
2918
  const cms = await getCms();
2717
2919
  const llm = cms.getPlugin("llm");
2718
2920
  if (!llm?.chat) return json({ error: "LLM not configured" }, { status: 503 });
2921
+ const llmSettings = await loadLlmSettingsMap(dataSource, entityMap);
2922
+ const supportMode = normalizeChatModeSetting(llmSettings.chatMode);
2923
+ let effectiveSlug = (body?.agentSlug ?? "").trim();
2924
+ if (!effectiveSlug && supportMode === "llm" && entityMap.llm_agents) {
2925
+ effectiveSlug = (llmSettings.attachedAgentSlug ?? "").trim();
2926
+ }
2927
+ let agentRow = null;
2928
+ if (effectiveSlug) {
2929
+ if (!entityMap.llm_agents) {
2930
+ return json({ error: "LLM agents are not configured on this deployment" }, { status: 400 });
2931
+ }
2932
+ const agentRepo = dataSource.getRepository(
2933
+ entityMap.llm_agents
2934
+ );
2935
+ agentRow = await agentRepo.findOne({
2936
+ where: { slug: effectiveSlug, deleted: false, enabled: true }
2937
+ });
2938
+ if (!agentRow && (body?.agentSlug ?? "").trim()) {
2939
+ return json({ error: "Agent not found or disabled", agentSlug: effectiveSlug }, { status: 404 });
2940
+ }
2941
+ }
2942
+ console.info(RAG_LOG, "step 1 | resolve agent", {
2943
+ agentSlug: effectiveSlug || "(none)",
2944
+ agentFound: !!agentRow,
2945
+ agentId: agentRow?.id ?? null
2946
+ });
2947
+ const parsedValidation = agentRow ? parseLlmAgentValidationRules(agentRow.validationRules) : { structured: {}, guardrailsForPrompt: null };
2948
+ if (agentRow) {
2949
+ const v = validateUserMessageAgainstStructuredRules(message, parsedValidation.structured);
2950
+ if (!v.ok) return json({ error: v.error, reason: "validation_failed" }, { status: 400 });
2951
+ }
2952
+ let kbDocumentScope;
2953
+ const Junction = entityMap.llm_agent_knowledge_documents;
2954
+ if (agentRow && Junction) {
2955
+ const linkRepo = dataSource.getRepository(Junction);
2956
+ const links = await linkRepo.find({ where: { agentId: agentRow.id } });
2957
+ const ids = [...new Set(links.map((l) => l.documentId))];
2958
+ if (ids.length > 0) kbDocumentScope = ids;
2959
+ }
2960
+ console.info(RAG_LOG, "step 2 | knowledge scope", {
2961
+ scopedDocumentIds: kbDocumentScope ?? "(all documents)",
2962
+ documentCount: kbDocumentScope?.length ?? "all"
2963
+ });
2964
+ const msgRepoInst = msgRepo();
2965
+ await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "user", content: message }));
2719
2966
  let contextParts = [];
2967
+ console.info(RAG_LOG, "step 3 | embed user query", {
2968
+ messageChars: message.length,
2969
+ embedAvailable: !!llm.embed
2970
+ });
2720
2971
  const queryEmbedding = llm.embed ? await llm.embed(message) : null;
2972
+ console.info(RAG_LOG, "step 4 | query embedding result", {
2973
+ dimensions: queryEmbedding?.length ?? 0,
2974
+ hasEmbedding: !!(queryEmbedding && queryEmbedding.length > 0)
2975
+ });
2721
2976
  if (queryEmbedding && queryEmbedding.length > 0) {
2722
2977
  const vectorStr = "[" + queryEmbedding.join(",") + "]";
2723
2978
  try {
2724
- const rows = await dataSource.query(
2979
+ const rows = kbDocumentScope?.length ? await dataSource.query(
2980
+ `SELECT id, content FROM knowledge_base_chunks WHERE embedding IS NOT NULL AND "documentId" = ANY($3::int[]) ORDER BY embedding <=> $1::vector LIMIT $2`,
2981
+ [vectorStr, KB_CHUNK_LIMIT, kbDocumentScope]
2982
+ ) : await dataSource.query(
2725
2983
  `SELECT id, content FROM knowledge_base_chunks WHERE embedding IS NOT NULL ORDER BY embedding <=> $1::vector LIMIT $2`,
2726
2984
  [vectorStr, KB_CHUNK_LIMIT]
2727
2985
  );
2986
+ console.info(RAG_LOG, "step 5 | vector search results", {
2987
+ rowsReturned: rows.length,
2988
+ chunkIds: rows.map((r) => r.id)
2989
+ });
2728
2990
  let totalLen = 0;
2729
2991
  for (const r of rows) {
2730
2992
  const text = (r.content ?? "").trim();
@@ -2732,13 +2994,24 @@ function createChatHandlers(config) {
2732
2994
  contextParts.push(text);
2733
2995
  totalLen += text.length;
2734
2996
  }
2735
- } catch {
2997
+ console.info(RAG_LOG, "step 5a | vector context selected", {
2998
+ chunksUsed: contextParts.length,
2999
+ totalChars: totalLen
3000
+ });
3001
+ } catch (vecErr) {
3002
+ console.warn(RAG_LOG, "step 5 | vector search failed; falling back to keyword", {
3003
+ err: vecErr instanceof Error ? vecErr.message : String(vecErr)
3004
+ });
2736
3005
  }
2737
3006
  }
2738
3007
  if (contextParts.length === 0) {
2739
3008
  const terms = getQueryTerms(message);
3009
+ console.info(RAG_LOG, "step 6 | keyword fallback", {
3010
+ reason: !(queryEmbedding && queryEmbedding.length > 0) ? "no embedding" : "vector search returned nothing",
3011
+ searchTerms: terms
3012
+ });
2740
3013
  if (terms.length > 0) {
2741
- const conditions = terms.map((t) => ({ content: (0, import_typeorm4.ILike)(`%${t}%`) }));
3014
+ const conditions = kbDocumentScope?.length ? terms.map((t) => ({ content: (0, import_typeorm5.ILike)(`%${t}%`), documentId: (0, import_typeorm5.In)(kbDocumentScope) })) : terms.map((t) => ({ content: (0, import_typeorm5.ILike)(`%${t}%`) }));
2742
3015
  const chunks = await chunkRepo().find({
2743
3016
  where: conditions,
2744
3017
  take: KB_CHUNK_LIMIT,
@@ -2753,19 +3026,66 @@ function createChatHandlers(config) {
2753
3026
  contextParts.push(text);
2754
3027
  totalLen += text.length;
2755
3028
  }
3029
+ console.info(RAG_LOG, "step 6a | keyword results", {
3030
+ chunksFound: chunks.length,
3031
+ chunksUsed: contextParts.length,
3032
+ totalChars: totalLen
3033
+ });
2756
3034
  }
2757
3035
  }
2758
- const history = (conv.messages ?? []).sort((a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()).map((m) => ({ role: m.role, content: m.content }));
2759
- const systemContent = contextParts.length > 0 ? `Use the following context about the company and its products to answer. If the answer is not in the context, say so.
3036
+ const historyRaw = (conv.messages ?? []).sort((a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()).map((m) => ({ role: m.role, content: m.content }));
3037
+ const history = historyBeforeCurrentUser(historyRaw, message);
3038
+ let content;
3039
+ const ragContext = contextParts.length > 0 ? contextParts.join("\n\n") : void 0;
3040
+ console.info(RAG_LOG, "step 7 | final context", {
3041
+ method: contextParts.length > 0 ? "rag" : "none",
3042
+ contextChunks: contextParts.length,
3043
+ contextChars: ragContext?.length ?? 0,
3044
+ contextPreview: ragContext ? ragContext.slice(0, 200) + (ragContext.length > 200 ? "\u2026" : "") : "(no context)"
3045
+ });
3046
+ if (agentRow && llm.chatAgent) {
3047
+ const fromAgent = llmAgentToChatAgentOptions(agentRow);
3048
+ const systemPrompt = mergeGuardrailsIntoSystemPrompt(
3049
+ fromAgent.systemPrompt,
3050
+ parsedValidation.guardrailsForPrompt
3051
+ );
3052
+ const res = await llm.chatAgent({
3053
+ ...fromAgent,
3054
+ systemPrompt: systemPrompt || void 0,
3055
+ context: ragContext,
3056
+ history,
3057
+ userPrompt: message
3058
+ });
3059
+ content = res.content;
3060
+ } else {
3061
+ const ragSystem = contextParts.length > 0 ? `Use the following context about the company and its products to answer. If the answer is not in the context, say so.
2760
3062
 
2761
3063
  Context:
2762
- ${contextParts.join("\n\n")}` : "You are a helpful assistant for the company. If you do not have specific information, say so.";
2763
- const messages = [
2764
- { role: "system", content: systemContent },
2765
- ...history,
2766
- { role: "user", content: message }
2767
- ];
2768
- const { content } = await llm.chat(messages);
3064
+ ${contextParts.join("\n\n")}` : "";
3065
+ const defaultSystem = "You are a helpful assistant for the company. If you do not have specific information, say so.";
3066
+ let systemContent;
3067
+ if (agentRow) {
3068
+ const base = agentRow.systemInstruction?.trim() || "";
3069
+ systemContent = mergeGuardrailsIntoSystemPrompt(
3070
+ [base, ragSystem].filter(Boolean).join("\n\n") || defaultSystem,
3071
+ parsedValidation.guardrailsForPrompt
3072
+ );
3073
+ } else {
3074
+ systemContent = ragSystem || defaultSystem;
3075
+ }
3076
+ const messages = [
3077
+ { role: "system", content: systemContent },
3078
+ ...history,
3079
+ { role: "user", content: message }
3080
+ ];
3081
+ const chatOpts = agentRow ? {
3082
+ model: agentRow.model ?? void 0,
3083
+ temperature: agentRow.temperature ?? void 0,
3084
+ max_tokens: agentRow.maxTokens ?? void 0
3085
+ } : {};
3086
+ const res = await llm.chat(messages, chatOpts);
3087
+ content = res.content;
3088
+ }
2769
3089
  await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "assistant", content }));
2770
3090
  return json({ content });
2771
3091
  } catch (err) {
@@ -2776,6 +3096,314 @@ ${contextParts.join("\n\n")}` : "You are a helpful assistant for the company. If
2776
3096
  };
2777
3097
  }
2778
3098
 
3099
+ // src/api/llm-agent-knowledge-handlers.ts
3100
+ var import_typeorm6 = require("typeorm");
3101
+ var INGEST_CHUNK_CHARS = 900;
3102
+ var MAX_CHUNKS_PER_UPLOAD = 400;
3103
+ var EMBED_CONCURRENCY = 5;
3104
+ var MAX_PDF_BYTES = 25 * 1024 * 1024;
3105
+ var TEXT_FILE_TYPES = /* @__PURE__ */ new Set(["text/plain", "text/markdown", "application/json"]);
3106
+ var KB_LOG = "[llm-agent-knowledge]";
3107
+ function llmEmbedDebug() {
3108
+ const v = process.env.LLM_EMBED_DEBUG?.toLowerCase();
3109
+ return v === "1" || v === "true" || v === "yes";
3110
+ }
3111
+ function isPdfUpload(mime, fileName) {
3112
+ if (mime === "application/pdf") return true;
3113
+ const n = fileName.toLowerCase();
3114
+ return n.endsWith(".pdf");
3115
+ }
3116
+ async function extractTextFromPdf(buffer) {
3117
+ const pdfParse = await import("pdf-parse");
3118
+ const data = await pdfParse(buffer);
3119
+ return (data?.text ?? "").trim();
3120
+ }
3121
+ async function writeEmbeddingsConcurrent(dataSource, embed, chunks, concurrency) {
3122
+ let next = 0;
3123
+ let written = 0;
3124
+ let failed = 0;
3125
+ let skippedEmpty = 0;
3126
+ async function worker() {
3127
+ for (; ; ) {
3128
+ const i = next++;
3129
+ if (i >= chunks.length) return;
3130
+ const c = chunks[i];
3131
+ try {
3132
+ const emb = await embed(c.content);
3133
+ if (emb?.length) {
3134
+ const vectorStr = "[" + emb.join(",") + "]";
3135
+ await dataSource.query(
3136
+ `UPDATE knowledge_base_chunks SET embedding = $1::vector WHERE id = $2`,
3137
+ [vectorStr, c.id]
3138
+ );
3139
+ written++;
3140
+ } else {
3141
+ skippedEmpty++;
3142
+ if (llmEmbedDebug()) {
3143
+ console.warn(`${KB_LOG} embed() returned empty vector`, { chunkId: c.id });
3144
+ }
3145
+ }
3146
+ } catch (err) {
3147
+ failed++;
3148
+ console.error(`${KB_LOG} embedding DB update failed`, {
3149
+ chunkId: c.id,
3150
+ err: err instanceof Error ? err.message : String(err)
3151
+ });
3152
+ }
3153
+ }
3154
+ }
3155
+ const n = Math.max(1, Math.min(concurrency, chunks.length));
3156
+ await Promise.all(Array.from({ length: n }, () => worker()));
3157
+ const summary = {
3158
+ chunkCount: chunks.length,
3159
+ written,
3160
+ failed,
3161
+ skippedEmpty
3162
+ };
3163
+ if (skippedEmpty > 0 && written === 0) {
3164
+ summary.hint = "embed() returned empty vectors \u2014 see [LLM embed] logs (often HTTP 404 on /v1/embeddings, or wrong response shape).";
3165
+ }
3166
+ console.error(`${KB_LOG} embedding pass finished`, summary);
3167
+ return { written, failed };
3168
+ }
3169
+ function splitIntoChunks(text, maxLen) {
3170
+ const t = text.trim();
3171
+ if (!t) return [];
3172
+ const chunks = [];
3173
+ for (let i = 0; i < t.length; i += maxLen) {
3174
+ chunks.push(t.slice(i, i + maxLen));
3175
+ }
3176
+ return chunks;
3177
+ }
3178
+ async function findAgentBySlug(dataSource, llmAgents, slug) {
3179
+ const repo = dataSource.getRepository(llmAgents);
3180
+ return repo.findOne({
3181
+ where: { slug, deleted: false }
3182
+ });
3183
+ }
3184
+ function createLlmAgentKnowledgeHandlers(config) {
3185
+ const { dataSource, entityMap, getCms, json, requireAuth, requireEntityPermission } = config;
3186
+ const kbDoc = entityMap.knowledge_base_documents;
3187
+ const kbChunk = entityMap.knowledge_base_chunks;
3188
+ const llmAgents = entityMap.llm_agents;
3189
+ const junction = entityMap.llm_agent_knowledge_documents;
3190
+ if (!kbDoc || !kbChunk || !llmAgents || !junction) {
3191
+ return null;
3192
+ }
3193
+ async function gate(req, action) {
3194
+ const a = await requireAuth(req);
3195
+ if (a) return a;
3196
+ if (requireEntityPermission) {
3197
+ const pe = await requireEntityPermission(req, "llm_agents", action);
3198
+ if (pe) return pe;
3199
+ }
3200
+ return null;
3201
+ }
3202
+ return {
3203
+ async list(req, slug) {
3204
+ const denied = await gate(req, "read");
3205
+ if (denied) return denied;
3206
+ try {
3207
+ const agent = await findAgentBySlug(dataSource, llmAgents, slug);
3208
+ if (!agent) return json({ error: "Agent not found" }, { status: 404 });
3209
+ const linkRepo = dataSource.getRepository(junction);
3210
+ const links = await linkRepo.find({ where: { agentId: agent.id } });
3211
+ const docIds = [...new Set(links.map((l) => l.documentId))];
3212
+ if (docIds.length === 0) return json({ documents: [] });
3213
+ const docRepo = dataSource.getRepository(kbDoc);
3214
+ const docs = await docRepo.find({ where: { id: (0, import_typeorm6.In)(docIds) } });
3215
+ const byId = new Map(docs.map((d) => [d.id, d]));
3216
+ const documents = docIds.map((id) => {
3217
+ const d = byId.get(id);
3218
+ return d ? { id: d.id, name: d.name } : null;
3219
+ }).filter(Boolean);
3220
+ return json({ documents });
3221
+ } catch (err) {
3222
+ const msg = err instanceof Error ? err.message : "Failed to list knowledge";
3223
+ return json({ error: msg }, { status: 500 });
3224
+ }
3225
+ },
3226
+ async post(req, slug) {
3227
+ const denied = await gate(req, "update");
3228
+ if (denied) return denied;
3229
+ try {
3230
+ const agent = await findAgentBySlug(dataSource, llmAgents, slug);
3231
+ if (!agent) return json({ error: "Agent not found" }, { status: 404 });
3232
+ let name = "";
3233
+ let text = "";
3234
+ let sourceUrl = null;
3235
+ let existingDocumentId = null;
3236
+ const ct = req.headers.get("content-type") || "";
3237
+ if (ct.includes("application/json")) {
3238
+ const body = await req.json();
3239
+ existingDocumentId = typeof body?.documentId === "number" && Number.isFinite(body.documentId) ? body.documentId : null;
3240
+ name = (body?.name ?? "").trim();
3241
+ text = (body?.text ?? "").trim();
3242
+ sourceUrl = typeof body?.sourceUrl === "string" && body.sourceUrl.trim() ? body.sourceUrl.trim() : null;
3243
+ } else if (ct.includes("multipart/form-data")) {
3244
+ const form = await req.formData();
3245
+ name = form.get("name")?.trim() ?? "";
3246
+ text = form.get("text")?.trim() ?? "";
3247
+ const file = form.get("file");
3248
+ if (file && typeof file !== "string" && "arrayBuffer" in file) {
3249
+ const f = file;
3250
+ const mime = (f.type || "").split(";")[0].trim().toLowerCase();
3251
+ const buf = Buffer.from(await f.arrayBuffer());
3252
+ if (TEXT_FILE_TYPES.has(mime)) {
3253
+ const decoded = buf.toString("utf8");
3254
+ if (!text) text = decoded;
3255
+ } else if (isPdfUpload(mime, f.name || "")) {
3256
+ if (buf.length > MAX_PDF_BYTES) {
3257
+ return json(
3258
+ { error: `PDF too large (max ${Math.floor(MAX_PDF_BYTES / (1024 * 1024))}MB)` },
3259
+ { status: 413 }
3260
+ );
3261
+ }
3262
+ try {
3263
+ const extracted = await extractTextFromPdf(buf);
3264
+ if (!text) text = extracted;
3265
+ } catch {
3266
+ return json(
3267
+ { error: "Could not read PDF text (file may be encrypted, corrupt, or image-only)" },
3268
+ { status: 422 }
3269
+ );
3270
+ }
3271
+ } else {
3272
+ return json(
3273
+ {
3274
+ error: "Unsupported file type; use text/plain, text/markdown, application/json, or application/pdf"
3275
+ },
3276
+ { status: 415 }
3277
+ );
3278
+ }
3279
+ if (!name && f.name) name = f.name.replace(/\.[^/.]+$/, "") || f.name;
3280
+ }
3281
+ } else {
3282
+ return json({ error: "Use application/json or multipart/form-data" }, { status: 400 });
3283
+ }
3284
+ const linkRepo = dataSource.getRepository(junction);
3285
+ if (existingDocumentId != null) {
3286
+ const docRepo2 = dataSource.getRepository(kbDoc);
3287
+ const existing = await docRepo2.findOne({ where: { id: existingDocumentId } });
3288
+ if (!existing) return json({ error: "documentId not found" }, { status: 404 });
3289
+ const dup2 = await linkRepo.findOne({
3290
+ where: { agentId: agent.id, documentId: existingDocumentId }
3291
+ });
3292
+ if (!dup2) {
3293
+ await linkRepo.save(linkRepo.create({ agentId: agent.id, documentId: existingDocumentId }));
3294
+ }
3295
+ return json({ documentId: existingDocumentId, linked: true, created: false });
3296
+ }
3297
+ if (!text) return json({ error: "text or file with text content is required" }, { status: 400 });
3298
+ if (!name) name = "Untitled";
3299
+ const parts = splitIntoChunks(text, INGEST_CHUNK_CHARS);
3300
+ if (parts.length === 0) {
3301
+ return json({ error: "text or file with text content is required" }, { status: 400 });
3302
+ }
3303
+ if (parts.length > MAX_CHUNKS_PER_UPLOAD) {
3304
+ return json(
3305
+ {
3306
+ error: `Document is too large for one upload (${parts.length} chunks; max ${MAX_CHUNKS_PER_UPLOAD}). Split into smaller files.`
3307
+ },
3308
+ { status: 413 }
3309
+ );
3310
+ }
3311
+ const docRepo = dataSource.getRepository(kbDoc);
3312
+ const chunkRepo = dataSource.getRepository(kbChunk);
3313
+ const now = /* @__PURE__ */ new Date();
3314
+ const doc = await docRepo.save(
3315
+ docRepo.create({ name, content: text, sourceUrl, createdAt: now, updatedAt: now })
3316
+ );
3317
+ const docId = doc.id;
3318
+ const chunkRows = parts.map(
3319
+ (content, i) => chunkRepo.create({ documentId: docId, content, chunkIndex: i, createdAt: now })
3320
+ );
3321
+ const savedList = await chunkRepo.save(chunkRows);
3322
+ const savedChunks = savedList.map(
3323
+ (row, i) => ({
3324
+ id: row.id,
3325
+ content: parts[i]
3326
+ })
3327
+ );
3328
+ const dup = await linkRepo.findOne({ where: { agentId: agent.id, documentId: docId } });
3329
+ if (!dup) {
3330
+ await linkRepo.save(linkRepo.create({ agentId: agent.id, documentId: docId }));
3331
+ }
3332
+ let embeddingsWritten = 0;
3333
+ let embeddingsFailed = 0;
3334
+ try {
3335
+ const cms = await getCms();
3336
+ const llm = cms.getPlugin("llm");
3337
+ if (llm?.embed && savedChunks.length > 0) {
3338
+ console.info(`${KB_LOG} starting embedding pass`, { slug, chunkCount: savedChunks.length });
3339
+ const embedBound = (text2) => llm.embed(text2);
3340
+ const { written, failed } = await writeEmbeddingsConcurrent(
3341
+ dataSource,
3342
+ embedBound,
3343
+ savedChunks,
3344
+ EMBED_CONCURRENCY
3345
+ );
3346
+ embeddingsWritten = written;
3347
+ embeddingsFailed = failed;
3348
+ } else {
3349
+ console.error(`${KB_LOG} embeddings skipped`, {
3350
+ slug,
3351
+ hasLlmPlugin: !!llm,
3352
+ hasEmbed: typeof llm?.embed === "function",
3353
+ chunkCount: savedChunks.length,
3354
+ hint: !llm || typeof llm.embed !== "function" ? "LLM plugin missing or no embed(); check LLM_GATEWAY_URL + LLM_API_KEY so llm plugin initializes." : void 0
3355
+ });
3356
+ }
3357
+ } catch (embErr) {
3358
+ const detail = embErr instanceof Error ? embErr.message : String(embErr);
3359
+ console.error(`${KB_LOG} embedding step threw before/during batch`, { slug, detail, embErr });
3360
+ return json(
3361
+ {
3362
+ documentId: docId,
3363
+ chunkCount: savedChunks.length,
3364
+ created: true,
3365
+ linked: true,
3366
+ embeddingsWritten: 0,
3367
+ embeddingsFailed: savedChunks.length,
3368
+ warning: "Document saved and linked; embedding step failed.",
3369
+ detail
3370
+ },
3371
+ { status: 201 }
3372
+ );
3373
+ }
3374
+ return json({
3375
+ documentId: docId,
3376
+ chunkCount: savedChunks.length,
3377
+ created: true,
3378
+ linked: true,
3379
+ embeddingsWritten,
3380
+ embeddingsFailed
3381
+ });
3382
+ } catch (err) {
3383
+ const msg = err instanceof Error ? err.message : "Failed to ingest knowledge";
3384
+ const name = err instanceof Error ? err.name : "";
3385
+ return json({ error: msg, errorName: name || void 0 }, { status: 500 });
3386
+ }
3387
+ },
3388
+ async unlink(req, slug, documentIdStr) {
3389
+ const denied = await gate(req, "update");
3390
+ if (denied) return denied;
3391
+ const documentId = parseInt(documentIdStr, 10);
3392
+ if (!Number.isFinite(documentId)) return json({ error: "Invalid document id" }, { status: 400 });
3393
+ try {
3394
+ const agent = await findAgentBySlug(dataSource, llmAgents, slug);
3395
+ if (!agent) return json({ error: "Agent not found" }, { status: 404 });
3396
+ const linkRepo = dataSource.getRepository(junction);
3397
+ await linkRepo.delete({ agentId: agent.id, documentId });
3398
+ return json({ ok: true });
3399
+ } catch (err) {
3400
+ const msg = err instanceof Error ? err.message : "Failed to unlink";
3401
+ return json({ error: msg }, { status: 500 });
3402
+ }
3403
+ }
3404
+ };
3405
+ }
3406
+
2779
3407
  // src/message-templates/sms-defaults.ts
2780
3408
  var SMS_MESSAGE_TEMPLATE_DEFAULTS = [
2781
3409
  {
@@ -3084,6 +3712,26 @@ function createAdminRolesHandlers(config) {
3084
3712
  }
3085
3713
 
3086
3714
  // src/api/cms-api-handler.ts
3715
+ var KNOWLEDGE_SUFFIX = "knowledge";
3716
+ function matchLlmAgentKnowledgeRoute(path) {
3717
+ const p = path[0] === "api" ? path.slice(1) : path;
3718
+ if (p[0] !== "llm_agents" || p.length < 2) return null;
3719
+ const seg1 = p[1];
3720
+ if (!seg1) return null;
3721
+ if (p[2] === KNOWLEDGE_SUFFIX) {
3722
+ return {
3723
+ slug: seg1,
3724
+ documentId: p.length >= 4 ? p[3] : void 0
3725
+ };
3726
+ }
3727
+ if (seg1.endsWith(KNOWLEDGE_SUFFIX) && seg1.length > KNOWLEDGE_SUFFIX.length) {
3728
+ const slug = seg1.slice(0, -KNOWLEDGE_SUFFIX.length);
3729
+ if (!slug) return null;
3730
+ if (p.length === 2) return { slug, documentId: void 0 };
3731
+ if (p.length === 3) return { slug, documentId: p[2] };
3732
+ }
3733
+ return null;
3734
+ }
3087
3735
  var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
3088
3736
  "users",
3089
3737
  "password_reset_tokens",
@@ -3096,7 +3744,8 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
3096
3744
  "cart_items",
3097
3745
  "wishlists",
3098
3746
  "wishlist_items",
3099
- "message_templates"
3747
+ "message_templates",
3748
+ "llm_agent_knowledge_documents"
3100
3749
  ]);
3101
3750
  function createCmsApiHandler(config) {
3102
3751
  const {
@@ -3120,6 +3769,7 @@ function createCmsApiHandler(config) {
3120
3769
  userProfile,
3121
3770
  settings: settingsConfig,
3122
3771
  chat: chatConfig,
3772
+ llmAgentKnowledge: llmAgentKnowledgeConfig,
3123
3773
  requireEntityPermission: userRequireEntityPermission,
3124
3774
  getSessionUser
3125
3775
  } = config;
@@ -3227,6 +3877,17 @@ function createCmsApiHandler(config) {
3227
3877
  requireEntityPermission: requireEntityPermissionEffective
3228
3878
  });
3229
3879
  const chatHandlers = chatConfig ? createChatHandlers(chatConfig) : null;
3880
+ const llmAgentKnowledgeMerged = llmAgentKnowledgeConfig ?? (chatConfig ? {
3881
+ dataSource: chatConfig.dataSource,
3882
+ entityMap: chatConfig.entityMap,
3883
+ getCms: chatConfig.getCms,
3884
+ json: chatConfig.json,
3885
+ requireAuth: chatConfig.requireAuth
3886
+ } : void 0);
3887
+ const llmAgentKnowledgeHandlers = llmAgentKnowledgeMerged ? createLlmAgentKnowledgeHandlers({
3888
+ ...llmAgentKnowledgeMerged,
3889
+ requireEntityPermission: requireEntityPermissionEffective
3890
+ }) : null;
3230
3891
  function resolveResource(segment) {
3231
3892
  const model = pathToModel(segment);
3232
3893
  return crudResources.includes(model) ? model : segment;
@@ -3338,7 +3999,32 @@ function createCmsApiHandler(config) {
3338
3999
  if (method === "GET") return smsMessageTemplateHandlers.GET(req);
3339
4000
  if (method === "PUT") return smsMessageTemplateHandlers.PUT(req);
3340
4001
  }
4002
+ {
4003
+ const kbMatch = matchLlmAgentKnowledgeRoute(path);
4004
+ if (kbMatch) {
4005
+ if (!llmAgentKnowledgeHandlers) {
4006
+ return config.json(
4007
+ {
4008
+ error: "LLM agent knowledge is not available",
4009
+ hint: "With chat enabled, routes usually work automatically. Otherwise pass llmAgentKnowledge. If this persists, ensure entityMap includes knowledge_base_documents, knowledge_base_chunks, llm_agent_knowledge_documents, and run migrations."
4010
+ },
4011
+ { status: 503 }
4012
+ );
4013
+ }
4014
+ const { slug, documentId } = kbMatch;
4015
+ if (method === "DELETE" && documentId != null && documentId !== "") {
4016
+ return llmAgentKnowledgeHandlers.unlink(req, slug, documentId);
4017
+ }
4018
+ if (method === "GET" && (documentId == null || documentId === "")) {
4019
+ return llmAgentKnowledgeHandlers.list(req, slug);
4020
+ }
4021
+ if (method === "POST" && (documentId == null || documentId === "")) {
4022
+ return llmAgentKnowledgeHandlers.post(req, slug);
4023
+ }
4024
+ }
4025
+ }
3341
4026
  if (path[0] === "chat" && chatHandlers) {
4027
+ if (path.length === 2 && path[1] === "config" && method === "GET") return chatHandlers.publicConfig(req);
3342
4028
  if (path.length === 2 && path[1] === "identify" && method === "POST") return chatHandlers.identify(req);
3343
4029
  if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && method === "GET") return chatHandlers.getMessages(req, path[2]);
3344
4030
  if (path.length === 2 && path[1] === "messages" && method === "POST") return chatHandlers.postMessage(req);
@@ -3407,7 +4093,7 @@ function createCmsApiHandler(config) {
3407
4093
  }
3408
4094
 
3409
4095
  // src/api/storefront-handlers.ts
3410
- var import_typeorm6 = require("typeorm");
4096
+ var import_typeorm8 = require("typeorm");
3411
4097
 
3412
4098
  // src/lib/is-valid-signup-email.ts
3413
4099
  var MAX_EMAIL = 254;
@@ -3667,7 +4353,7 @@ async function queueSms(cms, payload) {
3667
4353
 
3668
4354
  // src/lib/otp-challenge.ts
3669
4355
  var import_crypto = require("crypto");
3670
- var import_typeorm5 = require("typeorm");
4356
+ var import_typeorm7 = require("typeorm");
3671
4357
  var OTP_TTL_MS = 10 * 60 * 1e3;
3672
4358
  var MAX_SENDS_PER_HOUR = 5;
3673
4359
  var MAX_VERIFY_ATTEMPTS = 8;
@@ -3701,7 +4387,7 @@ function normalizePhoneE164(raw, defaultCountryCode) {
3701
4387
  async function countRecentOtpSends(dataSource, entityMap, purpose, identifier, since) {
3702
4388
  const repo = dataSource.getRepository(entityMap.otp_challenges);
3703
4389
  return repo.count({
3704
- where: { purpose, identifier, createdAt: (0, import_typeorm5.MoreThan)(since) }
4390
+ where: { purpose, identifier, createdAt: (0, import_typeorm7.MoreThan)(since) }
3705
4391
  });
3706
4392
  }
3707
4393
  async function createOtpChallenge(dataSource, entityMap, input) {
@@ -3715,7 +4401,7 @@ async function createOtpChallenge(dataSource, entityMap, input) {
3715
4401
  await repo.delete({
3716
4402
  purpose,
3717
4403
  identifier,
3718
- consumedAt: (0, import_typeorm5.IsNull)()
4404
+ consumedAt: (0, import_typeorm7.IsNull)()
3719
4405
  });
3720
4406
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
3721
4407
  const codeHash = hashOtpCode(code, purpose, identifier, pepper);
@@ -3736,7 +4422,7 @@ async function verifyAndConsumeOtpChallenge(dataSource, entityMap, input) {
3736
4422
  const { purpose, identifier, code, pepper } = input;
3737
4423
  const repo = dataSource.getRepository(entityMap.otp_challenges);
3738
4424
  const row = await repo.findOne({
3739
- where: { purpose, identifier, consumedAt: (0, import_typeorm5.IsNull)() },
4425
+ where: { purpose, identifier, consumedAt: (0, import_typeorm7.IsNull)() },
3740
4426
  order: { id: "DESC" }
3741
4427
  });
3742
4428
  if (!row) {
@@ -3970,7 +4656,7 @@ function createStorefrontApiHandler(config) {
3970
4656
  const u = await userRepo().findOne({ where: { id: userId } });
3971
4657
  if (!u) return null;
3972
4658
  const unclaimed = await contactRepo().findOne({
3973
- where: { email: u.email, userId: (0, import_typeorm6.IsNull)(), deleted: false }
4659
+ where: { email: u.email, userId: (0, import_typeorm8.IsNull)(), deleted: false }
3974
4660
  });
3975
4661
  if (unclaimed) {
3976
4662
  await contactRepo().update(unclaimed.id, { userId });
@@ -5011,7 +5697,7 @@ function createStorefrontApiHandler(config) {
5011
5697
  const previewByOrder = {};
5012
5698
  if (orderIds.length) {
5013
5699
  const oItems = await orderItemRepo().find({
5014
- where: { orderId: (0, import_typeorm6.In)(orderIds) },
5700
+ where: { orderId: (0, import_typeorm8.In)(orderIds) },
5015
5701
  relations: ["product"],
5016
5702
  order: { id: "ASC" }
5017
5703
  });
@@ -5148,6 +5834,7 @@ function createStorefrontApiHandler(config) {
5148
5834
  createForgotPasswordHandler,
5149
5835
  createFormBySlugHandler,
5150
5836
  createInviteAcceptHandler,
5837
+ createLlmAgentKnowledgeHandlers,
5151
5838
  createMediaZipExtractHandler,
5152
5839
  createSetPasswordHandler,
5153
5840
  createSettingsApiHandlers,
@@ -5157,6 +5844,10 @@ function createStorefrontApiHandler(config) {
5157
5844
  createUserAvatarHandler,
5158
5845
  createUserProfileHandler,
5159
5846
  createUsersApiHandlers,
5160
- getPublicSettingsGroup
5847
+ getPublicSettingsGroup,
5848
+ mergeGuardrailsIntoSystemPrompt,
5849
+ parseLlmAgentValidationRules,
5850
+ validateUserMessageAgainstAgentRules,
5851
+ validateUserMessageAgainstStructuredRules
5161
5852
  });
5162
5853
  //# sourceMappingURL=api.cjs.map