@juspay/neurolink 11.2.3 → 11.3.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 (87) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/browser/neurolink.min.js +396 -396
  3. package/dist/core/handlerRegistry.d.ts +29 -0
  4. package/dist/core/handlerRegistry.js +61 -0
  5. package/dist/core/loopEngine.d.ts +23 -0
  6. package/dist/core/loopEngine.js +245 -0
  7. package/dist/core/nativeToolFormat.d.ts +33 -0
  8. package/dist/core/nativeToolFormat.js +30 -0
  9. package/dist/core/streamChannel.d.ts +10 -0
  10. package/dist/core/streamChannel.js +76 -0
  11. package/dist/factories/providerRegistry.d.ts +4 -16
  12. package/dist/factories/providerRegistry.js +4 -56
  13. package/dist/lib/core/handlerRegistry.d.ts +29 -0
  14. package/dist/lib/core/handlerRegistry.js +62 -0
  15. package/dist/lib/core/loopEngine.d.ts +23 -0
  16. package/dist/lib/core/loopEngine.js +246 -0
  17. package/dist/lib/core/nativeToolFormat.d.ts +33 -0
  18. package/dist/lib/core/nativeToolFormat.js +31 -0
  19. package/dist/lib/core/streamChannel.d.ts +10 -0
  20. package/dist/lib/core/streamChannel.js +77 -0
  21. package/dist/lib/factories/providerRegistry.d.ts +4 -16
  22. package/dist/lib/factories/providerRegistry.js +4 -56
  23. package/dist/lib/providers/anthropic/cacheControl.d.ts +12 -0
  24. package/dist/lib/providers/anthropic/cacheControl.js +15 -0
  25. package/dist/lib/providers/anthropic/client.js +12 -45
  26. package/dist/lib/providers/googleAiStudio/client.js +7 -5
  27. package/dist/lib/providers/googleNativeGemini3/utils.d.ts +4 -11
  28. package/dist/lib/providers/googleNativeGemini3/utils.js +1 -75
  29. package/dist/lib/providers/googleVertex/client.js +28 -30
  30. package/dist/lib/providers/openaiChatCompletionsBase.js +10 -12
  31. package/dist/lib/providers/openaiChatCompletionsClient.d.ts +1 -5
  32. package/dist/lib/providers/openaiChatCompletionsClient.js +0 -26
  33. package/dist/lib/types/index.d.ts +3 -0
  34. package/dist/lib/types/index.js +3 -0
  35. package/dist/lib/types/loopEngine.d.ts +78 -0
  36. package/dist/lib/types/loopEngine.js +2 -0
  37. package/dist/lib/types/nativeTools.d.ts +16 -0
  38. package/dist/lib/types/nativeTools.js +2 -0
  39. package/dist/lib/types/openaiCompatible.d.ts +2 -2
  40. package/dist/lib/types/providers.d.ts +0 -13
  41. package/dist/lib/types/streaming.d.ts +15 -0
  42. package/dist/lib/types/streaming.js +2 -0
  43. package/dist/lib/utils/avatarProcessor.d.ts +5 -1
  44. package/dist/lib/utils/avatarProcessor.js +13 -18
  45. package/dist/lib/utils/musicProcessor.d.ts +5 -1
  46. package/dist/lib/utils/musicProcessor.js +13 -18
  47. package/dist/lib/utils/sttProcessor.d.ts +10 -2
  48. package/dist/lib/utils/sttProcessor.js +22 -18
  49. package/dist/lib/utils/ttsProcessor.d.ts +10 -2
  50. package/dist/lib/utils/ttsProcessor.js +22 -18
  51. package/dist/lib/utils/videoProcessor.d.ts +5 -1
  52. package/dist/lib/utils/videoProcessor.js +13 -18
  53. package/dist/lib/voice/RealtimeVoiceAPI.d.ts +1 -1
  54. package/dist/lib/voice/RealtimeVoiceAPI.js +17 -28
  55. package/dist/providers/anthropic/cacheControl.d.ts +12 -0
  56. package/dist/providers/anthropic/cacheControl.js +14 -0
  57. package/dist/providers/anthropic/client.js +12 -45
  58. package/dist/providers/googleAiStudio/client.js +7 -5
  59. package/dist/providers/googleNativeGemini3/utils.d.ts +4 -11
  60. package/dist/providers/googleNativeGemini3/utils.js +1 -75
  61. package/dist/providers/googleVertex/client.js +28 -30
  62. package/dist/providers/openaiChatCompletionsBase.js +10 -12
  63. package/dist/providers/openaiChatCompletionsClient.d.ts +1 -5
  64. package/dist/providers/openaiChatCompletionsClient.js +0 -26
  65. package/dist/types/index.d.ts +3 -0
  66. package/dist/types/index.js +3 -0
  67. package/dist/types/loopEngine.d.ts +78 -0
  68. package/dist/types/loopEngine.js +1 -0
  69. package/dist/types/nativeTools.d.ts +16 -0
  70. package/dist/types/nativeTools.js +1 -0
  71. package/dist/types/openaiCompatible.d.ts +2 -2
  72. package/dist/types/providers.d.ts +0 -13
  73. package/dist/types/streaming.d.ts +15 -0
  74. package/dist/types/streaming.js +1 -0
  75. package/dist/utils/avatarProcessor.d.ts +5 -1
  76. package/dist/utils/avatarProcessor.js +13 -18
  77. package/dist/utils/musicProcessor.d.ts +5 -1
  78. package/dist/utils/musicProcessor.js +13 -18
  79. package/dist/utils/sttProcessor.d.ts +10 -2
  80. package/dist/utils/sttProcessor.js +22 -18
  81. package/dist/utils/ttsProcessor.d.ts +10 -2
  82. package/dist/utils/ttsProcessor.js +22 -18
  83. package/dist/utils/videoProcessor.d.ts +5 -1
  84. package/dist/utils/videoProcessor.js +13 -18
  85. package/dist/voice/RealtimeVoiceAPI.d.ts +1 -1
  86. package/dist/voice/RealtimeVoiceAPI.js +17 -28
  87. package/package.json +9 -1
