@juspay/neurolink 11.2.1 → 11.2.3

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 (43) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/browser/neurolink.min.js +392 -392
  3. package/dist/factories/providerRegistry.d.ts +16 -4
  4. package/dist/factories/providerRegistry.js +70 -48
  5. package/dist/lib/factories/providerRegistry.d.ts +16 -4
  6. package/dist/lib/factories/providerRegistry.js +70 -48
  7. package/dist/lib/providers/configuredOpenAICompat.js +10 -3
  8. package/dist/lib/providers/openaiChatCompletionsBase.js +26 -14
  9. package/dist/lib/providers/openaiCompatCatalog.js +9 -11
  10. package/dist/lib/types/providers.d.ts +15 -1
  11. package/dist/providers/configuredOpenAICompat.js +10 -3
  12. package/dist/providers/openaiChatCompletionsBase.js +26 -14
  13. package/dist/providers/openaiCompatCatalog.js +9 -11
  14. package/dist/types/providers.d.ts +15 -1
  15. package/package.json +3 -2
  16. package/dist/lib/providers/cloudflare.d.ts +0 -26
  17. package/dist/lib/providers/cloudflare.js +0 -83
  18. package/dist/lib/providers/fireworks.d.ts +0 -22
  19. package/dist/lib/providers/fireworks.js +0 -73
  20. package/dist/lib/providers/groq.d.ts +0 -24
  21. package/dist/lib/providers/groq.js +0 -83
  22. package/dist/lib/providers/mistral.d.ts +0 -25
  23. package/dist/lib/providers/mistral.js +0 -87
  24. package/dist/lib/providers/perplexity.d.ts +0 -24
  25. package/dist/lib/providers/perplexity.js +0 -69
  26. package/dist/lib/providers/togetherAi.d.ts +0 -23
  27. package/dist/lib/providers/togetherAi.js +0 -73
  28. package/dist/lib/providers/xai.d.ts +0 -22
  29. package/dist/lib/providers/xai.js +0 -77
  30. package/dist/providers/cloudflare.d.ts +0 -26
  31. package/dist/providers/cloudflare.js +0 -82
  32. package/dist/providers/fireworks.d.ts +0 -22
  33. package/dist/providers/fireworks.js +0 -72
  34. package/dist/providers/groq.d.ts +0 -24
  35. package/dist/providers/groq.js +0 -82
  36. package/dist/providers/mistral.d.ts +0 -25
  37. package/dist/providers/mistral.js +0 -86
  38. package/dist/providers/perplexity.d.ts +0 -24
  39. package/dist/providers/perplexity.js +0 -68
  40. package/dist/providers/togetherAi.d.ts +0 -23
  41. package/dist/providers/togetherAi.js +0 -72
  42. package/dist/providers/xai.d.ts +0 -22
  43. package/dist/providers/xai.js +0 -76
@@ -1,4 +1,14 @@
1
1
  import type { ProviderRegistryOptions } from "../types/index.js";
2
+ import { AIProviderName } from "../constants/enums.js";
3
+ /**
4
+ * Static module -> provider-ID manifest for every provider registered below.
5
+ * Module names (file/dir under src/lib/providers/) intentionally differ from
6
+ * their canonical IDs (e.g. amazonBedrock -> "bedrock", googleVertex ->
7
+ * "vertex"); static scanners that cannot resolve the dynamic import() calls
8
+ * use this mapping to confirm a module is registered. Enforced at runtime so
9
+ * the registry cannot silently drift from this manifest.
10
+ */
11
+ export declare const PROVIDER_MODULE_TO_ID: Readonly<Record<string, AIProviderName>>;
2
12
  /**
3
13
  * Provider Registry - registers all providers with the factory
4
14
  * This is where we migrate providers one by one to the new pattern
@@ -33,10 +43,12 @@ export declare class ProviderRegistry {
33
43
  * (avoids circular dependencies; see CLAUDE.md).
34
44
  *
35
45
  * Not registered (by design): index.ts, providerTypeUtils.ts,
36
- * anthropicBaseProvider.ts (legacy; anthropic.ts is live),
37
- * googleNativeGemini3.ts (shared helpers). Filename != provider ID
38
- * (e.g. amazonBedrock -> "bedrock"); static scanners that miss dynamic
39
- * imports may false-positive (Pattern Analysis #1178).
46
+ * openaiChatCompletionsBase.ts, openaiChatCompletionsClient.ts,
47
+ * anthropicImageBlocks.ts (shared base/helper modules), anthropicBaseProvider.ts
48
+ * (legacy; anthropic.ts is live), googleNativeGemini3.ts (shared helpers).
49
+ * Filename != provider ID (e.g. amazonBedrock -> "bedrock"); the
50
+ * statically-scannable PROVIDER_MODULE_TO_ID manifest maps every registered
51
+ * module to its provider ID and is enforced below (Pattern Analysis #1178/#1317).
40
52
  */
