@juspay/neurolink 11.0.0 → 11.1.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.
Files changed (121) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/browser/neurolink.min.js +510 -510
  3. package/dist/cli/commands/setup.d.ts +8 -1
  4. package/dist/cli/commands/setup.js +62 -46
  5. package/dist/cli/factories/commandFactory.d.ts +1157 -1
  6. package/dist/cli/factories/commandFactory.js +20 -53
  7. package/dist/constants/networkErrorCodes.d.ts +14 -0
  8. package/dist/constants/networkErrorCodes.js +21 -0
  9. package/dist/factories/providerDescriptors.d.ts +18 -0
  10. package/dist/factories/providerDescriptors.js +546 -0
  11. package/dist/factories/providerFactory.d.ts +25 -14
  12. package/dist/factories/providerFactory.js +46 -23
  13. package/dist/factories/providerRegistry.js +31 -30
  14. package/dist/index.d.ts +10 -1
  15. package/dist/index.js +13 -3
  16. package/dist/lib/constants/networkErrorCodes.d.ts +14 -0
  17. package/dist/lib/constants/networkErrorCodes.js +22 -0
  18. package/dist/lib/factories/providerDescriptors.d.ts +18 -0
  19. package/dist/lib/factories/providerDescriptors.js +547 -0
  20. package/dist/lib/factories/providerFactory.d.ts +25 -14
  21. package/dist/lib/factories/providerFactory.js +46 -23
  22. package/dist/lib/factories/providerRegistry.js +31 -30
  23. package/dist/lib/index.d.ts +10 -1
  24. package/dist/lib/index.js +13 -3
  25. package/dist/lib/neurolink.js +25 -37
  26. package/dist/lib/processors/base/BaseFileProcessor.d.ts +17 -0
  27. package/dist/lib/processors/base/BaseFileProcessor.js +40 -0
  28. package/dist/lib/processors/document/OpenDocumentProcessor.js +12 -2
  29. package/dist/lib/providers/amazonBedrock/client.js +34 -16
  30. package/dist/lib/providers/anthropic/client.js +39 -34
  31. package/dist/lib/providers/azureOpenai.js +17 -15
  32. package/dist/lib/providers/cloudflare.js +12 -21
  33. package/dist/lib/providers/cohere.js +31 -25
  34. package/dist/lib/providers/deepseek.js +23 -26
  35. package/dist/lib/providers/fireworks.js +12 -21
  36. package/dist/lib/providers/googleAiStudio/client.d.ts +2 -0
  37. package/dist/lib/providers/googleAiStudio/client.js +39 -17
  38. package/dist/lib/providers/googleVertex/client.js +107 -99
  39. package/dist/lib/providers/groq.js +19 -22
  40. package/dist/lib/providers/huggingFace/client.js +26 -24
  41. package/dist/lib/providers/litellm/client.js +54 -39
  42. package/dist/lib/providers/llamaCpp.js +21 -18
  43. package/dist/lib/providers/lmStudio.js +22 -19
  44. package/dist/lib/providers/mistral.js +12 -24
  45. package/dist/lib/providers/nvidiaNim/client.js +37 -34
  46. package/dist/lib/providers/ollama/client.js +50 -35
  47. package/dist/lib/providers/openAI/client.js +37 -40
  48. package/dist/lib/providers/openRouter/client.js +53 -47
  49. package/dist/lib/providers/openaiChatCompletionsBase.js +40 -16
  50. package/dist/lib/providers/openaiCompatible/client.js +36 -32
  51. package/dist/lib/providers/perplexity.js +12 -21
  52. package/dist/lib/providers/togetherAi.js +12 -21
  53. package/dist/lib/providers/xai.js +17 -26
  54. package/dist/lib/proxy/proxyFetch.js +1 -9
  55. package/dist/lib/server/errors.d.ts +1 -1
  56. package/dist/lib/server/errors.js +2 -2
  57. package/dist/lib/server/index.d.ts +2 -2
  58. package/dist/lib/server/index.js +5 -3
  59. package/dist/lib/server/middleware/rateLimit.d.ts +0 -4
  60. package/dist/lib/server/middleware/rateLimit.js +0 -4
  61. package/dist/lib/types/cli.d.ts +2 -0
  62. package/dist/lib/types/errors.d.ts +35 -0
  63. package/dist/lib/types/providers.d.ts +79 -0
  64. package/dist/lib/utils/errorClassifier.d.ts +30 -0
  65. package/dist/lib/utils/errorClassifier.js +183 -0
  66. package/dist/lib/utils/fileDetector.js +6 -43
  67. package/dist/lib/utils/providerConfig.d.ts +16 -0
  68. package/dist/lib/utils/providerConfig.js +23 -0
  69. package/dist/lib/utils/providerHealth.d.ts +53 -12
  70. package/dist/lib/utils/providerHealth.js +125 -131
  71. package/dist/lib/utils/providerUtils.js +22 -64
  72. package/dist/neurolink.js +25 -37
  73. package/dist/processors/base/BaseFileProcessor.d.ts +17 -0
  74. package/dist/processors/base/BaseFileProcessor.js +40 -0
  75. package/dist/processors/document/OpenDocumentProcessor.js +12 -2
  76. package/dist/providers/amazonBedrock/client.js +34 -16
  77. package/dist/providers/amazonSagemaker.d.ts +1 -1
  78. package/dist/providers/anthropic/client.js +39 -34
  79. package/dist/providers/azureOpenai.js +17 -15
  80. package/dist/providers/cloudflare.js +12 -21
  81. package/dist/providers/cohere.js +31 -25
  82. package/dist/providers/deepseek.js +23 -26
  83. package/dist/providers/fireworks.js +12 -21
  84. package/dist/providers/googleAiStudio/client.d.ts +2 -0
  85. package/dist/providers/googleAiStudio/client.js +39 -17
  86. package/dist/providers/googleVertex/client.js +107 -99
  87. package/dist/providers/groq.js +19 -22
  88. package/dist/providers/huggingFace/client.js +26 -24
  89. package/dist/providers/litellm/client.js +54 -39
  90. package/dist/providers/llamaCpp.js +21 -18
  91. package/dist/providers/lmStudio.js +22 -19
  92. package/dist/providers/mistral.js +12 -24
  93. package/dist/providers/nvidiaNim/client.js +37 -34
  94. package/dist/providers/ollama/client.js +50 -35
  95. package/dist/providers/openAI/client.js +37 -40
  96. package/dist/providers/openRouter/client.js +53 -47
  97. package/dist/providers/openaiChatCompletionsBase.js +40 -16
  98. package/dist/providers/openaiCompatible/client.js +36 -32
  99. package/dist/providers/perplexity.js +12 -21
  100. package/dist/providers/sagemaker/language-model.d.ts +2 -2
  101. package/dist/providers/togetherAi.js +12 -21
  102. package/dist/providers/xai.js +17 -26
  103. package/dist/proxy/proxyFetch.js +1 -9
  104. package/dist/server/errors.d.ts +1 -1
  105. package/dist/server/errors.js +2 -2
  106. package/dist/server/index.d.ts +2 -2
  107. package/dist/server/index.js +5 -3
  108. package/dist/server/middleware/rateLimit.d.ts +0 -4
  109. package/dist/server/middleware/rateLimit.js +0 -4
  110. package/dist/types/cli.d.ts +2 -0
  111. package/dist/types/errors.d.ts +35 -0
  112. package/dist/types/providers.d.ts +79 -0
  113. package/dist/utils/errorClassifier.d.ts +30 -0
  114. package/dist/utils/errorClassifier.js +182 -0
  115. package/dist/utils/fileDetector.js +6 -43
  116. package/dist/utils/providerConfig.d.ts +16 -0
  117. package/dist/utils/providerConfig.js +23 -0
  118. package/dist/utils/providerHealth.d.ts +53 -12
  119. package/dist/utils/providerHealth.js +125 -131
  120. package/dist/utils/providerUtils.js +22 -64
  121. package/package.json +15 -70
