@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/admin.cjs +534 -11
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.js +564 -23
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +727 -36
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +705 -18
- package/dist/api.js.map +1 -1
- package/dist/{index-GMn7-9PX.d.ts → index--GBYw5JE.d.ts} +84 -2
- package/dist/{index-D2C1O9b4.d.cts → index-DGtM2Gsk.d.cts} +84 -2
- package/dist/index.cjs +1701 -713
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -13
- package/dist/index.d.ts +99 -13
- package/dist/index.js +1554 -574
- package/dist/index.js.map +1 -1
- package/dist/migrations/1775300000000-LlmAgents.ts +57 -0
- package/dist/migrations/1775300000001-LlmAgentsValidationRulesText.ts +43 -0
- package/dist/migrations/1775300000002-SeedLlmAgentsPermissions.ts +33 -0
- package/dist/migrations/1775300000003-LlmAgentKnowledgeDocuments.ts +50 -0
- package/dist/migrations/1775400000000-KnowledgeBaseVectorDimension384.ts +32 -0
- package/package.json +8 -6
package/dist/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
4
|
var __esm = (fn, res) => function __init() {
|
|
4
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
@@ -7,6 +8,14 @@ var __export = (target, all) => {
|
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
10
|
};
|
|
11
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
12
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
13
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
14
|
+
if (decorator = decorators[i])
|
|
15
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
16
|
+
if (kind && result) __defProp(target, key, result);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
10
19
|
|
|
11
20
|
// src/plugins/erp/erp-queue.ts
|
|
12
21
|
async function queueErp(cms, payload) {
|
|
@@ -1335,6 +1344,86 @@ async function assertCaptchaOk(getCms, body, req, json) {
|
|
|
1335
1344
|
return json({ error: result.message }, { status: result.status });
|
|
1336
1345
|
}
|
|
1337
1346
|
|
|
1347
|
+
// src/entities/llm-agent.entity.ts
|
|
1348
|
+
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
|
|
1349
|
+
var LlmAgent = class {
|
|
1350
|
+
id;
|
|
1351
|
+
name;
|
|
1352
|
+
slug;
|
|
1353
|
+
systemInstruction;
|
|
1354
|
+
model;
|
|
1355
|
+
temperature;
|
|
1356
|
+
maxTokens;
|
|
1357
|
+
validationRules;
|
|
1358
|
+
enabled;
|
|
1359
|
+
createdAt;
|
|
1360
|
+
updatedAt;
|
|
1361
|
+
deletedAt;
|
|
1362
|
+
deleted;
|
|
1363
|
+
createdBy;
|
|
1364
|
+
updatedBy;
|
|
1365
|
+
deletedBy;
|
|
1366
|
+
};
|
|
1367
|
+
__decorateClass([
|
|
1368
|
+
PrimaryGeneratedColumn()
|
|
1369
|
+
], LlmAgent.prototype, "id", 2);
|
|
1370
|
+
__decorateClass([
|
|
1371
|
+
Column("varchar")
|
|
1372
|
+
], LlmAgent.prototype, "name", 2);
|
|
1373
|
+
__decorateClass([
|
|
1374
|
+
Column("varchar")
|
|
1375
|
+
], LlmAgent.prototype, "slug", 2);
|
|
1376
|
+
__decorateClass([
|
|
1377
|
+
Column("text", { name: "system_instruction", default: "" })
|
|
1378
|
+
], LlmAgent.prototype, "systemInstruction", 2);
|
|
1379
|
+
__decorateClass([
|
|
1380
|
+
Column("varchar", { nullable: true })
|
|
1381
|
+
], LlmAgent.prototype, "model", 2);
|
|
1382
|
+
__decorateClass([
|
|
1383
|
+
Column("double precision", { name: "temperature", nullable: true })
|
|
1384
|
+
], LlmAgent.prototype, "temperature", 2);
|
|
1385
|
+
__decorateClass([
|
|
1386
|
+
Column("int", { name: "max_tokens", nullable: true })
|
|
1387
|
+
], LlmAgent.prototype, "maxTokens", 2);
|
|
1388
|
+
__decorateClass([
|
|
1389
|
+
Column("text", { name: "validation_rules", nullable: true })
|
|
1390
|
+
], LlmAgent.prototype, "validationRules", 2);
|
|
1391
|
+
__decorateClass([
|
|
1392
|
+
Column("boolean", { default: true })
|
|
1393
|
+
], LlmAgent.prototype, "enabled", 2);
|
|
1394
|
+
__decorateClass([
|
|
1395
|
+
Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
1396
|
+
], LlmAgent.prototype, "createdAt", 2);
|
|
1397
|
+
__decorateClass([
|
|
1398
|
+
Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
1399
|
+
], LlmAgent.prototype, "updatedAt", 2);
|
|
1400
|
+
__decorateClass([
|
|
1401
|
+
Column({ type: "timestamp", nullable: true })
|
|
1402
|
+
], LlmAgent.prototype, "deletedAt", 2);
|
|
1403
|
+
__decorateClass([
|
|
1404
|
+
Column("boolean", { default: false })
|
|
1405
|
+
], LlmAgent.prototype, "deleted", 2);
|
|
1406
|
+
__decorateClass([
|
|
1407
|
+
Column("int", { nullable: true })
|
|
1408
|
+
], LlmAgent.prototype, "createdBy", 2);
|
|
1409
|
+
__decorateClass([
|
|
1410
|
+
Column("int", { nullable: true })
|
|
1411
|
+
], LlmAgent.prototype, "updatedBy", 2);
|
|
1412
|
+
__decorateClass([
|
|
1413
|
+
Column("int", { nullable: true })
|
|
1414
|
+
], LlmAgent.prototype, "deletedBy", 2);
|
|
1415
|
+
LlmAgent = __decorateClass([
|
|
1416
|
+
Entity("llm_agents")
|
|
1417
|
+
], LlmAgent);
|
|
1418
|
+
function llmAgentToChatAgentOptions(agent) {
|
|
1419
|
+
return {
|
|
1420
|
+
systemPrompt: agent.systemInstruction?.trim() || void 0,
|
|
1421
|
+
model: agent.model?.trim() || void 0,
|
|
1422
|
+
temperature: agent.temperature ?? void 0,
|
|
1423
|
+
max_tokens: agent.maxTokens ?? void 0
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1338
1427
|
// src/lib/media-folder-path.ts
|
|
1339
1428
|
function sanitizeMediaFolderPath(input) {
|
|
1340
1429
|
if (input == null) return "";
|
|
@@ -1524,6 +1613,82 @@ async function extractZipMediaIntoParentTree(opts) {
|
|
|
1524
1613
|
}
|
|
1525
1614
|
|
|
1526
1615
|
// src/api/cms-handlers.ts
|
|
1616
|
+
function historyBeforeCurrentUser(history, currentUserContent) {
|
|
1617
|
+
const last = history[history.length - 1];
|
|
1618
|
+
if (last?.role === "user" && last.content === currentUserContent) {
|
|
1619
|
+
return history.slice(0, -1);
|
|
1620
|
+
}
|
|
1621
|
+
return [...history];
|
|
1622
|
+
}
|
|
1623
|
+
function pickStructuredRules(rules) {
|
|
1624
|
+
return {
|
|
1625
|
+
maxUserChars: rules.maxUserChars,
|
|
1626
|
+
maxMessageLength: rules.maxMessageLength,
|
|
1627
|
+
minUserChars: rules.minUserChars,
|
|
1628
|
+
minMessageLength: rules.minMessageLength,
|
|
1629
|
+
blockedSubstrings: rules.blockedSubstrings
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
function guardrailsTextFromJsonObject(rules) {
|
|
1633
|
+
const g = typeof rules.guardrails === "string" && rules.guardrails.trim() || typeof rules.outputRules === "string" && rules.outputRules.trim() || typeof rules.outputInstructions === "string" && rules.outputInstructions.trim() || "";
|
|
1634
|
+
return g || null;
|
|
1635
|
+
}
|
|
1636
|
+
function parseLlmAgentValidationRules(validationRulesText) {
|
|
1637
|
+
const raw = validationRulesText?.trim();
|
|
1638
|
+
if (!raw) return { structured: {}, guardrailsForPrompt: null };
|
|
1639
|
+
try {
|
|
1640
|
+
const parsed = JSON.parse(raw);
|
|
1641
|
+
if (typeof parsed === "string") {
|
|
1642
|
+
const s = parsed.trim();
|
|
1643
|
+
return { structured: {}, guardrailsForPrompt: s || null };
|
|
1644
|
+
}
|
|
1645
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1646
|
+
return { structured: {}, guardrailsForPrompt: raw };
|
|
1647
|
+
}
|
|
1648
|
+
const rules = parsed;
|
|
1649
|
+
return {
|
|
1650
|
+
structured: pickStructuredRules(rules),
|
|
1651
|
+
guardrailsForPrompt: guardrailsTextFromJsonObject(rules)
|
|
1652
|
+
};
|
|
1653
|
+
} catch {
|
|
1654
|
+
return { structured: {}, guardrailsForPrompt: raw };
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function validateUserMessageAgainstStructuredRules(message, structured) {
|
|
1658
|
+
const maxLen = structured.maxUserChars ?? structured.maxMessageLength;
|
|
1659
|
+
if (typeof maxLen === "number" && Number.isFinite(maxLen) && maxLen >= 0 && message.length > maxLen) {
|
|
1660
|
+
return { ok: false, error: `Message exceeds maximum length (${maxLen} characters)` };
|
|
1661
|
+
}
|
|
1662
|
+
const minLen = structured.minUserChars ?? structured.minMessageLength;
|
|
1663
|
+
if (typeof minLen === "number" && Number.isFinite(minLen) && minLen > 0 && message.length < minLen) {
|
|
1664
|
+
return { ok: false, error: `Message is shorter than minimum length (${minLen} characters)` };
|
|
1665
|
+
}
|
|
1666
|
+
const blocked = structured.blockedSubstrings;
|
|
1667
|
+
if (Array.isArray(blocked) && blocked.length > 0) {
|
|
1668
|
+
const lower = message.toLowerCase();
|
|
1669
|
+
for (const s of blocked) {
|
|
1670
|
+
if (typeof s !== "string" || !s.trim()) continue;
|
|
1671
|
+
if (lower.includes(s.toLowerCase())) {
|
|
1672
|
+
return { ok: false, error: "Message contains disallowed content" };
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return { ok: true };
|
|
1677
|
+
}
|
|
1678
|
+
function validateUserMessageAgainstAgentRules(message, validationRulesText) {
|
|
1679
|
+
const { structured } = parseLlmAgentValidationRules(validationRulesText);
|
|
1680
|
+
return validateUserMessageAgainstStructuredRules(message, structured);
|
|
1681
|
+
}
|
|
1682
|
+
function mergeGuardrailsIntoSystemPrompt(baseSystem, guardrailsForPrompt) {
|
|
1683
|
+
const g = guardrailsForPrompt?.trim();
|
|
1684
|
+
const b = (baseSystem ?? "").trim();
|
|
1685
|
+
if (!g) return b;
|
|
1686
|
+
const block = `### Output guardrails (follow in every reply)
|
|
1687
|
+
${g}`;
|
|
1688
|
+
return b ? `${b}
|
|
1689
|
+
|
|
1690
|
+
${block}` : block;
|
|
1691
|
+
}
|
|
1527
1692
|
function createDashboardStatsHandler(config) {
|
|
1528
1693
|
const { dataSource, entityMap, json, requireAuth, requirePermission, requireEntityPermission } = config;
|
|
1529
1694
|
return async function GET(req) {
|
|
@@ -2605,9 +2770,24 @@ function createSettingsApiHandlers(config) {
|
|
|
2605
2770
|
}
|
|
2606
2771
|
var KB_CHUNK_LIMIT = 10;
|
|
2607
2772
|
var KB_CONTEXT_MAX_CHARS = 4e3;
|
|
2773
|
+
var RAG_LOG = "[rag-context]";
|
|
2608
2774
|
function getQueryTerms(message) {
|
|
2609
2775
|
return message.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 6);
|
|
2610
2776
|
}
|
|
2777
|
+
function normalizeChatModeSetting(raw) {
|
|
2778
|
+
if (raw === "external" || raw === "llm") return raw;
|
|
2779
|
+
return "whatsapp";
|
|
2780
|
+
}
|
|
2781
|
+
async function loadLlmSettingsMap(dataSource, entityMap) {
|
|
2782
|
+
if (!entityMap.configs) return {};
|
|
2783
|
+
const repo = dataSource.getRepository(entityMap.configs);
|
|
2784
|
+
const rows = await repo.find({ where: { settings: "llm", deleted: false } });
|
|
2785
|
+
const out = {};
|
|
2786
|
+
for (const row of rows) {
|
|
2787
|
+
out[row.key] = row.value;
|
|
2788
|
+
}
|
|
2789
|
+
return out;
|
|
2790
|
+
}
|
|
2611
2791
|
function createChatHandlers(config) {
|
|
2612
2792
|
const { dataSource, entityMap, json, getCms } = config;
|
|
2613
2793
|
const contactRepo = () => dataSource.getRepository(entityMap.contacts);
|
|
@@ -2615,6 +2795,26 @@ function createChatHandlers(config) {
|
|
|
2615
2795
|
const msgRepo = () => dataSource.getRepository(entityMap.chat_messages);
|
|
2616
2796
|
const chunkRepo = () => dataSource.getRepository(entityMap.knowledge_base_chunks);
|
|
2617
2797
|
return {
|
|
2798
|
+
async publicConfig(_req) {
|
|
2799
|
+
try {
|
|
2800
|
+
const map = await loadLlmSettingsMap(dataSource, entityMap);
|
|
2801
|
+
const mode = normalizeChatModeSetting(map.chatMode);
|
|
2802
|
+
const body = {
|
|
2803
|
+
enabled: map.enabled !== "false",
|
|
2804
|
+
chatMode: mode,
|
|
2805
|
+
agentSlug: mode === "llm" ? (map.attachedAgentSlug ?? "").trim() : "",
|
|
2806
|
+
botName: map.botName ?? "",
|
|
2807
|
+
icon: map.icon ?? "",
|
|
2808
|
+
iconImageUrl: map.iconImageUrl ?? "",
|
|
2809
|
+
iconBackgroundColor: map.iconBackgroundColor ?? "#6366f1",
|
|
2810
|
+
headerColor: map.headerColor ?? "#6366f1",
|
|
2811
|
+
whatsappPhone: map.whatsappPhone ?? ""
|
|
2812
|
+
};
|
|
2813
|
+
return json(body);
|
|
2814
|
+
} catch {
|
|
2815
|
+
return json({ error: "Failed to load chat config" }, { status: 500 });
|
|
2816
|
+
}
|
|
2817
|
+
},
|
|
2618
2818
|
async identify(req) {
|
|
2619
2819
|
try {
|
|
2620
2820
|
const body = await req.json();
|
|
@@ -2662,20 +2862,78 @@ function createChatHandlers(config) {
|
|
|
2662
2862
|
relations: ["messages"]
|
|
2663
2863
|
});
|
|
2664
2864
|
if (!conv) return json({ error: "Conversation not found" }, { status: 404 });
|
|
2665
|
-
const msgRepoInst = msgRepo();
|
|
2666
|
-
await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "user", content: message }));
|
|
2667
2865
|
const cms = await getCms();
|
|
2668
2866
|
const llm = cms.getPlugin("llm");
|
|
2669
2867
|
if (!llm?.chat) return json({ error: "LLM not configured" }, { status: 503 });
|
|
2868
|
+
const llmSettings = await loadLlmSettingsMap(dataSource, entityMap);
|
|
2869
|
+
const supportMode = normalizeChatModeSetting(llmSettings.chatMode);
|
|
2870
|
+
let effectiveSlug = (body?.agentSlug ?? "").trim();
|
|
2871
|
+
if (!effectiveSlug && supportMode === "llm" && entityMap.llm_agents) {
|
|
2872
|
+
effectiveSlug = (llmSettings.attachedAgentSlug ?? "").trim();
|
|
2873
|
+
}
|
|
2874
|
+
let agentRow = null;
|
|
2875
|
+
if (effectiveSlug) {
|
|
2876
|
+
if (!entityMap.llm_agents) {
|
|
2877
|
+
return json({ error: "LLM agents are not configured on this deployment" }, { status: 400 });
|
|
2878
|
+
}
|
|
2879
|
+
const agentRepo = dataSource.getRepository(
|
|
2880
|
+
entityMap.llm_agents
|
|
2881
|
+
);
|
|
2882
|
+
agentRow = await agentRepo.findOne({
|
|
2883
|
+
where: { slug: effectiveSlug, deleted: false, enabled: true }
|
|
2884
|
+
});
|
|
2885
|
+
if (!agentRow && (body?.agentSlug ?? "").trim()) {
|
|
2886
|
+
return json({ error: "Agent not found or disabled", agentSlug: effectiveSlug }, { status: 404 });
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
console.info(RAG_LOG, "step 1 | resolve agent", {
|
|
2890
|
+
agentSlug: effectiveSlug || "(none)",
|
|
2891
|
+
agentFound: !!agentRow,
|
|
2892
|
+
agentId: agentRow?.id ?? null
|
|
2893
|
+
});
|
|
2894
|
+
const parsedValidation = agentRow ? parseLlmAgentValidationRules(agentRow.validationRules) : { structured: {}, guardrailsForPrompt: null };
|
|
2895
|
+
if (agentRow) {
|
|
2896
|
+
const v = validateUserMessageAgainstStructuredRules(message, parsedValidation.structured);
|
|
2897
|
+
if (!v.ok) return json({ error: v.error, reason: "validation_failed" }, { status: 400 });
|
|
2898
|
+
}
|
|
2899
|
+
let kbDocumentScope;
|
|
2900
|
+
const Junction = entityMap.llm_agent_knowledge_documents;
|
|
2901
|
+
if (agentRow && Junction) {
|
|
2902
|
+
const linkRepo = dataSource.getRepository(Junction);
|
|
2903
|
+
const links = await linkRepo.find({ where: { agentId: agentRow.id } });
|
|
2904
|
+
const ids = [...new Set(links.map((l) => l.documentId))];
|
|
2905
|
+
if (ids.length > 0) kbDocumentScope = ids;
|
|
2906
|
+
}
|
|
2907
|
+
console.info(RAG_LOG, "step 2 | knowledge scope", {
|
|
2908
|
+
scopedDocumentIds: kbDocumentScope ?? "(all documents)",
|
|
2909
|
+
documentCount: kbDocumentScope?.length ?? "all"
|
|
2910
|
+
});
|
|
2911
|
+
const msgRepoInst = msgRepo();
|
|
2912
|
+
await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "user", content: message }));
|
|
2670
2913
|
let contextParts = [];
|
|
2914
|
+
console.info(RAG_LOG, "step 3 | embed user query", {
|
|
2915
|
+
messageChars: message.length,
|
|
2916
|
+
embedAvailable: !!llm.embed
|
|
2917
|
+
});
|
|
2671
2918
|
const queryEmbedding = llm.embed ? await llm.embed(message) : null;
|
|
2919
|
+
console.info(RAG_LOG, "step 4 | query embedding result", {
|
|
2920
|
+
dimensions: queryEmbedding?.length ?? 0,
|
|
2921
|
+
hasEmbedding: !!(queryEmbedding && queryEmbedding.length > 0)
|
|
2922
|
+
});
|
|
2672
2923
|
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
2673
2924
|
const vectorStr = "[" + queryEmbedding.join(",") + "]";
|
|
2674
2925
|
try {
|
|
2675
|
-
const rows = await dataSource.query(
|
|
2926
|
+
const rows = kbDocumentScope?.length ? await dataSource.query(
|
|
2927
|
+
`SELECT id, content FROM knowledge_base_chunks WHERE embedding IS NOT NULL AND "documentId" = ANY($3::int[]) ORDER BY embedding <=> $1::vector LIMIT $2`,
|
|
2928
|
+
[vectorStr, KB_CHUNK_LIMIT, kbDocumentScope]
|
|
2929
|
+
) : await dataSource.query(
|
|
2676
2930
|
`SELECT id, content FROM knowledge_base_chunks WHERE embedding IS NOT NULL ORDER BY embedding <=> $1::vector LIMIT $2`,
|
|
2677
2931
|
[vectorStr, KB_CHUNK_LIMIT]
|
|
2678
2932
|
);
|
|
2933
|
+
console.info(RAG_LOG, "step 5 | vector search results", {
|
|
2934
|
+
rowsReturned: rows.length,
|
|
2935
|
+
chunkIds: rows.map((r) => r.id)
|
|
2936
|
+
});
|
|
2679
2937
|
let totalLen = 0;
|
|
2680
2938
|
for (const r of rows) {
|
|
2681
2939
|
const text = (r.content ?? "").trim();
|
|
@@ -2683,13 +2941,24 @@ function createChatHandlers(config) {
|
|
|
2683
2941
|
contextParts.push(text);
|
|
2684
2942
|
totalLen += text.length;
|
|
2685
2943
|
}
|
|
2686
|
-
|
|
2944
|
+
console.info(RAG_LOG, "step 5a | vector context selected", {
|
|
2945
|
+
chunksUsed: contextParts.length,
|
|
2946
|
+
totalChars: totalLen
|
|
2947
|
+
});
|
|
2948
|
+
} catch (vecErr) {
|
|
2949
|
+
console.warn(RAG_LOG, "step 5 | vector search failed; falling back to keyword", {
|
|
2950
|
+
err: vecErr instanceof Error ? vecErr.message : String(vecErr)
|
|
2951
|
+
});
|
|
2687
2952
|
}
|
|
2688
2953
|
}
|
|
2689
2954
|
if (contextParts.length === 0) {
|
|
2690
2955
|
const terms = getQueryTerms(message);
|
|
2956
|
+
console.info(RAG_LOG, "step 6 | keyword fallback", {
|
|
2957
|
+
reason: !(queryEmbedding && queryEmbedding.length > 0) ? "no embedding" : "vector search returned nothing",
|
|
2958
|
+
searchTerms: terms
|
|
2959
|
+
});
|
|
2691
2960
|
if (terms.length > 0) {
|
|
2692
|
-
const conditions = terms.map((t) => ({ content: ILike2(`%${t}%`) }));
|
|
2961
|
+
const conditions = kbDocumentScope?.length ? terms.map((t) => ({ content: ILike2(`%${t}%`), documentId: In(kbDocumentScope) })) : terms.map((t) => ({ content: ILike2(`%${t}%`) }));
|
|
2693
2962
|
const chunks = await chunkRepo().find({
|
|
2694
2963
|
where: conditions,
|
|
2695
2964
|
take: KB_CHUNK_LIMIT,
|
|
@@ -2704,19 +2973,66 @@ function createChatHandlers(config) {
|
|
|
2704
2973
|
contextParts.push(text);
|
|
2705
2974
|
totalLen += text.length;
|
|
2706
2975
|
}
|
|
2976
|
+
console.info(RAG_LOG, "step 6a | keyword results", {
|
|
2977
|
+
chunksFound: chunks.length,
|
|
2978
|
+
chunksUsed: contextParts.length,
|
|
2979
|
+
totalChars: totalLen
|
|
2980
|
+
});
|
|
2707
2981
|
}
|
|
2708
2982
|
}
|
|
2709
|
-
const
|
|
2710
|
-
const
|
|
2983
|
+
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 }));
|
|
2984
|
+
const history = historyBeforeCurrentUser(historyRaw, message);
|
|
2985
|
+
let content;
|
|
2986
|
+
const ragContext = contextParts.length > 0 ? contextParts.join("\n\n") : void 0;
|
|
2987
|
+
console.info(RAG_LOG, "step 7 | final context", {
|
|
2988
|
+
method: contextParts.length > 0 ? "rag" : "none",
|
|
2989
|
+
contextChunks: contextParts.length,
|
|
2990
|
+
contextChars: ragContext?.length ?? 0,
|
|
2991
|
+
contextPreview: ragContext ? ragContext.slice(0, 200) + (ragContext.length > 200 ? "\u2026" : "") : "(no context)"
|
|
2992
|
+
});
|
|
2993
|
+
if (agentRow && llm.chatAgent) {
|
|
2994
|
+
const fromAgent = llmAgentToChatAgentOptions(agentRow);
|
|
2995
|
+
const systemPrompt = mergeGuardrailsIntoSystemPrompt(
|
|
2996
|
+
fromAgent.systemPrompt,
|
|
2997
|
+
parsedValidation.guardrailsForPrompt
|
|
2998
|
+
);
|
|
2999
|
+
const res = await llm.chatAgent({
|
|
3000
|
+
...fromAgent,
|
|
3001
|
+
systemPrompt: systemPrompt || void 0,
|
|
3002
|
+
context: ragContext,
|
|
3003
|
+
history,
|
|
3004
|
+
userPrompt: message
|
|
3005
|
+
});
|
|
3006
|
+
content = res.content;
|
|
3007
|
+
} else {
|
|
3008
|
+
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.
|
|
2711
3009
|
|
|
2712
3010
|
Context:
|
|
2713
|
-
${contextParts.join("\n\n")}` : "
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
3011
|
+
${contextParts.join("\n\n")}` : "";
|
|
3012
|
+
const defaultSystem = "You are a helpful assistant for the company. If you do not have specific information, say so.";
|
|
3013
|
+
let systemContent;
|
|
3014
|
+
if (agentRow) {
|
|
3015
|
+
const base = agentRow.systemInstruction?.trim() || "";
|
|
3016
|
+
systemContent = mergeGuardrailsIntoSystemPrompt(
|
|
3017
|
+
[base, ragSystem].filter(Boolean).join("\n\n") || defaultSystem,
|
|
3018
|
+
parsedValidation.guardrailsForPrompt
|
|
3019
|
+
);
|
|
3020
|
+
} else {
|
|
3021
|
+
systemContent = ragSystem || defaultSystem;
|
|
3022
|
+
}
|
|
3023
|
+
const messages = [
|
|
3024
|
+
{ role: "system", content: systemContent },
|
|
3025
|
+
...history,
|
|
3026
|
+
{ role: "user", content: message }
|
|
3027
|
+
];
|
|
3028
|
+
const chatOpts = agentRow ? {
|
|
3029
|
+
model: agentRow.model ?? void 0,
|
|
3030
|
+
temperature: agentRow.temperature ?? void 0,
|
|
3031
|
+
max_tokens: agentRow.maxTokens ?? void 0
|
|
3032
|
+
} : {};
|
|
3033
|
+
const res = await llm.chat(messages, chatOpts);
|
|
3034
|
+
content = res.content;
|
|
3035
|
+
}
|
|
2720
3036
|
await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "assistant", content }));
|
|
2721
3037
|
return json({ content });
|
|
2722
3038
|
} catch (err) {
|
|
@@ -2727,6 +3043,314 @@ ${contextParts.join("\n\n")}` : "You are a helpful assistant for the company. If
|
|
|
2727
3043
|
};
|
|
2728
3044
|
}
|
|
2729
3045
|
|
|
3046
|
+
// src/api/llm-agent-knowledge-handlers.ts
|
|
3047
|
+
import { In as In2 } from "typeorm";
|
|
3048
|
+
var INGEST_CHUNK_CHARS = 900;
|
|
3049
|
+
var MAX_CHUNKS_PER_UPLOAD = 400;
|
|
3050
|
+
var EMBED_CONCURRENCY = 5;
|
|
3051
|
+
var MAX_PDF_BYTES = 25 * 1024 * 1024;
|
|
3052
|
+
var TEXT_FILE_TYPES = /* @__PURE__ */ new Set(["text/plain", "text/markdown", "application/json"]);
|
|
3053
|
+
var KB_LOG = "[llm-agent-knowledge]";
|
|
3054
|
+
function llmEmbedDebug() {
|
|
3055
|
+
const v = process.env.LLM_EMBED_DEBUG?.toLowerCase();
|
|
3056
|
+
return v === "1" || v === "true" || v === "yes";
|
|
3057
|
+
}
|
|
3058
|
+
function isPdfUpload(mime, fileName) {
|
|
3059
|
+
if (mime === "application/pdf") return true;
|
|
3060
|
+
const n = fileName.toLowerCase();
|
|
3061
|
+
return n.endsWith(".pdf");
|
|
3062
|
+
}
|
|
3063
|
+
async function extractTextFromPdf(buffer) {
|
|
3064
|
+
const pdfParse = await import("pdf-parse");
|
|
3065
|
+
const data = await pdfParse(buffer);
|
|
3066
|
+
return (data?.text ?? "").trim();
|
|
3067
|
+
}
|
|
3068
|
+
async function writeEmbeddingsConcurrent(dataSource, embed, chunks, concurrency) {
|
|
3069
|
+
let next = 0;
|
|
3070
|
+
let written = 0;
|
|
3071
|
+
let failed = 0;
|
|
3072
|
+
let skippedEmpty = 0;
|
|
3073
|
+
async function worker() {
|
|
3074
|
+
for (; ; ) {
|
|
3075
|
+
const i = next++;
|
|
3076
|
+
if (i >= chunks.length) return;
|
|
3077
|
+
const c = chunks[i];
|
|
3078
|
+
try {
|
|
3079
|
+
const emb = await embed(c.content);
|
|
3080
|
+
if (emb?.length) {
|
|
3081
|
+
const vectorStr = "[" + emb.join(",") + "]";
|
|
3082
|
+
await dataSource.query(
|
|
3083
|
+
`UPDATE knowledge_base_chunks SET embedding = $1::vector WHERE id = $2`,
|
|
3084
|
+
[vectorStr, c.id]
|
|
3085
|
+
);
|
|
3086
|
+
written++;
|
|
3087
|
+
} else {
|
|
3088
|
+
skippedEmpty++;
|
|
3089
|
+
if (llmEmbedDebug()) {
|
|
3090
|
+
console.warn(`${KB_LOG} embed() returned empty vector`, { chunkId: c.id });
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
} catch (err) {
|
|
3094
|
+
failed++;
|
|
3095
|
+
console.error(`${KB_LOG} embedding DB update failed`, {
|
|
3096
|
+
chunkId: c.id,
|
|
3097
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
const n = Math.max(1, Math.min(concurrency, chunks.length));
|
|
3103
|
+
await Promise.all(Array.from({ length: n }, () => worker()));
|
|
3104
|
+
const summary = {
|
|
3105
|
+
chunkCount: chunks.length,
|
|
3106
|
+
written,
|
|
3107
|
+
failed,
|
|
3108
|
+
skippedEmpty
|
|
3109
|
+
};
|
|
3110
|
+
if (skippedEmpty > 0 && written === 0) {
|
|
3111
|
+
summary.hint = "embed() returned empty vectors \u2014 see [LLM embed] logs (often HTTP 404 on /v1/embeddings, or wrong response shape).";
|
|
3112
|
+
}
|
|
3113
|
+
console.error(`${KB_LOG} embedding pass finished`, summary);
|
|
3114
|
+
return { written, failed };
|
|
3115
|
+
}
|
|
3116
|
+
function splitIntoChunks(text, maxLen) {
|
|
3117
|
+
const t = text.trim();
|
|
3118
|
+
if (!t) return [];
|
|
3119
|
+
const chunks = [];
|
|
3120
|
+
for (let i = 0; i < t.length; i += maxLen) {
|
|
3121
|
+
chunks.push(t.slice(i, i + maxLen));
|
|
3122
|
+
}
|
|
3123
|
+
return chunks;
|
|
3124
|
+
}
|
|
3125
|
+
async function findAgentBySlug(dataSource, llmAgents, slug) {
|
|
3126
|
+
const repo = dataSource.getRepository(llmAgents);
|
|
3127
|
+
return repo.findOne({
|
|
3128
|
+
where: { slug, deleted: false }
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
function createLlmAgentKnowledgeHandlers(config) {
|
|
3132
|
+
const { dataSource, entityMap, getCms, json, requireAuth, requireEntityPermission } = config;
|
|
3133
|
+
const kbDoc = entityMap.knowledge_base_documents;
|
|
3134
|
+
const kbChunk = entityMap.knowledge_base_chunks;
|
|
3135
|
+
const llmAgents = entityMap.llm_agents;
|
|
3136
|
+
const junction = entityMap.llm_agent_knowledge_documents;
|
|
3137
|
+
if (!kbDoc || !kbChunk || !llmAgents || !junction) {
|
|
3138
|
+
return null;
|
|
3139
|
+
}
|
|
3140
|
+
async function gate(req, action) {
|
|
3141
|
+
const a = await requireAuth(req);
|
|
3142
|
+
if (a) return a;
|
|
3143
|
+
if (requireEntityPermission) {
|
|
3144
|
+
const pe = await requireEntityPermission(req, "llm_agents", action);
|
|
3145
|
+
if (pe) return pe;
|
|
3146
|
+
}
|
|
3147
|
+
return null;
|
|
3148
|
+
}
|
|
3149
|
+
return {
|
|
3150
|
+
async list(req, slug) {
|
|
3151
|
+
const denied = await gate(req, "read");
|
|
3152
|
+
if (denied) return denied;
|
|
3153
|
+
try {
|
|
3154
|
+
const agent = await findAgentBySlug(dataSource, llmAgents, slug);
|
|
3155
|
+
if (!agent) return json({ error: "Agent not found" }, { status: 404 });
|
|
3156
|
+
const linkRepo = dataSource.getRepository(junction);
|
|
3157
|
+
const links = await linkRepo.find({ where: { agentId: agent.id } });
|
|
3158
|
+
const docIds = [...new Set(links.map((l) => l.documentId))];
|
|
3159
|
+
if (docIds.length === 0) return json({ documents: [] });
|
|
3160
|
+
const docRepo = dataSource.getRepository(kbDoc);
|
|
3161
|
+
const docs = await docRepo.find({ where: { id: In2(docIds) } });
|
|
3162
|
+
const byId = new Map(docs.map((d) => [d.id, d]));
|
|
3163
|
+
const documents = docIds.map((id) => {
|
|
3164
|
+
const d = byId.get(id);
|
|
3165
|
+
return d ? { id: d.id, name: d.name } : null;
|
|
3166
|
+
}).filter(Boolean);
|
|
3167
|
+
return json({ documents });
|
|
3168
|
+
} catch (err) {
|
|
3169
|
+
const msg = err instanceof Error ? err.message : "Failed to list knowledge";
|
|
3170
|
+
return json({ error: msg }, { status: 500 });
|
|
3171
|
+
}
|
|
3172
|
+
},
|
|
3173
|
+
async post(req, slug) {
|
|
3174
|
+
const denied = await gate(req, "update");
|
|
3175
|
+
if (denied) return denied;
|
|
3176
|
+
try {
|
|
3177
|
+
const agent = await findAgentBySlug(dataSource, llmAgents, slug);
|
|
3178
|
+
if (!agent) return json({ error: "Agent not found" }, { status: 404 });
|
|
3179
|
+
let name = "";
|
|
3180
|
+
let text = "";
|
|
3181
|
+
let sourceUrl = null;
|
|
3182
|
+
let existingDocumentId = null;
|
|
3183
|
+
const ct = req.headers.get("content-type") || "";
|
|
3184
|
+
if (ct.includes("application/json")) {
|
|
3185
|
+
const body = await req.json();
|
|
3186
|
+
existingDocumentId = typeof body?.documentId === "number" && Number.isFinite(body.documentId) ? body.documentId : null;
|
|
3187
|
+
name = (body?.name ?? "").trim();
|
|
3188
|
+
text = (body?.text ?? "").trim();
|
|
3189
|
+
sourceUrl = typeof body?.sourceUrl === "string" && body.sourceUrl.trim() ? body.sourceUrl.trim() : null;
|
|
3190
|
+
} else if (ct.includes("multipart/form-data")) {
|
|
3191
|
+
const form = await req.formData();
|
|
3192
|
+
name = form.get("name")?.trim() ?? "";
|
|
3193
|
+
text = form.get("text")?.trim() ?? "";
|
|
3194
|
+
const file = form.get("file");
|
|
3195
|
+
if (file && typeof file !== "string" && "arrayBuffer" in file) {
|
|
3196
|
+
const f = file;
|
|
3197
|
+
const mime = (f.type || "").split(";")[0].trim().toLowerCase();
|
|
3198
|
+
const buf = Buffer.from(await f.arrayBuffer());
|
|
3199
|
+
if (TEXT_FILE_TYPES.has(mime)) {
|
|
3200
|
+
const decoded = buf.toString("utf8");
|
|
3201
|
+
if (!text) text = decoded;
|
|
3202
|
+
} else if (isPdfUpload(mime, f.name || "")) {
|
|
3203
|
+
if (buf.length > MAX_PDF_BYTES) {
|
|
3204
|
+
return json(
|
|
3205
|
+
{ error: `PDF too large (max ${Math.floor(MAX_PDF_BYTES / (1024 * 1024))}MB)` },
|
|
3206
|
+
{ status: 413 }
|
|
3207
|
+
);
|
|
3208
|
+
}
|
|
3209
|
+
try {
|
|
3210
|
+
const extracted = await extractTextFromPdf(buf);
|
|
3211
|
+
if (!text) text = extracted;
|
|
3212
|
+
} catch {
|
|
3213
|
+
return json(
|
|
3214
|
+
{ error: "Could not read PDF text (file may be encrypted, corrupt, or image-only)" },
|
|
3215
|
+
{ status: 422 }
|
|
3216
|
+
);
|
|
3217
|
+
}
|
|
3218
|
+
} else {
|
|
3219
|
+
return json(
|
|
3220
|
+
{
|
|
3221
|
+
error: "Unsupported file type; use text/plain, text/markdown, application/json, or application/pdf"
|
|
3222
|
+
},
|
|
3223
|
+
{ status: 415 }
|
|
3224
|
+
);
|
|
3225
|
+
}
|
|
3226
|
+
if (!name && f.name) name = f.name.replace(/\.[^/.]+$/, "") || f.name;
|
|
3227
|
+
}
|
|
3228
|
+
} else {
|
|
3229
|
+
return json({ error: "Use application/json or multipart/form-data" }, { status: 400 });
|
|
3230
|
+
}
|
|
3231
|
+
const linkRepo = dataSource.getRepository(junction);
|
|
3232
|
+
if (existingDocumentId != null) {
|
|
3233
|
+
const docRepo2 = dataSource.getRepository(kbDoc);
|
|
3234
|
+
const existing = await docRepo2.findOne({ where: { id: existingDocumentId } });
|
|
3235
|
+
if (!existing) return json({ error: "documentId not found" }, { status: 404 });
|
|
3236
|
+
const dup2 = await linkRepo.findOne({
|
|
3237
|
+
where: { agentId: agent.id, documentId: existingDocumentId }
|
|
3238
|
+
});
|
|
3239
|
+
if (!dup2) {
|
|
3240
|
+
await linkRepo.save(linkRepo.create({ agentId: agent.id, documentId: existingDocumentId }));
|
|
3241
|
+
}
|
|
3242
|
+
return json({ documentId: existingDocumentId, linked: true, created: false });
|
|
3243
|
+
}
|
|
3244
|
+
if (!text) return json({ error: "text or file with text content is required" }, { status: 400 });
|
|
3245
|
+
if (!name) name = "Untitled";
|
|
3246
|
+
const parts = splitIntoChunks(text, INGEST_CHUNK_CHARS);
|
|
3247
|
+
if (parts.length === 0) {
|
|
3248
|
+
return json({ error: "text or file with text content is required" }, { status: 400 });
|
|
3249
|
+
}
|
|
3250
|
+
if (parts.length > MAX_CHUNKS_PER_UPLOAD) {
|
|
3251
|
+
return json(
|
|
3252
|
+
{
|
|
3253
|
+
error: `Document is too large for one upload (${parts.length} chunks; max ${MAX_CHUNKS_PER_UPLOAD}). Split into smaller files.`
|
|
3254
|
+
},
|
|
3255
|
+
{ status: 413 }
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
3258
|
+
const docRepo = dataSource.getRepository(kbDoc);
|
|
3259
|
+
const chunkRepo = dataSource.getRepository(kbChunk);
|
|
3260
|
+
const now = /* @__PURE__ */ new Date();
|
|
3261
|
+
const doc = await docRepo.save(
|
|
3262
|
+
docRepo.create({ name, content: text, sourceUrl, createdAt: now, updatedAt: now })
|
|
3263
|
+
);
|
|
3264
|
+
const docId = doc.id;
|
|
3265
|
+
const chunkRows = parts.map(
|
|
3266
|
+
(content, i) => chunkRepo.create({ documentId: docId, content, chunkIndex: i, createdAt: now })
|
|
3267
|
+
);
|
|
3268
|
+
const savedList = await chunkRepo.save(chunkRows);
|
|
3269
|
+
const savedChunks = savedList.map(
|
|
3270
|
+
(row, i) => ({
|
|
3271
|
+
id: row.id,
|
|
3272
|
+
content: parts[i]
|
|
3273
|
+
})
|
|
3274
|
+
);
|
|
3275
|
+
const dup = await linkRepo.findOne({ where: { agentId: agent.id, documentId: docId } });
|
|
3276
|
+
if (!dup) {
|
|
3277
|
+
await linkRepo.save(linkRepo.create({ agentId: agent.id, documentId: docId }));
|
|
3278
|
+
}
|
|
3279
|
+
let embeddingsWritten = 0;
|
|
3280
|
+
let embeddingsFailed = 0;
|
|
3281
|
+
try {
|
|
3282
|
+
const cms = await getCms();
|
|
3283
|
+
const llm = cms.getPlugin("llm");
|
|
3284
|
+
if (llm?.embed && savedChunks.length > 0) {
|
|
3285
|
+
console.info(`${KB_LOG} starting embedding pass`, { slug, chunkCount: savedChunks.length });
|
|
3286
|
+
const embedBound = (text2) => llm.embed(text2);
|
|
3287
|
+
const { written, failed } = await writeEmbeddingsConcurrent(
|
|
3288
|
+
dataSource,
|
|
3289
|
+
embedBound,
|
|
3290
|
+
savedChunks,
|
|
3291
|
+
EMBED_CONCURRENCY
|
|
3292
|
+
);
|
|
3293
|
+
embeddingsWritten = written;
|
|
3294
|
+
embeddingsFailed = failed;
|
|
3295
|
+
} else {
|
|
3296
|
+
console.error(`${KB_LOG} embeddings skipped`, {
|
|
3297
|
+
slug,
|
|
3298
|
+
hasLlmPlugin: !!llm,
|
|
3299
|
+
hasEmbed: typeof llm?.embed === "function",
|
|
3300
|
+
chunkCount: savedChunks.length,
|
|
3301
|
+
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
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
} catch (embErr) {
|
|
3305
|
+
const detail = embErr instanceof Error ? embErr.message : String(embErr);
|
|
3306
|
+
console.error(`${KB_LOG} embedding step threw before/during batch`, { slug, detail, embErr });
|
|
3307
|
+
return json(
|
|
3308
|
+
{
|
|
3309
|
+
documentId: docId,
|
|
3310
|
+
chunkCount: savedChunks.length,
|
|
3311
|
+
created: true,
|
|
3312
|
+
linked: true,
|
|
3313
|
+
embeddingsWritten: 0,
|
|
3314
|
+
embeddingsFailed: savedChunks.length,
|
|
3315
|
+
warning: "Document saved and linked; embedding step failed.",
|
|
3316
|
+
detail
|
|
3317
|
+
},
|
|
3318
|
+
{ status: 201 }
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
return json({
|
|
3322
|
+
documentId: docId,
|
|
3323
|
+
chunkCount: savedChunks.length,
|
|
3324
|
+
created: true,
|
|
3325
|
+
linked: true,
|
|
3326
|
+
embeddingsWritten,
|
|
3327
|
+
embeddingsFailed
|
|
3328
|
+
});
|
|
3329
|
+
} catch (err) {
|
|
3330
|
+
const msg = err instanceof Error ? err.message : "Failed to ingest knowledge";
|
|
3331
|
+
const name = err instanceof Error ? err.name : "";
|
|
3332
|
+
return json({ error: msg, errorName: name || void 0 }, { status: 500 });
|
|
3333
|
+
}
|
|
3334
|
+
},
|
|
3335
|
+
async unlink(req, slug, documentIdStr) {
|
|
3336
|
+
const denied = await gate(req, "update");
|
|
3337
|
+
if (denied) return denied;
|
|
3338
|
+
const documentId = parseInt(documentIdStr, 10);
|
|
3339
|
+
if (!Number.isFinite(documentId)) return json({ error: "Invalid document id" }, { status: 400 });
|
|
3340
|
+
try {
|
|
3341
|
+
const agent = await findAgentBySlug(dataSource, llmAgents, slug);
|
|
3342
|
+
if (!agent) return json({ error: "Agent not found" }, { status: 404 });
|
|
3343
|
+
const linkRepo = dataSource.getRepository(junction);
|
|
3344
|
+
await linkRepo.delete({ agentId: agent.id, documentId });
|
|
3345
|
+
return json({ ok: true });
|
|
3346
|
+
} catch (err) {
|
|
3347
|
+
const msg = err instanceof Error ? err.message : "Failed to unlink";
|
|
3348
|
+
return json({ error: msg }, { status: 500 });
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
|
|
2730
3354
|
// src/message-templates/sms-defaults.ts
|
|
2731
3355
|
var SMS_MESSAGE_TEMPLATE_DEFAULTS = [
|
|
2732
3356
|
{
|
|
@@ -3035,6 +3659,26 @@ function createAdminRolesHandlers(config) {
|
|
|
3035
3659
|
}
|
|
3036
3660
|
|
|
3037
3661
|
// src/api/cms-api-handler.ts
|
|
3662
|
+
var KNOWLEDGE_SUFFIX = "knowledge";
|
|
3663
|
+
function matchLlmAgentKnowledgeRoute(path) {
|
|
3664
|
+
const p = path[0] === "api" ? path.slice(1) : path;
|
|
3665
|
+
if (p[0] !== "llm_agents" || p.length < 2) return null;
|
|
3666
|
+
const seg1 = p[1];
|
|
3667
|
+
if (!seg1) return null;
|
|
3668
|
+
if (p[2] === KNOWLEDGE_SUFFIX) {
|
|
3669
|
+
return {
|
|
3670
|
+
slug: seg1,
|
|
3671
|
+
documentId: p.length >= 4 ? p[3] : void 0
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
if (seg1.endsWith(KNOWLEDGE_SUFFIX) && seg1.length > KNOWLEDGE_SUFFIX.length) {
|
|
3675
|
+
const slug = seg1.slice(0, -KNOWLEDGE_SUFFIX.length);
|
|
3676
|
+
if (!slug) return null;
|
|
3677
|
+
if (p.length === 2) return { slug, documentId: void 0 };
|
|
3678
|
+
if (p.length === 3) return { slug, documentId: p[2] };
|
|
3679
|
+
}
|
|
3680
|
+
return null;
|
|
3681
|
+
}
|
|
3038
3682
|
var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
|
|
3039
3683
|
"users",
|
|
3040
3684
|
"password_reset_tokens",
|
|
@@ -3047,7 +3691,8 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
3047
3691
|
"cart_items",
|
|
3048
3692
|
"wishlists",
|
|
3049
3693
|
"wishlist_items",
|
|
3050
|
-
"message_templates"
|
|
3694
|
+
"message_templates",
|
|
3695
|
+
"llm_agent_knowledge_documents"
|
|
3051
3696
|
]);
|
|
3052
3697
|
function createCmsApiHandler(config) {
|
|
3053
3698
|
const {
|
|
@@ -3071,6 +3716,7 @@ function createCmsApiHandler(config) {
|
|
|
3071
3716
|
userProfile,
|
|
3072
3717
|
settings: settingsConfig,
|
|
3073
3718
|
chat: chatConfig,
|
|
3719
|
+
llmAgentKnowledge: llmAgentKnowledgeConfig,
|
|
3074
3720
|
requireEntityPermission: userRequireEntityPermission,
|
|
3075
3721
|
getSessionUser
|
|
3076
3722
|
} = config;
|
|
@@ -3178,6 +3824,17 @@ function createCmsApiHandler(config) {
|
|
|
3178
3824
|
requireEntityPermission: requireEntityPermissionEffective
|
|
3179
3825
|
});
|
|
3180
3826
|
const chatHandlers = chatConfig ? createChatHandlers(chatConfig) : null;
|
|
3827
|
+
const llmAgentKnowledgeMerged = llmAgentKnowledgeConfig ?? (chatConfig ? {
|
|
3828
|
+
dataSource: chatConfig.dataSource,
|
|
3829
|
+
entityMap: chatConfig.entityMap,
|
|
3830
|
+
getCms: chatConfig.getCms,
|
|
3831
|
+
json: chatConfig.json,
|
|
3832
|
+
requireAuth: chatConfig.requireAuth
|
|
3833
|
+
} : void 0);
|
|
3834
|
+
const llmAgentKnowledgeHandlers = llmAgentKnowledgeMerged ? createLlmAgentKnowledgeHandlers({
|
|
3835
|
+
...llmAgentKnowledgeMerged,
|
|
3836
|
+
requireEntityPermission: requireEntityPermissionEffective
|
|
3837
|
+
}) : null;
|
|
3181
3838
|
function resolveResource(segment) {
|
|
3182
3839
|
const model = pathToModel(segment);
|
|
3183
3840
|
return crudResources.includes(model) ? model : segment;
|
|
@@ -3289,7 +3946,32 @@ function createCmsApiHandler(config) {
|
|
|
3289
3946
|
if (method === "GET") return smsMessageTemplateHandlers.GET(req);
|
|
3290
3947
|
if (method === "PUT") return smsMessageTemplateHandlers.PUT(req);
|
|
3291
3948
|
}
|
|
3949
|
+
{
|
|
3950
|
+
const kbMatch = matchLlmAgentKnowledgeRoute(path);
|
|
3951
|
+
if (kbMatch) {
|
|
3952
|
+
if (!llmAgentKnowledgeHandlers) {
|
|
3953
|
+
return config.json(
|
|
3954
|
+
{
|
|
3955
|
+
error: "LLM agent knowledge is not available",
|
|
3956
|
+
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."
|
|
3957
|
+
},
|
|
3958
|
+
{ status: 503 }
|
|
3959
|
+
);
|
|
3960
|
+
}
|
|
3961
|
+
const { slug, documentId } = kbMatch;
|
|
3962
|
+
if (method === "DELETE" && documentId != null && documentId !== "") {
|
|
3963
|
+
return llmAgentKnowledgeHandlers.unlink(req, slug, documentId);
|
|
3964
|
+
}
|
|
3965
|
+
if (method === "GET" && (documentId == null || documentId === "")) {
|
|
3966
|
+
return llmAgentKnowledgeHandlers.list(req, slug);
|
|
3967
|
+
}
|
|
3968
|
+
if (method === "POST" && (documentId == null || documentId === "")) {
|
|
3969
|
+
return llmAgentKnowledgeHandlers.post(req, slug);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3292
3973
|
if (path[0] === "chat" && chatHandlers) {
|
|
3974
|
+
if (path.length === 2 && path[1] === "config" && method === "GET") return chatHandlers.publicConfig(req);
|
|
3293
3975
|
if (path.length === 2 && path[1] === "identify" && method === "POST") return chatHandlers.identify(req);
|
|
3294
3976
|
if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && method === "GET") return chatHandlers.getMessages(req, path[2]);
|
|
3295
3977
|
if (path.length === 2 && path[1] === "messages" && method === "POST") return chatHandlers.postMessage(req);
|
|
@@ -3358,7 +4040,7 @@ function createCmsApiHandler(config) {
|
|
|
3358
4040
|
}
|
|
3359
4041
|
|
|
3360
4042
|
// src/api/storefront-handlers.ts
|
|
3361
|
-
import { In as
|
|
4043
|
+
import { In as In3, IsNull as IsNull4 } from "typeorm";
|
|
3362
4044
|
|
|
3363
4045
|
// src/lib/is-valid-signup-email.ts
|
|
3364
4046
|
var MAX_EMAIL = 254;
|
|
@@ -4962,7 +5644,7 @@ function createStorefrontApiHandler(config) {
|
|
|
4962
5644
|
const previewByOrder = {};
|
|
4963
5645
|
if (orderIds.length) {
|
|
4964
5646
|
const oItems = await orderItemRepo().find({
|
|
4965
|
-
where: { orderId:
|
|
5647
|
+
where: { orderId: In3(orderIds) },
|
|
4966
5648
|
relations: ["product"],
|
|
4967
5649
|
order: { id: "ASC" }
|
|
4968
5650
|
});
|
|
@@ -5098,6 +5780,7 @@ export {
|
|
|
5098
5780
|
createForgotPasswordHandler,
|
|
5099
5781
|
createFormBySlugHandler,
|
|
5100
5782
|
createInviteAcceptHandler,
|
|
5783
|
+
createLlmAgentKnowledgeHandlers,
|
|
5101
5784
|
createMediaZipExtractHandler,
|
|
5102
5785
|
createSetPasswordHandler,
|
|
5103
5786
|
createSettingsApiHandlers,
|
|
@@ -5107,6 +5790,10 @@ export {
|
|
|
5107
5790
|
createUserAvatarHandler,
|
|
5108
5791
|
createUserProfileHandler,
|
|
5109
5792
|
createUsersApiHandlers,
|
|
5110
|
-
getPublicSettingsGroup
|
|
5793
|
+
getPublicSettingsGroup,
|
|
5794
|
+
mergeGuardrailsIntoSystemPrompt,
|
|
5795
|
+
parseLlmAgentValidationRules,
|
|
5796
|
+
validateUserMessageAgainstAgentRules,
|
|
5797
|
+
validateUserMessageAgainstStructuredRules
|
|
5111
5798
|
};
|
|
5112
5799
|
//# sourceMappingURL=api.js.map
|