41
53
  private static _doRegister;
42
54
  /**
@@ -1,7 +1,48 @@
1
1
  import { ProviderFactory } from "./providerFactory.js";
2
2
  import { logger } from "../utils/logger.js";
3
- import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, MistralModels, OllamaModels, LiteLLMModels, HuggingFaceModels, DeepSeekModels, NvidiaNimModels, OpenRouterModels, XaiModels, GroqModels, CohereModels, TogetherAIModels, FireworksModels, PerplexityModels, CloudflareModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
3
+ import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, HuggingFaceModels, DeepSeekModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
4
4
  import { PROVIDER_DESCRIPTORS_BY_NAME } from "./providerDescriptors.js";
5
+ import { OPENAI_COMPAT_CATALOG } from "../providers/openaiCompatCatalog.js";
6
+ /**
7
+ * Static module -> provider-ID manifest for every provider registered below.
8
+ * Module names (file/dir under src/lib/providers/) intentionally differ from
9
+ * their canonical IDs (e.g. amazonBedrock -> "bedrock", googleVertex ->
10
+ * "vertex"); static scanners that cannot resolve the dynamic import() calls
11
+ * use this mapping to confirm a module is registered. Enforced at runtime so
12
+ * the registry cannot silently drift from this manifest.
13
+ */
14
+ export const PROVIDER_MODULE_TO_ID = {
15
+ amazonBedrock: AIProviderName.BEDROCK,
16
+ amazonSagemaker: AIProviderName.SAGEMAKER,
17
+ anthropic: AIProviderName.ANTHROPIC,
18
+ azureOpenai: AIProviderName.AZURE,
19
+ cloudflare: AIProviderName.CLOUDFLARE,
20
+ cohere: AIProviderName.COHERE,
21
+ deepseek: AIProviderName.DEEPSEEK,
22
+ fireworks: AIProviderName.FIREWORKS,
23
+ googleAiStudio: AIProviderName.GOOGLE_AI,
24
+ googleVertex: AIProviderName.VERTEX,
25
+ groq: AIProviderName.GROQ,
26
+ huggingFace: AIProviderName.HUGGINGFACE,
27
+ ideogram: AIProviderName.IDEOGRAM,
28
+ jina: AIProviderName.JINA,
29
+ litellm: AIProviderName.LITELLM,
30
+ llamaCpp: AIProviderName.LLAMACPP,
31
+ lmStudio: AIProviderName.LM_STUDIO,
32
+ mistral: AIProviderName.MISTRAL,
33
+ nvidiaNim: AIProviderName.NVIDIA_NIM,
34
+ ollama: AIProviderName.OLLAMA,
35
+ openAI: AIProviderName.OPENAI,
36
+ openaiCompatible: AIProviderName.OPENAI_COMPATIBLE,
37
+ openRouter: AIProviderName.OPENROUTER,
38
+ perplexity: AIProviderName.PERPLEXITY,
39
+ recraft: AIProviderName.RECRAFT,
40
+ replicate: AIProviderName.REPLICATE,
41
+ stability: AIProviderName.STABILITY,
42
+ togetherAi: AIProviderName.TOGETHER_AI,
43
+ voyage: AIProviderName.VOYAGE,
44
+ xai: AIProviderName.XAI,
45
+ };
5
46
  /**
6
47
  * Provider Registry - registers all providers with the factory
7
48
  * This is where we migrate providers one by one to the new pattern
@@ -53,10 +94,12 @@ export class ProviderRegistry {
53
94
  * (avoids circular dependencies; see CLAUDE.md).
54
95
  *
55
96
  * Not registered (by design): index.ts, providerTypeUtils.ts,
56
- * anthropicBaseProvider.ts (legacy; anthropic.ts is live),
57
- * googleNativeGemini3.ts (shared helpers). Filename != provider ID
58
- * (e.g. amazonBedrock -> "bedrock"); static scanners that miss dynamic
59
- * imports may false-positive (Pattern Analysis #1178).
97
+ * openaiChatCompletionsBase.ts, openaiChatCompletionsClient.ts,
98
+ * anthropicImageBlocks.ts (shared base/helper modules), anthropicBaseProvider.ts
99
+ * (legacy; anthropic.ts is live), googleNativeGemini3.ts (shared helpers).
100
+ * Filename != provider ID (e.g. amazonBedrock -> "bedrock"); the
101
+ * statically-scannable PROVIDER_MODULE_TO_ID manifest maps every registered
102
+ * module to its provider ID and is enforced below (Pattern Analysis #1178/#1317).
60
103
  */
61
104
  // eslint-disable-next-line max-lines-per-function