@@ -47,6 +47,41 @@ export declare class RateLimitError extends ProviderError {
47
47
  export declare class InvalidModelError extends ProviderError {
48
48
  constructor(message: string, provider?: string);
49
49
  }
50
+ /**
51
+ * Everything a ProviderErrorRule's `match`/`message` functions can inspect
52
+ * about a raw thrown error, pre-extracted once so every rule doesn't
53
+ * re-derive the same duck-typed fields.
54
+ */
55
+ export type ProviderErrorContext = {
56
+ /** The raw thrown value, for rules that need custom inspection beyond the extracted fields. */
57
+ error: unknown;
58
+ /** `.message` off the raw error, or "Unknown error" if absent/non-string. */
59
+ message: string;
60
+ /** HTTP status code, duck-typed from `.statusCode` / `.status`. */
61
+ statusCode: number | undefined;
62
+ /** `.name` off the raw error (e.g. AWS SDK exception names like "ThrottlingException"). */
63
+ errorName: string | undefined;
64
+ /** `.code` off the raw error (e.g. AWS SDK / Node network error codes). */
65
+ errorCode: string | undefined;
66
+ /** Provider key passed to classifyProviderError (e.g. "mistral", "vertex"). */
67
+ provider: string;
68
+ /** Model name in effect for this call, when the caller has one available. */
69
+ modelName: string | undefined;
70
+ };
71
+ /**
72
+ * One row of a provider's error-classification table. Rules are tried in
73
+ * array order; the first `match` to return true wins. `errorClass` must be
74
+ * `ProviderError` or one of its subclasses (AuthenticationError,
75
+ * RateLimitError, InvalidModelError, NetworkError, ...) sharing its
76
+ * `(message, provider?)` constructor shape. `message` can be a static
77
+ * string or a function of the context, for providers that need to
78
+ * interpolate a model name, a scraped retry-delay, or an AWS error code.
79
+ */
80
+ export type ProviderErrorRule = {
81
+ match: (ctx: ProviderErrorContext) => boolean;
82
+ errorClass: new (message: string, provider?: string) => ProviderError;
83
+ message: string | ((ctx: ProviderErrorContext) => string);
84
+ };
50
85
  /**
51
86
  * Base class for OAuth-specific errors
52
87
  */
@@ -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,82 @@ 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). Each entry is either a single env var name (satisfied alone) or a nested array of names that must ALL be present together (e.g. Vertex's GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY pair, which is only valid as a pair). Evaluate with `satisfiesFallbacks()` (providerConfig.ts) rather than re-deriving this logic at each call site. */
1718
+ extraRequiredFallbacks?: readonly (string | 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;
1744
+ /**
1745
+ * True when this provider's credentials are resolved by an external chain
1746
+ * or its own config validator rather than by plain env-var presence, so
1747
+ * its required-env-vars can't be expressed as "every one of these exact
1748
+ * names must be literally set". Examples: Vertex accepts a service-account
1749
+ * file OR individual client-email/private-key fields OR a base64 key
1750
+ * (an OR, not an AND, of auth paths); Bedrock falls back to the AWS SDK's
1751
+ * own default credential chain (shared profile, IAM role) with no env
1752
+ * vars required at all; LiteLLM is a documented zero-config local proxy.
1753
+ * `ProviderHealthChecker.getRequiredEnvironmentVariables()` returns `[]`
1754
+ * for these providers and defers to `checkProviderSpecificConfig()`'s
1755
+ * dedicated per-provider check instead of deriving a flat AND-list from
1756
+ * `envVars`.
1757
+ */
1758
+ credentialsResolvedExternally?: boolean;
1680
1759
  };
1681
1760
  /** Minimal NeuroLink-like instance accepted by the image generation service. */
1682
1761
  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,183 @@
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
+ import { TRANSIENT_NETWORK_CODES } from "../constants/networkErrorCodes.js";
17
+ import { redactUrlsInText } from "./logSanitize.js";
18
+ /** Bounded walk depth for `.cause` chains — matches the precedent in
19
+ * `proxy/proxyFetch.ts`'s `isTransientNetworkError`. Guards against
20
+ * pathological/cyclic `.cause` chains hanging classification. */
21
+ const MAX_CAUSE_DEPTH = 5;
22
+ /**
23
+ * Walk `error.cause` up to `MAX_CAUSE_DEPTH` links, guarded by a seen-set so
24
+ * a cyclic chain (`a.cause === a`, or a longer cycle) terminates instead of
25
+ * looping. Node's native `fetch` (undici) throws `TypeError: fetch failed`
26
+ * with the real transport error nested under `.cause` — sometimes another
27
+ * level deep (e.g. a SocketError inside a ConnectTimeoutError) — so a
28
+ * classifier that only reads the outer error's `.message`/`.code` never
29
+ * sees it.
30
+ */
31
+ function collectCauseChain(error) {
32
+ const chain = [];
33
+ const seen = new Set();
34
+ let current = error;
35
+ while (current &&
36
+ typeof current === "object" &&
37
+ !seen.has(current) &&
38
+ chain.length < MAX_CAUSE_DEPTH) {
39
+ seen.add(current);
40
+ const record = current;
41
+ chain.push(record);
42
+ current = record.cause;
43
+ }
44
+ return chain;
45
+ }
46
+ function firstString(chain, key) {
47
+ for (const record of chain) {
48
+ if (typeof record[key] === "string") {
49
+ return record[key];
50
+ }
51
+ }
52
+ return undefined;
53
+ }
54
+ function buildErrorContext(error, provider, modelName) {
55
+ const chain = collectCauseChain(error);
56
+ const top = chain[0];
57
+ const topMessage = typeof top?.message === "string"
58
+ ? top.message
59
+ : error instanceof Error
60
+ ? error.message
61
+ : "Unknown error";
62
+ // Compose (never replace) the message: append the deepest cause's message
63
+ // when it differs from the top, so existing rules matching the outer text
64
+ // (e.g. "rate limit", "model not found") keep matching, while the real
65
+ // transport failure buried in .cause becomes visible to rules that need
66
+ // it (e.g. a nested "ECONNREFUSED").
67
+ // The nested message is redacted before it is composed in: an undici cause
68
+ // carries the full request URL, so a presigned token would otherwise reach
69
+ // a client-facing error message through this path. Only the nested text is
70
+ // scrubbed — the provider's own top-level message is left alone, since
71
+ // several providers deliberately name their base URL in it.
72
+ const deepest = chain[chain.length - 1];
73
+ const deepestMessage = typeof deepest?.message === "string"
74
+ ? redactUrlsInText(deepest.message)
75
+ : undefined;
76
+ const message = deepestMessage && deepestMessage !== topMessage
77
+ ? `${topMessage}: ${deepestMessage}`
78
+ : topMessage;
79
+ // errorCode/errorName/statusCode: prefer the outer error's own value,
80
+ // falling back to the first cause in the chain that has one.
81
+ let statusCode;
82
+ for (const record of chain) {
83
+ statusCode = duckTypedStatusCode(record);
84
+ if (statusCode !== undefined) {
85
+ break;
86
+ }
87
+ }
88
+ return {
89
+ error,
90
+ message,
91
+ statusCode,
92
+ errorName: firstString(chain, "name"),
93
+ errorCode: firstString(chain, "code"),
94
+ provider,
95
+ modelName,
96
+ };
97
+ }
98
+ /**
99
+ * Classify a raw provider error into a NeuroLink `ProviderError` subclass.
100
+ * `rules` are tried in order; the first match wins. `TimeoutError` is
101
+ * always handled first, ahead of any rule table — every provider treated
102
+ * it identically before this change, so it is not made overridable.
103
+ */
104
+ export function classifyProviderError(error, rules, provider, modelName) {
105
+ if (error instanceof TimeoutError) {
106
+ return new NetworkError(`Request timed out: ${error.message}`, provider);
107
+ }
108
+ const ctx = buildErrorContext(error, provider, modelName);
109
+ const rule = rules.find((r) => r.match(ctx));
110
+ if (!rule) {
111
+ return new ProviderError(`${provider} error: ${ctx.message}`, provider);
112
+ }
113
+ const message = typeof rule.message === "function" ? rule.message(ctx) : rule.message;
114
+ return new rule.errorClass(message, provider);
115
+ }
116
+ /**
117
+ * Generic fallback rule table covering the five categories every
118
+ * OpenAI-compatible provider already hand-rolled near-identically:
119
+ * auth (401), rate limit (429), model-not-found (404), network/connection
120
+ * errors, and 5xx server errors. Providers with a provider-specific auth
121
+ * message (naming the exact env var) prepend one override rule and spread
122
+ * this table after it — see errorClassifier usage in any migrated
123
+ * provider's formatProviderError for the pattern.
124
+ */
125
+ export const DEFAULT_ERROR_RULES = [
126
+ {
127
+ match: (ctx) => ctx.statusCode === 401 ||
128
+ /API_KEY_INVALID|Invalid API key|Unauthorized|invalid_api_key/i.test(ctx.message),
129
+ errorClass: AuthenticationError,
130
+ message: (ctx) => `Invalid ${ctx.provider} API key. Please check your credentials.`,
131
+ },
132
+ {
133
+ match: (ctx) => ctx.statusCode === 429 || /rate limit/i.test(ctx.message),
134
+ errorClass: RateLimitError,
135
+ message: (ctx) => `${ctx.provider} rate limit exceeded. Please try again later.`,
136
+ },
137
+ {
138
+ match: (ctx) => ctx.statusCode === 404 ||
139
+ /model_not_found|model not found/i.test(ctx.message),
140
+ errorClass: InvalidModelError,
141
+ message: (ctx) => ctx.modelName
142
+ ? `${ctx.provider} model '${ctx.modelName}' not found.`
143
+ : `${ctx.provider} model not found.`,
144
+ },
145
+ {
146
+ // Message regex covers providers/SDKs that surface a code as text
147
+ // (e.g. AWS SDK wrapping "ECONNRESET" into its own message). errorCode
148
+ // covers undici's native fetch(), which wraps transport failures as
149
+ // `TypeError: fetch failed` and puts the *structured* code
150
+ // (ECONNREFUSED, UND_ERR_SOCKET, ...) on a nested `.cause` rather than
151
+ // in any message text — buildErrorContext's cause walk surfaces it here.
152
+ match: (ctx) => /ECONNRESET|ENOTFOUND|ECONNREFUSED|ETIMEDOUT|network|connection/i.test(ctx.message) ||
153
+ (ctx.errorCode !== undefined &&
154
+ TRANSIENT_NETWORK_CODES.has(ctx.errorCode)),
155
+ errorClass: NetworkError,
156
+ message: (ctx) => `Connection error: ${ctx.message}`,
157
+ },
158
+ {
159
+ // Batch J Task 3: the old `/\b5\d\d\b/` matched ANY bare 3-digit number
160
+ // in [500,599) anywhere in the message — e.g. "max_tokens (500) exceeds
161
+ // model limit" — with no relation to an actual HTTP status. Tightened to
162
+ // require the number sit in a status-shaped context: immediately next
163
+ // to "error" (either order) or "status"/"status code" (a common HTTP
164
+ // client wrapper phrase, e.g. axios's "Request failed with status code
165
+ // 500"), with a bounded gap so unrelated digits nearby can't bridge the
166
+ // match — or a named 5xx phrase that needs no digit at all ("bad
167
+ // gateway", "service unavailable", "gateway timeout", "server error",
168
+ // which already covers "... Internal Server Error"). This changes the
169
+ // MATCHED MESSAGE TEXT only, never the classified class: when no rule
170
+ // matches, `classifyProviderError`'s fallback also returns
171
+ // `ProviderError` (see above) — the same class this rule assigns — so
172
+ // narrowing this regex can only move a message between "${provider}
173
+ // server error: ..." and "${provider} error: ...", never between error
174
+ // classes.
175
+ match: (ctx) => (ctx.statusCode !== undefined &&
176
+ ctx.statusCode >= 500 &&
177
+ ctx.statusCode <= 599) ||
178
+ /server error|bad gateway|service unavailable|gateway timeout|\berror\b\D{0,12}\b5\d\d\b|\b5\d\d\b\D{0,12}\berror\b|\bstatus(?:\s*code)?\b\D{0,12}\b5\d\d\b/i.test(ctx.message),
179
+ errorClass: ProviderError,
180
+ message: (ctx) => `${ctx.provider} server error: ${ctx.message}`,
181
+ },
182
+ ];
183
+ //# sourceMappingURL=errorClassifier.js.map
@@ -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
- }, { maxRetries, retryDelay });
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
@@ -70,6 +70,22 @@ export declare function getProviderModel(envVar: string, defaultModel: string):
70
70
  * @returns True if one of the credentials is available
