@retrivora-ai/rag-engine 2.2.3 → 2.2.5

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.3",
3
+ "version": "2.2.5",
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",
@@ -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;
@@ -620,7 +620,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
620
620
  const promptSources = fullSources.slice(0, 15);
621
621
  let context = promptSources.length
622
622
  ? `[SEARCH SUMMARY: Found ${totalCount} matching record(s) out of ${rawSources.length} total resources searched in database. Context includes top ${promptSources.length} relevant records]\n\n` +
623
- promptSources.map((m, i) => `[Source ${i + 1}]\n${m?.content ?? ''}`).join('\n\n---\n\n')
623
+ promptSources.map((m, i) => `[Source ${i + 1}]\n${m?.content ?? ''}`).join('\n\n---\n\n')
624
624
  : 'No relevant context found.';
625
625
 
626
626
  // Do NOT truncate UI sources — keep all qualified matches for UI citations & tables
@@ -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 () => {
@@ -938,7 +935,7 @@ You are a helpful assistant. Use the provided context to answer questions accura
938
935
  const costEst = Number(finalTrace?.tokens?.estimatedCostUsd || 0);
939
936
  const latencyDuration = Number(finalTrace?.latency?.totalMs || 0);
940
937
 
941
- console.log(`[Retrivora Pipeline Telemetry] 📊 Dispatching RAG telemetry -> Project: "${ns}", Model: "${providerName}/${modelName}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
938
+ console.log(`[Retrivora Pipeline Telemetry] 📊 Dispatching RAG telemetry -> Project: "${ns}", Tokens: ${tokenCount}, Latency: ${latencyDuration}ms`);
942
939
 
943
940
  await fetch(absoluteUrl, {
944
941
  method: 'POST',
@@ -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;
@@ -208,7 +205,7 @@ function reportTelemetry(
208
205
  details,
209
206
  };
210
207
 
211
- console.log(`[Retrivora SDK Telemetry] 📊 Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Model: "${provider}/${model}", Latency: ${latencyMs}ms`);
208
+ console.log(`[Retrivora SDK Telemetry] Dispatching telemetry -> Feature: "${action}", Status: "${status}", Project: "${projectId}", Latency: ${latencyMs}ms`);
212
209
 
213
210
  fetch(absoluteUrl, {
214
211
  method: 'POST',
@@ -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
 
@@ -766,8 +763,8 @@ export function createSuggestionsHandler(
766
763
  namespace = url.searchParams.get('namespace') || undefined;
767
764
  }
768
765
 
769
- if (typeof query !== 'string') {
770
- return NextResponse.json({ error: 'query is required' }, { status: 400 });
766
+ if (!query || typeof query !== 'string' || !query.trim()) {
767
+ return NextResponse.json({ suggestions: [] });
771
768
  }
772
769
 
773
770
  const suggestions = await plugin.getSuggestions(query, namespace);
@@ -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);
@@ -43,10 +43,7 @@ export class UniversalLLMAdapter implements ILLMProvider {
43
43
  constructor(config: LLMConfig | EmbeddingConfig) {
44
44
  this.model = config.model;
45
45
 
46
- // Type narrow to extract LLM-specific fields safely
47
46
  const llmConfig = config as LLMConfig;
48
-
49
- // Merge profile defaults if specified in options
50
47
  const options = (llmConfig.options as Record<string, unknown>) ?? {};
51
48
  let profile: Record<string, unknown> = {};
52
49
 
@@ -112,7 +109,7 @@ export class UniversalLLMAdapter implements ILLMProvider {
112
109
  };
113
110
  }
114
111
 
115
- // Try direct HTTP POST request to llm.retrivora.com first
112
+ // Tier 1: Remote HTTP POST to Retrivora Cloud Proxy (e.g. https://www.retrivora.com/api/v1/chat/completions)
116
113
  try {
117
114
  const { data } = await this.http.post(path, payload);
118
115
  const extractPath = this.opts.responseExtractPath ?? 'choices[0].message.content';
@@ -121,20 +118,9 @@ export class UniversalLLMAdapter implements ILLMProvider {
121
118
  return String(result);
122
119
  }
123
120
  } catch (httpErr: any) {
124
- console.warn(`[UniversalLLMAdapter] Direct HTTP POST to ${this.baseUrl}${path} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
125
-
121
+ // Tier 2: In-process fallback (only if HTTP call fails and server registered dispatch on globalThis)
126
122
  const _g = globalThis as any;
127
- let dispatch = _g.__retrivoraDispatchChat;
128
- if (typeof dispatch !== 'function') {
129
- try {
130
- const gateway = await import('../../../../../services/llm-gateway/router');
131
- if (gateway?.dispatchChatCompletion) {
132
- _g.__retrivoraDispatchChat = gateway.dispatchChatCompletion;
133
- _g.__retrivoraDispatchEmbedding = gateway.dispatchEmbedding;
134
- dispatch = gateway.dispatchChatCompletion;
135
- }
136
- } catch { /* proceed */ }
137
- }
123
+ const dispatch = _g.__retrivoraDispatchChat;
138
124
 
139
125
  if (typeof dispatch === 'function') {
140
126
  const res = await dispatch({
@@ -153,7 +139,6 @@ export class UniversalLLMAdapter implements ILLMProvider {
153
139
  }
154
140
 
155
141
  const { data } = await this.http.post(path, payload);
156
-
157
142
  const extractPath = this.opts.responseExtractPath ?? 'choices[0].message.content';
158
143
  const result = resolvePath(data, extractPath) ?? extractContent(data);
159
144
 
@@ -167,12 +152,10 @@ export class UniversalLLMAdapter implements ILLMProvider {
167
152
  /**
168
153
  * Streaming chat using native fetch + ReadableStream.
169
154
  * Parses OpenAI-compatible SSE frames: `data: {...}\n\n`
170
- * Works with vLLM, LMStudio, Together AI, Fireworks, and any OpenAI-compatible API.
171
155
  */
172
156
  async *chatStream(messages: ChatMessage[], context?: string): AsyncIterable<string> {
173
157
  const path = this.opts.chatPath ?? '/chat/completions';
174
158
  const url = `${this.baseUrl.replace(/\/$/, '')}${path}`;
175
- // Delta extract path — same root as chat but with delta instead of message
176
159
  const extractPath = (this.opts.responseExtractPath ?? 'choices[0].message.content')
177
160
  .replace('message.content', 'delta.content');
178
161
 
@@ -196,7 +179,6 @@ export class UniversalLLMAdapter implements ILLMProvider {
196
179
  maxTokens: this.maxTokens,
197
180
  temperature: this.temperature,
198
181
  });
199
- // Inject stream: true into the payload if it's an object
200
182
  if (typeof payload === 'object' && payload !== null) {
201
183
  (payload as Record<string, unknown>).stream = true;
202
184
  }
@@ -210,22 +192,40 @@ export class UniversalLLMAdapter implements ILLMProvider {
210
192
  };
211
193
  }
212
194
 
213
- const isSelfHost = this.baseUrl.includes('retrivora.com') || this.baseUrl.includes('localhost');
214
195
  let streamBody: ReadableStream<Uint8Array> | null = null;
215
196
 
216
- if (isSelfHost || Boolean(process.env.VERCEL)) {
217
- const _g = globalThis as any;
218
- let dispatch = _g.__retrivoraDispatchChat;
219
- if (typeof dispatch !== 'function') {
220
- try {
221
- const gateway = await import('../../../../../services/llm-gateway/router');
222
- if (gateway?.dispatchChatCompletion) {
223
- _g.__retrivoraDispatchChat = gateway.dispatchChatCompletion;
224
- _g.__retrivoraDispatchEmbedding = gateway.dispatchEmbedding;
225
- dispatch = gateway.dispatchChatCompletion;
197
+ // ── Tier 1: HTTP Stream Request to Retrivora Gateway Proxy ─────────────────
198
+ try {
199
+ const response = await fetch(url, {
200
+ method: 'POST',
201
+ headers: this.resolvedHeaders,
202
+ body: typeof payload === 'string' ? payload : JSON.stringify(payload),
203
+ });
204
+
205
+ if (response.ok) {
206
+ const contentType = response.headers.get('content-type') || '';
207
+ if (contentType.includes('application/json')) {
208
+ const json = await response.json();
209
+ const text = (resolvePath(json, extractPath) as string | undefined) ?? extractContent(json);
210
+ if (text && typeof text === 'string') {
211
+ yield text;
212
+ return;
226
213
  }
227
- } catch { /* proceed */ }
214
+ } else if (response.body) {
215
+ streamBody = response.body;
216
+ }
217
+ } else {
218
+ const errorText = await response.text().catch(() => response.statusText);
219
+ console.warn(`[UniversalLLMAdapter] Remote HTTP stream returned ${response.status}: ${errorText}. Attempting in-process fallback...`);
228
220
  }
221
+ } catch (fetchErr: any) {
222
+ console.warn(`[UniversalLLMAdapter] Remote HTTP stream fetch warning: ${fetchErr?.message}. Attempting in-process fallback...`);
223
+ }
224
+
225
+ // ── Tier 2: In-process Dispatch Fallback (if remote HTTP fails) ────────────
226
+ if (!streamBody) {
227
+ const _g = globalThis as any;
228
+ const dispatch = _g.__retrivoraDispatchChat;
229
229
 
230
230
  if (typeof dispatch === 'function') {
231
231
  try {
@@ -245,32 +245,15 @@ export class UniversalLLMAdapter implements ILLMProvider {
245
245
  yield content;
246
246
  return;
247
247
  }
248
- throw new Error(`[UniversalLLMAdapter] In-process dispatch stream returned empty response for model: ${this.model}`);
249
248
  }
250
249
  } catch (dispatchErr) {
251
- throw dispatchErr;
250
+ console.warn('[UniversalLLMAdapter] In-process dispatch error:', dispatchErr);
252
251
  }
253
- } else {
254
- throw new Error(`[UniversalLLMAdapter] In-process gateway dispatch not registered. Direct self-referential HTTP calls to ${this.baseUrl} are disabled on Vercel to prevent HTTP 508 Loop Detected.`);
255
252
  }
256
253
  }
257
254
 
258
255
  if (!streamBody) {
259
- const response = await fetch(url, {
260
- method: 'POST',
261
- headers: this.resolvedHeaders,
262
- body: JSON.stringify(payload),
263
- });
264
-
265
- if (!response.ok) {
266
- const errorText = await response.text().catch(() => response.statusText);
267
- throw new Error(`[UniversalLLMAdapter] Streaming request failed (${response.status}): ${errorText}`);
268
- }
269
-
270
- if (!response.body) {
271
- throw new Error('[UniversalLLMAdapter] Response body is null — server did not send a streaming response.');
272
- }
273
- streamBody = response.body;
256
+ throw new Error(`[UniversalLLMAdapter] Streaming request failed. Ensure RETRIVORA_LICENSE_KEY is valid or LLM service is available.`);
274
257
  }
275
258
 
276
259
  const reader = streamBody.getReader();
@@ -296,12 +279,11 @@ export class UniversalLLMAdapter implements ILLMProvider {
296
279
  const text = (resolvePath(json, extractPath) as string | undefined) ?? extractContent(json);
297
280
  if (text && typeof text === 'string') yield text;
298
281
  } catch {
299
- // Malformed SSE line — skip
282
+ // Skip malformed SSE lines
300
283
  }
301
284
  }
302
285
  }
303
286
 
304
- // Flush remaining buffer
305
287
  if (buffer.trim() && buffer.trim() !== 'data: [DONE]') {
306
288
  const jsonStr = buffer.replace(/^data:\s*/, '').trim();
307
289
  try {
@@ -317,85 +299,52 @@ export class UniversalLLMAdapter implements ILLMProvider {
317
299
 
318
300
  async embed(text: string): Promise<number[]> {
319
301
  const path = this.opts.embedPath ?? '/embeddings';
302
+ const payload = this.opts.embedPayloadTemplate
303
+ ? buildPayload(this.opts.embedPayloadTemplate, { input: text, model: this.model })
304
+ : { input: text, model: this.model };
320
305
 
321
- let payload: unknown;
322
- if (this.opts.embedPayloadTemplate) {
323
- payload = buildPayload(this.opts.embedPayloadTemplate, {
324
- model: this.model,
325
- input: text,
326
- });
327
- } else {
328
- payload = {
329
- model: this.model,
330
- input: text,
331
- };
332
- }
333
-
334
- // Try direct HTTP POST request to llm.retrivora.com first
335
306
  try {
336
307
  const { data } = await this.http.post(path, payload);
337
308
  const extractPath = this.opts.embedExtractPath ?? 'data[0].embedding';
338
- const vector = resolvePath(data, extractPath);
339
- if (Array.isArray(vector)) {
340
- return vector as number[];
341
- }
342
- } catch (httpErr: any) {
343
- console.warn(`[UniversalLLMAdapter] Direct HTTP embedding POST to ${this.baseUrl}${path} failed (${httpErr.message}). Attempting in-process gateway dispatch fallback...`);
344
-
309
+ const vector = resolvePath(data, extractPath) as number[] | undefined;
310
+ if (Array.isArray(vector)) return vector;
311
+ } catch {
312
+ // In-process fallback for embeddings
345
313
  const _g = globalThis as any;
346
- let dispatch = _g.__retrivoraDispatchEmbedding;
347
- if (typeof dispatch !== 'function') {
348
- try {
349
- const gateway = await import('../../../../../services/llm-gateway/router');
350
- if (gateway?.dispatchEmbedding) {
351
- _g.__retrivoraDispatchChat = gateway.dispatchChatCompletion;
352
- _g.__retrivoraDispatchEmbedding = gateway.dispatchEmbedding;
353
- dispatch = gateway.dispatchEmbedding;
354
- }
355
- } catch { /* proceed */ }
356
- }
357
-
358
- if (typeof dispatch === 'function') {
359
- const res = await dispatch({
360
- model: this.model,
361
- input: text,
362
- }, this.apiKey);
314
+ const dispatchEmbed = _g.__retrivoraDispatchEmbedding;
363
315
 
316
+ if (typeof dispatchEmbed === 'function') {
317
+ const res = await dispatchEmbed({ input: text, model: this.model }, this.apiKey);
364
318
  if (res?.data?.[0]?.embedding) {
365
319
  return res.data[0].embedding;
366
320
  }
367
321
  }
368
- throw httpErr;
369
322
  }
370
323
 
371
324
  const { data } = await this.http.post(path, payload);
372
-
373
325
  const extractPath = this.opts.embedExtractPath ?? 'data[0].embedding';
374
- const vector = resolvePath(data, extractPath);
326
+ const vector = resolvePath(data, extractPath) as number[] | undefined;
375
327
 
376
328
  if (!Array.isArray(vector)) {
377
- throw new Error(`[UniversalLLMAdapter] Expected a number array at '${extractPath}' for embeddings.`);
329
+ throw new Error(`[UniversalLLMAdapter] Could not extract embedding vector from path '${extractPath}' in response.`);
378
330
  }
379
331
 
380
332
  return vector;
381
333
  }
382
334
 
383
335
  async batchEmbed(texts: string[]): Promise<number[][]> {
384
- const vectors: number[][] = [];
336
+ const results: number[][] = [];
385
337
  for (const text of texts) {
386
- vectors.push(await this.embed(text));
338
+ results.push(await this.embed(text));
387
339
  }
388
- return vectors;
340
+ return results;
389
341
  }
390
342
 
391
343
  async ping(): Promise<boolean> {
392
344
  try {
393
- if (this.opts.pingPath) {
394
- await this.http.get(this.opts.pingPath);
395
- }
345
+ await this.embed('ping');
396
346
  return true;
397
- } catch (err) {
398
- console.error('[UniversalLLMAdapter] Ping failed:', err);
347
+ } catch {
399
348
  return false;
400
349
  }
401
350
  }