@juspay/neurolink 12.14.21 → 12.15.0

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +3 -7
  2. package/dist/browser/neurolink.min.js +385 -385
  3. package/dist/core/baseProvider.d.ts +6 -3
  4. package/dist/core/baseProvider.js +7 -3
  5. package/dist/providers/amazonBedrock/client.d.ts +7 -5
  6. package/dist/providers/amazonBedrock/client.js +125 -16
  7. package/dist/providers/cohere.d.ts +2 -2
  8. package/dist/providers/cohere.js +5 -1
  9. package/dist/providers/googleAiStudio/client.d.ts +3 -3
  10. package/dist/providers/googleAiStudio/client.js +6 -2
  11. package/dist/providers/googleVertex/client.d.ts +2 -2
  12. package/dist/providers/googleVertex/client.js +5 -1
  13. package/dist/providers/jina.d.ts +2 -2
  14. package/dist/providers/jina.js +5 -1
  15. package/dist/providers/litellm/client.d.ts +2 -2
  16. package/dist/providers/litellm/client.js +5 -1
  17. package/dist/providers/ollama/client.d.ts +2 -2
  18. package/dist/providers/ollama/client.js +5 -1
  19. package/dist/providers/openAI/client.d.ts +3 -3
  20. package/dist/providers/openAI/client.js +6 -2
  21. package/dist/providers/voyage.d.ts +2 -2
  22. package/dist/providers/voyage.js +5 -1
  23. package/dist/rag/document/MDocument.d.ts +19 -1
  24. package/dist/rag/document/MDocument.js +30 -3
  25. package/dist/rag/document/imageLoader.d.ts +55 -0
  26. package/dist/rag/document/imageLoader.js +183 -0
  27. package/dist/rag/index.d.ts +1 -0
  28. package/dist/rag/index.js +2 -0
  29. package/dist/rag/pipeline/RAGPipeline.d.ts +54 -1
  30. package/dist/rag/pipeline/RAGPipeline.js +358 -1
  31. package/dist/rag/ragIntegration.js +146 -25
  32. package/dist/types/providers.d.ts +21 -1
  33. package/dist/types/rag.d.ts +144 -0
  34. package/dist/types/rag.js +12 -1
  35. package/package.json +2 -1
@@ -3,8 +3,11 @@ import { extname, resolve } from "path";
3
3
  import { z } from "zod";
4
4
  import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
5
5
  import { logger } from "../utils/logger.js";
6
+ import { redactUrlForError } from "../utils/logSanitize.js";
6
7
  import { createChunker } from "./ChunkerFactory.js";
7
8
  import { createVectorQueryTool, InMemoryVectorStore, } from "./retrieval/vectorQueryTool.js";
9
+ import { ImageLoader } from "./document/imageLoader.js";
10
+ import { IMAGE_EXTENSIONS } from "../processors/config/index.js";
8
11
  import { withSpan } from "../telemetry/withSpan.js";
9
12
  import { tracers } from "../telemetry/tracers.js";
