@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.
- package/CHANGELOG.md +15 -4
- package/README.md +78 -3
- package/dist/cli/commands/config.d.ts +275 -3
- package/dist/cli/commands/config.js +121 -0
- package/dist/cli/commands/mcp.js +77 -28
- package/dist/cli/factories/commandFactory.js +359 -6
- package/dist/core/analytics.js +7 -27
- package/dist/core/baseProvider.js +43 -4
- package/dist/core/constants.d.ts +46 -0
- package/dist/core/constants.js +47 -0
- package/dist/core/dynamicModels.d.ts +16 -4
- package/dist/core/dynamicModels.js +130 -26
- package/dist/core/evaluation.js +5 -1
- package/dist/core/evaluationProviders.d.ts +6 -2
- package/dist/core/evaluationProviders.js +41 -125
- package/dist/core/factory.d.ts +5 -0
- package/dist/core/factory.js +62 -50
- package/dist/core/modelConfiguration.d.ts +246 -0
- package/dist/core/modelConfiguration.js +775 -0
- package/dist/core/types.d.ts +22 -3
- package/dist/core/types.js +5 -1
- package/dist/factories/providerRegistry.js +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/core/analytics.js +7 -27
- package/dist/lib/core/baseProvider.js +43 -4
- package/dist/lib/core/constants.d.ts +46 -0
- package/dist/lib/core/constants.js +47 -0
- package/dist/lib/core/dynamicModels.d.ts +16 -4
- package/dist/lib/core/dynamicModels.js +130 -26
- package/dist/lib/core/evaluation.js +5 -1
- package/dist/lib/core/evaluationProviders.d.ts +6 -2
- package/dist/lib/core/evaluationProviders.js +41 -125
- package/dist/lib/core/factory.d.ts +5 -0
- package/dist/lib/core/factory.js +63 -50
- package/dist/lib/core/modelConfiguration.d.ts +246 -0
- package/dist/lib/core/modelConfiguration.js +775 -0
- package/dist/lib/core/types.d.ts +22 -3
- package/dist/lib/core/types.js +5 -1
- package/dist/lib/factories/providerRegistry.js +3 -3
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/mcp/factory.d.ts +5 -5
- package/dist/lib/mcp/factory.js +2 -2
- package/dist/lib/mcp/servers/utilities/utilityServer.d.ts +1 -1
- package/dist/lib/mcp/servers/utilities/utilityServer.js +1 -1
- package/dist/lib/mcp/toolRegistry.js +2 -2
- package/dist/lib/neurolink.d.ts +168 -12
- package/dist/lib/neurolink.js +685 -123
- package/dist/lib/providers/anthropic.js +52 -2
- package/dist/lib/providers/googleAiStudio.js +4 -0
- package/dist/lib/providers/googleVertex.d.ts +75 -9
- package/dist/lib/providers/googleVertex.js +365 -46
- package/dist/lib/providers/huggingFace.d.ts +52 -11
- package/dist/lib/providers/huggingFace.js +180 -42
- package/dist/lib/providers/litellm.d.ts +9 -9
- package/dist/lib/providers/litellm.js +103 -16
- package/dist/lib/providers/ollama.d.ts +52 -17
- package/dist/lib/providers/ollama.js +276 -68
- package/dist/lib/sdk/toolRegistration.d.ts +42 -0
- package/dist/lib/sdk/toolRegistration.js +269 -27
- package/dist/lib/telemetry/telemetryService.d.ts +6 -0
- package/dist/lib/telemetry/telemetryService.js +38 -3
- package/dist/lib/types/contextTypes.d.ts +75 -11
- package/dist/lib/types/contextTypes.js +227 -1
- package/dist/lib/types/domainTypes.d.ts +62 -0
- package/dist/lib/types/domainTypes.js +5 -0
- package/dist/lib/types/generateTypes.d.ts +52 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/mcpTypes.d.ts +1 -1
- package/dist/lib/types/mcpTypes.js +1 -1
- package/dist/lib/types/streamTypes.d.ts +14 -0
- package/dist/lib/types/universalProviderOptions.d.ts +1 -1
- package/dist/lib/utils/errorHandling.d.ts +142 -0
- package/dist/lib/utils/errorHandling.js +316 -0
- package/dist/lib/utils/factoryProcessing.d.ts +74 -0
- package/dist/lib/utils/factoryProcessing.js +588 -0
- package/dist/lib/utils/optionsConversion.d.ts +54 -0
- package/dist/lib/utils/optionsConversion.js +126 -0
- package/dist/lib/utils/optionsUtils.d.ts +246 -0
- package/dist/lib/utils/optionsUtils.js +960 -0
- package/dist/lib/utils/providerConfig.js +6 -2
- package/dist/lib/utils/providerHealth.d.ts +107 -0
- package/dist/lib/utils/providerHealth.js +543 -0
- package/dist/lib/utils/providerUtils.d.ts +17 -0
- package/dist/lib/utils/providerUtils.js +271 -16
- package/dist/lib/utils/timeout.js +1 -1
- package/dist/lib/utils/tokenLimits.d.ts +33 -0
- package/dist/lib/utils/tokenLimits.js +118 -0
- package/dist/mcp/factory.d.ts +5 -5
- package/dist/mcp/factory.js +2 -2
- package/dist/mcp/servers/utilities/utilityServer.d.ts +1 -1
- package/dist/mcp/servers/utilities/utilityServer.js +1 -1
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/neurolink.d.ts +168 -12
- package/dist/neurolink.js +685 -123
- package/dist/providers/anthropic.js +52 -2
- package/dist/providers/googleAiStudio.js +4 -0
- package/dist/providers/googleVertex.d.ts +75 -9
- package/dist/providers/googleVertex.js +365 -46
- package/dist/providers/huggingFace.d.ts +52 -11
- package/dist/providers/huggingFace.js +181 -43
- package/dist/providers/litellm.d.ts +9 -9
- package/dist/providers/litellm.js +103 -16
- package/dist/providers/ollama.d.ts +52 -17
- package/dist/providers/ollama.js +276 -68
- package/dist/sdk/toolRegistration.d.ts +42 -0
- package/dist/sdk/toolRegistration.js +269 -27
- package/dist/telemetry/telemetryService.d.ts +6 -0
- package/dist/telemetry/telemetryService.js +38 -3
- package/dist/types/contextTypes.d.ts +75 -11
- package/dist/types/contextTypes.js +227 -2
- package/dist/types/domainTypes.d.ts +62 -0
- package/dist/types/domainTypes.js +5 -0
- package/dist/types/generateTypes.d.ts +52 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcpTypes.d.ts +1 -1
- package/dist/types/mcpTypes.js +1 -1
- package/dist/types/streamTypes.d.ts +14 -0
- package/dist/types/universalProviderOptions.d.ts +1 -1
- package/dist/types/universalProviderOptions.js +0 -1
- package/dist/utils/errorHandling.d.ts +142 -0
- package/dist/utils/errorHandling.js +316 -0
- package/dist/utils/factoryProcessing.d.ts +74 -0
- package/dist/utils/factoryProcessing.js +588 -0
- package/dist/utils/optionsConversion.d.ts +54 -0
- package/dist/utils/optionsConversion.js +126 -0
- package/dist/utils/optionsUtils.d.ts +246 -0
- package/dist/utils/optionsUtils.js +960 -0
- package/dist/utils/providerConfig.js +6 -2
- package/dist/utils/providerHealth.d.ts +107 -0
- package/dist/utils/providerHealth.js +543 -0
- package/dist/utils/providerUtils.d.ts +17 -0
- package/dist/utils/providerUtils.js +271 -16
- package/dist/utils/timeout.js +1 -1
- package/dist/utils/tokenLimits.d.ts +33 -0
- package/dist/utils/tokenLimits.js +118 -0
- 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: [
|
|
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"
|
|
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
|
+
}
|