@juspay/neurolink 7.6.1 → 7.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +15 -4
  2. package/README.md +78 -3
  3. package/dist/cli/commands/config.d.ts +275 -3
  4. package/dist/cli/commands/config.js +121 -0
  5. package/dist/cli/commands/mcp.js +77 -28
  6. package/dist/cli/factories/commandFactory.js +359 -6
  7. package/dist/core/analytics.js +7 -27
  8. package/dist/core/baseProvider.js +43 -4
  9. package/dist/core/constants.d.ts +46 -0
  10. package/dist/core/constants.js +47 -0
  11. package/dist/core/dynamicModels.d.ts +16 -4
  12. package/dist/core/dynamicModels.js +130 -26
  13. package/dist/core/evaluation.js +5 -1
  14. package/dist/core/evaluationProviders.d.ts +6 -2
  15. package/dist/core/evaluationProviders.js +41 -125
  16. package/dist/core/factory.d.ts +5 -0
  17. package/dist/core/factory.js +62 -50
  18. package/dist/core/modelConfiguration.d.ts +246 -0
  19. package/dist/core/modelConfiguration.js +775 -0
  20. package/dist/core/types.d.ts +22 -3
  21. package/dist/core/types.js +5 -1
  22. package/dist/factories/providerRegistry.js +3 -3
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/lib/core/analytics.js +7 -27
  26. package/dist/lib/core/baseProvider.js +43 -4
  27. package/dist/lib/core/constants.d.ts +46 -0
  28. package/dist/lib/core/constants.js +47 -0
  29. package/dist/lib/core/dynamicModels.d.ts +16 -4
  30. package/dist/lib/core/dynamicModels.js +130 -26
  31. package/dist/lib/core/evaluation.js +5 -1
  32. package/dist/lib/core/evaluationProviders.d.ts +6 -2
  33. package/dist/lib/core/evaluationProviders.js +41 -125
  34. package/dist/lib/core/factory.d.ts +5 -0
  35. package/dist/lib/core/factory.js +63 -50
  36. package/dist/lib/core/modelConfiguration.d.ts +246 -0
  37. package/dist/lib/core/modelConfiguration.js +775 -0
  38. package/dist/lib/core/types.d.ts +22 -3
  39. package/dist/lib/core/types.js +5 -1
  40. package/dist/lib/factories/providerRegistry.js +3 -3
  41. package/dist/lib/index.d.ts +1 -1
  42. package/dist/lib/index.js +1 -1
  43. package/dist/lib/mcp/factory.d.ts +5 -5
  44. package/dist/lib/mcp/factory.js +2 -2
  45. package/dist/lib/mcp/servers/utilities/utilityServer.d.ts +1 -1
  46. package/dist/lib/mcp/servers/utilities/utilityServer.js +1 -1
  47. package/dist/lib/mcp/toolRegistry.js +2 -2
  48. package/dist/lib/neurolink.d.ts +168 -12
  49. package/dist/lib/neurolink.js +685 -123
  50. package/dist/lib/providers/anthropic.js +52 -2
  51. package/dist/lib/providers/googleAiStudio.js +4 -0
  52. package/dist/lib/providers/googleVertex.d.ts +75 -9
  53. package/dist/lib/providers/googleVertex.js +365 -46
  54. package/dist/lib/providers/huggingFace.d.ts +52 -11
  55. package/dist/lib/providers/huggingFace.js +180 -42
  56. package/dist/lib/providers/litellm.d.ts +9 -9
  57. package/dist/lib/providers/litellm.js +103 -16
  58. package/dist/lib/providers/ollama.d.ts +52 -17
  59. package/dist/lib/providers/ollama.js +276 -68
  60. package/dist/lib/sdk/toolRegistration.d.ts +42 -0
  61. package/dist/lib/sdk/toolRegistration.js +269 -27
  62. package/dist/lib/telemetry/telemetryService.d.ts +6 -0
  63. package/dist/lib/telemetry/telemetryService.js +38 -3
  64. package/dist/lib/types/contextTypes.d.ts +75 -11
  65. package/dist/lib/types/contextTypes.js +227 -1
  66. package/dist/lib/types/domainTypes.d.ts +62 -0
  67. package/dist/lib/types/domainTypes.js +5 -0
  68. package/dist/lib/types/generateTypes.d.ts +52 -0
  69. package/dist/lib/types/index.d.ts +1 -0
  70. package/dist/lib/types/mcpTypes.d.ts +1 -1
  71. package/dist/lib/types/mcpTypes.js +1 -1
  72. package/dist/lib/types/streamTypes.d.ts +14 -0
  73. package/dist/lib/types/universalProviderOptions.d.ts +1 -1
  74. package/dist/lib/utils/errorHandling.d.ts +142 -0
  75. package/dist/lib/utils/errorHandling.js +316 -0
  76. package/dist/lib/utils/factoryProcessing.d.ts +74 -0
  77. package/dist/lib/utils/factoryProcessing.js +588 -0
  78. package/dist/lib/utils/optionsConversion.d.ts +54 -0
  79. package/dist/lib/utils/optionsConversion.js +126 -0
  80. package/dist/lib/utils/optionsUtils.d.ts +246 -0
  81. package/dist/lib/utils/optionsUtils.js +960 -0
  82. package/dist/lib/utils/providerConfig.js +6 -2
  83. package/dist/lib/utils/providerHealth.d.ts +107 -0
  84. package/dist/lib/utils/providerHealth.js +543 -0
  85. package/dist/lib/utils/providerUtils.d.ts +17 -0
  86. package/dist/lib/utils/providerUtils.js +271 -16
  87. package/dist/lib/utils/timeout.js +1 -1
  88. package/dist/lib/utils/tokenLimits.d.ts +33 -0
  89. package/dist/lib/utils/tokenLimits.js +118 -0
  90. package/dist/mcp/factory.d.ts +5 -5
  91. package/dist/mcp/factory.js +2 -2
  92. package/dist/mcp/servers/utilities/utilityServer.d.ts +1 -1
  93. package/dist/mcp/servers/utilities/utilityServer.js +1 -1
  94. package/dist/mcp/toolRegistry.js +2 -2
  95. package/dist/neurolink.d.ts +168 -12
  96. package/dist/neurolink.js +685 -123
  97. package/dist/providers/anthropic.js +52 -2
  98. package/dist/providers/googleAiStudio.js +4 -0
  99. package/dist/providers/googleVertex.d.ts +75 -9
  100. package/dist/providers/googleVertex.js +365 -46
  101. package/dist/providers/huggingFace.d.ts +52 -11
  102. package/dist/providers/huggingFace.js +181 -43
  103. package/dist/providers/litellm.d.ts +9 -9
  104. package/dist/providers/litellm.js +103 -16
  105. package/dist/providers/ollama.d.ts +52 -17
  106. package/dist/providers/ollama.js +276 -68
  107. package/dist/sdk/toolRegistration.d.ts +42 -0
  108. package/dist/sdk/toolRegistration.js +269 -27
  109. package/dist/telemetry/telemetryService.d.ts +6 -0
  110. package/dist/telemetry/telemetryService.js +38 -3
  111. package/dist/types/contextTypes.d.ts +75 -11
  112. package/dist/types/contextTypes.js +227 -2
  113. package/dist/types/domainTypes.d.ts +62 -0
  114. package/dist/types/domainTypes.js +5 -0
  115. package/dist/types/generateTypes.d.ts +52 -0
  116. package/dist/types/index.d.ts +1 -0
  117. package/dist/types/mcpTypes.d.ts +1 -1
  118. package/dist/types/mcpTypes.js +1 -1
  119. package/dist/types/streamTypes.d.ts +14 -0
  120. package/dist/types/universalProviderOptions.d.ts +1 -1
  121. package/dist/types/universalProviderOptions.js +0 -1
  122. package/dist/utils/errorHandling.d.ts +142 -0
  123. package/dist/utils/errorHandling.js +316 -0
  124. package/dist/utils/factoryProcessing.d.ts +74 -0
  125. package/dist/utils/factoryProcessing.js +588 -0
  126. package/dist/utils/optionsConversion.d.ts +54 -0
  127. package/dist/utils/optionsConversion.js +126 -0
  128. package/dist/utils/optionsUtils.d.ts +246 -0
  129. package/dist/utils/optionsUtils.js +960 -0
  130. package/dist/utils/providerConfig.js +6 -2
  131. package/dist/utils/providerHealth.d.ts +107 -0
  132. package/dist/utils/providerHealth.js +543 -0
  133. package/dist/utils/providerUtils.d.ts +17 -0
  134. package/dist/utils/providerUtils.js +271 -16
  135. package/dist/utils/timeout.js +1 -1
  136. package/dist/utils/tokenLimits.d.ts +33 -0
  137. package/dist/utils/tokenLimits.js +118 -0
  138. package/package.json +2 -2
