@juspay/neurolink 11.0.0 → 11.1.0
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 +6 -0
- package/dist/browser/neurolink.min.js +524 -524
- package/dist/cli/commands/setup.d.ts +4 -0
- package/dist/cli/commands/setup.js +19 -33
- package/dist/cli/factories/commandFactory.d.ts +1157 -1
- package/dist/cli/factories/commandFactory.js +20 -53
- package/dist/factories/providerDescriptors.d.ts +18 -0
- package/dist/factories/providerDescriptors.js +536 -0
- package/dist/factories/providerFactory.d.ts +25 -14
- package/dist/factories/providerFactory.js +46 -23
- package/dist/factories/providerRegistry.js +31 -30
- package/dist/index.d.ts +10 -1
- package/dist/index.js +13 -3
- package/dist/lib/factories/providerDescriptors.d.ts +18 -0
- package/dist/lib/factories/providerDescriptors.js +537 -0
- package/dist/lib/factories/providerFactory.d.ts +25 -14
- package/dist/lib/factories/providerFactory.js +46 -23
- package/dist/lib/factories/providerRegistry.js +31 -30
- package/dist/lib/index.d.ts +10 -1
- package/dist/lib/index.js +13 -3
- package/dist/lib/neurolink.js +25 -37
- package/dist/lib/providers/amazonBedrock/client.js +34 -16
- package/dist/lib/providers/anthropic/client.js +39 -34
- package/dist/lib/providers/azureOpenai.js +17 -15
- package/dist/lib/providers/cloudflare.js +12 -21
- package/dist/lib/providers/cohere.js +31 -25
- package/dist/lib/providers/deepseek.js +23 -26
- package/dist/lib/providers/fireworks.js +12 -21
- package/dist/lib/providers/googleAiStudio/client.d.ts +2 -0
- package/dist/lib/providers/googleAiStudio/client.js +39 -17
- package/dist/lib/providers/googleVertex/client.js +107 -99
- package/dist/lib/providers/groq.js +19 -22
- package/dist/lib/providers/huggingFace/client.js +26 -24
- package/dist/lib/providers/litellm/client.js +54 -39
- package/dist/lib/providers/llamaCpp.js +21 -18
- package/dist/lib/providers/lmStudio.js +22 -19
- package/dist/lib/providers/mistral.js +12 -24
- package/dist/lib/providers/nvidiaNim/client.js +37 -34
- package/dist/lib/providers/ollama/client.js +50 -35
- package/dist/lib/providers/openAI/client.js +37 -40
- package/dist/lib/providers/openRouter/client.js +53 -47
- package/dist/lib/providers/openaiChatCompletionsBase.js +40 -16
- package/dist/lib/providers/openaiCompatible/client.js +36 -32
- package/dist/lib/providers/perplexity.js +12 -21
- package/dist/lib/providers/togetherAi.js +12 -21
- package/dist/lib/providers/xai.js +17 -26
- package/dist/lib/server/errors.d.ts +1 -1
- package/dist/lib/server/errors.js +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +5 -3
- package/dist/lib/server/middleware/rateLimit.d.ts +0 -4
- package/dist/lib/server/middleware/rateLimit.js +0 -4
- package/dist/lib/types/errors.d.ts +35 -0
- package/dist/lib/types/providers.d.ts +64 -0
- package/dist/lib/utils/errorClassifier.d.ts +30 -0
- package/dist/lib/utils/errorClassifier.js +94 -0
- package/dist/lib/utils/fileDetector.js +6 -43
- package/dist/lib/utils/providerHealth.d.ts +42 -7
- package/dist/lib/utils/providerHealth.js +115 -122
- package/dist/lib/utils/providerUtils.js +21 -63
- package/dist/neurolink.js +25 -37
- package/dist/providers/amazonBedrock/client.js +34 -16
- package/dist/providers/amazonSagemaker.d.ts +1 -1
- package/dist/providers/anthropic/client.js +39 -34
- package/dist/providers/azureOpenai.js +17 -15
- package/dist/providers/cloudflare.js +12 -21
- package/dist/providers/cohere.js +31 -25
- package/dist/providers/deepseek.js +23 -26
- package/dist/providers/fireworks.js +12 -21
- package/dist/providers/googleAiStudio/client.d.ts +2 -0
- package/dist/providers/googleAiStudio/client.js +39 -17
- package/dist/providers/googleVertex/client.js +107 -99
- package/dist/providers/groq.js +19 -22
- package/dist/providers/huggingFace/client.js +26 -24
- package/dist/providers/litellm/client.js +54 -39
- package/dist/providers/llamaCpp.js +21 -18
- package/dist/providers/lmStudio.js +22 -19
- package/dist/providers/mistral.js +12 -24
- package/dist/providers/nvidiaNim/client.js +37 -34
- package/dist/providers/ollama/client.js +50 -35
- package/dist/providers/openAI/client.js +37 -40
- package/dist/providers/openRouter/client.js +53 -47
- package/dist/providers/openaiChatCompletionsBase.js +40 -16
- package/dist/providers/openaiCompatible/client.js +36 -32
- package/dist/providers/perplexity.js +12 -21
- package/dist/providers/sagemaker/language-model.d.ts +2 -2
- package/dist/providers/togetherAi.js +12 -21
- package/dist/providers/xai.js +17 -26
- package/dist/server/errors.d.ts +1 -1
- package/dist/server/errors.js +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +5 -3
- package/dist/server/middleware/rateLimit.d.ts +0 -4
- package/dist/server/middleware/rateLimit.js +0 -4
- package/dist/types/errors.d.ts +35 -0
- package/dist/types/providers.d.ts +64 -0
- package/dist/utils/errorClassifier.d.ts +30 -0
- package/dist/utils/errorClassifier.js +93 -0
- package/dist/utils/fileDetector.js +6 -43
- package/dist/utils/providerHealth.d.ts +42 -7
- package/dist/utils/providerHealth.js +115 -122
- package/dist/utils/providerUtils.js +21 -63
- package/package.json +15 -70
|
@@ -96,6 +96,7 @@ export type NeurolinkCredentials = {
|
|
|
96
96
|
};
|
|
97
97
|
googleAiStudio?: {
|
|
98
98
|
apiKey?: string;
|
|
99
|
+
baseURL?: string;
|
|
99
100
|
};
|
|
100
101
|
vertex?: {
|
|
101
102
|
projectId?: string;
|
|
@@ -888,6 +889,8 @@ export type GenAIClient = {
|
|
|
888
889
|
export type GoogleGenAIHttpOptions = {
|
|
889
890
|
/** Custom fetch implementation for proxy support */
|
|
890
891
|
fetch?: typeof fetch;
|
|
892
|
+
/** Override the API base URL (e.g. a corporate proxy or mock endpoint) */
|
|
893
|
+
baseUrl?: string;
|
|
891
894
|
};
|
|
892
895
|
/**
|
|
893
896
|
* Google GenAI constructor type
|
|
@@ -1677,6 +1680,67 @@ export type ProviderRegistration = {
|
|
|
1677
1680
|
constructor: ProviderConstructor;
|
|
1678
1681
|
defaultModel?: string;
|
|
1679
1682
|
aliases?: string[];
|
|
1683
|
+
descriptor?: ProviderDescriptor;
|
|
1684
|
+
};
|
|
1685
|
+
/**
|
|
1686
|
+
* Single source of truth for one AI provider's static identity: how it's
|
|
1687
|
+
* addressed (name/aliases), how it's authenticated (credentialsKey/envVars),
|
|
1688
|
+
* what it defaults to (defaultModel), and how the rest of the codebase
|
|
1689
|
+
* should treat it (toolSupport/localRuntime/healthCheck). Every consumer
|
|
1690
|
+
* that used to hand-maintain its own provider table (CLI choices,
|
|
1691
|
+
* CREDENTIAL_KEY_MAP, env-var checks, health-check dispatch, auto-select
|
|
1692
|
+
* priority, PROMPT_ONLY_TOOL_PROVIDERS) derives from PROVIDER_DESCRIPTORS
|
|
1693
|
+
* instead. See src/lib/factories/providerDescriptors.ts for the data.
|
|
1694
|
+
*/
|
|
1695
|
+
export type ProviderDescriptor = {
|
|
1696
|
+
/** Canonical identity — matches an AIProviderName enum member (never AUTO). */
|
|
1697
|
+
name: AIProviderName;
|
|
1698
|
+
/** Alternate spellings accepted by the CLI and the alias index (kebab-case, shorthand, legacy names). Does not include `name` itself. */
|
|
1699
|
+
aliases: readonly string[];
|
|
1700
|
+
/** Key into NeurolinkCredentials for per-call/per-instance credential overrides. */
|
|
1701
|
+
credentialsKey: keyof NeurolinkCredentials;
|
|
1702
|
+
/** Environment variables this provider reads at runtime. */
|
|
1703
|
+
envVars: {
|
|
1704
|
+
/** Primary identity/secret env var. Absent for providers with no required credential (Ollama, LM Studio, llama.cpp) or that use extraRequired instead of a single key (Vertex). */
|
|
1705
|
+
apiKey?: string;
|
|
1706
|
+
/** Alternate env vars accepted in place of apiKey, checked in order after apiKey. */
|
|
1707
|
+
fallbacks?: readonly string[];
|
|
1708
|
+
baseURL?: string;
|
|
1709
|
+
/** Alternate env vars accepted in place of baseURL. */
|
|
1710
|
+
baseURLFallbacks?: readonly string[];
|
|
1711
|
+
/** Env var that overrides the static defaultModel at runtime. */
|
|
1712
|
+
model?: string;
|
|
1713
|
+
/** Alternate env vars accepted in place of model, checked in order after model. */
|
|
1714
|
+
modelFallbacks?: readonly string[];
|
|
1715
|
+
/** Additional env vars required alongside apiKey (e.g. AWS secret key, Azure endpoint). */
|
|
1716
|
+
extraRequired?: readonly string[];
|
|
1717
|
+
/** Alternate ways to satisfy extraRequired when it isn't a plain env-var list (e.g. Vertex's file-path-OR-individual-fields auth). */
|
|
1718
|
+
extraRequiredFallbacks?: readonly string[];
|
|
1719
|
+
/** True when the provider is usable with zero configuration (local runtime with a documented default URL, or a documented non-secret default like LiteLLM's "sk-anything"). */
|
|
1720
|
+
optional?: boolean;
|
|
1721
|
+
};
|
|
1722
|
+
/**
|
|
1723
|
+
* Static fallback model. The empty string "" is a documented sentinel
|
|
1724
|
+
* meaning "no static default — resolved at runtime via envVars.model or
|
|
1725
|
+
* provider-side auto-discovery" (used by Bedrock, OpenAI-Compatible,
|
|
1726
|
+
* LM Studio, llama.cpp, matching how providerRegistry.ts already passes
|
|
1727
|
+
* `undefined` as their defaultModel argument today).
|
|
1728
|
+
*/
|
|
1729
|
+
defaultModel: string;
|
|
1730
|
+
toolSupport: "native" | "prompt-only" | "none" | "model-dependent";
|
|
1731
|
+
/** True only for providers that run entirely on the caller's machine with no cloud account (Ollama, LM Studio, llama.cpp). LiteLLM is a local proxy but commonly points at cloud models, so it is deliberately false. */
|
|
1732
|
+
localRuntime: boolean;
|
|
1733
|
+
/** How ProviderHealthChecker should verify this provider is reachable. */
|
|
1734
|
+
healthCheck: "env-only" | "models-probe" | "live-generate";
|
|
1735
|
+
setupUrl?: string;
|
|
1736
|
+
timeouts?: {
|
|
1737
|
+
generateMs?: number;
|
|
1738
|
+
streamMs?: number;
|
|
1739
|
+
};
|
|
1740
|
+
/** Ascending priority (1 = tried first) in the auto-select fallback chain used by getBestProvider(). Undefined = not part of the auto-select chain. */
|
|
1741
|
+
autoSelectPriority?: number;
|
|
1742
|
+
/** Format-validation regex sourced from providerConfig.ts's API_KEY_FORMATS, when one exists for this provider. */
|
|
1743
|
+
apiKeyFormatPattern?: RegExp;
|
|
1680
1744
|
};
|
|
1681
1745
|
/** Minimal NeuroLink-like instance accepted by the image generation service. */
|
|
1682
1746
|
export type NeuroLinkInstance = {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared provider-error classification. Every provider's
|
|
3
|
+
* `formatProviderError(error)` delegates here instead of hand-rolling its
|
|
4
|
+
* own TimeoutError-check → .includes()-chain → `new XError(...)` ladder.
|
|
5
|
+
*
|
|
6
|
+
* `classifyProviderError` picks the Error subclass + message; it does NOT
|
|
7
|
+
* stamp statusCode/isRetryable/retryAfterMs onto the result — that
|
|
8
|
+
* passthrough already happens generically in
|
|
9
|
+
* `BaseProvider.handleProviderError()` (src/lib/core/baseProvider.ts) for
|
|
10
|
+
* every provider's returned error, migrated or not, so duplicating it here
|
|
11
|
+
* would risk the two copies disagreeing.
|
|
12
|
+
*/
|
|
13
|
+
import { type ProviderErrorRule } from "../types/index.js";
|
|
14
|
+
/**
|
|
15
|
+
* Classify a raw provider error into a NeuroLink `ProviderError` subclass.
|
|
16
|
+
* `rules` are tried in order; the first match wins. `TimeoutError` is
|
|
17
|
+
* always handled first, ahead of any rule table — every provider treated
|
|
18
|
+
* it identically before this change, so it is not made overridable.
|
|
19
|
+
*/
|
|
20
|
+
export declare function classifyProviderError(error: unknown, rules: ProviderErrorRule[], provider: string, modelName?: string): Error;
|
|
21
|
+
/**
|
|
22
|
+
* Generic fallback rule table covering the five categories every
|
|
23
|
+
* OpenAI-compatible provider already hand-rolled near-identically:
|
|
24
|
+
* auth (401), rate limit (429), model-not-found (404), network/connection
|
|
25
|
+
* errors, and 5xx server errors. Providers with a provider-specific auth
|
|
26
|
+
* message (naming the exact env var) prepend one override rule and spread
|
|
27
|
+
* this table after it — see errorClassifier usage in any migrated
|
|
28
|
+
* provider's formatProviderError for the pattern.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_ERROR_RULES: ProviderErrorRule[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared provider-error classification. Every provider's
|
|
3
|
+
* `formatProviderError(error)` delegates here instead of hand-rolling its
|
|
4
|
+
* own TimeoutError-check → .includes()-chain → `new XError(...)` ladder.
|
|
5
|
+
*
|
|
6
|
+
* `classifyProviderError` picks the Error subclass + message; it does NOT
|
|
7
|
+
* stamp statusCode/isRetryable/retryAfterMs onto the result — that
|
|
8
|
+
* passthrough already happens generically in
|
|
9
|
+
* `BaseProvider.handleProviderError()` (src/lib/core/baseProvider.ts) for
|
|
10
|
+
* every provider's returned error, migrated or not, so duplicating it here
|
|
11
|
+
* would risk the two copies disagreeing.
|
|
12
|
+
*/
|
|
13
|
+
import { ProviderError, AuthenticationError, RateLimitError, InvalidModelError, NetworkError, } from "../types/index.js";
|
|
14
|
+
import { TimeoutError } from "./timeout.js";
|
|
15
|
+
import { duckTypedStatusCode } from "./providerRetry.js";
|
|
16
|
+
function buildErrorContext(error, provider, modelName) {
|
|
17
|
+
const record = error && typeof error === "object"
|
|
18
|
+
? error
|
|
19
|
+
: undefined;
|
|
20
|
+
const message = typeof record?.message === "string"
|
|
21
|
+
? record.message
|
|
22
|
+
: error instanceof Error
|
|
23
|
+
? error.message
|
|
24
|
+
: "Unknown error";
|
|
25
|
+
return {
|
|
26
|
+
error,
|
|
27
|
+
message,
|
|
28
|
+
statusCode: duckTypedStatusCode(error),
|
|
29
|
+
errorName: typeof record?.name === "string" ? record.name : undefined,
|
|
30
|
+
errorCode: typeof record?.code === "string" ? record.code : undefined,
|
|
31
|
+
provider,
|
|
32
|
+
modelName,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Classify a raw provider error into a NeuroLink `ProviderError` subclass.
|
|
37
|
+
* `rules` are tried in order; the first match wins. `TimeoutError` is
|
|
38
|
+
* always handled first, ahead of any rule table — every provider treated
|
|
39
|
+
* it identically before this change, so it is not made overridable.
|
|
40
|
+
*/
|
|
41
|
+
export function classifyProviderError(error, rules, provider, modelName) {
|
|
42
|
+
if (error instanceof TimeoutError) {
|
|
43
|
+
return new NetworkError(`Request timed out: ${error.message}`, provider);
|
|
44
|
+
}
|
|
45
|
+
const ctx = buildErrorContext(error, provider, modelName);
|
|
46
|
+
const rule = rules.find((r) => r.match(ctx));
|
|
47
|
+
if (!rule) {
|
|
48
|
+
return new ProviderError(`${provider} error: ${ctx.message}`, provider);
|
|
49
|
+
}
|
|
50
|
+
const message = typeof rule.message === "function" ? rule.message(ctx) : rule.message;
|
|
51
|
+
return new rule.errorClass(message, provider);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generic fallback rule table covering the five categories every
|
|
55
|
+
* OpenAI-compatible provider already hand-rolled near-identically:
|
|
56
|
+
* auth (401), rate limit (429), model-not-found (404), network/connection
|
|
57
|
+
* errors, and 5xx server errors. Providers with a provider-specific auth
|
|
58
|
+
* message (naming the exact env var) prepend one override rule and spread
|
|
59
|
+
* this table after it — see errorClassifier usage in any migrated
|
|
60
|
+
* provider's formatProviderError for the pattern.
|
|
61
|
+
*/
|
|
62
|
+
export const DEFAULT_ERROR_RULES = [
|
|
63
|
+
{
|
|
64
|
+
match: (ctx) => ctx.statusCode === 401 ||
|
|
65
|
+
/API_KEY_INVALID|Invalid API key|Unauthorized|invalid_api_key/i.test(ctx.message),
|
|
66
|
+
errorClass: AuthenticationError,
|
|
67
|
+
message: (ctx) => `Invalid ${ctx.provider} API key. Please check your credentials.`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
match: (ctx) => ctx.statusCode === 429 || /rate limit/i.test(ctx.message),
|
|
71
|
+
errorClass: RateLimitError,
|
|
72
|
+
message: (ctx) => `${ctx.provider} rate limit exceeded. Please try again later.`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
match: (ctx) => ctx.statusCode === 404 ||
|
|
76
|
+
/model_not_found|model not found/i.test(ctx.message),
|
|
77
|
+
errorClass: InvalidModelError,
|
|
78
|
+
message: (ctx) => ctx.modelName
|
|
79
|
+
? `${ctx.provider} model '${ctx.modelName}' not found.`
|
|
80
|
+
: `${ctx.provider} model not found.`,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
match: (ctx) => /ECONNRESET|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|network|connection/i.test(ctx.message),
|
|
84
|
+
errorClass: NetworkError,
|
|
85
|
+
message: (ctx) => `Connection error: ${ctx.message}`,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
match: (ctx) => (ctx.statusCode !== undefined && ctx.statusCode >= 500) ||
|
|
89
|
+
/\b5\d\d\b|server error/i.test(ctx.message),
|
|
90
|
+
errorClass: ProviderError,
|
|
91
|
+
message: (ctx) => `${ctx.provider} server error: ${ctx.message}`,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
@@ -26,6 +26,7 @@ import { fileTypeForExtension, lookupByMimeType, normalizeExtension, } from "../
|
|
|
26
26
|
import { LANGUAGE_MAP } from "../processors/config/languageMap.js";
|
|
27
27
|
import { getMimeTypeForExtension, TEXT_EXTENSION_MIME_MAP, } from "../processors/config/mimeConstants.js";
|
|
28
28
|
import { CSVProcessor } from "./csvProcessor.js";
|
|
29
|
+
import { withRetry } from "../core/infrastructure/retry.js";
|
|
29
30
|
import { ImageProcessor } from "./imageProcessor.js";
|
|
30
31
|
import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
|
|
31
32
|
import { logger } from "./logger.js";
|
|
@@ -34,11 +35,6 @@ import { withTimeout } from "./errorHandling.js";
|
|
|
34
35
|
import { normalizeUrlForCache, redactUrlForError, sanitizeErrorCause, } from "./logSanitize.js";
|
|
35
36
|
import { mimeHintToExtension, mimeHintToFileType, normalizeMimeHint, } from "./mimeTypeHints.js";
|
|
36
37
|
import { PDFProcessor } from "./pdfProcessor.js";
|
|
37
|
-
/**
|
|
38
|
-
* Default retry configuration constants
|
|
39
|
-
*/
|
|
40
|
-
const DEFAULT_MAX_RETRIES = 3;
|
|
41
|
-
const DEFAULT_RETRY_DELAY = 1000; // milliseconds
|
|
42
38
|
/**
|
|
43
39
|
* Short-TTL cache of URL → Content-Type (#323). A URL is commonly detected more
|
|
44
40
|
* than once (repeated multimodal prompts reuse the same asset URL); caching the
|
|
@@ -202,41 +198,6 @@ function isRetryableNetworkError(error) {
|
|
|
202
198
|
];
|
|
203
199
|
return transientKeywords.some((keyword) => errorMessage.includes(keyword));
|
|
204
200
|
}
|
|
205
|
-
/**
|
|
206
|
-
* Execute an operation with automatic retry logic on transient network errors
|
|
207
|
-
*
|
|
208
|
-
* @param operation - Async function to execute
|
|
209
|
-
* @param options - Retry configuration options
|
|
210
|
-
* @returns Promise resolving to the operation result
|
|
211
|
-
* @throws Error if all retry attempts fail or error is non-retryable
|
|
212
|
-
*/
|
|
213
|
-
async function withRetry(operation, options = {}) {
|
|
214
|
-
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
215
|
-
const retryDelay = options.retryDelay ?? DEFAULT_RETRY_DELAY;
|
|
216
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
217
|
-
try {
|
|
218
|
-
return await operation();
|
|
219
|
-
}
|
|
220
|
-
catch (error) {
|
|
221
|
-
const isRetryable = isRetryableNetworkError(error);
|
|
222
|
-
const isLastAttempt = attempt === maxRetries;
|
|
223
|
-
if (!isRetryable || isLastAttempt) {
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
// Calculate exponential backoff delay
|
|
227
|
-
const delay = retryDelay * 2 ** attempt;
|
|
228
|
-
logger.debug("Retrying network operation after transient error", {
|
|
229
|
-
attempt: attempt + 1,
|
|
230
|
-
maxRetries,
|
|
231
|
-
delay,
|
|
232
|
-
error: error instanceof Error ? error.message : String(error),
|
|
233
|
-
});
|
|
234
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
// TypeScript exhaustiveness check - should never reach here
|
|
238
|
-
throw new Error("Retry logic failed unexpectedly");
|
|
239
|
-
}
|
|
240
201
|
/**
|
|
241
202
|
* Check if text has JSON markers (starts with { or [ and ends with corresponding closing bracket)
|
|
242
203
|
*/
|
|
@@ -1542,8 +1503,6 @@ export class FileDetector {
|
|
|
1542
1503
|
static async loadFromURL(url, options) {
|
|
1543
1504
|
const maxSize = options?.maxSize || 200 * 1024 * 1024; // 200MB default (matches Curator memory-safety cap)
|
|
1544
1505
|
const timeout = options?.timeout || FileDetector.DEFAULT_NETWORK_TIMEOUT;
|
|
1545
|
-
const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1546
|
-
const retryDelay = options?.retryDelay ?? DEFAULT_RETRY_DELAY;
|
|
1547
1506
|
// #317: pre-flight HEAD to reject an oversized file BEFORE downloading any
|
|
1548
1507
|
// body. content-length is advisory (chunked responses omit it), so a
|
|
1549
1508
|
// missing/invalid header — or a server that refuses HEAD — falls through to
|
|
@@ -1641,7 +1600,11 @@ export class FileDetector {
|
|
|
1641
1600
|
}
|
|
1642
1601
|
throw redacted;
|
|
1643
1602
|
}
|
|
1644
|
-
}, {
|
|
1603
|
+
}, {
|
|
1604
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
1605
|
+
baseDelayMs: options?.retryDelay ?? 1000,
|
|
1606
|
+
shouldRetry: isRetryableNetworkError,
|
|
1607
|
+
});
|
|
1645
1608
|
}
|
|
1646
1609
|
/**
|
|
1647
1610
|
* Load file from filesystem path
|
|
@@ -36,21 +36,56 @@ export declare class ProviderHealthChecker {
|
|
|
36
36
|
*/
|
|
37
37
|
private static checkModelAvailability;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
* Providers whose credential model can't be reduced to "every one of
|
|
40
|
+
* these exact env vars must be literally set" — the check in
|
|
41
|
+
* checkEnvironmentConfiguration() below ANDs every entry in the
|
|
42
|
+
* returned list together, which can't express Vertex's file-OR-
|
|
43
|
+
* individual-creds-OR-service-account auth, Bedrock's AWS SDK default
|
|
44
|
+
* provider chain (profile / IAM role, no env vars at all), or
|
|
45
|
+
* LiteLLM's documented zero-config local proxy. Their real requirement
|
|
46
|
+
* is validated by checkProviderSpecificConfig()'s dedicated per-provider
|
|
47
|
+
* checks instead — matches the original hand-written switch's
|
|
48
|
+
* deliberate `return []` for exactly these three. Naively deriving
|
|
49
|
+
* [apiKey, ...extraRequired] from the descriptor here (as for the other
|
|
50
|
+
* 27 providers) would make checkEnvironmentConfiguration() push a false
|
|
51
|
+
* "missing environment variables" issue — and therefore isHealthy=false
|
|
52
|
+
* — for legitimate fallback-based Vertex auth, AWS_PROFILE/IAM-role
|
|
53
|
+
* Bedrock auth, and unauthenticated local LiteLLM proxies. See
|
|
54
|
+
* hasProviderEnvVars() in providerUtils.ts for the equivalent OR-aware
|
|
55
|
+
* check used for auto-select gating.
|
|
56
|
+
*/
|
|
57
|
+
private static readonly ENV_CHECK_DELEGATED_TO_SPECIFIC_CONFIG;
|
|
42
58
|
/**
|
|
43
|
-
* Get
|
|
59
|
+
* Get required environment variables for a provider
|
|
44
60
|
*/
|
|
45
|
-
|
|
61
|
+
static getRequiredEnvironmentVariables(providerName: string): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Get API key environment variable for a provider.
|
|
64
|
+
*
|
|
65
|
+
* Dead code at its sole call site today (checkApiKeyValidity() early-
|
|
66
|
+
* returns for VERTEX/OLLAMA/BEDROCK/LITELLM before reaching this), but
|
|
67
|
+
* now `static` (public) so a future direct caller is plausible — hence
|
|
68
|
+
* the two intentional diffs from the original hand-written switch are
|
|
69
|
+
* documented here rather than left silent:
|
|
70
|
+
* - vertex: old switch returned "GOOGLE_APPLICATION_CREDENTIALS"
|
|
71
|
+
* (one of several valid auth vars); the descriptor's `envVars.apiKey`
|
|
72
|
+
* is "GOOGLE_CLOUD_PROJECT_ID" instead — the actual identity var for
|
|
73
|
+
* the provider, not one specific credential-supply mechanism.
|
|
74
|
+
* - ollama: old switch returned "OLLAMA_BASE_URL"; the descriptor has
|
|
75
|
+
* no `envVars.apiKey` for ollama (it's a local, unauthenticated
|
|
76
|
+
* runtime), so this now returns "".
|
|
77
|
+
* Both are kept deliberately (semantically truer identity vars) rather
|
|
78
|
+
* than reproduced byte-for-byte from the old switch.
|
|
79
|
+
*/
|
|
80
|
+
static getApiKeyEnvironmentVariable(providerName: string): string;
|
|
46
81
|
/**
|
|
47
82
|
* Validate API key format for a provider
|
|
48
83
|
*/
|
|
49
|
-
|
|
84
|
+
static validateApiKeyFormat(providerName: string, apiKey: string): boolean;
|
|
50
85
|
/**
|
|
51
86
|
* Get health check endpoint for connectivity testing
|
|
52
87
|
*/
|
|
53
|
-
|
|
88
|
+
static getProviderHealthEndpoint(providerName: string): string | null;
|
|
54
89
|
/**
|
|
55
90
|
* Provider-specific configuration checks
|
|
56
91
|
*/
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { logger } from "./logger.js";
|
|
6
6
|
import { AIProviderName, OpenAIModels, GoogleAIModels, AnthropicModels, BedrockModels, } from "../constants/enums.js";
|
|
7
|
-
import {
|
|
7
|
+
import { PROJECT_ID_FORMAT } from "./providerConfig.js";
|
|
8
8
|
import { basename } from "path";
|
|
9
9
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
10
10
|
import { DEFAULT_OLLAMA_MODEL } from "../providers/ollama/constants.js";
|
|
11
|
+
import { ProviderFactory } from "../factories/providerFactory.js";
|
|
11
12
|
export class ProviderHealthChecker {
|
|
12
13
|
static healthCache = new Map();
|
|
13
14
|
static DEFAULT_TIMEOUT = 5000; // 5 seconds
|
|
@@ -172,55 +173,36 @@ export class ProviderHealthChecker {
|
|
|
172
173
|
logger.debug("Vertex AI authentication check starting", {
|
|
173
174
|
providerName,
|
|
174
175
|
});
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// Method 2: Check individual environment variables
|
|
201
|
-
const hasIndividualAuth = !!(process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
|
|
202
|
-
process.env.GOOGLE_AUTH_PRIVATE_KEY);
|
|
203
|
-
logger.debug("Individual auth check", {
|
|
204
|
-
hasClientEmail: !!process.env.GOOGLE_AUTH_CLIENT_EMAIL,
|
|
205
|
-
hasPrivateKey: !!process.env.GOOGLE_AUTH_PRIVATE_KEY,
|
|
206
|
-
hasIndividualAuth,
|
|
207
|
-
});
|
|
208
|
-
// Vertex is valid if EITHER auth method works
|
|
209
|
-
const hasValidAuth = fileBasedAuthValid || hasIndividualAuth;
|
|
210
|
-
logger.debug("Vertex auth final result", {
|
|
211
|
-
fileBasedAuthValid,
|
|
212
|
-
hasIndividualAuth,
|
|
213
|
-
hasValidAuth,
|
|
214
|
-
});
|
|
176
|
+
// Derive auth-var acceptance from the vertex descriptor instead of a
|
|
177
|
+
// hand-maintained var list, so this stays in sync with the real
|
|
178
|
+
// gating logic (hasGoogleCredentials() in googleVertex/client.ts and
|
|
179
|
+
// googleVertex/utils.ts): extraRequired.every(...) ||
|
|
180
|
+
// extraRequiredFallbacks.some(...), the same pattern used by
|
|
181
|
+
// hasProviderEnvVars() (providerUtils.ts), setup.ts, and
|
|
182
|
+
// environmentManager.ts. The old two-method check here only
|
|
183
|
+
// recognized GOOGLE_APPLICATION_CREDENTIALS or the
|
|
184
|
+
// GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY pair — missing
|
|
185
|
+
// GOOGLE_SERVICE_ACCOUNT_KEY and, most importantly,
|
|
186
|
+
// GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, the descriptor's
|
|
187
|
+
// documented first-priority fallback. A user configured via that
|
|
188
|
+
// var alone had a fully working Vertex provider but was reported
|
|
189
|
+
// unhealthy here. Note: this is a presence-only check — it no
|
|
190
|
+
// longer verifies the GOOGLE_APPLICATION_CREDENTIALS file actually
|
|
191
|
+
// exists on disk (the old file-based method did via fs.access),
|
|
192
|
+
// matching the presence-only semantics already used by the other
|
|
193
|
+
// three callers of this pattern.
|
|
194
|
+
const descriptor = ProviderFactory.getDescriptor(AIProviderName.VERTEX);
|
|
195
|
+
const { extraRequired, extraRequiredFallbacks } = descriptor?.envVars ?? {};
|
|
196
|
+
const hasValidAuth = (extraRequired ?? []).every((v) => !!process.env[v]) ||
|
|
197
|
+
(extraRequiredFallbacks ?? []).some((v) => !!process.env[v]);
|
|
198
|
+
logger.debug("Vertex auth final result", { hasValidAuth });
|
|
215
199
|
if (hasValidAuth) {
|
|
216
200
|
healthStatus.hasApiKey = true;
|
|
217
|
-
logger.debug("Vertex auth SUCCESS"
|
|
218
|
-
authMethod: fileBasedAuthValid ? "file-based" : "individual-env-vars",
|
|
219
|
-
});
|
|
201
|
+
logger.debug("Vertex auth SUCCESS");
|
|
220
202
|
}
|
|
221
203
|
else {
|
|
222
204
|
healthStatus.hasApiKey = false;
|
|
223
|
-
healthStatus.configurationIssues.push(
|
|
205
|
+
healthStatus.configurationIssues.push("Vertex AI authentication not found: set GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY, or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
|
|
224
206
|
logger.debug("Vertex auth FAILED", {
|
|
225
207
|
reason: "No valid auth method found",
|
|
226
208
|
});
|
|
@@ -359,104 +341,115 @@ export class ProviderHealthChecker {
|
|
|
359
341
|
}
|
|
360
342
|
}
|
|
361
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Providers whose credential model can't be reduced to "every one of
|
|
346
|
+
* these exact env vars must be literally set" — the check in
|
|
347
|
+
* checkEnvironmentConfiguration() below ANDs every entry in the
|
|
348
|
+
* returned list together, which can't express Vertex's file-OR-
|
|
349
|
+
* individual-creds-OR-service-account auth, Bedrock's AWS SDK default
|
|
350
|
+
* provider chain (profile / IAM role, no env vars at all), or
|
|
351
|
+
* LiteLLM's documented zero-config local proxy. Their real requirement
|
|
352
|
+
* is validated by checkProviderSpecificConfig()'s dedicated per-provider
|
|
353
|
+
* checks instead — matches the original hand-written switch's
|
|
354
|
+
* deliberate `return []` for exactly these three. Naively deriving
|
|
355
|
+
* [apiKey, ...extraRequired] from the descriptor here (as for the other
|
|
356
|
+
* 27 providers) would make checkEnvironmentConfiguration() push a false
|
|
357
|
+
* "missing environment variables" issue — and therefore isHealthy=false
|
|
358
|
+
* — for legitimate fallback-based Vertex auth, AWS_PROFILE/IAM-role
|
|
359
|
+
* Bedrock auth, and unauthenticated local LiteLLM proxies. See
|
|
360
|
+
* hasProviderEnvVars() in providerUtils.ts for the equivalent OR-aware
|
|
361
|
+
* check used for auto-select gating.
|
|
362
|
+
*/
|
|
363
|
+
static ENV_CHECK_DELEGATED_TO_SPECIFIC_CONFIG = new Set([
|
|
364
|
+
AIProviderName.VERTEX,
|
|
365
|
+
AIProviderName.BEDROCK,
|
|
366
|
+
AIProviderName.LITELLM,
|
|
367
|
+
]);
|
|
362
368
|
/**
|
|
363
369
|
* Get required environment variables for a provider
|
|
364
370
|
*/
|
|
365
371
|
static getRequiredEnvironmentVariables(providerName) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
case AIProviderName.AZURE:
|
|
384
|
-
return ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"];
|
|
385
|
-
case AIProviderName.LITELLM:
|
|
386
|
-
return [];
|
|
387
|
-
case AIProviderName.OLLAMA:
|
|
388
|
-
return []; // Ollama typically doesn't require API keys
|
|
389
|
-
default:
|
|
390
|
-
return [];
|
|
391
|
-
}
|
|
372
|
+
// Resolve the descriptor FIRST so the delegation-Set check below is
|
|
373
|
+
// keyed on the canonical, alias-resolved `descriptor.name` — not the
|
|
374
|
+
// raw, possibly-aliased `providerName` argument. Checking the raw
|
|
375
|
+
// input here would let documented aliases (e.g. "googleVertex" for
|
|
376
|
+
// vertex, "aws" for bedrock) skip the delegation and fall through to
|
|
377
|
+
// the naive [apiKey, ...extraRequired] derivation below, reproducing
|
|
378
|
+
// the exact false "missing environment variables" regression this
|
|
379
|
+
// Set exists to prevent.
|
|
380
|
+
const descriptor = ProviderFactory.getDescriptor(providerName);
|
|
381
|
+
if (!descriptor) {
|
|
382
|
+
return [];
|
|
383
|
+
}
|
|
384
|
+
if (this.ENV_CHECK_DELEGATED_TO_SPECIFIC_CONFIG.has(descriptor.name)) {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
const { apiKey, extraRequired } = descriptor.envVars;
|
|
388
|
+
return [...(apiKey ? [apiKey] : []), ...(extraRequired ?? [])];
|
|
392
389
|
}
|
|
393
390
|
/**
|
|
394
|
-
* Get API key environment variable for a provider
|
|
391
|
+
* Get API key environment variable for a provider.
|
|
392
|
+
*
|
|
393
|
+
* Dead code at its sole call site today (checkApiKeyValidity() early-
|
|
394
|
+
* returns for VERTEX/OLLAMA/BEDROCK/LITELLM before reaching this), but
|
|
395
|
+
* now `static` (public) so a future direct caller is plausible — hence
|
|
396
|
+
* the two intentional diffs from the original hand-written switch are
|
|
397
|
+
* documented here rather than left silent:
|
|
398
|
+
* - vertex: old switch returned "GOOGLE_APPLICATION_CREDENTIALS"
|
|
399
|
+
* (one of several valid auth vars); the descriptor's `envVars.apiKey`
|
|
400
|
+
* is "GOOGLE_CLOUD_PROJECT_ID" instead — the actual identity var for
|
|
401
|
+
* the provider, not one specific credential-supply mechanism.
|
|
402
|
+
* - ollama: old switch returned "OLLAMA_BASE_URL"; the descriptor has
|
|
403
|
+
* no `envVars.apiKey` for ollama (it's a local, unauthenticated
|
|
404
|
+
* runtime), so this now returns "".
|
|
405
|
+
* Both are kept deliberately (semantically truer identity vars) rather
|
|
406
|
+
* than reproduced byte-for-byte from the old switch.
|
|
395
407
|
*/
|
|
396
408
|
static getApiKeyEnvironmentVariable(providerName) {
|
|
397
|
-
|
|
398
|
-
case AIProviderName.ANTHROPIC:
|
|
399
|
-
return "ANTHROPIC_API_KEY";
|
|
400
|
-
case AIProviderName.OPENAI:
|
|
401
|
-
return "OPENAI_API_KEY";
|
|
402
|
-
case AIProviderName.VERTEX:
|
|
403
|
-
return "GOOGLE_APPLICATION_CREDENTIALS";
|
|
404
|
-
case AIProviderName.GOOGLE_AI:
|
|
405
|
-
return "GOOGLE_AI_API_KEY";
|
|
406
|
-
case AIProviderName.BEDROCK:
|
|
407
|
-
return "AWS_ACCESS_KEY_ID";
|
|
408
|
-
case AIProviderName.AZURE:
|
|
409
|
-
return "AZURE_OPENAI_API_KEY";
|
|
410
|
-
case AIProviderName.LITELLM:
|
|
411
|
-
return "LITELLM_API_KEY";
|
|
412
|
-
case AIProviderName.OLLAMA:
|
|
413
|
-
return "OLLAMA_BASE_URL";
|
|
414
|
-
default:
|
|
415
|
-
return "";
|
|
416
|
-
}
|
|
409
|
+
return ProviderFactory.getDescriptor(providerName)?.envVars.apiKey ?? "";
|
|
417
410
|
}
|
|
418
411
|
/**
|
|
419
412
|
* Validate API key format for a provider
|
|
420
413
|
*/
|
|
421
414
|
static validateApiKeyFormat(providerName, apiKey) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
default:
|
|
442
|
-
return true; // Default to true for unknown providers
|
|
415
|
+
// Vertex's "API key" is really a credentials file path (or an inline
|
|
416
|
+
// JSON blob via GOOGLE_SERVICE_ACCOUNT_KEY) — never governed by the
|
|
417
|
+
// shared API_KEY_FORMATS regexes. Preserved exactly from the original
|
|
418
|
+
// switch even though checkApiKeyValidity() early-returns before ever
|
|
419
|
+
// reaching this call for Vertex today; kept correct for any other/
|
|
420
|
+
// future caller.
|
|
421
|
+
if (providerName === AIProviderName.VERTEX) {
|
|
422
|
+
return apiKey.endsWith(".json") || apiKey.includes("type");
|
|
423
|
+
}
|
|
424
|
+
const descriptor = ProviderFactory.getDescriptor(providerName);
|
|
425
|
+
if (!descriptor?.apiKeyFormatPattern) {
|
|
426
|
+
// Providers with no documented format (or local runtimes / providers
|
|
427
|
+
// usable with zero configuration) are accepted as-is — matches the
|
|
428
|
+
// old switch's per-provider `default: true`/no-format-check branches
|
|
429
|
+
// (ollama, litellm).
|
|
430
|
+
if (descriptor?.localRuntime || descriptor?.envVars.optional) {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
return apiKey.length > 0;
|
|
443
434
|
}
|
|
435
|
+
return descriptor.apiKeyFormatPattern.test(apiKey);
|
|
444
436
|
}
|
|
445
437
|
/**
|
|
446
438
|
* Get health check endpoint for connectivity testing
|
|
447
439
|
*/
|
|
448
440
|
static getProviderHealthEndpoint(providerName) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
441
|
+
const descriptor = ProviderFactory.getDescriptor(providerName);
|
|
442
|
+
if (descriptor?.healthCheck !== "models-probe") {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
// OpenAI/LiteLLM/Ollama each need a provider-specific URL builder that
|
|
446
|
+
// isn't pure descriptor data (LiteLLM/Ollama depend on the configured
|
|
447
|
+
// base URL) — keep the existing per-provider dispatch for the 3
|
|
448
|
+
// models-probe providers, now gated on the descriptor instead of a
|
|
449
|
+
// hardcoded name list.
|
|
450
|
+
switch (descriptor.name) {
|
|
452
451
|
case AIProviderName.OPENAI:
|
|
453
452
|
return "https://api.openai.com/v1/models";
|
|
454
|
-
case AIProviderName.GOOGLE_AI:
|
|
455
|
-
return null; // No public health endpoint
|
|
456
|
-
case AIProviderName.VERTEX:
|
|
457
|
-
return null; // Complex authentication required
|
|
458
|
-
case AIProviderName.BEDROCK:
|
|
459
|
-
return null; // AWS endpoints vary by region
|
|
460
453
|
case AIProviderName.LITELLM:
|
|
461
454
|
return this.getLiteLLMModelsUrl();
|
|
462
455
|
case AIProviderName.OLLAMA:
|