@juspay/neurolink 8.12.0 → 8.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [8.13.1](https://github.com/juspay/neurolink/compare/v8.13.0...v8.13.1) (2025-12-13)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **(provider):** Implement image count limits with validation and warnings ([ff3e27a](https://github.com/juspay/neurolink/commit/ff3e27a5ab3aafffc8312f645e0ebc566600cc63))
6
+
7
+ ## [8.13.0](https://github.com/juspay/neurolink/compare/v8.12.0...v8.13.0) (2025-12-13)
8
+
9
+ ### Features
10
+
11
+ - **(tts):** Implement TTSProcessor.synthesize() method ([d6f3567](https://github.com/juspay/neurolink/commit/d6f3567dda26191f0ca9fd82a8cd7ccff5c9f819))
12
+
1
13
  ## [8.12.0](https://github.com/juspay/neurolink/compare/v8.11.0...v8.12.0) (2025-12-13)
2
14
 
3
15
  ### Features
@@ -33,6 +33,11 @@ export declare class ProviderImageAdapter {
33
33
  * Format content for Vertex AI (model-specific routing)
34
34
  */
35
35
  private static formatForVertex;
36
+ /**
37
+ * Validate image count against provider limits
38
+ * Warns at 80% threshold, throws error if limit exceeded
39
+ */
40
+ private static validateImageCount;
36
41
  /**
37
42
  * Validate that provider and model support vision
38
43
  */
@@ -16,6 +16,28 @@ export class MultimodalLogger {
16
16
  }
17
17
  }
18
18
  }
19
+ /**
20
+ * Image count limits per provider
21
+ * These limits prevent API rejections when too many images are sent
22
+ */
23
+ const IMAGE_LIMITS = {
24
+ openai: 10,
25
+ azure: 10, // Same as OpenAI
26
+ "google-ai": 16,
27
+ google: 16,
28
+ anthropic: 20,
29
+ vertex: {
30
+ // Vertex has model-specific limits
31
+ claude: 20, // Claude models on Vertex
32
+ gemini: 16, // Gemini models on Vertex
33
+ default: 16,
34
+ },
35
+ ollama: 10, // Conservative limit for Ollama
36
+ litellm: 10, // Conservative limit, as it proxies to various providers
37
+ mistral: 10, // Conservative limit for Mistral
38
+ // Note: Bedrock limit defined for future use when vision support is added
39
+ bedrock: 20, // Same as Anthropic for Claude models on Bedrock
40
+ };
19
41
  /**
20
42
  * Vision capability definitions for each provider
21
43
  */
@@ -368,7 +390,9 @@ export class ProviderImageAdapter {
368
390
  break;
369
391
  case "azure":
370
392
  case "azure-openai":
371
- adaptedPayload = this.formatForOpenAI(text, images);
393
+ // Azure uses same format as OpenAI but validate with azure provider name
394
+ this.validateImageCount(images.length, "azure");
395
+ adaptedPayload = this.formatForOpenAI(text, images, true);
372
396
  break;
373
397
  case "google-ai":
374
398
  case "google":
@@ -381,7 +405,9 @@ export class ProviderImageAdapter {
381
405
  adaptedPayload = this.formatForVertex(text, images, model);
382
406
  break;
383
407
  case "ollama":
384
- adaptedPayload = this.formatForOpenAI(text, images);
408
+ // Ollama uses same format as OpenAI but validate with ollama provider name
409
+ this.validateImageCount(images.length, "ollama");
410
+ adaptedPayload = this.formatForOpenAI(text, images, true);
385
411
  break;
386
412
  case "huggingface":
387
413
  adaptedPayload = this.formatForOpenAI(text, images);
@@ -415,7 +441,11 @@ export class ProviderImageAdapter {
415
441
  /**
416
442
  * Format content for OpenAI (GPT-4o format)
417
443
  */
418
- static formatForOpenAI(text, images) {
444
+ static formatForOpenAI(text, images, skipValidation = false) {
445
+ // Validate image count before processing (unless called from another formatter)
446
+ if (!skipValidation) {
447
+ this.validateImageCount(images.length, "openai");
448
+ }
419
449
  const content = [{ type: "text", text }];
420
450
  images.forEach((image, index) => {
421
451
  try {
@@ -438,7 +468,11 @@ export class ProviderImageAdapter {
438
468
  /**
439
469
  * Format content for Google AI (Gemini format)
440
470
  */
441
- static formatForGoogleAI(text, images) {
471
+ static formatForGoogleAI(text, images, skipValidation = false) {
472
+ // Validate image count before processing (unless called from another formatter)
473
+ if (!skipValidation) {
474
+ this.validateImageCount(images.length, "google-ai");
475
+ }
442
476
  const parts = [{ text }];
443
477
  images.forEach((image, index) => {
444
478
  try {
@@ -460,7 +494,11 @@ export class ProviderImageAdapter {
460
494
  /**
461
495
  * Format content for Anthropic (Claude format)
462
496
  */
463
- static formatForAnthropic(text, images) {
497
+ static formatForAnthropic(text, images, skipValidation = false) {
498
+ // Validate image count before processing (unless called from another formatter)
499
+ if (!skipValidation) {
500
+ this.validateImageCount(images.length, "anthropic");
501
+ }
464
502
  const content = [{ type: "text", text }];
465
503
  images.forEach((image, index) => {
466
504
  try {
@@ -488,15 +526,65 @@ export class ProviderImageAdapter {
488
526
  * Format content for Vertex AI (model-specific routing)
489
527
  */
490
528
  static formatForVertex(text, images, model) {
491
- // Route based on model type
529
+ // Validate image count with model-specific limits before processing
530
+ this.validateImageCount(images.length, "vertex", model);
531
+ // Route based on model type, skip validation in delegated methods
492
532
  if (model.includes("gemini")) {
493
- return this.formatForGoogleAI(text, images);
533
+ return this.formatForGoogleAI(text, images, true);
494
534
  }
495
535
  else if (model.includes("claude")) {
496
- return this.formatForAnthropic(text, images);
536
+ return this.formatForAnthropic(text, images, true);
537
+ }
538
+ else {
539
+ return this.formatForGoogleAI(text, images, true);
540
+ }
541
+ }
542
+ /**
543
+ * Validate image count against provider limits
544
+ * Warns at 80% threshold, throws error if limit exceeded
545
+ */
546
+ static validateImageCount(imageCount, provider, model) {
547
+ const normalizedProvider = provider.toLowerCase();
548
+ let limit;
549
+ // Determine the limit based on provider
550
+ if (normalizedProvider === "vertex" && model) {
551
+ // Vertex has model-specific limits
552
+ if (model.includes("claude")) {
553
+ limit = IMAGE_LIMITS.vertex.claude;
554
+ }
555
+ else if (model.includes("gemini")) {
556
+ limit = IMAGE_LIMITS.vertex.gemini;
557
+ }
558
+ else {
559
+ limit = IMAGE_LIMITS.vertex.default;
560
+ }
497
561
  }
498
562
  else {
499
- return this.formatForGoogleAI(text, images);
563
+ // Use provider-specific limit
564
+ const providerLimit = normalizedProvider in IMAGE_LIMITS
565
+ ? IMAGE_LIMITS[normalizedProvider]
566
+ : undefined;
567
+ // If provider not found in limits map, use a conservative default
568
+ if (providerLimit === undefined) {
569
+ // Conservative default for unknown providers
570
+ limit = 10;
571
+ logger.warn(`Image count limit not defined for provider ${provider}. Using conservative default of 10 images.`);
572
+ }
573
+ else {
574
+ // providerLimit is always a number when defined (except vertex which is handled separately)
575
+ limit = providerLimit;
576
+ }
577
+ }
578
+ // Warn only once at 80% threshold to avoid noise in batch processing
579
+ const warningThreshold = Math.floor(limit * 0.8);
580
+ if (imageCount === warningThreshold) {
581
+ logger.warn(`Image count (${imageCount}) is approaching the limit for ${provider}. ` +
582
+ `Maximum allowed: ${limit}. Please reduce the number of images.`);
583
+ }
584
+ // Throw error if limit exceeded
585
+ if (imageCount > limit) {
586
+ throw new Error(`Image count (${imageCount}) exceeds the maximum limit for ${provider}. ` +
587
+ `Maximum allowed: ${limit}. Please reduce the number of images.`);
500
588
  }
501
589
  }
502
590
  /**
@@ -33,6 +33,11 @@ export declare class ProviderImageAdapter {
33
33
  * Format content for Vertex AI (model-specific routing)
34
34
  */
35
35
  private static formatForVertex;
36
+ /**
37
+ * Validate image count against provider limits
38
+ * Warns at 80% threshold, throws error if limit exceeded
39
+ */
40
+ private static validateImageCount;
36
41
  /**
37
42
  * Validate that provider and model support vision
38
43
  */
@@ -16,6 +16,28 @@ export class MultimodalLogger {
16
16
  }
17
17
  }
18
18
  }
19
+ /**
20
+ * Image count limits per provider
21
+ * These limits prevent API rejections when too many images are sent
22
+ */
23
+ const IMAGE_LIMITS = {
24
+ openai: 10,
25
+ azure: 10, // Same as OpenAI
26
+ "google-ai": 16,
27
+ google: 16,
28
+ anthropic: 20,
29
+ vertex: {
30
+ // Vertex has model-specific limits
31
+ claude: 20, // Claude models on Vertex
32
+ gemini: 16, // Gemini models on Vertex
33
+ default: 16,
34
+ },
35
+ ollama: 10, // Conservative limit for Ollama
36
+ litellm: 10, // Conservative limit, as it proxies to various providers
37
+ mistral: 10, // Conservative limit for Mistral
38
+ // Note: Bedrock limit defined for future use when vision support is added
39
+ bedrock: 20, // Same as Anthropic for Claude models on Bedrock
40
+ };
19
41
  /**
20
42
  * Vision capability definitions for each provider
21
43
  */
@@ -368,7 +390,9 @@ export class ProviderImageAdapter {
368
390
  break;
369
391
  case "azure":
370
392
  case "azure-openai":
371
- adaptedPayload = this.formatForOpenAI(text, images);
393
+ // Azure uses same format as OpenAI but validate with azure provider name
394
+ this.validateImageCount(images.length, "azure");
395
+ adaptedPayload = this.formatForOpenAI(text, images, true);
372
396
  break;
373
397
  case "google-ai":
374
398
  case "google":
@@ -381,7 +405,9 @@ export class ProviderImageAdapter {
381
405
  adaptedPayload = this.formatForVertex(text, images, model);
382
406
  break;
383
407
  case "ollama":
384
- adaptedPayload = this.formatForOpenAI(text, images);
408
+ // Ollama uses same format as OpenAI but validate with ollama provider name
409
+ this.validateImageCount(images.length, "ollama");
410
+ adaptedPayload = this.formatForOpenAI(text, images, true);
385
411
  break;
386
412
  case "huggingface":
387
413
  adaptedPayload = this.formatForOpenAI(text, images);
@@ -415,7 +441,11 @@ export class ProviderImageAdapter {
415
441
  /**
416
442
  * Format content for OpenAI (GPT-4o format)
417
443
  */
418
- static formatForOpenAI(text, images) {
444
+ static formatForOpenAI(text, images, skipValidation = false) {
445
+ // Validate image count before processing (unless called from another formatter)
446
+ if (!skipValidation) {
447
+ this.validateImageCount(images.length, "openai");
448
+ }
419
449
  const content = [{ type: "text", text }];
420
450
  images.forEach((image, index) => {
421
451
  try {
@@ -438,7 +468,11 @@ export class ProviderImageAdapter {
438
468
  /**
439
469
  * Format content for Google AI (Gemini format)
440
470
  */
441
- static formatForGoogleAI(text, images) {
471
+ static formatForGoogleAI(text, images, skipValidation = false) {
472
+ // Validate image count before processing (unless called from another formatter)
473
+ if (!skipValidation) {
474
+ this.validateImageCount(images.length, "google-ai");
475
+ }
442
476
  const parts = [{ text }];
443
477
  images.forEach((image, index) => {
444
478
  try {
@@ -460,7 +494,11 @@ export class ProviderImageAdapter {
460
494
  /**
461
495
  * Format content for Anthropic (Claude format)
462
496
  */
463
- static formatForAnthropic(text, images) {
497
+ static formatForAnthropic(text, images, skipValidation = false) {
498
+ // Validate image count before processing (unless called from another formatter)
499
+ if (!skipValidation) {
500
+ this.validateImageCount(images.length, "anthropic");
501
+ }
464
502
  const content = [{ type: "text", text }];
465
503
  images.forEach((image, index) => {
466
504
  try {
@@ -488,15 +526,65 @@ export class ProviderImageAdapter {
488
526
  * Format content for Vertex AI (model-specific routing)
489
527
  */
490
528
  static formatForVertex(text, images, model) {
491
- // Route based on model type
529
+ // Validate image count with model-specific limits before processing
530
+ this.validateImageCount(images.length, "vertex", model);
531
+ // Route based on model type, skip validation in delegated methods
492
532
  if (model.includes("gemini")) {
493
- return this.formatForGoogleAI(text, images);
533
+ return this.formatForGoogleAI(text, images, true);
494
534
  }
495
535
  else if (model.includes("claude")) {
496
- return this.formatForAnthropic(text, images);
536
+ return this.formatForAnthropic(text, images, true);
537
+ }
538
+ else {
539
+ return this.formatForGoogleAI(text, images, true);
540
+ }
541
+ }
542
+ /**
543
+ * Validate image count against provider limits
544
+ * Warns at 80% threshold, throws error if limit exceeded
545
+ */
546
+ static validateImageCount(imageCount, provider, model) {
547
+ const normalizedProvider = provider.toLowerCase();
548
+ let limit;
549
+ // Determine the limit based on provider
550
+ if (normalizedProvider === "vertex" && model) {
551
+ // Vertex has model-specific limits
552
+ if (model.includes("claude")) {
553
+ limit = IMAGE_LIMITS.vertex.claude;
554
+ }
555
+ else if (model.includes("gemini")) {
556
+ limit = IMAGE_LIMITS.vertex.gemini;
557
+ }
558
+ else {
559
+ limit = IMAGE_LIMITS.vertex.default;
560
+ }
497
561
  }
498
562
  else {
499
- return this.formatForGoogleAI(text, images);
563
+ // Use provider-specific limit
564
+ const providerLimit = normalizedProvider in IMAGE_LIMITS
565
+ ? IMAGE_LIMITS[normalizedProvider]
566
+ : undefined;
567
+ // If provider not found in limits map, use a conservative default
568
+ if (providerLimit === undefined) {
569
+ // Conservative default for unknown providers
570
+ limit = 10;
571
+ logger.warn(`Image count limit not defined for provider ${provider}. Using conservative default of 10 images.`);
572
+ }
573
+ else {
574
+ // providerLimit is always a number when defined (except vertex which is handled separately)
575
+ limit = providerLimit;
576
+ }
577
+ }
578
+ // Warn only once at 80% threshold to avoid noise in batch processing
579
+ const warningThreshold = Math.floor(limit * 0.8);
580
+ if (imageCount === warningThreshold) {
581
+ logger.warn(`Image count (${imageCount}) is approaching the limit for ${provider}. ` +
582
+ `Maximum allowed: ${limit}. Please reduce the number of images.`);
583
+ }
584
+ // Throw error if limit exceeded
585
+ if (imageCount > limit) {
586
+ throw new Error(`Image count (${imageCount}) exceeds the maximum limit for ${provider}. ` +
587
+ `Maximum allowed: ${limit}. Please reduce the number of images.`);
500
588
  }
501
589
  }
502
590
  /**
@@ -85,35 +85,6 @@ export declare const VALID_TTS_QUALITIES: readonly TTSQuality[];
85
85
  * Type guard to check if an object is a TTSResult
86
86
  */
87
87
  export declare function isTTSResult(value: unknown): value is TTSResult;
88
- /**
89
- * TTS Handler type for provider-specific implementations
90
- *
91
- * Each provider (Google AI, OpenAI, etc.) implements this type
92
- * to provide TTS generation capabilities using their respective APIs.
93
- */
94
- export type TTSHandler = {
95
- /**
96
- * Generate audio from text using provider-specific TTS API
97
- *
98
- * @param text - Text to convert to speech
99
- * @param options - TTS configuration options
100
- * @returns Audio buffer with metadata
101
- */
102
- synthesize(text: string, options: TTSOptions): Promise<TTSResult>;
103
- /**
104
- * Get available voices for the provider
105
- *
106
- * @param languageCode - Optional language filter (e.g., "en-US")
107
- * @returns List of available voices
108
- */
109
- getVoices(languageCode?: string): Promise<TTSVoice[]>;
110
- /**
111
- * Validate that the provider is properly configured
112
- *
113
- * @returns True if provider can generate TTS
114
- */
115
- isConfigured(): boolean;
116
- };
117
88
  /**
118
89
  * Type guard to check if TTSOptions are valid
119
90
  */
@@ -6,7 +6,69 @@
6
6
  *
7
7
  * @module utils/ttsProcessor
8
8
  */
9
- import type { TTSHandler } from "../types/ttsTypes.js";
9
+ import type { TTSOptions, TTSResult, TTSVoice } from "../types/ttsTypes.js";
10
+ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
11
+ import { NeuroLinkError } from "./errorHandling.js";
12
+ /**
13
+ * TTS-specific error codes
14
+ */
15
+ export declare const TTS_ERROR_CODES: {
16
+ readonly EMPTY_TEXT: "TTS_EMPTY_TEXT";
17
+ readonly TEXT_TOO_LONG: "TTS_TEXT_TOO_LONG";
18
+ readonly PROVIDER_NOT_SUPPORTED: "TTS_PROVIDER_NOT_SUPPORTED";
19
+ readonly PROVIDER_NOT_CONFIGURED: "TTS_PROVIDER_NOT_CONFIGURED";
20
+ readonly SYNTHESIS_FAILED: "TTS_SYNTHESIS_FAILED";
21
+ };
22
+ /**
23
+ * TTS Error class for text-to-speech specific errors
24
+ */
25
+ export declare class TTSError extends NeuroLinkError {
26
+ constructor(options: {
27
+ code: string;
28
+ message: string;
29
+ category?: ErrorCategory;
30
+ severity?: ErrorSeverity;
31
+ retriable?: boolean;
32
+ context?: Record<string, unknown>;
33
+ originalError?: Error;
34
+ });
35
+ }
36
+ /**
37
+ * TTS Handler interface for provider-specific implementations
38
+ *
39
+ * Each provider (Google AI, OpenAI, etc.) implements this interface
40
+ * to provide TTS generation capabilities using their respective APIs.
41
+ */
42
+ export interface TTSHandler {
43
+ /**
44
+ * Generate audio from text using provider-specific TTS API
45
+ *
46
+ * @param text - Text to convert to speech
47
+ * @param options - TTS configuration options
48
+ * @returns Audio buffer with metadata
49
+ */
50
+ synthesize(text: string, options: TTSOptions): Promise<TTSResult>;
51
+ /**
52
+ * Get available voices for the provider
53
+ *
54
+ * @param languageCode - Optional language filter (e.g., "en-US")
55
+ * @returns List of available voices
56
+ */
57
+ getVoices?(languageCode?: string): Promise<TTSVoice[]>;
58
+ /**
59
+ * Validate that the provider is properly configured
60
+ *
61
+ * @returns True if provider can generate TTS
62
+ */
63
+ isConfigured(): boolean;
64
+ /**
65
+ * Maximum text length supported by this provider (in characters)
66
+ * Different providers have different limits
67
+ *
68
+ * @default 3000 if not specified
69
+ */
70
+ maxTextLength?: number;
71
+ }
10
72
  /**
11
73
  * TTS processor class for orchestrating text-to-speech operations
12
74
  *
@@ -32,6 +94,26 @@ export declare class TTSProcessor {
32
94
  * @private
33
95
  */
34
96
  private static readonly handlers;
97
+ /**
98
+ * Default maximum text length for TTS synthesis (characters)
99
+ *
100
+ * Providers can override this value by specifying the `maxTextLength` property
101
+ * in their respective `TTSHandler` implementation. If not specified, this default
102
+ * value will be used.
103
+ *
104
+ * @private
105
+ */
106
+ private static readonly DEFAULT_MAX_TEXT_LENGTH;
107
+ /**
108
+ * Default timeout for TTS synthesis operations (milliseconds)
109
+ *
110
+ * This timeout prevents indefinite hangs in provider API calls and serves as
111
+ * a safety net for all TTS operations. Individual handlers may implement
112
+ * shorter provider-specific timeouts.
113
+ *
114
+ * @private
115
+ */
116
+ private static readonly DEFAULT_SYNTHESIS_TIMEOUT_MS;
35
117
  /**
36
118
  * Register a TTS handler for a specific provider
37
119
  *
@@ -74,4 +156,33 @@ export declare class TTSProcessor {
74
156
  * ```
75
157
  */
76
158
  static supports(providerName: string): boolean;
159
+ /**
160
+ * Synthesize speech from text using a registered TTS provider
161
+ *
162
+ * Orchestrates the text-to-speech generation process:
163
+ * 1. Validates input text (not empty, within length limits)
164
+ * 2. Looks up the provider handler
165
+ * 3. Verifies provider configuration
166
+ * 4. Delegates synthesis to the provider
167
+ * 5. Enriches result with metadata
168
+ *
169
+ * @param text - Text to convert to speech
170
+ * @param provider - Provider identifier
171
+ * @param options - TTS configuration options
172
+ * @returns Audio result with buffer and metadata
173
+ * @throws TTSError if validation fails, provider not supported/configured, or synthesis times out
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const result = await TTSProcessor.synthesize("Hello, world!", "google-ai", {
178
+ * voice: "en-US-Neural2-C",
179
+ * format: "mp3",
180
+ * speed: 1.0
181
+ * });
182
+ *
183
+ * console.log(`Generated ${result.size} bytes of ${result.format} audio`);
184
+ * // Save to file or play the audio buffer
185
+ * ```
186
+ */
187
+ static synthesize(text: string, provider: string, options: TTSOptions): Promise<TTSResult>;
77
188
  }
@@ -7,6 +7,35 @@
7
7
  * @module utils/ttsProcessor
8
8
  */
9
9
  import { logger } from "./logger.js";
10
+ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
11
+ import { NeuroLinkError, withTimeout } from "./errorHandling.js";
12
+ /**
13
+ * TTS-specific error codes
14
+ */
15
+ export const TTS_ERROR_CODES = {
16
+ EMPTY_TEXT: "TTS_EMPTY_TEXT",
17
+ TEXT_TOO_LONG: "TTS_TEXT_TOO_LONG",
18
+ PROVIDER_NOT_SUPPORTED: "TTS_PROVIDER_NOT_SUPPORTED",
19
+ PROVIDER_NOT_CONFIGURED: "TTS_PROVIDER_NOT_CONFIGURED",
20
+ SYNTHESIS_FAILED: "TTS_SYNTHESIS_FAILED",
21
+ };
22
+ /**
23
+ * TTS Error class for text-to-speech specific errors
24
+ */
25
+ export class TTSError extends NeuroLinkError {
26
+ constructor(options) {
27
+ super({
28
+ code: options.code,
29
+ message: options.message,
30
+ category: options.category ?? ErrorCategory.VALIDATION,
31
+ severity: options.severity ?? ErrorSeverity.MEDIUM,
32
+ retriable: options.retriable ?? false,
33
+ context: options.context,
34
+ originalError: options.originalError,
35
+ });
36
+ this.name = "TTSError";
37
+ }
38
+ }
10
39
  /**
11
40
  * TTS processor class for orchestrating text-to-speech operations
12
41
  *
@@ -32,6 +61,26 @@ export class TTSProcessor {
32
61
  * @private
33
62
  */
34
63
  static handlers = new Map();
64
+ /**
65
+ * Default maximum text length for TTS synthesis (characters)
66
+ *
67
+ * Providers can override this value by specifying the `maxTextLength` property
68
+ * in their respective `TTSHandler` implementation. If not specified, this default
69
+ * value will be used.
70
+ *
71
+ * @private
72
+ */
73
+ static DEFAULT_MAX_TEXT_LENGTH = 3000;
74
+ /**
75
+ * Default timeout for TTS synthesis operations (milliseconds)
76
+ *
77
+ * This timeout prevents indefinite hangs in provider API calls and serves as
78
+ * a safety net for all TTS operations. Individual handlers may implement
79
+ * shorter provider-specific timeouts.
80
+ *
81
+ * @private
82
+ */
83
+ static DEFAULT_SYNTHESIS_TIMEOUT_MS = 60000;
35
84
  /**
36
85
  * Register a TTS handler for a specific provider
37
86
  *
@@ -101,5 +150,138 @@ export class TTSProcessor {
101
150
  }
102
151
  return isSupported;
103
152
  }
153
+ /**
154
+ * Synthesize speech from text using a registered TTS provider
155
+ *
156
+ * Orchestrates the text-to-speech generation process:
157
+ * 1. Validates input text (not empty, within length limits)
158
+ * 2. Looks up the provider handler
159
+ * 3. Verifies provider configuration
160
+ * 4. Delegates synthesis to the provider
161
+ * 5. Enriches result with metadata
162
+ *
163
+ * @param text - Text to convert to speech
164
+ * @param provider - Provider identifier
165
+ * @param options - TTS configuration options
166
+ * @returns Audio result with buffer and metadata
167
+ * @throws TTSError if validation fails, provider not supported/configured, or synthesis times out
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const result = await TTSProcessor.synthesize("Hello, world!", "google-ai", {
172
+ * voice: "en-US-Neural2-C",
173
+ * format: "mp3",
174
+ * speed: 1.0
175
+ * });
176
+ *
177
+ * console.log(`Generated ${result.size} bytes of ${result.format} audio`);
178
+ * // Save to file or play the audio buffer
179
+ * ```
180
+ */
181
+ static async synthesize(text, provider, options) {
182
+ // Trim the text once at the start
183
+ const trimmedText = text.trim();
184
+ // 1. Text validation: reject empty text
185
+ if (!trimmedText) {
186
+ logger.error("[TTSProcessor] Text is required for synthesis");
187
+ throw new TTSError({
188
+ code: TTS_ERROR_CODES.EMPTY_TEXT,
189
+ message: "Text is required for TTS synthesis",
190
+ severity: ErrorSeverity.LOW,
191
+ retriable: false,
192
+ context: { provider },
193
+ });
194
+ }
195
+ // 2. Handler lookup and error if provider not supported
196
+ const handler = this.getHandler(provider);
197
+ if (!handler) {
198
+ logger.error(`[TTSProcessor] Provider "${provider}" is not registered`);
199
+ throw new TTSError({
200
+ code: TTS_ERROR_CODES.PROVIDER_NOT_SUPPORTED,
201
+ message: `TTS provider "${provider}" is not supported. Use TTSProcessor.registerHandler() to register it.`,
202
+ severity: ErrorSeverity.HIGH,
203
+ retriable: false,
204
+ context: {
205
+ provider,
206
+ availableProviders: Array.from(this.handlers.keys()),
207
+ },
208
+ });
209
+ }
210
+ // 3. Text validation: reject text exceeding provider-specific max length
211
+ const maxTextLength = handler.maxTextLength ?? this.DEFAULT_MAX_TEXT_LENGTH;
212
+ if (trimmedText.length > maxTextLength) {
213
+ logger.error(`[TTSProcessor] Text exceeds maximum length of ${maxTextLength} characters for provider "${provider}"`);
214
+ throw new TTSError({
215
+ code: TTS_ERROR_CODES.TEXT_TOO_LONG,
216
+ message: `Text length (${trimmedText.length}) exceeds maximum allowed length (${maxTextLength} characters) for provider "${provider}"`,
217
+ severity: ErrorSeverity.MEDIUM,
218
+ retriable: false,
219
+ context: {
220
+ provider,
221
+ textLength: trimmedText.length,
222
+ maxLength: maxTextLength,
223
+ },
224
+ });
225
+ }
226
+ // 4. Configuration check
227
+ if (!handler.isConfigured()) {
228
+ logger.warn(`[TTSProcessor] Provider "${provider}" is not properly configured`);
229
+ throw new TTSError({
230
+ code: TTS_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
231
+ message: `TTS provider "${provider}" is not configured. Please set the required API keys.`,
232
+ category: ErrorCategory.CONFIGURATION,
233
+ severity: ErrorSeverity.HIGH,
234
+ retriable: false,
235
+ context: { provider },
236
+ });
237
+ }
238
+ try {
239
+ logger.debug(`[TTSProcessor] Starting synthesis with provider: ${provider}`);
240
+ // 5. Call handler.synthesize() with timeout protection (60 second safety net)
241
+ const result = await withTimeout(handler.synthesize(trimmedText, options), this.DEFAULT_SYNTHESIS_TIMEOUT_MS, new TTSError({
242
+ code: TTS_ERROR_CODES.SYNTHESIS_FAILED,
243
+ message: `TTS synthesis timeout for provider "${provider}" after ${this.DEFAULT_SYNTHESIS_TIMEOUT_MS}ms`,
244
+ category: ErrorCategory.EXECUTION,
245
+ severity: ErrorSeverity.HIGH,
246
+ retriable: true,
247
+ context: {
248
+ provider,
249
+ timeoutMs: this.DEFAULT_SYNTHESIS_TIMEOUT_MS,
250
+ textLength: trimmedText.length,
251
+ },
252
+ }));
253
+ // 6. Post-processing: add metadata
254
+ const enrichedResult = {
255
+ ...result,
256
+ voice: result.voice ?? options.voice,
257
+ };
258
+ logger.info(`[TTSProcessor] Successfully synthesized ${result.size} bytes of audio`);
259
+ // 7. Returns TTSResult with buffer, format, metadata
260
+ return enrichedResult;
261
+ }
262
+ catch (err) {
263
+ // 8. Comprehensive error handling
264
+ // Re-throw TTSError as-is
265
+ if (err instanceof TTSError) {
266
+ throw err;
267
+ }
268
+ // Wrap other errors in TTSError
269
+ const errorMessage = err instanceof Error ? err.message : String(err || "Unknown error");
270
+ logger.error(`[TTSProcessor] Synthesis failed for provider "${provider}": ${errorMessage}`);
271
+ throw new TTSError({
272
+ code: TTS_ERROR_CODES.SYNTHESIS_FAILED,
273
+ message: `TTS synthesis failed for provider "${provider}": ${errorMessage}`,
274
+ category: ErrorCategory.EXECUTION,
275
+ severity: ErrorSeverity.HIGH,
276
+ retriable: true,
277
+ context: {
278
+ provider,
279
+ textLength: trimmedText.length,
280
+ options,
281
+ },
282
+ originalError: err instanceof Error ? err : undefined,
283
+ });
284
+ }
285
+ }
104
286
  }
105
287
  //# sourceMappingURL=ttsProcessor.js.map
@@ -85,35 +85,6 @@ export declare const VALID_TTS_QUALITIES: readonly TTSQuality[];
85
85
  * Type guard to check if an object is a TTSResult
86
86
  */
87
87
  export declare function isTTSResult(value: unknown): value is TTSResult;
88
- /**
89
- * TTS Handler type for provider-specific implementations
90
- *
91
- * Each provider (Google AI, OpenAI, etc.) implements this type
92
- * to provide TTS generation capabilities using their respective APIs.
93
- */
94
- export type TTSHandler = {
95
- /**
96
- * Generate audio from text using provider-specific TTS API
97
- *
98
- * @param text - Text to convert to speech
99
- * @param options - TTS configuration options
100
- * @returns Audio buffer with metadata
101
- */
102
- synthesize(text: string, options: TTSOptions): Promise<TTSResult>;
103
- /**
104
- * Get available voices for the provider
105
- *
106
- * @param languageCode - Optional language filter (e.g., "en-US")
107
- * @returns List of available voices
108
- */
109
- getVoices(languageCode?: string): Promise<TTSVoice[]>;
110
- /**
111
- * Validate that the provider is properly configured
112
- *
113
- * @returns True if provider can generate TTS
114
- */
115
- isConfigured(): boolean;
116
- };
117
88
  /**
118
89
  * Type guard to check if TTSOptions are valid
119
90
  */
@@ -6,7 +6,69 @@
6
6
  *
7
7
  * @module utils/ttsProcessor
8
8
  */
9
- import type { TTSHandler } from "../types/ttsTypes.js";
9
+ import type { TTSOptions, TTSResult, TTSVoice } from "../types/ttsTypes.js";
10
+ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
11
+ import { NeuroLinkError } from "./errorHandling.js";
12
+ /**
13
+ * TTS-specific error codes
14
+ */
15
+ export declare const TTS_ERROR_CODES: {
16
+ readonly EMPTY_TEXT: "TTS_EMPTY_TEXT";
17
+ readonly TEXT_TOO_LONG: "TTS_TEXT_TOO_LONG";
18
+ readonly PROVIDER_NOT_SUPPORTED: "TTS_PROVIDER_NOT_SUPPORTED";
19
+ readonly PROVIDER_NOT_CONFIGURED: "TTS_PROVIDER_NOT_CONFIGURED";
20
+ readonly SYNTHESIS_FAILED: "TTS_SYNTHESIS_FAILED";
21
+ };
22
+ /**
23
+ * TTS Error class for text-to-speech specific errors
24
+ */
25
+ export declare class TTSError extends NeuroLinkError {
26
+ constructor(options: {
27
+ code: string;
28
+ message: string;
29
+ category?: ErrorCategory;
30
+ severity?: ErrorSeverity;
31
+ retriable?: boolean;
32
+ context?: Record<string, unknown>;
33
+ originalError?: Error;
34
+ });
35
+ }
36
+ /**
37
+ * TTS Handler interface for provider-specific implementations
38
+ *
39
+ * Each provider (Google AI, OpenAI, etc.) implements this interface
40
+ * to provide TTS generation capabilities using their respective APIs.
41
+ */
42
+ export interface TTSHandler {
43
+ /**
44
+ * Generate audio from text using provider-specific TTS API
45
+ *
46
+ * @param text - Text to convert to speech
47
+ * @param options - TTS configuration options
48
+ * @returns Audio buffer with metadata
49
+ */
50
+ synthesize(text: string, options: TTSOptions): Promise<TTSResult>;
51
+ /**
52
+ * Get available voices for the provider
53
+ *
54
+ * @param languageCode - Optional language filter (e.g., "en-US")
55
+ * @returns List of available voices
56
+ */
57
+ getVoices?(languageCode?: string): Promise<TTSVoice[]>;
58
+ /**
59
+ * Validate that the provider is properly configured
60
+ *
61
+ * @returns True if provider can generate TTS
62
+ */
63
+ isConfigured(): boolean;
64
+ /**
65
+ * Maximum text length supported by this provider (in characters)
66
+ * Different providers have different limits
67
+ *
68
+ * @default 3000 if not specified
69
+ */
70
+ maxTextLength?: number;
71
+ }
10
72
  /**
11
73
  * TTS processor class for orchestrating text-to-speech operations
12
74
  *
@@ -32,6 +94,26 @@ export declare class TTSProcessor {
32
94
  * @private
33
95
  */
34
96
  private static readonly handlers;
97
+ /**
98
+ * Default maximum text length for TTS synthesis (characters)
99
+ *
100
+ * Providers can override this value by specifying the `maxTextLength` property
101
+ * in their respective `TTSHandler` implementation. If not specified, this default
102
+ * value will be used.
103
+ *
104
+ * @private
105
+ */
106
+ private static readonly DEFAULT_MAX_TEXT_LENGTH;
107
+ /**
108
+ * Default timeout for TTS synthesis operations (milliseconds)
109
+ *
110
+ * This timeout prevents indefinite hangs in provider API calls and serves as
111
+ * a safety net for all TTS operations. Individual handlers may implement
112
+ * shorter provider-specific timeouts.
113
+ *
114
+ * @private
115
+ */
116
+ private static readonly DEFAULT_SYNTHESIS_TIMEOUT_MS;
35
117
  /**
36
118
  * Register a TTS handler for a specific provider
37
119
  *
@@ -74,4 +156,33 @@ export declare class TTSProcessor {
74
156
  * ```
75
157
  */
76
158
  static supports(providerName: string): boolean;
159
+ /**
160
+ * Synthesize speech from text using a registered TTS provider
161
+ *
162
+ * Orchestrates the text-to-speech generation process:
163
+ * 1. Validates input text (not empty, within length limits)
164
+ * 2. Looks up the provider handler
165
+ * 3. Verifies provider configuration
166
+ * 4. Delegates synthesis to the provider
167
+ * 5. Enriches result with metadata
168
+ *
169
+ * @param text - Text to convert to speech
170
+ * @param provider - Provider identifier
171
+ * @param options - TTS configuration options
172
+ * @returns Audio result with buffer and metadata
173
+ * @throws TTSError if validation fails, provider not supported/configured, or synthesis times out
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const result = await TTSProcessor.synthesize("Hello, world!", "google-ai", {
178
+ * voice: "en-US-Neural2-C",
179
+ * format: "mp3",
180
+ * speed: 1.0
181
+ * });
182
+ *
183
+ * console.log(`Generated ${result.size} bytes of ${result.format} audio`);
184
+ * // Save to file or play the audio buffer
185
+ * ```
186
+ */
187
+ static synthesize(text: string, provider: string, options: TTSOptions): Promise<TTSResult>;
77
188
  }
@@ -7,6 +7,35 @@
7
7
  * @module utils/ttsProcessor
8
8
  */
9
9
  import { logger } from "./logger.js";
10
+ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
11
+ import { NeuroLinkError, withTimeout } from "./errorHandling.js";
12
+ /**
13
+ * TTS-specific error codes
14
+ */
15
+ export const TTS_ERROR_CODES = {
16
+ EMPTY_TEXT: "TTS_EMPTY_TEXT",
17
+ TEXT_TOO_LONG: "TTS_TEXT_TOO_LONG",
18
+ PROVIDER_NOT_SUPPORTED: "TTS_PROVIDER_NOT_SUPPORTED",
19
+ PROVIDER_NOT_CONFIGURED: "TTS_PROVIDER_NOT_CONFIGURED",
20
+ SYNTHESIS_FAILED: "TTS_SYNTHESIS_FAILED",
21
+ };
22
+ /**
23
+ * TTS Error class for text-to-speech specific errors
24
+ */
25
+ export class TTSError extends NeuroLinkError {
26
+ constructor(options) {
27
+ super({
28
+ code: options.code,
29
+ message: options.message,
30
+ category: options.category ?? ErrorCategory.VALIDATION,
31
+ severity: options.severity ?? ErrorSeverity.MEDIUM,
32
+ retriable: options.retriable ?? false,
33
+ context: options.context,
34
+ originalError: options.originalError,
35
+ });
36
+ this.name = "TTSError";
37
+ }
38
+ }
10
39
  /**
11
40
  * TTS processor class for orchestrating text-to-speech operations
12
41
  *
@@ -32,6 +61,26 @@ export class TTSProcessor {
32
61
  * @private
33
62
  */
34
63
  static handlers = new Map();
64
+ /**
65
+ * Default maximum text length for TTS synthesis (characters)
66
+ *
67
+ * Providers can override this value by specifying the `maxTextLength` property
68
+ * in their respective `TTSHandler` implementation. If not specified, this default
69
+ * value will be used.
70
+ *
71
+ * @private
72
+ */
73
+ static DEFAULT_MAX_TEXT_LENGTH = 3000;
74
+ /**
75
+ * Default timeout for TTS synthesis operations (milliseconds)
76
+ *
77
+ * This timeout prevents indefinite hangs in provider API calls and serves as
78
+ * a safety net for all TTS operations. Individual handlers may implement
79
+ * shorter provider-specific timeouts.
80
+ *
81
+ * @private
82
+ */
83
+ static DEFAULT_SYNTHESIS_TIMEOUT_MS = 60000;
35
84
  /**
36
85
  * Register a TTS handler for a specific provider
37
86
  *
@@ -101,4 +150,137 @@ export class TTSProcessor {
101
150
  }
102
151
  return isSupported;
103
152
  }
153
+ /**
154
+ * Synthesize speech from text using a registered TTS provider
155
+ *
156
+ * Orchestrates the text-to-speech generation process:
157
+ * 1. Validates input text (not empty, within length limits)
158
+ * 2. Looks up the provider handler
159
+ * 3. Verifies provider configuration
160
+ * 4. Delegates synthesis to the provider
161
+ * 5. Enriches result with metadata
162
+ *
163
+ * @param text - Text to convert to speech
164
+ * @param provider - Provider identifier
165
+ * @param options - TTS configuration options
166
+ * @returns Audio result with buffer and metadata
167
+ * @throws TTSError if validation fails, provider not supported/configured, or synthesis times out
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const result = await TTSProcessor.synthesize("Hello, world!", "google-ai", {
172
+ * voice: "en-US-Neural2-C",
173
+ * format: "mp3",
174
+ * speed: 1.0
175
+ * });
176
+ *
177
+ * console.log(`Generated ${result.size} bytes of ${result.format} audio`);
178
+ * // Save to file or play the audio buffer
179
+ * ```
180
+ */
181
+ static async synthesize(text, provider, options) {
182
+ // Trim the text once at the start
183
+ const trimmedText = text.trim();
184
+ // 1. Text validation: reject empty text
185
+ if (!trimmedText) {
186
+ logger.error("[TTSProcessor] Text is required for synthesis");
187
+ throw new TTSError({
188
+ code: TTS_ERROR_CODES.EMPTY_TEXT,
189
+ message: "Text is required for TTS synthesis",
190
+ severity: ErrorSeverity.LOW,
191
+ retriable: false,
192
+ context: { provider },
193
+ });
194
+ }
195
+ // 2. Handler lookup and error if provider not supported
196
+ const handler = this.getHandler(provider);
197
+ if (!handler) {
198
+ logger.error(`[TTSProcessor] Provider "${provider}" is not registered`);
199
+ throw new TTSError({
200
+ code: TTS_ERROR_CODES.PROVIDER_NOT_SUPPORTED,
201
+ message: `TTS provider "${provider}" is not supported. Use TTSProcessor.registerHandler() to register it.`,
202
+ severity: ErrorSeverity.HIGH,
203
+ retriable: false,
204
+ context: {
205
+ provider,
206
+ availableProviders: Array.from(this.handlers.keys()),
207
+ },
208
+ });
209
+ }
210
+ // 3. Text validation: reject text exceeding provider-specific max length
211
+ const maxTextLength = handler.maxTextLength ?? this.DEFAULT_MAX_TEXT_LENGTH;
212
+ if (trimmedText.length > maxTextLength) {
213
+ logger.error(`[TTSProcessor] Text exceeds maximum length of ${maxTextLength} characters for provider "${provider}"`);
214
+ throw new TTSError({
215
+ code: TTS_ERROR_CODES.TEXT_TOO_LONG,
216
+ message: `Text length (${trimmedText.length}) exceeds maximum allowed length (${maxTextLength} characters) for provider "${provider}"`,
217
+ severity: ErrorSeverity.MEDIUM,
218
+ retriable: false,
219
+ context: {
220
+ provider,
221
+ textLength: trimmedText.length,
222
+ maxLength: maxTextLength,
223
+ },
224
+ });
225
+ }
226
+ // 4. Configuration check
227
+ if (!handler.isConfigured()) {
228
+ logger.warn(`[TTSProcessor] Provider "${provider}" is not properly configured`);
229
+ throw new TTSError({
230
+ code: TTS_ERROR_CODES.PROVIDER_NOT_CONFIGURED,
231
+ message: `TTS provider "${provider}" is not configured. Please set the required API keys.`,
232
+ category: ErrorCategory.CONFIGURATION,
233
+ severity: ErrorSeverity.HIGH,
234
+ retriable: false,
235
+ context: { provider },
236
+ });
237
+ }
238
+ try {
239
+ logger.debug(`[TTSProcessor] Starting synthesis with provider: ${provider}`);
240
+ // 5. Call handler.synthesize() with timeout protection (60 second safety net)
241
+ const result = await withTimeout(handler.synthesize(trimmedText, options), this.DEFAULT_SYNTHESIS_TIMEOUT_MS, new TTSError({
242
+ code: TTS_ERROR_CODES.SYNTHESIS_FAILED,
243
+ message: `TTS synthesis timeout for provider "${provider}" after ${this.DEFAULT_SYNTHESIS_TIMEOUT_MS}ms`,
244
+ category: ErrorCategory.EXECUTION,
245
+ severity: ErrorSeverity.HIGH,
246
+ retriable: true,
247
+ context: {
248
+ provider,
249
+ timeoutMs: this.DEFAULT_SYNTHESIS_TIMEOUT_MS,
250
+ textLength: trimmedText.length,
251
+ },
252
+ }));
253
+ // 6. Post-processing: add metadata
254
+ const enrichedResult = {
255
+ ...result,
256
+ voice: result.voice ?? options.voice,
257
+ };
258
+ logger.info(`[TTSProcessor] Successfully synthesized ${result.size} bytes of audio`);
259
+ // 7. Returns TTSResult with buffer, format, metadata
260
+ return enrichedResult;
261
+ }
262
+ catch (err) {
263
+ // 8. Comprehensive error handling
264
+ // Re-throw TTSError as-is
265
+ if (err instanceof TTSError) {
266
+ throw err;
267
+ }
268
+ // Wrap other errors in TTSError
269
+ const errorMessage = err instanceof Error ? err.message : String(err || "Unknown error");
270
+ logger.error(`[TTSProcessor] Synthesis failed for provider "${provider}": ${errorMessage}`);
271
+ throw new TTSError({
272
+ code: TTS_ERROR_CODES.SYNTHESIS_FAILED,
273
+ message: `TTS synthesis failed for provider "${provider}": ${errorMessage}`,
274
+ category: ErrorCategory.EXECUTION,
275
+ severity: ErrorSeverity.HIGH,
276
+ retriable: true,
277
+ context: {
278
+ provider,
279
+ textLength: trimmedText.length,
280
+ options,
281
+ },
282
+ originalError: err instanceof Error ? err : undefined,
283
+ });
284
+ }
285
+ }
104
286
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "8.12.0",
3
+ "version": "8.13.1",
4
4
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",