10
13
  /**
@@ -43,6 +46,8 @@ function detectStrategy(filePath) {
43
46
  const ext = extname(filePath).toLowerCase();
44
47
  return EXTENSION_TO_STRATEGY[ext] || "recursive";
45
48
  }
49
+ /** Embedding dimension used by {@link generateSimpleEmbedding}. */
50
+ const EMBEDDING_DIMENSION = 128;
46
51
  /**
47
52
  * Simple hash function for strings (FNV-1a variant).
48
53
  * Maps a word to a bucket index deterministically.
@@ -177,14 +182,62 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
177
182
  if (!files || files.length === 0) {
178
183
  throw new Error("RAG config requires at least one file path in 'files'");
179
184
  }
180
- // 1. Load files
185
+ // 1. Load files — separate text and image files
181
186
  const fileContents = [];
187
+ const imageFiles = [];
182
188
  for (const filePath of files) {
189
+ // HTTP(S) sources are handled by ImageLoader directly and must not go
190
+ // through resolve()/existsSync(), which would mangle the URL into a
191
+ // bogus local path and always report it as missing.
192
+ if (/^https?:\/\//i.test(filePath)) {
193
+ // `new URL()` throws on a string the guarding regex still accepts —
194
+ // "https://" is the minimal case. Uncaught, it leaves this loop, is
195
+ // rethrown by prepareRAGTool, and disables RAG for the WHOLE call, so
196
+ // one bad entry discards every file already loaded. Skip the source
197
+ // instead, matching how an unreadable local file is handled below.
198
+ let ext;
199
+ try {
200
+ ext = extname(new URL(filePath).pathname).toLowerCase();
201
+ }
202
+ catch {
203
+ logger.warn(`[RAG] Malformed URL source, skipping: ${redactUrlForError(filePath)}`);
204
+ continue;
205
+ }
206
+ if (ext === ".svg") {
207
+ // IMAGE_EXTENSIONS includes .svg, but every consumer downstream of
208
+ // here treats an image as raster bytes: ImageLoader returns the file
209
+ // verbatim, captioning base64s it into a vision call, and the Bedrock
210
+ // embed path ships it as-is — Nova rejects the format outright and
211
+ // Titan accepts markup it cannot embed meaningfully. Skipping is
212
+ // honest; supporting it needs a rasterize-or-sanitize step in
213
+ // ImageLoader, not a classification change here.
214
+ logger.warn(`[RAG] SVG is not supported as a RAG image source, skipping: ${redactUrlForError(filePath)}`);
215
+ continue;
216
+ }
217
+ if (IMAGE_EXTENSIONS.includes(ext)) {
218
+ imageFiles.push({ path: filePath, ext });
219
+ }
220
+ else {
221
+ logger.warn(`[RAG] Unsupported URL source, skipping: ${redactUrlForError(filePath)}`);
222
+ }
223
+ continue;
224
+ }
183
225
  const resolvedPath = resolve(filePath);
184
226
  if (!existsSync(resolvedPath)) {
185
227
  logger.warn(`[RAG] File not found, skipping: ${resolvedPath}`);
186
228
  continue;
187
229
  }
230
+ // Detect image files by extension
231
+ const ext = extname(resolvedPath).toLowerCase();
232
+ if (ext === ".svg") {
233
+ // See the URL branch above for why SVG is excluded from the raster path.
234
+ logger.warn(`[RAG] SVG is not supported as a RAG image source, skipping: ${resolvedPath}`);
235
+ continue;
236
+ }
237
+ if (IMAGE_EXTENSIONS.includes(ext)) {
238
+ imageFiles.push({ path: resolvedPath, ext });
239
+ continue;
240
+ }
188
241
  try {
189
242
  const content = readFileSync(resolvedPath, "utf-8");
190
243
  const strategy = userStrategy || detectStrategy(resolvedPath);
@@ -195,11 +248,12 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
195
248
  }
196
249
  }
197
250
  // Auto-increase topK for multi-file scenarios to ensure coverage
198
- // (computed after loading so it reflects only files that actually exist)
199
- const topK = fileContents.length > 1
200
- ? Math.max(userTopK, fileContents.length * 3)
201
- : userTopK;
202
- if (fileContents.length === 0) {
251
+ // (computed after loading so it reflects only files that actually exist).
252
+ // Image files count toward the file total so image-only RAG gets a topK
253
+ // that covers every source.
254
+ const totalSources = fileContents.length + imageFiles.length;
255
+ const topK = totalSources > 1 ? Math.max(userTopK, totalSources * 3) : userTopK;
256
+ if (fileContents.length === 0 && imageFiles.length === 0) {
203
257
  throw new Error("RAG: No files could be loaded. Check that file paths exist and are readable.");
204
258
  }
205
259
  logger.info(`[RAG] Loaded ${fileContents.length} files for indexing`);
@@ -230,9 +284,44 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
230
284
  });
231
285
  }
232
286
  }
233
- logger.info(`[RAG] Created ${allChunks.length} chunks from ${fileContents.length} files`);
287
+ logger.info(`[RAG] Created ${allChunks.length} chunks from ${fileContents.length} text files`);
288
+ // 2b. Load image files (multi-modal RAG).
289
+ //
290
+ // Deliberately NOT embedded here. Images are vectorised below through the
291
+ // same `embedFn` as the text chunks, because index-wide a single embedding
292
+ // space is the whole precondition for similarity to mean anything. Embedding
293
+ // them here — as this path originally did, with a hash of the caption —
294
+ // produced a 128-dimension image vector sitting in an index whose text
295
+ // vectors came from a provider at 768 or 1536, which is not a worse ranking
296
+ // but an incomparable one.
297
+ const imageChunks = [];
298
+ if (imageFiles.length > 0) {
299
+ const imageLoader = new ImageLoader();
300
+ for (const { path: imgPath } of imageFiles) {
301
+ try {
302
+ const imageDoc = await imageLoader.load(imgPath);
303
+ // Use filename-based text representation for simple embedding
304
+ const imageText = imageDoc.text;
305
+ imageChunks.push({
306
+ id: `img-chunk-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
307
+ text: imageText,
308
+ metadata: {
309
+ text: imageText,
310
+ source: imgPath,
311
+ hasImage: true,
312
+ mimeType: imageDoc.mimeType,
313
+ imageWidth: imageDoc.metadata.width,
314
+ imageHeight: imageDoc.metadata.height,
315
+ },
316
+ });
317
+ }
318
+ catch (error) {
319
+ logger.warn(`[RAG] Failed to load image: ${imgPath}: ${error instanceof Error ? error.message : String(error)}`);
320
+ }
321
+ }
322
+ logger.info(`[RAG] Loaded ${imageChunks.length} image files for multi-modal RAG`);
323
+ }
234
324
  // 3. Generate embeddings and store in vector store
235
- const EMBEDDING_DIMENSION = 128;
236
325
  const vectorStore = new InMemoryVectorStore();
237
326
  const indexName = "rag-index";
238
327
  // When the caller configured an embedding provider/model, embed BOTH the
@@ -262,9 +351,19 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
262
351
  logger.warn("[RAG] Failed to create embedding provider; falling back to hash embeddings", { error: error instanceof Error ? error.message : String(error) });
263
352
  }
264
353
  }
354
+ // Text and image captions embed together, in one call and under one
355
+ // fallback. Splitting them into two try/catch blocks would let the text side
356
+ // succeed on the provider while the image side fell back to the hash, which
357
+ // is the mixed-space index this is trying to prevent.
265
358
  let chunkVectors;
359
+ let imageVectors;
266
360
  try {
267
- chunkVectors = await Promise.all(allChunks.map((chunk) => embedFn(chunk.text)));
361
+ const allVectors = await Promise.all([
362
+ ...allChunks.map((chunk) => embedFn(chunk.text)),
363
+ ...imageChunks.map((chunk) => embedFn(chunk.text)),
364
+ ]);
365
+ chunkVectors = allVectors.slice(0, allChunks.length);
366
+ imageVectors = allVectors.slice(allChunks.length);
268
367
  }
269
368
  catch (error) {
270
369
  // One failed chunk must not leave a mixed-space index — flip the whole
@@ -272,17 +371,36 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
272
371
  logger.warn("[RAG] Provider embedding failed mid-index; falling back to hash embeddings for index and queries", { error: error instanceof Error ? error.message : String(error) });
273
372
  embedFn = (text) => Promise.resolve(generateSimpleEmbedding(text, EMBEDDING_DIMENSION));
274
373
  chunkVectors = allChunks.map((chunk) => generateSimpleEmbedding(chunk.text, EMBEDDING_DIMENSION));
374
+ imageVectors = imageChunks.map((chunk) => generateSimpleEmbedding(chunk.text, EMBEDDING_DIMENSION));
375
+ }
376
+ const items = [
377
+ ...allChunks.map((chunk, i) => ({
378
+ id: `rag-chunk-${i}`,
379
+ vector: chunkVectors[i],
380
+ metadata: {
381
+ text: chunk.text,
382
+ ...chunk.metadata,
383
+ },
384
+ })),
385
+ ...imageChunks.map((chunk, i) => ({
386
+ id: chunk.id,
387
+ vector: imageVectors[i],
388
+ metadata: chunk.metadata,
389
+ })),
390
+ ];
391
+ // The guard near the top of this function fires when no source was found
392
+ // at all. This one covers the other way to end up with an empty index:
393
+ // sources WERE found and every one of them failed to load — an image over
394
+ // the size ceiling, a corrupt file, an unreachable URL. Each of those is
395
+ // caught and warned individually, so without this the call returns
396
+ // `chunksIndexed: 0` alongside a positive `filesLoaded` and reports
397
+ // success, and the caller gets a silently empty knowledge base.
398
+ if (items.length === 0) {
399
+ throw new Error("RAG: every source failed to load, so nothing was indexed. " +
400
+ "Check the warnings above for the per-source reason.");
275
401
  }
276
- const items = allChunks.map((chunk, i) => ({
277
- id: `rag-chunk-${i}`,
278
- vector: chunkVectors[i],
279
- metadata: {
280
- text: chunk.text,
281
- ...chunk.metadata,
282
- },
283
- }));
284
402
  await vectorStore.upsert(indexName, items);
285
- logger.info(`[RAG] Indexed ${items.length} chunks in vector store`);
403
+ logger.info(`[RAG] Indexed ${items.length} chunks (${allChunks.length} text + ${imageChunks.length} images) in vector store`);
286
404
  // 4. Create the search tool
287
405
  // Determine embedding provider/model for the query tool
288
406
  const provider = embeddingProvider || fallbackProvider || "vertex";
@@ -315,15 +433,16 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
315
433
  // Query through the same embedding space the index was built in —
316
434
  // provider embeddings when configured (and healthy), else the hash.
317
435
  const queryEmbedding = await embedFn(query).catch(() => generateSimpleEmbedding(query, EMBEDDING_DIMENSION));
318
- // Fetch more candidates than needed so diversity can select across files
319
- const fetchK = fileContents.length > 1 ? topK * 3 : topK;
436
+ // Fetch more candidates than needed so diversity can select across files and images
437
+ const loadedSources = fileContents.length + imageChunks.length;
438
+ const fetchK = loadedSources > 1 ? topK * 3 : topK;
320
439
  const rawResults = await vectorStore.query({
321
440
  indexName,
322
441
  queryVector: queryEmbedding,
323
442
  topK: fetchK,
324
443
  });
325
- // Apply source-file diversity for multi-file RAG
326
- const results = fileContents.length > 1
444
+ // Apply source-file diversity for multi-file/multi-image RAG
445
+ const results = loadedSources > 1
327
446
  ? diversifyResults(rawResults, topK)
328
447
  : rawResults.slice(0, topK);
329
448
  if (results.length === 0) {
@@ -335,7 +454,8 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
335
454
  };
336
455
  }
337
456
  const relevantContext = results
338
- .map((r, i) => `[${i + 1}] ${r.metadata?.text || r.text || ""}`)
457
+ .map((r, i) => `[${i + 1}] ${r.metadata?.text || r.text || ""}` +
458
+ (r.metadata?.hasImage ? " [image]" : ""))
339
459
  .join("\n\n");
340
460
  span.setAttribute("rag.results_count", results.length);
341
461
  return {
@@ -345,6 +465,7 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
345
465
  score: r.score,
346
466
  source: r.metadata?.source,
347
467
  text: (r.metadata?.text || r.text || "").slice(0, 200),
468
+ hasImage: r.metadata?.hasImage ?? false,
348
469
  })),
349
470
  totalResults: results.length,
350
471
  };
@@ -354,8 +475,8 @@ async function _prepareRAGToolInner(ragConfig, fallbackProvider) {
354
475
  return {
355
476
  tool: aiTool,
356
477
  toolName,
357
- chunksIndexed: allChunks.length,
358
- filesLoaded: fileContents.length,
478
+ chunksIndexed: allChunks.length + imageChunks.length,
479
+ filesLoaded: fileContents.length + imageFiles.length,
359
480
  };
360
481
  }
361
482
  /** @internal Exported for testing only */