@@ -0,0 +1,78 @@
1
+ export type AgenticLoopToolCall = {
2
+ id: string;
3
+ name: string;
4
+ args: Record<string, unknown>;
5
+ };
6
+ export type AgenticLoopUsage = {
7
+ inputTokens: number;
8
+ outputTokens: number;
9
+ cacheReadTokens?: number;
10
+ cacheWriteTokens?: number;
11
+ reasoningTokens?: number;
12
+ };
13
+ export type AgenticLoopStepResult<TRaw = unknown> = {
14
+ text: string;
15
+ reasoning?: string;
16
+ toolCalls: AgenticLoopToolCall[];
17
+ usage: AgenticLoopUsage;
18
+ /** Provider's own raw stop/finish-reason string, e.g. "tool_use", "MAX_TOKENS" */
19
+ rawStopReason: string | undefined;
20
+ /** Adapter-private accumulated response data needed by buildToolResultMessages
21
+ * (e.g. Anthropic's ordered content blocks, Gemini's rawResponseParts). */
22
+ raw: TRaw;
23
+ };
24
+ export type AgenticLoopToolCallResult = AgenticLoopToolCall & {
25
+ output: unknown;
26
+ error?: string;
27
+ permanentlyFailed?: boolean;
28
+ };
29
+ export type AgenticLoopStepRequest = {
30
+ raw: unknown;
31
+ };
32
+ export type AgenticLoopReclaimResult<TConversation> = {
33
+ conversation: TConversation;
34
+ };
35
+ export type AgenticLoopToolFailureBreaker = {
36
+ maxRetries: number;
37
+ };
38
+ export type AgenticLoopAdapter<TConversation = unknown, TRaw = unknown> = {
39
+ readonly providerLabel: string;
40
+ readonly maxSteps: number;
41
+ /** Pre-existing per-family flat timeout, used as createTurnClock's deadline default. */
42
+ readonly defaultTurnTimeoutMs?: number;
43
+ readonly stallTimeoutMs?: number;
44
+ /** Set only for adapter instances whose client has the TOOL_NOT_FOUND strike breaker today: both Gemini adapters (AI Studio, Vertex+Gemini) AND the Vertex+Claude call to createAnthropicLoopAdapter — NOT the native-Anthropic call to that same factory, and not Bedrock. See Verified Fact 4. */
45
+ readonly toolFailureBreaker?: AgenticLoopToolFailureBreaker;
46
+ buildStepRequest(conversation: TConversation, step: number): AgenticLoopStepRequest;
47
+ executeStep(request: AgenticLoopStepRequest, channel: {
48
+ push(chunk: {
49
+ content: string;
50
+ }): void;
51
+ }, signal: AbortSignal): Promise<AgenticLoopStepResult<TRaw>>;
52
+ buildToolResultMessages(conversation: TConversation, stepResult: AgenticLoopStepResult<TRaw>, toolResults: AgenticLoopToolCallResult[]): TConversation;
53
+ mapFinishReason(rawStopReason: string | undefined, hadToolCalls: boolean): string;
54
+ /** Optional: in-turn context-budget reclaim, called once per step before buildStepRequest. */
55
+ planReclaim?(conversation: TConversation, step: number): AgenticLoopReclaimResult<TConversation> | undefined;
56
+ /** Optional: Vertex+Gemini-only single-retry-on-malformed-call. */
57
+ isMalformedStep?(stepResult: AgenticLoopStepResult<TRaw>): boolean;
58
+ buildMalformedRetryNote?(conversation: TConversation): TConversation;
59
+ };
60
+ export type AgenticLoopOptions = {
61
+ tools?: Record<string, {
62
+ execute?: (args: Record<string, unknown>, opts: unknown) => Promise<unknown>;
63
+ }>;
64
+ abortSignal?: AbortSignal;
65
+ };
66
+ export type AgenticLoopResult<TConversation> = {
67
+ text: string;
68
+ toolCalls: AgenticLoopToolCall[];
69
+ toolExecutions: Array<{
70
+ name: string;
71
+ input: Record<string, unknown>;
72
+ output: unknown;
73
+ }>;
74
+ usage: AgenticLoopUsage;
75
+ finishReason: string;
76
+ rawStopReason: string | undefined;
77
+ conversation: TConversation;
78
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=loopEngine.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Wire formats accepted by `toNativeToolDeclarations` (src/lib/core/nativeToolFormat.ts).
3
+ * `"input_schema"` is Anthropic's native Messages-API tool shape;
4
+ * `"functionDeclarations"` is the @google/genai SDK shape shared by the
5
+ * Gemini-family native providers (Google AI Studio, Vertex+Gemini).
6
+ */
7
+ export type NativeToolFormat = "input_schema" | "functionDeclarations";
8
+ /** A single tool declaration in Anthropic's native `input_schema` wire format. */
9
+ export type NativeAnthropicToolDeclaration = {
10
+ name: string;
11
+ description?: string;
12
+ input_schema: Record<string, unknown>;
13
+ cache_control?: {
14
+ type: "ephemeral";
15
+ };
16
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=nativeTools.js.map
@@ -203,8 +203,6 @@ export type OpenAICompatSSEResult = {
203
203
  export type OpenAICompatStreamChunk = {
204
204
  content: string;
205
205
  reasoning?: string;
206
- } | {
207
- done: true;
208
206
  };
209
207
  export type ToolExecutionSummaryInternal = {
210
208
  toolCallId: string;
@@ -232,6 +230,8 @@ export type StreamLoopArgs = {
232
230
  toolsUsed: string[];
233
231
  toolExecutionSummaries: ToolExecutionSummaryInternal[];
234
232
  pushChunk: (chunk: OpenAICompatStreamChunk) => void;
233
+ /** Signals the channel that no further chunks will arrive (success or error path alike). */
234
+ closeChannel: () => void;
235
235
  resolveUsage: (u: {
236
236
  promptTokens: number;
237
237
  completionTokens: number;
@@ -1740,19 +1740,6 @@ export type CollectedChunkResult = {
1740
1740
  */
1741
1741
  reasoningTokens?: number;
1742
1742
  };
1743
- /** Push-based text channel for incremental streaming. */
1744
- export type TextChannel = {
1745
- /** Push a text chunk to the consumer. */
1746
- push: (text: string) => void;
1747
- /** Signal that no more chunks will arrive. */
1748
- close: () => void;
1749
- /** Signal that the producer encountered a fatal error. */
1750
- error: (err: unknown) => void;
1751
- /** Async iterable consumed by the StreamResult. */
1752
- iterable: AsyncIterable<{
1753
- content: string;
1754
- }>;
1755
- };
1756
1743
  /** Language model object shape (LanguageModelV2/V3). */
1757
1744
  export type LanguageModelObject = {
1758
1745
  readonly modelId: string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared push-based channel bridging a background producer (an agentic
3
+ * tool-calling loop) with an async-iterable consumer. Replaces the two
4
+ * independently-invented primitives this type unifies: the OpenAI-family
5
+ * `createChunkQueue` (pull-based, in-band `{done:true}` sentinel) and the
6
+ * Gemini-family `createTextChannel` (push-based, out-of-band close/error).
7
+ */
8
+ export type StreamChannel<T = {
9
+ content: string;
10
+ }> = {
11
+ push(value: T): void;
12
+ close(): void;
13
+ error(err: unknown): void;
14
+ readonly iterable: AsyncIterable<T>;
15
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=streaming.js.map
@@ -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.
@@ -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
  * Music-specific error codes.
18
19
  */
@@ -46,38 +47,26 @@ export class MusicError extends NeuroLinkError {
46
47
  * Static processor managing the music handler registry.
47
48
  */
48
49
  export class MusicProcessor {
49
- static handlers = new Map();
50
+ static registry = new HandlerRegistry("MusicProcessor");
50
51
  /**
51
52
  * Register a music handler for a specific provider.
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 key = providerName.toLowerCase();
61
- if (this.handlers.has(key)) {
62
- logger.warn(`[MusicProcessor] Overwriting existing handler for provider: ${key}`);
63
- }
64
- this.handlers.set(key, handler);
55
+ const key = providerName ? providerName.toLowerCase() : providerName;
56
+ this.registry.register(providerName, handler);
65
57
  logger.debug(`[MusicProcessor] Registered music handler: ${key}`);
66
58
  }
67
59
  /**
68
60
  * Check if a provider has a registered music handler.
69
61
  */
70
62
  static supports(providerName) {
71
- if (!providerName) {
72
- return false;
73
- }
74
- return this.handlers.has(providerName.toLowerCase());
63
+ return this.registry.supports(providerName);
75
64
  }
76
65
  /**
77
66
  * List the names of all registered providers.
78
67
  */
79
68
  static listProviders() {
80
- return Array.from(this.handlers.keys());
69
+ return this.registry.list();
81
70
  }
82
71
  /**
83
72
  * Get a registered music handler by provider name.
@@ -86,7 +75,13 @@ export class MusicProcessor {
86
75
  * already-registered primary handler when backfilling its aliases.
87
76
  */
88
77
  static getHandler(providerName) {
89
- return this.handlers.get(providerName.toLowerCase());
78
+ return this.registry.get(providerName);
79
+ }
80
+ /**
81
+ * Clear all registered handlers (for testing).
82
+ */
83
+ static clearHandlers() {
84
+ this.registry.clear();
90
85
  }
91
86
  static buildSpanAttributes(provider, options) {
92
87
  return {
@@ -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
  */