@juspay/neurolink 11.2.2 → 11.2.4

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 (67) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +408 -408
  3. package/dist/core/handlerRegistry.d.ts +29 -0
  4. package/dist/core/handlerRegistry.js +61 -0
  5. package/dist/factories/providerRegistry.js +14 -44
  6. package/dist/lib/core/handlerRegistry.d.ts +29 -0
  7. package/dist/lib/core/handlerRegistry.js +62 -0
  8. package/dist/lib/factories/providerRegistry.js +14 -44
  9. package/dist/lib/providers/configuredOpenAICompat.js +10 -3
  10. package/dist/lib/providers/openaiCompatCatalog.js +9 -11
  11. package/dist/lib/types/providers.d.ts +15 -1
  12. package/dist/lib/utils/avatarProcessor.d.ts +5 -1
  13. package/dist/lib/utils/avatarProcessor.js +13 -18
  14. package/dist/lib/utils/musicProcessor.d.ts +5 -1
  15. package/dist/lib/utils/musicProcessor.js +13 -18
  16. package/dist/lib/utils/sttProcessor.d.ts +10 -2
  17. package/dist/lib/utils/sttProcessor.js +22 -18
  18. package/dist/lib/utils/ttsProcessor.d.ts +10 -2
  19. package/dist/lib/utils/ttsProcessor.js +22 -18
  20. package/dist/lib/utils/videoProcessor.d.ts +5 -1
  21. package/dist/lib/utils/videoProcessor.js +13 -18
  22. package/dist/lib/voice/RealtimeVoiceAPI.d.ts +1 -1
  23. package/dist/lib/voice/RealtimeVoiceAPI.js +17 -28
  24. package/dist/providers/configuredOpenAICompat.js +10 -3
  25. package/dist/providers/openaiCompatCatalog.js +9 -11
  26. package/dist/types/providers.d.ts +15 -1
  27. package/dist/utils/avatarProcessor.d.ts +5 -1
  28. package/dist/utils/avatarProcessor.js +13 -18
  29. package/dist/utils/musicProcessor.d.ts +5 -1
  30. package/dist/utils/musicProcessor.js +13 -18
  31. package/dist/utils/sttProcessor.d.ts +10 -2
  32. package/dist/utils/sttProcessor.js +22 -18
  33. package/dist/utils/ttsProcessor.d.ts +10 -2
  34. package/dist/utils/ttsProcessor.js +22 -18
  35. package/dist/utils/videoProcessor.d.ts +5 -1
  36. package/dist/utils/videoProcessor.js +13 -18
  37. package/dist/voice/RealtimeVoiceAPI.d.ts +1 -1
  38. package/dist/voice/RealtimeVoiceAPI.js +17 -28
  39. package/package.json +8 -2
  40. package/dist/lib/providers/cloudflare.d.ts +0 -26
  41. package/dist/lib/providers/cloudflare.js +0 -83
  42. package/dist/lib/providers/fireworks.d.ts +0 -22
  43. package/dist/lib/providers/fireworks.js +0 -73
  44. package/dist/lib/providers/groq.d.ts +0 -24
  45. package/dist/lib/providers/groq.js +0 -83
  46. package/dist/lib/providers/mistral.d.ts +0 -25
  47. package/dist/lib/providers/mistral.js +0 -87
  48. package/dist/lib/providers/perplexity.d.ts +0 -24
  49. package/dist/lib/providers/perplexity.js +0 -69
  50. package/dist/lib/providers/togetherAi.d.ts +0 -23
  51. package/dist/lib/providers/togetherAi.js +0 -73
  52. package/dist/lib/providers/xai.d.ts +0 -22
  53. package/dist/lib/providers/xai.js +0 -77
  54. package/dist/providers/cloudflare.d.ts +0 -26
  55. package/dist/providers/cloudflare.js +0 -82
  56. package/dist/providers/fireworks.d.ts +0 -22
  57. package/dist/providers/fireworks.js +0 -72
  58. package/dist/providers/groq.d.ts +0 -24
  59. package/dist/providers/groq.js +0 -82
  60. package/dist/providers/mistral.d.ts +0 -25
  61. package/dist/providers/mistral.js +0 -86
  62. package/dist/providers/perplexity.d.ts +0 -24
  63. package/dist/providers/perplexity.js +0 -68
  64. package/dist/providers/togetherAi.d.ts +0 -23
  65. package/dist/providers/togetherAi.js +0 -72
  66. package/dist/providers/xai.d.ts +0 -22
  67. package/dist/providers/xai.js +0 -76