@@ -28,6 +28,18 @@ export type SupportedModelName = BedrockModels | DeepSeekModels | OpenAIModels |
28
28
  * Extract provider names from enum
29
29
  */
30
30
  export type ProviderName = (typeof AIProviderName)[keyof typeof AIProviderName];
31
+ /**
32
+ * Multi-modal embedding input — accepts text, image, or both.
33
+ * Used by providers that support multi-modal embeddings (e.g. Bedrock Titan Image, Nova Multimodal).
34
+ */
35
+ export type EmbedInput = {
36
+ /** Text content to embed */
37
+ text?: string;
38
+ /** Image data as Buffer or base64 string */
39
+ image?: Buffer | string;
40
+ /** MIME type of the image (e.g. "image/png", "image/jpeg") */
41
+ mimeType?: string;
42
+ };
31
43
  /**
32
44
  * Provider status information
33
45
  */
@@ -741,7 +753,15 @@ export type AIProvider = {
741
753
  stream(optionsOrPrompt: StreamOptions | string, analysisSchema?: ValidationSchema): Promise<StreamResult>;
742
754
  generate(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>;
743
755
  gen(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>;
744
- embed(text: string, modelName?: string): Promise<number[]>;
756
+ /**
757
+ * Generate an embedding vector for text or multi-modal input.
758
+ * Accepts either a plain string (text-only) or an EmbedInput object
759
+ * for multi-modal embeddings (text + image).
760
+ */
761
+ embed(input: string | EmbedInput, modelName?: string): Promise<number[]>;
762
+ /**
763
+ * Generate embedding vectors for multiple text inputs in batch.
764
+ */
745
765
  embedMany(texts: string[], modelName?: string): Promise<number[][]>;
746
766
  setupToolExecutor(sdk: {
747
767
  customTools: Map<string, unknown>;
@@ -1,4 +1,5 @@
1
1
  import type { Tool } from "./tools.js";
2
+ import type { EmbedInput } from "./providers.js";
2
3
  /**
3
4
  * RAG Type Definitions
4
5
  *
@@ -231,6 +232,8 @@ export type RAGPipelineConfig = {
231
232
  enableReranking?: boolean;
232
233
  /** Reranking model configuration */
233
234
  rerankingModel?: EmbeddingModelConfig;
235
+ /** Multi-modal RAG configuration (image + text embeddings) */
236
+ multiModal?: MultiModalRAGConfig;
234
237
  };
235
238
  /**
236
239
  * Ingestion options
@@ -419,6 +422,18 @@ export type VectorStore = {
419
422
  includeVectors?: boolean;
420
423
  }): Promise<VectorQueryResult[]>;
421
424
  };
425
+ /**
426
+ * Structural extension of {@link VectorStore} for stores that can also write
427
+ * vectors via `upsert()` (e.g. {@link InMemoryVectorStore}). Custom stores
428
+ * that expose an `upsert` method satisfy this type without a cast.
429
+ */
430
+ export type UpsertableVectorStore = VectorStore & {
431
+ upsert(indexName: string, items: Array<{
432
+ id: string;
433
+ vector: number[];
434
+ metadata?: Record<string, unknown>;
435
+ }>): Promise<void>;
436
+ };
422
437
  /** Minimal shape of a query result, satisfied by both `pg` and `pglite`. */
423
438
  export type PgQueryResult<T = unknown> = {
424
439
  rows: T[];
@@ -1330,3 +1345,132 @@ export type DocumentState = {
1330
1345
  };
1331
1346
  /** Canonical RAG error code. */
1332
1347
  export type RAGErrorCode = "RAG_CHUNKING_ERROR" | "RAG_CHUNKING_INVALID_CONFIG" | "RAG_CHUNKING_STRATEGY_NOT_FOUND" | "RAG_CHUNKING_EMPTY_CONTENT" | "RAG_CHUNKING_SIZE_EXCEEDED" | "RAG_METADATA_EXTRACTION_ERROR" | "RAG_METADATA_EXTRACTION_TIMEOUT" | "RAG_METADATA_SCHEMA_INVALID" | "RAG_METADATA_EXTRACTOR_NOT_FOUND" | "RAG_EMBEDDING_ERROR" | "RAG_EMBEDDING_DIMENSION_MISMATCH" | "RAG_EMBEDDING_RATE_LIMIT" | "RAG_EMBEDDING_PROVIDER_ERROR" | "RAG_VECTOR_QUERY_ERROR" | "RAG_VECTOR_QUERY_TIMEOUT" | "RAG_VECTOR_STORE_UNAVAILABLE" | "RAG_VECTOR_STORE_CONNECTION_ERROR" | "RAG_VECTOR_INDEX_NOT_FOUND" | "RAG_RERANKER_ERROR" | "RAG_RERANKER_NOT_FOUND" | "RAG_RERANKER_API_ERROR" | "RAG_GRAPH_ERROR" | "RAG_GRAPH_TRAVERSAL_ERROR" | "RAG_GRAPH_NODE_NOT_FOUND" | "RAG_PIPELINE_ERROR" | "RAG_PIPELINE_STAGE_FAILED" | "RAG_PIPELINE_PARTIAL_FAILURE" | "RAG_CIRCUIT_BREAKER_OPEN" | "RAG_CIRCUIT_BREAKER_HALF_OPEN_LIMIT" | "RAG_OPERATION_TIMEOUT" | "RAG_RETRY_EXHAUSTED" | "RAG_INVALID_CONFIGURATION";
1348
+ /**
1349
+ * Modality of an embedding model
1350
+ */
1351
+ export type EmbeddingModality = "text" | "image" | "multimodal";
1352
+ /**
1353
+ * Multi-modal embedding model configuration
1354
+ */
1355
+ export type MultiModalEmbeddingConfig = {
1356
+ /** Provider name (e.g. "bedrock") */
1357
+ provider: string;
1358
+ /** Model name (e.g. "amazon.titan-embed-image-v1") */
1359
+ modelName: string;
1360
+ /** What modalities this model supports */
1361
+ modality: EmbeddingModality;
1362
+ /** Embedding dimension (e.g. 1024 for Titan Image) */
1363
+ dimensions?: number;
1364
+ };
1365
+ /**
1366
+ * Image metadata attached to multi-modal chunks
1367
+ */
1368
+ export type ImageChunkMetadata = {
1369
+ /** Image width in pixels */
1370
+ width?: number;
1371
+ /** Image height in pixels */
1372
+ height?: number;
1373
+ /** Image MIME type */
1374
+ mimeType?: string;
1375
+ /** Image format (jpeg, png, webp, etc.) */
1376
+ format?: string;
1377
+ /** Original file path or URL */
1378
+ source?: string;
1379
+ /** Whether this chunk contains image data */
1380
+ hasImage: boolean;
1381
+ };
1382
+ /**
1383
+ * Extended chunk type for multi-modal RAG.
1384
+ * Contains optional image data alongside text for image+text search.
1385
+ */
1386
+ export type MultiModalChunk = Chunk & {
1387
+ /** Image data as Buffer or base64 string (present for image chunks) */
1388
+ image?: Buffer | string;
1389
+ /** Image MIME type */
1390
+ imageMimeType?: string;
1391
+ /** Image-specific metadata */
1392
+ imageMeta?: ImageChunkMetadata;
1393
+ };
1394
+ /**
1395
+ * Multi-modal query input — can be text, image, or both.
1396
+ * Same shape as EmbedInput.
1397
+ */
1398
+ export type MultiModalQuery = EmbedInput;
1399
+ /**
1400
+ * Multi-modal RAG pipeline configuration extension.
1401
+ * Passed alongside RAGPipelineConfig to enable multi-modal features.
1402
+ */
1403
+ export type MultiModalRAGConfig = {
1404
+ /** Whether multi-modal RAG is enabled */
1405
+ enabled: boolean;
1406
+ /** Embedding model for multi-modal content */
1407
+ embeddingModel: MultiModalEmbeddingConfig;
1408
+ /** How to generate text from images for indexing: "caption" uses LLM vision, "filename" uses filename, "none" skips text */
1409
+ imageTextStrategy?: "caption" | "filename" | "none";
1410
+ /** Maximum image file size in bytes (default: 10MB) */
1411
+ maxImageSize?: number;
1412
+ /** Supported image MIME types */
1413
+ supportedFormats?: string[];
1414
+ /** Provider for generating image captions (used with imageTextStrategy: "caption") */
1415
+ captionProvider?: string;
1416
+ /** Model for generating image captions */
1417
+ captionModel?: string;
1418
+ };
1419
+ /**
1420
+ * Match type indicating how the result was found
1421
+ */
1422
+ export type MultiModalMatchType = "text-to-text" | "text-to-image" | "image-to-image" | "image-to-text";
1423
+ /**
1424
+ * Image search result from multi-modal retrieval
1425
+ */
1426
+ export type MultiModalSearchResult = {
1427
+ /** The chunk containing the match */
1428
+ chunk: MultiModalChunk;
1429
+ /** Similarity score (0-1) */
1430
+ score: number;
1431
+ /** How this match was found */
1432
+ matchType: MultiModalMatchType;
1433
+ };
1434
+ /**
1435
+ * Supported image MIME types for multi-modal RAG
1436
+ */
1437
+ export declare const SUPPORTED_IMAGE_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/gif", "image/webp", "image/bmp", "image/tiff", "image/avif"];
1438
+ /**
1439
+ * Image document loaded for RAG ingestion
1440
+ */
1441
+ export type ImageDocument = {
1442
+ /** Unique identifier for this image document */
1443
+ id: string;
1444
+ /** Text representation (filename, alt text, or caption) */
1445
+ text: string;
1446
+ /** Raw image data */
1447
+ image: Buffer;
1448
+ /** Image MIME type */
1449
+ mimeType: string;
1450
+ /** Metadata about the image */
1451
+ metadata: {
1452
+ /** Original file path or URL */
1453
+ source: string;
1454
+ /** Image width in pixels (if detectable) */
1455
+ width?: number;
1456
+ /** Image height in pixels (if detectable) */
1457
+ height?: number;
1458
+ /** Image format (jpeg, png, webp, etc.) */
1459
+ format?: string;
1460
+ /** File size in bytes */
1461
+ size: number;
1462
+ };
1463
+ };
1464
+ /**
1465
+ * Options for loading images
1466
+ */
1467
+ export type ImageLoaderOptions = {
1468
+ /** Custom metadata to attach to the image document */
1469
+ metadata?: Record<string, unknown>;
1470
+ /** Maximum image file size in bytes (default: 10MB) */
1471
+ maxImageSize?: number;
1472
+ /** Timeout for URL fetches in milliseconds (default: 30000) */
1473
+ fetchTimeout?: number;
1474
+ /** Custom headers for URL fetches */
1475
+ headers?: Record<string, string>;
1476
+ };
package/dist/types/rag.js CHANGED
@@ -1 +1,12 @@
1
- export {};
1
+ /**
2
+ * Supported image MIME types for multi-modal RAG
3
+ */
4
+ export const SUPPORTED_IMAGE_MIME_TYPES = [
5
+ "image/jpeg",
6
+ "image/png",
7
+ "image/gif",
8
+ "image/webp",
9
+ "image/bmp",
10
+ "image/tiff",
11
+ "image/avif",
12
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.14.21",
3
+ "version": "12.15.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {
@@ -92,6 +92,7 @@
92
92
  "test:media": "pnpm exec tsx test/continuous-test-suite-media-gen.ts",
93
93
  "test:media-registry-collisions": "pnpm exec tsx test/continuous-test-suite-media-registry-collisions.ts",
94
94
  "test:memory": "pnpm exec tsx test/continuous-test-suite-memory.ts",
95
+ "test:multimodal-rag": "pnpm exec tsx test/continuous-test-suite-multimodal-rag.ts",
95
96
  "test:proxy-sharing": "pnpm run build && pnpm exec tsx test/continuous-test-suite-proxy-sharing.ts",
96
97
  "test:openai-compat-streaming-retry": "pnpm exec tsx test/continuous-test-suite-openai-compat-streaming-retry.ts",
97
98
  "test:anthropic-streaming-retry": "pnpm exec tsx test/continuous-test-suite-anthropic-streaming-retry.ts",