@juspay/neurolink 7.27.0 → 7.28.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 +13 -0
- package/dist/cli/commands/config.d.ts +3 -3
- package/dist/cli/commands/ollama.d.ts +3 -0
- package/dist/cli/commands/ollama.js +288 -0
- package/dist/cli/factories/ollamaCommandFactory.d.ts +4 -0
- package/dist/cli/factories/ollamaCommandFactory.js +86 -93
- package/dist/cli/utils/ollamaUtils.d.ts +24 -0
- package/dist/cli/utils/ollamaUtils.js +161 -0
- package/dist/lib/mcp/toolDiscoveryService.js +1 -1
- package/dist/lib/neurolink.js +1099 -56
- package/dist/lib/providers/amazonBedrock.d.ts +2 -2
- package/dist/lib/providers/amazonBedrock.js +16 -7
- package/dist/lib/providers/googleVertex.d.ts +28 -3
- package/dist/lib/providers/googleVertex.js +1132 -84
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +7 -4
- package/dist/lib/providers/openaiCompatible.d.ts +1 -1
- package/dist/lib/providers/openaiCompatible.js +7 -4
- package/dist/lib/proxy/proxyFetch.js +124 -2
- package/dist/lib/utils/providerHealth.d.ts +57 -1
- package/dist/lib/utils/providerHealth.js +638 -33
- package/dist/lib/utils/transformationUtils.js +3 -3
- package/dist/mcp/toolDiscoveryService.js +1 -1
- package/dist/neurolink.js +1099 -56
- package/dist/providers/amazonBedrock.d.ts +2 -2
- package/dist/providers/amazonBedrock.js +16 -7
- package/dist/providers/googleVertex.d.ts +28 -3
- package/dist/providers/googleVertex.js +1132 -84
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +7 -4
- package/dist/providers/openaiCompatible.d.ts +1 -1
- package/dist/providers/openaiCompatible.js +7 -4
- package/dist/proxy/proxyFetch.js +124 -2
- package/dist/utils/providerHealth.d.ts +57 -1
- package/dist/utils/providerHealth.js +638 -33
- package/dist/utils/transformationUtils.js +3 -3
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { logger } from "./logger.js";
|
|
6
6
|
import { AIProviderName } from "../core/types.js";
|
|
7
7
|
import { basename } from "path";
|
|
8
|
+
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
8
9
|
export class ProviderHealthChecker {
|
|
9
10
|
static healthCache = new Map();
|
|
10
11
|
static DEFAULT_TIMEOUT = 5000; // 5 seconds
|
|
@@ -150,6 +151,67 @@ export class ProviderHealthChecker {
|
|
|
150
151
|
* Check API key validity (format validation)
|
|
151
152
|
*/
|
|
152
153
|
static async checkApiKeyValidity(providerName, healthStatus) {
|
|
154
|
+
// 🎯 SPECIAL HANDLING FOR VERTEX AI: Check both auth methods
|
|
155
|
+
if (providerName === AIProviderName.VERTEX) {
|
|
156
|
+
logger.debug("Vertex AI authentication check starting", {
|
|
157
|
+
providerName,
|
|
158
|
+
});
|
|
159
|
+
// Method 1: Check GOOGLE_APPLICATION_CREDENTIALS (file-based)
|
|
160
|
+
const credentialsFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
161
|
+
let fileBasedAuthValid = false;
|
|
162
|
+
if (credentialsFile) {
|
|
163
|
+
logger.debug("Checking GOOGLE_APPLICATION_CREDENTIALS file");
|
|
164
|
+
try {
|
|
165
|
+
const { promises: fs } = await import("fs");
|
|
166
|
+
try {
|
|
167
|
+
await fs.access(credentialsFile);
|
|
168
|
+
fileBasedAuthValid = true;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
fileBasedAuthValid = false;
|
|
172
|
+
}
|
|
173
|
+
logger.debug("File auth check result", {
|
|
174
|
+
fileExists: fileBasedAuthValid,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
logger.debug("File auth check error", {
|
|
179
|
+
error: String(error),
|
|
180
|
+
});
|
|
181
|
+
fileBasedAuthValid = false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Method 2: Check individual environment variables
|
|
185
|
+
const hasIndividualAuth = !!(process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
|
|
186
|
+
process.env.GOOGLE_AUTH_PRIVATE_KEY);
|
|
187
|
+
logger.debug("Individual auth check", {
|
|
188
|
+
hasClientEmail: !!process.env.GOOGLE_AUTH_CLIENT_EMAIL,
|
|
189
|
+
hasPrivateKey: !!process.env.GOOGLE_AUTH_PRIVATE_KEY,
|
|
190
|
+
hasIndividualAuth,
|
|
191
|
+
});
|
|
192
|
+
// Vertex is valid if EITHER auth method works
|
|
193
|
+
const hasValidAuth = fileBasedAuthValid || hasIndividualAuth;
|
|
194
|
+
logger.debug("Vertex auth final result", {
|
|
195
|
+
fileBasedAuthValid,
|
|
196
|
+
hasIndividualAuth,
|
|
197
|
+
hasValidAuth,
|
|
198
|
+
});
|
|
199
|
+
if (hasValidAuth) {
|
|
200
|
+
healthStatus.hasApiKey = true;
|
|
201
|
+
logger.debug("Vertex auth SUCCESS", {
|
|
202
|
+
authMethod: fileBasedAuthValid ? "file-based" : "individual-env-vars",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
healthStatus.hasApiKey = false;
|
|
207
|
+
healthStatus.configurationIssues.push(`Vertex AI authentication not found: neither GOOGLE_APPLICATION_CREDENTIALS file nor individual credentials (GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY) are properly configured`);
|
|
208
|
+
logger.debug("Vertex auth FAILED", {
|
|
209
|
+
reason: "No valid auth method found",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// 🔧 STANDARD HANDLING FOR OTHER PROVIDERS
|
|
153
215
|
const apiKeyVar = this.getApiKeyEnvironmentVariable(providerName);
|
|
154
216
|
const apiKey = process.env[apiKeyVar];
|
|
155
217
|
if (!apiKey) {
|
|
@@ -180,13 +242,24 @@ export class ProviderHealthChecker {
|
|
|
180
242
|
try {
|
|
181
243
|
const controller = new AbortController();
|
|
182
244
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
183
|
-
const
|
|
245
|
+
const proxyFetch = createProxyFetch();
|
|
246
|
+
let response = await proxyFetch(endpoint, {
|
|
184
247
|
method: "HEAD",
|
|
185
248
|
signal: controller.signal,
|
|
186
249
|
headers: {
|
|
187
250
|
"User-Agent": "NeuroLink-HealthCheck/1.0",
|
|
188
251
|
},
|
|
189
252
|
});
|
|
253
|
+
// Fallback to GET if HEAD returns 405 (Method Not Allowed) for restrictive gateways
|
|
254
|
+
if (response.status === 405) {
|
|
255
|
+
response = await proxyFetch(endpoint, {
|
|
256
|
+
method: "GET",
|
|
257
|
+
signal: controller.signal,
|
|
258
|
+
headers: {
|
|
259
|
+
"User-Agent": "NeuroLink-HealthCheck/1.0",
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
190
263
|
clearTimeout(timeoutId);
|
|
191
264
|
if (!response.ok) {
|
|
192
265
|
healthStatus.configurationIssues.push(`Connectivity test failed: HTTP ${response.status}`);
|
|
@@ -233,11 +306,18 @@ export class ProviderHealthChecker {
|
|
|
233
306
|
const commonModels = this.getCommonModelsForProvider(providerName);
|
|
234
307
|
if (commonModels.length > 0) {
|
|
235
308
|
if (providerName === AIProviderName.VERTEX) {
|
|
236
|
-
// Provide
|
|
237
|
-
healthStatus.recommendations.push(`Available models for ${providerName}:\n` +
|
|
238
|
-
` Google Models
|
|
239
|
-
`
|
|
240
|
-
`
|
|
309
|
+
// Provide detailed information about dual provider architecture
|
|
310
|
+
healthStatus.recommendations.push(`Available models for ${providerName} (using dual provider architecture):\n` +
|
|
311
|
+
` Google Models (via vertex provider):\n` +
|
|
312
|
+
` • gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite\n` +
|
|
313
|
+
` • gemini-2.0-flash-001, gemini-1.5-pro, gemini-1.5-flash\n` +
|
|
314
|
+
` Anthropic Models (via vertexAnthropic provider):\n` +
|
|
315
|
+
` • claude-sonnet-4@20250514, claude-opus-4@20250514\n` +
|
|
316
|
+
` • claude-3-5-sonnet-20241022, claude-3-5-haiku-20241022\n` +
|
|
317
|
+
` • claude-3-sonnet-20240229, claude-3-haiku-20240307, claude-3-opus-20240229\n` +
|
|
318
|
+
` Implementation: Uses @ai-sdk/google-vertex with dual provider setup\n` +
|
|
319
|
+
` Authentication: Requires Google Cloud project with Vertex AI API enabled\n` +
|
|
320
|
+
` Note: Anthropic models require Anthropic integration in your Google Cloud project`);
|
|
241
321
|
}
|
|
242
322
|
else {
|
|
243
323
|
healthStatus.recommendations.push(`Common models for ${providerName}: ${commonModels.slice(0, 3).join(", ")}`);
|
|
@@ -263,6 +343,8 @@ export class ProviderHealthChecker {
|
|
|
263
343
|
return ["GOOGLE_AI_API_KEY"];
|
|
264
344
|
case AIProviderName.BEDROCK:
|
|
265
345
|
return ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
|
|
346
|
+
case AIProviderName.AZURE:
|
|
347
|
+
return ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"];
|
|
266
348
|
case AIProviderName.OLLAMA:
|
|
267
349
|
return []; // Ollama typically doesn't require API keys
|
|
268
350
|
default:
|
|
@@ -284,6 +366,8 @@ export class ProviderHealthChecker {
|
|
|
284
366
|
return "GOOGLE_AI_API_KEY";
|
|
285
367
|
case AIProviderName.BEDROCK:
|
|
286
368
|
return "AWS_ACCESS_KEY_ID";
|
|
369
|
+
case AIProviderName.AZURE:
|
|
370
|
+
return "AZURE_OPENAI_API_KEY";
|
|
287
371
|
case AIProviderName.OLLAMA:
|
|
288
372
|
return "OLLAMA_API_BASE";
|
|
289
373
|
default:
|
|
@@ -305,6 +389,8 @@ export class ProviderHealthChecker {
|
|
|
305
389
|
return apiKey.endsWith(".json") || apiKey.includes("type"); // JSON key format
|
|
306
390
|
case AIProviderName.BEDROCK:
|
|
307
391
|
return apiKey.length >= 20; // AWS access key length
|
|
392
|
+
case AIProviderName.AZURE:
|
|
393
|
+
return apiKey.length >= 32; // Azure OpenAI API key length
|
|
308
394
|
case AIProviderName.OLLAMA:
|
|
309
395
|
return true; // Ollama usually doesn't require specific format
|
|
310
396
|
default:
|
|
@@ -338,47 +424,135 @@ export class ProviderHealthChecker {
|
|
|
338
424
|
static async checkProviderSpecificConfig(providerName, healthStatus) {
|
|
339
425
|
switch (providerName) {
|
|
340
426
|
case AIProviderName.VERTEX: {
|
|
427
|
+
logger.debug("Starting Vertex AI health check", {
|
|
428
|
+
providerName,
|
|
429
|
+
});
|
|
341
430
|
// Check for Google Cloud project ID (with fallbacks)
|
|
342
431
|
const projectId = process.env.GOOGLE_PROJECT_ID ||
|
|
343
432
|
process.env.GOOGLE_CLOUD_PROJECT_ID ||
|
|
344
433
|
process.env.GOOGLE_VERTEX_PROJECT ||
|
|
345
434
|
process.env.GOOGLE_CLOUD_PROJECT ||
|
|
346
435
|
process.env.VERTEX_PROJECT_ID;
|
|
436
|
+
logger.debug("Project ID validation", {
|
|
437
|
+
hasProjectId: !!projectId,
|
|
438
|
+
});
|
|
347
439
|
if (!projectId) {
|
|
348
440
|
healthStatus.configurationIssues.push("Google Cloud project ID not set");
|
|
349
441
|
healthStatus.recommendations.push("Set one of: GOOGLE_VERTEX_PROJECT, GOOGLE_CLOUD_PROJECT_ID, GOOGLE_PROJECT_ID, or GOOGLE_CLOUD_PROJECT");
|
|
350
442
|
}
|
|
351
|
-
// Check for authentication
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
process.env.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
443
|
+
// Check for authentication with proper file existence validation
|
|
444
|
+
// This aligns with the authentication logic fix in googleVertex.ts
|
|
445
|
+
let hasValidAuth = false;
|
|
446
|
+
logger.debug("Authentication validation starting", {
|
|
447
|
+
hasGoogleApplicationCredentials: !!process.env.GOOGLE_APPLICATION_CREDENTIALS,
|
|
448
|
+
});
|
|
449
|
+
// Check for principal account authentication first (recommended for production)
|
|
450
|
+
// BUT CRITICALLY: Also verify the file actually exists
|
|
451
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
452
|
+
const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
453
|
+
logger.debug("Checking GOOGLE_APPLICATION_CREDENTIALS file");
|
|
454
|
+
// Check if the credentials file actually exists
|
|
455
|
+
let fileExists = false;
|
|
456
|
+
try {
|
|
457
|
+
const { promises: fs } = await import("fs");
|
|
458
|
+
try {
|
|
459
|
+
await fs.access(credentialsPath);
|
|
460
|
+
fileExists = true;
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
fileExists = false;
|
|
464
|
+
}
|
|
465
|
+
logger.debug("File existence check completed", {
|
|
466
|
+
fileExists,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
logger.debug("File existence check failed", {
|
|
471
|
+
error: String(error),
|
|
472
|
+
});
|
|
473
|
+
healthStatus.warning = `Failed to check credentials file existence: ${error}`;
|
|
474
|
+
fileExists = false;
|
|
475
|
+
}
|
|
476
|
+
if (fileExists) {
|
|
477
|
+
// Validate file format
|
|
478
|
+
const fileName = basename(credentialsPath);
|
|
479
|
+
const jsonFilePattern = /\.json(\.\w+)?$/;
|
|
480
|
+
if (!jsonFilePattern.test(fileName)) {
|
|
481
|
+
healthStatus.warning =
|
|
482
|
+
"GOOGLE_APPLICATION_CREDENTIALS should point to a JSON file (e.g., 'credentials.json' or 'key.json.backup')";
|
|
483
|
+
}
|
|
484
|
+
hasValidAuth = true;
|
|
485
|
+
healthStatus.hasApiKey = true;
|
|
486
|
+
logger.debug("GOOGLE_APPLICATION_CREDENTIALS file validated", {
|
|
487
|
+
fileName,
|
|
488
|
+
hasValidAuth,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
healthStatus.warning = `GOOGLE_APPLICATION_CREDENTIALS file does not exist: ${credentialsPath}`;
|
|
493
|
+
logger.debug("GOOGLE_APPLICATION_CREDENTIALS file missing, falling back to individual env vars");
|
|
494
|
+
// Fall through to check individual environment variables
|
|
495
|
+
}
|
|
361
496
|
}
|
|
362
497
|
else {
|
|
363
|
-
|
|
498
|
+
logger.debug("GOOGLE_APPLICATION_CREDENTIALS not set, checking individual env vars");
|
|
364
499
|
}
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
500
|
+
// Fallback to individual credentials for development and production
|
|
501
|
+
// Enhanced to check ALL required fields from the .env file configuration
|
|
502
|
+
if (!hasValidAuth) {
|
|
503
|
+
const hasServiceAccountKey = !!process.env.GOOGLE_SERVICE_ACCOUNT_KEY;
|
|
504
|
+
const hasIndividualCredentials = !!(process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
|
|
505
|
+
process.env.GOOGLE_AUTH_PRIVATE_KEY);
|
|
506
|
+
logger.debug("Individual credentials check", {
|
|
507
|
+
hasServiceAccountKey,
|
|
508
|
+
hasGoogleAuthClientEmail: !!process.env.GOOGLE_AUTH_CLIENT_EMAIL,
|
|
509
|
+
hasGoogleAuthPrivateKey: !!process.env.GOOGLE_AUTH_PRIVATE_KEY,
|
|
510
|
+
hasIndividualCredentials,
|
|
511
|
+
});
|
|
512
|
+
if (hasServiceAccountKey || hasIndividualCredentials) {
|
|
513
|
+
hasValidAuth = true;
|
|
514
|
+
healthStatus.hasApiKey = true;
|
|
515
|
+
logger.debug("Individual credentials validated successfully", {
|
|
516
|
+
hasServiceAccountKey,
|
|
517
|
+
hasIndividualCredentials,
|
|
518
|
+
hasValidAuth,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
logger.debug("Individual credentials validation failed", {
|
|
523
|
+
hasServiceAccountKey,
|
|
524
|
+
hasIndividualCredentials,
|
|
525
|
+
});
|
|
374
526
|
}
|
|
375
527
|
}
|
|
528
|
+
// Final validation
|
|
529
|
+
if (!hasValidAuth) {
|
|
530
|
+
healthStatus.configurationIssues.push("Google Cloud authentication not configured or credentials file missing");
|
|
531
|
+
healthStatus.recommendations.push("Set either GOOGLE_APPLICATION_CREDENTIALS (valid file path), GOOGLE_SERVICE_ACCOUNT_KEY (base64), or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
|
|
532
|
+
logger.debug("Final auth validation FAILED", {
|
|
533
|
+
hasValidAuth,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
logger.debug("Final auth validation SUCCESS", {
|
|
538
|
+
hasValidAuth,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
376
541
|
// Mark as configured if we have both project ID and auth
|
|
377
|
-
if (projectId &&
|
|
378
|
-
(hasCredentialsFile ||
|
|
379
|
-
hasServiceAccountKey ||
|
|
380
|
-
hasIndividualCredentials)) {
|
|
542
|
+
if (projectId && hasValidAuth) {
|
|
381
543
|
healthStatus.isConfigured = true;
|
|
544
|
+
logger.debug("Vertex AI health check PASSED", {
|
|
545
|
+
hasProjectId: !!projectId,
|
|
546
|
+
hasValidAuth,
|
|
547
|
+
isConfigured: healthStatus.isConfigured,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
logger.debug("Vertex AI health check FAILED", {
|
|
552
|
+
hasProjectId: !!projectId,
|
|
553
|
+
hasValidAuth,
|
|
554
|
+
isConfigured: healthStatus.isConfigured,
|
|
555
|
+
});
|
|
382
556
|
}
|
|
383
557
|
break;
|
|
384
558
|
}
|
|
@@ -389,6 +563,20 @@ export class ProviderHealthChecker {
|
|
|
389
563
|
healthStatus.recommendations.push("Set AWS_REGION (e.g., us-east-1)");
|
|
390
564
|
}
|
|
391
565
|
break;
|
|
566
|
+
case AIProviderName.AZURE: {
|
|
567
|
+
// Check Azure OpenAI endpoint
|
|
568
|
+
const azureEndpoint = process.env.AZURE_OPENAI_ENDPOINT;
|
|
569
|
+
if (azureEndpoint && !azureEndpoint.startsWith("https://")) {
|
|
570
|
+
healthStatus.configurationIssues.push("Invalid AZURE_OPENAI_ENDPOINT format");
|
|
571
|
+
healthStatus.recommendations.push("Set AZURE_OPENAI_ENDPOINT to a valid URL (e.g., https://your-resource.openai.azure.com/)");
|
|
572
|
+
}
|
|
573
|
+
// Check for deployment name
|
|
574
|
+
if (!process.env.AZURE_OPENAI_DEPLOYMENT_NAME) {
|
|
575
|
+
healthStatus.configurationIssues.push("AZURE_OPENAI_DEPLOYMENT_NAME not set");
|
|
576
|
+
healthStatus.recommendations.push("Set AZURE_OPENAI_DEPLOYMENT_NAME to your deployment name");
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
392
580
|
case AIProviderName.OLLAMA: {
|
|
393
581
|
// Check if custom endpoint is set
|
|
394
582
|
const ollamaBase = process.env.OLLAMA_API_BASE || "http://localhost:11434";
|
|
@@ -417,9 +605,18 @@ export class ProviderHealthChecker {
|
|
|
417
605
|
return ["gemini-1.5-pro", "gemini-1.5-flash", "gemini-pro"];
|
|
418
606
|
case AIProviderName.VERTEX:
|
|
419
607
|
return [
|
|
608
|
+
// Google models (via vertex provider)
|
|
609
|
+
"gemini-2.5-pro",
|
|
610
|
+
"gemini-2.5-flash",
|
|
611
|
+
"gemini-2.5-flash-lite",
|
|
612
|
+
"gemini-2.0-flash-001",
|
|
420
613
|
"gemini-1.5-pro",
|
|
421
614
|
"gemini-1.5-flash",
|
|
615
|
+
// Anthropic models (via vertexAnthropic provider)
|
|
616
|
+
"claude-sonnet-4@20250514",
|
|
617
|
+
"claude-opus-4@20250514",
|
|
422
618
|
"claude-3-5-sonnet-20241022",
|
|
619
|
+
"claude-3-5-haiku-20241022",
|
|
423
620
|
"claude-3-sonnet-20240229",
|
|
424
621
|
"claude-3-haiku-20240307",
|
|
425
622
|
"claude-3-opus-20240229",
|
|
@@ -429,6 +626,8 @@ export class ProviderHealthChecker {
|
|
|
429
626
|
"anthropic.claude-3-sonnet-20240229-v1:0",
|
|
430
627
|
"anthropic.claude-3-haiku-20240307-v1:0",
|
|
431
628
|
];
|
|
629
|
+
case AIProviderName.AZURE:
|
|
630
|
+
return ["gpt-4o", "gpt-4o-mini", "gpt-35-turbo"];
|
|
432
631
|
case AIProviderName.OLLAMA:
|
|
433
632
|
return ["llama3.2:latest", "llama3.1:latest", "mistral:latest"];
|
|
434
633
|
default:
|
|
@@ -450,6 +649,409 @@ export class ProviderHealthChecker {
|
|
|
450
649
|
}
|
|
451
650
|
return cached.status;
|
|
452
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* Check if Vertex AI supports Anthropic models (dual provider architecture)
|
|
654
|
+
*/
|
|
655
|
+
static async checkVertexAnthropicSupport() {
|
|
656
|
+
const result = {
|
|
657
|
+
isSupported: false,
|
|
658
|
+
hasCreateVertexAnthropic: false,
|
|
659
|
+
hasCorrectTypes: false,
|
|
660
|
+
hasValidProject: false,
|
|
661
|
+
hasRegionalSupport: false,
|
|
662
|
+
hasNetworkAccess: false,
|
|
663
|
+
hasAnthropicModels: false,
|
|
664
|
+
authentication: {
|
|
665
|
+
isValid: false,
|
|
666
|
+
method: "none",
|
|
667
|
+
issues: [],
|
|
668
|
+
},
|
|
669
|
+
projectConfiguration: {
|
|
670
|
+
isValid: false,
|
|
671
|
+
projectId: undefined,
|
|
672
|
+
region: undefined,
|
|
673
|
+
issues: [],
|
|
674
|
+
},
|
|
675
|
+
modelSupport: {
|
|
676
|
+
availableModels: [],
|
|
677
|
+
recommendedModels: [
|
|
678
|
+
"claude-sonnet-4@20250514",
|
|
679
|
+
"claude-opus-4@20250514",
|
|
680
|
+
"claude-3-5-sonnet-20241022",
|
|
681
|
+
"claude-3-5-haiku-20241022",
|
|
682
|
+
"claude-3-sonnet-20240229",
|
|
683
|
+
"claude-3-haiku-20240307",
|
|
684
|
+
],
|
|
685
|
+
deprecatedModels: [
|
|
686
|
+
"claude-3-opus-20240229", // Still available but newer versions preferred
|
|
687
|
+
],
|
|
688
|
+
},
|
|
689
|
+
recommendations: [],
|
|
690
|
+
troubleshooting: [],
|
|
691
|
+
};
|
|
692
|
+
logger.debug("Starting comprehensive Vertex Anthropic support verification");
|
|
693
|
+
try {
|
|
694
|
+
// 1. Check SDK module availability
|
|
695
|
+
logger.debug("Checking @ai-sdk/google-vertex/anthropic module availability");
|
|
696
|
+
const anthropicModule = await import("@ai-sdk/google-vertex/anthropic");
|
|
697
|
+
result.hasCreateVertexAnthropic =
|
|
698
|
+
typeof anthropicModule.createVertexAnthropic === "function";
|
|
699
|
+
result.hasCorrectTypes = true; // Types are bundled with the function
|
|
700
|
+
if (!result.hasCreateVertexAnthropic) {
|
|
701
|
+
result.troubleshooting.push("📦 Update @ai-sdk/google-vertex to latest version with Anthropic support", "🔄 Run: npm install @ai-sdk/google-vertex@latest", "📖 See: https://sdk.vercel.ai/providers/ai-sdk-providers/google-vertex#anthropic-models");
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
704
|
+
logger.debug("SDK module verified successfully");
|
|
705
|
+
// 2. Comprehensive Authentication Validation
|
|
706
|
+
logger.debug("Starting authentication validation");
|
|
707
|
+
result.authentication = await this.validateVertexAuthentication();
|
|
708
|
+
if (!result.authentication.isValid) {
|
|
709
|
+
result.troubleshooting.push("🔐 Fix authentication configuration:", " Option 1: Set GOOGLE_APPLICATION_CREDENTIALS to valid service account file", " Option 2: Set individual env vars: GOOGLE_AUTH_CLIENT_EMAIL, GOOGLE_AUTH_PRIVATE_KEY", "📖 See: https://cloud.google.com/docs/authentication/provide-credentials-adc");
|
|
710
|
+
}
|
|
711
|
+
// 3. Project Configuration Validation
|
|
712
|
+
logger.debug("Starting project configuration validation");
|
|
713
|
+
result.projectConfiguration =
|
|
714
|
+
await this.validateVertexProjectConfiguration();
|
|
715
|
+
result.hasValidProject = result.projectConfiguration.isValid;
|
|
716
|
+
if (!result.hasValidProject) {
|
|
717
|
+
result.troubleshooting.push("🏗️ Fix project configuration:", " Set GOOGLE_VERTEX_PROJECT or GOOGLE_CLOUD_PROJECT environment variable", " Ensure project exists and has Vertex AI API enabled", "📖 See: https://console.cloud.google.com/apis/library/aiplatform.googleapis.com");
|
|
718
|
+
}
|
|
719
|
+
// 4. Regional Support Validation
|
|
720
|
+
logger.debug("Starting regional support validation");
|
|
721
|
+
result.hasRegionalSupport = await this.checkVertexRegionalSupport(result.projectConfiguration.region);
|
|
722
|
+
if (!result.hasRegionalSupport) {
|
|
723
|
+
result.troubleshooting.push("🌍 Regional support issues:", " Anthropic models may not be available in your region", " Try regions: us-central1, us-east4, europe-west1, asia-southeast1", " Set GOOGLE_CLOUD_LOCATION environment variable");
|
|
724
|
+
}
|
|
725
|
+
// 5. Network Connectivity Check (non-blocking)
|
|
726
|
+
logger.debug("Starting network connectivity check");
|
|
727
|
+
result.hasNetworkAccess = await this.checkVertexNetworkConnectivity(result.projectConfiguration.region || "us-central1");
|
|
728
|
+
if (!result.hasNetworkAccess) {
|
|
729
|
+
result.troubleshooting.push("🌐 Network connectivity issues:", " Check proxy configuration if behind corporate firewall", " Verify DNS resolution for *.googleapis.com", " Ensure firewall allows HTTPS to Google Cloud endpoints");
|
|
730
|
+
}
|
|
731
|
+
// 6. Anthropic Model Integration Check
|
|
732
|
+
logger.debug("Starting Anthropic model integration check");
|
|
733
|
+
result.hasAnthropicModels = await this.checkAnthropicModelIntegration(result.projectConfiguration.projectId, result.projectConfiguration.region);
|
|
734
|
+
if (!result.hasAnthropicModels) {
|
|
735
|
+
result.troubleshooting.push("🤖 Anthropic model integration issues:", " Enable Anthropic integration in Google Cloud Console", " Navigate to: Vertex AI > Model Garden > Anthropic", " Accept terms and enable Claude model access", "📖 See: https://console.cloud.google.com/vertex-ai/publishers/anthropic");
|
|
736
|
+
}
|
|
737
|
+
// Calculate overall support status
|
|
738
|
+
result.isSupported =
|
|
739
|
+
result.hasCreateVertexAnthropic &&
|
|
740
|
+
result.authentication.isValid &&
|
|
741
|
+
result.hasValidProject &&
|
|
742
|
+
result.hasRegionalSupport;
|
|
743
|
+
// Note: Network and model integration are nice-to-have but not blocking
|
|
744
|
+
// Generate comprehensive recommendations
|
|
745
|
+
if (result.isSupported) {
|
|
746
|
+
result.recommendations.push("✅ Vertex Anthropic support is fully configured", "✅ Claude models are available via vertexAnthropic provider", `✅ Authentication: ${result.authentication.method}`, `✅ Project: ${result.projectConfiguration.projectId}`, `✅ Region: ${result.projectConfiguration.region}`);
|
|
747
|
+
if (result.hasNetworkAccess) {
|
|
748
|
+
result.recommendations.push("✅ Network connectivity verified");
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
result.recommendations.push("⚠️ Network connectivity not verified (may still work)");
|
|
752
|
+
}
|
|
753
|
+
if (result.hasAnthropicModels) {
|
|
754
|
+
result.recommendations.push("✅ Anthropic model integration verified");
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
result.recommendations.push("⚠️ Anthropic model integration not verified");
|
|
758
|
+
}
|
|
759
|
+
result.recommendations.push("", "🎯 Recommended Claude models:", ...result.modelSupport.recommendedModels.map((model) => ` • ${model}`), "", "📚 Usage example:", ' const vertex = new GoogleVertexProvider("claude-3-5-sonnet-20241022")', ' const result = await vertex.generate("Hello, Claude!")');
|
|
760
|
+
logger.info("Vertex Anthropic support verification: FULLY_SUPPORTED");
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
const missingComponents = [];
|
|
764
|
+
if (!result.hasCreateVertexAnthropic) {
|
|
765
|
+
missingComponents.push("SDK module");
|
|
766
|
+
}
|
|
767
|
+
if (!result.authentication.isValid) {
|
|
768
|
+
missingComponents.push("authentication");
|
|
769
|
+
}
|
|
770
|
+
if (!result.hasValidProject) {
|
|
771
|
+
missingComponents.push("project configuration");
|
|
772
|
+
}
|
|
773
|
+
if (!result.hasRegionalSupport) {
|
|
774
|
+
missingComponents.push("regional support");
|
|
775
|
+
}
|
|
776
|
+
result.recommendations.push(`⚠️ Vertex Anthropic support partially available`, `❌ Missing: ${missingComponents.join(", ")}`, "", "🔧 Quick fixes needed:");
|
|
777
|
+
result.recommendations.push(...result.troubleshooting);
|
|
778
|
+
logger.warn("Vertex Anthropic support verification: PARTIALLY_SUPPORTED", {
|
|
779
|
+
missingComponents,
|
|
780
|
+
hasBasicSupport: result.hasCreateVertexAnthropic,
|
|
781
|
+
authenticationValid: result.authentication.isValid,
|
|
782
|
+
projectValid: result.hasValidProject,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
logger.error("Vertex Anthropic support check failed", {
|
|
788
|
+
error: error instanceof Error ? error.message : String(error),
|
|
789
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
790
|
+
});
|
|
791
|
+
result.recommendations.push("❌ Comprehensive Anthropic support check failed", `🐛 Error: ${error instanceof Error ? error.message : String(error)}`, "", "🔧 Troubleshooting steps:", "1. Update @ai-sdk/google-vertex to latest version", "2. Verify Google Cloud authentication setup", "3. Check project ID and region configuration", "4. Enable Vertex AI API in Google Cloud Console", "5. Enable Anthropic integration in Vertex AI Model Garden");
|
|
792
|
+
}
|
|
793
|
+
return result;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Validate Vertex AI authentication configuration
|
|
797
|
+
*/
|
|
798
|
+
static async validateVertexAuthentication() {
|
|
799
|
+
const result = {
|
|
800
|
+
isValid: false,
|
|
801
|
+
method: "none",
|
|
802
|
+
issues: [],
|
|
803
|
+
};
|
|
804
|
+
try {
|
|
805
|
+
// Check for service account file authentication (preferred)
|
|
806
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
807
|
+
const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
808
|
+
try {
|
|
809
|
+
const { promises: fs } = await import("fs");
|
|
810
|
+
try {
|
|
811
|
+
await fs.access(credentialsPath);
|
|
812
|
+
// Validate JSON structure
|
|
813
|
+
const credentialsContent = await fs.readFile(credentialsPath, "utf8");
|
|
814
|
+
const credentials = JSON.parse(credentialsContent);
|
|
815
|
+
if (credentials.type === "service_account" &&
|
|
816
|
+
credentials.project_id &&
|
|
817
|
+
credentials.client_email &&
|
|
818
|
+
credentials.private_key) {
|
|
819
|
+
result.isValid = true;
|
|
820
|
+
result.method = "service_account_file";
|
|
821
|
+
return result;
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
result.issues.push("Service account file missing required fields");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
catch {
|
|
828
|
+
result.issues.push(`Service account file not found: ${credentialsPath}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch (fileError) {
|
|
832
|
+
result.issues.push(`Service account file validation failed: ${fileError}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Check for individual environment variables
|
|
836
|
+
if (process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
|
|
837
|
+
process.env.GOOGLE_AUTH_PRIVATE_KEY) {
|
|
838
|
+
const email = process.env.GOOGLE_AUTH_CLIENT_EMAIL;
|
|
839
|
+
const privateKey = process.env.GOOGLE_AUTH_PRIVATE_KEY;
|
|
840
|
+
if (email.includes("@") && privateKey.includes("BEGIN PRIVATE KEY")) {
|
|
841
|
+
result.isValid = true;
|
|
842
|
+
result.method = "environment_variables";
|
|
843
|
+
return result;
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
result.issues.push("Individual credentials format validation failed");
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
result.issues.push("Missing individual credential environment variables");
|
|
851
|
+
}
|
|
852
|
+
// Check for Application Default Credentials (ADC)
|
|
853
|
+
try {
|
|
854
|
+
// This is a simple heuristic - in real implementation you'd use Google Auth library
|
|
855
|
+
if (process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT) {
|
|
856
|
+
result.method = "application_default_credentials";
|
|
857
|
+
result.isValid = true; // Assume valid if environment suggests ADC
|
|
858
|
+
return result;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
catch (adcError) {
|
|
862
|
+
result.issues.push(`ADC check failed: ${adcError}`);
|
|
863
|
+
}
|
|
864
|
+
if (!result.isValid) {
|
|
865
|
+
result.issues.push("No valid authentication method found");
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch (error) {
|
|
869
|
+
result.issues.push(`Authentication validation error: ${error}`);
|
|
870
|
+
}
|
|
871
|
+
return result;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Validate Vertex AI project configuration
|
|
875
|
+
*/
|
|
876
|
+
static async validateVertexProjectConfiguration() {
|
|
877
|
+
const result = {
|
|
878
|
+
isValid: false,
|
|
879
|
+
projectId: undefined,
|
|
880
|
+
region: undefined,
|
|
881
|
+
issues: [],
|
|
882
|
+
};
|
|
883
|
+
// Check project ID
|
|
884
|
+
const projectId = process.env.GOOGLE_VERTEX_PROJECT ||
|
|
885
|
+
process.env.GOOGLE_CLOUD_PROJECT_ID ||
|
|
886
|
+
process.env.GOOGLE_PROJECT_ID ||
|
|
887
|
+
process.env.GOOGLE_CLOUD_PROJECT;
|
|
888
|
+
if (projectId) {
|
|
889
|
+
result.projectId = projectId;
|
|
890
|
+
// Validate project ID format
|
|
891
|
+
const projectIdPattern = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
892
|
+
if (projectIdPattern.test(projectId)) {
|
|
893
|
+
result.isValid = true;
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
result.issues.push(`Invalid project ID format: ${projectId}`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
result.issues.push("No project ID configured");
|
|
901
|
+
}
|
|
902
|
+
// Check region/location
|
|
903
|
+
const region = process.env.GOOGLE_CLOUD_LOCATION ||
|
|
904
|
+
process.env.VERTEX_LOCATION ||
|
|
905
|
+
process.env.GOOGLE_VERTEX_LOCATION ||
|
|
906
|
+
"us-central1";
|
|
907
|
+
result.region = region;
|
|
908
|
+
// Validate region format
|
|
909
|
+
const regionPattern = /^[a-z]+-[a-z]+\d+$/;
|
|
910
|
+
if (!regionPattern.test(region)) {
|
|
911
|
+
result.issues.push(`Invalid region format: ${region}`);
|
|
912
|
+
result.isValid = false;
|
|
913
|
+
}
|
|
914
|
+
return result;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Check if the specified region supports Anthropic models
|
|
918
|
+
*/
|
|
919
|
+
static async checkVertexRegionalSupport(region = "us-central1") {
|
|
920
|
+
// Based on Google Cloud documentation, these regions support Anthropic models
|
|
921
|
+
const supportedRegions = [
|
|
922
|
+
"us-central1",
|
|
923
|
+
"us-east4",
|
|
924
|
+
"us-west1",
|
|
925
|
+
"us-west4",
|
|
926
|
+
"europe-west1",
|
|
927
|
+
"europe-west4",
|
|
928
|
+
"asia-southeast1",
|
|
929
|
+
"asia-northeast1",
|
|
930
|
+
];
|
|
931
|
+
const isSupported = supportedRegions.includes(region);
|
|
932
|
+
logger.debug("Regional support check", {
|
|
933
|
+
region,
|
|
934
|
+
isSupported,
|
|
935
|
+
supportedRegions,
|
|
936
|
+
});
|
|
937
|
+
return isSupported;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Check network connectivity to Vertex AI endpoints
|
|
941
|
+
*/
|
|
942
|
+
static async checkVertexNetworkConnectivity(region = "us-central1") {
|
|
943
|
+
try {
|
|
944
|
+
const endpoint = `https://${region}-aiplatform.googleapis.com`;
|
|
945
|
+
// Simple connectivity check with timeout
|
|
946
|
+
const controller = new AbortController();
|
|
947
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
948
|
+
const proxyFetch = createProxyFetch();
|
|
949
|
+
let response = await proxyFetch(endpoint, {
|
|
950
|
+
method: "HEAD",
|
|
951
|
+
signal: controller.signal,
|
|
952
|
+
});
|
|
953
|
+
// Fallback to GET if HEAD returns 405 (Method Not Allowed) for restrictive gateways
|
|
954
|
+
if (response.status === 405) {
|
|
955
|
+
response = await proxyFetch(endpoint, {
|
|
956
|
+
method: "GET",
|
|
957
|
+
signal: controller.signal,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
clearTimeout(timeoutId);
|
|
961
|
+
// Any response (even 401/403) indicates network connectivity
|
|
962
|
+
const isConnected = response.status !== undefined;
|
|
963
|
+
logger.debug("Network connectivity check", {
|
|
964
|
+
endpoint,
|
|
965
|
+
status: response.status,
|
|
966
|
+
isConnected,
|
|
967
|
+
});
|
|
968
|
+
return isConnected;
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
logger.debug("Network connectivity check failed", {
|
|
972
|
+
region,
|
|
973
|
+
error: error instanceof Error ? error.message : String(error),
|
|
974
|
+
});
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Check if Anthropic model integration is enabled in the project
|
|
980
|
+
*/
|
|
981
|
+
static async checkAnthropicModelIntegration(projectId, region = "us-central1") {
|
|
982
|
+
if (!projectId) {
|
|
983
|
+
logger.debug("Cannot check Anthropic integration without project ID");
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
try {
|
|
987
|
+
// This is a simplified check - in a real implementation, you would:
|
|
988
|
+
// 1. Use Google Cloud APIs to check model availability
|
|
989
|
+
// 2. Verify Anthropic integration status
|
|
990
|
+
// 3. Check model access permissions
|
|
991
|
+
// For now, we'll do a basic endpoint check
|
|
992
|
+
const modelEndpoint = `https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}/publishers/anthropic/models`;
|
|
993
|
+
const controller = new AbortController();
|
|
994
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
995
|
+
const proxyFetch = createProxyFetch();
|
|
996
|
+
let response = await proxyFetch(modelEndpoint, {
|
|
997
|
+
method: "HEAD",
|
|
998
|
+
signal: controller.signal,
|
|
999
|
+
});
|
|
1000
|
+
// Fallback to GET if HEAD returns 405 (Method Not Allowed) for restrictive gateways
|
|
1001
|
+
if (response.status === 405) {
|
|
1002
|
+
response = await proxyFetch(modelEndpoint, {
|
|
1003
|
+
method: "GET",
|
|
1004
|
+
signal: controller.signal,
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
clearTimeout(timeoutId);
|
|
1008
|
+
// Status 200 or 401/403 suggests the endpoint exists (integration enabled)
|
|
1009
|
+
// Status 404 suggests integration not enabled
|
|
1010
|
+
const integrationEnabled = response.status !== 404;
|
|
1011
|
+
logger.debug("Anthropic integration check", {
|
|
1012
|
+
projectId,
|
|
1013
|
+
region,
|
|
1014
|
+
endpoint: modelEndpoint,
|
|
1015
|
+
status: response.status,
|
|
1016
|
+
integrationEnabled,
|
|
1017
|
+
});
|
|
1018
|
+
return integrationEnabled;
|
|
1019
|
+
}
|
|
1020
|
+
catch (error) {
|
|
1021
|
+
logger.debug("Anthropic integration check failed", {
|
|
1022
|
+
projectId,
|
|
1023
|
+
region,
|
|
1024
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1025
|
+
});
|
|
1026
|
+
// Assume integration might be enabled if we can't verify
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Initialize health checks in the background (NON-BLOCKING)
|
|
1032
|
+
* Starts background health monitoring without blocking initialization
|
|
1033
|
+
*/
|
|
1034
|
+
static initializeBackgroundHealthChecks() {
|
|
1035
|
+
// Run health checks in the background without awaiting
|
|
1036
|
+
const backgroundHealthCheck = async () => {
|
|
1037
|
+
try {
|
|
1038
|
+
logger.debug("Starting background health check initialization");
|
|
1039
|
+
await this.checkAllProvidersHealth({
|
|
1040
|
+
includeConnectivityTest: false,
|
|
1041
|
+
cacheResults: true,
|
|
1042
|
+
timeout: 2000, // 2-second timeout for background checks
|
|
1043
|
+
});
|
|
1044
|
+
logger.debug("Background health check initialization completed");
|
|
1045
|
+
}
|
|
1046
|
+
catch (error) {
|
|
1047
|
+
logger.warn("Background health check initialization failed", {
|
|
1048
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
// Start background check without blocking
|
|
1053
|
+
backgroundHealthCheck();
|
|
1054
|
+
}
|
|
453
1055
|
/**
|
|
454
1056
|
* Clear health cache for a provider or all providers
|
|
455
1057
|
*/
|
|
@@ -464,8 +1066,9 @@ export class ProviderHealthChecker {
|
|
|
464
1066
|
}
|
|
465
1067
|
}
|
|
466
1068
|
/**
|
|
467
|
-
* Get the best healthy provider from a list of options
|
|
1069
|
+
* Get the best healthy provider from a list of options (NON-BLOCKING)
|
|
468
1070
|
* Prioritizes healthy providers over configured but unhealthy ones
|
|
1071
|
+
* Uses fast, cached health checks to avoid blocking initialization
|
|
469
1072
|
*/
|
|
470
1073
|
static async getBestHealthyProvider(preferredProviders = [
|
|
471
1074
|
"openai",
|
|
@@ -478,6 +1081,7 @@ export class ProviderHealthChecker {
|
|
|
478
1081
|
const healthStatuses = await this.checkAllProvidersHealth({
|
|
479
1082
|
includeConnectivityTest: false, // Quick config check only
|
|
480
1083
|
cacheResults: true,
|
|
1084
|
+
timeout: 1000, // Fast 1-second timeout to avoid blocking
|
|
481
1085
|
});
|
|
482
1086
|
// First try to find a healthy provider in order of preference
|
|
483
1087
|
for (const provider of preferredProviders) {
|
|
@@ -507,11 +1111,12 @@ export class ProviderHealthChecker {
|
|
|
507
1111
|
*/
|
|
508
1112
|
static async checkAllProvidersHealth(options = {}) {
|
|
509
1113
|
const providers = [
|
|
510
|
-
AIProviderName.ANTHROPIC,
|
|
511
|
-
AIProviderName.OPENAI,
|
|
512
1114
|
AIProviderName.VERTEX,
|
|
513
1115
|
AIProviderName.GOOGLE_AI,
|
|
1116
|
+
AIProviderName.ANTHROPIC,
|
|
1117
|
+
AIProviderName.OPENAI,
|
|
514
1118
|
AIProviderName.BEDROCK,
|
|
1119
|
+
AIProviderName.AZURE,
|
|
515
1120
|
AIProviderName.OLLAMA,
|
|
516
1121
|
];
|
|
517
1122
|
const healthChecks = providers.map((provider) => this.checkProviderHealth(provider, options));
|