UNPKG

@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
/** * 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[]; }; }