@infuro/cms-core 1.0.20 → 1.0.21
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 +194 -75
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.js +188 -70
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +3212 -259
- 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 +3214 -252
- package/dist/api.js.map +1 -1
- package/dist/{index-DGtM2Gsk.d.cts → index-BGAh4fPQ.d.cts} +18 -1
- package/dist/{index--GBYw5JE.d.ts → index-Cnwh7B3r.d.ts} +18 -1
- package/dist/index.cjs +729 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -9
- package/dist/index.d.ts +34 -9
- package/dist/index.js +729 -203
- package/dist/index.js.map +1 -1
- package/dist/migrations/1775300000000-LlmAgents.ts +57 -57
- package/dist/migrations/1775300000001-LlmAgentsValidationRulesText.ts +43 -43
- package/dist/migrations/1775300000002-SeedLlmAgentsPermissions.ts +33 -33
- package/dist/migrations/1775300000003-LlmAgentKnowledgeDocuments.ts +50 -50
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2432,6 +2432,17 @@ function localStoragePlugin(config = {}) {
|
|
|
2432
2432
|
}
|
|
2433
2433
|
|
|
2434
2434
|
// src/plugins/llm/llm-service.ts
|
|
2435
|
+
function parseOpenAiEmbeddingJson(data) {
|
|
2436
|
+
let embedding = data?.data?.[0]?.embedding;
|
|
2437
|
+
if (!Array.isArray(embedding) && Array.isArray(data?.embedding)) {
|
|
2438
|
+
embedding = data.embedding;
|
|
2439
|
+
}
|
|
2440
|
+
if (!Array.isArray(embedding) && Array.isArray(data?.data?.[0])) {
|
|
2441
|
+
const first = data.data[0];
|
|
2442
|
+
embedding = Array.isArray(first) ? first : first?.embedding;
|
|
2443
|
+
}
|
|
2444
|
+
return Array.isArray(embedding) ? embedding : null;
|
|
2445
|
+
}
|
|
2435
2446
|
function parseHfInferenceEmbeddingBody(data) {
|
|
2436
2447
|
if (data === null || data === void 0) return null;
|
|
2437
2448
|
if (Array.isArray(data)) {
|
|
@@ -2453,28 +2464,59 @@ function parseHfInferenceEmbeddingBody(data) {
|
|
|
2453
2464
|
}
|
|
2454
2465
|
return null;
|
|
2455
2466
|
}
|
|
2467
|
+
var FALLBACK_CHAT_MODEL_OPENAI_SHAPE = "openai/gpt-4o-mini";
|
|
2468
|
+
var FALLBACK_CHAT_MODEL_HUGGINGFACE = "meta-llama/Meta-Llama-3.1-8B-Instruct";
|
|
2469
|
+
function defaultChatModelForGateway(baseURL) {
|
|
2470
|
+
const u = baseURL.toLowerCase();
|
|
2471
|
+
if (u.includes("huggingface.co") || u.includes("hf.co") || u.includes("hf-inference")) {
|
|
2472
|
+
return FALLBACK_CHAT_MODEL_HUGGINGFACE;
|
|
2473
|
+
}
|
|
2474
|
+
return FALLBACK_CHAT_MODEL_OPENAI_SHAPE;
|
|
2475
|
+
}
|
|
2476
|
+
function formatLlmGatewayError(status, bodyText, defaultChatModel) {
|
|
2477
|
+
let hint = "";
|
|
2478
|
+
try {
|
|
2479
|
+
const j = JSON.parse(bodyText);
|
|
2480
|
+
const code = j?.error?.code;
|
|
2481
|
+
if (code === "model_not_supported" || code === "model_not_found" || /not supported by any provider|does not exist/i.test(j?.error?.message ?? "")) {
|
|
2482
|
+
hint = ` Hint: set LLM_CHAT_MODEL (or CMS) to a model your gateway lists. HF router: Hub ids (e.g. "${FALLBACK_CHAT_MODEL_HUGGINGFACE}"); OpenRouter: e.g. "${FALLBACK_CHAT_MODEL_OPENAI_SHAPE}".`;
|
|
2483
|
+
}
|
|
2484
|
+
} catch {
|
|
2485
|
+
}
|
|
2486
|
+
return `LLM gateway error: ${status} ${bodyText}${hint}`;
|
|
2487
|
+
}
|
|
2456
2488
|
var LlmService = class _LlmService {
|
|
2457
2489
|
static embedHttpErrorLogged = false;
|
|
2458
2490
|
static embedShapeErrorLogged = false;
|
|
2459
2491
|
static embedSuccessLogged = false;
|
|
2460
2492
|
static hfDimensionMismatchWarned = false;
|
|
2493
|
+
static hfInferenceSdkErrorLogged = false;
|
|
2461
2494
|
baseURL;
|
|
2462
2495
|
apiKey;
|
|
2463
2496
|
defaultEmbeddingModel;
|
|
2464
2497
|
embeddingProvider;
|
|
2465
2498
|
hfInferenceBaseURL;
|
|
2466
2499
|
embeddingApiKey;
|
|
2500
|
+
defaultChatModel;
|
|
2467
2501
|
constructor(baseURL, apiKey, defaultEmbeddingModel, embedOpts) {
|
|
2468
2502
|
this.baseURL = baseURL.replace(/\/$/, "");
|
|
2469
2503
|
this.apiKey = apiKey;
|
|
2470
2504
|
this.defaultEmbeddingModel = defaultEmbeddingModel;
|
|
2471
2505
|
const p = (embedOpts?.embeddingProvider ?? "openai").toLowerCase();
|
|
2472
2506
|
this.embeddingProvider = p === "huggingface" ? "huggingface" : "openai";
|
|
2473
|
-
this.hfInferenceBaseURL = (embedOpts?.hfInferenceBaseUrl ?? "https://
|
|
2507
|
+
this.hfInferenceBaseURL = (embedOpts?.hfInferenceBaseUrl ?? "https://router.huggingface.co/v1").replace(
|
|
2474
2508
|
/\/$/,
|
|
2475
2509
|
""
|
|
2476
2510
|
);
|
|
2477
2511
|
this.embeddingApiKey = (embedOpts?.embeddingApiKey ?? apiKey).trim();
|
|
2512
|
+
const fromOpts = embedOpts?.defaultChatModel?.trim();
|
|
2513
|
+
const fromEnv = typeof process !== "undefined" ? process.env.LLM_CHAT_MODEL?.trim() : "";
|
|
2514
|
+
const auto = defaultChatModelForGateway(this.baseURL);
|
|
2515
|
+
this.defaultChatModel = (fromOpts || fromEnv || auto).trim() || auto;
|
|
2516
|
+
}
|
|
2517
|
+
resolveChatModel(explicit) {
|
|
2518
|
+
const m = (explicit?.trim() || this.defaultChatModel).trim();
|
|
2519
|
+
return m || defaultChatModelForGateway(this.baseURL);
|
|
2478
2520
|
}
|
|
2479
2521
|
get base() {
|
|
2480
2522
|
return this.baseURL.replace(/\/$/, "");
|
|
@@ -2514,7 +2556,7 @@ var LlmService = class _LlmService {
|
|
|
2514
2556
|
}
|
|
2515
2557
|
logChatCompletionsRequest(kind, messages, options, stream) {
|
|
2516
2558
|
if (!this.llmDebugEnabled()) return;
|
|
2517
|
-
const model = options.model
|
|
2559
|
+
const model = this.resolveChatModel(options.model);
|
|
2518
2560
|
console.log(`
|
|
2519
2561
|
[LLM ${kind}] POST`, this.url);
|
|
2520
2562
|
console.log("[Request options]", {
|
|
@@ -2532,7 +2574,12 @@ var LlmService = class _LlmService {
|
|
|
2532
2574
|
return this.embedHuggingFaceInference(input, model2);
|
|
2533
2575
|
}
|
|
2534
2576
|
const model = options.model ?? this.defaultEmbeddingModel ?? "text-embedding-3-small";
|
|
2535
|
-
|
|
2577
|
+
return this.fetchOpenAiShapeEmbeddings(`${this.base}/v1/embeddings`, model, input, this.apiKey);
|
|
2578
|
+
}
|
|
2579
|
+
/**
|
|
2580
|
+
* POST `{ url }` with OpenAI-style `{ model, input }` and Bearer token; parses `{ data:[{embedding}] }`.
|
|
2581
|
+
*/
|
|
2582
|
+
async fetchOpenAiShapeEmbeddings(embedUrl, model, input, bearerToken) {
|
|
2536
2583
|
if (this.embedDebugEnabled()) {
|
|
2537
2584
|
console.info("[LLM embed] request", { url: embedUrl, model, inputChars: input.length });
|
|
2538
2585
|
}
|
|
@@ -2540,7 +2587,7 @@ var LlmService = class _LlmService {
|
|
|
2540
2587
|
method: "POST",
|
|
2541
2588
|
headers: {
|
|
2542
2589
|
"Content-Type": "application/json",
|
|
2543
|
-
Authorization: `Bearer ${
|
|
2590
|
+
Authorization: `Bearer ${bearerToken}`
|
|
2544
2591
|
},
|
|
2545
2592
|
body: JSON.stringify({
|
|
2546
2593
|
model,
|
|
@@ -2549,7 +2596,7 @@ var LlmService = class _LlmService {
|
|
|
2549
2596
|
});
|
|
2550
2597
|
if (!res.ok) {
|
|
2551
2598
|
const body = await res.text();
|
|
2552
|
-
const hint404 = res.status === 404 ? "This base URL does not expose OpenAI-compatible POST /v1/embeddings
|
|
2599
|
+
const hint404 = res.status === 404 ? "This base URL does not expose OpenAI-compatible POST /v1/embeddings. For Hugging Face embeddings, set EMBEDDING_PROVIDER=huggingface and an HF token (EMBEDDING_API_KEY); the server uses @huggingface/inference (router /v1/embeddings is chat-only and is not used)." : "Embeddings HTTP error \u2014 knowledge_base_chunks.embedding will stay NULL for failed calls.";
|
|
2553
2600
|
if (!_LlmService.embedHttpErrorLogged) {
|
|
2554
2601
|
_LlmService.embedHttpErrorLogged = true;
|
|
2555
2602
|
console.error("[LLM embed]", hint404, {
|
|
@@ -2573,15 +2620,8 @@ var LlmService = class _LlmService {
|
|
|
2573
2620
|
}
|
|
2574
2621
|
return [];
|
|
2575
2622
|
}
|
|
2576
|
-
|
|
2577
|
-
if (!
|
|
2578
|
-
embedding = data.embedding;
|
|
2579
|
-
}
|
|
2580
|
-
if (!Array.isArray(embedding) && Array.isArray(data?.data?.[0])) {
|
|
2581
|
-
const first = data.data[0];
|
|
2582
|
-
embedding = Array.isArray(first) ? first : first?.embedding;
|
|
2583
|
-
}
|
|
2584
|
-
if (!Array.isArray(embedding)) {
|
|
2623
|
+
const embedding = parseOpenAiEmbeddingJson(data);
|
|
2624
|
+
if (!embedding) {
|
|
2585
2625
|
if (!_LlmService.embedShapeErrorLogged) {
|
|
2586
2626
|
_LlmService.embedShapeErrorLogged = true;
|
|
2587
2627
|
console.error(
|
|
@@ -2603,96 +2643,201 @@ var LlmService = class _LlmService {
|
|
|
2603
2643
|
}
|
|
2604
2644
|
return embedding;
|
|
2605
2645
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
let embedUrl = pipelineUrl;
|
|
2619
|
-
let res = await fetch(embedUrl, {
|
|
2620
|
-
method: "POST",
|
|
2621
|
-
headers: {
|
|
2622
|
-
"Content-Type": "application/json",
|
|
2623
|
-
Authorization: `Bearer ${this.embeddingApiKey}`
|
|
2624
|
-
},
|
|
2625
|
-
body: requestBody
|
|
2626
|
-
});
|
|
2627
|
-
let bodyText = await res.text();
|
|
2628
|
-
if (res.status === 404) {
|
|
2629
|
-
embedUrl = modelUrl;
|
|
2630
|
-
if (this.embedDebugEnabled()) {
|
|
2631
|
-
console.warn("[LLM embed HF] /pipeline route returned 404; retrying with /models.", {
|
|
2632
|
-
model,
|
|
2633
|
-
fallbackUrl: embedUrl
|
|
2634
|
-
});
|
|
2635
|
-
}
|
|
2636
|
-
res = await fetch(embedUrl, {
|
|
2637
|
-
method: "POST",
|
|
2638
|
-
headers: {
|
|
2639
|
-
"Content-Type": "application/json",
|
|
2640
|
-
Authorization: `Bearer ${this.embeddingApiKey}`
|
|
2641
|
-
},
|
|
2642
|
-
body: requestBody
|
|
2646
|
+
/**
|
|
2647
|
+
* Hugging Face–recommended path: Hub resolves a provider that supports `feature-extraction` for the model.
|
|
2648
|
+
*/
|
|
2649
|
+
async embedViaHuggingfaceInferenceSdk(input, model) {
|
|
2650
|
+
const token = this.embeddingApiKey.trim();
|
|
2651
|
+
if (!token) return null;
|
|
2652
|
+
try {
|
|
2653
|
+
const { InferenceClient } = await import("@huggingface/inference");
|
|
2654
|
+
const hf = new InferenceClient(token);
|
|
2655
|
+
const raw = await hf.featureExtraction({
|
|
2656
|
+
model,
|
|
2657
|
+
inputs: input
|
|
2643
2658
|
});
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2659
|
+
const embedding = parseHfInferenceEmbeddingBody(raw);
|
|
2660
|
+
if (!embedding?.length) {
|
|
2661
|
+
if (this.embedDebugEnabled()) {
|
|
2662
|
+
console.warn("[LLM embed HF] Inference SDK returned no parseable vector", {
|
|
2663
|
+
model,
|
|
2664
|
+
shape: Array.isArray(raw) ? `array(len=${raw.length})` : typeof raw
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
return null;
|
|
2668
|
+
}
|
|
2669
|
+
return embedding;
|
|
2670
|
+
} catch (err) {
|
|
2671
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2672
|
+
if (!_LlmService.hfInferenceSdkErrorLogged) {
|
|
2673
|
+
_LlmService.hfInferenceSdkErrorLogged = true;
|
|
2674
|
+
console.warn(
|
|
2675
|
+
"[LLM embed HF] @huggingface/inference featureExtraction failed \u2014 trying legacy HTTP URLs.",
|
|
2676
|
+
{ model, message: message.slice(0, 500) }
|
|
2658
2677
|
);
|
|
2659
2678
|
} else if (this.embedDebugEnabled()) {
|
|
2660
|
-
console.warn("[LLM embed HF] repeat
|
|
2679
|
+
console.warn("[LLM embed HF] repeat Inference SDK failure", { message: message.slice(0, 200) });
|
|
2661
2680
|
}
|
|
2662
|
-
return
|
|
2681
|
+
return null;
|
|
2663
2682
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
_LlmService.embedShapeErrorLogged = true;
|
|
2670
|
-
console.error("[LLM embed HF] Response is not JSON \u2014 treating as no embedding.");
|
|
2671
|
-
}
|
|
2672
|
-
return [];
|
|
2683
|
+
}
|
|
2684
|
+
/** Base URL for legacy `…/pipeline/…` and `…/models/{model}` tries (HF router only). */
|
|
2685
|
+
hfLegacyInferenceBase(normalizedBase) {
|
|
2686
|
+
if (/router\.huggingface\.co|api-inference\.huggingface\.co/i.test(normalizedBase)) {
|
|
2687
|
+
return "https://router.huggingface.co/hf-inference";
|
|
2673
2688
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2689
|
+
return normalizedBase;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Hugging Face embeddings: `@huggingface/inference` (Hub-routed feature extraction), then
|
|
2693
|
+
* legacy direct `hf-inference` HTTP tries. Note: `https://router.huggingface.co/v1/embeddings` is not
|
|
2694
|
+
* offered for embeddings (OpenAI-compatible `/v1` on HF is chat-only).
|
|
2695
|
+
*/
|
|
2696
|
+
async embedHuggingFaceInference(input, model) {
|
|
2697
|
+
const normalizedBase = this.hfInferenceBaseURL.replace(/\/$/, "");
|
|
2698
|
+
const fromSdk = await this.embedViaHuggingfaceInferenceSdk(input, model);
|
|
2699
|
+
if (fromSdk?.length) {
|
|
2700
|
+
if (!_LlmService.embedSuccessLogged) {
|
|
2701
|
+
_LlmService.embedSuccessLogged = true;
|
|
2702
|
+
console.info("[LLM embed HF] first success", { dimension: fromSdk.length, model, via: "@huggingface/inference" });
|
|
2703
|
+
if (!_LlmService.hfDimensionMismatchWarned && fromSdk.length !== 1536) {
|
|
2704
|
+
_LlmService.hfDimensionMismatchWarned = true;
|
|
2705
|
+
console.warn(
|
|
2706
|
+
`[LLM embed HF] Embedding dimensions=${fromSdk.length}; ensure knowledge_base_chunks.embedding uses vector(${fromSdk.length}) (see migrations).`
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
} else if (this.embedDebugEnabled()) {
|
|
2710
|
+
console.info("[LLM embed HF] ok", { dimension: fromSdk.length, via: "@huggingface/inference" });
|
|
2680
2711
|
}
|
|
2681
|
-
return
|
|
2712
|
+
return fromSdk;
|
|
2682
2713
|
}
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2714
|
+
const legacyBase = this.hfLegacyInferenceBase(normalizedBase);
|
|
2715
|
+
const baseWithProvider = /\/hf-inference$/i.test(legacyBase) || !/router\.huggingface\.co/i.test(legacyBase) ? legacyBase : `${legacyBase.replace(/\/$/, "")}/hf-inference`;
|
|
2716
|
+
const bodyVariants = [
|
|
2717
|
+
{
|
|
2718
|
+
label: "inputs_parameters_mean_normalize",
|
|
2719
|
+
body: JSON.stringify({
|
|
2720
|
+
inputs: input,
|
|
2721
|
+
parameters: { pooling: "mean", normalize: true },
|
|
2722
|
+
options: { wait_for_model: true }
|
|
2723
|
+
})
|
|
2724
|
+
},
|
|
2725
|
+
{
|
|
2726
|
+
label: "inputs_string",
|
|
2727
|
+
body: JSON.stringify({ inputs: input, options: { wait_for_model: true } })
|
|
2728
|
+
},
|
|
2729
|
+
{
|
|
2730
|
+
label: "inputs_string_array",
|
|
2731
|
+
body: JSON.stringify({ inputs: [input], options: { wait_for_model: true } })
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
label: "inputs_wrapped_sentences",
|
|
2735
|
+
body: JSON.stringify({ inputs: { sentences: [input] }, options: { wait_for_model: true } })
|
|
2736
|
+
},
|
|
2737
|
+
{
|
|
2738
|
+
label: "top_level_sentences",
|
|
2739
|
+
body: JSON.stringify({ sentences: [input], options: { wait_for_model: true } })
|
|
2691
2740
|
}
|
|
2741
|
+
];
|
|
2742
|
+
const headers = {
|
|
2743
|
+
"Content-Type": "application/json",
|
|
2744
|
+
Authorization: `Bearer ${this.embeddingApiKey}`
|
|
2745
|
+
};
|
|
2746
|
+
const candidateUrls = [];
|
|
2747
|
+
const pushUrl = (u) => {
|
|
2748
|
+
if (!candidateUrls.includes(u)) candidateUrls.push(u);
|
|
2749
|
+
};
|
|
2750
|
+
pushUrl(`${baseWithProvider}/pipeline/feature-extraction/${model}`);
|
|
2751
|
+
pushUrl(`${baseWithProvider}/models/${model}`);
|
|
2752
|
+
let lastUrl = "";
|
|
2753
|
+
let lastStatus = 0;
|
|
2754
|
+
let lastBodyPreview = "";
|
|
2755
|
+
let lastAttempt = "";
|
|
2756
|
+
for (const embedUrl of candidateUrls) {
|
|
2757
|
+
for (const { label, body } of bodyVariants) {
|
|
2758
|
+
if (this.embedDebugEnabled()) {
|
|
2759
|
+
console.info("[LLM embed HF] attempt", { url: embedUrl, model, inputChars: input.length, body: label });
|
|
2760
|
+
}
|
|
2761
|
+
const res = await fetch(embedUrl, {
|
|
2762
|
+
method: "POST",
|
|
2763
|
+
headers,
|
|
2764
|
+
body
|
|
2765
|
+
});
|
|
2766
|
+
const bodyText = await res.text();
|
|
2767
|
+
lastUrl = embedUrl;
|
|
2768
|
+
lastStatus = res.status;
|
|
2769
|
+
lastBodyPreview = bodyText.slice(0, 800);
|
|
2770
|
+
lastAttempt = `${embedUrl} (${label})`;
|
|
2771
|
+
if (!res.ok) {
|
|
2772
|
+
if (this.embedDebugEnabled()) {
|
|
2773
|
+
console.warn("[LLM embed HF] attempt failed, trying next if any", {
|
|
2774
|
+
url: embedUrl,
|
|
2775
|
+
body: label,
|
|
2776
|
+
status: res.status,
|
|
2777
|
+
bodyPreview: bodyText.slice(0, 200)
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
let data;
|
|
2783
|
+
try {
|
|
2784
|
+
data = JSON.parse(bodyText);
|
|
2785
|
+
} catch {
|
|
2786
|
+
if (this.embedDebugEnabled()) {
|
|
2787
|
+
console.warn("[LLM embed HF] response not JSON, trying next if any", { url: embedUrl, body: label });
|
|
2788
|
+
}
|
|
2789
|
+
continue;
|
|
2790
|
+
}
|
|
2791
|
+
const embedding = parseHfInferenceEmbeddingBody(data);
|
|
2792
|
+
if (!embedding?.length) {
|
|
2793
|
+
if (this.embedDebugEnabled()) {
|
|
2794
|
+
const preview = typeof data === "object" && data !== null && !Array.isArray(data) ? Object.keys(data) : typeof data;
|
|
2795
|
+
console.warn("[LLM embed HF] no parseable embedding, trying next if any", {
|
|
2796
|
+
url: embedUrl,
|
|
2797
|
+
body: label,
|
|
2798
|
+
preview
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
continue;
|
|
2802
|
+
}
|
|
2803
|
+
if (!_LlmService.embedSuccessLogged) {
|
|
2804
|
+
_LlmService.embedSuccessLogged = true;
|
|
2805
|
+
console.info("[LLM embed HF] first success", { dimension: embedding.length, model, url: embedUrl, body: label });
|
|
2806
|
+
if (!_LlmService.hfDimensionMismatchWarned && embedding.length !== 1536) {
|
|
2807
|
+
_LlmService.hfDimensionMismatchWarned = true;
|
|
2808
|
+
console.warn(
|
|
2809
|
+
`[LLM embed HF] Embedding dimensions=${embedding.length}; ensure knowledge_base_chunks.embedding uses vector(${embedding.length}) (see migrations).`
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
} else if (this.embedDebugEnabled()) {
|
|
2813
|
+
console.info("[LLM embed HF] ok", { dimension: embedding.length, url: embedUrl, body: label });
|
|
2814
|
+
}
|
|
2815
|
+
return embedding;
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
if (!_LlmService.embedHttpErrorLogged) {
|
|
2819
|
+
_LlmService.embedHttpErrorLogged = true;
|
|
2820
|
+
console.error(
|
|
2821
|
+
"[LLM embed HF] All Hugging Face embedding attempts failed \u2014 knowledge chunks stay without vectors.",
|
|
2822
|
+
{
|
|
2823
|
+
triedInferenceSdk: true,
|
|
2824
|
+
triedLegacyHttp: candidateUrls,
|
|
2825
|
+
lastAttempt,
|
|
2826
|
+
lastUrl,
|
|
2827
|
+
lastStatus,
|
|
2828
|
+
lastBodyPreview,
|
|
2829
|
+
hint: "Use an HF token with permission to call Inference Providers / Hub API mapping. Install ships @huggingface/inference; EMBEDDING_MODEL defaults to sentence-transformers/all-MiniLM-L6-v2. Router OpenAI /v1/embeddings is not used (chat-only)."
|
|
2830
|
+
}
|
|
2831
|
+
);
|
|
2692
2832
|
} else if (this.embedDebugEnabled()) {
|
|
2693
|
-
console.
|
|
2833
|
+
console.warn("[LLM embed HF] repeat failure (all attempts)", {
|
|
2834
|
+
lastAttempt,
|
|
2835
|
+
lastUrl,
|
|
2836
|
+
lastStatus,
|
|
2837
|
+
bodyPreview: lastBodyPreview.slice(0, 200)
|
|
2838
|
+
});
|
|
2694
2839
|
}
|
|
2695
|
-
return
|
|
2840
|
+
return [];
|
|
2696
2841
|
}
|
|
2697
2842
|
buildAgentMessages(options) {
|
|
2698
2843
|
const { systemPrompt, userPrompt, history = [], context } = options;
|
|
@@ -2736,7 +2881,7 @@ ${context}`;
|
|
|
2736
2881
|
Authorization: `Bearer ${this.apiKey}`
|
|
2737
2882
|
},
|
|
2738
2883
|
body: JSON.stringify({
|
|
2739
|
-
model: options.model
|
|
2884
|
+
model: this.resolveChatModel(options.model),
|
|
2740
2885
|
messages,
|
|
2741
2886
|
stream: false,
|
|
2742
2887
|
temperature: options.temperature,
|
|
@@ -2745,7 +2890,7 @@ ${context}`;
|
|
|
2745
2890
|
});
|
|
2746
2891
|
if (!res.ok) {
|
|
2747
2892
|
const text = await res.text();
|
|
2748
|
-
throw new Error(
|
|
2893
|
+
throw new Error(formatLlmGatewayError(res.status, text, this.defaultChatModel));
|
|
2749
2894
|
}
|
|
2750
2895
|
const data = await res.json();
|
|
2751
2896
|
const content = data.choices?.[0]?.message?.content ?? "";
|
|
@@ -2763,7 +2908,7 @@ ${context}`;
|
|
|
2763
2908
|
Authorization: `Bearer ${this.apiKey}`
|
|
2764
2909
|
},
|
|
2765
2910
|
body: JSON.stringify({
|
|
2766
|
-
model: options.model
|
|
2911
|
+
model: this.resolveChatModel(options.model),
|
|
2767
2912
|
messages,
|
|
2768
2913
|
stream: true,
|
|
2769
2914
|
temperature: options.temperature,
|
|
@@ -2772,7 +2917,7 @@ ${context}`;
|
|
|
2772
2917
|
});
|
|
2773
2918
|
if (!res.ok) {
|
|
2774
2919
|
const text = await res.text();
|
|
2775
|
-
throw new Error(
|
|
2920
|
+
throw new Error(formatLlmGatewayError(res.status, text, this.defaultChatModel));
|
|
2776
2921
|
}
|
|
2777
2922
|
const reader = res.body?.getReader();
|
|
2778
2923
|
if (!reader) return;
|
|
@@ -2825,6 +2970,7 @@ function llmPlugin(config = {}) {
|
|
|
2825
2970
|
return void 0;
|
|
2826
2971
|
}
|
|
2827
2972
|
const embeddingModel = config.embeddingModel ?? context.config.EMBEDDING_MODEL;
|
|
2973
|
+
const defaultChatModel = config.defaultChatModel?.trim() || (typeof context.config.LLM_CHAT_MODEL === "string" ? context.config.LLM_CHAT_MODEL.trim() : "") || void 0;
|
|
2828
2974
|
const embeddingProvider = normalizeEmbeddingProvider(
|
|
2829
2975
|
config.embeddingProvider ?? context.config.EMBEDDING_PROVIDER
|
|
2830
2976
|
);
|
|
@@ -2833,7 +2979,8 @@ function llmPlugin(config = {}) {
|
|
|
2833
2979
|
return new LlmService(baseURL.replace(/\/$/, ""), apiKey, embeddingModel, {
|
|
2834
2980
|
embeddingProvider,
|
|
2835
2981
|
hfInferenceBaseUrl,
|
|
2836
|
-
embeddingApiKey
|
|
2982
|
+
embeddingApiKey,
|
|
2983
|
+
defaultChatModel
|
|
2837
2984
|
});
|
|
2838
2985
|
}
|
|
2839
2986
|
};
|
|
@@ -8363,6 +8510,13 @@ function getNextAuthOptions(config) {
|
|
|
8363
8510
|
|
|
8364
8511
|
// src/api/crud.ts
|
|
8365
8512
|
import { Brackets, ILike as ILike2, MoreThan as MoreThan2 } from "typeorm";
|
|
8513
|
+
var CRUD_LOG = "[cms-crud]";
|
|
8514
|
+
function logCrudClientError(op, detail) {
|
|
8515
|
+
console.warn(CRUD_LOG, op, detail);
|
|
8516
|
+
}
|
|
8517
|
+
function logCrudServerError(op, detail) {
|
|
8518
|
+
console.error(CRUD_LOG, op, detail);
|
|
8519
|
+
}
|
|
8366
8520
|
var DATE_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
8367
8521
|
"date",
|
|
8368
8522
|
"datetime",
|
|
@@ -8474,6 +8628,13 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8474
8628
|
if (authError) return authError;
|
|
8475
8629
|
const entity = entityMap[resource];
|
|
8476
8630
|
if (!resource || !entity) {
|
|
8631
|
+
logCrudClientError("GET list", {
|
|
8632
|
+
reason: "invalid_resource",
|
|
8633
|
+
resource,
|
|
8634
|
+
hasEntity: Boolean(entity),
|
|
8635
|
+
entityMapHasLlmAgents: Boolean(entityMap.llm_agents),
|
|
8636
|
+
entityMapKeyCount: Object.keys(entityMap).length
|
|
8637
|
+
});
|
|
8477
8638
|
return json({ error: "Invalid resource" }, { status: 400 });
|
|
8478
8639
|
}
|
|
8479
8640
|
const { searchParams } = new URL(req.url);
|
|
@@ -8681,12 +8842,22 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8681
8842
|
}
|
|
8682
8843
|
}
|
|
8683
8844
|
where = mergeDeletedFalseWhere(repo, where);
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8845
|
+
let data;
|
|
8846
|
+
let total;
|
|
8847
|
+
try {
|
|
8848
|
+
const r = await repo.findAndCount({
|
|
8849
|
+
skip,
|
|
8850
|
+
take: limit,
|
|
8851
|
+
order: { [sortField]: sortOrder },
|
|
8852
|
+
where
|
|
8853
|
+
});
|
|
8854
|
+
data = r[0];
|
|
8855
|
+
total = r[1];
|
|
8856
|
+
} catch (err) {
|
|
8857
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8858
|
+
logCrudServerError("GET list query failed", { resource, sortField, sortOrder, message });
|
|
8859
|
+
throw err;
|
|
8860
|
+
}
|
|
8690
8861
|
return json({ total, page, limit, totalPages: Math.ceil(total / limit), data });
|
|
8691
8862
|
},
|
|
8692
8863
|
async POST(req, resource) {
|
|
@@ -8694,12 +8865,25 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8694
8865
|
if (authError) return authError;
|
|
8695
8866
|
const entity = entityMap[resource];
|
|
8696
8867
|
if (!resource || !entity) {
|
|
8868
|
+
logCrudClientError("POST create", {
|
|
8869
|
+
reason: "invalid_resource",
|
|
8870
|
+
resource,
|
|
8871
|
+
hasEntity: Boolean(entity),
|
|
8872
|
+
entityMapHasLlmAgents: Boolean(entityMap.llm_agents)
|
|
8873
|
+
});
|
|
8697
8874
|
return json({ error: "Invalid resource" }, { status: 400 });
|
|
8698
8875
|
}
|
|
8699
|
-
const
|
|
8700
|
-
if (!
|
|
8876
|
+
const rawPostBody = await req.json();
|
|
8877
|
+
if (!rawPostBody || typeof rawPostBody !== "object" || Object.keys(rawPostBody).length === 0) {
|
|
8878
|
+
logCrudClientError("POST create", {
|
|
8879
|
+
reason: "invalid_request_payload",
|
|
8880
|
+
resource,
|
|
8881
|
+
rawType: rawPostBody == null ? "nullish" : typeof rawPostBody,
|
|
8882
|
+
keyCount: rawPostBody && typeof rawPostBody === "object" ? Object.keys(rawPostBody).length : 0
|
|
8883
|
+
});
|
|
8701
8884
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
8702
8885
|
}
|
|
8886
|
+
const body = rawPostBody;
|
|
8703
8887
|
if (resource === "media") {
|
|
8704
8888
|
const b = body;
|
|
8705
8889
|
const kind = b.kind === "folder" ? "folder" : "file";
|
|
@@ -8733,8 +8917,24 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8733
8917
|
}
|
|
8734
8918
|
}
|
|
8735
8919
|
const repo = dataSource.getRepository(entity);
|
|
8736
|
-
|
|
8737
|
-
|
|
8920
|
+
const persistBody = resource === "media" ? body : pickColumnUpdates(repo, body);
|
|
8921
|
+
if (resource !== "media" && Object.keys(persistBody).length === 0) {
|
|
8922
|
+
logCrudClientError("POST create", {
|
|
8923
|
+
reason: "no_scalar_columns_after_pick",
|
|
8924
|
+
resource,
|
|
8925
|
+
incomingKeys: Object.keys(body)
|
|
8926
|
+
});
|
|
8927
|
+
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
8928
|
+
}
|
|
8929
|
+
sanitizeBodyForEntity(repo, persistBody);
|
|
8930
|
+
let created;
|
|
8931
|
+
try {
|
|
8932
|
+
created = await repo.save(repo.create(persistBody));
|
|
8933
|
+
} catch (err) {
|
|
8934
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8935
|
+
logCrudServerError("POST create save failed", { resource, message, persistKeys: Object.keys(persistBody) });
|
|
8936
|
+
throw err;
|
|
8937
|
+
}
|
|
8738
8938
|
if (resource === "contacts") {
|
|
8739
8939
|
await syncContactRowToErp(created);
|
|
8740
8940
|
}
|
|
@@ -8749,6 +8949,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8749
8949
|
if (authError) return authError;
|
|
8750
8950
|
const entity = entityMap[resource];
|
|
8751
8951
|
if (!resource || !entity) {
|
|
8952
|
+
logCrudClientError("GET_METADATA", { reason: "invalid_resource", resource });
|
|
8752
8953
|
return json({ error: "Invalid resource" }, { status: 400 });
|
|
8753
8954
|
}
|
|
8754
8955
|
const repo = dataSource.getRepository(entity);
|
|
@@ -8780,11 +8981,18 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8780
8981
|
if (authError) return authError;
|
|
8781
8982
|
const entity = entityMap[resource];
|
|
8782
8983
|
if (!resource || !entity) {
|
|
8984
|
+
logCrudClientError("BULK_POST", { reason: "invalid_resource", resource });
|
|
8783
8985
|
return json({ error: "Invalid resource" }, { status: 400 });
|
|
8784
8986
|
}
|
|
8785
8987
|
const body = await req.json();
|
|
8786
8988
|
const { records, upsertKey = "id" } = body;
|
|
8787
8989
|
if (!Array.isArray(records) || records.length === 0) {
|
|
8990
|
+
logCrudClientError("BULK_POST", {
|
|
8991
|
+
reason: "records_required",
|
|
8992
|
+
resource,
|
|
8993
|
+
recordsIsArray: Array.isArray(records),
|
|
8994
|
+
recordCount: Array.isArray(records) ? records.length : 0
|
|
8995
|
+
});
|
|
8788
8996
|
return json({ error: "Records array is required" }, { status: 400 });
|
|
8789
8997
|
}
|
|
8790
8998
|
const repo = dataSource.getRepository(entity);
|
|
@@ -8803,6 +9011,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8803
9011
|
});
|
|
8804
9012
|
} catch (error) {
|
|
8805
9013
|
const message = error instanceof Error ? error.message : "Bulk import failed";
|
|
9014
|
+
logCrudClientError("BULK_POST upsert failed", { resource, upsertKey, message });
|
|
8806
9015
|
return json({ error: message }, { status: 400 });
|
|
8807
9016
|
}
|
|
8808
9017
|
},
|
|
@@ -8811,6 +9020,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
8811
9020
|
if (authError) return authError;
|
|
8812
9021
|
const entity = entityMap[resource];
|
|
8813
9022
|
if (!resource || !entity) {
|
|
9023
|
+
logCrudClientError("GET_EXPORT", { reason: "invalid_resource", resource });
|
|
8814
9024
|
return json({ error: "Invalid resource" }, { status: 400 });
|
|
8815
9025
|
}
|
|
8816
9026
|
const { searchParams } = new URL(req.url);
|
|
@@ -8865,7 +9075,10 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
8865
9075
|
const authError = await authz(req, resource, "read");
|
|
8866
9076
|
if (authError) return authError;
|
|
8867
9077
|
const entity = entityMap[resource];
|
|
8868
|
-
if (!entity)
|
|
9078
|
+
if (!entity) {
|
|
9079
|
+
logCrudClientError("GET by id", { reason: "invalid_resource", resource, id });
|
|
9080
|
+
return json({ error: "Invalid resource" }, { status: 400 });
|
|
9081
|
+
}
|
|
8869
9082
|
const repo = dataSource.getRepository(entity);
|
|
8870
9083
|
if (resource === "orders") {
|
|
8871
9084
|
const order = await repo.findOne({
|
|
@@ -8924,7 +9137,10 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
8924
9137
|
const authError = await authz(req, resource, "update");
|
|
8925
9138
|
if (authError) return authError;
|
|
8926
9139
|
const entity = entityMap[resource];
|
|
8927
|
-
if (!entity)
|
|
9140
|
+
if (!entity) {
|
|
9141
|
+
logCrudClientError("PUT by id", { reason: "invalid_resource", resource, id });
|
|
9142
|
+
return json({ error: "Invalid resource" }, { status: 400 });
|
|
9143
|
+
}
|
|
8928
9144
|
const rawBody = await req.json();
|
|
8929
9145
|
const repo = dataSource.getRepository(entity);
|
|
8930
9146
|
const numericId = Number(id);
|
|
@@ -9037,7 +9253,10 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
9037
9253
|
const authError = await authz(req, resource, "delete");
|
|
9038
9254
|
if (authError) return authError;
|
|
9039
9255
|
const entity = entityMap[resource];
|
|
9040
|
-
if (!entity)
|
|
9256
|
+
if (!entity) {
|
|
9257
|
+
logCrudClientError("DELETE by id", { reason: "invalid_resource", resource, id });
|
|
9258
|
+
return json({ error: "Invalid resource" }, { status: 400 });
|
|
9259
|
+
}
|
|
9041
9260
|
const repo = dataSource.getRepository(entity);
|
|
9042
9261
|
const numericId = Number(id);
|
|
9043
9262
|
if (entityHasSoftDelete(repo)) {
|
|
@@ -9207,6 +9426,9 @@ var EMBED_CONCURRENCY = 5;
|
|
|
9207
9426
|
var MAX_PDF_BYTES = 25 * 1024 * 1024;
|
|
9208
9427
|
var TEXT_FILE_TYPES = /* @__PURE__ */ new Set(["text/plain", "text/markdown", "application/json"]);
|
|
9209
9428
|
var KB_LOG = "[llm-agent-knowledge]";
|
|
9429
|
+
function logKbPipeline(step, meta) {
|
|
9430
|
+
console.info(`${KB_LOG} pipeline`, { step, ...meta });
|
|
9431
|
+
}
|
|
9210
9432
|
function llmEmbedDebug() {
|
|
9211
9433
|
const v = process.env.LLM_EMBED_DEBUG?.toLowerCase();
|
|
9212
9434
|
return v === "1" || v === "true" || v === "yes";
|
|
@@ -9221,11 +9443,29 @@ async function extractTextFromPdf(buffer) {
|
|
|
9221
9443
|
const data = await pdfParse(buffer);
|
|
9222
9444
|
return (data?.text ?? "").trim();
|
|
9223
9445
|
}
|
|
9224
|
-
async function
|
|
9446
|
+
async function loadChunksMissingEmbeddings(dataSource, documentId, slug) {
|
|
9447
|
+
const rows = await dataSource.query(
|
|
9448
|
+
`SELECT id, content FROM knowledge_base_chunks WHERE "documentId" = $1 AND embedding IS NULL ORDER BY "chunkIndex" ASC`,
|
|
9449
|
+
[documentId]
|
|
9450
|
+
);
|
|
9451
|
+
const list = rows ?? [];
|
|
9452
|
+
logKbPipeline("07_query_chunks_missing_embedding", {
|
|
9453
|
+
slug,
|
|
9454
|
+
documentId,
|
|
9455
|
+
missingEmbeddingCount: list.length
|
|
9456
|
+
});
|
|
9457
|
+
return list;
|
|
9458
|
+
}
|
|
9459
|
+
async function writeEmbeddingsConcurrent(dataSource, embed, chunks, concurrency, slug) {
|
|
9225
9460
|
let next = 0;
|
|
9226
9461
|
let written = 0;
|
|
9227
9462
|
let failed = 0;
|
|
9228
9463
|
let skippedEmpty = 0;
|
|
9464
|
+
logKbPipeline("09_embedding_workers_start", {
|
|
9465
|
+
slug,
|
|
9466
|
+
chunkCount: chunks.length,
|
|
9467
|
+
concurrency: Math.max(1, Math.min(concurrency, chunks.length))
|
|
9468
|
+
});
|
|
9229
9469
|
async function worker() {
|
|
9230
9470
|
for (; ; ) {
|
|
9231
9471
|
const i = next++;
|
|
@@ -9264,9 +9504,12 @@ async function writeEmbeddingsConcurrent(dataSource, embed, chunks, concurrency)
|
|
|
9264
9504
|
skippedEmpty
|
|
9265
9505
|
};
|
|
9266
9506
|
if (skippedEmpty > 0 && written === 0) {
|
|
9267
|
-
summary.hint = "embed() returned empty vectors \u2014 see [LLM embed] logs (
|
|
9507
|
+
summary.hint = "embed() returned empty vectors \u2014 see [LLM embed] / [LLM embed HF] logs (OpenAI gateway vs @huggingface/inference + legacy HTTP).";
|
|
9508
|
+
}
|
|
9509
|
+
logKbPipeline("10_embedding_workers_finished", { slug, ...summary });
|
|
9510
|
+
if (failed > 0 || skippedEmpty > 0 && written === 0) {
|
|
9511
|
+
console.error(`${KB_LOG} embedding pass finished with issues`, summary);
|
|
9268
9512
|
}
|
|
9269
|
-
console.error(`${KB_LOG} embedding pass finished`, summary);
|
|
9270
9513
|
return { written, failed };
|
|
9271
9514
|
}
|
|
9272
9515
|
function splitIntoChunks(text, maxLen) {
|
|
@@ -9302,6 +9545,58 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9302
9545
|
}
|
|
9303
9546
|
return null;
|
|
9304
9547
|
}
|
|
9548
|
+
async function runEmbeddingPass(slug, savedChunks) {
|
|
9549
|
+
let embeddingsWritten = 0;
|
|
9550
|
+
let embeddingsFailed = 0;
|
|
9551
|
+
try {
|
|
9552
|
+
logKbPipeline("08_resolve_llm_plugin", { slug, chunkCount: savedChunks.length });
|
|
9553
|
+
const cms = await getCms();
|
|
9554
|
+
const llm = cms.getPlugin("llm");
|
|
9555
|
+
if (llm?.embed && savedChunks.length > 0) {
|
|
9556
|
+
logKbPipeline("08b_embed_fn_available", {
|
|
9557
|
+
slug,
|
|
9558
|
+
chunkCount: savedChunks.length,
|
|
9559
|
+
firstChunkId: savedChunks[0]?.id,
|
|
9560
|
+
lastChunkId: savedChunks[savedChunks.length - 1]?.id
|
|
9561
|
+
});
|
|
9562
|
+
const embedBound = (text) => llm.embed(text);
|
|
9563
|
+
const { written, failed } = await writeEmbeddingsConcurrent(
|
|
9564
|
+
dataSource,
|
|
9565
|
+
embedBound,
|
|
9566
|
+
savedChunks,
|
|
9567
|
+
EMBED_CONCURRENCY,
|
|
9568
|
+
slug
|
|
9569
|
+
);
|
|
9570
|
+
embeddingsWritten = written;
|
|
9571
|
+
embeddingsFailed = failed;
|
|
9572
|
+
} else {
|
|
9573
|
+
logKbPipeline("08c_embed_skipped", {
|
|
9574
|
+
slug,
|
|
9575
|
+
hasLlmPlugin: !!llm,
|
|
9576
|
+
hasEmbed: typeof llm?.embed === "function",
|
|
9577
|
+
chunkCount: savedChunks.length,
|
|
9578
|
+
reason: savedChunks.length === 0 ? "no_chunks" : !llm ? "no_llm_plugin" : "no_embed_method"
|
|
9579
|
+
});
|
|
9580
|
+
console.error(`${KB_LOG} embeddings skipped`, {
|
|
9581
|
+
slug,
|
|
9582
|
+
hasLlmPlugin: !!llm,
|
|
9583
|
+
hasEmbed: typeof llm?.embed === "function",
|
|
9584
|
+
chunkCount: savedChunks.length,
|
|
9585
|
+
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
|
|
9586
|
+
});
|
|
9587
|
+
}
|
|
9588
|
+
} catch (embErr) {
|
|
9589
|
+
const detail = embErr instanceof Error ? embErr.message : String(embErr);
|
|
9590
|
+
logKbPipeline("08d_embed_pass_exception", { slug, detail });
|
|
9591
|
+
console.error(`${KB_LOG} embedding step threw before/during batch`, { slug, detail, embErr });
|
|
9592
|
+
return {
|
|
9593
|
+
embeddingsWritten: 0,
|
|
9594
|
+
embeddingsFailed: savedChunks.length,
|
|
9595
|
+
embedError: detail
|
|
9596
|
+
};
|
|
9597
|
+
}
|
|
9598
|
+
return { embeddingsWritten, embeddingsFailed };
|
|
9599
|
+
}
|
|
9305
9600
|
return {
|
|
9306
9601
|
async list(req, slug) {
|
|
9307
9602
|
const denied = await gate(req, "read");
|
|
@@ -9330,8 +9625,11 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9330
9625
|
const denied = await gate(req, "update");
|
|
9331
9626
|
if (denied) return denied;
|
|
9332
9627
|
try {
|
|
9628
|
+
const ct0 = req.headers.get("content-type") || "";
|
|
9629
|
+
logKbPipeline("01_request", { slug, contentType: ct0.slice(0, 80) });
|
|
9333
9630
|
const agent = await findAgentBySlug(dataSource, llmAgents, slug);
|
|
9334
9631
|
if (!agent) return json({ error: "Agent not found" }, { status: 404 });
|
|
9632
|
+
logKbPipeline("02_agent_loaded", { slug, agentId: agent.id });
|
|
9335
9633
|
let name = "";
|
|
9336
9634
|
let text = "";
|
|
9337
9635
|
let sourceUrl = null;
|
|
@@ -9343,6 +9641,14 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9343
9641
|
name = (body?.name ?? "").trim();
|
|
9344
9642
|
text = (body?.text ?? "").trim();
|
|
9345
9643
|
sourceUrl = typeof body?.sourceUrl === "string" && body.sourceUrl.trim() ? body.sourceUrl.trim() : null;
|
|
9644
|
+
logKbPipeline("03_body_parsed", {
|
|
9645
|
+
slug,
|
|
9646
|
+
mode: "json",
|
|
9647
|
+
existingDocumentId,
|
|
9648
|
+
nameLen: name.length,
|
|
9649
|
+
textChars: text.length,
|
|
9650
|
+
hasSourceUrl: !!sourceUrl
|
|
9651
|
+
});
|
|
9346
9652
|
} else if (ct.includes("multipart/form-data")) {
|
|
9347
9653
|
const form = await req.formData();
|
|
9348
9654
|
name = form.get("name")?.trim() ?? "";
|
|
@@ -9352,9 +9658,17 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9352
9658
|
const f = file;
|
|
9353
9659
|
const mime = (f.type || "").split(";")[0].trim().toLowerCase();
|
|
9354
9660
|
const buf = Buffer.from(await f.arrayBuffer());
|
|
9661
|
+
logKbPipeline("03_body_parsed", {
|
|
9662
|
+
slug,
|
|
9663
|
+
mode: "multipart",
|
|
9664
|
+
fileName: f.name || "(no name)",
|
|
9665
|
+
mime,
|
|
9666
|
+
fileBytes: buf.length
|
|
9667
|
+
});
|
|
9355
9668
|
if (TEXT_FILE_TYPES.has(mime)) {
|
|
9356
9669
|
const decoded = buf.toString("utf8");
|
|
9357
9670
|
if (!text) text = decoded;
|
|
9671
|
+
logKbPipeline("04_file_decoded_text", { slug, mime, textChars: text.length });
|
|
9358
9672
|
} else if (isPdfUpload(mime, f.name || "")) {
|
|
9359
9673
|
if (buf.length > MAX_PDF_BYTES) {
|
|
9360
9674
|
return json(
|
|
@@ -9363,8 +9677,10 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9363
9677
|
);
|
|
9364
9678
|
}
|
|
9365
9679
|
try {
|
|
9680
|
+
logKbPipeline("04_pdf_extract_start", { slug, fileBytes: buf.length });
|
|
9366
9681
|
const extracted = await extractTextFromPdf(buf);
|
|
9367
9682
|
if (!text) text = extracted;
|
|
9683
|
+
logKbPipeline("04_pdf_extract_done", { slug, textChars: text.length });
|
|
9368
9684
|
} catch {
|
|
9369
9685
|
return json(
|
|
9370
9686
|
{ error: "Could not read PDF text (file may be encrypted, corrupt, or image-only)" },
|
|
@@ -9380,12 +9696,15 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9380
9696
|
);
|
|
9381
9697
|
}
|
|
9382
9698
|
if (!name && f.name) name = f.name.replace(/\.[^/.]+$/, "") || f.name;
|
|
9699
|
+
} else {
|
|
9700
|
+
logKbPipeline("03_body_parsed", { slug, mode: "multipart", hasFile: false, textChars: text.length });
|
|
9383
9701
|
}
|
|
9384
9702
|
} else {
|
|
9385
9703
|
return json({ error: "Use application/json or multipart/form-data" }, { status: 400 });
|
|
9386
9704
|
}
|
|
9387
9705
|
const linkRepo = dataSource.getRepository(junction);
|
|
9388
9706
|
if (existingDocumentId != null) {
|
|
9707
|
+
logKbPipeline("05_branch", { slug, branch: "link_existing_document", documentId: existingDocumentId });
|
|
9389
9708
|
const docRepo2 = dataSource.getRepository(kbDoc);
|
|
9390
9709
|
const existing = await docRepo2.findOne({ where: { id: existingDocumentId } });
|
|
9391
9710
|
if (!existing) return json({ error: "documentId not found" }, { status: 404 });
|
|
@@ -9394,9 +9713,137 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9394
9713
|
});
|
|
9395
9714
|
if (!dup2) {
|
|
9396
9715
|
await linkRepo.save(linkRepo.create({ agentId: agent.id, documentId: existingDocumentId }));
|
|
9716
|
+
logKbPipeline("06_junction_link_created", {
|
|
9717
|
+
slug,
|
|
9718
|
+
agentId: agent.id,
|
|
9719
|
+
documentId: existingDocumentId
|
|
9720
|
+
});
|
|
9721
|
+
} else {
|
|
9722
|
+
logKbPipeline("06_junction_link_exists", {
|
|
9723
|
+
slug,
|
|
9724
|
+
agentId: agent.id,
|
|
9725
|
+
documentId: existingDocumentId
|
|
9726
|
+
});
|
|
9397
9727
|
}
|
|
9398
|
-
|
|
9728
|
+
const chunkRepo2 = dataSource.getRepository(kbChunk);
|
|
9729
|
+
const docRow = existing;
|
|
9730
|
+
const chunkCount = await chunkRepo2.count({ where: { documentId: existingDocumentId } });
|
|
9731
|
+
logKbPipeline("06b_chunks_existing_count", {
|
|
9732
|
+
slug,
|
|
9733
|
+
documentId: existingDocumentId,
|
|
9734
|
+
chunkCount
|
|
9735
|
+
});
|
|
9736
|
+
let chunksToEmbed = [];
|
|
9737
|
+
let chunksCreated = 0;
|
|
9738
|
+
if (chunkCount === 0) {
|
|
9739
|
+
const text2 = (docRow.content ?? "").trim();
|
|
9740
|
+
if (!text2) {
|
|
9741
|
+
logKbPipeline("07_ingest_aborted_empty_document", { slug, documentId: existingDocumentId });
|
|
9742
|
+
return json({
|
|
9743
|
+
documentId: existingDocumentId,
|
|
9744
|
+
linked: true,
|
|
9745
|
+
created: false,
|
|
9746
|
+
chunkCount: 0,
|
|
9747
|
+
embeddingsWritten: 0,
|
|
9748
|
+
embeddingsFailed: 0,
|
|
9749
|
+
warning: "Document has no text content; add content then attach again to build chunks and embeddings."
|
|
9750
|
+
});
|
|
9751
|
+
}
|
|
9752
|
+
const parts2 = splitIntoChunks(text2, INGEST_CHUNK_CHARS);
|
|
9753
|
+
if (parts2.length > MAX_CHUNKS_PER_UPLOAD) {
|
|
9754
|
+
return json(
|
|
9755
|
+
{
|
|
9756
|
+
error: `Document is too large for one ingest (${parts2.length} chunks; max ${MAX_CHUNKS_PER_UPLOAD}). Split into smaller files.`
|
|
9757
|
+
},
|
|
9758
|
+
{ status: 413 }
|
|
9759
|
+
);
|
|
9760
|
+
}
|
|
9761
|
+
logKbPipeline("07_chunk_split", {
|
|
9762
|
+
slug,
|
|
9763
|
+
documentId: existingDocumentId,
|
|
9764
|
+
partCount: parts2.length,
|
|
9765
|
+
maxChunkChars: INGEST_CHUNK_CHARS,
|
|
9766
|
+
totalChars: text2.length
|
|
9767
|
+
});
|
|
9768
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
9769
|
+
const chunkRows2 = parts2.map(
|
|
9770
|
+
(content, i) => chunkRepo2.create({
|
|
9771
|
+
documentId: existingDocumentId,
|
|
9772
|
+
content,
|
|
9773
|
+
chunkIndex: i,
|
|
9774
|
+
createdAt: now2
|
|
9775
|
+
})
|
|
9776
|
+
);
|
|
9777
|
+
const savedList2 = await chunkRepo2.save(chunkRows2);
|
|
9778
|
+
chunksCreated = savedList2.length;
|
|
9779
|
+
chunksToEmbed = savedList2.map((row, i) => ({
|
|
9780
|
+
id: row.id,
|
|
9781
|
+
content: parts2[i]
|
|
9782
|
+
}));
|
|
9783
|
+
logKbPipeline("07b_chunks_persisted", {
|
|
9784
|
+
slug,
|
|
9785
|
+
documentId: existingDocumentId,
|
|
9786
|
+
rowsInserted: chunksCreated
|
|
9787
|
+
});
|
|
9788
|
+
} else {
|
|
9789
|
+
chunksToEmbed = await loadChunksMissingEmbeddings(dataSource, existingDocumentId, slug);
|
|
9790
|
+
if (chunksToEmbed.length === 0) {
|
|
9791
|
+
logKbPipeline("08_skip_embed_all_chunks_have_vectors", {
|
|
9792
|
+
slug,
|
|
9793
|
+
documentId: existingDocumentId,
|
|
9794
|
+
existingChunkCount: chunkCount
|
|
9795
|
+
});
|
|
9796
|
+
}
|
|
9797
|
+
}
|
|
9798
|
+
const embedResult2 = chunksToEmbed.length > 0 ? await runEmbeddingPass(slug, chunksToEmbed) : { embeddingsWritten: 0, embeddingsFailed: 0 };
|
|
9799
|
+
if (embedResult2.embedError) {
|
|
9800
|
+
logKbPipeline("11_response", {
|
|
9801
|
+
slug,
|
|
9802
|
+
branch: "link",
|
|
9803
|
+
documentId: existingDocumentId,
|
|
9804
|
+
ok: false,
|
|
9805
|
+
embeddingError: true
|
|
9806
|
+
});
|
|
9807
|
+
return json(
|
|
9808
|
+
{
|
|
9809
|
+
documentId: existingDocumentId,
|
|
9810
|
+
linked: true,
|
|
9811
|
+
created: false,
|
|
9812
|
+
chunkCount: chunkCount + chunksCreated,
|
|
9813
|
+
chunksCreated,
|
|
9814
|
+
embeddingAttempted: true,
|
|
9815
|
+
embeddingsWritten: 0,
|
|
9816
|
+
embeddingsFailed: chunksToEmbed.length,
|
|
9817
|
+
warning: "Document linked; embedding step failed. Fix LLM/embed config and attach again (or unlink and re-attach) to retry NULL embeddings.",
|
|
9818
|
+
detail: embedResult2.embedError
|
|
9819
|
+
},
|
|
9820
|
+
{ status: 201 }
|
|
9821
|
+
);
|
|
9822
|
+
}
|
|
9823
|
+
logKbPipeline("11_response", {
|
|
9824
|
+
slug,
|
|
9825
|
+
branch: "link",
|
|
9826
|
+
documentId: existingDocumentId,
|
|
9827
|
+
ok: true,
|
|
9828
|
+
chunkCount: chunkCount + chunksCreated,
|
|
9829
|
+
chunksCreated,
|
|
9830
|
+
chunksQueuedForEmbedding: chunksToEmbed.length,
|
|
9831
|
+
embeddingsWritten: embedResult2.embeddingsWritten,
|
|
9832
|
+
embeddingsFailed: embedResult2.embeddingsFailed
|
|
9833
|
+
});
|
|
9834
|
+
return json({
|
|
9835
|
+
documentId: existingDocumentId,
|
|
9836
|
+
linked: true,
|
|
9837
|
+
created: false,
|
|
9838
|
+
chunkCount: chunkCount + chunksCreated,
|
|
9839
|
+
chunksCreated: chunksCreated || void 0,
|
|
9840
|
+
chunksQueuedForEmbedding: chunksToEmbed.length,
|
|
9841
|
+
embeddingAttempted: chunksToEmbed.length > 0,
|
|
9842
|
+
embeddingsWritten: embedResult2.embeddingsWritten,
|
|
9843
|
+
embeddingsFailed: embedResult2.embeddingsFailed
|
|
9844
|
+
});
|
|
9399
9845
|
}
|
|
9846
|
+
logKbPipeline("05_branch", { slug, branch: "new_upload_ingest" });
|
|
9400
9847
|
if (!text) return json({ error: "text or file with text content is required" }, { status: 400 });
|
|
9401
9848
|
if (!name) name = "Untitled";
|
|
9402
9849
|
const parts = splitIntoChunks(text, INGEST_CHUNK_CHARS);
|
|
@@ -9411,6 +9858,13 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9411
9858
|
{ status: 413 }
|
|
9412
9859
|
);
|
|
9413
9860
|
}
|
|
9861
|
+
logKbPipeline("07_chunk_split", {
|
|
9862
|
+
slug,
|
|
9863
|
+
partCount: parts.length,
|
|
9864
|
+
maxChunkChars: INGEST_CHUNK_CHARS,
|
|
9865
|
+
totalChars: text.length,
|
|
9866
|
+
documentName: name
|
|
9867
|
+
});
|
|
9414
9868
|
const docRepo = dataSource.getRepository(kbDoc);
|
|
9415
9869
|
const chunkRepo = dataSource.getRepository(kbChunk);
|
|
9416
9870
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -9418,6 +9872,13 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9418
9872
|
docRepo.create({ name, content: text, sourceUrl, createdAt: now, updatedAt: now })
|
|
9419
9873
|
);
|
|
9420
9874
|
const docId = doc.id;
|
|
9875
|
+
logKbPipeline("06_knowledge_document_saved", {
|
|
9876
|
+
slug,
|
|
9877
|
+
documentId: docId,
|
|
9878
|
+
name,
|
|
9879
|
+
contentChars: text.length,
|
|
9880
|
+
hasSourceUrl: !!sourceUrl
|
|
9881
|
+
});
|
|
9421
9882
|
const chunkRows = parts.map(
|
|
9422
9883
|
(content, i) => chunkRepo.create({ documentId: docId, content, chunkIndex: i, createdAt: now })
|
|
9423
9884
|
);
|
|
@@ -9428,63 +9889,66 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9428
9889
|
content: parts[i]
|
|
9429
9890
|
})
|
|
9430
9891
|
);
|
|
9892
|
+
logKbPipeline("07b_chunks_persisted", {
|
|
9893
|
+
slug,
|
|
9894
|
+
documentId: docId,
|
|
9895
|
+
rowsInserted: savedChunks.length,
|
|
9896
|
+
firstChunkId: savedChunks[0]?.id,
|
|
9897
|
+
lastChunkId: savedChunks[savedChunks.length - 1]?.id
|
|
9898
|
+
});
|
|
9431
9899
|
const dup = await linkRepo.findOne({ where: { agentId: agent.id, documentId: docId } });
|
|
9432
9900
|
if (!dup) {
|
|
9433
9901
|
await linkRepo.save(linkRepo.create({ agentId: agent.id, documentId: docId }));
|
|
9902
|
+
logKbPipeline("06c_junction_link_created", { slug, agentId: agent.id, documentId: docId });
|
|
9903
|
+
} else {
|
|
9904
|
+
logKbPipeline("06c_junction_link_exists", { slug, agentId: agent.id, documentId: docId });
|
|
9434
9905
|
}
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
dataSource,
|
|
9445
|
-
embedBound,
|
|
9446
|
-
savedChunks,
|
|
9447
|
-
EMBED_CONCURRENCY
|
|
9448
|
-
);
|
|
9449
|
-
embeddingsWritten = written;
|
|
9450
|
-
embeddingsFailed = failed;
|
|
9451
|
-
} else {
|
|
9452
|
-
console.error(`${KB_LOG} embeddings skipped`, {
|
|
9453
|
-
slug,
|
|
9454
|
-
hasLlmPlugin: !!llm,
|
|
9455
|
-
hasEmbed: typeof llm?.embed === "function",
|
|
9456
|
-
chunkCount: savedChunks.length,
|
|
9457
|
-
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
|
|
9458
|
-
});
|
|
9459
|
-
}
|
|
9460
|
-
} catch (embErr) {
|
|
9461
|
-
const detail = embErr instanceof Error ? embErr.message : String(embErr);
|
|
9462
|
-
console.error(`${KB_LOG} embedding step threw before/during batch`, { slug, detail, embErr });
|
|
9906
|
+
const embedResult = await runEmbeddingPass(slug, savedChunks);
|
|
9907
|
+
if (embedResult.embedError) {
|
|
9908
|
+
logKbPipeline("11_response", {
|
|
9909
|
+
slug,
|
|
9910
|
+
branch: "ingest",
|
|
9911
|
+
documentId: docId,
|
|
9912
|
+
ok: false,
|
|
9913
|
+
embeddingError: true
|
|
9914
|
+
});
|
|
9463
9915
|
return json(
|
|
9464
9916
|
{
|
|
9465
9917
|
documentId: docId,
|
|
9466
9918
|
chunkCount: savedChunks.length,
|
|
9919
|
+
embeddingAttempted: true,
|
|
9467
9920
|
created: true,
|
|
9468
9921
|
linked: true,
|
|
9469
9922
|
embeddingsWritten: 0,
|
|
9470
9923
|
embeddingsFailed: savedChunks.length,
|
|
9471
9924
|
warning: "Document saved and linked; embedding step failed.",
|
|
9472
|
-
detail
|
|
9925
|
+
detail: embedResult.embedError
|
|
9473
9926
|
},
|
|
9474
9927
|
{ status: 201 }
|
|
9475
9928
|
);
|
|
9476
9929
|
}
|
|
9930
|
+
logKbPipeline("11_response", {
|
|
9931
|
+
slug,
|
|
9932
|
+
branch: "ingest",
|
|
9933
|
+
documentId: docId,
|
|
9934
|
+
ok: true,
|
|
9935
|
+
chunkCount: savedChunks.length,
|
|
9936
|
+
embeddingsWritten: embedResult.embeddingsWritten,
|
|
9937
|
+
embeddingsFailed: embedResult.embeddingsFailed
|
|
9938
|
+
});
|
|
9477
9939
|
return json({
|
|
9478
9940
|
documentId: docId,
|
|
9479
9941
|
chunkCount: savedChunks.length,
|
|
9942
|
+
embeddingAttempted: savedChunks.length > 0,
|
|
9480
9943
|
created: true,
|
|
9481
9944
|
linked: true,
|
|
9482
|
-
embeddingsWritten,
|
|
9483
|
-
embeddingsFailed
|
|
9945
|
+
embeddingsWritten: embedResult.embeddingsWritten,
|
|
9946
|
+
embeddingsFailed: embedResult.embeddingsFailed
|
|
9484
9947
|
});
|
|
9485
9948
|
} catch (err) {
|
|
9486
9949
|
const msg = err instanceof Error ? err.message : "Failed to ingest knowledge";
|
|
9487
9950
|
const name = err instanceof Error ? err.name : "";
|
|
9951
|
+
logKbPipeline("99_pipeline_error", { slug, errorName: name, message: msg });
|
|
9488
9952
|
return json({ error: msg, errorName: name || void 0 }, { status: 500 });
|
|
9489
9953
|
}
|
|
9490
9954
|
},
|
|
@@ -9496,6 +9960,7 @@ function createLlmAgentKnowledgeHandlers(config) {
|
|
|
9496
9960
|
try {
|
|
9497
9961
|
const agent = await findAgentBySlug(dataSource, llmAgents, slug);
|
|
9498
9962
|
if (!agent) return json({ error: "Agent not found" }, { status: 404 });
|
|
9963
|
+
logKbPipeline("unlink_junction", { slug, agentId: agent.id, documentId });
|
|
9499
9964
|
const linkRepo = dataSource.getRepository(junction);
|
|
9500
9965
|
await linkRepo.delete({ agentId: agent.id, documentId });
|
|
9501
9966
|
return json({ ok: true });
|
|
@@ -9757,6 +10222,28 @@ function createAdminRolesHandlers(config) {
|
|
|
9757
10222
|
|
|
9758
10223
|
// src/api/cms-api-handler.ts
|
|
9759
10224
|
var KNOWLEDGE_SUFFIX = "knowledge";
|
|
10225
|
+
var CMS_API_LOG = "[cms-api]";
|
|
10226
|
+
function withLlmKnowledgeEntityFallbacks(base) {
|
|
10227
|
+
const keys = [
|
|
10228
|
+
"llm_agents",
|
|
10229
|
+
"llm_agent_knowledge_documents",
|
|
10230
|
+
"knowledge_base_documents",
|
|
10231
|
+
"knowledge_base_chunks"
|
|
10232
|
+
];
|
|
10233
|
+
const patch = {};
|
|
10234
|
+
for (const key of keys) {
|
|
10235
|
+
if (!(key in base) || base[key] == null) {
|
|
10236
|
+
const fallback = CMS_ENTITY_MAP[key];
|
|
10237
|
+
if (fallback) patch[key] = fallback;
|
|
10238
|
+
}
|
|
10239
|
+
}
|
|
10240
|
+
const merged = Object.keys(patch).length > 0 ? { ...base, ...patch } : base;
|
|
10241
|
+
if (!merged.llm_agents) {
|
|
10242
|
+
const agent = CMS_ENTITY_MAP.llm_agents ?? LlmAgent;
|
|
10243
|
+
return { ...merged, llm_agents: agent };
|
|
10244
|
+
}
|
|
10245
|
+
return merged;
|
|
10246
|
+
}
|
|
9760
10247
|
function matchLlmAgentKnowledgeRoute(path) {
|
|
9761
10248
|
const p = path[0] === "api" ? path.slice(1) : path;
|
|
9762
10249
|
if (p[0] !== "llm_agents" || p.length < 2) return null;
|
|
@@ -9794,9 +10281,9 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
9794
10281
|
function createCmsApiHandler(config) {
|
|
9795
10282
|
const {
|
|
9796
10283
|
dataSource,
|
|
9797
|
-
entityMap,
|
|
10284
|
+
entityMap: rawEntityMap,
|
|
9798
10285
|
pathToModel = (s) => s,
|
|
9799
|
-
crudResources
|
|
10286
|
+
crudResources: crudResourcesOverride,
|
|
9800
10287
|
getCms,
|
|
9801
10288
|
userAuth: userAuthConfig,
|
|
9802
10289
|
dashboard,
|
|
@@ -9817,6 +10304,9 @@ function createCmsApiHandler(config) {
|
|
|
9817
10304
|
requireEntityPermission: userRequireEntityPermission,
|
|
9818
10305
|
getSessionUser
|
|
9819
10306
|
} = config;
|
|
10307
|
+
const entityMap = withLlmKnowledgeEntityFallbacks(rawEntityMap);
|
|
10308
|
+
const baseCrudResources = crudResourcesOverride ?? Object.keys(entityMap).filter((k) => !DEFAULT_EXCLUDE.has(k));
|
|
10309
|
+
const crudResources = entityMap.llm_agents && !baseCrudResources.includes("llm_agents") ? [...baseCrudResources, "llm_agents"] : baseCrudResources;
|
|
9820
10310
|
const requireEntityPermissionEffective = userRequireEntityPermission ?? (async (_req, entity, action) => config.json({ error: "Forbidden", reason: "entity_rbac_required", entity, action }, { status: 403 }));
|
|
9821
10311
|
const analytics = analyticsConfig ?? (getCms ? {
|
|
9822
10312
|
json: config.json,
|
|
@@ -9923,7 +10413,7 @@ function createCmsApiHandler(config) {
|
|
|
9923
10413
|
const chatHandlers = chatConfig ? createChatHandlers(chatConfig) : null;
|
|
9924
10414
|
const llmAgentKnowledgeMerged = llmAgentKnowledgeConfig ?? (chatConfig ? {
|
|
9925
10415
|
dataSource: chatConfig.dataSource,
|
|
9926
|
-
entityMap: chatConfig.entityMap,
|
|
10416
|
+
entityMap: withLlmKnowledgeEntityFallbacks(chatConfig.entityMap ?? rawEntityMap),
|
|
9927
10417
|
getCms: chatConfig.getCms,
|
|
9928
10418
|
json: chatConfig.json,
|
|
9929
10419
|
requireAuth: chatConfig.requireAuth
|
|
@@ -9937,7 +10427,9 @@ function createCmsApiHandler(config) {
|
|
|
9937
10427
|
return crudResources.includes(model) ? model : segment;
|
|
9938
10428
|
}
|
|
9939
10429
|
return {
|
|
9940
|
-
async handle(method,
|
|
10430
|
+
async handle(method, pathInput, req) {
|
|
10431
|
+
const m = typeof method === "string" ? method.toUpperCase() : "GET";
|
|
10432
|
+
const path = pathInput.length > 0 && pathInput[0] === "api" ? pathInput.slice(1) : pathInput;
|
|
9941
10433
|
async function analyticsGate() {
|
|
9942
10434
|
const a = await config.requireAuth(req);
|
|
9943
10435
|
if (a) return a;
|
|
@@ -9945,86 +10437,86 @@ function createCmsApiHandler(config) {
|
|
|
9945
10437
|
}
|
|
9946
10438
|
if (path[0] === "admin" && path[1] === "roles") {
|
|
9947
10439
|
if (!adminRoles) return config.json({ error: "Not found" }, { status: 404 });
|
|
9948
|
-
if (path.length === 2 &&
|
|
9949
|
-
if (path.length === 2 &&
|
|
9950
|
-
if (path.length === 3 &&
|
|
9951
|
-
if (path.length === 3 &&
|
|
9952
|
-
if (path.length === 4 && path[3] === "permissions" &&
|
|
10440
|
+
if (path.length === 2 && m === "GET") return adminRoles.list();
|
|
10441
|
+
if (path.length === 2 && m === "POST") return adminRoles.createGroup(req);
|
|
10442
|
+
if (path.length === 3 && m === "PATCH") return adminRoles.patchGroup(req, path[2]);
|
|
10443
|
+
if (path.length === 3 && m === "DELETE") return adminRoles.deleteGroup(path[2]);
|
|
10444
|
+
if (path.length === 4 && path[3] === "permissions" && m === "PUT") return adminRoles.putPermissions(req, path[2]);
|
|
9953
10445
|
return config.json({ error: "Not found" }, { status: 404 });
|
|
9954
10446
|
}
|
|
9955
|
-
if (path[0] === "dashboard" && path[1] === "stats" && path.length === 2 &&
|
|
10447
|
+
if (path[0] === "dashboard" && path[1] === "stats" && path.length === 2 && m === "GET" && dashboardGet) {
|
|
9956
10448
|
return dashboardGet(req);
|
|
9957
10449
|
}
|
|
9958
|
-
if (path[0] === "dashboard" && path[1] === "ecommerce" && path.length === 2 &&
|
|
10450
|
+
if (path[0] === "dashboard" && path[1] === "ecommerce" && path.length === 2 && m === "GET" && ecommerceAnalyticsGet) {
|
|
9959
10451
|
const g = await analyticsGate();
|
|
9960
10452
|
if (g) return g;
|
|
9961
10453
|
return ecommerceAnalyticsGet(req);
|
|
9962
10454
|
}
|
|
9963
10455
|
if (path[0] === "analytics" && analyticsHandlers) {
|
|
9964
|
-
if (path.length === 1 &&
|
|
10456
|
+
if (path.length === 1 && m === "GET") {
|
|
9965
10457
|
const g = await analyticsGate();
|
|
9966
10458
|
if (g) return g;
|
|
9967
10459
|
return analyticsHandlers.GET(req);
|
|
9968
10460
|
}
|
|
9969
|
-
if (path.length === 2 && path[1] === "property-id" &&
|
|
10461
|
+
if (path.length === 2 && path[1] === "property-id" && m === "GET") {
|
|
9970
10462
|
const g = await analyticsGate();
|
|
9971
10463
|
if (g) return g;
|
|
9972
10464
|
return analyticsHandlers.propertyId();
|
|
9973
10465
|
}
|
|
9974
|
-
if (path.length === 2 && path[1] === "permissions" &&
|
|
10466
|
+
if (path.length === 2 && path[1] === "permissions" && m === "GET") {
|
|
9975
10467
|
const g = await analyticsGate();
|
|
9976
10468
|
if (g) return g;
|
|
9977
10469
|
return analyticsHandlers.permissions();
|
|
9978
10470
|
}
|
|
9979
10471
|
}
|
|
9980
|
-
if (path[0] === "upload" && path.length === 1 &&
|
|
9981
|
-
if (path[0] === "media" && path[1] === "extract" && path.length === 3 &&
|
|
10472
|
+
if (path[0] === "upload" && path.length === 1 && m === "POST" && uploadPost) return uploadPost(req);
|
|
10473
|
+
if (path[0] === "media" && path[1] === "extract" && path.length === 3 && m === "POST" && zipExtractPost) {
|
|
9982
10474
|
return zipExtractPost(req, path[2]);
|
|
9983
10475
|
}
|
|
9984
|
-
if (path[0] === "blogs" && path[1] === "slug" && path.length === 3 &&
|
|
10476
|
+
if (path[0] === "blogs" && path[1] === "slug" && path.length === 3 && m === "GET" && blogBySlugGet) {
|
|
9985
10477
|
return blogBySlugGet(req, path[2]);
|
|
9986
10478
|
}
|
|
9987
|
-
if (path[0] === "forms" && path[1] === "slug" && path.length === 3 &&
|
|
10479
|
+
if (path[0] === "forms" && path[1] === "slug" && path.length === 3 && m === "GET" && formBySlugGet) {
|
|
9988
10480
|
return formBySlugGet(req, path[2]);
|
|
9989
10481
|
}
|
|
9990
10482
|
if (path[0] === "form-submissions") {
|
|
9991
10483
|
if (path.length === 1) {
|
|
9992
|
-
if (
|
|
9993
|
-
if (
|
|
10484
|
+
if (m === "GET" && formSubmissionList) return formSubmissionList(req);
|
|
10485
|
+
if (m === "POST" && formSubmissionPost) return formSubmissionPost(req);
|
|
9994
10486
|
}
|
|
9995
|
-
if (path.length === 2 &&
|
|
10487
|
+
if (path.length === 2 && m === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path[1]);
|
|
9996
10488
|
}
|
|
9997
10489
|
if (path[0] === "forms" && formSaveHandlers) {
|
|
9998
|
-
if (path.length === 1 &&
|
|
10490
|
+
if (path.length === 1 && m === "POST") return formSaveHandlers.POST(req);
|
|
9999
10491
|
if (path.length === 2) {
|
|
10000
|
-
if (
|
|
10001
|
-
if (
|
|
10492
|
+
if (m === "GET") return formSaveHandlers.GET(req, path[1]);
|
|
10493
|
+
if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path[1]);
|
|
10002
10494
|
}
|
|
10003
10495
|
}
|
|
10004
10496
|
if (path[0] === "users" && usersHandlers) {
|
|
10005
10497
|
if (path.length === 1) {
|
|
10006
|
-
if (
|
|
10007
|
-
if (
|
|
10498
|
+
if (m === "GET") return usersHandlers.list(req);
|
|
10499
|
+
if (m === "POST") return usersHandlers.create(req);
|
|
10008
10500
|
}
|
|
10009
10501
|
if (path.length === 2) {
|
|
10010
|
-
if (path[1] === "avatar" &&
|
|
10011
|
-
if (path[1] === "profile" &&
|
|
10502
|
+
if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
|
|
10503
|
+
if (path[1] === "profile" && m === "PUT" && profilePut) return profilePut(req);
|
|
10012
10504
|
const id = path[1];
|
|
10013
|
-
if (
|
|
10014
|
-
if (
|
|
10015
|
-
if (
|
|
10505
|
+
if (m === "GET") return usersHandlers.getById(req, id);
|
|
10506
|
+
if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
|
|
10507
|
+
if (m === "DELETE") return usersHandlers.delete(req, id);
|
|
10016
10508
|
}
|
|
10017
|
-
if (path.length === 3 && path[2] === "regenerate-invite" &&
|
|
10509
|
+
if (path.length === 3 && path[2] === "regenerate-invite" && m === "POST") {
|
|
10018
10510
|
return usersHandlers.regenerateInvite(req, path[1]);
|
|
10019
10511
|
}
|
|
10020
10512
|
}
|
|
10021
|
-
if (path[0] === "users" && path.length === 2 && userAuthRouter &&
|
|
10513
|
+
if (path[0] === "users" && path.length === 2 && userAuthRouter && m === "POST") {
|
|
10022
10514
|
return userAuthRouter.POST(req, path[1]);
|
|
10023
10515
|
}
|
|
10024
10516
|
if (path[0] === "settings" && path.length === 2 && settingsHandlers) {
|
|
10025
10517
|
const group = path[1];
|
|
10026
10518
|
const isPublic = settingsConfig?.publicGetGroups?.includes(group);
|
|
10027
|
-
if (
|
|
10519
|
+
if (m === "GET") {
|
|
10028
10520
|
if (!isPublic) {
|
|
10029
10521
|
const a = await config.requireAuth(req);
|
|
10030
10522
|
if (a) return a;
|
|
@@ -10033,15 +10525,38 @@ function createCmsApiHandler(config) {
|
|
|
10033
10525
|
}
|
|
10034
10526
|
return settingsHandlers.GET(req, group);
|
|
10035
10527
|
}
|
|
10036
|
-
if (
|
|
10528
|
+
if (m === "PUT") {
|
|
10037
10529
|
const pe = await requireEntityPermissionEffective(req, "settings", "update");
|
|
10038
10530
|
if (pe) return pe;
|
|
10039
10531
|
return settingsHandlers.PUT(req, group);
|
|
10040
10532
|
}
|
|
10041
10533
|
}
|
|
10042
10534
|
if (path[0] === "message-templates" && path[1] === "sms" && path.length === 2) {
|
|
10043
|
-
if (
|
|
10044
|
-
if (
|
|
10535
|
+
if (m === "GET") return smsMessageTemplateHandlers.GET(req);
|
|
10536
|
+
if (m === "PUT") return smsMessageTemplateHandlers.PUT(req);
|
|
10537
|
+
}
|
|
10538
|
+
if (path[0] === "llm_agents") {
|
|
10539
|
+
if (!entityMap.llm_agents) {
|
|
10540
|
+
console.error(CMS_API_LOG, "llm_agents route: entity missing after merge", {
|
|
10541
|
+
method: m,
|
|
10542
|
+
pathJoined: path.join("/"),
|
|
10543
|
+
entityMapKeyCount: Object.keys(entityMap).length
|
|
10544
|
+
});
|
|
10545
|
+
return config.json(
|
|
10546
|
+
{ error: "LlmAgent entity is not registered", hint: "Ensure migrations ran and entities include LlmAgent." },
|
|
10547
|
+
{ status: 503 }
|
|
10548
|
+
);
|
|
10549
|
+
}
|
|
10550
|
+
if (path.length === 1) {
|
|
10551
|
+
if (m === "GET") return crud.GET(req, "llm_agents");
|
|
10552
|
+
if (m === "POST") return crud.POST(req, "llm_agents");
|
|
10553
|
+
}
|
|
10554
|
+
if (path.length === 2 && !path[1]?.includes("knowledge")) {
|
|
10555
|
+
const id = path[1];
|
|
10556
|
+
if (m === "GET") return crudById.GET(req, "llm_agents", id);
|
|
10557
|
+
if (m === "PUT" || m === "PATCH") return crudById.PUT(req, "llm_agents", id);
|
|
10558
|
+
if (m === "DELETE") return crudById.DELETE(req, "llm_agents", id);
|
|
10559
|
+
}
|
|
10045
10560
|
}
|
|
10046
10561
|
{
|
|
10047
10562
|
const kbMatch = matchLlmAgentKnowledgeRoute(path);
|
|
@@ -10056,24 +10571,24 @@ function createCmsApiHandler(config) {
|
|
|
10056
10571
|
);
|
|
10057
10572
|
}
|
|
10058
10573
|
const { slug, documentId } = kbMatch;
|
|
10059
|
-
if (
|
|
10574
|
+
if (m === "DELETE" && documentId != null && documentId !== "") {
|
|
10060
10575
|
return llmAgentKnowledgeHandlers.unlink(req, slug, documentId);
|
|
10061
10576
|
}
|
|
10062
|
-
if (
|
|
10577
|
+
if (m === "GET" && (documentId == null || documentId === "")) {
|
|
10063
10578
|
return llmAgentKnowledgeHandlers.list(req, slug);
|
|
10064
10579
|
}
|
|
10065
|
-
if (
|
|
10580
|
+
if (m === "POST" && (documentId == null || documentId === "")) {
|
|
10066
10581
|
return llmAgentKnowledgeHandlers.post(req, slug);
|
|
10067
10582
|
}
|
|
10068
10583
|
}
|
|
10069
10584
|
}
|
|
10070
10585
|
if (path[0] === "chat" && chatHandlers) {
|
|
10071
|
-
if (path.length === 2 && path[1] === "config" &&
|
|
10072
|
-
if (path.length === 2 && path[1] === "identify" &&
|
|
10073
|
-
if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" &&
|
|
10074
|
-
if (path.length === 2 && path[1] === "messages" &&
|
|
10586
|
+
if (path.length === 2 && path[1] === "config" && m === "GET") return chatHandlers.publicConfig(req);
|
|
10587
|
+
if (path.length === 2 && path[1] === "identify" && m === "POST") return chatHandlers.identify(req);
|
|
10588
|
+
if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && m === "GET") return chatHandlers.getMessages(req, path[2]);
|
|
10589
|
+
if (path.length === 2 && path[1] === "messages" && m === "POST") return chatHandlers.postMessage(req);
|
|
10075
10590
|
}
|
|
10076
|
-
if (path[0] === "orders" && path.length === 3 && path[2] === "invoice" &&
|
|
10591
|
+
if (path[0] === "orders" && path.length === 3 && path[2] === "invoice" && m === "GET" && getCms) {
|
|
10077
10592
|
const a = await config.requireAuth(req);
|
|
10078
10593
|
if (a) return a;
|
|
10079
10594
|
const pe = await requireEntityPermissionEffective(req, "orders", "read");
|
|
@@ -10087,17 +10602,17 @@ function createCmsApiHandler(config) {
|
|
|
10087
10602
|
if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
|
|
10088
10603
|
const a = await config.requireAuth(req);
|
|
10089
10604
|
if (a) return a;
|
|
10090
|
-
const pe = await requireEntityPermissionEffective(req, "orders",
|
|
10605
|
+
const pe = await requireEntityPermissionEffective(req, "orders", m === "GET" ? "read" : "update");
|
|
10091
10606
|
if (pe) return pe;
|
|
10092
10607
|
const oid = Number(path[1]);
|
|
10093
10608
|
if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
|
|
10094
10609
|
const cms = await getCms();
|
|
10095
10610
|
const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
|
|
10096
10611
|
const enabled = await isErpIntegrationEnabled3(cms, dataSource, entityMap);
|
|
10097
|
-
if (
|
|
10612
|
+
if (m === "GET") {
|
|
10098
10613
|
return config.json({ enabled });
|
|
10099
10614
|
}
|
|
10100
|
-
if (
|
|
10615
|
+
if (m === "POST") {
|
|
10101
10616
|
if (!enabled) return config.json({ error: "ERP integration is disabled" }, { status: 409 });
|
|
10102
10617
|
const { queueErpPaidOrderForOrderId: queueErpPaidOrderForOrderId2 } = await Promise.resolve().then(() => (init_paid_order_erp(), paid_order_erp_exports));
|
|
10103
10618
|
await queueErpPaidOrderForOrderId2(cms, dataSource, entityMap, oid);
|
|
@@ -10107,28 +10622,39 @@ function createCmsApiHandler(config) {
|
|
|
10107
10622
|
}
|
|
10108
10623
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
10109
10624
|
const resource = resolveResource(path[0]);
|
|
10110
|
-
if (!crudResources.includes(resource))
|
|
10625
|
+
if (!crudResources.includes(resource)) {
|
|
10626
|
+
console.warn(CMS_API_LOG, "generic CRUD gate: Invalid resource (not in crudResources)", {
|
|
10627
|
+
method: m,
|
|
10628
|
+
pathJoined: path.join("/"),
|
|
10629
|
+
pathSegments: path,
|
|
10630
|
+
resource,
|
|
10631
|
+
hasLlmAgentsEntity: Boolean(entityMap.llm_agents),
|
|
10632
|
+
crudResourcesHasResource: crudResources.includes(resource),
|
|
10633
|
+
crudResourcesLength: crudResources.length
|
|
10634
|
+
});
|
|
10635
|
+
return config.json({ error: "Invalid resource" }, { status: 400 });
|
|
10636
|
+
}
|
|
10111
10637
|
if (path.length === 2) {
|
|
10112
|
-
if (path[1] === "metadata" &&
|
|
10638
|
+
if (path[1] === "metadata" && m === "GET") {
|
|
10113
10639
|
return crud.GET_METADATA(req, resource);
|
|
10114
10640
|
}
|
|
10115
|
-
if (path[1] === "bulk" &&
|
|
10641
|
+
if (path[1] === "bulk" && m === "POST") {
|
|
10116
10642
|
return crud.BULK_POST(req, resource);
|
|
10117
10643
|
}
|
|
10118
|
-
if (path[1] === "export" &&
|
|
10644
|
+
if (path[1] === "export" && m === "GET") {
|
|
10119
10645
|
return crud.GET_EXPORT(req, resource);
|
|
10120
10646
|
}
|
|
10121
10647
|
}
|
|
10122
10648
|
if (path.length === 1) {
|
|
10123
|
-
if (
|
|
10124
|
-
if (
|
|
10649
|
+
if (m === "GET") return crud.GET(req, resource);
|
|
10650
|
+
if (m === "POST") return crud.POST(req, resource);
|
|
10125
10651
|
return config.json({ error: "Method not allowed" }, { status: 405 });
|
|
10126
10652
|
}
|
|
10127
10653
|
if (path.length === 2) {
|
|
10128
10654
|
const id = path[1];
|
|
10129
|
-
if (
|
|
10130
|
-
if (
|
|
10131
|
-
if (
|
|
10655
|
+
if (m === "GET") return crudById.GET(req, resource, id);
|
|
10656
|
+
if (m === "PUT" || m === "PATCH") return crudById.PUT(req, resource, id);
|
|
10657
|
+
if (m === "DELETE") return crudById.DELETE(req, resource, id);
|
|
10132
10658
|
return config.json({ error: "Method not allowed" }, { status: 405 });
|
|
10133
10659
|
}
|
|
10134
10660
|
return config.json({ error: "Not found" }, { status: 404 });
|