@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/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://api-inference.huggingface.co").replace(
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 ?? "meta-llama/Meta-Llama-3-8B-Instruct";
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
- const embedUrl = `${this.base}/v1/embeddings`;
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 ${this.apiKey}`
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 (e.g. router.huggingface.co). Set EMBEDDING_PROVIDER=huggingface and EMBEDDING_MODEL to a HF Inference\u2013supported embedding model, or use a host with /v1/embeddings." : "Embeddings HTTP error \u2014 knowledge_base_chunks.embedding will stay NULL for failed calls.";
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
- let embedding = data?.data?.[0]?.embedding;
2577
- if (!Array.isArray(embedding) && Array.isArray(data?.embedding)) {
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
- async embedHuggingFaceInference(input, model) {
2607
- const normalizedBase = this.hfInferenceBaseURL.replace(/\/$/, "");
2608
- const baseWithProvider = /router\.huggingface\.co\/hf-inference$/i.test(normalizedBase) ? normalizedBase : /router\.huggingface\.co$/i.test(normalizedBase) ? `${normalizedBase}/hf-inference` : normalizedBase;
2609
- const requestBody = JSON.stringify({
2610
- inputs: input,
2611
- options: { wait_for_model: true }
2612
- });
2613
- const pipelineUrl = `${baseWithProvider}/pipeline/feature-extraction/${model}`;
2614
- const modelUrl = `${baseWithProvider}/models/${model}`;
2615
- if (this.embedDebugEnabled()) {
2616
- console.info("[LLM embed HF]", { url: pipelineUrl, model, inputChars: input.length });
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
- bodyText = await res.text();
2645
- }
2646
- if (!res.ok) {
2647
- if (!_LlmService.embedHttpErrorLogged) {
2648
- _LlmService.embedHttpErrorLogged = true;
2649
- console.error(
2650
- "[LLM embed HF] Hugging Face Inference request failed \u2014 knowledge chunks stay without vectors.",
2651
- {
2652
- url: embedUrl,
2653
- status: res.status,
2654
- statusText: res.statusText,
2655
- bodyPreview: bodyText.slice(0, 800),
2656
- hint: res.status === 410 || res.status === 404 ? "Model or route unavailable on Inference API; pick another EMBEDDING_MODEL (e.g. sentence-transformers/all-MiniLM-L6-v2)." : void 0
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 HTTP error", { status: res.status, bodyPreview: bodyText.slice(0, 200) });
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
- let data;
2665
- try {
2666
- data = JSON.parse(bodyText);
2667
- } catch {
2668
- if (!_LlmService.embedShapeErrorLogged) {
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
- const embedding = parseHfInferenceEmbeddingBody(data);
2675
- if (!embedding?.length) {
2676
- if (!_LlmService.embedShapeErrorLogged) {
2677
- _LlmService.embedShapeErrorLogged = true;
2678
- const preview = typeof data === "object" && data !== null && !Array.isArray(data) ? Object.keys(data) : typeof data;
2679
- console.error("[LLM embed HF] Unrecognized embedding shape from Inference API.", { url: embedUrl, preview });
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
- if (!_LlmService.embedSuccessLogged) {
2684
- _LlmService.embedSuccessLogged = true;
2685
- console.info("[LLM embed HF] first success", { dimension: embedding.length, model });
2686
- if (!_LlmService.hfDimensionMismatchWarned && embedding.length !== 1536) {
2687
- _LlmService.hfDimensionMismatchWarned = true;
2688
- console.warn(
2689
- `[LLM embed HF] Embedding dimensions=${embedding.length}; default CMS migration uses vector(1536). If pgvector updates fail, alter the column, e.g. ALTER TABLE knowledge_base_chunks ALTER COLUMN embedding TYPE vector(${embedding.length});`
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.info("[LLM embed HF] ok", { dimension: embedding.length });
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 embedding;
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 ?? "meta-llama/Meta-Llama-3-8B-Instruct",
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(`LLM gateway error: ${res.status} ${text}`);
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 ?? "meta-llama/Meta-Llama-3-8B-Instruct",
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(`LLM gateway error: ${res.status} ${text}`);
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
- const [data, total] = await repo.findAndCount({
8685
- skip,
8686
- take: limit,
8687
- order: { [sortField]: sortOrder },
8688
- where
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 body = await req.json();
8700
- if (!body || typeof body !== "object" || Object.keys(body).length === 0) {
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
- sanitizeBodyForEntity(repo, body);
8737
- const created = await repo.save(repo.create(body));
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) return json({ error: "Invalid resource" }, { status: 400 });
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) return json({ error: "Invalid resource" }, { status: 400 });
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) return json({ error: "Invalid resource" }, { status: 400 });
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 writeEmbeddingsConcurrent(dataSource, embed, chunks, concurrency) {
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 (often HTTP 404 on /v1/embeddings, or wrong response shape).";
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
- return json({ documentId: existingDocumentId, linked: true, created: false });
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
- let embeddingsWritten = 0;
9436
- let embeddingsFailed = 0;
9437
- try {
9438
- const cms = await getCms();
9439
- const llm = cms.getPlugin("llm");
9440
- if (llm?.embed && savedChunks.length > 0) {
9441
- console.info(`${KB_LOG} starting embedding pass`, { slug, chunkCount: savedChunks.length });
9442
- const embedBound = (text2) => llm.embed(text2);
9443
- const { written, failed } = await writeEmbeddingsConcurrent(
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 = Object.keys(entityMap).filter((k) => !DEFAULT_EXCLUDE.has(k)),
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, path, req) {
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 && method === "GET") return adminRoles.list();
9949
- if (path.length === 2 && method === "POST") return adminRoles.createGroup(req);
9950
- if (path.length === 3 && method === "PATCH") return adminRoles.patchGroup(req, path[2]);
9951
- if (path.length === 3 && method === "DELETE") return adminRoles.deleteGroup(path[2]);
9952
- if (path.length === 4 && path[3] === "permissions" && method === "PUT") return adminRoles.putPermissions(req, path[2]);
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 && method === "GET" && dashboardGet) {
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 && method === "GET" && ecommerceAnalyticsGet) {
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 && method === "GET") {
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" && method === "GET") {
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" && method === "GET") {
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 && method === "POST" && uploadPost) return uploadPost(req);
9981
- if (path[0] === "media" && path[1] === "extract" && path.length === 3 && method === "POST" && zipExtractPost) {
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 && method === "GET" && blogBySlugGet) {
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 && method === "GET" && formBySlugGet) {
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 (method === "GET" && formSubmissionList) return formSubmissionList(req);
9993
- if (method === "POST" && formSubmissionPost) return formSubmissionPost(req);
10484
+ if (m === "GET" && formSubmissionList) return formSubmissionList(req);
10485
+ if (m === "POST" && formSubmissionPost) return formSubmissionPost(req);
9994
10486
  }
9995
- if (path.length === 2 && method === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path[1]);
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 && method === "POST") return formSaveHandlers.POST(req);
10490
+ if (path.length === 1 && m === "POST") return formSaveHandlers.POST(req);
9999
10491
  if (path.length === 2) {
10000
- if (method === "GET") return formSaveHandlers.GET(req, path[1]);
10001
- if (method === "PUT" || method === "PATCH") return formSaveHandlers.PUT(req, path[1]);
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 (method === "GET") return usersHandlers.list(req);
10007
- if (method === "POST") return usersHandlers.create(req);
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" && method === "POST" && avatarPost) return avatarPost(req);
10011
- if (path[1] === "profile" && method === "PUT" && profilePut) return profilePut(req);
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 (method === "GET") return usersHandlers.getById(req, id);
10014
- if (method === "PUT" || method === "PATCH") return usersHandlers.update(req, id);
10015
- if (method === "DELETE") return usersHandlers.delete(req, id);
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" && method === "POST") {
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 && method === "POST") {
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 (method === "GET") {
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 (method === "PUT") {
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 (method === "GET") return smsMessageTemplateHandlers.GET(req);
10044
- if (method === "PUT") return smsMessageTemplateHandlers.PUT(req);
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 (method === "DELETE" && documentId != null && documentId !== "") {
10574
+ if (m === "DELETE" && documentId != null && documentId !== "") {
10060
10575
  return llmAgentKnowledgeHandlers.unlink(req, slug, documentId);
10061
10576
  }
10062
- if (method === "GET" && (documentId == null || documentId === "")) {
10577
+ if (m === "GET" && (documentId == null || documentId === "")) {
10063
10578
  return llmAgentKnowledgeHandlers.list(req, slug);
10064
10579
  }
10065
- if (method === "POST" && (documentId == null || documentId === "")) {
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" && method === "GET") return chatHandlers.publicConfig(req);
10072
- if (path.length === 2 && path[1] === "identify" && method === "POST") return chatHandlers.identify(req);
10073
- if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && method === "GET") return chatHandlers.getMessages(req, path[2]);
10074
- if (path.length === 2 && path[1] === "messages" && method === "POST") return chatHandlers.postMessage(req);
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" && method === "GET" && getCms) {
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", method === "GET" ? "read" : "update");
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 (method === "GET") {
10612
+ if (m === "GET") {
10098
10613
  return config.json({ enabled });
10099
10614
  }
10100
- if (method === "POST") {
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)) return config.json({ error: "Invalid resource" }, { status: 400 });
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" && method === "GET") {
10638
+ if (path[1] === "metadata" && m === "GET") {
10113
10639
  return crud.GET_METADATA(req, resource);
10114
10640
  }
10115
- if (path[1] === "bulk" && method === "POST") {
10641
+ if (path[1] === "bulk" && m === "POST") {
10116
10642
  return crud.BULK_POST(req, resource);
10117
10643
  }
10118
- if (path[1] === "export" && method === "GET") {
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 (method === "GET") return crud.GET(req, resource);
10124
- if (method === "POST") return crud.POST(req, resource);
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 (method === "GET") return crudById.GET(req, resource, id);
10130
- if (method === "PUT" || method === "PATCH") return crudById.PUT(req, resource, id);
10131
- if (method === "DELETE") return crudById.DELETE(req, resource, id);
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 });