@juspay/neurolink 8.5.1 → 8.7.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 (54) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/adapters/providerImageAdapter.d.ts +4 -2
  3. package/dist/adapters/providerImageAdapter.js +16 -2
  4. package/dist/cli/factories/commandFactory.d.ts +5 -0
  5. package/dist/cli/factories/commandFactory.js +96 -0
  6. package/dist/cli/utils/audioFileUtils.d.ts +70 -0
  7. package/dist/cli/utils/audioFileUtils.js +174 -0
  8. package/dist/core/baseProvider.js +6 -2
  9. package/dist/core/modules/TelemetryHandler.js +6 -1
  10. package/dist/lib/adapters/providerImageAdapter.d.ts +4 -2
  11. package/dist/lib/adapters/providerImageAdapter.js +16 -2
  12. package/dist/lib/core/baseProvider.js +6 -2
  13. package/dist/lib/core/modules/TelemetryHandler.js +6 -1
  14. package/dist/lib/middleware/builtin/guardrails.js +7 -0
  15. package/dist/lib/neurolink.js +75 -5
  16. package/dist/lib/telemetry/telemetryService.d.ts +1 -1
  17. package/dist/lib/telemetry/telemetryService.js +4 -4
  18. package/dist/lib/types/cli.d.ts +2 -0
  19. package/dist/lib/types/common.d.ts +5 -0
  20. package/dist/lib/types/content.d.ts +1 -1
  21. package/dist/lib/types/fileTypes.d.ts +13 -12
  22. package/dist/lib/types/generateTypes.d.ts +19 -2
  23. package/dist/lib/types/index.d.ts +1 -0
  24. package/dist/lib/types/index.js +2 -0
  25. package/dist/lib/types/multimodal.d.ts +38 -1
  26. package/dist/lib/types/streamTypes.d.ts +21 -2
  27. package/dist/lib/types/ttsTypes.d.ts +91 -0
  28. package/dist/lib/types/ttsTypes.js +58 -0
  29. package/dist/lib/utils/imageProcessor.d.ts +38 -5
  30. package/dist/lib/utils/imageProcessor.js +131 -7
  31. package/dist/lib/utils/messageBuilder.js +52 -7
  32. package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -1
  33. package/dist/lib/utils/pdfProcessor.js +24 -2
  34. package/dist/middleware/builtin/guardrails.js +7 -0
  35. package/dist/neurolink.js +75 -5
  36. package/dist/telemetry/telemetryService.d.ts +1 -1
  37. package/dist/telemetry/telemetryService.js +4 -4
  38. package/dist/types/cli.d.ts +2 -0
  39. package/dist/types/common.d.ts +5 -0
  40. package/dist/types/content.d.ts +1 -1
  41. package/dist/types/fileTypes.d.ts +13 -12
  42. package/dist/types/generateTypes.d.ts +19 -2
  43. package/dist/types/index.d.ts +1 -0
  44. package/dist/types/index.js +2 -0
  45. package/dist/types/multimodal.d.ts +38 -1
  46. package/dist/types/streamTypes.d.ts +21 -2
  47. package/dist/types/ttsTypes.d.ts +91 -0
  48. package/dist/types/ttsTypes.js +57 -0
  49. package/dist/utils/imageProcessor.d.ts +38 -5
  50. package/dist/utils/imageProcessor.js +131 -7
  51. package/dist/utils/messageBuilder.js +52 -7
  52. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -1
  53. package/dist/utils/pdfProcessor.js +24 -2
  54. package/package.json +7 -4
@@ -3,6 +3,57 @@
3
3
  * Handles format conversion for different AI providers
4
4
  */
5
5
  import { logger } from "./logger.js";
