@juspay/neurolink 7.35.0 → 7.36.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.
package/dist/neurolink.js CHANGED
@@ -854,7 +854,7 @@ export class NeuroLink {
854
854
  // Continue with warning rather than throwing - graceful degradation
855
855
  }
856
856
  }
857
- // Convert to TextGenerationOptions using factory utilities
857
+ // 🔧 CRITICAL FIX: Convert to TextGenerationOptions while preserving the input object for multimodal support
858
858
  const baseOptions = {
859
859
  prompt: options.input.text,
860
860
  provider: options.provider,
@@ -868,6 +868,7 @@ export class NeuroLink {
868
868
  context: options.context,
869
869
  evaluationDomain: options.evaluationDomain,
870
870
  toolUsageContext: options.toolUsageContext,
871
+ input: options.input, // This includes text, images, and content arrays
871
872
  };
872
873
  // Apply factory enhancement using centralized utilities
873
874
  const textOptions = enhanceTextGenerationOptions(baseOptions, factoryResult);
@@ -1664,7 +1665,9 @@ export class NeuroLink {
1664
1665
  const processedStream = (async function* (self) {
1665
1666
  try {
1666
1667
  for await (const chunk of mcpStream) {
1667
- if (chunk && "content" in chunk && typeof chunk.content === "string") {
1668
+ if (chunk &&
1669
+ "content" in chunk &&
1670
+ typeof chunk.content === "string") {
1668
1671
  accumulatedContent += chunk.content;
1669
1672
  // Emit chunk event for compatibility
1670
1673
  self.emitter.emit("response:chunk", chunk.content);
@@ -1941,7 +1944,9 @@ export class NeuroLink {
1941
1944
  const fallbackProcessedStream = (async function* (self) {
1942
1945
  try {
1943
1946
  for await (const chunk of fallbackStreamResult.stream) {
1944
- if (chunk && "content" in chunk && typeof chunk.content === "string") {
1947
+ if (chunk &&
1948
+ "content" in chunk &&
1949
+ typeof chunk.content === "string") {
1945
1950
  fallbackAccumulatedContent += chunk.content;
1946
1951
  // Emit chunk event
1947
1952
  self.emitter.emit("response:chunk", chunk.content);
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Content type definitions for multimodal support
3
+ * Supports text and image content with provider-specific formatting
4
+ */
5
+ /**
6
+ * Text content type for multimodal messages
7
+ */
8
+ export interface TextContent {
9
+ type: "text";
10
+ text: string;
11
+ }
12
+ /**
13
+ * Image content type for multimodal messages
14
+ */
15
+ export interface ImageContent {
16
+ type: "image";
17
+ data: Buffer | string;
18
+ mediaType?: "image/jpeg" | "image/png" | "image/gif" | "image/webp" | "image/bmp" | "image/tiff";
19
+ metadata?: {
20
+ description?: string;
21
+ quality?: "low" | "high" | "auto";
22
+ dimensions?: {
23
+ width: number;
24
+ height: number;
25
+ };
26
+ filename?: string;
27
+ };
28
+ }
29
+ /**
30
+ * Union type for all content types
31
+ */
32
+ export type Content = TextContent | ImageContent;
33
+ /**
34
+ * Vision capability information for providers
35
+ */
36
+ export interface VisionCapability {
37
+ provider: string;
38
+ supportedModels: string[];
39
+ maxImageSize?: number;
40
+ supportedFormats: string[];
41
+ maxImagesPerRequest?: number;
42
+ }
43
+ /**
44
+ * Provider-specific image format requirements
45
+ */
46
+ export interface ProviderImageFormat {
47
+ provider: string;
48
+ format: "data_uri" | "base64" | "inline_data" | "source";
49
+ requiresPrefix?: boolean;
50
+ mimeTypeField?: string;
51
+ dataField?: string;
52
+ }
53
+ /**
54
+ * Image processing result
55
+ */
56
+ export interface ProcessedImage {
57
+ data: string;
58
+ mediaType: string;
59
+ size: number;
60
+ format: "data_uri" | "base64" | "inline_data" | "source";
61
+ }
62
+ /**
63
+ * Multimodal message structure for provider adapters
64
+ */
65
+ export interface MultimodalMessage {
66
+ role: "user" | "assistant" | "system";
67
+ content: Content[];
68
+ }
69
+ /**
70
+ * Provider-specific multimodal payload
71
+ */
72
+ export interface ProviderMultimodalPayload {
73
+ provider: string;
74
+ model: string;
75
+ messages?: MultimodalMessage[];
76
+ contents?: unknown[];
77
+ [key: string]: unknown;
78
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Content type definitions for multimodal support
3
+ * Supports text and image content with provider-specific formatting
4
+ */
5
+ export {};
@@ -66,6 +66,25 @@ export interface ChatMessage {
66
66
  /** Content of the message */
67
67
  content: string;
68
68
  }
69
+ /**
70
+ * Content format for multimodal messages (used internally)
71
+ */
72
+ export interface MessageContent {
73
+ type: string;
74
+ text?: string;
75
+ image?: string;
76
+ mimeType?: string;
77
+ [key: string]: unknown;
78
+ }
79
+ /**
80
+ * Extended chat message for multimodal support (internal use)
81
+ */
82
+ export interface MultimodalChatMessage {
83
+ /** Role of the message sender */
84
+ role: "user" | "assistant" | "system";
85
+ /** Content of the message - can be text or multimodal content array */
86
+ content: string | MessageContent[];
87
+ }
69
88
  /**
70
89
  * Events emitted by conversation memory system
71
90
  */
@@ -6,13 +6,16 @@ import type { EvaluationData } from "./evaluation.js";
6
6
  import type { ChatMessage, ConversationMemoryConfig } from "./conversation.js";
7
7
  import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
8
8
  import type { JsonValue } from "./common.js";
9
+ import type { TextContent, ImageContent } from "./content.js";
9
10
  /**
10
11
  * Generate function options type - Primary method for content generation
11
- * Future-ready for multi-modal capabilities while maintaining text focus
12
+ * Supports multimodal content while maintaining backward compatibility
12
13
  */
13
14
  export type GenerateOptions = {
14
15
  input: {
15
16
  text: string;
17
+ images?: Array<Buffer | string>;
18
+ content?: Array<TextContent | ImageContent>;
16
19
  };
17
20
  output?: {
18
21
  format?: "text" | "structured" | "json";
@@ -5,6 +5,7 @@ import type { AnalyticsData, TokenUsage } from "./analytics.js";
5
5
  import type { EvaluationData } from "./evaluation.js";
6
6
  import type { UnknownRecord, JsonValue } from "./common.js";
7
7
  import type { ChatMessage } from "./conversation.js";
8
+ import type { TextContent, ImageContent } from "./content.js";
8
9
  import type { MiddlewareFactoryOptions } from "./middlewareTypes.js";
9
10
  /**
10
11
  * Progress tracking and metadata for streaming operations
@@ -118,10 +119,12 @@ export interface AudioChunk {
118
119
  channels: number;
119
120
  encoding: PCMEncoding;
120
121
  }
121
- export type StreamOptions = {
122
+ export interface StreamOptions {
122
123
  input: {
123
- text?: string;
124
+ text: string;
124
125
  audio?: AudioInputSpec;
126
+ images?: Array<Buffer | string>;
127
+ content?: Array<TextContent | ImageContent>;
125
128
  };
126
129
  output?: {
127
130
  format?: "text" | "structured" | "json";
@@ -166,7 +169,7 @@ export type StreamOptions = {
166
169
  };
167
170
  conversationMessages?: ChatMessage[];
168
171
  middleware?: MiddlewareFactoryOptions;
169
- };
172
+ }
170
173
  /**
171
174
  * Stream function result type - Primary output format for streaming
172
175
  * Future-ready for multi-modal outputs while maintaining text focus
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Image processing utilities for multimodal support
3
+ * Handles format conversion for different AI providers
4
+ */
5
+ import type { ProcessedImage } from "../types/content.js";
6
+ /**
7
+ * Image processor class for handling provider-specific image formatting
8
+ */
9
+ export declare class ImageProcessor {
10
+ /**
11
+ * Process image for OpenAI (requires data URI format)
12
+ */
13
+ static processImageForOpenAI(image: Buffer | string): string;
14
+ /**
15
+ * Process image for Google AI (requires base64 without data URI prefix)
16
+ */
17
+ static processImageForGoogle(image: Buffer | string): {
18
+ mimeType: string;
19
+ data: string;
20
+ };
21
+ /**
22
+ * Process image for Anthropic (requires base64 without data URI prefix)
23
+ */
24
+ static processImageForAnthropic(image: Buffer | string): {
25
+ mediaType: string;
26
+ data: string;
27
+ };
28
+ /**
29
+ * Process image for Vertex AI (model-specific routing)
30
+ */
31
+ static processImageForVertex(image: Buffer | string, model: string): {
32
+ mimeType?: string;
33
+ mediaType?: string;
34
+ data: string;
35
+ };
36
+ /**
37
+ * Detect image type from filename or data
38
+ */
39
+ static detectImageType(input: string | Buffer): string;
40
+ /**
41
+ * Validate image size (default 10MB limit)
42
+ */
43
+ static validateImageSize(data: Buffer | string, maxSize?: number): boolean;
44
+ /**
45
+ * Validate image format
46
+ */
47
+ static validateImageFormat(mediaType: string): boolean;
48
+ /**
49
+ * Get image dimensions from Buffer (basic implementation)
50
+ */
51
+ static getImageDimensions(buffer: Buffer): {
52
+ width: number;
53
+ height: number;
54
+ } | null;
55
+ /**
56
+ * Convert image to ProcessedImage format
57
+ */
58
+ static processImage(image: Buffer | string, provider: string, model?: string): ProcessedImage;
59
+ }
60
+ /**
61
+ * Utility functions for image handling
62
+ */
63
+ export declare const imageUtils: {
64
+ /**
65
+ * Check if a string is a valid data URI
66
+ */
67
+ isDataUri: (str: string) => boolean;
68
+ /**
69
+ * Check if a string is a valid URL
70
+ */
71
+ isUrl: (str: string) => boolean;
72
+ /**
73
+ * Check if a string is base64 encoded
74
+ */
75
+ isBase64: (str: string) => boolean;
76
+ /**
77
+ * Extract file extension from filename or URL
78
+ */
79
+ getFileExtension: (filename: string) => string | null;
80
+ /**
81
+ * Convert file size to human readable format
82
+ */
83
+ formatFileSize: (bytes: number) => string;
84
+ };
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Image processing utilities for multimodal support
3
+ * Handles format conversion for different AI providers
4
+ */
5
+ import { logger } from "./logger.js";
6
+ /**
7
+ * Image processor class for handling provider-specific image formatting
8
+ */
9
+ export class ImageProcessor {
10
+ /**
11
+ * Process image for OpenAI (requires data URI format)
12
+ */
13
+ static processImageForOpenAI(image) {
14
+ try {
15
+ if (typeof image === "string") {
16
+ // Handle URLs
17
+ if (image.startsWith("http")) {
18
+ return image;
19
+ }
20
+ // Handle data URIs
21
+ if (image.startsWith("data:")) {
22
+ return image;
23
+ }
24
+ // Handle base64 - convert to data URI
25
+ return `data:image/jpeg;base64,${image}`;
26
+ }
27
+ // Handle Buffer - convert to data URI
28
+ const base64 = image.toString("base64");
29
+ return `data:image/jpeg;base64,${base64}`;
30
+ }
31
+ catch (error) {
32
+ logger.error("Failed to process image for OpenAI:", error);
33
+ throw new Error(`Image processing failed for OpenAI: ${error instanceof Error ? error.message : "Unknown error"}`);
34
+ }
35
+ }
36
+ /**
37
+ * Process image for Google AI (requires base64 without data URI prefix)
38
+ */
39
+ static processImageForGoogle(image) {
40
+ try {
41
+ let base64Data;
42
+ let mimeType = "image/jpeg"; // Default
43
+ if (typeof image === "string") {
44
+ if (image.startsWith("data:")) {
45
+ // Extract mime type and base64 from data URI
46
+ const match = image.match(/^data:([^;]+);base64,(.+)$/);
47
+ if (match) {
48
+ mimeType = match[1];
49
+ base64Data = match[2];
50
+ }
51
+ else {
52
+ base64Data = image.split(",")[1] || image;
53
+ }
54
+ }
55
+ else {
56
+ base64Data = image;
57
+ }
58
+ }
59
+ else {
60
+ base64Data = image.toString("base64");
61
+ }
62
+ return {
63
+ mimeType,
64
+ data: base64Data, // Google wants base64 WITHOUT data URI prefix
65
+ };
66
+ }
67
+ catch (error) {
68
+ logger.error("Failed to process image for Google AI:", error);
69
+ throw new Error(`Image processing failed for Google AI: ${error instanceof Error ? error.message : "Unknown error"}`);
70
+ }
71
+ }
72
+ /**
73
+ * Process image for Anthropic (requires base64 without data URI prefix)
74
+ */
75
+ static processImageForAnthropic(image) {
76
+ try {
77
+ let base64Data;
78
+ let mediaType = "image/jpeg"; // Default
79
+ if (typeof image === "string") {
80
+ if (image.startsWith("data:")) {
81
+ // Extract mime type and base64 from data URI
82
+ const match = image.match(/^data:([^;]+);base64,(.+)$/);
83
+ if (match) {
84
+ mediaType = match[1];
85
+ base64Data = match[2];
86
+ }
87
+ else {
88
+ base64Data = image.split(",")[1] || image;
89
+ }
90
+ }
91
+ else {
92
+ base64Data = image;
93
+ }
94
+ }
95
+ else {
96
+ base64Data = image.toString("base64");
97
+ }
98
+ return {
99
+ mediaType,
100
+ data: base64Data, // Anthropic wants base64 WITHOUT data URI prefix
101
+ };
102
+ }
103
+ catch (error) {
104
+ logger.error("Failed to process image for Anthropic:", error);
105
+ throw new Error(`Image processing failed for Anthropic: ${error instanceof Error ? error.message : "Unknown error"}`);
106
+ }
107
+ }
108
+ /**
109
+ * Process image for Vertex AI (model-specific routing)
110
+ */
111
+ static processImageForVertex(image, model) {
112
+ try {
113
+ // Route based on model type
114
+ if (model.includes("gemini")) {
115
+ // Use Google AI format for Gemini models
116
+ return ImageProcessor.processImageForGoogle(image);
117
+ }
118
+ else if (model.includes("claude")) {
119
+ // Use Anthropic format for Claude models
120
+ return ImageProcessor.processImageForAnthropic(image);
121
+ }
122
+ else {
123
+ // Default to Google format
124
+ return ImageProcessor.processImageForGoogle(image);
125
+ }
126
+ }
127
+ catch (error) {
128
+ logger.error("Failed to process image for Vertex AI:", error);
129
+ throw new Error(`Image processing failed for Vertex AI: ${error instanceof Error ? error.message : "Unknown error"}`);
130
+ }
131
+ }
132
+ /**
133
+ * Detect image type from filename or data
134
+ */
135
+ static detectImageType(input) {
136
+ try {
137
+ if (typeof input === "string") {
138
+ // Check if it's a data URI
139
+ if (input.startsWith("data:")) {
140
+ const match = input.match(/^data:([^;]+);/);
141
+ return match ? match[1] : "image/jpeg";
142
+ }
143
+ // Check if it's a filename
144
+ const extension = input.toLowerCase().split(".").pop();
145
+ const imageTypes = {
146
+ jpg: "image/jpeg",
147
+ jpeg: "image/jpeg",
148
+ png: "image/png",
149
+ gif: "image/gif",
150
+ webp: "image/webp",
151
+ bmp: "image/bmp",
152
+ tiff: "image/tiff",
153
+ tif: "image/tiff",
154
+ };
155
+ return imageTypes[extension || ""] || "image/jpeg";
156
+ }
157
+ // For Buffer, try to detect from magic bytes
158
+ if (input.length >= 4) {
159
+ const header = input.subarray(0, 4);
160
+ // PNG: 89 50 4E 47
161
+ if (header[0] === 0x89 &&
162
+ header[1] === 0x50 &&
163
+ header[2] === 0x4e &&
164
+ header[3] === 0x47) {
165
+ return "image/png";
166
+ }
167
+ // JPEG: FF D8 FF
168
+ if (header[0] === 0xff && header[1] === 0xd8 && header[2] === 0xff) {
169
+ return "image/jpeg";
170
+ }
171
+ // GIF: 47 49 46 38
172
+ if (header[0] === 0x47 &&
173
+ header[1] === 0x49 &&
174
+ header[2] === 0x46 &&
175
+ header[3] === 0x38) {
176
+ return "image/gif";
177
+ }
178
+ // WebP: check for RIFF and WEBP
179
+ if (input.length >= 12) {
180
+ const riff = input.subarray(0, 4);
181
+ const webp = input.subarray(8, 12);
182
+ if (riff.toString() === "RIFF" && webp.toString() === "WEBP") {
183
+ return "image/webp";
184
+ }
185
+ }
186
+ }
187
+ return "image/jpeg"; // Default fallback
188
+ }
189
+ catch (error) {
190
+ logger.warn("Failed to detect image type, using default:", error);
191
+ return "image/jpeg";
192
+ }
193
+ }
194
+ /**
195
+ * Validate image size (default 10MB limit)
196
+ */
197
+ static validateImageSize(data, maxSize = 10 * 1024 * 1024) {
198
+ try {
199
+ const size = typeof data === "string"
200
+ ? Buffer.byteLength(data, "base64")
201
+ : data.length;
202
+ return size <= maxSize;
203
+ }
204
+ catch (error) {
205
+ logger.warn("Failed to validate image size:", error);
206
+ return false;
207
+ }
208
+ }
209
+ /**
210
+ * Validate image format
211
+ */
212
+ static validateImageFormat(mediaType) {
213
+ const supportedFormats = [
214
+ "image/jpeg",
215
+ "image/png",
216
+ "image/gif",
217
+ "image/webp",
218
+ "image/bmp",
219
+ "image/tiff",
220
+ ];
221
+ return supportedFormats.includes(mediaType.toLowerCase());
222
+ }
223
+ /**
224
+ * Get image dimensions from Buffer (basic implementation)
225
+ */
226
+ static getImageDimensions(buffer) {
227
+ try {
228
+ // Basic PNG dimension extraction
229
+ if (buffer.length >= 24 &&
230
+ buffer.subarray(0, 8).toString("hex") === "89504e470d0a1a0a") {
231
+ const width = buffer.readUInt32BE(16);
232
+ const height = buffer.readUInt32BE(20);
233
+ return { width, height };
234
+ }
235
+ // Basic JPEG dimension extraction (simplified)
236
+ if (buffer.length >= 4 && buffer[0] === 0xff && buffer[1] === 0xd8) {
237
+ // This is a very basic implementation
238
+ // For production, consider using a proper image library
239
+ return null;
240
+ }
241
+ return null;
242
+ }
243
+ catch (error) {
244
+ logger.warn("Failed to extract image dimensions:", error);
245
+ return null;
246
+ }
247
+ }
248
+ /**
249
+ * Convert image to ProcessedImage format
250
+ */
251
+ static processImage(image, provider, model) {
252
+ try {
253
+ const mediaType = ImageProcessor.detectImageType(image);
254
+ const size = typeof image === "string"
255
+ ? Buffer.byteLength(image, "base64")
256
+ : image.length;
257
+ let data;
258
+ let format;
259
+ switch (provider.toLowerCase()) {
260
+ case "openai":
261
+ data = ImageProcessor.processImageForOpenAI(image);
262
+ format = "data_uri";
263
+ break;
264
+ case "google-ai":
265
+ case "google": {
266
+ const googleResult = ImageProcessor.processImageForGoogle(image);
267
+ data = googleResult.data;
268
+ format = "base64";
269
+ break;
270
+ }
271
+ case "anthropic": {
272
+ const anthropicResult = ImageProcessor.processImageForAnthropic(image);
273
+ data = anthropicResult.data;
274
+ format = "base64";
275
+ break;
276
+ }
277
+ case "vertex": {
278
+ const vertexResult = ImageProcessor.processImageForVertex(image, model || "");
279
+ data = vertexResult.data;
280
+ format = "base64";
281
+ break;
282
+ }
283
+ default:
284
+ // Default to base64
285
+ if (typeof image === "string") {
286
+ data = image.startsWith("data:")
287
+ ? image.split(",")[1] || image
288
+ : image;
289
+ }
290
+ else {
291
+ data = image.toString("base64");
292
+ }
293
+ format = "base64";
294
+ }
295
+ return {
296
+ data,
297
+ mediaType,
298
+ size,
299
+ format,
300
+ };
301
+ }
302
+ catch (error) {
303
+ logger.error(`Failed to process image for ${provider}:`, error);
304
+ throw new Error(`Image processing failed: ${error instanceof Error ? error.message : "Unknown error"}`);
305
+ }
306
+ }
307
+ }
308
+ /**
309
+ * Utility functions for image handling
310
+ */
311
+ export const imageUtils = {
312
+ /**
313
+ * Check if a string is a valid data URI
314
+ */
315
+ isDataUri: (str) => {
316
+ return (typeof str === "string" &&
317
+ str.startsWith("data:") &&
318
+ str.includes("base64,"));
319
+ },
320
+ /**
321
+ * Check if a string is a valid URL
322
+ */
323
+ isUrl: (str) => {
324
+ try {
325
+ new URL(str);
326
+ return str.startsWith("http://") || str.startsWith("https://");
327
+ }
328
+ catch {
329
+ return false;
330
+ }
331
+ },
332
+ /**
333
+ * Check if a string is base64 encoded
334
+ */
335
+ isBase64: (str) => {
336
+ try {
337
+ return btoa(atob(str)) === str;
338
+ }
339
+ catch {
340
+ return false;
341
+ }
342
+ },
343
+ /**
344
+ * Extract file extension from filename or URL
345
+ */
346
+ getFileExtension: (filename) => {
347
+ const match = filename.match(/\.([^.]+)$/);
348
+ return match ? match[1].toLowerCase() : null;
349
+ },
350
+ /**
351
+ * Convert file size to human readable format
352
+ */
353
+ formatFileSize: (bytes) => {
354
+ if (bytes === 0) {
355
+ return "0 Bytes";
356
+ }
357
+ const k = 1024;
358
+ const sizes = ["Bytes", "KB", "MB", "GB"];
359
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
360
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
361
+ },
362
+ };
@@ -1,13 +1,20 @@
1
1
  /**
2
2
  * Message Builder Utility
3
3
  * Centralized logic for building message arrays from TextGenerationOptions
4
+ * Enhanced with multimodal support for images
4
5
  */
5
- import type { ChatMessage } from "../types/conversation.js";
6
+ import type { ChatMessage, MultimodalChatMessage } from "../types/conversation.js";
6
7
  import type { TextGenerationOptions } from "../types/index.js";
7
8
  import type { StreamOptions } from "../types/streamTypes.js";
9
+ import type { GenerateOptions } from "../types/generateTypes.js";
8
10
  /**
9
11
  * Build a properly formatted message array for AI providers
10
12
  * Combines system prompt, conversation history, and current user prompt
11
13
  * Supports both TextGenerationOptions and StreamOptions
12
14
  */
13
15
  export declare function buildMessagesArray(options: TextGenerationOptions | StreamOptions): ChatMessage[];
16
+ /**
17
+ * Build multimodal message array with image support
18
+ * Detects when images are present and routes through provider adapter
19
+ */
20
+ export declare function buildMultimodalMessagesArray(options: GenerateOptions, provider: string, model: string): Promise<MultimodalChatMessage[]>;