62
105
  static async _doRegister() {
@@ -111,12 +154,18 @@ export class ProviderRegistry {
111
154
  return new HuggingFaceProvider(modelName, sdk, region, hfCreds);
112
155
  }, process.env.HUGGINGFACE_MODEL ||
113
156
  HuggingFaceModels.QWEN_2_5_72B_INSTRUCT, ["huggingface", "hf"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.HUGGINGFACE));
114
- // Register Mistral AI provider
115
- ProviderFactory.registerProvider(AIProviderName.MISTRAL, async (modelName, _providerName, sdk, _region, credentials) => {
116
- const mistralCreds = credentials;
117
- const { MistralProvider } = await import("../providers/mistral.js");
118
- return new MistralProvider(modelName, sdk, undefined, mistralCreds);
119
- }, MistralModels.MISTRAL_LARGE_LATEST, ["mistral"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.MISTRAL));
157
+ // Register the config-driven OpenAI-compatible catalog providers
158
+ // (groq, xai, together-ai, fireworks, perplexity, mistral, cloudflare).
159
+ // To add a new zero-quirk OpenAI-compatible provider, add one entry to
160
+ // OPENAI_COMPAT_CATALOG (openaiCompatCatalog.ts) not a new block here.
161
+ for (const entry of OPENAI_COMPAT_CATALOG) {
162
+ ProviderFactory.registerProvider(entry.providerName, async (modelName, _providerName, sdk, _region, credentials) => {
163
+ const { ConfiguredOpenAICompatProvider } = await import("../providers/configuredOpenAICompat.js");
164
+ return new ConfiguredOpenAICompatProvider(entry, modelName, sdk, credentials);
165
+ }, entry.registryDefaultModelChecksEnvVar
166
+ ? process.env[entry.modelEnvVar] || entry.registryDefaultModel
167
+ : entry.registryDefaultModel, entry.aliases, PROVIDER_DESCRIPTORS_BY_NAME.get(entry.providerName));
168
+ }
120
169
  // Register Ollama provider
