@retrivora-ai/rag-engine 2.3.0 → 2.3.1
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/{LicenseValidator-CENvo9o2.d.mts → BatchProcessor-7yV-UCHW.d.mts} +142 -3
- package/dist/{LicenseValidator-CsjJp2PP.d.ts → BatchProcessor-BfzuU4cK.d.ts} +142 -3
- package/dist/handlers/index.d.mts +1 -1
- package/dist/handlers/index.d.ts +1 -1
- package/dist/handlers/index.js +880 -327
- package/dist/handlers/index.mjs +880 -327
- package/dist/index-DR_O_B-W.d.ts +394 -0
- package/dist/index-fnpaCuma.d.mts +394 -0
- package/dist/index.css +58 -0
- package/dist/index.d.mts +63 -3
- package/dist/index.d.ts +63 -3
- package/dist/index.js +452 -35
- package/dist/index.mjs +451 -37
- package/dist/server.d.mts +35 -73
- package/dist/server.d.ts +35 -73
- package/dist/server.js +910 -341
- package/dist/server.mjs +910 -341
- package/package.json +1 -1
- package/src/components/ChatWidget.tsx +147 -46
- package/src/components/ChatWindow.tsx +52 -8
- package/src/core/BatchProcessor.ts +42 -4
- package/src/core/CircuitBreaker.ts +118 -0
- package/src/core/DatabaseStorage.ts +55 -24
- package/src/core/FreeTierLimitsGuard.ts +281 -0
- package/src/core/LicenseValidator.ts +66 -2
- package/src/core/LicenseVerifier.ts +31 -3
- package/src/core/VectorPlugin.ts +30 -0
- package/src/handlers/index.ts +491 -36
- package/src/index.css +58 -0
- package/src/index.ts +4 -0
- package/src/server.ts +1 -0
- package/dist/index-BPJ3KDYI.d.ts +0 -195
- package/dist/index-Dmq5lH0j.d.mts +0 -195
package/src/handlers/index.ts
CHANGED
|
@@ -5,16 +5,38 @@ import { ChatMessage, ChatResponse } from '../types';
|
|
|
5
5
|
import { DocumentParser } from '../utils/DocumentParser';
|
|
6
6
|
import { UITransformer } from '../utils/UITransformer';
|
|
7
7
|
import { DatabaseStorage, HistoryMessage } from '../core/DatabaseStorage';
|
|
8
|
-
import { LicenseVerifier } from '../core/LicenseVerifier';
|
|
8
|
+
import { LicenseVerifier, LicensePayload } from '../core/LicenseVerifier';
|
|
9
9
|
import { SDK_VERSION } from '../version';
|
|
10
|
+
import { FreeTierLimitsGuard, FREE_TIER_QUOTAS } from '../core/FreeTierLimitsGuard';
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Shared options accepted by every handler factory.
|
|
14
|
+
*
|
|
15
|
+
* Pass `onAuthorize` to plug in per-request auth rules; the callback is
|
|
16
|
+
* called before rate-limiting, license enforcement, or any business logic
|
|
17
|
+
* runs. Return `true` or the resolved promise `true` to allow, `false`
|
|
18
|
+
* to get a 401, or return a `Response` to fully control the denial (for
|
|
19
|
+
* example redirecting an OAuth-style flow to a login page).
|
|
20
|
+
*/
|
|
11
21
|
export interface HandlerOptions {
|
|
12
|
-
|
|
22
|
+
/** Optional synchronous or asynchronous authorization gate. */
|
|
23
|
+
onAuthorize?: (req: NextRequest) => boolean | Response | Promise<boolean | Response>;
|
|
24
|
+
/**
|
|
25
|
+
* Optional scope resolver for multi-tenant deployments.
|
|
26
|
+
*
|
|
27
|
+
* Returns an allow-list of project ids that the caller is authorized to
|
|
28
|
+
* access on the `sessions`, `history`, and `feedback` endpoints. When
|
|
29
|
+
* the callback returns a non-empty array, server-side filtering is
|
|
30
|
+
* applied to every `listSessions`, `getHistory`, `clearHistory`,
|
|
31
|
+
* `listFeedback`, and `getFeedback` call so the caller can never leak
|
|
32
|
+
* another tenant's data even when client-side filters are tampered with.
|
|
33
|
+
*/
|
|
34
|
+
onResolveScope?: (req: NextRequest) => string[] | undefined | Promise<string[] | undefined>;
|
|
13
35
|
}
|
|
14
36
|
|
|
15
37
|
async function checkAuth(
|
|
16
38
|
req: NextRequest,
|
|
17
|
-
onAuthorize?: (req: NextRequest) => boolean | Promise<boolean
|
|
39
|
+
onAuthorize?: (req: NextRequest) => boolean | Response | Promise<boolean | Response>
|
|
18
40
|
): Promise<Response | null> {
|
|
19
41
|
if (!onAuthorize) return null;
|
|
20
42
|
try {
|
|
@@ -109,6 +131,115 @@ function checkRateLimit(req: NextRequest): Response | null {
|
|
|
109
131
|
return null;
|
|
110
132
|
}
|
|
111
133
|
|
|
134
|
+
// ─── Free Tier Limits Guard ───────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
const FREE_TIER_NAMES = new Set(['hobby', 'free', 'free_trial', 'trial']);
|
|
137
|
+
|
|
138
|
+
function isFreeTier(tier?: string | null): boolean {
|
|
139
|
+
if (!tier) return true;
|
|
140
|
+
return FREE_TIER_NAMES.has(tier.toLowerCase().trim());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const _freeTierGuardInstance: any = _g.__retrivoraFreeTierGuard ??
|
|
144
|
+
(_g.__retrivoraFreeTierGuard = new (FreeTierLimitsGuard as any)());
|
|
145
|
+
|
|
146
|
+
function createPaymentRequiredResponse(reason: string, details?: Record<string, unknown>): Response {
|
|
147
|
+
const body = {
|
|
148
|
+
type: 'https://retrivora.com/docs/errors/payment-required',
|
|
149
|
+
title: 'Payment Required',
|
|
150
|
+
status: 402,
|
|
151
|
+
detail: reason,
|
|
152
|
+
code: 'FREE_TIER_LIMIT_EXCEEDED',
|
|
153
|
+
instance: undefined as string | undefined,
|
|
154
|
+
...(details ?? {}),
|
|
155
|
+
quota: {
|
|
156
|
+
maxDailyRequestUnits: FREE_TIER_QUOTAS.MAX_DAILY_REQUEST_UNITS,
|
|
157
|
+
maxTrialRequestUnits: FREE_TIER_QUOTAS.MAX_TRIAL_REQUEST_UNITS,
|
|
158
|
+
maxDocuments: FREE_TIER_QUOTAS.MAX_DOCUMENTS,
|
|
159
|
+
maxStorageBytes: FREE_TIER_QUOTAS.MAX_STORAGE_BYTES,
|
|
160
|
+
upgradeUrl: 'https://retrivora.com/pricing',
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
return new Response(JSON.stringify(body), {
|
|
164
|
+
status: 402,
|
|
165
|
+
headers: {
|
|
166
|
+
'Content-Type': 'application/problem+json',
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── License Enforcement ──────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Resolve the license key from a request, trying (in priority order):
|
|
175
|
+
* 1. `x-license-key` header
|
|
176
|
+
* 2. Body property `licenseKey` (already-parsed body, optional)
|
|
177
|
+
* 3. `Authorization: Bearer <key>` header
|
|
178
|
+
* 4. SDK/plugin config and environment variables
|
|
179
|
+
*/
|
|
180
|
+
function resolveLicenseKey(
|
|
181
|
+
req: NextRequest,
|
|
182
|
+
config: RagConfig,
|
|
183
|
+
bodyLicenseKey?: string
|
|
184
|
+
): string {
|
|
185
|
+
return (
|
|
186
|
+
req.headers.get('x-license-key') ||
|
|
187
|
+
bodyLicenseKey ||
|
|
188
|
+
req.headers.get('authorization')?.replace(/^Bearer\s+/i, '') ||
|
|
189
|
+
config.licenseKey ||
|
|
190
|
+
process.env.NEXT_PUBLIC_RETRIVORA_LICENSE_KEY ||
|
|
191
|
+
process.env.RETRIVORA_LICENSE_KEY ||
|
|
192
|
+
process.env.RAG_LICENSE_KEY ||
|
|
193
|
+
''
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Enforce a valid Retrivora license key.
|
|
199
|
+
*
|
|
200
|
+
* - Extracts license key from headers / body / env / config
|
|
201
|
+
* - Performs cryptographic + expiration validation via `LicenseVerifier.verify`
|
|
202
|
+
* - Returns `null` when validation passes (contains the decoded payload for future tier checks)
|
|
203
|
+
* - Returns a 403 Response ready to be sent when validation fails
|
|
204
|
+
*
|
|
205
|
+
* In development (`NODE_ENV !== 'production'`) a missing key still produces a
|
|
206
|
+
* console warning but passes (fail-open local-dev behavior); in production any
|
|
207
|
+
* invalid / expired / missing key produces a hard 403.
|
|
208
|
+
*/
|
|
209
|
+
function enforceLicense(
|
|
210
|
+
req: NextRequest,
|
|
211
|
+
config: RagConfig,
|
|
212
|
+
bodyLicenseKey?: string
|
|
213
|
+
): { ok: true; payload: LicensePayload } | { ok: false; response: Response } {
|
|
214
|
+
const rawKey = resolveLicenseKey(req, config, bodyLicenseKey);
|
|
215
|
+
const licenseKey = (rawKey || '').trim().replace(/^["']|["']$/g, '').trim();
|
|
216
|
+
const projectId = config.projectId || 'my-rag-app';
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const payload = LicenseVerifier.verify(licenseKey, projectId);
|
|
220
|
+
return { ok: true, payload };
|
|
221
|
+
} catch (err: any) {
|
|
222
|
+
const message =
|
|
223
|
+
err instanceof Error
|
|
224
|
+
? err.message
|
|
225
|
+
: 'Missing or invalid RETRIVORA_LICENSE_KEY. Set NEXT_PUBLIC_RETRIVORA_LICENSE_KEY in your environment or contact your administrator.';
|
|
226
|
+
const body = {
|
|
227
|
+
error: {
|
|
228
|
+
code: 'LICENSE_REVOKED',
|
|
229
|
+
message: 'Your Retrivora license key has been terminated, suspended, or revoked. Access denied.',
|
|
230
|
+
},
|
|
231
|
+
details: message,
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
response: new Response(JSON.stringify(body), {
|
|
236
|
+
status: 403,
|
|
237
|
+
headers: { 'Content-Type': 'application/json' },
|
|
238
|
+
}),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
112
243
|
// ─── Input Sanitization ───────────────────────────────────────────────────────
|
|
113
244
|
|
|
114
245
|
const MAX_MESSAGE_LENGTH = 8000;
|
|
@@ -205,8 +336,6 @@ function reportTelemetry(
|
|
|
205
336
|
details,
|
|
206
337
|
};
|
|
207
338
|
|
|
208
|
-
console.log(`[Retrivora SDK Telemetry] Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Latency: ${latencyMs}ms`);
|
|
209
|
-
|
|
210
339
|
fetch(absoluteUrl, {
|
|
211
340
|
method: 'POST',
|
|
212
341
|
headers: {
|
|
@@ -272,7 +401,29 @@ const SSE_HEADERS: Record<string, string> = {
|
|
|
272
401
|
// ─── Handler Factories ────────────────────────────────────────────────────────
|
|
273
402
|
|
|
274
403
|
/**
|
|
275
|
-
*
|
|
404
|
+
* Build a Next.js App Router POST handler for **synchronous** RAG chat.
|
|
405
|
+
*
|
|
406
|
+
* Pipeline (in execution order):
|
|
407
|
+
* 1. `options.onAuthorize` — custom authorization gate (optional)
|
|
408
|
+
* 2. In-memory IP rate limiter (30 req / 60 s sliding window)
|
|
409
|
+
* 3. JSON body parse → `enforceLicense()` RSA signature validation
|
|
410
|
+
* 4. Input sanitization + length / toxicity / PII / prompt-injection heuristics
|
|
411
|
+
* 5. User-message persistence via `DatabaseStorage`
|
|
412
|
+
* 6. LLM-driven RAG retrieval via `plugin.chat()`
|
|
413
|
+
* 7. Observability UI transformation + response persistence
|
|
414
|
+
* 8. JSON response (`ChatResponse` shape)
|
|
415
|
+
*
|
|
416
|
+
* Use this handler when you don't need streaming (e.g. server-side SDKs).
|
|
417
|
+
* For streaming use `createStreamHandler` — the same pipeline but with
|
|
418
|
+
* Server-Sent Events for incremental tokens.
|
|
419
|
+
*
|
|
420
|
+
* @param configOrPlugin - Either a partial `RagConfig` (a plugin will be
|
|
421
|
+
* created or reused for you) or an already-constructed
|
|
422
|
+
* `VectorPlugin` instance.
|
|
423
|
+
* @param options - See `HandlerOptions`. `onAuthorize` is recommended
|
|
424
|
+
* for multi-tenant deployments to gate access.
|
|
425
|
+
* @returns An async `POST(req, ctx)` function suitable for being the default
|
|
426
|
+
* export of a Next.js route file.
|
|
276
427
|
*/
|
|
277
428
|
export function createChatHandler(
|
|
278
429
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -290,9 +441,38 @@ export function createChatHandler(
|
|
|
290
441
|
const rateLimited = checkRateLimit(req);
|
|
291
442
|
if (rateLimited) return rateLimited;
|
|
292
443
|
|
|
444
|
+
const config = plugin.getConfig();
|
|
445
|
+
|
|
293
446
|
try {
|
|
294
447
|
const body = await req.json();
|
|
295
448
|
const bodyObj = (body ?? {}) as any;
|
|
449
|
+
|
|
450
|
+
// 2. License enforcement (after body parse so we can read body.licenseKey)
|
|
451
|
+
const licenseCheck = enforceLicense(req as NextRequest, config, bodyObj?.licenseKey);
|
|
452
|
+
if (!licenseCheck.ok) return licenseCheck.response as any;
|
|
453
|
+
|
|
454
|
+
// 2b. Free Tier limits check (hobby / free / free_trial tiers)
|
|
455
|
+
if (isFreeTier(licenseCheck.payload.tier)) {
|
|
456
|
+
const estimatedInputTokens = Math.ceil(
|
|
457
|
+
((bodyObj.message?.length || 0) +
|
|
458
|
+
(Array.isArray(bodyObj.messages)
|
|
459
|
+
? bodyObj.messages.reduce((a: number, m: any) => a + (m?.content?.length || m?.text?.length || 0), 0)
|
|
460
|
+
: 0) +
|
|
461
|
+
(bodyObj.history?.reduce((a: number, m: any) => a + (m?.content?.length || m?.text?.length || 0), 0) || 0)) / 4
|
|
462
|
+
);
|
|
463
|
+
const chatCheck = (FreeTierLimitsGuard as any).checkRequestAllowed({
|
|
464
|
+
operationType: 'chat',
|
|
465
|
+
inputTokens: estimatedInputTokens,
|
|
466
|
+
projectId: licenseCheck.payload.projectId,
|
|
467
|
+
});
|
|
468
|
+
if (!chatCheck.allowed) {
|
|
469
|
+
return createPaymentRequiredResponse(
|
|
470
|
+
chatCheck.reason || 'Free tier quota exceeded.',
|
|
471
|
+
{ tier: licenseCheck.payload.tier, projectId: licenseCheck.payload.projectId }
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
296
476
|
let rawMessage = bodyObj.message;
|
|
297
477
|
if (!rawMessage && Array.isArray(bodyObj.messages) && bodyObj.messages.length > 0) {
|
|
298
478
|
const lastMsg = bodyObj.messages[bodyObj.messages.length - 1];
|
|
@@ -304,7 +484,7 @@ export function createChatHandler(
|
|
|
304
484
|
const history = bodyObj.history || (Array.isArray(bodyObj.messages) ? bodyObj.messages.slice(0, -1) : []);
|
|
305
485
|
const { namespace, sessionId = 'default', messageId, userMessageId } = bodyObj;
|
|
306
486
|
|
|
307
|
-
//
|
|
487
|
+
// 3. Input sanitization
|
|
308
488
|
const sanitized = sanitizeInput(rawMessage ?? '');
|
|
309
489
|
if (!sanitized.ok) {
|
|
310
490
|
return NextResponse.json({ error: { code: 'INVALID_INPUT', message: sanitized.error } }, { status: 400 });
|
|
@@ -348,7 +528,30 @@ export function createChatHandler(
|
|
|
348
528
|
}
|
|
349
529
|
|
|
350
530
|
/**
|
|
351
|
-
*
|
|
531
|
+
* Build a Next.js App Router POST handler for **streaming** RAG chat via SSE.
|
|
532
|
+
*
|
|
533
|
+
* Same pipeline as `createChatHandler`, but the `plugin.chat()` call is run
|
|
534
|
+
* in streaming mode and every token is immediately emitted as a Server-Sent
|
|
535
|
+
* Event frame. The stream contains frames for `text`, `thinking`,
|
|
536
|
+
* `metadata`, `ui_transformation`, `observability`, and a terminal `done`.
|
|
537
|
+
*
|
|
538
|
+
* Multipart upload short-circuit: When the request `Content-Type` is
|
|
539
|
+
* `multipart/form-data` the request is transparently delegated to
|
|
540
|
+
* `createUploadHandler` (which also runs its own `enforceLicense()` gate).
|
|
541
|
+
*
|
|
542
|
+
* Pipeline (in execution order):
|
|
543
|
+
* 1. `options.onAuthorize` — custom authorization gate (optional)
|
|
544
|
+
* 2. In-memory IP rate limiter (30 req / 60 s sliding window)
|
|
545
|
+
* 3. JSON body parse (or form-data upload delegation)
|
|
546
|
+
* 4. `enforceLicense()` RSA signature validation — 403 if missing / terminated
|
|
547
|
+
* 5. Input sanitization + length / toxicity / PII / prompt-injection heuristics
|
|
548
|
+
* 6. User-message persistence via `DatabaseStorage`
|
|
549
|
+
* 7. Streaming RAG retrieval via `plugin.chat({ stream: true })`
|
|
550
|
+
* 8. Persist assistant message + emit SSE frames
|
|
551
|
+
*
|
|
552
|
+
* @param configOrPlugin - Partial `RagConfig` or an existing `VectorPlugin`.
|
|
553
|
+
* @param options - See `HandlerOptions`.
|
|
554
|
+
* @returns Async `POST(req, ctx)` function for `app/.../route.ts`.
|
|
352
555
|
*/
|
|
353
556
|
export function createStreamHandler(
|
|
354
557
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -366,6 +569,8 @@ export function createStreamHandler(
|
|
|
366
569
|
const rateLimited = checkRateLimit(req);
|
|
367
570
|
if (rateLimited) return rateLimited;
|
|
368
571
|
|
|
572
|
+
const config = plugin.getConfig();
|
|
573
|
+
|
|
369
574
|
let body: {
|
|
370
575
|
message?: string;
|
|
371
576
|
history?: ChatMessage[];
|
|
@@ -373,9 +578,11 @@ export function createStreamHandler(
|
|
|
373
578
|
sessionId?: string;
|
|
374
579
|
messageId?: string;
|
|
375
580
|
userMessageId?: string;
|
|
581
|
+
licenseKey?: string;
|
|
376
582
|
};
|
|
377
583
|
try {
|
|
378
584
|
if (req.headers.get('content-type')?.includes('multipart/form-data')) {
|
|
585
|
+
// Uploads bypass the JSON body parse — createUploadHandler enforces its own license check
|
|
379
586
|
const uploader = createUploadHandler(plugin, { onAuthorize });
|
|
380
587
|
return uploader(req);
|
|
381
588
|
}
|
|
@@ -388,6 +595,33 @@ export function createStreamHandler(
|
|
|
388
595
|
}
|
|
389
596
|
|
|
390
597
|
const bodyObj = (body ?? {}) as any;
|
|
598
|
+
|
|
599
|
+
// 2b. License enforcement (for JSON-body requests)
|
|
600
|
+
const licenseCheck = enforceLicense(req as NextRequest, config, bodyObj?.licenseKey);
|
|
601
|
+
if (!licenseCheck.ok) return licenseCheck.response as any;
|
|
602
|
+
|
|
603
|
+
// 2c. Free Tier limits check (hobby / free / free_trial tiers)
|
|
604
|
+
if (isFreeTier(licenseCheck.payload.tier)) {
|
|
605
|
+
const estimatedInputTokens = Math.ceil(
|
|
606
|
+
((bodyObj.message?.length || 0) +
|
|
607
|
+
(Array.isArray(bodyObj.messages)
|
|
608
|
+
? bodyObj.messages.reduce((a: number, m: any) => a + (m?.content?.length || m?.text?.length || 0), 0)
|
|
609
|
+
: 0) +
|
|
610
|
+
(bodyObj.history?.reduce((a: number, m: any) => a + (m?.content?.length || m?.text?.length || 0), 0) || 0)) / 4
|
|
611
|
+
);
|
|
612
|
+
const chatCheck = (FreeTierLimitsGuard as any).checkRequestAllowed({
|
|
613
|
+
operationType: 'chat_stream',
|
|
614
|
+
inputTokens: estimatedInputTokens,
|
|
615
|
+
projectId: licenseCheck.payload.projectId,
|
|
616
|
+
});
|
|
617
|
+
if (!chatCheck.allowed) {
|
|
618
|
+
return createPaymentRequiredResponse(
|
|
619
|
+
chatCheck.reason || 'Free tier quota exceeded.',
|
|
620
|
+
{ tier: licenseCheck.payload.tier, projectId: licenseCheck.payload.projectId }
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
391
625
|
let rawMessage = bodyObj.message;
|
|
392
626
|
if (!rawMessage && Array.isArray(bodyObj.messages) && bodyObj.messages.length > 0) {
|
|
393
627
|
const lastMsg = bodyObj.messages[bodyObj.messages.length - 1];
|
|
@@ -399,7 +633,7 @@ export function createStreamHandler(
|
|
|
399
633
|
const history = bodyObj.history || (Array.isArray(bodyObj.messages) ? bodyObj.messages.slice(0, -1) : []);
|
|
400
634
|
const { namespace, sessionId = 'default', messageId, userMessageId } = bodyObj;
|
|
401
635
|
|
|
402
|
-
//
|
|
636
|
+
// 3. Input sanitization
|
|
403
637
|
const sanitized = sanitizeInput(rawMessage ?? '');
|
|
404
638
|
if (!sanitized.ok) {
|
|
405
639
|
return new Response(
|
|
@@ -530,7 +764,19 @@ export function createStreamHandler(
|
|
|
530
764
|
}
|
|
531
765
|
|
|
532
766
|
/**
|
|
533
|
-
*
|
|
767
|
+
* Build a POST handler that ingests raw pre-parsed documents into the vector DB.
|
|
768
|
+
*
|
|
769
|
+
* Unlike `createUploadHandler` (which takes files and does parsing/splitting),
|
|
770
|
+
* this endpoint expects the caller to supply `documents[]` already split and
|
|
771
|
+
* prepared. This is useful for bulk backfill jobs, migrations, or programmatic
|
|
772
|
+
* sync pipelines.
|
|
773
|
+
*
|
|
774
|
+
* Security: Requires a valid license (RSA signature check via `enforceLicense`)
|
|
775
|
+
* AFTER JSON body parse so the optional `body.licenseKey` override is read.
|
|
776
|
+
*
|
|
777
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
778
|
+
* @param options - Optional `onAuthorize` gate.
|
|
779
|
+
* @returns Async POST handler.
|
|
534
780
|
*/
|
|
535
781
|
export function createIngestHandler(
|
|
536
782
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -543,8 +789,15 @@ export function createIngestHandler(
|
|
|
543
789
|
const authResult = await checkAuth(req, onAuthorize);
|
|
544
790
|
if (authResult) return authResult as any;
|
|
545
791
|
|
|
792
|
+
const config = plugin.getConfig();
|
|
793
|
+
|
|
546
794
|
try {
|
|
547
795
|
const body = await req.json();
|
|
796
|
+
|
|
797
|
+
// License enforcement before ingestion
|
|
798
|
+
const licenseCheck = enforceLicense(req as NextRequest, config, body?.licenseKey);
|
|
799
|
+
if (!licenseCheck.ok) return licenseCheck.response as any;
|
|
800
|
+
|
|
548
801
|
const { documents, namespace } = body as {
|
|
549
802
|
documents: Array<{ docId: string; content: string; metadata?: Record<string, unknown> }>;
|
|
550
803
|
namespace?: string;
|
|
@@ -554,6 +807,29 @@ export function createIngestHandler(
|
|
|
554
807
|
return NextResponse.json({ error: 'documents array is required' }, { status: 400 });
|
|
555
808
|
}
|
|
556
809
|
|
|
810
|
+
// Free Tier ingestion limits check
|
|
811
|
+
if (isFreeTier(licenseCheck.payload.tier)) {
|
|
812
|
+
const totalIncomingBytes = documents.reduce(
|
|
813
|
+
(sum, d) => sum + Buffer.byteLength(d.content || '', 'utf8'),
|
|
814
|
+
0
|
|
815
|
+
);
|
|
816
|
+
const ingestCheck = (FreeTierLimitsGuard as any).checkIngestionAllowed(
|
|
817
|
+
{ documentCount: documents.length },
|
|
818
|
+
totalIncomingBytes
|
|
819
|
+
);
|
|
820
|
+
if (!ingestCheck.allowed) {
|
|
821
|
+
return createPaymentRequiredResponse(
|
|
822
|
+
ingestCheck.reason || 'Free tier ingestion limit reached.',
|
|
823
|
+
{
|
|
824
|
+
tier: licenseCheck.payload.tier,
|
|
825
|
+
projectId: licenseCheck.payload.projectId,
|
|
826
|
+
documents: documents.length,
|
|
827
|
+
incomingBytes: totalIncomingBytes,
|
|
828
|
+
}
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
557
833
|
const results = await plugin.ingest(documents, namespace);
|
|
558
834
|
reportTelemetry(req, plugin, 'DOCUMENT_INGESTION', 'success', `Ingested ${documents.length} document(s)`);
|
|
559
835
|
return NextResponse.json({ results });
|
|
@@ -566,7 +842,28 @@ export function createIngestHandler(
|
|
|
566
842
|
}
|
|
567
843
|
|
|
568
844
|
/**
|
|
569
|
-
*
|
|
845
|
+
* Build a GET handler that returns health / readiness status.
|
|
846
|
+
*
|
|
847
|
+
* Response shape (200 OK):
|
|
848
|
+
* ```json
|
|
849
|
+
* {
|
|
850
|
+
* "ok": true,
|
|
851
|
+
* "status": "healthy",
|
|
852
|
+
* "sdkVersion": "x.y.z",
|
|
853
|
+
* "timestamp": "ISO string",
|
|
854
|
+
* "embedding": { "provider": "...", "ok": true },
|
|
855
|
+
* "vectorStore": { "provider": "...", "ok": true }
|
|
856
|
+
* }
|
|
857
|
+
* ```
|
|
858
|
+
*
|
|
859
|
+
* License check: This endpoint is intentionally **NOT** license-gated so
|
|
860
|
+
* orchestrators (Kubernetes, uptime probes, Vercel cron pings) can use it
|
|
861
|
+
* to assert the process is alive even when no license has been configured
|
|
862
|
+
* yet during deploy.
|
|
863
|
+
*
|
|
864
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
865
|
+
* @param options - Optional `onAuthorize` gate (rarely used here).
|
|
866
|
+
* @returns Async GET handler.
|
|
570
867
|
*/
|
|
571
868
|
export function createHealthHandler(
|
|
572
869
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -597,7 +894,19 @@ export function createHealthHandler(
|
|
|
597
894
|
}
|
|
598
895
|
|
|
599
896
|
/**
|
|
600
|
-
*
|
|
897
|
+
* Build a POST handler for the SDK-facing `/v1/license/validate` endpoint.
|
|
898
|
+
*
|
|
899
|
+
* Client flow: `LicenseValidator.validate()` calls this URL, receives
|
|
900
|
+
* authoritative DB-backed status (ACTIVE / TERMINATED / SUSPENDED / EXPIRED /
|
|
901
|
+
* REVOKED), and falls back to pure-local `LicenseVerifier.verify()` if the
|
|
902
|
+
* server is unreachable or returns an error.
|
|
903
|
+
*
|
|
904
|
+
* This handler does NOT enforce license — it is the verifier itself. It does,
|
|
905
|
+
* however, still apply rate limiting to mitigate abuse.
|
|
906
|
+
*
|
|
907
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
908
|
+
* @param options - Optional `onAuthorize` gate.
|
|
909
|
+
* @returns Async POST handler returning `LicenseValidationResponse` JSON.
|
|
601
910
|
*/
|
|
602
911
|
export function createLicenseHandler(
|
|
603
912
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -657,7 +966,21 @@ export function createLicenseHandler(
|
|
|
657
966
|
}
|
|
658
967
|
|
|
659
968
|
/**
|
|
660
|
-
*
|
|
969
|
+
* Build a POST handler that accepts `multipart/form-data` file uploads.
|
|
970
|
+
*
|
|
971
|
+
* Workflow:
|
|
972
|
+
* 1. `onAuthorize` → license enforcement via `enforceLicense` (no JSON body,
|
|
973
|
+
* so the key must come from headers or the environment).
|
|
974
|
+
* 2. Read FormData files[] list → max 25 files per request, 100 MB each.
|
|
975
|
+
* 3. Per-file: use `DocumentParser` to extract raw text by MIME type (PDF,
|
|
976
|
+
* DOCX, TXT, MD, HTML, JSON, CSV, images via OCR fallback, etc.).
|
|
977
|
+
* 4. Chunk by the configured chunker (RecursiveCharacter, semantic, etc.).
|
|
978
|
+
* 5. Call `plugin.ingest()` to embed and insert into the vector store.
|
|
979
|
+
* 6. Return JSON summary per file (status, chunks ingested, docId).
|
|
980
|
+
*
|
|
981
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
982
|
+
* @param options - Optional `onAuthorize` gate.
|
|
983
|
+
* @returns Async POST handler.
|
|
661
984
|
*/
|
|
662
985
|
export function createUploadHandler(
|
|
663
986
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -670,6 +993,11 @@ export function createUploadHandler(
|
|
|
670
993
|
const authResult = await checkAuth(req, onAuthorize);
|
|
671
994
|
if (authResult) return authResult as any;
|
|
672
995
|
|
|
996
|
+
// License enforcement (uploads use headers/env for license key, no JSON body)
|
|
997
|
+
const config = plugin.getConfig();
|
|
998
|
+
const licenseCheck = enforceLicense(req as NextRequest, config);
|
|
999
|
+
if (!licenseCheck.ok) return licenseCheck.response as any;
|
|
1000
|
+
|
|
673
1001
|
try {
|
|
674
1002
|
const formData = await req.formData();
|
|
675
1003
|
const files = formData.getAll('files') as File[];
|
|
@@ -681,6 +1009,28 @@ export function createUploadHandler(
|
|
|
681
1009
|
return NextResponse.json({ error: 'No files provided' }, { status: 400 });
|
|
682
1010
|
}
|
|
683
1011
|
|
|
1012
|
+
// Free Tier upload limits check (before parsing to fail fast on large uploads)
|
|
1013
|
+
if (isFreeTier(licenseCheck.payload.tier)) {
|
|
1014
|
+
const totalUploadBytes = files.reduce((sum, f) => sum + (f.size || 0), 0);
|
|
1015
|
+
const uploadCheck = (FreeTierLimitsGuard as any).checkIngestionAllowed(
|
|
1016
|
+
{ documentCount: files.length },
|
|
1017
|
+
totalUploadBytes
|
|
1018
|
+
);
|
|
1019
|
+
if (!uploadCheck.allowed) {
|
|
1020
|
+
return createPaymentRequiredResponse(
|
|
1021
|
+
uploadCheck.reason || 'Free tier upload limit reached.',
|
|
1022
|
+
{
|
|
1023
|
+
tier: licenseCheck.payload.tier,
|
|
1024
|
+
projectId: licenseCheck.payload.projectId,
|
|
1025
|
+
fileCount: files.length,
|
|
1026
|
+
totalUploadBytes,
|
|
1027
|
+
maxFileSizeBytes: FREE_TIER_QUOTAS.MAX_FILE_SIZE_BYTES,
|
|
1028
|
+
maxStorageBytes: FREE_TIER_QUOTAS.MAX_STORAGE_BYTES,
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
684
1034
|
const documents: { docId: string; content: string; metadata?: Record<string, unknown> }[] = [];
|
|
685
1035
|
|
|
686
1036
|
for (const file of files) {
|
|
@@ -805,8 +1155,21 @@ export function createUploadHandler(
|
|
|
805
1155
|
}
|
|
806
1156
|
|
|
807
1157
|
/**
|
|
808
|
-
*
|
|
809
|
-
*
|
|
1158
|
+
* Build a GET/POST handler for auto-suggestions (used by ChatWidget prompt chips).
|
|
1159
|
+
*
|
|
1160
|
+
* Requests:
|
|
1161
|
+
* - GET `?query=...&namespace=...`
|
|
1162
|
+
* - POST `{ query: string, namespace?: string, licenseKey?: string }`
|
|
1163
|
+
*
|
|
1164
|
+
* Response (200 OK): `{ suggestions: string[] }`
|
|
1165
|
+
*
|
|
1166
|
+
* License enforcement: Runs BEFORE body parse on headers/env, and if the POST
|
|
1167
|
+
* body contains a `licenseKey` override the check is repeated with the body
|
|
1168
|
+
* key included so the per-request override is honored.
|
|
1169
|
+
*
|
|
1170
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
1171
|
+
* @param options - Optional `onAuthorize` gate.
|
|
1172
|
+
* @returns Async handler accepting both GET and POST methods.
|
|
810
1173
|
*/
|
|
811
1174
|
export function createSuggestionsHandler(
|
|
812
1175
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -819,14 +1182,26 @@ export function createSuggestionsHandler(
|
|
|
819
1182
|
const authResult = await checkAuth(req, onAuthorize);
|
|
820
1183
|
if (authResult) return authResult as any;
|
|
821
1184
|
|
|
1185
|
+
// License enforcement for auto-suggestions endpoint
|
|
1186
|
+
const config = plugin.getConfig();
|
|
1187
|
+
const licenseCheck = enforceLicense(req, config);
|
|
1188
|
+
if (!licenseCheck.ok) return licenseCheck.response as any;
|
|
1189
|
+
|
|
822
1190
|
try {
|
|
823
1191
|
let query = '';
|
|
824
1192
|
let namespace: string | undefined = undefined;
|
|
1193
|
+
let bodyLicenseKey: string | undefined = undefined;
|
|
825
1194
|
|
|
826
1195
|
if (req.method === 'POST') {
|
|
827
1196
|
const body = await req.json().catch(() => ({}));
|
|
828
1197
|
query = body.query || '';
|
|
829
1198
|
namespace = body.namespace;
|
|
1199
|
+
bodyLicenseKey = body.licenseKey;
|
|
1200
|
+
// Re-check license if body contained a key override
|
|
1201
|
+
if (bodyLicenseKey) {
|
|
1202
|
+
const recheck = enforceLicense(req, config, bodyLicenseKey);
|
|
1203
|
+
if (!recheck.ok) return recheck.response as any;
|
|
1204
|
+
}
|
|
830
1205
|
} else {
|
|
831
1206
|
const url = new URL(req.url);
|
|
832
1207
|
query = url.searchParams.get('query') || '';
|
|
@@ -849,7 +1224,19 @@ export function createSuggestionsHandler(
|
|
|
849
1224
|
}
|
|
850
1225
|
|
|
851
1226
|
/**
|
|
852
|
-
*
|
|
1227
|
+
* Build a handler for chat-history listing and clearing.
|
|
1228
|
+
*
|
|
1229
|
+
* Routes dispatched by `createRagHandler`:
|
|
1230
|
+
* - GET `/history?sessionId=...&limit=...` → returns `{ messages: HistoryMessage[] }`
|
|
1231
|
+
* - POST `/history/clear` with `{ sessionId? }` JSON body → returns `{ ok: true }`
|
|
1232
|
+
*
|
|
1233
|
+
* Storage: Uses `DatabaseStorage` (default SQLite or custom configured driver).
|
|
1234
|
+
* License enforcement is handled by the upstream `createRagHandler` router when
|
|
1235
|
+
* this handler is composed through it; standalone usage is rare.
|
|
1236
|
+
*
|
|
1237
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
1238
|
+
* @param options - Optional `onAuthorize` gate.
|
|
1239
|
+
* @returns Async handler accepting GET (list) or POST (clear) methods.
|
|
853
1240
|
*/
|
|
854
1241
|
export function createHistoryHandler(
|
|
855
1242
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -858,17 +1245,20 @@ export function createHistoryHandler(
|
|
|
858
1245
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
859
1246
|
const storage = new DatabaseStorage(plugin.getConfig());
|
|
860
1247
|
const onAuthorize = options?.onAuthorize;
|
|
1248
|
+
const onResolveScope = options?.onResolveScope;
|
|
861
1249
|
|
|
862
1250
|
return async function handler(req: NextRequest) {
|
|
863
1251
|
const authResult = await checkAuth(req, onAuthorize);
|
|
864
1252
|
if (authResult) return authResult as any;
|
|
865
1253
|
|
|
1254
|
+
const scope = onResolveScope ? await onResolveScope(req) : undefined;
|
|
1255
|
+
|
|
866
1256
|
const url = new URL(req.url);
|
|
867
1257
|
const sessionId = url.searchParams.get('sessionId') || 'default';
|
|
868
1258
|
|
|
869
1259
|
if (req.method === 'GET') {
|
|
870
1260
|
try {
|
|
871
|
-
const history = await storage.getHistory(sessionId);
|
|
1261
|
+
const history = await storage.getHistory(sessionId, scope);
|
|
872
1262
|
return NextResponse.json({ history });
|
|
873
1263
|
} catch (err) {
|
|
874
1264
|
const message = err instanceof Error ? err.message : 'Failed to fetch history';
|
|
@@ -878,7 +1268,7 @@ export function createHistoryHandler(
|
|
|
878
1268
|
try {
|
|
879
1269
|
const body = await req.json().catch(() => ({}));
|
|
880
1270
|
const sid = body.sessionId || sessionId;
|
|
881
|
-
await storage.clearHistory(sid);
|
|
1271
|
+
await storage.clearHistory(sid, scope);
|
|
882
1272
|
return NextResponse.json({ success: true });
|
|
883
1273
|
} catch (err) {
|
|
884
1274
|
const message = err instanceof Error ? err.message : 'Failed to clear history';
|
|
@@ -890,7 +1280,17 @@ export function createHistoryHandler(
|
|
|
890
1280
|
}
|
|
891
1281
|
|
|
892
1282
|
/**
|
|
893
|
-
*
|
|
1283
|
+
* Build a POST handler for collecting thumbs-up / thumbs-down user feedback.
|
|
1284
|
+
*
|
|
1285
|
+
* Request: `{ messageId, sessionId?, rating: 1 | -1, comment?, reason? }`
|
|
1286
|
+
* Response: `{ ok: true, id: string }` (201 Created)
|
|
1287
|
+
*
|
|
1288
|
+
* Feedback is written to `DatabaseStorage.feedback` for RAG pipeline
|
|
1289
|
+
* evaluation, offline fine-tuning dataset curation, and support triage.
|
|
1290
|
+
*
|
|
1291
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
1292
|
+
* @param options - Optional `onAuthorize` gate.
|
|
1293
|
+
* @returns Async POST handler.
|
|
894
1294
|
*/
|
|
895
1295
|
export function createFeedbackHandler(
|
|
896
1296
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -899,20 +1299,23 @@ export function createFeedbackHandler(
|
|
|
899
1299
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
900
1300
|
const storage = new DatabaseStorage(plugin.getConfig());
|
|
901
1301
|
const onAuthorize = options?.onAuthorize;
|
|
1302
|
+
const onResolveScope = options?.onResolveScope;
|
|
902
1303
|
|
|
903
1304
|
return async function handler(req: NextRequest) {
|
|
904
1305
|
const authResult = await checkAuth(req, onAuthorize);
|
|
905
1306
|
if (authResult) return authResult as any;
|
|
906
1307
|
|
|
1308
|
+
const scope = onResolveScope ? await onResolveScope(req) : undefined;
|
|
1309
|
+
|
|
907
1310
|
if (req.method === 'GET') {
|
|
908
1311
|
try {
|
|
909
1312
|
const url = new URL(req.url);
|
|
910
1313
|
const messageId = url.searchParams.get('messageId');
|
|
911
1314
|
if (!messageId) {
|
|
912
|
-
const feedbackList = await storage.listFeedback();
|
|
1315
|
+
const feedbackList = await storage.listFeedback(scope);
|
|
913
1316
|
return NextResponse.json({ feedback: feedbackList });
|
|
914
1317
|
}
|
|
915
|
-
const feedback = await storage.getFeedback(messageId);
|
|
1318
|
+
const feedback = await storage.getFeedback(messageId, scope);
|
|
916
1319
|
return NextResponse.json({ feedback });
|
|
917
1320
|
} catch (err) {
|
|
918
1321
|
const message = err instanceof Error ? err.message : 'Failed to fetch feedback';
|
|
@@ -934,6 +1337,17 @@ export function createFeedbackHandler(
|
|
|
934
1337
|
if (!rating) {
|
|
935
1338
|
return NextResponse.json({ error: 'rating is required' }, { status: 400 });
|
|
936
1339
|
}
|
|
1340
|
+
if (scope && scope.length > 0) {
|
|
1341
|
+
const matches = scope.some(
|
|
1342
|
+
(pid) => pid && (sessionId === pid || sessionId.startsWith(pid) || sessionId.includes(pid))
|
|
1343
|
+
);
|
|
1344
|
+
if (!matches) {
|
|
1345
|
+
return NextResponse.json(
|
|
1346
|
+
{ error: 'sessionId is outside the authorized project scope' },
|
|
1347
|
+
{ status: 403 }
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
937
1351
|
|
|
938
1352
|
await storage.saveFeedback({ messageId, sessionId, rating, comment });
|
|
939
1353
|
return NextResponse.json({ success: true });
|
|
@@ -947,7 +1361,17 @@ export function createFeedbackHandler(
|
|
|
947
1361
|
}
|
|
948
1362
|
|
|
949
1363
|
/**
|
|
950
|
-
*
|
|
1364
|
+
* Build a GET handler that lists the set of chat sessions.
|
|
1365
|
+
*
|
|
1366
|
+
* Response: `{ sessions: { id, title, lastMessageAt, preview }[] }`
|
|
1367
|
+
*
|
|
1368
|
+
* Sessions are read from `DatabaseStorage`. Combined with `createHistoryHandler`
|
|
1369
|
+
* this enables a sidebar of prior conversations for custom UIs built on top of
|
|
1370
|
+
* the `useRagChat` headless hook.
|
|
1371
|
+
*
|
|
1372
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
1373
|
+
* @param options - Optional `onAuthorize` gate.
|
|
1374
|
+
* @returns Async GET handler returning a session listing.
|
|
951
1375
|
*/
|
|
952
1376
|
export function createSessionsHandler(
|
|
953
1377
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -956,15 +1380,16 @@ export function createSessionsHandler(
|
|
|
956
1380
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
957
1381
|
const storage = new DatabaseStorage(plugin.getConfig());
|
|
958
1382
|
const onAuthorize = options?.onAuthorize;
|
|
1383
|
+
const onResolveScope = options?.onResolveScope;
|
|
959
1384
|
|
|
960
1385
|
return async function GET(req?: NextRequest) {
|
|
961
1386
|
if (req) {
|
|
962
1387
|
const authResult = await checkAuth(req, onAuthorize);
|
|
963
1388
|
if (authResult) return authResult as any;
|
|
964
1389
|
}
|
|
965
|
-
void req;
|
|
966
1390
|
try {
|
|
967
|
-
const
|
|
1391
|
+
const scope = req && onResolveScope ? await onResolveScope(req) : undefined;
|
|
1392
|
+
const sessions = await storage.listSessions(scope);
|
|
968
1393
|
return NextResponse.json({ sessions });
|
|
969
1394
|
} catch (err) {
|
|
970
1395
|
const message = err instanceof Error ? err.message : 'Failed to list sessions';
|
|
@@ -974,8 +1399,36 @@ export function createSessionsHandler(
|
|
|
974
1399
|
}
|
|
975
1400
|
|
|
976
1401
|
/**
|
|
977
|
-
*
|
|
978
|
-
*
|
|
1402
|
+
* Build a Next.js catch-all route that dispatches to every sub-handler by path.
|
|
1403
|
+
*
|
|
1404
|
+
* Intended drop-in export:
|
|
1405
|
+
* ```ts
|
|
1406
|
+
* // app/api/retrivora/[[...retrivora]]/route.ts
|
|
1407
|
+
* import { createRagHandler } from '@retrivora-ai/rag-engine';
|
|
1408
|
+
* const { GET, POST } = createRagHandler({ /* RagConfig *\/ });
|
|
1409
|
+
* export { GET, POST };
|
|
1410
|
+
* ```
|
|
1411
|
+
*
|
|
1412
|
+
* Segments dispatched (POST unless marked GET):
|
|
1413
|
+
* - `chat` → createStreamHandler (SSE streaming chat)
|
|
1414
|
+
* - `upload` → createUploadHandler (file ingestion)
|
|
1415
|
+
* - `suggestions` → createSuggestionsHandler (GET/POST)
|
|
1416
|
+
* - `v1/license/validate` → createLicenseHandler
|
|
1417
|
+
* - `health` → createHealthHandler (GET)
|
|
1418
|
+
* - `feedback` → createFeedbackHandler
|
|
1419
|
+
* - `history` → createHistoryHandler (GET/POST)
|
|
1420
|
+
* - `sessions` → createSessionsHandler (GET)
|
|
1421
|
+
* - Any other segment → 404 with a JSON error body
|
|
1422
|
+
*
|
|
1423
|
+
* This is the **recommended** way to mount the SDK in a Next.js app. Using
|
|
1424
|
+
* any single handler individually without its license enforcement gate is a
|
|
1425
|
+
* security anti-pattern — prefer `createRagHandler` unless you have a very
|
|
1426
|
+
* specific reason to split routes up.
|
|
1427
|
+
*
|
|
1428
|
+
* @param configOrPlugin - Partial `RagConfig` or existing `VectorPlugin`.
|
|
1429
|
+
* @param options - Optional `onAuthorize` gate (propagated to every
|
|
1430
|
+
* sub-handler so auth rules apply uniformly).
|
|
1431
|
+
* @returns An object `{ GET, POST }` to re-export as your route file defaults.
|
|
979
1432
|
*/
|
|
980
1433
|
export function createRagHandler(
|
|
981
1434
|
configOrPlugin?: Partial<RagConfig> | VectorPlugin,
|
|
@@ -983,16 +1436,18 @@ export function createRagHandler(
|
|
|
983
1436
|
) {
|
|
984
1437
|
const plugin = getOrCreatePlugin(configOrPlugin);
|
|
985
1438
|
const onAuthorize = options?.onAuthorize;
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
const
|
|
991
|
-
const
|
|
992
|
-
const
|
|
993
|
-
const
|
|
994
|
-
const
|
|
995
|
-
const
|
|
1439
|
+
const onResolveScope = options?.onResolveScope;
|
|
1440
|
+
const scopeableOptions: HandlerOptions = { onAuthorize, onResolveScope };
|
|
1441
|
+
|
|
1442
|
+
const chatHandler = createChatHandler(plugin, scopeableOptions);
|
|
1443
|
+
const streamHandler = createStreamHandler(plugin, scopeableOptions);
|
|
1444
|
+
const uploadHandler = createUploadHandler(plugin, scopeableOptions);
|
|
1445
|
+
const healthHandler = createHealthHandler(plugin, scopeableOptions);
|
|
1446
|
+
const suggestionsHandler = createSuggestionsHandler(plugin, scopeableOptions);
|
|
1447
|
+
const historyHandler = createHistoryHandler(plugin, scopeableOptions);
|
|
1448
|
+
const feedbackHandler = createFeedbackHandler(plugin, scopeableOptions);
|
|
1449
|
+
const sessionsHandler = createSessionsHandler(plugin, scopeableOptions);
|
|
1450
|
+
const licenseHandler = createLicenseHandler(plugin, scopeableOptions);
|
|
996
1451
|
|
|
997
1452
|
async function routePostRequest(req: any, segment: string) {
|
|
998
1453
|
switch (segment) {
|