@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
318 lines (317 loc) • 13.6 kB
TypeScript
/**
* Provider Health Checking System
* Prevents 500 errors by validating provider availability and configuration
*/
import { AIProviderName } from "../constants/enums.js";
import type { ProviderHealthCheckOptions, ProviderHealthStatusOptions } from "../types/index.js";
export declare class ProviderHealthChecker {
private static healthCache;
private static readonly DEFAULT_TIMEOUT;
private static readonly DEFAULT_CACHE_AGE;
/**
* Fixed backoff window for the circuit breaker below, in milliseconds.
* Deliberately NOT the per-call `maxCacheAge` option: `consecutiveFailures`
* is a single process-wide map keyed only by provider name, shared by every
* caller (e.g. `checkFallbackProviderAvailability` hard-codes a 15s
* `maxCacheAge` for its own health-status cache). Evicting the breaker
* entry using whichever caller happens to read it next would let a
* short-TTL caller silently erase a backoff another caller is relying on
* — see `checkProviderHealth`. This constant governs breaker expiry alone
* and is intentionally independent of `maxCacheAge`/`DEFAULT_CACHE_AGE`.
*/
private static readonly CIRCUIT_BREAKER_RESET_MS;
private static readonly CONSECUTIVE_FAILURE_THRESHOLD;
/**
* Circuit breaker state, keyed by provider.
*
* `count` is a count of consecutive failed CONNECTIVITY PROBES (plus the
* rare case of the check itself throwing) — never of "the provider is
* unconfigured". Those are different facts and only one of them is worth
* backing off from. A missing credential is a free, local, determinate
* answer that costs nothing to re-derive and flips the instant an env var
* is set; treating it as a failure is what let a sweep of ~38 providers
* (issue #1305 grew it from 8) blacklist every provider the machine
* happens not to have credentials for, after which the sweep reported
* `isConfigured: false` and no `responseTime` for a provider that was
* merely unconfigured — and kept reporting it after the key was supplied.
*
* `lastFailureAt` bounds how long a run of failures is held against a
* provider; see `checkProviderHealth`.
*/
private static consecutiveFailures;
/**
* Hard cap on simultaneous per-provider checks inside
* `checkAllProvidersHealth`. Issue #1305: the sweep used to cover only 8
* of ~38 registered providers; now every non-opted-out descriptor is
* included (see `excludeFromHealthSweep`). Firing all ~38 at once via a
* single `Promise.allSettled` would be harmless by default
* (`includeConnectivityTest` defaults to false, so most calls never
* leave the process), but a caller who opts into
* `includeConnectivityTest: true` would otherwise open up to ~38
* concurrent outbound HTTP connections to that many different vendors in
* one burst. A worker pool keeps that bounded without reducing coverage
* — every provider is still checked, just not all in the same instant,
* and without the latency a fixed batch would add (see
* `checkAllProvidersHealth`).
*/
private static readonly MAX_CONCURRENT_HEALTH_CHECKS;
/**
* Validate and return a safe failure threshold value
*/
private static getValidatedFailureThreshold;
/**
* Comprehensive health check for a provider
*/
static checkProviderHealth(providerName: AIProviderName, options?: ProviderHealthCheckOptions): Promise<ProviderHealthStatusOptions>;
/**
* Check environment configuration for a provider
*/
private static checkEnvironmentConfiguration;
/**
* Check API key validity (format validation)
*/
private static checkApiKeyValidity;
/**
* Check connectivity to provider endpoints
*/
private static checkConnectivity;
private static getConnectivityHeaders;
/**
* Check model availability (if possible without making API calls)
*/
private static checkModelAvailability;
/**
* Get required environment variables for a provider.
*
* Returns `[]` for providers with `descriptor.credentialsResolvedExternally
* === true` (Vertex, Bedrock, LiteLLM) — the check in
* checkEnvironmentConfiguration() below ANDs every entry in the returned
* list together, which can't express Vertex's file-OR-individual-creds-
* OR-service-account auth, Bedrock's AWS SDK default provider chain
* (profile / IAM role, no env vars at all), or LiteLLM's documented
* zero-config local proxy. Their real requirement is validated by
* checkProviderSpecificConfig()'s dedicated per-provider checks instead.
* Naively deriving [apiKey, ...extraRequired] from the descriptor for
* these three (as for the other providers) would make
* checkEnvironmentConfiguration() push a false "missing environment
* variables" issue — and therefore isHealthy=false — for legitimate
* fallback-based Vertex auth, AWS_PROFILE/IAM-role Bedrock auth, and
* unauthenticated local LiteLLM proxies. See hasProviderEnvVars() in
* providerUtils.ts for the equivalent OR-aware check used for
* auto-select gating. See `ProviderDescriptor.credentialsResolvedExternally`
* (types/providers.ts) for the field's full documentation.
*/
static getRequiredEnvironmentVariables(providerName: string): string[];
/**
* Get API key environment variable for a provider.
*
* Dead code at its sole call site today (checkApiKeyValidity() early-
* returns for VERTEX/OLLAMA/BEDROCK/LITELLM before reaching this), but
* now `static` (public) so a future direct caller is plausible — hence
* the two intentional diffs from the original hand-written switch are
* documented here rather than left silent:
* - vertex: old switch returned "GOOGLE_APPLICATION_CREDENTIALS"
* (one of several valid auth vars); the descriptor's `envVars.apiKey`
* is "GOOGLE_CLOUD_PROJECT_ID" instead — the actual identity var for
* the provider, not one specific credential-supply mechanism.
* - ollama: old switch returned "OLLAMA_BASE_URL"; the descriptor has
* no `envVars.apiKey` for ollama (it's a local, unauthenticated
* runtime), so this now returns "".
* Both are kept deliberately (semantically truer identity vars) rather
* than reproduced byte-for-byte from the old switch.
*/
static getApiKeyEnvironmentVariable(providerName: string): string;
/**
* Validate API key format for a provider
*/
static validateApiKeyFormat(providerName: string, apiKey: string): boolean;
/**
* Get health check endpoint for connectivity testing
*/
static getProviderHealthEndpoint(providerName: string): string | null;
/**
* Provider-specific configuration checks
*/
private static checkProviderSpecificConfig;
/**
* Check Vertex AI configuration
*/
private static checkVertexAIConfig;
/**
* Get Vertex AI project ID from environment variables
*/
private static getVertexProjectId;
/**
* Check Vertex AI authentication
*/
private static checkVertexAuthentication;
/**
* Check Google Application Credentials file
*/
private static checkGoogleApplicationCredentials;
/**
* Check Vertex's non-file auth fallbacks (GOOGLE_APPLICATION_CREDENTIALS_
* NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY, or the GOOGLE_AUTH_CLIENT_EMAIL +
* GOOGLE_AUTH_PRIVATE_KEY pair) via the descriptor's extraRequiredFallbacks
* instead of a hand-maintained env-var list, so this stays in sync with
* the real gating logic (hasGoogleCredentials()) the same way
* checkApiKeyValidity()'s vertex branch does. The previous hand-rolled
* version only recognized GOOGLE_SERVICE_ACCOUNT_KEY or the email+key
* pair — missing GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, the
* descriptor's documented first-priority fallback.
*/
private static checkExtraRequiredFallbackCredentials;
/**
* Check AWS Bedrock configuration
*/
private static checkBedrockConfig;
/**
* Check AWS region configuration
*/
private static checkAWSRegion;
/**
* Check AWS credentials
*/
private static checkAWSCredentials;
/**
* Check Bedrock models
*/
private static checkBedrockModels;
/**
* Check Bedrock endpoint
*/
private static checkBedrockEndpoint;
/**
* Check Azure OpenAI configuration
*/
private static checkAzureConfig;
private static getLiteLLMBaseUrl;
private static getLiteLLMModelsUrl;
private static getConfiguredLiteLLMModel;
private static getOllamaBaseUrl;
private static getOllamaTagsUrl;
private static getConfiguredOllamaModel;
private static fetchJsonWithTimeout;
private static normalizeModelList;
private static hasRequestedModel;
private static getOllamaAvailableModels;
private static getLiteLLMAvailableModels;
private static checkOllamaAvailability;
private static checkLiteLLMAvailability;
private static checkLiteLLMConfig;
/**
* Check Ollama configuration
*/
private static checkOllamaConfig;
/**
* Get common models for a provider
*/
private static getCommonModelsForProvider;
/**
* Get cached health status if still valid
*/
private static getCachedHealth;
/**
* Check if Vertex AI supports Anthropic models (dual provider architecture)
*/
static checkVertexAnthropicSupport(): Promise<{
isSupported: boolean;
hasCreateVertexAnthropic: boolean;
hasCorrectTypes: boolean;
hasValidProject: boolean;
hasRegionalSupport: boolean;
hasNetworkAccess: boolean;
hasAnthropicModels: boolean;
authentication: {
isValid: boolean;
method: string;
issues: string[];
};
projectConfiguration: {
isValid: boolean;
projectId: string | undefined;
region: string | undefined;
issues: string[];
};
modelSupport: {
availableModels: string[];
recommendedModels: string[];
deprecatedModels: string[];
};
recommendations: string[];
troubleshooting: string[];
}>;
/**
* Validate Vertex AI authentication configuration
*/
private static validateVertexAuthentication;
/**
* Validate Vertex AI project configuration
*/
private static validateVertexProjectConfiguration;
/**
* Check if the specified region supports Anthropic models
*/
private static checkVertexRegionalSupport;
/**
* Check network connectivity to Vertex AI endpoints
*/
private static checkVertexNetworkConnectivity;
/**
* Check if Anthropic model integration is enabled in the project
*/
private static checkAnthropicModelIntegration;
/**
* Initialize health checks in the background (NON-BLOCKING)
* Starts background health monitoring without blocking initialization
*/
static initializeBackgroundHealthChecks(): void;
/**
* Clear health cache for a provider or all providers
*/
static clearHealthCache(providerName?: AIProviderName): void;
static checkFallbackProviderAvailability(providerName: string, model: string): Promise<{
available: boolean;
reason?: string;
}>;
/**
* Get the best healthy provider from a list of options (NON-BLOCKING)
* Prioritizes healthy providers over configured but unhealthy ones
* Uses fast, cached health checks to avoid blocking initialization
*/
static getBestHealthyProvider(preferredProviders?: string[]): Promise<string | null>;
/**
* Get health status for all registered providers.
*
* Membership: every descriptor in PROVIDER_DESCRIPTORS EXCEPT those that
* explicitly opt out via `excludeFromHealthSweep` (none currently do).
* Before issue #1305's fix, membership was implicit opt-IN via
* `defaultHealthSweepPriority`, which only 8 of ~38 descriptors carried —
* every other registered provider was silently invisible to this sweep,
* including anything a caller had actually configured and could use.
*
* Order: `defaultHealthSweepPriority` still decides ORDER for the
* providers that set it (lower = first — order is behaviour for
* first-healthy-wins callers like `getBestHealthyProvider`); everything
* else sorts after them, in PROVIDER_DESCRIPTORS's own declaration order
* (Array.prototype.sort is stable, so ties never reshuffle).
*
* Checks run through a worker pool of at most
* MAX_CONCURRENT_HEALTH_CHECKS, so a full sweep of every provider can't
* fire dozens of concurrent requests at once when a caller opts into
* `includeConnectivityTest`, and no check waits on a slower one beside
* it.
*/
static checkAllProvidersHealth(options?: ProviderHealthCheckOptions): Promise<ProviderHealthStatusOptions[]>;
/**
* Get a summary of provider health
*/
static getHealthSummary(healthStatuses: ProviderHealthStatusOptions[]): {
total: number;
healthy: number;
configured: number;
hasIssues: number;
healthyProviders: string[];
unhealthyProviders: string[];
};
}