121
170
  ProviderFactory.registerProvider(AIProviderName.OLLAMA, async (modelName, _providerName, sdk, _region, credentials) => {
122
171
  const ollamaCreds = credentials;
@@ -178,49 +227,12 @@ export class ProviderRegistry {
178
227
  const { LlamaCppProvider } = await import("../providers/llamaCpp.js");
179
228
  return new LlamaCppProvider(modelName, sdk, undefined, llamaCppCreds);
180
229
  }, process.env.LLAMACPP_MODEL || undefined, ["llamacpp", "llama.cpp", "llama-cpp"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.LLAMACPP));
181
- // Register xAI Grok provider
182
- ProviderFactory.registerProvider(AIProviderName.XAI, async (modelName, _providerName, sdk, _region, credentials) => {
183
- const xaiCreds = credentials;
184
- const { XaiProvider } = await import("../providers/xai.js");
185
- return new XaiProvider(modelName, sdk, undefined, xaiCreds);
186
- }, process.env.XAI_MODEL || XaiModels.GROK_3, ["xai", "grok"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.XAI));
187
- // Register Groq provider
188
- ProviderFactory.registerProvider(AIProviderName.GROQ, async (modelName, _providerName, sdk, _region, credentials) => {
189
- const groqCreds = credentials;
190
- const { GroqProvider } = await import("../providers/groq.js");
191
- return new GroqProvider(modelName, sdk, undefined, groqCreds);
192
- }, process.env.GROQ_MODEL || GroqModels.LLAMA_3_3_70B_VERSATILE, ["groq"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.GROQ));
193
230
  // Register Cohere provider
194
231
  ProviderFactory.registerProvider(AIProviderName.COHERE, async (modelName, _providerName, sdk, _region, credentials) => {
195
232
  const cohereCreds = credentials;
196
233
  const { CohereProvider } = await import("../providers/cohere.js");
197
234
  return new CohereProvider(modelName, sdk, undefined, cohereCreds);
198
235
  }, process.env.COHERE_MODEL || CohereModels.COMMAND_R_PLUS, ["cohere"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.COHERE));
199
- // Register Together AI provider
200
- ProviderFactory.registerProvider(AIProviderName.TOGETHER_AI, async (modelName, _providerName, sdk, _region, credentials) => {
201
- const togetherCreds = credentials;
202
- const { TogetherAIProvider } = await import("../providers/togetherAi.js");
203
- return new TogetherAIProvider(modelName, sdk, undefined, togetherCreds);
204
- }, process.env.TOGETHER_MODEL ||
205
- TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO, ["together-ai", "together"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.TOGETHER_AI));
206
- // Register Fireworks AI provider
207
- ProviderFactory.registerProvider(AIProviderName.FIREWORKS, async (modelName, _providerName, sdk, _region, credentials) => {
208
- const fireworksCreds = credentials;
209
- const { FireworksProvider } = await import("../providers/fireworks.js");
210
- return new FireworksProvider(modelName, sdk, undefined, fireworksCreds);
211
- }, process.env.FIREWORKS_MODEL || FireworksModels.DEEPSEEK_V4_PRO, ["fireworks"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.FIREWORKS));
212
- // Register Perplexity provider
213
- ProviderFactory.registerProvider(AIProviderName.PERPLEXITY, async (modelName, _providerName, sdk, _region, credentials) => {
214
- const perplexityCreds = credentials;
215
- const { PerplexityProvider } = await import("../providers/perplexity.js");
216
- return new PerplexityProvider(modelName, sdk, undefined, perplexityCreds);
217
- }, process.env.PERPLEXITY_MODEL || PerplexityModels.SONAR, ["perplexity", "pplx"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.PERPLEXITY));
218
- // Register Cloudflare Workers AI provider
219
- ProviderFactory.registerProvider(AIProviderName.CLOUDFLARE, async (modelName, _providerName, sdk, _region, credentials) => {
220
- const cloudflareCreds = credentials;
221
- const { CloudflareProvider } = await import("../providers/cloudflare.js");
222
- return new CloudflareProvider(modelName, sdk, undefined, cloudflareCreds);
223
- }, process.env.CLOUDFLARE_MODEL || CloudflareModels.LLAMA_3_3_70B_FAST, ["cloudflare", "workers-ai", "cf-ai"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.CLOUDFLARE));
224
236
  // Register Voyage AI embeddings provider
225
237
  ProviderFactory.registerProvider(AIProviderName.VOYAGE, async (modelName, _providerName, sdk, _region, credentials) => {
226
238
  const voyageCreds = credentials;
@@ -258,6 +270,16 @@ export class ProviderRegistry {
258
270
  const { RecraftProvider } = await import("../providers/recraft.js");
259
271
  return new RecraftProvider(modelName, sdk, undefined, recraftCreds);
260
272
  }, process.env.RECRAFT_MODEL || RecraftModels.RECRAFT_V3, ["recraft"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.RECRAFT));
273
+ const unregistered = Object.entries(PROVIDER_MODULE_TO_ID).filter(([module, id]) => {
274
+ if (!ProviderFactory.hasProvider(id)) {
275
+ logger.error(`[ProviderRegistry] drift: module "${module}" not registered as "${id}"`);
276
+ return true;
277
+ }
278
+ return false;
279
+ });
280
+ if (unregistered.length > 0) {
281
+ throw new Error(`ProviderRegistry drift: ${unregistered.length} module(s) in PROVIDER_MODULE_TO_ID are not registered`);
282
+ }
261
283
  logger.debug("All AI providers registered successfully");
262
284
  // ===== TTS HANDLER REGISTRATION =====
263
285
  try {
@@ -1,4 +1,14 @@
1
1
  import type { ProviderRegistryOptions } from "../types/index.js";
2
+ import { AIProviderName } from "../constants/enums.js";
3
+ /**
4
+ * Static module -> provider-ID manifest for every provider registered below.
5
+ * Module names (file/dir under src/lib/providers/) intentionally differ from
6
+ * their canonical IDs (e.g. amazonBedrock -> "bedrock", googleVertex ->
7
+ * "vertex"); static scanners that cannot resolve the dynamic import() calls
8
+ * use this mapping to confirm a module is registered. Enforced at runtime so
9
+ * the registry cannot silently drift from this manifest.
10
+ */
11
+ export declare const PROVIDER_MODULE_TO_ID: Readonly<Record<string, AIProviderName>>;
2
12
  /**
3
13
  * Provider Registry - registers all providers with the factory
4
14
  * This is where we migrate providers one by one to the new pattern
@@ -33,10 +43,12 @@ export declare class ProviderRegistry {
33
43
  * (avoids circular dependencies; see CLAUDE.md).
34
44
  *
35
45
  * Not registered (by design): index.ts, providerTypeUtils.ts,
36
- * anthropicBaseProvider.ts (legacy; anthropic.ts is live),
37
- * googleNativeGemini3.ts (shared helpers). Filename != provider ID
38
- * (e.g. amazonBedrock -> "bedrock"); static scanners that miss dynamic
39
- * imports may false-positive (Pattern Analysis #1178).
46
+ * openaiChatCompletionsBase.ts, openaiChatCompletionsClient.ts,
47
+ * anthropicImageBlocks.ts (shared base/helper modules), anthropicBaseProvider.ts
48
+ * (legacy; anthropic.ts is live), googleNativeGemini3.ts (shared helpers).
49
+ * Filename != provider ID (e.g. amazonBedrock -> "bedrock"); the
50
+ * statically-scannable PROVIDER_MODULE_TO_ID manifest maps every registered
51
+ * module to its provider ID and is enforced below (Pattern Analysis #1178/#1317).
40
52
  */
41
53
  private static _doRegister;
42
54
  /**
@@ -1,7 +1,48 @@
1
1
  import { ProviderFactory } from "./providerFactory.js";
2
2
  import { logger } from "../utils/logger.js";
3
- import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, MistralModels, OllamaModels, LiteLLMModels, HuggingFaceModels, DeepSeekModels, NvidiaNimModels, OpenRouterModels, XaiModels, GroqModels, CohereModels, TogetherAIModels, FireworksModels, PerplexityModels, CloudflareModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
3
+ import { AIProviderName, GoogleAIModels, OpenAIModels, AnthropicModels, VertexModels, OllamaModels, LiteLLMModels, HuggingFaceModels, DeepSeekModels, NvidiaNimModels, OpenRouterModels, CohereModels, VoyageModels, JinaModels, StabilityModels, IdeogramModels, RecraftModels, ReplicateModels, } from "../constants/enums.js";
4
4
  import { PROVIDER_DESCRIPTORS_BY_NAME } from "./providerDescriptors.js";
5
+ import { OPENAI_COMPAT_CATALOG } from "../providers/openaiCompatCatalog.js";
6
+ /**
7
+ * Static module -> provider-ID manifest for every provider registered below.
8
+ * Module names (file/dir under src/lib/providers/) intentionally differ from
9
+ * their canonical IDs (e.g. amazonBedrock -> "bedrock", googleVertex ->
10
+ * "vertex"); static scanners that cannot resolve the dynamic import() calls
11
+ * use this mapping to confirm a module is registered. Enforced at runtime so
12
+ * the registry cannot silently drift from this manifest.
13
+ */
14
+ export const PROVIDER_MODULE_TO_ID = {
15
+ amazonBedrock: AIProviderName.BEDROCK,
16
+ amazonSagemaker: AIProviderName.SAGEMAKER,
17
+ anthropic: AIProviderName.ANTHROPIC,
18
+ azureOpenai: AIProviderName.AZURE,
19
+ cloudflare: AIProviderName.CLOUDFLARE,
20
+ cohere: AIProviderName.COHERE,
21
+ deepseek: AIProviderName.DEEPSEEK,
22
+ fireworks: AIProviderName.FIREWORKS,
23
+ googleAiStudio: AIProviderName.GOOGLE_AI,
24
+ googleVertex: AIProviderName.VERTEX,
25
+ groq: AIProviderName.GROQ,
26
+ huggingFace: AIProviderName.HUGGINGFACE,
27
+ ideogram: AIProviderName.IDEOGRAM,
28
+ jina: AIProviderName.JINA,
29
+ litellm: AIProviderName.LITELLM,
30
+ llamaCpp: AIProviderName.LLAMACPP,
31
+ lmStudio: AIProviderName.LM_STUDIO,
32
+ mistral: AIProviderName.MISTRAL,
33
+ nvidiaNim: AIProviderName.NVIDIA_NIM,
34
+ ollama: AIProviderName.OLLAMA,
35
+ openAI: AIProviderName.OPENAI,
36
+ openaiCompatible: AIProviderName.OPENAI_COMPATIBLE,
37
+ openRouter: AIProviderName.OPENROUTER,
38
+ perplexity: AIProviderName.PERPLEXITY,
39
+ recraft: AIProviderName.RECRAFT,
40
+ replicate: AIProviderName.REPLICATE,
41
+ stability: AIProviderName.STABILITY,
42
+ togetherAi: AIProviderName.TOGETHER_AI,
43
+ voyage: AIProviderName.VOYAGE,
44
+ xai: AIProviderName.XAI,
45
+ };
5
46
  /**
6
47
  * Provider Registry - registers all providers with the factory
7
48
  * This is where we migrate providers one by one to the new pattern
@@ -53,10 +94,12 @@ export class ProviderRegistry {
53
94
  * (avoids circular dependencies; see CLAUDE.md).
54
95
  *
55
96
  * Not registered (by design): index.ts, providerTypeUtils.ts,
56
- * anthropicBaseProvider.ts (legacy; anthropic.ts is live),
57
- * googleNativeGemini3.ts (shared helpers). Filename != provider ID
58
- * (e.g. amazonBedrock -> "bedrock"); static scanners that miss dynamic
59
- * imports may false-positive (Pattern Analysis #1178).
97
+ * openaiChatCompletionsBase.ts, openaiChatCompletionsClient.ts,
98
+ * anthropicImageBlocks.ts (shared base/helper modules), anthropicBaseProvider.ts
99
+ * (legacy; anthropic.ts is live), googleNativeGemini3.ts (shared helpers).
100
+ * Filename != provider ID (e.g. amazonBedrock -> "bedrock"); the
101
+ * statically-scannable PROVIDER_MODULE_TO_ID manifest maps every registered
102
+ * module to its provider ID and is enforced below (Pattern Analysis #1178/#1317).
60
103
  */
61
104
  // eslint-disable-next-line max-lines-per-function
62
105
  static async _doRegister() {
@@ -111,12 +154,18 @@ export class ProviderRegistry {
111
154
  return new HuggingFaceProvider(modelName, sdk, region, hfCreds);
112
155
  }, process.env.HUGGINGFACE_MODEL ||
113
156
  HuggingFaceModels.QWEN_2_5_72B_INSTRUCT, ["huggingface", "hf"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.HUGGINGFACE));
114
- // Register Mistral AI provider
115
- ProviderFactory.registerProvider(AIProviderName.MISTRAL, async (modelName, _providerName, sdk, _region, credentials) => {
116
- const mistralCreds = credentials;
117
- const { MistralProvider } = await import("../providers/mistral.js");
118
- return new MistralProvider(modelName, sdk, undefined, mistralCreds);
119
- }, MistralModels.MISTRAL_LARGE_LATEST, ["mistral"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.MISTRAL));
157
+ // Register the config-driven OpenAI-compatible catalog providers
158
+ // (groq, xai, together-ai, fireworks, perplexity, mistral, cloudflare).
159
+ // To add a new zero-quirk OpenAI-compatible provider, add one entry to
160
+ // OPENAI_COMPAT_CATALOG (openaiCompatCatalog.ts) not a new block here.
161
+ for (const entry of OPENAI_COMPAT_CATALOG) {
162
+ ProviderFactory.registerProvider(entry.providerName, async (modelName, _providerName, sdk, _region, credentials) => {
163
+ const { ConfiguredOpenAICompatProvider } = await import("../providers/configuredOpenAICompat.js");
164
+ return new ConfiguredOpenAICompatProvider(entry, modelName, sdk, credentials);
165
+ }, entry.registryDefaultModelChecksEnvVar
166
+ ? process.env[entry.modelEnvVar] || entry.registryDefaultModel
167
+ : entry.registryDefaultModel, entry.aliases, PROVIDER_DESCRIPTORS_BY_NAME.get(entry.providerName));
168
+ }
120
169
  // Register Ollama provider
121
170
  ProviderFactory.registerProvider(AIProviderName.OLLAMA, async (modelName, _providerName, sdk, _region, credentials) => {
122
171
  const ollamaCreds = credentials;
@@ -178,49 +227,12 @@ export class ProviderRegistry {
178
227
  const { LlamaCppProvider } = await import("../providers/llamaCpp.js");
179
228
  return new LlamaCppProvider(modelName, sdk, undefined, llamaCppCreds);
180
229
  }, process.env.LLAMACPP_MODEL || undefined, ["llamacpp", "llama.cpp", "llama-cpp"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.LLAMACPP));
181
- // Register xAI Grok provider
182
- ProviderFactory.registerProvider(AIProviderName.XAI, async (modelName, _providerName, sdk, _region, credentials) => {
183
- const xaiCreds = credentials;
184
- const { XaiProvider } = await import("../providers/xai.js");
185
- return new XaiProvider(modelName, sdk, undefined, xaiCreds);
186
- }, process.env.XAI_MODEL || XaiModels.GROK_3, ["xai", "grok"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.XAI));
187
- // Register Groq provider
188
- ProviderFactory.registerProvider(AIProviderName.GROQ, async (modelName, _providerName, sdk, _region, credentials) => {
189
- const groqCreds = credentials;
190
- const { GroqProvider } = await import("../providers/groq.js");
191
- return new GroqProvider(modelName, sdk, undefined, groqCreds);
192
- }, process.env.GROQ_MODEL || GroqModels.LLAMA_3_3_70B_VERSATILE, ["groq"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.GROQ));
193
230
  // Register Cohere provider
194
231
  ProviderFactory.registerProvider(AIProviderName.COHERE, async (modelName, _providerName, sdk, _region, credentials) => {
195
232
  const cohereCreds = credentials;
196
233
  const { CohereProvider } = await import("../providers/cohere.js");
197
234
  return new CohereProvider(modelName, sdk, undefined, cohereCreds);
198
235
  }, process.env.COHERE_MODEL || CohereModels.COMMAND_R_PLUS, ["cohere"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.COHERE));
199
- // Register Together AI provider
200
- ProviderFactory.registerProvider(AIProviderName.TOGETHER_AI, async (modelName, _providerName, sdk, _region, credentials) => {
201
- const togetherCreds = credentials;
202
- const { TogetherAIProvider } = await import("../providers/togetherAi.js");
203
- return new TogetherAIProvider(modelName, sdk, undefined, togetherCreds);
204
- }, process.env.TOGETHER_MODEL ||
205
- TogetherAIModels.LLAMA_3_3_70B_INSTRUCT_TURBO, ["together-ai", "together"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.TOGETHER_AI));
206
- // Register Fireworks AI provider
207
- ProviderFactory.registerProvider(AIProviderName.FIREWORKS, async (modelName, _providerName, sdk, _region, credentials) => {
208
- const fireworksCreds = credentials;
209
- const { FireworksProvider } = await import("../providers/fireworks.js");
210
- return new FireworksProvider(modelName, sdk, undefined, fireworksCreds);
211
- }, process.env.FIREWORKS_MODEL || FireworksModels.DEEPSEEK_V4_PRO, ["fireworks"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.FIREWORKS));
212
- // Register Perplexity provider
213
- ProviderFactory.registerProvider(AIProviderName.PERPLEXITY, async (modelName, _providerName, sdk, _region, credentials) => {
214
- const perplexityCreds = credentials;
215
- const { PerplexityProvider } = await import("../providers/perplexity.js");
216
- return new PerplexityProvider(modelName, sdk, undefined, perplexityCreds);
217
- }, process.env.PERPLEXITY_MODEL || PerplexityModels.SONAR, ["perplexity", "pplx"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.PERPLEXITY));
218
- // Register Cloudflare Workers AI provider
219
- ProviderFactory.registerProvider(AIProviderName.CLOUDFLARE, async (modelName, _providerName, sdk, _region, credentials) => {
220
- const cloudflareCreds = credentials;
221
- const { CloudflareProvider } = await import("../providers/cloudflare.js");
222
- return new CloudflareProvider(modelName, sdk, undefined, cloudflareCreds);
223
- }, process.env.CLOUDFLARE_MODEL || CloudflareModels.LLAMA_3_3_70B_FAST, ["cloudflare", "workers-ai", "cf-ai"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.CLOUDFLARE));
224
236
  // Register Voyage AI embeddings provider
225
237
  ProviderFactory.registerProvider(AIProviderName.VOYAGE, async (modelName, _providerName, sdk, _region, credentials) => {
226
238
  const voyageCreds = credentials;
@@ -258,6 +270,16 @@ export class ProviderRegistry {
258
270
  const { RecraftProvider } = await import("../providers/recraft.js");
259
271
  return new RecraftProvider(modelName, sdk, undefined, recraftCreds);
260
272
  }, process.env.RECRAFT_MODEL || RecraftModels.RECRAFT_V3, ["recraft"], PROVIDER_DESCRIPTORS_BY_NAME.get(AIProviderName.RECRAFT));
273
+ const unregistered = Object.entries(PROVIDER_MODULE_TO_ID).filter(([module, id]) => {
274
+ if (!ProviderFactory.hasProvider(id)) {
275
+ logger.error(`[ProviderRegistry] drift: module "${module}" not registered as "${id}"`);
276
+ return true;
277
+ }
278
+ return false;
279
+ });
280
+ if (unregistered.length > 0) {
281
+ throw new Error(`ProviderRegistry drift: ${unregistered.length} module(s) in PROVIDER_MODULE_TO_ID are not registered`);
282
+ }
261
283
  logger.debug("All AI providers registered successfully");
262
284
  // ===== TTS HANDLER REGISTRATION =====
263
285
  try {
@@ -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
  }
@@ -464,15 +464,22 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
464
464
  const apiErr = await buildAPIError(url, body, res);
465
465
  // One-shot 400 retry. The overflow corrector runs FIRST (it can
466
466
  // re-fit max_tokens from the provider's own numbers and also
467
- // self-heals the runtime window registry); otherwise a subclass
468
- // may strip a rejected field and return a modified body (e.g.
469
- // NIM's chat_template / reasoning_budget). The retry runs under
470
- // the SAME timeout controller as the first attempt, so the
471
- // configured timeout caps the overall call matching the
472
- // streaming path, which reuses its composed signal for the retry.
467
+ // self-heals the runtime window registry); its output then feeds
468
+ // a subclass hook that may strip a rejected field (e.g. NIM's
469
+ // chat_template / reasoning_budget), so a body that needs BOTH
470
+ // fixes gets both a plain `??` between the two would let
471
+ // whichever ran first silently win and drop the other's fix. The
472
+ // retry runs under the SAME timeout controller as the first
473
+ // attempt, so the configured timeout caps the overall call —
474
+ // matching the streaming path, which reuses its composed signal
475
+ // for the retry.
473
476
  const retryBody = res.status === 400
474
- ? (correctBodyAfterContextOverflow(body, apiErr) ??
475
- adjustBodyAfter400(body, apiErr))
477
+ ? (() => {
478
+ const typedErr = apiErr;
479
+ const overflowCorrected = correctBodyAfterContextOverflow(body, typedErr);
480
+ return (adjustBodyAfter400(overflowCorrected ?? body, typedErr) ??
481
+ overflowCorrected);
482
+ })()
476
483
  : undefined;
477
484
  if (!retryBody) {
478
485
  throw apiErr;
@@ -967,13 +974,18 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
967
974
  // consumed inside doFetch's closure, either returned on success or
968
975
  // discarded after buildAPIError read its body on failure.
969
976
  const apiErr = err;
970
- // Overflow corrector first (re-fits max_tokens from the provider's
971
- // own numbers + self-heals the window registry), then the subclass
972
- // hook (e.g. NIM strips chat_template / reasoning_budget when a model
973
- // rejects them).
977
+ // Overflow corrector first (re-fits max_tokens from the provider's own
978
+ // numbers + self-heals the window registry); its output then feeds the
979
+ // subclass hook (e.g. NIM strips chat_template / reasoning_budget when
980
+ // a model rejects them), so a body needing BOTH fixes gets both — a
981
+ // plain `??` between the two would let whichever ran first silently
982
+ // win and drop the other's fix.
974
983
  const retryBody = apiErr.statusCode === 400
975
- ? (this.correctBodyAfterContextOverflow(body, apiErr) ??
976
- this.adjustBodyAfter400(body, apiErr))
984
+ ? (() => {
985
+ const overflowCorrected = this.correctBodyAfterContextOverflow(body, apiErr);
986
+ return (this.adjustBodyAfter400(overflowCorrected ?? body, apiErr) ??
987
+ overflowCorrected);
988
+ })()
977
989
  : undefined;
978
990
  if (!retryBody) {
979
991
  throw apiErr;
@@ -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
@@ -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
  }
@@ -464,15 +464,22 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
464
464
  const apiErr = await buildAPIError(url, body, res);
465
465
  // One-shot 400 retry. The overflow corrector runs FIRST (it can
466
466
  // re-fit max_tokens from the provider's own numbers and also
467
- // self-heals the runtime window registry); otherwise a subclass
468
- // may strip a rejected field and return a modified body (e.g.
469
- // NIM's chat_template / reasoning_budget). The retry runs under
470
- // the SAME timeout controller as the first attempt, so the
471
- // configured timeout caps the overall call matching the
472
- // streaming path, which reuses its composed signal for the retry.
467
+ // self-heals the runtime window registry); its output then feeds
468
+ // a subclass hook that may strip a rejected field (e.g. NIM's
469
+ // chat_template / reasoning_budget), so a body that needs BOTH
470
+ // fixes gets both a plain `??` between the two would let
471
+ // whichever ran first silently win and drop the other's fix. The
472
+ // retry runs under the SAME timeout controller as the first
473
+ // attempt, so the configured timeout caps the overall call —
474
+ // matching the streaming path, which reuses its composed signal
475
+ // for the retry.
473
476
  const retryBody = res.status === 400
474
- ? (correctBodyAfterContextOverflow(body, apiErr) ??
475
- adjustBodyAfter400(body, apiErr))
477
+ ? (() => {
478
+ const typedErr = apiErr;
479
+ const overflowCorrected = correctBodyAfterContextOverflow(body, typedErr);
480
+ return (adjustBodyAfter400(overflowCorrected ?? body, typedErr) ??
481
+ overflowCorrected);
482
+ })()
476
483
  : undefined;
477
484
  if (!retryBody) {
478
485
  throw apiErr;
@@ -967,13 +974,18 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
967
974
  // consumed inside doFetch's closure, either returned on success or
968
975
  // discarded after buildAPIError read its body on failure.
969
976
  const apiErr = err;
970
- // Overflow corrector first (re-fits max_tokens from the provider's
971
- // own numbers + self-heals the window registry), then the subclass
972
- // hook (e.g. NIM strips chat_template / reasoning_budget when a model
973
- // rejects them).
977
+ // Overflow corrector first (re-fits max_tokens from the provider's own
978
+ // numbers + self-heals the window registry); its output then feeds the
979
+ // subclass hook (e.g. NIM strips chat_template / reasoning_budget when
980
+ // a model rejects them), so a body needing BOTH fixes gets both — a
981
+ // plain `??` between the two would let whichever ran first silently
982
+ // win and drop the other's fix.
974
983
  const retryBody = apiErr.statusCode === 400
975
- ? (this.correctBodyAfterContextOverflow(body, apiErr) ??
976
- this.adjustBodyAfter400(body, apiErr))
984
+ ? (() => {
985
+ const overflowCorrected = this.correctBodyAfterContextOverflow(body, apiErr);
986
+ return (this.adjustBodyAfter400(overflowCorrected ?? body, apiErr) ??
987
+ overflowCorrected);
988
+ })()
977
989
  : undefined;
978
990
  if (!retryBody) {
979
991
  throw apiErr;