@@ -308,7 +308,11 @@ export function createVertexProjectConfig() {
308
308
  "3. Enable Vertex AI API",
309
309
  "4. Set up authentication",
310
310
  ],
311
- fallbackEnvVars: ["VERTEX_PROJECT_ID"],
311
+ fallbackEnvVars: [
312
+ "VERTEX_PROJECT_ID",
313
+ "GOOGLE_VERTEX_PROJECT",
314
+ "GOOGLE_CLOUD_PROJECT",
315
+ ],
312
316
  };
313
317
  }
314
318
  /**
@@ -331,7 +335,7 @@ export function createGoogleAuthConfig() {
331
335
  "GOOGLE_AUTH_CLIENT_EMAIL=your-service-account@project.iam.gserviceaccount.com",
332
336
  "GOOGLE_AUTH_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----...",
333
337
  ],
334
- fallbackEnvVars: ["GOOGLE_SERVICE_ACCOUNT_KEY", "GOOGLE_AUTH_CLIENT_EMAIL"],
338
+ fallbackEnvVars: ["GOOGLE_SERVICE_ACCOUNT_KEY"],
335
339
  };
336
340
  }
337
341
  /**
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Provider Health Checking System
3
+ * Prevents 500 errors by validating provider availability and configuration
4
+ */
5
+ import { AIProviderName } from "../core/types.js";
6
+ export interface ProviderHealthStatus {
7
+ provider: AIProviderName;
8
+ isHealthy: boolean;
9
+ isConfigured: boolean;
10
+ hasApiKey: boolean;
11
+ lastChecked: Date;
12
+ error?: string;
13
+ warning?: string;
14
+ responseTime?: number;
15
+ configurationIssues: string[];
16
+ recommendations: string[];
17
+ }
18
+ export interface ProviderHealthCheckOptions {
19
+ timeout?: number;
20
+ includeConnectivityTest?: boolean;
21
+ includeModelValidation?: boolean;
22
+ cacheResults?: boolean;
23
+ maxCacheAge?: number;
24
+ }
25
+ export declare class ProviderHealthChecker {
26
+ private static healthCache;
27
+ private static readonly DEFAULT_TIMEOUT;
28
+ private static readonly DEFAULT_CACHE_AGE;
29
+ private static readonly CONSECUTIVE_FAILURE_THRESHOLD;
30
+ private static consecutiveFailures;
31
+ /**
32
+ * Validate and return a safe failure threshold value
33
+ */
34
+ private static getValidatedFailureThreshold;
35
+ /**
36
+ * Comprehensive health check for a provider
37
+ */
38
+ static checkProviderHealth(providerName: AIProviderName, options?: ProviderHealthCheckOptions): Promise<ProviderHealthStatus>;
39
+ /**
40
+ * Check environment configuration for a provider
41
+ */
42
+ private static checkEnvironmentConfiguration;
43
+ /**
44
+ * Check API key validity (format validation)
45
+ */
46
+ private static checkApiKeyValidity;
47
+ /**
48
+ * Check connectivity to provider endpoints
49
+ */
50
+ private static checkConnectivity;
51
+ /**
52
+ * Check model availability (if possible without making API calls)
53
+ */
54
+ private static checkModelAvailability;
55
+ /**
56
+ * Get required environment variables for a provider
57
+ */
58
+ private static getRequiredEnvironmentVariables;
59
+ /**
60
+ * Get API key environment variable for a provider
61
+ */
62
+ private static getApiKeyEnvironmentVariable;
63
+ /**
64
+ * Validate API key format for a provider
65
+ */
66
+ private static validateApiKeyFormat;
67
+ /**
68
+ * Get health check endpoint for connectivity testing
69
+ */
70
+ private static getProviderHealthEndpoint;
71
+ /**
72
+ * Provider-specific configuration checks
73
+ */
74
+ private static checkProviderSpecificConfig;
75
+ /**
76
+ * Get common models for a provider
77
+ */
78
+ private static getCommonModelsForProvider;
79
+ /**
80
+ * Get cached health status if still valid
81
+ */
82
+ private static getCachedHealth;
83
+ /**
84
+ * Clear health cache for a provider or all providers
85
+ */
86
+ static clearHealthCache(providerName?: AIProviderName): void;
87
+ /**
88
+ * Get the best healthy provider from a list of options
89
+ * Prioritizes healthy providers over configured but unhealthy ones
90
+ */
91
+ static getBestHealthyProvider(preferredProviders?: string[]): Promise<string | null>;
92
+ /**
93
+ * Get health status for all registered providers
94
+ */
95
+ static checkAllProvidersHealth(options?: ProviderHealthCheckOptions): Promise<ProviderHealthStatus[]>;
96
+ /**
97
+ * Get a summary of provider health
98
+ */
99
+ static getHealthSummary(healthStatuses: ProviderHealthStatus[]): {
100
+ total: number;
101
+ healthy: number;
102
+ configured: number;
103
+ hasIssues: number;
104
+ healthyProviders: string[];
105
+ unhealthyProviders: string[];
106
+ };
107
+ }
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Provider Health Checking System
3
+ * Prevents 500 errors by validating provider availability and configuration
4
+ */
5
+ import { logger } from "./logger.js";
6
+ import { AIProviderName } from "../core/types.js";
7
+ import { basename } from "path";
8
+ export class ProviderHealthChecker {
9
+ static healthCache = new Map();
10
+ static DEFAULT_TIMEOUT = 5000; // 5 seconds
11
+ static DEFAULT_CACHE_AGE = 300000; // 5 minutes
12
+ static CONSECUTIVE_FAILURE_THRESHOLD = ProviderHealthChecker.getValidatedFailureThreshold();
13
+ static consecutiveFailures = new Map();
14
+ /**
15
+ * Validate and return a safe failure threshold value
16
+ */
17
+ static getValidatedFailureThreshold() {
18
+ const envValue = process.env.PROVIDER_FAILURE_THRESHOLD;
19
+ if (!envValue) {
20
+ return 3; // default
21
+ }
22
+ const parsed = Number(envValue);
23
+ if (isNaN(parsed) || parsed <= 0 || parsed > 10) {
24
+ console.warn(`Invalid PROVIDER_FAILURE_THRESHOLD: ${envValue} (must be between 1 and 10), using default: 3`);
25
+ return 3;
26
+ }
27
+ return parsed;
28
+ }
29
+ /**
30
+ * Comprehensive health check for a provider
31
+ */
32
+ static async checkProviderHealth(providerName, options = {}) {
33
+ const { timeout = this.DEFAULT_TIMEOUT, includeConnectivityTest = false, includeModelValidation = false, cacheResults = true, maxCacheAge = this.DEFAULT_CACHE_AGE, } = options;
34
+ // Check cache first
35
+ if (cacheResults) {
36
+ const cached = this.getCachedHealth(providerName, maxCacheAge);
37
+ if (cached) {
38
+ logger.debug(`Using cached health status for ${providerName}`);
39
+ return cached;
40
+ }
41
+ }
42
+ // Check if provider has consecutive failures (blacklisting)
43
+ const failureCount = this.consecutiveFailures.get(providerName) || 0;
44
+ if (failureCount >= this.CONSECUTIVE_FAILURE_THRESHOLD) {
45
+ const healthStatus = {
46
+ provider: providerName,
47
+ isHealthy: false,
48
+ isConfigured: false,
49
+ hasApiKey: false,
50
+ lastChecked: new Date(),
51
+ error: `Provider blacklisted after ${failureCount} consecutive failures`,
52
+ warning: "Provider will be retried after cache TTL expires",
53
+ configurationIssues: [
54
+ `Blacklisted due to ${failureCount} consecutive failures`,
55
+ ],
56
+ recommendations: ["Check provider status and configuration"],
57
+ };
58
+ logger.warn(`Provider ${providerName} blacklisted due to consecutive failures`, { failureCount });
59
+ return healthStatus;
60
+ }
61
+ const startTime = Date.now();
62
+ const healthStatus = {
63
+ provider: providerName,
64
+ isHealthy: false,
65
+ isConfigured: false,
66
+ hasApiKey: false,
67
+ lastChecked: new Date(),
68
+ configurationIssues: [],
69
+ recommendations: [],
70
+ };
71
+ try {
72
+ // 1. Check environment configuration
73
+ await this.checkEnvironmentConfiguration(providerName, healthStatus);
74
+ // 2. Check API key validity (basic format validation)
75
+ await this.checkApiKeyValidity(providerName, healthStatus);
76
+ // 3. Optional: Connectivity test
77
+ if (includeConnectivityTest) {
78
+ await this.checkConnectivity(providerName, healthStatus, timeout);
79
+ }
80
+ // 4. Optional: Model validation
81
+ if (includeModelValidation) {
82
+ await this.checkModelAvailability(providerName, healthStatus);
83
+ }
84
+ // 5. Determine overall health
85
+ healthStatus.isHealthy =
86
+ healthStatus.isConfigured &&
87
+ healthStatus.hasApiKey &&
88
+ healthStatus.configurationIssues.length === 0;
89
+ healthStatus.responseTime = Date.now() - startTime;
90
+ // Cache results
91
+ if (cacheResults) {
92
+ this.healthCache.set(providerName, {
93
+ status: healthStatus,
94
+ timestamp: Date.now(),
95
+ });
96
+ }
97
+ // Reset failure count on success
98
+ if (healthStatus.isHealthy) {
99
+ this.consecutiveFailures.delete(providerName);
100
+ }
101
+ else {
102
+ // Track consecutive failures
103
+ const currentFailures = this.consecutiveFailures.get(providerName) || 0;
104
+ this.consecutiveFailures.set(providerName, currentFailures + 1);
105
+ }
106
+ logger.debug(`Health check completed for ${providerName}`, {
107
+ isHealthy: healthStatus.isHealthy,
108
+ responseTime: healthStatus.responseTime,
109
+ issues: healthStatus.configurationIssues.length,
110
+ });
111
+ }
112
+ catch (error) {
113
+ const errorMessage = error instanceof Error ? error.message : String(error);
114
+ healthStatus.error = errorMessage;
115
+ healthStatus.configurationIssues.push(`Health check failed: ${errorMessage}`);
116
+ healthStatus.responseTime = Date.now() - startTime;
117
+ // Track consecutive failures
118
+ const currentFailures = this.consecutiveFailures.get(providerName) || 0;
119
+ this.consecutiveFailures.set(providerName, currentFailures + 1);
120
+ logger.warn(`Health check failed for ${providerName}`, {
121
+ error: errorMessage,
122
+ consecutiveFailures: currentFailures + 1,
123
+ });
124
+ }
125
+ return healthStatus;
126
+ }
127
+ /**
128
+ * Check environment configuration for a provider
129
+ */
130
+ static async checkEnvironmentConfiguration(providerName, healthStatus) {
131
+ const requiredEnvVars = this.getRequiredEnvironmentVariables(providerName);
132
+ let allConfigured = true;
133
+ const missingVars = [];
134
+ for (const envVar of requiredEnvVars) {
135
+ const value = process.env[envVar];
136
+ if (!value || value.trim() === "") {
137
+ allConfigured = false;
138
+ missingVars.push(envVar);
139
+ }
140
+ }
141
+ healthStatus.isConfigured = allConfigured;
142
+ if (!allConfigured) {
143
+ healthStatus.configurationIssues.push(`Missing required environment variables: ${missingVars.join(", ")}`);
144
+ healthStatus.recommendations.push(`Set the following environment variables: ${missingVars.join(", ")}`);
145
+ }
146
+ // Provider-specific configuration checks
147
+ await this.checkProviderSpecificConfig(providerName, healthStatus);
148
+ }
149
+ /**
150
+ * Check API key validity (format validation)
151
+ */
152
+ static async checkApiKeyValidity(providerName, healthStatus) {
153
+ const apiKeyVar = this.getApiKeyEnvironmentVariable(providerName);
154
+ const apiKey = process.env[apiKeyVar];
155
+ if (!apiKey) {
156
+ healthStatus.hasApiKey = false;
157
+ healthStatus.configurationIssues.push(`API key not found in ${apiKeyVar}`);
158
+ return;
159
+ }
160
+ // Basic format validation
161
+ const isValidFormat = this.validateApiKeyFormat(providerName, apiKey);
162
+ if (!isValidFormat) {
163
+ healthStatus.hasApiKey = false;
164
+ healthStatus.configurationIssues.push(`API key format appears invalid for ${providerName}`);
165
+ healthStatus.recommendations.push(`Verify the API key format for ${providerName}`);
166
+ }
167
+ else {
168
+ healthStatus.hasApiKey = true;
169
+ }
170
+ }
171
+ /**
172
+ * Check connectivity to provider endpoints
173
+ */
174
+ static async checkConnectivity(providerName, healthStatus, timeout) {
175
+ const endpoint = this.getProviderHealthEndpoint(providerName);
176
+ if (!endpoint) {
177
+ healthStatus.warning = "No connectivity test available for this provider";
178
+ return;
179
+ }
180
+ try {
181
+ const controller = new AbortController();
182
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
183
+ const response = await fetch(endpoint, {
184
+ method: "HEAD",
185
+ signal: controller.signal,
186
+ headers: {
187
+ "User-Agent": "NeuroLink-HealthCheck/1.0",
188
+ },
189
+ });
190
+ clearTimeout(timeoutId);
191
+ if (!response.ok) {
192
+ healthStatus.configurationIssues.push(`Connectivity test failed: HTTP ${response.status}`);
193
+ }
194
+ }
195
+ catch (error) {
196
+ const errorMessage = error instanceof Error ? error.message : String(error);
197
+ // Provide specific error messages for common network issues
198
+ if (errorMessage.includes("abort")) {
199
+ healthStatus.configurationIssues.push(`Connectivity test timed out after ${timeout}ms`);
200
+ }
201
+ else if (errorMessage.includes("ENOTFOUND") ||
202
+ errorMessage.includes("getaddrinfo")) {
203
+ healthStatus.configurationIssues.push(`DNS resolution failed: Cannot resolve hostname for ${providerName}`);
204
+ }
205
+ else if (errorMessage.includes("ECONNREFUSED")) {
206
+ healthStatus.configurationIssues.push(`Connection refused: ${providerName} service is not accepting connections`);
207
+ }
208
+ else if (errorMessage.includes("ETIMEDOUT")) {
209
+ healthStatus.configurationIssues.push(`Connection timeout: ${providerName} service did not respond`);
210
+ }
211
+ else if (errorMessage.includes("certificate") ||
212
+ errorMessage.includes("SSL") ||
213
+ errorMessage.includes("TLS")) {
214
+ healthStatus.configurationIssues.push(`SSL/TLS certificate error: ${providerName} has certificate issues`);
215
+ }
216
+ else if (errorMessage.includes("ECONNRESET")) {
217
+ healthStatus.configurationIssues.push(`Connection reset: ${providerName} terminated the connection`);
218
+ }
219
+ else if (errorMessage.includes("network") ||
220
+ errorMessage.includes("offline")) {
221
+ healthStatus.configurationIssues.push(`Network error: Check internet connectivity and firewall settings`);
222
+ }
223
+ else {
224
+ healthStatus.configurationIssues.push(`Connectivity test failed: ${errorMessage}`);
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Check model availability (if possible without making API calls)
230
+ */
231
+ static async checkModelAvailability(providerName, healthStatus) {
232
+ // For now, we'll do basic model name validation
233
+ // In the future, this could be enhanced with actual API calls
234
+ const commonModels = this.getCommonModelsForProvider(providerName);
235
+ if (commonModels.length > 0) {
236
+ healthStatus.recommendations.push(`Common models for ${providerName}: ${commonModels.slice(0, 3).join(", ")}`);
237
+ }
238
+ }
239
+ /**
240
+ * Get required environment variables for a provider
241
+ */
242
+ static getRequiredEnvironmentVariables(providerName) {
243
+ switch (providerName) {
244
+ case AIProviderName.ANTHROPIC:
245
+ return ["ANTHROPIC_API_KEY"];
246
+ case AIProviderName.OPENAI:
247
+ return ["OPENAI_API_KEY"];
248
+ case AIProviderName.VERTEX:
249
+ // Vertex AI requires authentication, but not via a single environment variable.
250
+ // Authentication can be provided via a credential file or individual credentials + project.
251
+ // The required authentication is checked in checkProviderSpecificConfig instead of here.
252
+ // Returning an empty array here does NOT mean authentication is not required.
253
+ return [];
254
+ case AIProviderName.GOOGLE_AI:
255
+ return ["GOOGLE_AI_API_KEY"];
256
+ case AIProviderName.BEDROCK:
257
+ return ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
258
+ case AIProviderName.OLLAMA:
259
+ return []; // Ollama typically doesn't require API keys
260
+ default:
261
+ return [];
262
+ }
263
+ }
264
+ /**
265
+ * Get API key environment variable for a provider
266
+ */
267
+ static getApiKeyEnvironmentVariable(providerName) {
268
+ switch (providerName) {
269
+ case AIProviderName.ANTHROPIC:
270
+ return "ANTHROPIC_API_KEY";
271
+ case AIProviderName.OPENAI:
272
+ return "OPENAI_API_KEY";
273
+ case AIProviderName.VERTEX:
274
+ return "GOOGLE_APPLICATION_CREDENTIALS";
275
+ case AIProviderName.GOOGLE_AI:
276
+ return "GOOGLE_AI_API_KEY";
277
+ case AIProviderName.BEDROCK:
278
+ return "AWS_ACCESS_KEY_ID";
279
+ case AIProviderName.OLLAMA:
280
+ return "OLLAMA_API_BASE";
281
+ default:
282
+ return "";
283
+ }
284
+ }
285
+ /**
286
+ * Validate API key format for a provider
287
+ */
288
+ static validateApiKeyFormat(providerName, apiKey) {
289
+ switch (providerName) {
290
+ case AIProviderName.ANTHROPIC:
291
+ return apiKey.startsWith("sk-ant-") && apiKey.length > 20;
292
+ case AIProviderName.OPENAI:
293
+ return apiKey.startsWith("sk-") && apiKey.length > 20;
294
+ case AIProviderName.GOOGLE_AI:
295
+ return apiKey.length > 20; // Basic length check
296
+ case AIProviderName.VERTEX:
297
+ return apiKey.endsWith(".json") || apiKey.includes("type"); // JSON key format
298
+ case AIProviderName.BEDROCK:
299
+ return apiKey.length >= 20; // AWS access key length
300
+ case AIProviderName.OLLAMA:
301
+ return true; // Ollama usually doesn't require specific format
302
+ default:
303
+ return true; // Default to true for unknown providers
304
+ }
305
+ }
306
+ /**
307
+ * Get health check endpoint for connectivity testing
308
+ */
309
+ static getProviderHealthEndpoint(providerName) {
310
+ switch (providerName) {
311
+ case AIProviderName.ANTHROPIC:
312
+ return null; // Anthropic doesn't have a public health endpoint
313
+ case AIProviderName.OPENAI:
314
+ return "https://api.openai.com/v1/models";
315
+ case AIProviderName.GOOGLE_AI:
316
+ return null; // No public health endpoint
317
+ case AIProviderName.VERTEX:
318
+ return null; // Complex authentication required
319
+ case AIProviderName.BEDROCK:
320
+ return null; // AWS endpoints vary by region
321
+ case AIProviderName.OLLAMA:
322
+ return "http://localhost:11434/api/version";
323
+ default:
324
+ return null;
325
+ }
326
+ }
327
+ /**
328
+ * Provider-specific configuration checks
329
+ */
330
+ static async checkProviderSpecificConfig(providerName, healthStatus) {
331
+ switch (providerName) {
332
+ case AIProviderName.VERTEX: {
333
+ // Check for Google Cloud project ID (with fallbacks)
334
+ const projectId = process.env.GOOGLE_PROJECT_ID ||
335
+ process.env.GOOGLE_CLOUD_PROJECT_ID ||
336
+ process.env.GOOGLE_VERTEX_PROJECT ||
337
+ process.env.GOOGLE_CLOUD_PROJECT ||
338
+ process.env.VERTEX_PROJECT_ID;
339
+ if (!projectId) {
340
+ healthStatus.configurationIssues.push("Google Cloud project ID not set");
341
+ healthStatus.recommendations.push("Set one of: GOOGLE_VERTEX_PROJECT, GOOGLE_CLOUD_PROJECT_ID, GOOGLE_PROJECT_ID, or GOOGLE_CLOUD_PROJECT");
342
+ }
343
+ // Check for authentication (either credentials file OR individual credentials)
344
+ const hasCredentialsFile = !!process.env.GOOGLE_APPLICATION_CREDENTIALS;
345
+ const hasServiceAccountKey = !!process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
346
+ const hasIndividualCredentials = !!(process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
347
+ process.env.GOOGLE_AUTH_PRIVATE_KEY);
348
+ if (!hasCredentialsFile &&
349
+ !hasServiceAccountKey &&
350
+ !hasIndividualCredentials) {
351
+ healthStatus.configurationIssues.push("Google Cloud authentication not configured");
352
+ healthStatus.recommendations.push("Set either GOOGLE_APPLICATION_CREDENTIALS (file path), GOOGLE_SERVICE_ACCOUNT_KEY (base64), or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
353
+ }
354
+ else {
355
+ healthStatus.hasApiKey = true; // At least one auth method is configured
356
+ }
357
+ // Validate credentials file if provided
358
+ if (hasCredentialsFile) {
359
+ const credPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
360
+ const fileName = basename(credPath);
361
+ // Use regex to match .json files with optional backup extensions
362
+ const jsonFilePattern = /\.json(\.\w+)?$/;
363
+ if (!jsonFilePattern.test(fileName)) {
364
+ healthStatus.warning =
365
+ "GOOGLE_APPLICATION_CREDENTIALS should point to a JSON file (e.g., 'credentials.json' or 'key.json.backup')";
366
+ }
367
+ }
368
+ // Mark as configured if we have both project ID and auth
369
+ if (projectId &&
370
+ (hasCredentialsFile ||
371
+ hasServiceAccountKey ||
372
+ hasIndividualCredentials)) {
373
+ healthStatus.isConfigured = true;
374
+ }
375
+ break;
376
+ }
377
+ case AIProviderName.BEDROCK:
378
+ // Check AWS region
379
+ if (!process.env.AWS_REGION) {
380
+ healthStatus.configurationIssues.push("AWS_REGION not set");
381
+ healthStatus.recommendations.push("Set AWS_REGION (e.g., us-east-1)");
382
+ }
383
+ break;
384
+ case AIProviderName.OLLAMA: {
385
+ // Check if custom endpoint is set
386
+ const ollamaBase = process.env.OLLAMA_API_BASE || "http://localhost:11434";
387
+ if (!ollamaBase.startsWith("http")) {
388
+ healthStatus.configurationIssues.push("Invalid OLLAMA_API_BASE format");
389
+ healthStatus.recommendations.push("Set OLLAMA_API_BASE to a valid URL (e.g., http://localhost:11434)");
390
+ }
391
+ break;
392
+ }
393
+ }
394
+ }
395
+ /**
396
+ * Get common models for a provider
397
+ */
398
+ static getCommonModelsForProvider(providerName) {
399
+ switch (providerName) {
400
+ case AIProviderName.ANTHROPIC:
401
+ return [
402
+ "claude-3-5-sonnet-20241022",
403
+ "claude-3-haiku-20240307",
404
+ "claude-3-opus-20240229",
405
+ ];
406
+ case AIProviderName.OPENAI:
407
+ return ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"];
408
+ case AIProviderName.GOOGLE_AI:
409
+ return ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-pro"];
410
+ case AIProviderName.VERTEX:
411
+ return ["gemini-1.5-pro", "gemini-1.5-flash"];
412
+ case AIProviderName.BEDROCK:
413
+ return [
414
+ "anthropic.claude-3-sonnet-20240229-v1:0",
415
+ "anthropic.claude-3-haiku-20240307-v1:0",
416
+ ];
417
+ case AIProviderName.OLLAMA:
418
+ return ["llama3.2:latest", "llama3.1:latest", "mistral:latest"];
419
+ default:
420
+ return [];
421
+ }
422
+ }
423
+ /**
424
+ * Get cached health status if still valid
425
+ */
426
+ static getCachedHealth(providerName, maxAge) {
427
+ const cached = this.healthCache.get(providerName);
428
+ if (!cached) {
429
+ return null;
430
+ }
431
+ const age = Date.now() - cached.timestamp;
432
+ if (age > maxAge) {
433
+ this.healthCache.delete(providerName);
434
+ return null;
435
+ }
436
+ return cached.status;
437
+ }
438
+ /**
439
+ * Clear health cache for a provider or all providers
440
+ */
441
+ static clearHealthCache(providerName) {
442
+ if (providerName) {
443
+ this.healthCache.delete(providerName);
444
+ this.consecutiveFailures.delete(providerName);
445
+ }
446
+ else {
447
+ this.healthCache.clear();
448
+ this.consecutiveFailures.clear();
449
+ }
450
+ }
451
+ /**
452
+ * Get the best healthy provider from a list of options
453
+ * Prioritizes healthy providers over configured but unhealthy ones
454
+ */
455
+ static async getBestHealthyProvider(preferredProviders = [
456
+ "openai",
457
+ "anthropic",
458
+ "vertex",
459
+ "bedrock",
460
+ "azure",
461
+ "google-ai",
462
+ ]) {
463
+ const healthStatuses = await this.checkAllProvidersHealth({
464
+ includeConnectivityTest: false, // Quick config check only
465
+ cacheResults: true,
466
+ });
467
+ // First try to find a healthy provider in order of preference
468
+ for (const provider of preferredProviders) {
469
+ const health = healthStatuses.find((h) => h.provider === provider);
470
+ if (health?.isHealthy) {
471
+ logger.debug(`Selected healthy provider: ${provider}`);
472
+ return provider;
473
+ }
474
+ }
475
+ // Fallback to any healthy provider
476
+ const anyHealthy = healthStatuses.find((h) => h.isHealthy);
477
+ if (anyHealthy) {
478
+ logger.info(`Using fallback healthy provider: ${anyHealthy.provider}`);
479
+ return anyHealthy.provider;
480
+ }
481
+ // Last resort: any configured provider
482
+ const anyConfigured = healthStatuses.find((h) => h.isConfigured);
483
+ if (anyConfigured) {
484
+ logger.warn(`Using configured but potentially unhealthy provider: ${anyConfigured.provider}`);
485
+ return anyConfigured.provider;
486
+ }
487
+ logger.error("No healthy or configured providers found");
488
+ return null;
489
+ }
490
+ /**
491
+ * Get health status for all registered providers
492
+ */
493
+ static async checkAllProvidersHealth(options = {}) {
494
+ const providers = [
495
+ AIProviderName.ANTHROPIC,
496
+ AIProviderName.OPENAI,
497
+ AIProviderName.VERTEX,
498
+ AIProviderName.GOOGLE_AI,
499
+ AIProviderName.BEDROCK,
500
+ AIProviderName.OLLAMA,
501
+ ];
502
+ const healthChecks = providers.map((provider) => this.checkProviderHealth(provider, options));
503
+ const results = await Promise.allSettled(healthChecks);
504
+ return results.map((result, index) => {
505
+ if (result.status === "fulfilled") {
506
+ return result.value;
507
+ }
508
+ else {
509
+ // Return a failed health status for rejected promises
510
+ return {
511
+ provider: providers[index],
512
+ isHealthy: false,
513
+ isConfigured: false,
514
+ hasApiKey: false,
515
+ lastChecked: new Date(),
516
+ error: result.reason?.message || "Health check failed",
517
+ configurationIssues: ["Health check promise rejected"],
518
+ recommendations: [
519
+ "Check provider configuration and network connectivity",
520
+ ],
521
+ };
522
+ }
523
+ });
524
+ }
525
+ /**
526
+ * Get a summary of provider health
527
+ */
528
+ static getHealthSummary(healthStatuses) {
529
+ const healthy = healthStatuses.filter((h) => h.isHealthy);
530
+ const configured = healthStatuses.filter((h) => h.isConfigured);
531
+ const hasIssues = healthStatuses.filter((h) => h.configurationIssues.length > 0);
532
+ return {
533
+ total: healthStatuses.length,
534
+ healthy: healthy.length,
535
+ configured: configured.length,
536
+ hasIssues: hasIssues.length,
537
+ healthyProviders: healthy.map((h) => h.provider),
538
+ unhealthyProviders: healthStatuses
539
+ .filter((h) => !h.isHealthy)
540
+ .map((h) => h.provider),
541
+ };
542
+ }
543
+ }