71
71
  */
72
72
  export declare function hasProviderCredentials(envVars: string[]): boolean;
73
+ /**
74
+ * Evaluates a `ProviderDescriptor.envVars.extraRequiredFallbacks`-shaped
75
+ * list against an env-var source. Each entry is either a single env var
76
+ * name (satisfied on its own) or a nested array of names that must ALL be
77
+ * present together (e.g. Vertex's GOOGLE_AUTH_CLIENT_EMAIL +
78
+ * GOOGLE_AUTH_PRIVATE_KEY pair, which is only valid auth as a pair).
79
+ * Returns true when at least one entry is satisfied. The single evaluation
80
+ * site for this shape — every consumer (providerUtils.ts, providerHealth.ts,
81
+ * setup.ts, environmentManager.ts) must call this instead of re-deriving the
82
+ * same `.some()`/`.every()` logic, so they can't drift out of sync with each
83
+ * other or with the real auth gate (hasGoogleCredentials()).
84
+ * @param env Explicit env-var source (`process.env`, or a parsed .env file) —
85
+ * never hardcoded, so callers checking a file's contents (not the live
86
+ * process env) can reuse this too.
87
+ */
88
+ export declare function satisfiesFallbacks(fallbacks: readonly (string | readonly string[])[] | undefined, env: Record<string, string | undefined>): boolean;
73
89
  /**
74
90
  * Creates Anthropic provider configuration
75
91
  * Supports both API key and OAuth authentication methods
@@ -185,6 +185,29 @@ export function getProviderModel(envVar, defaultModel) {
185
185
  export function hasProviderCredentials(envVars) {
186
186
  return envVars.some((envVar) => !!process.env[envVar]);
187
187
  }
188
+ /**
189
+ * Evaluates a `ProviderDescriptor.envVars.extraRequiredFallbacks`-shaped
190
+ * list against an env-var source. Each entry is either a single env var
191
+ * name (satisfied on its own) or a nested array of names that must ALL be
192
+ * present together (e.g. Vertex's GOOGLE_AUTH_CLIENT_EMAIL +
193
+ * GOOGLE_AUTH_PRIVATE_KEY pair, which is only valid auth as a pair).
194
+ * Returns true when at least one entry is satisfied. The single evaluation
195
+ * site for this shape — every consumer (providerUtils.ts, providerHealth.ts,
196
+ * setup.ts, environmentManager.ts) must call this instead of re-deriving the
197
+ * same `.some()`/`.every()` logic, so they can't drift out of sync with each
198
+ * other or with the real auth gate (hasGoogleCredentials()).
199
+ * @param env Explicit env-var source (`process.env`, or a parsed .env file) —
200
+ * never hardcoded, so callers checking a file's contents (not the live
201
+ * process env) can reuse this too.
202
+ */
203
+ export function satisfiesFallbacks(fallbacks, env) {
204
+ if (!fallbacks) {
205
+ return false;
206
+ }
207
+ return fallbacks.some((entry) => typeof entry === "string"
208
+ ? !!env[entry]
209
+ : entry.every((name) => !!env[name]));
210
+ }
188
211
  // =============================================================================