@@ -27,11 +27,10 @@ import type { STTOptions, STTResult, STTHandler } from "../types/index.js";
27
27
  export declare class STTProcessor {
28
28
  /**
29
29
  * Handler registry mapping provider names to STT handlers
30
- * Uses Map for O(1) lookups and better type safety
31
30
  *
32
31
  * @private
33
32
  */
34
- private static readonly handlers;
33
+ private static readonly registry;
35
34
  /**
36
35
  * Default maximum audio duration for STT transcription (in seconds)
37
36
  *
@@ -72,6 +71,15 @@ export declare class STTProcessor {
72
71
  * @returns Handler instance or undefined if not registered
73
72
  */
74
73
  static getHandler(providerName: string): STTHandler | undefined;
74
+ /**
75
+ * List the names of all registered providers.
76
+ */
77
+ static listProviders(): string[];
78
+ /**
79
+ * Removes every registered STT handler. Primarily for test isolation —
80
+ * production code should not need to call this.
81
+ */
82
+ static clearHandlers(): void;
75
83
  /**
76
84
  * Check if a provider is supported (has a registered STT handler)
77
85
  *
@@ -10,6 +10,7 @@ import { logger } from "./logger.js";
10
10
  import { STT_ERROR_CODES } from "../types/index.js";
11
11
  import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
12
12
  import { STTError } from "../voice/errors.js";
13
+ import { HandlerRegistry } from "../core/handlerRegistry.js";
13
14
  import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
14
15
  /**
15
16
  * STT processor class for orchestrating speech-to-text operations
@@ -31,11 +32,10 @@ import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../
31
32
  export class STTProcessor {
32
33
  /**
33
34
  * Handler registry mapping provider names to STT handlers
34
- * Uses Map for O(1) lookups and better type safety
35
35
  *
36
36
  * @private
37
37
  */
38
- static handlers = new Map();
38
+ static registry = new HandlerRegistry("STTProcessor");
39
39
  /**
40
40
  * Default maximum audio duration for STT transcription (in seconds)
41
41
  *
@@ -66,17 +66,10 @@ export class STTProcessor {
66
66
  * ```
67
67
  */
68
68
  static registerHandler(providerName, handler) {
69
- if (!providerName) {
70
- throw new Error("Provider name is required");
71
- }
72
- if (!handler) {
73
- throw new Error("Handler is required");
74
- }
75
- const normalizedName = providerName.toLowerCase();
76
- if (this.handlers.has(normalizedName)) {
77
- logger.warn(`[STTProcessor] Overwriting existing handler for provider: ${normalizedName}`);
78
- }
79
- this.handlers.set(normalizedName, handler);
69
+ const normalizedName = providerName
70
+ ? providerName.toLowerCase()
71
+ : providerName;
72
+ this.registry.register(providerName, handler);
80
73
  logger.debug(`[STTProcessor] Registered STT handler for provider: ${normalizedName}`);
81
74
  }
82
75
  /**
@@ -89,8 +82,20 @@ export class STTProcessor {
89
82
  * @returns Handler instance or undefined if not registered
90
83
  */
91
84
  static getHandler(providerName) {
92
- const normalizedName = providerName.toLowerCase();
93
- return this.handlers.get(normalizedName);
85
+ return this.registry.get(providerName);
86
+ }
87
+ /**
88
+ * List the names of all registered providers.
89
+ */
90
+ static listProviders() {
91
+ return this.registry.list();
92
+ }
93
+ /**
94
+ * Removes every registered STT handler. Primarily for test isolation —
95
+ * production code should not need to call this.
96
+ */
97
+ static clearHandlers() {
98
+ this.registry.clear();
94
99
  }
95
100
  /**
96
101
  * Check if a provider is supported (has a registered STT handler)
@@ -110,8 +115,7 @@ export class STTProcessor {
110
115
  logger.error("[STTProcessor] Provider name is required for supports check");
111
116
  return false;
112
117
  }
113
- const normalizedName = providerName.toLowerCase();
114
- const isSupported = this.handlers.has(normalizedName);
118
+ const isSupported = this.registry.supports(providerName);
115
119
  if (!isSupported) {
116
120
  logger.debug(`[STTProcessor] Provider ${providerName} is not supported`);
117
121
  }
@@ -192,7 +196,7 @@ export class STTProcessor {
192
196
  retriable: false,
193
197
  context: {
194
198
  provider,
195
- availableProviders: Array.from(this.handlers.keys()),
199
+ availableProviders: this.registry.list(),
196
200
  },
197
201
  });
198
202
  }
@@ -54,11 +54,10 @@ export declare class TTSError extends NeuroLinkError {
54
54
  export declare class TTSProcessor {
55
55
  /**
56
56
  * Handler registry mapping provider names to TTS handlers
57
- * Uses Map for O(1) lookups and better type safety
58
57
  *
59
58
  * @private
60
59
  */
61
- private static readonly handlers;
60
+ private static readonly registry;
62
61
  /**
63
62
  * Default maximum text length for TTS synthesis (in bytes)
64
63
  *
@@ -100,6 +99,15 @@ export declare class TTSProcessor {
100
99
  * @returns Handler instance or undefined if not registered
101
100
  */
102
101
  static getHandler(providerName: string): TTSHandler | undefined;
102
+ /**
103
+ * List the names of all registered providers.
104
+ */
105
+ static listProviders(): string[];
106
+ /**
107
+ * Removes every registered TTS handler. Primarily for test isolation —
108
+ * production code should not need to call this.
109
+ */
110
+ static clearHandlers(): void;
103
111
  /**
104
112
  * Check if a provider is supported (has a registered TTS handler)
105
113
  *
@@ -9,6 +9,7 @@
9
9
  import { logger } from "./logger.js";
10
10
  import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
11
11
  import { NeuroLinkError } from "./errorHandling.js";
12
+ import { HandlerRegistry } from "../core/handlerRegistry.js";
12
13
  import { SpanSerializer, SpanType, SpanStatus, getMetricsAggregator, } from "../observability/index.js";
13
14
  /**
14
15
  * TTS-specific error codes
@@ -58,11 +59,10 @@ export class TTSError extends NeuroLinkError {
58
59
  export class TTSProcessor {
59
60
  /**
60
61
  * Handler registry mapping provider names to TTS handlers
61
- * Uses Map for O(1) lookups and better type safety
62
62
  *
63
63
  * @private
64
64
  */
65
- static handlers = new Map();
65
+ static registry = new HandlerRegistry("TTSProcessor");
66
66
  /**
67
67
  * Default maximum text length for TTS synthesis (in bytes)
68
68
  *
@@ -93,17 +93,10 @@ export class TTSProcessor {
93
93
  * ```
94
94
  */
95
95
  static registerHandler(providerName, handler) {
96
- if (!providerName) {
97
- throw new Error("Provider name is required");
98
- }
99
- if (!handler) {
100
- throw new Error("Handler is required");
101
- }
102
- const normalizedName = providerName.toLowerCase();
103
- if (this.handlers.has(normalizedName)) {
104
- logger.warn(`[TTSProcessor] Overwriting existing handler for provider: ${normalizedName}`);
105
- }
106
- this.handlers.set(normalizedName, handler);
96
+ const normalizedName = providerName
97
+ ? providerName.toLowerCase()
98
+ : providerName;
99
+ this.registry.register(providerName, handler);
107
100
  logger.debug(`[TTSProcessor] Registered TTS handler for provider: ${normalizedName}`);
108
101
  }
109
102
  /**
@@ -117,8 +110,20 @@ export class TTSProcessor {
117
110
  * @returns Handler instance or undefined if not registered
118
111
  */
119
112
  static getHandler(providerName) {
120
- const normalizedName = providerName.toLowerCase();
121
- return this.handlers.get(normalizedName);
113
+ return this.registry.get(providerName);
114
+ }
115
+ /**
116
+ * List the names of all registered providers.
117
+ */
118
+ static listProviders() {
119
+ return this.registry.list();
120
+ }
121
+ /**
122
+ * Removes every registered TTS handler. Primarily for test isolation —
123
+ * production code should not need to call this.
124
+ */
125
+ static clearHandlers() {
126
+ this.registry.clear();
122
127
  }
123
128
  /**
124
129
  * Check if a provider is supported (has a registered TTS handler)
@@ -138,8 +143,7 @@ export class TTSProcessor {
138
143
  logger.error("[TTSProcessor] Provider name is required for supports check");
139
144
  return false;
140
145
  }
141
- const normalizedName = providerName.toLowerCase();
142
- const isSupported = this.handlers.has(normalizedName);
146
+ const isSupported = this.registry.supports(providerName);
143
147
  if (!isSupported) {
144
148
  logger.debug(`[TTSProcessor] Provider ${providerName} is not supported`);
145
149
  }
@@ -211,7 +215,7 @@ export class TTSProcessor {
211
215
  retriable: false,
212
216
  context: {
213
217
  provider,
214
- availableProviders: Array.from(this.handlers.keys()),
218
+ availableProviders: this.registry.list(),
215
219
  },
216
220
  });
217
221
  }
@@ -22,7 +22,7 @@ export { VideoError, VIDEO_ERROR_CODES };
22
22
  * O(1) on a normalised lower-case provider key.
23
23
  */
24
24
  export declare class VideoProcessor {
25
- private static readonly handlers;
25
+ private static readonly registry;
26
26
  /**
27
27
  * Register a video handler for a specific provider.
28
28
  */
@@ -36,6 +36,10 @@ export declare class VideoProcessor {
36
36
  */
37
37
  static listProviders(): string[];
38
38
  private static getHandler;
39
+ /**
40
+ * Clear all registered handlers (for testing).
41
+ */
42
+ static clearHandlers(): void;
39
43
  private static buildSpanAttributes;
40
44
  /**
41
45
  * Generate a single video clip via the registered handler.
@@ -19,6 +19,7 @@ import { logger } from "./logger.js";
19
19
  // the same module. Both throws and instanceof checks resolve to the same
20
20
  // class.
21
21
  import { VideoError } from "../adapters/video/vertexVideoHandler.js";
22
+ import { HandlerRegistry } from "../core/handlerRegistry.js";
22
23
  export { VideoError, VIDEO_ERROR_CODES };
23
24
  /**
24
25
  * Static processor managing the video handler registry.
@@ -28,41 +29,35 @@ export { VideoError, VIDEO_ERROR_CODES };
28
29
  * O(1) on a normalised lower-case provider key.
29
30
  */
30
31
  export class VideoProcessor {
31
- static handlers = new Map();
32
+ static registry = new HandlerRegistry("VideoProcessor");
32
33
  /**
33
34
  * Register a video handler for a specific provider.
34
35
  */
35
36
  static registerHandler(providerName, handler) {
36
- if (!providerName) {
37
- throw new Error("Provider name is required");
38
- }
39
- if (!handler) {
40
- throw new Error("Handler is required");
41
- }
42
- const key = providerName.toLowerCase();
43
- if (this.handlers.has(key)) {
44
- logger.warn(`[VideoProcessor] Overwriting existing handler for provider: ${key}`);
45
- }
46
- this.handlers.set(key, handler);
37
+ const key = providerName ? providerName.toLowerCase() : providerName;
38
+ this.registry.register(providerName, handler);
47
39
  logger.debug(`[VideoProcessor] Registered video handler: ${key}`);
48
40
  }
49
41
  /**
50
42
  * Check if a provider has a registered video handler.
51
43
  */
52
44
  static supports(providerName) {
53
- if (!providerName) {
54
- return false;
55
- }
56
- return this.handlers.has(providerName.toLowerCase());
45
+ return this.registry.supports(providerName);
57
46
  }
58
47
  /**
59
48
  * List the names of all registered providers.
60
49
  */
61
50
  static listProviders() {
62
- return Array.from(this.handlers.keys());
51
+ return this.registry.list();
63
52
  }
64
53
  static getHandler(providerName) {
65
- return this.handlers.get(providerName.toLowerCase());
54
+ return this.registry.get(providerName);
55
+ }
56
+ /**
57
+ * Clear all registered handlers (for testing).
58
+ */
59
+ static clearHandlers() {
60
+ this.registry.clear();
66
61
  }
67
62
  static buildSpanAttributes(provider, options) {
68
63
  return {
@@ -36,7 +36,7 @@ export declare class RealtimeProcessor {
36
36
  /**
37
37
  * Handler registry mapping provider names to Realtime handlers
38
38
  */
39
- private static readonly handlers;
39
+ private static readonly registry;
40
40
  /**
41
41
  * Active sessions by provider
42
42
  */
@@ -10,6 +10,7 @@ import { logger } from "../utils/logger.js";
10
10
  import { RealtimeError } from "./errors.js";
11
11
  import { DEFAULT_REALTIME_CONFIG, REALTIME_ERROR_CODES, } from "../types/index.js";
12
12
  import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
13
+ import { HandlerRegistry } from "../core/handlerRegistry.js";
13
14
  /**
14
15
  * Realtime Processor class for orchestrating realtime voice operations
15
16
  *
@@ -39,7 +40,7 @@ export class RealtimeProcessor {
39
40
  /**
40
41
  * Handler registry mapping provider names to Realtime handlers
41
42
  */
42
- static handlers = new Map();
43
+ static registry = new HandlerRegistry("RealtimeProcessor");
43
44
  /**
44
45
  * Active sessions by provider
45
46
  */
@@ -51,17 +52,10 @@ export class RealtimeProcessor {
51
52
  * @param handler - Realtime handler implementation
52
53
  */
53
54
  static registerHandler(providerName, handler) {
54
- if (!providerName) {
55
- throw new Error("Provider name is required");
56
- }
57
- if (!handler) {
58
- throw new Error("Handler is required");
59
- }
60
- const normalizedName = providerName.toLowerCase();
61
- if (this.handlers.has(normalizedName)) {
62
- logger.warn(`[RealtimeProcessor] Overwriting existing handler for provider: ${normalizedName}`);
63
- }
64
- this.handlers.set(normalizedName, handler);
55
+ const normalizedName = providerName
56
+ ? providerName.toLowerCase()
57
+ : providerName;
58
+ this.registry.register(providerName, handler);
65
59
  logger.debug(`[RealtimeProcessor] Registered Realtime handler for provider: ${normalizedName}`);
66
60
  }
67
61
  /**
@@ -71,24 +65,19 @@ export class RealtimeProcessor {
71
65
  * already-registered primary handler when backfilling its aliases.
72
66
  */
73
67
  static getHandler(providerName) {
74
- const normalizedName = providerName.toLowerCase();
75
- return this.handlers.get(normalizedName);
68
+ return this.registry.get(providerName);
76
69
  }
77
70
  /**
78
71
  * Check if a provider is supported
79
72
  */
80
73
  static supports(providerName) {
81
- if (!providerName) {
82
- return false;
83
- }
84
- const normalizedName = providerName.toLowerCase();
85
- return this.handlers.has(normalizedName);
74
+ return this.registry.supports(providerName);
86
75
  }
87
76
  /**
88
77
  * Get list of all registered providers
89
78
  */
90
79
  static getProviders() {
91
- return Array.from(this.handlers.keys());
80
+ return this.registry.list();
92
81
  }
93
82
  /**
94
83
  * Connect to a realtime session
@@ -101,7 +90,7 @@ export class RealtimeProcessor {
101
90
  static async connect(provider, config, handlers) {
102
91
  const handler = this.getHandler(provider);
103
92
  if (!handler) {
104
- throw RealtimeError.providerNotSupported(provider, Array.from(this.handlers.keys()));
93
+ throw RealtimeError.providerNotSupported(provider, this.registry.list());
105
94
  }
106
95
  if (!handler.isConfigured()) {
107
96
  throw RealtimeError.providerNotConfigured(provider);
@@ -145,7 +134,7 @@ export class RealtimeProcessor {
145
134
  static async disconnect(provider) {
146
135
  const handler = this.getHandler(provider);
147
136
  if (!handler) {
148
- throw RealtimeError.providerNotSupported(provider, Array.from(this.handlers.keys()));
137
+ throw RealtimeError.providerNotSupported(provider, this.registry.list());
149
138
  }
150
139
  if (!handler.isConnected()) {
151
140
  logger.warn(`[RealtimeProcessor] No active session for provider: ${provider}`);
@@ -174,7 +163,7 @@ export class RealtimeProcessor {
174
163
  static async sendAudio(provider, audio) {
175
164
  const handler = this.getHandler(provider);
176
165
  if (!handler) {
177
- throw RealtimeError.providerNotSupported(provider, Array.from(this.handlers.keys()));
166
+ throw RealtimeError.providerNotSupported(provider, this.registry.list());
178
167
  }
179
168
  if (!handler.isConnected()) {
180
169
  throw RealtimeError.sessionNotActive(provider);
@@ -199,7 +188,7 @@ export class RealtimeProcessor {
199
188
  static async sendText(provider, text) {
200
189
  const handler = this.getHandler(provider);
201
190
  if (!handler) {
202
- throw RealtimeError.providerNotSupported(provider, Array.from(this.handlers.keys()));
191
+ throw RealtimeError.providerNotSupported(provider, this.registry.list());
203
192
  }
204
193
  if (!handler.isConnected()) {
205
194
  throw RealtimeError.sessionNotActive(provider);
@@ -243,7 +232,7 @@ export class RealtimeProcessor {
243
232
  static async triggerResponse(provider) {
244
233
  const handler = this.getHandler(provider);
245
234
  if (!handler) {
246
- throw RealtimeError.providerNotSupported(provider, Array.from(this.handlers.keys()));
235
+ throw RealtimeError.providerNotSupported(provider, this.registry.list());
247
236
  }
248
237
  if (!handler.isConnected()) {
249
238
  throw RealtimeError.sessionNotActive(provider);
@@ -276,7 +265,7 @@ export class RealtimeProcessor {
276
265
  static async cancelResponse(provider) {
277
266
  const handler = this.getHandler(provider);
278
267
  if (!handler) {
279
- throw RealtimeError.providerNotSupported(provider, Array.from(this.handlers.keys()));
268
+ throw RealtimeError.providerNotSupported(provider, this.registry.list());
280
269
  }
281
270
  if (!handler.isConnected()) {
282
271
  return; // Nothing to cancel
@@ -335,15 +324,15 @@ export class RealtimeProcessor {
335
324
  static clearHandlers() {
336
325
  // Disconnect all active sessions
337
326
  for (const [provider] of this.sessions) {
338
- const handler = this.handlers.get(provider);
327
+ const handler = this.registry.get(provider);
339
328
  if (handler?.isConnected()) {
340
329
  handler.disconnect().catch(() => {
341
330
  // Ignore errors during cleanup
342
331
  });
343
332
  }
344
333
  }
345
- this.handlers.clear();
346
334
  this.sessions.clear();
335
+ this.registry.clear();
347
336
  logger.debug("[RealtimeProcessor] Cleared all handlers and sessions");
348
337
  }
349
338
  }
@@ -2,6 +2,7 @@ import { logger } from "../utils/logger.js";
2
2
  import { redactUrlCredentials } from "../utils/logSanitize.js";
3
3
  import { getProviderModel, resolveOpenAICompatConfig, } from "../utils/providerConfig.js";
4
4
  import { classifyProviderError } from "../utils/errorClassifier.js";
5
+ import { TimeoutError } from "../utils/timeout.js";
5
6
  import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
6
7
  /**
7
8
  * Generic OpenAI-compatible provider driven entirely by an
@@ -51,9 +52,15 @@ export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvide
51
52
  return this.entry.fallbackModels;
52
53
  }
53
54
  formatProviderError(error) {
54
- // classifyProviderError handles TimeoutError internally (always maps
55
- // to NetworkError, ahead of any rule table) no local pre-check
56
- // needed or wanted here; see this task's design note.
55
+ // classifyProviderError hard-codes TimeoutError -> NetworkError ahead
56
+ // of any rule table and does not allow a per-provider override. An
57
+ // entry can opt out of that default via timeoutErrorClass (currently
58
+ // only Groq, reproducing its pre-migration subclass's own TimeoutError
59
+ // interception) — checked here, before ever reaching the shared
60
+ // classifier, so every other entry still gets its unmodified default.
61
+ if (error instanceof TimeoutError && this.entry.timeoutErrorClass) {
62
+ return new this.entry.timeoutErrorClass(`${this.entry.configOptions.providerName} request timed out: ${error.message}`, this.entry.providerName);
63
+ }
57
64
  return classifyProviderError(error, this.entry.errorRules, this.entry.providerName, this.modelName);
58
65
  }
59
66
  }
@@ -49,17 +49,15 @@ export const OPENAI_COMPAT_CATALOG = [
49
49
  GroqModels.LLAMA_3_2_90B_VISION_PREVIEW,
50
50
  GroqModels.LLAMA_3_2_11B_VISION_PREVIEW,
51
51
  ],
52
- // KNOWN GAP (not reproducible by this data-only array flagged for PR C,
53
- // see plan-05/task-A-report.md): live GroqProvider.formatProviderError()
54
- // intercepts TimeoutError and returns a plain ProviderError BEFORE ever
55
- // calling classifyProviderError, overriding that function's own
56
- // (non-overridable) rule that TimeoutError always maps to NetworkError.
57
- // ConfiguredOpenAICompatProvider.formatProviderError() has no such
58
- // pre-check hook, so migrating Groq onto it as-is would silently
59
- // reclassify Groq timeouts as NetworkError. This entry's errorRules
60
- // still mirrors Groq's live rule array faithfully for every other error
61
- // shape; the TimeoutError special case is a structural gap in
62
- // ConfiguredOpenAICompatProvider, not a data error here.
52
+ // Groq's pre-migration subclass intercepted TimeoutError itself and
53
+ // returned a plain ProviderError, ahead of classifyProviderError's own
54
+ // non-overridable TimeoutError -> NetworkError default. Expressed here
55
+ // as data see OpenAICompatCatalogEntry.timeoutErrorClass and
56
+ // ConfiguredOpenAICompatProvider.formatProviderError, which consults
57
+ // this field before ever delegating to the shared classifier. No other
58
+ // entry in this catalog sets it, so every other provider still gets
59
+ // the classifier's unmodified default.
60
+ timeoutErrorClass: ProviderError,
63
61
  errorRules: [
64
62
  {
65
63
  match: (ctx) => ctx.statusCode === 401 ||
@@ -8,7 +8,7 @@ import type { ValidationSchema } from "./aliases.js";
8
8
  import type { EnhancedGenerateResult, GenerateResult, TextGenerationOptions } from "./generate.js";
9
9
  import type { MultimodalAudioEntry } from "./file.js";
10
10
  import type { StreamOptions, StreamResult } from "./stream.js";
11
- import type { ProviderErrorRule } from "./errors.js";
11
+ import type { ProviderError, ProviderErrorRule } from "./errors.js";
12
12
  import type { ExternalMCPToolInfo } from "./externalMcp.js";
13
13
  import type { ClaudeSubscriptionTier, AnthropicAuthMethod, AnthropicAuthConfig, SubscriptionInfo, OAuthToken } from "./subscription.js";
14
14
  import type { Tool } from "./tools.js";
@@ -671,6 +671,20 @@ export type OpenAICompatCatalogEntry = {
671
671
  * Task 4, so nothing actually mutates it at runtime.
672
672
  */
673
673
  errorRules: ProviderErrorRule[];
674
+ /**
675
+ * Optional override for the Error subclass a TimeoutError should produce
676
+ * for this entry. classifyProviderError() hard-codes
677
+ * TimeoutError -> NetworkError unconditionally, ahead of any rule table,
678
+ * and does not make that mapping overridable per-provider (see
679
+ * errorClassifier.ts). Groq's pre-migration subclass predates that
680
+ * shared classifier and intercepted TimeoutError itself, returning a
681
+ * plain ProviderError instead — this field lets
682
+ * ConfiguredOpenAICompatProvider reproduce that one documented
683
+ * divergence as data (see its formatProviderError), rather than adding a
684
+ * class-level hook back in. Omit for every entry whose timeout should use
685
+ * the classifier's default (six of the seven catalog entries).
686
+ */
687
+ timeoutErrorClass?: new (message: string, provider?: string) => ProviderError;
674
688
  };
675
689
  /** The subset of OpenAICompatCatalogEntry that resolveOpenAICompatConfig()
676
690
  * needs — lets call sites pass a minimal object without the full catalog
@@ -43,7 +43,7 @@ export declare class AvatarError extends NeuroLinkError {
43
43
  * Static processor managing the avatar handler registry.
44
44
  */
45
45
  export declare class AvatarProcessor {
46
- private static readonly handlers;
46
+ private static readonly registry;
47
47
  /**
48
48
  * Register an avatar handler for a specific provider.
49
49
  */
@@ -63,6 +63,10 @@ export declare class AvatarProcessor {
63
63
  * already-registered primary handler when backfilling its aliases.
64
64
  */
65
65
  static getHandler(providerName: string): AvatarHandler | undefined;
66
+ /**
67
+ * Clear all registered handlers (for testing).
68
+ */
69
+ static clearHandlers(): void;
66
70
  private static buildSpanAttributes;
67
71
  /**
68
72
  * Generate an avatar video via the registered handler.
@@ -13,6 +13,7 @@ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
13
13
  import { SpanSerializer, SpanStatus, SpanType, getMetricsAggregator, } from "../observability/index.js";
14
14
  import { NeuroLinkError } from "./errorHandling.js";
15
15
  import { logger } from "./logger.js";
16
+ import { HandlerRegistry } from "../core/handlerRegistry.js";
16
17
  /**
17
18
  * Avatar-specific error codes.
18
19
  */
@@ -47,38 +48,26 @@ export class AvatarError extends NeuroLinkError {
47
48
  * Static processor managing the avatar handler registry.
48
49
  */
49
50
  export class AvatarProcessor {
50
- static handlers = new Map();
51
+ static registry = new HandlerRegistry("AvatarProcessor");
51
52
  /**
52
53
  * Register an avatar handler for a specific provider.
53
54
  */
54
55
  static registerHandler(providerName, handler) {
55
- if (!providerName) {
56
- throw new Error("Provider name is required");
57
- }
58
- if (!handler) {
59
- throw new Error("Handler is required");
60
- }
61
- const key = providerName.toLowerCase();
62
- if (this.handlers.has(key)) {
63
- logger.warn(`[AvatarProcessor] Overwriting existing handler for provider: ${key}`);
64
- }
65
- this.handlers.set(key, handler);
56
+ const key = providerName ? providerName.toLowerCase() : providerName;
57
+ this.registry.register(providerName, handler);
66
58
  logger.debug(`[AvatarProcessor] Registered avatar handler: ${key}`);
67
59
  }
68
60
  /**
69
61
  * Check if a provider has a registered avatar handler.
70
62
  */
71
63
  static supports(providerName) {
72
- if (!providerName) {
73
- return false;
74
- }
75
- return this.handlers.has(providerName.toLowerCase());
64
+ return this.registry.supports(providerName);
76
65
  }
77
66
  /**
78
67
  * List the names of all registered providers.
79
68
  */
80
69
  static listProviders() {
81
- return Array.from(this.handlers.keys());
70
+ return this.registry.list();
82
71
  }
83
72
  /**
84
73
  * Get a registered avatar handler by provider name.
@@ -87,7 +76,13 @@ export class AvatarProcessor {
87
76
  * already-registered primary handler when backfilling its aliases.
88
77
  */
89
78
  static getHandler(providerName) {
90
- return this.handlers.get(providerName.toLowerCase());
79
+ return this.registry.get(providerName);
80
+ }
81
+ /**
82
+ * Clear all registered handlers (for testing).
83
+ */
84
+ static clearHandlers() {
85
+ this.registry.clear();
91
86
  }
92
87
  static buildSpanAttributes(provider, options) {
93
88
  return {
@@ -42,7 +42,7 @@ export declare class MusicError extends NeuroLinkError {
42
42
  * Static processor managing the music handler registry.
43
43
  */
44
44
  export declare class MusicProcessor {
45
- private static readonly handlers;
45
+ private static readonly registry;
46
46
  /**
47
47
  * Register a music handler for a specific provider.
48
48
  */
@@ -62,6 +62,10 @@ export declare class MusicProcessor {
62
62
  * already-registered primary handler when backfilling its aliases.
63
63
  */
64
64
  static getHandler(providerName: string): MusicHandler | undefined;
65
+ /**
66
+ * Clear all registered handlers (for testing).
67
+ */
68
+ static clearHandlers(): void;
65
69
  private static buildSpanAttributes;
66
70
  /**
67
71
  * Generate a music track via the registered handler.