@retrivora-ai/rag-engine 2.2.4 → 2.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "UNLICENSED",
@@ -37,6 +37,8 @@
37
37
  "import": "./dist/index.mjs"
38
38
  },
39
39
  "./style.css": "./dist/index.css",
40
+ "./index.css": "./dist/index.css",
41
+ "./styles.css": "./dist/index.css",
40
42
  "./handlers": {
41
43
  "types": "./dist/handlers/index.d.ts",
42
44
  "require": "./dist/handlers/index.js",
@@ -1,10 +1,9 @@
1
- 'use client';
2
-
3
1
  import React, { useState } from 'react';
4
- import { MessageSquare, X } from 'lucide-react';
2
+ import { MessageSquare, X, AlertTriangle } from 'lucide-react';
5
3
  import { ChatWindow } from './ChatWindow';
6
4
  import { useConfig } from './ConfigProvider';
7
5
  import { ChatWidgetProps } from '../types';
6
+ import { SDK_VERSION } from '../version';
8
7
 
9
8
  const DEFAULT_DIMENSIONS = { width: 380, height: 600 };
10
9
 
@@ -54,6 +53,8 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
54
53
  const [isResizing, setIsResizing] = useState(false);
55
54
  const [isMaximized, setIsMaximized] = useState(false);
56
55
  const [prevDimensions, setPrevDimensions] = useState(DEFAULT_DIMENSIONS);
56
+ const [widgetError, setWidgetError] = useState<string | null>(null);
57
+ const [isValidating, setIsValidating] = useState(false);
57
58
 
58
59
  if (ui.showWidget === false) return null;
59
60
 
@@ -67,9 +68,50 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
67
68
  ? 'bottom-20 left-6'
68
69
  : 'bottom-20 right-6';
69
70
 
70
- const handleOpen = () => {
71
- setIsOpen(true);
72
- setHasUnread(false);
71
+ const handleOpen = async () => {
72
+ setIsValidating(true);
73
+ try {
74
+ const base = retrivoraApiBase || '/api/retrivora';
75
+ const configUrl = `${base.replace(/\/$/, '')}/config?sdkVersion=${SDK_VERSION}`;
76
+ const res = await fetch(configUrl, {
77
+ method: 'GET',
78
+ cache: 'no-store',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ 'x-sdk-version': SDK_VERSION,
82
+ ...(headers || {})
83
+ },
84
+ });
85
+
86
+ if (!res.ok) {
87
+ const data = await res.json().catch(() => ({}));
88
+ if (res.status === 426 || data.code === 'SDK_VERSION_OUTDATED') {
89
+ const msg = data.error || 'Your Retrivora SDK package version is outdated. Please update your package to the latest version to continue.';
90
+ setWidgetError(msg);
91
+ setIsOpen(false); // DO NOT OPEN CHAT WIDGET
92
+ return;
93
+ }
94
+ if (
95
+ res.status === 403 ||
96
+ res.status === 401 ||
97
+ (typeof data.error === 'string' && data.error.toLowerCase().includes('license'))
98
+ ) {
99
+ const msg = (typeof data.error === 'string' ? data.error : data.error?.message) || 'Your Retrivora license is no longer valid. Please contact your administrator or obtain a valid license key.';
100
+ setWidgetError(msg);
101
+ setIsOpen(false); // DO NOT OPEN CHAT WIDGET
102
+ return;
103
+ }
104
+ }
105
+
106
+ setWidgetError(null);
107
+ setIsOpen(true);
108
+ setHasUnread(false);
109
+ } catch {
110
+ // Fallback
111
+ setIsOpen(true);
112
+ } finally {
113
+ setIsValidating(false);
114
+ }
73
115
  };
74
116
 
75
117
  const handleResizeStart = (e: React.MouseEvent) => {
@@ -157,6 +199,33 @@ export function ChatWidget({ position = 'bottom-right', onAddToCart, apiUrl, ret
157
199
  />
158
200
  </div>
159
201
 
202
+ {/* ── Widget Error Toast/Pop-over (when opening is blocked) ── */}
203
+ {widgetError && !isOpen && (
204
+ <div
205
+ className={`fixed z-[9999] max-w-sm p-4 rounded-2xl shadow-2xl border border-rose-200 dark:border-rose-900/60 bg-white/95 dark:bg-[#0f0f1a]/95 backdrop-blur-xl transition-all duration-300 animate-in fade-in slide-in-from-bottom-3 ${
206
+ position === 'bottom-left' ? 'bottom-24 left-6' : 'bottom-24 right-6'
207
+ }`}
208
+ >
209
+ <div className="flex items-start gap-3">
210
+ <div className="w-8 h-8 rounded-xl bg-rose-100 dark:bg-rose-900/40 text-rose-600 dark:text-rose-400 flex items-center justify-center shrink-0 mt-0.5">
211
+ <AlertTriangle className="w-4 h-4" />
212
+ </div>
213
+ <div className="flex-1 space-y-1">
214
+ <h5 className="text-xs font-bold text-slate-900 dark:text-white">Access Blocked</h5>
215
+ <p className="text-[11px] text-slate-600 dark:text-slate-300 leading-relaxed font-medium">
216
+ {widgetError}
217
+ </p>
218
+ </div>
219
+ <button
220
+ onClick={() => setWidgetError(null)}
221
+ className="text-slate-400 hover:text-slate-600 dark:hover:text-white transition-colors p-1"
222
+ >
223
+ <X className="w-3.5 h-3.5" />
224
+ </button>
225
+ </div>
226
+ </div>
227
+ )}
228
+
160
229
  {/* ── Floating Action Button ── */}
161
230
  <button
162
231
  onClick={isOpen ? () => setIsOpen(false) : handleOpen}
@@ -7,6 +7,7 @@ import {
7
7
  X,
8
8
  Sparkles,
9
9
  ArrowUp,
10
+ ArrowUpCircle,
10
11
  TriangleAlert,
11
12
  RotateCcw,
12
13
  Maximize2,
@@ -21,6 +22,7 @@ import { useConfig } from './ConfigProvider';
21
22
  import { useRagChat } from '../hooks/useRagChat';
22
23
  import { BORDER_RADIUS_MAP, CHAT_SUGGESTIONS } from '../config/uiConstants';
23
24
  import { ChatWindowProps } from '../types';
25
+ import { SDK_VERSION } from '../version';
24
26
 
25
27
  interface SpeechRecognitionEvent {
26
28
  resultIndex: number;
@@ -86,8 +88,58 @@ export function ChatWindow({
86
88
 
87
89
  const [isListening, setIsListening] = useState(false);
88
90
  const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
91
+ const [licenseError, setLicenseError] = useState<string | null>(null);
92
+ const [versionError, setVersionError] = useState<string | null>(null);
89
93
  const recognitionRef = useRef<ISpeechRecognition | null>(null);
90
94
 
95
+ // Validate server license and SDK version on ChatWindow session initialization
96
+ useEffect(() => {
97
+ let isMounted = true;
98
+ async function validateSessionLicense() {
99
+ try {
100
+ const base = retrivoraApiBase || '/api/retrivora';
101
+ const configUrl = `${base.replace(/\/$/, '')}/config?sdkVersion=${SDK_VERSION}`;
102
+ const res = await fetch(configUrl, {
103
+ method: 'GET',
104
+ cache: 'no-store',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ 'x-sdk-version': SDK_VERSION,
108
+ ...(headers || {})
109
+ },
110
+ });
111
+
112
+ if (!res.ok) {
113
+ const data = await res.json().catch(() => ({}));
114
+ if (res.status === 426 || data.code === 'SDK_VERSION_OUTDATED') {
115
+ if (isMounted) {
116
+ setVersionError(data.error || 'Your Retrivora SDK package is outdated. Please update your package to the latest version to continue.');
117
+ }
118
+ } else if (
119
+ res.status === 403 ||
120
+ res.status === 401 ||
121
+ (typeof data.error === 'string' && data.error.toLowerCase().includes('license'))
122
+ ) {
123
+ if (isMounted) {
124
+ const errMsg = typeof data.error === 'string' ? data.error : data.error?.message || 'Your Retrivora license is no longer valid. Please contact your administrator or obtain a valid license key.';
125
+ setLicenseError(errMsg);
126
+ }
127
+ }
128
+ } else {
129
+ if (isMounted) {
130
+ setLicenseError(null);
131
+ setVersionError(null);
132
+ }
133
+ }
134
+ } catch {
135
+ // Non-blocking fallback
136
+ }
137
+ }
138
+
139
+ validateSessionLicense();
140
+ return () => { isMounted = false; };
141
+ }, [retrivoraApiBase, headers]);
142
+
91
143
  // Initialize SpeechRecognition
92
144
  useEffect(() => {
93
145
  if (typeof window !== 'undefined') {
@@ -345,7 +397,36 @@ export function ChatWindow({
345
397
  </div>
346
398
 
347
399
  <div className="flex-1 overflow-y-auto px-4 py-4 space-y-5 scrollbar-thin scrollbar-thumb-slate-200 dark:scrollbar-thumb-white/10 scrollbar-track-transparent min-h-0 bg-slate-50 dark:bg-transparent">
348
- {!mounted || isEmpty ? (
400
+ {versionError ? (
401
+ <div className="h-full flex flex-col items-center justify-center text-center p-6 space-y-4 bg-amber-50/70 dark:bg-amber-950/20 rounded-2xl border border-amber-200/80 dark:border-amber-800/40 my-auto">
402
+ <div className="w-14 h-14 rounded-2xl bg-amber-100 dark:bg-amber-900/40 text-amber-600 dark:text-amber-400 flex items-center justify-center shadow-sm shrink-0">
403
+ <ArrowUpCircle className="w-7 h-7 animate-bounce" />
404
+ </div>
405
+ <div className="space-y-2 max-w-xs">
406
+ <h4 className="text-base font-extrabold text-slate-900 dark:text-white tracking-tight">Package Update Required</h4>
407
+ <p className="text-xs text-slate-600 dark:text-slate-300 leading-relaxed font-medium">
408
+ {versionError}
409
+ </p>
410
+ <div className="pt-2">
411
+ <code className="block bg-slate-900 text-amber-300 text-[10px] font-mono py-2 px-3 rounded-xl select-all border border-slate-800">
412
+ npm i @retrivora-ai/rag-engine@latest
413
+ </code>
414
+ </div>
415
+ </div>
416
+ </div>
417
+ ) : licenseError ? (
418
+ <div className="h-full flex flex-col items-center justify-center text-center p-6 space-y-4 bg-rose-50/50 dark:bg-rose-950/20 rounded-2xl border border-rose-200/60 dark:border-rose-800/40 my-auto">
419
+ <div className="w-14 h-14 rounded-2xl bg-rose-100 dark:bg-rose-900/40 text-rose-600 dark:text-rose-400 flex items-center justify-center shadow-sm shrink-0">
420
+ <TriangleAlert className="w-7 h-7" />
421
+ </div>
422
+ <div className="space-y-2 max-w-xs">
423
+ <h4 className="text-base font-extrabold text-slate-900 dark:text-white tracking-tight">License Invalid</h4>
424
+ <p className="text-xs text-slate-600 dark:text-slate-300 leading-relaxed font-medium">
425
+ {licenseError}
426
+ </p>
427
+ </div>
428
+ </div>
429
+ ) : !mounted || isEmpty ? (
349
430
  /* Welcome state */
350
431
  <div className="h-full flex flex-col items-center justify-center text-center gap-4 px-6 py-4">
351
432
  <div
@@ -408,7 +489,7 @@ export function ChatWindow({
408
489
  )}
409
490
 
410
491
  {/* Error */}
411
- {error && (
492
+ {error && !licenseError && (
412
493
  <div className="flex items-center gap-2 text-rose-500 dark:text-rose-400 text-sm bg-rose-50 dark:bg-rose-400/10 border border-rose-200 dark:border-rose-400/20 rounded-xl px-4 py-3">
413
494
  <TriangleAlert className="w-4 h-4 flex-shrink-0" />
414
495
  <span>{error}</span>
@@ -479,7 +560,7 @@ export function ChatWindow({
479
560
  onKeyDown={handleKeyDown}
480
561
  placeholder={ui.placeholder}
481
562
  rows={1}
482
- disabled={isLoading}
563
+ disabled={isLoading || !!licenseError || !!versionError}
483
564
  className="flex-1 bg-transparent text-slate-900 dark:text-white/90 placeholder-slate-400 dark:placeholder-white/30 text-sm resize-none outline-none py-1.5 px-2 max-h-32 leading-relaxed disabled:opacity-50"
484
565
  style={{ scrollbarWidth: 'none' }}
485
566
  />
@@ -488,7 +569,7 @@ export function ChatWindow({
488
569
  <button
489
570
  type="button"
490
571
  onClick={toggleListening}
491
- disabled={isLoading}
572
+ disabled={isLoading || !!licenseError || !!versionError}
492
573
  className={`flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:active:scale-100 ${isListening
493
574
  ? 'bg-rose-500 text-white animate-pulse shadow-md shadow-rose-500/20'
494
575
  : 'text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10'
@@ -503,7 +584,8 @@ export function ChatWindow({
503
584
  <button
504
585
  type="button"
505
586
  onClick={() => setIsUploadModalOpen(true)}
506
- className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10"
587
+ disabled={isLoading || !!licenseError || !!versionError}
588
+ className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all hover:scale-105 active:scale-95 text-slate-400 dark:text-white/40 hover:bg-slate-200 dark:hover:bg-white/10 disabled:opacity-50 disabled:cursor-not-allowed"
507
589
  title="Upload Document"
508
590
  >
509
591
  <Paperclip className="w-4 h-4" />
@@ -521,14 +603,10 @@ export function ChatWindow({
521
603
  ) : (
522
604
  <button
523
605
  onClick={sendMessage}
524
- disabled={!input.trim()}
525
- className="flex-shrink-0 w-9 h-9 rounded-xl flex items-center justify-center transition-all disabled:opacity-30 disabled:cursor-not-allowed hover:scale-105 active:scale-95 shadow-md"
526
- style={{
527
- background:
528
- input.trim()
529
- ? `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})`
530
- : 'rgba(148, 163, 184, 0.2)', // slate-400/20 for light mode disabled
531
- }}
606
+ disabled={!input.trim() || !!licenseError || !!versionError}
607
+ className={`flex-shrink-0 w-9 h-9 flex items-center justify-center transition-all shadow-md active:scale-95 cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed disabled:active:scale-100 ${ui.borderRadius === 'full' ? 'rounded-full' : 'rounded-xl'}`}
608
+ style={{ background: `linear-gradient(135deg, ${ui.primaryColor}, ${ui.accentColor})` }}
609
+ title="Send message"
532
610
  >
533
611
  <ArrowUp className="w-4 h-4 text-white" />
534
612
  </button>
@@ -14,8 +14,7 @@ const OPENAI_BASE = {
14
14
  export const LLM_PROFILES = {
15
15
  'openai-compatible': OPENAI_BASE,
16
16
  'litellm': {
17
- ...OPENAI_BASE,
18
- chatPayloadTemplate: '{"model":"{{model}}","messages":{{messages}},"max_tokens":{{maxTokens}},"temperature":{{temperature}},"stream":false}'
17
+ ...OPENAI_BASE,
19
18
  },
20
19
  'anthropic-claude': {
21
20
  chatPath: '/v1/messages',
@@ -187,8 +187,10 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
187
187
 
188
188
  // Default gateway: retrivora.com/api/v1 acts as a secure proxy that validates the license key
189
189
  // and forwards to llm.retrivora.com with LITELLM_MASTER_KEY (which end-users never need to know).
190
- // Developers who self-host can override with LITELLM_BASE_URL.
191
- const defaultGatewayUrl = readString(env, 'LITELLM_BASE_URL') ?? readString(env, 'LLM_BASE_URL') ?? 'https://www.retrivora.com/api/v1';
190
+ const defaultGatewayUrl =
191
+ readString(env, 'LITELLM_BASE_URL') ??
192
+ readString(env, 'LLM_BASE_URL') ??
193
+ 'https://www.retrivora.com/api/v1';
192
194
 
193
195
  const defaultLlmProvider = isFreeTier ? 'universal_rest' : 'universal_rest';
194
196
  const llmProvider = (readEnum(env, 'LLM_PROVIDER', defaultLlmProvider, LLM_PROVIDERS) ?? base?.llm?.provider ?? defaultLlmProvider) as LLMProvider;
@@ -277,7 +279,7 @@ export function getEnvConfig(env: Record<string, string | undefined> = process.e
277
279
  },
278
280
  telemetry: {
279
281
  enabled: telemetryEnabled,
280
- url: readString(env, 'TELEMETRY_URL') ?? readString(env, 'NEXT_PUBLIC_TELEMETRY_URL') ?? base?.telemetry?.url ?? (process.env.NODE_ENV === 'development' ? 'http://localhost:3001/api/telemetry' : 'https://retrivora.com/api/telemetry'),
282
+ url: readString(env, 'TELEMETRY_URL') ?? readString(env, 'NEXT_PUBLIC_TELEMETRY_URL') ?? base?.telemetry?.url ?? 'https://www.retrivora.com/api/telemetry',
281
283
  },
282
284
  // Optional graph DB — driven by GRAPH_DB_PROVIDER env var
283
285
  ...(readString(env, 'GRAPH_DB_PROVIDER') ? {
@@ -57,8 +57,8 @@ export class ConfigFetcher {
57
57
  process.env.NEXT_PUBLIC_RETRIVORA_CONTROL_PLANE_URL,
58
58
  'https://www.retrivora.com',
59
59
  'https://retrivora.com',
60
- 'http://localhost:3001',
61
60
  'http://localhost:3000',
61
+ 'http://localhost:3001',
62
62
  ].filter(Boolean) as string[];
63
63
 
64
64
  for (const baseUrl of controlPlaneUrls) {
@@ -586,16 +586,16 @@ You are a helpful assistant. Use the provided context to answer questions accura
586
586
  ? await this.vectorDB.query(queryVector, retrievalLimit, ns, filter)
587
587
  : []).filter((s): s is VectorMatch => Boolean(s && typeof s === 'object'));
588
588
 
589
- console.log('[Pipeline] Raw vectorDB.query response:', {
590
- rawCount: rawSources.length,
591
- sample: rawSources.slice(0, 2).map((s) => ({
592
- id: s?.id,
593
- score: s?.score,
594
- contentType: typeof s?.content,
595
- contentSnippet: String(s?.content ?? '').substring(0, 150),
596
- metadataKeys: Object.keys(s?.metadata || {}),
597
- })),
598
- });
589
+ // console.log('[Pipeline] Raw vectorDB.query response:', {
590
+ // rawCount: rawSources.length,
591
+ // sample: rawSources.slice(0, 2).map((s) => ({
592
+ // id: s?.id,
593
+ // score: s?.score,
594
+ // contentType: typeof s?.content,
595
+ // contentSnippet: String(s?.content ?? '').substring(0, 150),
596
+ // metadataKeys: Object.keys(s?.metadata || {}),
597
+ // })),
598
+ // });
599
599
 
600
600
  const retrieveEnd = performance.now();
601
601
  const embedMs = retrieveEnd - embedStart;
@@ -628,18 +628,18 @@ You are a helpful assistant. Use the provided context to answer questions accura
628
628
  .filter((s): s is VectorMatch => Boolean(s && typeof s === 'object'))
629
629
  .sort((a, b) => (b?.score ?? 0) - (a?.score ?? 0));
630
630
 
631
- console.log('[Pipeline] Final sources for prompt & UI:', {
632
- count: sources.length,
633
- totalRawCount: rawSources.length,
634
- sources: sources.slice(0, 10).map((s, idx) => ({
635
- rank: idx + 1,
636
- id: s?.id,
637
- score: s?.score,
638
- contentType: typeof s?.content,
639
- contentSnippet: String(s?.content ?? '').substring(0, 150),
640
- metadata: s?.metadata,
641
- })),
642
- });
631
+ // console.log('[Pipeline] Final sources for prompt & UI:', {
632
+ // count: sources.length,
633
+ // totalRawCount: rawSources.length,
634
+ // sources: sources.slice(0, 10).map((s, idx) => ({
635
+ // rank: idx + 1,
636
+ // id: s?.id,
637
+ // score: s?.score,
638
+ // contentType: typeof s?.content,
639
+ // contentSnippet: String(s?.content ?? '').substring(0, 150),
640
+ // metadata: s?.metadata,
641
+ // })),
642
+ // });
643
643
 
644
644
  if (graphData && graphData.nodes.length > 0) {
645
645
  const graphContext = graphData.nodes.map(n =>
@@ -912,14 +912,11 @@ You are a helpful assistant. Use the provided context to answer questions accura
912
912
  // Asynchronously trigger telemetry logging without blocking response yield
913
913
  const isTelemetryActive = this.config.telemetry?.enabled ?? Boolean(this.config.licenseKey);
914
914
  if (isTelemetryActive) {
915
- const defaultUrl = process.env.NODE_ENV === 'development' && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL
916
- ? 'http://localhost:3001/api/telemetry'
917
- : 'https://retrivora.com/api/telemetry';
918
-
915
+ const defaultUrl = 'https://www.retrivora.com/api/telemetry';
919
916
  const telemetryUrl = this.config.telemetry?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
920
917
  const absoluteUrl = telemetryUrl.startsWith('http')
921
918
  ? telemetryUrl
922
- : (process.env.NEXT_PUBLIC_APP_URL || `http://localhost:${process.env.PORT || 3000}`) + telemetryUrl;
919
+ : 'https://www.retrivora.com' + (telemetryUrl.startsWith('/') ? telemetryUrl : '/' + telemetryUrl);
923
920
 
924
921
  // Start background process for telemetry, awaiting hallucination scoring if it was backgrounded
925
922
  (async () => {
@@ -155,8 +155,8 @@ export class VectorPlugin {
155
155
  }
156
156
  try {
157
157
  return await this.pipeline.getSuggestions(query, namespace);
158
- } catch (err) {
159
- throw wrapError(err, 'RETRIEVAL_FAILED');
158
+ } catch {
159
+ return [];
160
160
  }
161
161
  }
162
162
  }
@@ -166,13 +166,10 @@ function reportTelemetry(
166
166
  const licenseKey = config.licenseKey || process.env.RAG_LICENSE_KEY || process.env.RETRIVORA_LICENSE_KEY || process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY;
167
167
  const telemetryConfig = config.telemetry;
168
168
 
169
- const enabled = telemetryConfig?.enabled ?? Boolean(licenseKey);
170
- const defaultUrl = process.env.NODE_ENV === 'development' && !process.env.TELEMETRY_URL && !process.env.NEXT_PUBLIC_TELEMETRY_URL
171
- ? 'http://localhost:3001/api/telemetry'
172
- : 'https://www.retrivora.com/api/telemetry';
169
+ const host = req.headers.get('host') || 'localhost';
170
+ const defaultUrl = 'https://www.retrivora.com/api/telemetry';
173
171
 
174
172
  const telemetryUrl = telemetryConfig?.url || process.env.TELEMETRY_URL || process.env.NEXT_PUBLIC_TELEMETRY_URL || defaultUrl;
175
- const host = req.headers.get('host') || 'localhost';
176
173
  const userAgent = req.headers.get('user-agent') || `Retrivora-SDK/${SDK_VERSION}`;
177
174
 
178
175
  let absoluteUrl = telemetryUrl;
@@ -285,7 +282,7 @@ export function createChatHandler(
285
282
  const storage = new DatabaseStorage(plugin.getConfig());
286
283
  const onAuthorize = options?.onAuthorize;
287
284
 
288
- return async function POST(req: NextRequest) {
285
+ return async function POST(req: any, context?: any) {
289
286
  const authResult = await checkAuth(req, onAuthorize);
290
287
  if (authResult) return authResult as any;
291
288
 
@@ -361,7 +358,7 @@ export function createStreamHandler(
361
358
  const storage = new DatabaseStorage(plugin.getConfig());
362
359
  const onAuthorize = options?.onAuthorize;
363
360
 
364
- return async function POST(req: NextRequest) {
361
+ return async function POST(req: any, context?: any) {
365
362
  const authResult = await checkAuth(req, onAuthorize);
366
363
  if (authResult) return authResult as any;
367
364
 
@@ -532,7 +529,7 @@ export function createIngestHandler(
532
529
  const plugin = getOrCreatePlugin(configOrPlugin);
533
530
  const onAuthorize = options?.onAuthorize;
534
531
 
535
- return async function POST(req: NextRequest) {
532
+ return async function POST(req: any, context?: any) {
536
533
  const authResult = await checkAuth(req, onAuthorize);
537
534
  if (authResult) return authResult as any;
538
535
 
@@ -599,7 +596,7 @@ export function createUploadHandler(
599
596
  const plugin = getOrCreatePlugin(configOrPlugin);
600
597
  const onAuthorize = options?.onAuthorize;
601
598
 
602
- return async function POST(req: NextRequest) {
599
+ return async function POST(req: any, context?: any) {
603
600
  const authResult = await checkAuth(req, onAuthorize);
604
601
  if (authResult) return authResult as any;
605
602
 
@@ -926,7 +923,7 @@ export function createRagHandler(
926
923
  const feedbackHandler = createFeedbackHandler(plugin, { onAuthorize });
927
924
  const sessionsHandler = createSessionsHandler(plugin, { onAuthorize });
928
925
 
929
- async function routePostRequest(req: NextRequest, segment: string) {
926
+ async function routePostRequest(req: any, segment: string) {
930
927
  switch (segment) {
931
928
  case 'chat':
932
929
  return streamHandler(req);
@@ -948,7 +945,7 @@ export function createRagHandler(
948
945
  }
949
946
  }
950
947
 
951
- async function routeGetRequest(req: NextRequest, segment: string) {
948
+ async function routeGetRequest(req: any, segment: string) {
952
949
  switch (segment) {
953
950
  case 'health':
954
951
  return healthHandler(req);
@@ -965,7 +962,7 @@ export function createRagHandler(
965
962
  }
966
963
  }
967
964
 
968
- async function getSegment(req: NextRequest, context: any): Promise<string> {
965
+ async function getSegment(req: any, context: any): Promise<string> {
969
966
  // 1. If content-type is multipart/form-data, it MUST be an upload request!
970
967
  const contentType = req.headers.get('content-type') || '';
971
968
  if (contentType.includes('multipart/form-data')) {
@@ -995,7 +992,7 @@ export function createRagHandler(
995
992
  }
996
993
 
997
994
  return {
998
- GET: async (req: NextRequest, context: any) => {
995
+ GET: async (req: any, context?: any) => {
999
996
  try {
1000
997
  const segment = await getSegment(req, context);
1001
998
  return await routeGetRequest(req, segment);
@@ -1004,7 +1001,7 @@ export function createRagHandler(
1004
1001
  return NextResponse.json({ error: msg }, { status: 500 });
1005
1002
  }
1006
1003
  },
1007
- POST: async (req: NextRequest, context: any) => {
1004
+ POST: async (req: any, context?: any) => {
1008
1005
  try {
1009
1006
  const segment = await getSegment(req, context);
1010
1007
  return await routePostRequest(req, segment);
@@ -154,7 +154,15 @@ export function useRagChat(
154
154
 
155
155
  if (!response.ok) {
156
156
  const errorData = await response.json().catch(() => ({}));
157
- throw new Error(errorData.error || `Server returned ${response.status}`);
157
+ if (
158
+ response.status === 403 ||
159
+ response.status === 401 ||
160
+ (typeof errorData.error === 'string' && errorData.error.toLowerCase().includes('license')) ||
161
+ errorData.error?.type === 'license_revoked'
162
+ ) {
163
+ throw new Error('Your Retrivora license is no longer valid. Please contact your administrator or obtain a valid license key.');
164
+ }
165
+ throw new Error(errorData.error?.message || errorData.error || `Server returned ${response.status}`);
158
166
  }
159
167
 
160
168
  if (!response.body) {