189
212
  // PROVIDER-SPECIFIC CONFIGURATION CREATORS
190
213
  // =============================================================================
@@ -36,21 +36,54 @@ export declare class ProviderHealthChecker {
36
36
  */
37
37
  private static checkModelAvailability;
38
38
  /**
39
- * Get required environment variables for a provider
40
- */
41
- private static getRequiredEnvironmentVariables;
42
- /**
43
- * Get API key environment variable for a provider
44
- */
45
- private static getApiKeyEnvironmentVariable;
39
+ * Get required environment variables for a provider.
40
+ *
41
+ * Returns `[]` for providers with `descriptor.credentialsResolvedExternally
42
+ * === true` (Vertex, Bedrock, LiteLLM) — the check in
43
+ * checkEnvironmentConfiguration() below ANDs every entry in the returned
44
+ * list together, which can't express Vertex's file-OR-individual-creds-
45
+ * OR-service-account auth, Bedrock's AWS SDK default provider chain
46
+ * (profile / IAM role, no env vars at all), or LiteLLM's documented
47
+ * zero-config local proxy. Their real requirement is validated by
48
+ * checkProviderSpecificConfig()'s dedicated per-provider checks instead.
49
+ * Naively deriving [apiKey, ...extraRequired] from the descriptor for
50
+ * these three (as for the other 27 providers) would make
51
+ * checkEnvironmentConfiguration() push a false "missing environment
52
+ * variables" issue — and therefore isHealthy=false — for legitimate
53
+ * fallback-based Vertex auth, AWS_PROFILE/IAM-role Bedrock auth, and
54
+ * unauthenticated local LiteLLM proxies. See hasProviderEnvVars() in
55
+ * providerUtils.ts for the equivalent OR-aware check used for
56
+ * auto-select gating. See `ProviderDescriptor.credentialsResolvedExternally`
57
+ * (types/providers.ts) for the field's full documentation.
58
+ */
59
+ static getRequiredEnvironmentVariables(providerName: string): string[];
60
+ /**
61
+ * Get API key environment variable for a provider.
62
+ *
63
+ * Dead code at its sole call site today (checkApiKeyValidity() early-
64
+ * returns for VERTEX/OLLAMA/BEDROCK/LITELLM before reaching this), but
65
+ * now `static` (public) so a future direct caller is plausible — hence
66
+ * the two intentional diffs from the original hand-written switch are
67
+ * documented here rather than left silent:
68
+ * - vertex: old switch returned "GOOGLE_APPLICATION_CREDENTIALS"
69
+ * (one of several valid auth vars); the descriptor's `envVars.apiKey`
70
+ * is "GOOGLE_CLOUD_PROJECT_ID" instead — the actual identity var for
71
+ * the provider, not one specific credential-supply mechanism.
72
+ * - ollama: old switch returned "OLLAMA_BASE_URL"; the descriptor has
73
+ * no `envVars.apiKey` for ollama (it's a local, unauthenticated
74
+ * runtime), so this now returns "".
75
+ * Both are kept deliberately (semantically truer identity vars) rather
76
+ * than reproduced byte-for-byte from the old switch.
77
+ */
78
+ static getApiKeyEnvironmentVariable(providerName: string): string;
46
79
  /**
47
80
  * Validate API key format for a provider
48
81
  */
49
- private static validateApiKeyFormat;
82
+ static validateApiKeyFormat(providerName: string, apiKey: string): boolean;
50
83
  /**
51
84
  * Get health check endpoint for connectivity testing
52
85
  */
53
- private static getProviderHealthEndpoint;
86
+ static getProviderHealthEndpoint(providerName: string): string | null;
54
87
  /**
55
88
  * Provider-specific configuration checks
56
89
  */
@@ -72,9 +105,17 @@ export declare class ProviderHealthChecker {
72
105
  */
73
106
  private static checkGoogleApplicationCredentials;
74
107
  /**
75
- * Check individual Google credentials
76
- */
77
- private static checkIndividualGoogleCredentials;
108
+ * Check Vertex's non-file auth fallbacks (GOOGLE_APPLICATION_CREDENTIALS_
109
+ * NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY, or the GOOGLE_AUTH_CLIENT_EMAIL +
110
+ * GOOGLE_AUTH_PRIVATE_KEY pair) via the descriptor's extraRequiredFallbacks
111
+ * instead of a hand-maintained env-var list, so this stays in sync with
112
+ * the real gating logic (hasGoogleCredentials()) the same way
113
+ * checkApiKeyValidity()'s vertex branch does. The previous hand-rolled
114
+ * version only recognized GOOGLE_SERVICE_ACCOUNT_KEY or the email+key
115
+ * pair — missing GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, the
116
+ * descriptor's documented first-priority fallback.
117
+ */
118
+ private static checkExtraRequiredFallbackCredentials;
78
119
  /**
79
120
  * Check AWS Bedrock configuration
80
121
  */