6
+ import { withRetry } from "./retryHandler.js";
7
+ import { SYSTEM_LIMITS } from "../core/constants.js";
8
+ /**
9
+ * Network error codes that should trigger a retry
10
+ */
11
+ const RETRYABLE_ERROR_CODES = new Set([
12
+ "ECONNRESET",
13
+ "ENOTFOUND",
14
+ "ECONNREFUSED",
15
+ "ETIMEDOUT",
16
+ "ERR_NETWORK",
17
+ ]);
18
+ /**
19
+ * Determines if an HTTP error is retryable based on status code
20
+ * Only network errors and certain HTTP status codes should be retried
21
+ * 4xx client errors like 404 (Not Found) and 403 (Forbidden) should NOT be retried
22
+ *
23
+ * @param error - The error to check
24
+ * @returns true if the error is retryable, false otherwise
25
+ */
26
+ function isRetryableDownloadError(error) {
27
+ // Network-related errors should be retried
28
+ if (error && typeof error === "object") {
29
+ const errorCode = error.code;
30
+ const errorName = error.name;
31
+ if (RETRYABLE_ERROR_CODES.has(errorCode || "") ||
32
+ errorName === "AbortError") {
33
+ return true;
34
+ }
35
+ }
36
+ // Check for HTTP status code in error message for retryable errors
37
+ // Only retry on 5xx server errors, 429 (Too Many Requests), and 408 (Request Timeout)
38
+ // Do NOT retry on 4xx client errors like 404 (Not Found) or 403 (Forbidden)
39
+ if (error instanceof Error) {
40
+ const message = error.message;
41
+ // Extract HTTP status from error message like "HTTP 503: Service Unavailable"
42
+ const statusMatch = message.match(/HTTP (\d{3}):/);
43
+ if (statusMatch) {
44
+ const status = parseInt(statusMatch[1], 10);
45
+ // Retry on 5xx server errors, 429 (rate limit), 408 (timeout)
46
+ return status >= 500 || status === 429 || status === 408;
47
+ }
48
+ // Check for timeout/network-related error messages
49
+ // Use more precise matching to avoid false positives like "No timeout specified"
50
+ if (/\b(request timed out|operation timed out|connection timed out|timed out)\b/i.test(message) ||
51
+ /\bnetwork (error|failure|unreachable|down)\b/i.test(message)) {
52
+ return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
6
57
  /**
7
58
  * Image processor class for handling provider-specific image formatting
8
59
  */
@@ -16,9 +67,16 @@ export class ImageProcessor {
16
67
  * @returns Processed image as data URI
17
68
  */
18
69
  static async process(content, _options) {
70
+ // Validate content is non-empty before processing
71
+ if (content.length === 0) {
72
+ logger.error("Empty buffer provided");
73
+ throw new Error("Invalid image processing: buffer is empty");
74
+ }
19
75
  const mediaType = this.detectImageType(content);
20
76
  const base64 = content.toString("base64");
21
77
  const dataUri = `data:${mediaType};base64,${base64}`;
78
+ // Validate output before returning
79
+ this.validateProcessOutput(dataUri, base64, mediaType);
22
80
  return {
23
81
  type: "image",
24
82
  content: dataUri,
@@ -29,6 +87,37 @@ export class ImageProcessor {
29
87
  },
30
88
  };
31
89
  }
90
+ /**
91
+ * Validate processed output meets required format
92
+ * Checks:
93
+ * - Base64 content is non-empty
94
+ * - Data URI format is valid (data:{mimeType};base64,{content})
95
+ * - MIME type is in the allowed list
96
+ * @param dataUri - The complete data URI string
97
+ * @param base64 - The base64-encoded content
98
+ * @param mediaType - The MIME type of the image
99
+ * @throws Error if any validation fails
100
+ */
101
+ static validateProcessOutput(dataUri, base64, mediaType) {
102
+ // Validate base64 is non-empty (check first for better error message)
103
+ if (base64.length === 0) {
104
+ logger.error("Empty base64 content generated");
105
+ throw new Error("Invalid image processing: base64 content is empty");
106
+ }
107
+ // Validate data URI format with proper base64 character validation
108
+ // Base64 can only have 0, 1, or 2 padding characters at the end
109
+ const dataUriRegex = /^data:[^;]+;base64,[A-Za-z0-9+/]*={0,2}$/;
110
+ if (!dataUriRegex.test(dataUri)) {
111
+ logger.error("Invalid data URI format generated", { dataUri });
112
+ throw new Error("Invalid data URI format: must be data:{mimeType};base64,{content}");
113
+ }
114
+ // Defensive check: ensure detectImageType() returns valid MIME type
115
+ // This validation protects against future changes to detectImageType()
116
+ if (!this.validateImageFormat(mediaType)) {
117
+ logger.error("Invalid MIME type generated", { mediaType });
118
+ throw new Error(`Invalid MIME type: ${mediaType} is not in allowed list`);
119
+ }
120
+ }
32
121
  /**
33
122
  * Process image for OpenAI (requires data URI format)
34
123
  */
@@ -434,14 +523,35 @@ export const imageUtils = {
434
523
  }
435
524
  },
436
525
  /**
437
- * Convert URL to base64 data URI by downloading the image
526
+ * Convert URL to base64 data URI by downloading the image.
527
+ * Implements retry logic with exponential backoff for network errors.
528
+ *
529
+ * Retries are performed for:
530
+ * - Network errors (ECONNRESET, ENOTFOUND, ECONNREFUSED, ETIMEDOUT, ERR_NETWORK, AbortError)
531
+ * - Server errors (5xx status codes)
532
+ * - Rate limiting (429 Too Many Requests)
533
+ * - Request timeouts (408 Request Timeout)
534
+ *
535
+ * Retries are NOT performed for:
536
+ * - Client errors (4xx status codes except 408, 429)
537
+ * - Invalid content type
538
+ * - Content size limit exceeded
539
+ * - Unsupported protocol
540
+ *
541
+ * @param url - The URL of the image to download
542
+ * @param options - Configuration options
543
+ * @param options.timeoutMs - Timeout for each download attempt (default: 15000ms)
544
+ * @param options.maxBytes - Maximum allowed file size (default: 10MB)
545
+ * @param options.maxAttempts - Maximum number of total attempts including initial attempt (default: 3)
546
+ * @returns Promise<string> - Base64 data URI of the downloaded image
438
547
  */
439
- urlToBase64DataUri: async (url, { timeoutMs = 15000, maxBytes = 10 * 1024 * 1024 } = {}) => {
440
- try {
441
- // Basic protocol whitelist
442
- if (!/^https?:\/\//i.test(url)) {
443
- throw new Error("Unsupported protocol");
444
- }
548
+ urlToBase64DataUri: async (url, { timeoutMs = 15000, maxBytes = 10 * 1024 * 1024, maxAttempts = 3, } = {}) => {
549
+ // Basic protocol whitelist - fail fast, no retry needed
550
+ if (!/^https?:\/\//i.test(url)) {
551
+ throw new Error("Unsupported protocol");
552
+ }
553
+ // Perform the actual download with retry logic
554
+ const performDownload = async () => {
445
555
  const controller = new AbortController();
446
556
  const t = setTimeout(() => controller.abort(), timeoutMs);
447
557
  try {
@@ -467,6 +577,20 @@ export const imageUtils = {
467
577
  finally {
468
578
  clearTimeout(t);
469
579
  }
580
+ };
581
+ try {
582
+ return await withRetry(performDownload, {
583
+ maxAttempts,
584
+ initialDelay: SYSTEM_LIMITS.DEFAULT_INITIAL_DELAY,
585
+ backoffMultiplier: SYSTEM_LIMITS.DEFAULT_BACKOFF_MULTIPLIER,
586
+ maxDelay: SYSTEM_LIMITS.DEFAULT_MAX_DELAY,
587
+ retryCondition: isRetryableDownloadError,
588
+ onRetry: (attempt, error) => {
589
+ const message = error instanceof Error ? error.message : String(error);
590
+ const attemptsLeft = maxAttempts - attempt;
591
+ logger.warn(`⚠️ Image download attempt ${attempt} failed for ${url}: ${message}. ${attemptsLeft} ${attemptsLeft === 1 ? "attempt" : "attempts"} remaining...`);
592
+ },
593
+ });
470
594
  }
471
595
  catch (error) {
472
596
  throw new Error(`Failed to download and convert URL to base64: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -10,6 +10,30 @@ import { FileDetector } from "./fileDetector.js";
10
10
  import { PDFProcessor } from "./pdfProcessor.js";
11
11
  import { request, getGlobalDispatcher, interceptors } from "undici";
12
12
  import { readFileSync, existsSync } from "fs";
13
+ /**
14
+ * Type guard to check if an image input has alt text
15
+ */
16
+ function isImageWithAltText(image) {
17
+ return (typeof image === "object" && !Buffer.isBuffer(image) && "data" in image);
18
+ }
19
+ /**
20
+ * Extract image data from an image input (handles both simple and alt text formats)
21
+ */
22
+ function extractImageData(image) {
23
+ if (isImageWithAltText(image)) {
24
+ return image.data;
25
+ }
26
+ return image;
27
+ }
28
+ /**
29
+ * Extract alt text from an image input if available
30
+ */
31
+ function extractAltText(image) {
32
+ if (isImageWithAltText(image)) {
33
+ return image.altText;
34
+ }
35
+ return undefined;
36
+ }
13
37
  /**
14
38
  * Type guard for validating message roles
15
39
  */
@@ -639,28 +663,47 @@ async function downloadImageFromUrl(url) {
639
663
  * - URLs: Downloaded and converted to base64 for Vercel AI SDK compatibility
640
664
  * - Local files: Converted to base64 for Vercel AI SDK compatibility
641
665
  * - Buffers/Data URIs: Processed normally
666
+ * - Supports alt text for accessibility (included as context in text parts)
642
667
  */
643
668
  async function convertSimpleImagesToProviderFormat(text, images, provider, _model) {
644
669
  // For Vercel AI SDK, we need to return the content in the standard format
645
670
  // The Vercel AI SDK will handle provider-specific formatting internally
671
+ // IMPORTANT: Generate alt text descriptions BEFORE URL downloading to maintain correct image numbering
672
+ // This ensures image numbers match the original order provided by users, even if some URLs fail to download
673
+ const altTextDescriptions = images
674
+ .map((image, idx) => {
675
+ const altText = extractAltText(image);
676
+ return altText ? `[Image ${idx + 1}: ${altText}]` : null;
677
+ })
678
+ .filter(Boolean);
679
+ // Build enhanced text with alt text context for accessibility
680
+ // NOTE: Alt text is appended to the user's prompt as contextual information because most AI providers
681
+ // don't have native alt text fields in their APIs. This approach ensures accessibility metadata
682
+ // is preserved and helps AI models better understand image content.
683
+ const enhancedText = altTextDescriptions.length > 0
684
+ ? `${text}\n\nImage descriptions for context: ${altTextDescriptions.join(" ")}`
685
+ : text;
646
686
  // Smart auto-detection: separate URLs from actual image data
687
+ // Also track alt text for each image
647
688
  const urlImages = [];
648
689
  const actualImages = [];
649
690
  images.forEach((image, _index) => {
650
- if (typeof image === "string" && isInternetUrl(image)) {
691
+ const imageData = extractImageData(image);
692
+ const altText = extractAltText(image);
693
+ if (typeof imageData === "string" && isInternetUrl(imageData)) {
651
694
  // Internet URL - will be downloaded and converted to base64
652
- urlImages.push(image);
695
+ urlImages.push({ url: imageData, altText });
653
696
  }
654
697
  else {
655
698
  // Actual image data (file path, Buffer, data URI) - process for Vercel AI SDK
656
- actualImages.push(image);
699
+ actualImages.push({ data: imageData, altText });
657
700
  }
658
701
  });
659
702
  // Download URL images and add to actual images
660
- for (const url of urlImages) {
703
+ for (const { url, altText } of urlImages) {
661
704
  try {
662
705
  const downloadedDataUri = await downloadImageFromUrl(url);
663
- actualImages.push(downloadedDataUri);
706
+ actualImages.push({ data: downloadedDataUri, altText });
664
707
  }
665
708
  catch (error) {
666
709
  MultimodalLogger.logError("URL_DOWNLOAD_FAILED_SKIPPING", error, { url });
@@ -668,9 +711,11 @@ async function convertSimpleImagesToProviderFormat(text, images, provider, _mode
668
711
  logger.warn(`Failed to download image from ${url}, skipping: ${error instanceof Error ? error.message : String(error)}`);
669
712
  }
670
713
  }
671
- const content = [{ type: "text", text }];
714
+ const content = [
715
+ { type: "text", text: enhancedText },
716
+ ];
672
717
  // Process all images (including downloaded URLs) for Vercel AI SDK
673
- actualImages.forEach((image, index) => {
718
+ actualImages.forEach(({ data: image }, index) => {
674
719
  try {
675
720
  // Vercel AI SDK expects { type: 'image', image: Buffer | string, mimeType?: string }
676
721
  // For Vertex AI, we need to include mimeType
@@ -44,7 +44,7 @@ import type { StreamOptions } from "../types/streamTypes.js";
44
44
  export declare function buildMultimodalOptions(options: StreamOptions, providerName: string, modelName: string): {
45
45
  input: {
46
46
  text: string;
47
- images: (string | Buffer<ArrayBufferLike>)[] | undefined;
47
+ images: (string | Buffer<ArrayBufferLike> | import("../types/multimodal.js").ImageWithAltText)[] | undefined;
48
48
  content: import("../types/multimodal.js").Content[] | undefined;
49
49
  files: (string | Buffer<ArrayBufferLike>)[] | undefined;
50
50
  csvFiles: (string | Buffer<ArrayBufferLike>)[] | undefined;
@@ -1,6 +1,6 @@
1
1
  import { logger } from "./logger.js";
2
- import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";
3
- import { createCanvas } from "canvas";
2
+ // Lazy-load pdfjs-dist to avoid DOMMatrix errors in Node.js server environment
3
+ // import * as pdfjs from "pdfjs-dist/legacy/build/pdf.mjs";
4
4
  const PDF_PROVIDER_CONFIGS = {
5
5
  anthropic: {
6
6
  maxSizeMB: 5,
@@ -196,6 +196,28 @@ export class PDFProcessor {
196
196
  }
197
197
  }
198
198
  static async convertPDFToImages(pdfBuffer, options) {
199
+ // Dynamic import canvas - only load when actually needed
200
+ let createCanvas;
201
+ try {
202
+ const canvasModule = await import("canvas");
203
+ createCanvas = canvasModule.createCanvas;
204
+ }
205
+ catch {
206
+ throw new Error("Canvas dependency not available. " +
207
+ "PDF-to-image conversion requires the 'canvas' package with native bindings. " +
208
+ "Install with: pnpm install canvas\n" +
209
+ "Note: This requires native build tools (Python, C++ compiler).");
210
+ }
211
+ // Dynamic import pdfjs - only load when actually needed to avoid DOMMatrix errors
212
+ let pdfjs;
213
+ try {
214
+ pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
215
+ }
216
+ catch {
217
+ throw new Error("pdfjs-dist dependency not available. " +
218
+ "PDF processing requires the 'pdfjs-dist' package. " +
219
+ "Install with: pnpm install pdfjs-dist");
220
+ }
199
221
  const maxPages = options?.maxPages || 10;
200
222
  const scale = options?.scale || 2.0;
201
223
  const format = options?.format || "png";
@@ -69,8 +69,10 @@ export function createGuardrailsMiddleware(config = {}) {
69
69
  };
70
70
  }
71
71
  const { stream, ...rest } = await doStream();
72
+ let hasYieldedChunks = false;
72
73
  const transformStream = new TransformStream({
73
74
  transform(chunk, controller) {
75
+ hasYieldedChunks = true;
74
76
  let filteredChunk = chunk;
75
77
  if (typeof filteredChunk === "object" &&
76
78
  "textDelta" in filteredChunk) {
@@ -84,6 +86,11 @@ export function createGuardrailsMiddleware(config = {}) {
84
86
  }
85
87
  controller.enqueue(filteredChunk);
86
88
  },
89
+ flush() {
90
+ if (!hasYieldedChunks) {
91
+ logger.warn(`[GuardrailsMiddleware] Stream ended without yielding any chunks`);
92
+ }
93
+ },
87
94
  });
88
95
  return {
89
96
  stream: stream.pipeThrough(transformStream),
package/dist/neurolink.js CHANGED
@@ -1998,19 +1998,85 @@ Current user's request: ${currentInput}`;
1998
1998
  }
1999
1999
  }
2000
2000
  const { stream: mcpStream, provider: providerName } = await this.createMCPStream(enhancedOptions);
2001
- // Create a wrapper around the stream that accumulates content
2002
2001
  let accumulatedContent = "";
2002
+ let chunkCount = 0;
2003
+ const metadata = {
2004
+ fallbackAttempted: false,
2005
+ guardrailsBlocked: false,
2006
+ error: undefined,
2007
+ };
2003
2008
  const processedStream = (async function* (self) {
2004
2009
  try {
2005
2010
  for await (const chunk of mcpStream) {
2011
+ chunkCount++;
2006
2012
  if (chunk &&
2007
2013
  "content" in chunk &&
2008
2014
  typeof chunk.content === "string") {
2009
2015
  accumulatedContent += chunk.content;
2010
- // Emit chunk event for compatibility
2011
2016
  self.emitter.emit("response:chunk", chunk.content);
2012
2017
  }
2013
- yield chunk; // Preserve original streaming behavior
2018
+ yield chunk;
2019
+ }
2020
+ if (chunkCount === 0 && !metadata.fallbackAttempted) {
2021
+ metadata.fallbackAttempted = true;
2022
+ const errorMsg = "Stream completed with 0 chunks (possible guardrails block)";
2023
+ metadata.error = errorMsg;
2024
+ const fallbackRoute = ModelRouter.getFallbackRoute(originalPrompt || enhancedOptions.input.text || "", {
2025
+ provider: providerName,
2026
+ model: enhancedOptions.model || "gpt-4o",
2027
+ reasoning: "primary failed",
2028
+ confidence: 0.5,
2029
+ }, { fallbackStrategy: "auto" });
2030
+ logger.warn("Retrying with fallback provider", {
2031
+ originalProvider: providerName,
2032
+ fallbackProvider: fallbackRoute.provider,
2033
+ reason: errorMsg,
2034
+ });
2035
+ try {
2036
+ const fallbackProvider = await AIProviderFactory.createProvider(fallbackRoute.provider, fallbackRoute.model);
2037
+ // Ensure fallback provider can execute tools
2038
+ fallbackProvider.setupToolExecutor({
2039
+ customTools: self.getCustomTools(),
2040
+ executeTool: self.executeTool.bind(self),
2041
+ }, "NeuroLink.fallbackStream");
2042
+ // Get conversation messages for context (same as primary stream)
2043
+ const conversationMessages = await getConversationMessages(self.conversationMemory, {
2044
+ prompt: enhancedOptions.input.text,
2045
+ context: enhancedOptions.context,
2046
+ });
2047
+ const fallbackResult = await fallbackProvider.stream({
2048
+ ...enhancedOptions,
2049
+ model: fallbackRoute.model,
2050
+ conversationMessages,
2051
+ });
2052
+ let fallbackChunkCount = 0;
2053
+ for await (const fallbackChunk of fallbackResult.stream) {
2054
+ fallbackChunkCount++;
2055
+ if (fallbackChunk &&
2056
+ "content" in fallbackChunk &&
2057
+ typeof fallbackChunk.content === "string") {
2058
+ accumulatedContent += fallbackChunk.content;
2059
+ self.emitter.emit("response:chunk", fallbackChunk.content);
2060
+ }
2061
+ yield fallbackChunk;
2062
+ }
2063
+ if (fallbackChunkCount === 0) {
2064
+ throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 chunks`);
2065
+ }
2066
+ // Fallback succeeded - likely guardrails blocked primary
2067
+ metadata.guardrailsBlocked = true;
2068
+ }
2069
+ catch (fallbackError) {
2070
+ const fallbackErrorMsg = fallbackError instanceof Error
2071
+ ? fallbackError.message
2072
+ : String(fallbackError);
2073
+ metadata.error = `${errorMsg}; Fallback failed: ${fallbackErrorMsg}`;
2074
+ logger.error("Fallback provider failed", {
2075
+ fallbackProvider: fallbackRoute.provider,
2076
+ error: fallbackErrorMsg,
2077
+ });
2078
+ throw fallbackError;
2079
+ }
2014
2080
  }
2015
2081
  }
2016
2082
  finally {
@@ -2053,7 +2119,7 @@ Current user's request: ${currentInput}`;
2053
2119
  }
2054
2120
  }
2055
2121
  })(this);
2056
- const streamResult = await this.processStreamResult(mcpStream, enhancedOptions, factoryResult);
2122
+ const streamResult = await this.processStreamResult(processedStream, enhancedOptions, factoryResult);
2057
2123
  const responseTime = Date.now() - startTime;
2058
2124
  this.emitStreamEndEvents(streamResult);
2059
2125
  return this.createStreamResponse(streamResult, processedStream, {
@@ -2062,7 +2128,9 @@ Current user's request: ${currentInput}`;
2062
2128
  startTime,
2063
2129
  responseTime,
2064
2130
  streamId,
2065
- fallback: false,
2131
+ fallback: metadata.fallbackAttempted,
2132
+ guardrailsBlocked: metadata.guardrailsBlocked,
2133
+ error: metadata.error,
2066
2134
  });
2067
2135
  }
2068
2136
  catch (error) {
@@ -2181,6 +2249,8 @@ Current user's request: ${currentInput}`;
2181
2249
  startTime: config.startTime,
2182
2250
  responseTime: config.responseTime,
2183
2251
  fallback: config.fallback || false,
2252
+ guardrailsBlocked: config.guardrailsBlocked,
2253
+ error: config.error,
2184
2254
  },
2185
2255
  };
2186
2256
  }
@@ -31,7 +31,7 @@ export declare class TelemetryService {
31
31
  private initializeTelemetry;
32
32
  private initializeMetrics;
33
33
  initialize(): Promise<void>;
34
- traceAIRequest<T>(provider: string, operation: () => Promise<T>): Promise<T>;
34
+ traceAIRequest<T>(provider: string, operation: () => Promise<T>, operationType?: string): Promise<T>;
35
35
  recordAIRequest(provider: string, model: string, tokens: number, duration: number): void;
36
36
  recordAIError(provider: string, error: Error): void;
37
37
  recordMCPToolCall(toolName: string, duration: number, success: boolean): void;
@@ -108,14 +108,14 @@ export class TelemetryService {
108
108
  }
109
109
  }
110
110
  // AI Operation Tracing (NO-OP when disabled)
111
- async traceAIRequest(provider, operation) {
111
+ async traceAIRequest(provider, operation, operationType = "generate_text") {
112
112
  if (!this.enabled || !this.tracer) {
113
- return await operation(); // Direct execution when disabled
113
+ return await operation();
114
114
  }
115
- const span = this.tracer.startSpan(`ai.${provider}.generate_text`, {
115
+ const span = this.tracer.startSpan(`ai.${provider}.${operationType}`, {
116
116
  attributes: {
117
117
  "ai.provider": provider,
118
- "ai.operation": "generate_text",
118
+ "ai.operation": operationType,
119
119
  },
120
120
  });
121
121
  try {
@@ -337,6 +337,8 @@ export type GenerateResult = CommandResult & {
337
337
  name: string;
338
338
  description: string;
339
339
  }>;
340
+ /** TTS audio result when TTS is enabled */
341
+ audio?: import("./index.js").TTSResult;
340
342
  };
341
343
  /**
342
344
  * Stream result chunk
@@ -129,3 +129,8 @@ export type TypedEventEmitter<TEvents extends Record<string, unknown>> = {
129
129
  listenerCount<K extends keyof TEvents>(event: K): number;
130
130
  listeners<K extends keyof TEvents>(event: K): Array<(...args: unknown[]) => void>;
131
131
  };
132
+ export type Context = {
133
+ traceName?: string;
134
+ userId?: string;
135
+ sessionId?: string;
136
+ };
@@ -14,5 +14,5 @@
14
14
  * import type { MultimodalInput } from './types/multimodal.js';
15
15
  * ```
16
16
  */
17
- export type { TextContent, ImageContent, CSVContent, PDFContent, AudioContent, VideoContent, Content, MultimodalInput, MultimodalMessage, VisionCapability, ProviderImageFormat, ProcessedImage, ProviderMultimodalPayload, } from "./multimodal.js";
17
+ export type { TextContent, ImageContent, CSVContent, PDFContent, AudioContent, VideoContent, Content, ImageWithAltText, MultimodalInput, MultimodalMessage, VisionCapability, ProviderImageFormat, ProcessedImage, ProviderMultimodalPayload, } from "./multimodal.js";
18
18
  export { isTextContent, isImageContent, isCSVContent, isPDFContent, isAudioContent, isVideoContent, isMultimodalInput, } from "./multimodal.js";
@@ -81,18 +81,7 @@ export type PDFProcessorOptions = {
81
81
  bedrockApiMode?: "converse" | "invokeModel";
82
82
  };
83
83
  /**
84
- * File detector options
85
- */
86
- export type FileDetectorOptions = {
87
- maxSize?: number;
88
- timeout?: number;
89
- allowedTypes?: FileType[];
90
- csvOptions?: CSVProcessorOptions;
91
- confidenceThreshold?: number;
92
- provider?: string;
93
- };
94
- /**
95
- * Audio processor options for transcription configuration
84
+ * Audio processor options
96
85
  */
97
86
  export type AudioProcessorOptions = {
98
87
  /** AI provider to use for transcription (e.g., 'openai', 'google', 'azure') */
@@ -108,6 +97,18 @@ export type AudioProcessorOptions = {
108
97
  /** Maximum file size in megabytes */
109
98
  maxSizeMB?: number;
110
99
  };
100
+ /**
101
+ * File detector options
102
+ */
103
+ export type FileDetectorOptions = {
104
+ maxSize?: number;
105
+ timeout?: number;
106
+ allowedTypes?: FileType[];
107
+ audioOptions?: AudioProcessorOptions;
108
+ csvOptions?: CSVProcessorOptions;
109
+ confidenceThreshold?: number;
110
+ provider?: string;
111
+ };
111
112
  /**
112
113
  * Google AI Studio Files API types
113
114
  */
@@ -6,7 +6,7 @@ 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 { Content } from "./content.js";
9
+ import type { Content, ImageWithAltText } from "./content.js";
10
10
  /**
11
11
  * Generate function options type - Primary method for content generation
12
12
  * Supports multimodal content while maintaining backward compatibility
@@ -14,7 +14,24 @@ import type { Content } from "./content.js";
14
14
  export type GenerateOptions = {
15
15
  input: {
16
16
  text: string;
17
- images?: Array<Buffer | string>;
17
+ /**
18
+ * Images to include in the request.
19
+ * Supports simple image data (Buffer, string) or objects with alt text for accessibility.
20
+ *
21
+ * @example Simple usage
22
+ * ```typescript
23
+ * images: [imageBuffer, "https://example.com/image.jpg"]
24
+ * ```
25
+ *
26
+ * @example With alt text for accessibility
27
+ * ```typescript
28
+ * images: [
29
+ * { data: imageBuffer, altText: "Product screenshot showing main dashboard" },
30
+ * { data: "https://example.com/chart.png", altText: "Sales chart for Q3 2024" }
31
+ * ]
32
+ * ```
33
+ */
34
+ images?: Array<Buffer | string | ImageWithAltText>;
18
35
  csvFiles?: Array<Buffer | string>;
19
36
  pdfFiles?: Array<Buffer | string>;
20
37
  files?: Array<Buffer | string>;
@@ -32,3 +32,4 @@ export * from "./utilities.js";
32
32
  export * from "./middlewareTypes.js";
33
33
  export * from "./fileTypes.js";
34
34
  export * from "./content.js";
35
+ export * from "./ttsTypes.js";
@@ -35,3 +35,5 @@ export * from "./middlewareTypes.js";
35
35
  export * from "./fileTypes.js";
36
36
  // Content types for multimodal support
37
37
  export * from "./content.js";
38
+ // TTS (Text-to-Speech) types
39
+ export * from "./ttsTypes.js";