@juspay/neurolink 11.1.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.
- package/CHANGELOG.md +9 -0
- package/dist/browser/neurolink.min.js +390 -390
- package/dist/cli/commands/setup.d.ts +4 -1
- package/dist/cli/commands/setup.js +44 -14
- package/dist/constants/networkErrorCodes.d.ts +14 -0
- package/dist/constants/networkErrorCodes.js +21 -0
- package/dist/factories/providerDescriptors.js +13 -3
- package/dist/lib/constants/networkErrorCodes.d.ts +14 -0
- package/dist/lib/constants/networkErrorCodes.js +22 -0
- package/dist/lib/factories/providerDescriptors.js +13 -3
- package/dist/lib/processors/base/BaseFileProcessor.d.ts +17 -0
- package/dist/lib/processors/base/BaseFileProcessor.js +40 -0
- package/dist/lib/processors/document/OpenDocumentProcessor.js +12 -2
- package/dist/lib/proxy/proxyFetch.js +1 -9
- package/dist/lib/types/cli.d.ts +2 -0
- package/dist/lib/types/providers.d.ts +17 -2
- package/dist/lib/utils/errorClassifier.js +100 -11
- package/dist/lib/utils/providerConfig.d.ts +16 -0
- package/dist/lib/utils/providerConfig.js +23 -0
- package/dist/lib/utils/providerHealth.d.ts +30 -24
- package/dist/lib/utils/providerHealth.js +42 -41
- package/dist/lib/utils/providerUtils.js +2 -2
- package/dist/processors/base/BaseFileProcessor.d.ts +17 -0
- package/dist/processors/base/BaseFileProcessor.js +40 -0
- package/dist/processors/document/OpenDocumentProcessor.js +12 -2
- package/dist/proxy/proxyFetch.js +1 -9
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/providers.d.ts +17 -2
- package/dist/utils/errorClassifier.js +100 -11
- package/dist/utils/providerConfig.d.ts +16 -0
- package/dist/utils/providerConfig.js +23 -0
- package/dist/utils/providerHealth.d.ts +30 -24
- package/dist/utils/providerHealth.js +42 -41
- package/dist/utils/providerUtils.js +2 -2
- package/package.json +1 -1
|
@@ -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,27 +36,25 @@ export declare class ProviderHealthChecker {
|
|
|
36
36
|
*/
|
|
37
37
|
private static checkModelAvailability;
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* [apiKey, ...extraRequired] from the descriptor
|
|
50
|
-
* 27 providers) would make
|
|
51
|
-
*
|
|
52
|
-
* —
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* check used for
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get required environment variables for a provider
|
|
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.
|
|
60
58
|
*/
|
|
61
59
|
static getRequiredEnvironmentVariables(providerName: string): string[];
|
|
62
60
|
/**
|
|
@@ -107,9 +105,17 @@ export declare class ProviderHealthChecker {
|
|
|
107
105
|
*/
|
|
108
106
|
private static checkGoogleApplicationCredentials;
|
|
109
107
|
/**
|
|
110
|
-
* Check
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
113
119
|
/**
|
|
114
120
|
* Check AWS Bedrock configuration
|
|
115
121
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { logger } from "./logger.js";
|
|
6
6
|
import { AIProviderName, OpenAIModels, GoogleAIModels, AnthropicModels, BedrockModels, } from "../constants/enums.js";
|
|
7
|
-
import { PROJECT_ID_FORMAT } from "./providerConfig.js";
|
|
7
|
+
import { PROJECT_ID_FORMAT, satisfiesFallbacks } 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";
|
|
@@ -194,7 +194,7 @@ export class ProviderHealthChecker {
|
|
|
194
194
|
const descriptor = ProviderFactory.getDescriptor(AIProviderName.VERTEX);
|
|
195
195
|
const { extraRequired, extraRequiredFallbacks } = descriptor?.envVars ?? {};
|
|
196
196
|
const hasValidAuth = (extraRequired ?? []).every((v) => !!process.env[v]) ||
|
|
197
|
-
(extraRequiredFallbacks
|
|
197
|
+
satisfiesFallbacks(extraRequiredFallbacks, process.env);
|
|
198
198
|
logger.debug("Vertex auth final result", { hasValidAuth });
|
|
199
199
|
if (hasValidAuth) {
|
|
200
200
|
healthStatus.hasApiKey = true;
|
|
@@ -342,46 +342,40 @@ export class ProviderHealthChecker {
|
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* [apiKey, ...extraRequired] from the descriptor
|
|
356
|
-
* 27 providers) would make
|
|
357
|
-
*
|
|
358
|
-
* —
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* check used for
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
AIProviderName.VERTEX,
|
|
365
|
-
AIProviderName.BEDROCK,
|
|
366
|
-
AIProviderName.LITELLM,
|
|
367
|
-
]);
|
|
368
|
-
/**
|
|
369
|
-
* Get required environment variables for a provider
|
|
345
|
+
* Get required environment variables for a provider.
|
|
346
|
+
*
|
|
347
|
+
* Returns `[]` for providers with `descriptor.credentialsResolvedExternally
|
|
348
|
+
* === true` (Vertex, Bedrock, LiteLLM) — the check in
|
|
349
|
+
* checkEnvironmentConfiguration() below ANDs every entry in the returned
|
|
350
|
+
* list together, which can't express Vertex's file-OR-individual-creds-
|
|
351
|
+
* OR-service-account auth, Bedrock's AWS SDK default provider chain
|
|
352
|
+
* (profile / IAM role, no env vars at all), or LiteLLM's documented
|
|
353
|
+
* zero-config local proxy. Their real requirement is validated by
|
|
354
|
+
* checkProviderSpecificConfig()'s dedicated per-provider checks instead.
|
|
355
|
+
* Naively deriving [apiKey, ...extraRequired] from the descriptor for
|
|
356
|
+
* these three (as for the other 27 providers) would make
|
|
357
|
+
* checkEnvironmentConfiguration() push a false "missing environment
|
|
358
|
+
* variables" issue — and therefore isHealthy=false — for legitimate
|
|
359
|
+
* fallback-based Vertex auth, AWS_PROFILE/IAM-role Bedrock auth, and
|
|
360
|
+
* unauthenticated local LiteLLM proxies. See hasProviderEnvVars() in
|
|
361
|
+
* providerUtils.ts for the equivalent OR-aware check used for
|
|
362
|
+
* auto-select gating. See `ProviderDescriptor.credentialsResolvedExternally`
|
|
363
|
+
* (types/providers.ts) for the field's full documentation.
|
|
370
364
|
*/
|
|
371
365
|
static getRequiredEnvironmentVariables(providerName) {
|
|
372
|
-
// Resolve the descriptor FIRST so the
|
|
373
|
-
//
|
|
374
|
-
// raw, possibly-aliased `providerName` argument. Checking the
|
|
375
|
-
// input here would let documented aliases (e.g. "googleVertex" for
|
|
366
|
+
// Resolve the descriptor FIRST so the field check below is keyed on
|
|
367
|
+
// the canonical, alias-resolved `descriptor.credentialsResolvedExternally`
|
|
368
|
+
// — not the raw, possibly-aliased `providerName` argument. Checking the
|
|
369
|
+
// raw input here would let documented aliases (e.g. "googleVertex" for
|
|
376
370
|
// vertex, "aws" for bedrock) skip the delegation and fall through to
|
|
377
371
|
// the naive [apiKey, ...extraRequired] derivation below, reproducing
|
|
378
372
|
// the exact false "missing environment variables" regression this
|
|
379
|
-
//
|
|
373
|
+
// field exists to prevent.
|
|
380
374
|
const descriptor = ProviderFactory.getDescriptor(providerName);
|
|
381
375
|
if (!descriptor) {
|
|
382
376
|
return [];
|
|
383
377
|
}
|
|
384
|
-
if (
|
|
378
|
+
if (descriptor.credentialsResolvedExternally) {
|
|
385
379
|
return [];
|
|
386
380
|
}
|
|
387
381
|
const { apiKey, extraRequired } = descriptor.envVars;
|
|
@@ -518,11 +512,11 @@ export class ProviderHealthChecker {
|
|
|
518
512
|
hasValidAuth = await this.checkGoogleApplicationCredentials(healthStatus);
|
|
519
513
|
}
|
|
520
514
|
if (!hasValidAuth) {
|
|
521
|
-
hasValidAuth = this.
|
|
515
|
+
hasValidAuth = this.checkExtraRequiredFallbackCredentials(healthStatus);
|
|
522
516
|
}
|
|
523
517
|
if (!hasValidAuth) {
|
|
524
518
|
healthStatus.configurationIssues.push("Google Cloud authentication not configured or credentials file missing");
|
|
525
|
-
healthStatus.recommendations.push("Set either GOOGLE_APPLICATION_CREDENTIALS (valid file path), GOOGLE_SERVICE_ACCOUNT_KEY (base64), or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
|
|
519
|
+
healthStatus.recommendations.push("Set either GOOGLE_APPLICATION_CREDENTIALS (valid file path), GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY (base64), or both GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY");
|
|
526
520
|
}
|
|
527
521
|
return hasValidAuth;
|
|
528
522
|
}
|
|
@@ -554,13 +548,20 @@ export class ProviderHealthChecker {
|
|
|
554
548
|
}
|
|
555
549
|
}
|
|
556
550
|
/**
|
|
557
|
-
* Check
|
|
551
|
+
* Check Vertex's non-file auth fallbacks (GOOGLE_APPLICATION_CREDENTIALS_
|
|
552
|
+
* NEUROLINK, GOOGLE_SERVICE_ACCOUNT_KEY, or the GOOGLE_AUTH_CLIENT_EMAIL +
|
|
553
|
+
* GOOGLE_AUTH_PRIVATE_KEY pair) via the descriptor's extraRequiredFallbacks
|
|
554
|
+
* instead of a hand-maintained env-var list, so this stays in sync with
|
|
555
|
+
* the real gating logic (hasGoogleCredentials()) the same way
|
|
556
|
+
* checkApiKeyValidity()'s vertex branch does. The previous hand-rolled
|
|
557
|
+
* version only recognized GOOGLE_SERVICE_ACCOUNT_KEY or the email+key
|
|
558
|
+
* pair — missing GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK, the
|
|
559
|
+
* descriptor's documented first-priority fallback.
|
|
558
560
|
*/
|
|
559
|
-
static
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
if (hasServiceAccountKey || hasIndividualCredentials) {
|
|
561
|
+
static checkExtraRequiredFallbackCredentials(healthStatus) {
|
|
562
|
+
const descriptor = ProviderFactory.getDescriptor(AIProviderName.VERTEX);
|
|
563
|
+
const hasValidAuth = satisfiesFallbacks(descriptor?.envVars.extraRequiredFallbacks, process.env);
|
|
564
|
+
if (hasValidAuth) {
|
|
564
565
|
healthStatus.hasApiKey = true;
|
|
565
566
|
return true;
|
|
566
567
|
}
|
|
@@ -6,7 +6,7 @@ import { AIProviderFactory } from "../core/factory.js";
|
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
7
|
import { AIProviderName } from "../constants/enums.js";
|
|
8
8
|
import { ProviderHealthChecker } from "./providerHealth.js";
|
|
9
|
-
import { API_KEY_FORMATS, API_KEY_LENGTHS, PROJECT_ID_FORMAT, } from "./providerConfig.js";
|
|
9
|
+
import { API_KEY_FORMATS, API_KEY_LENGTHS, PROJECT_ID_FORMAT, satisfiesFallbacks, } from "./providerConfig.js";
|
|
10
10
|
import { DEFAULT_OLLAMA_MODEL } from "../providers/ollama/constants.js";
|
|
11
11
|
import { ProviderFactory } from "../factories/providerFactory.js";
|
|
12
12
|
import { PROVIDER_DESCRIPTORS } from "../factories/providerDescriptors.js";
|
|
@@ -340,7 +340,7 @@ export function hasProviderEnvVars(provider) {
|
|
|
340
340
|
return false;
|
|
341
341
|
}
|
|
342
342
|
return ((extraRequired ?? []).every((v) => !!process.env[v]) ||
|
|
343
|
-
(extraRequiredFallbacks
|
|
343
|
+
satisfiesFallbacks(extraRequiredFallbacks, process.env));
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
346
346
|
* Get available provider names
|
|
@@ -45,6 +45,23 @@
|
|
|
45
45
|
*/
|
|
46
46
|
import { FileErrorCode } from "../errors/index.js";
|
|
47
47
|
import type { BatchProcessingSummary, FileInfo, FileProcessingError, ProcessorFileProcessingResult, FileProcessorConfig, ProcessorOperationResult, ProcessedFileBase, ProcessOptions } from "../../types/index.js";
|
|
48
|
+
/**
|
|
49
|
+
* Marker on the error raised when a processor refuses content for exceeding
|
|
50
|
+
* the size limit *after* the download — a ZIP entry that expands past the
|
|
51
|
+
* bound, say.
|
|
52
|
+
*
|
|
53
|
+
* Separate from DOWNLOAD_TOO_LARGE because the two fire at different stages,
|
|
54
|
+
* but it exists for the same reason. Without it a bound that fires reaches
|
|
55
|
+
* `buildProcessedResultWithResult` as a plain Error and becomes
|
|
56
|
+
* PROCESSING_FAILED, which is `retryable: true` — so a caller retries a
|
|
57
|
+
* deterministic refusal three times and gets the identical answer each time.
|
|
58
|
+
* FILE_TOO_LARGE is `retryable: false`, which is the truth about this failure.
|
|
59
|
+
*/
|
|
60
|
+
export declare const CONTENT_TOO_LARGE_CODE = "CONTENT_TOO_LARGE";
|
|
61
|
+
/** An error that a processor's own size bound was exceeded. */
|
|
62
|
+
export declare function contentTooLargeError(message: string): Error;
|
|
63
|
+
/** Whether `error` is a processor size bound firing rather than a fault. */
|
|
64
|
+
export declare function isContentTooLarge(error: unknown): boolean;
|
|
48
65
|
/**
|
|
49
66
|
* Abstract base class for file processors.
|
|
50
67
|
* Provides common download, validation, and error handling functionality.
|
|
@@ -72,6 +72,29 @@ function downloadTooLargeError(maxSizeMB, typeName) {
|
|
|
72
72
|
function isDownloadTooLarge(error) {
|
|
73
73
|
return (error?.code === DOWNLOAD_TOO_LARGE_CODE);
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Marker on the error raised when a processor refuses content for exceeding
|
|
77
|
+
* the size limit *after* the download — a ZIP entry that expands past the
|
|
78
|
+
* bound, say.
|
|
79
|
+
*
|
|
80
|
+
* Separate from DOWNLOAD_TOO_LARGE because the two fire at different stages,
|
|
81
|
+
* but it exists for the same reason. Without it a bound that fires reaches
|
|
82
|
+
* `buildProcessedResultWithResult` as a plain Error and becomes
|
|
83
|
+
* PROCESSING_FAILED, which is `retryable: true` — so a caller retries a
|
|
84
|
+
* deterministic refusal three times and gets the identical answer each time.
|
|
85
|
+
* FILE_TOO_LARGE is `retryable: false`, which is the truth about this failure.
|
|
86
|
+
*/
|
|
87
|
+
export const CONTENT_TOO_LARGE_CODE = "CONTENT_TOO_LARGE";
|
|
88
|
+
/** An error that a processor's own size bound was exceeded. */
|
|
89
|
+
export function contentTooLargeError(message) {
|
|
90
|
+
const error = new Error(message);
|
|
91
|
+
error.code = CONTENT_TOO_LARGE_CODE;
|
|
92
|
+
return error;
|
|
93
|
+
}
|
|
94
|
+
/** Whether `error` is a processor size bound firing rather than a fault. */
|
|
95
|
+
export function isContentTooLarge(error) {
|
|
96
|
+
return (error?.code === CONTENT_TOO_LARGE_CODE);
|
|
97
|
+
}
|
|
75
98
|
/** Node's signal that a zlib output bound was reached. */
|
|
76
99
|
function isBufferTooLargeError(error) {
|
|
77
100
|
return (error?.code === "ERR_BUFFER_TOO_LARGE");
|
|
@@ -360,6 +383,23 @@ export class BaseFileProcessor {
|
|
|
360
383
|
return { success: true, data: result };
|
|
361
384
|
}
|
|
362
385
|
catch (error) {
|
|
386
|
+
// A size bound firing is a verdict, not a fault: PROCESSING_FAILED is
|
|
387
|
+
// retryable, and re-running a decompression that will hit the same
|
|
388
|
+
// ceiling is the one thing worth not doing three times.
|
|
389
|
+
if (isContentTooLarge(error)) {
|
|
390
|
+
return {
|
|
391
|
+
success: false,
|
|
392
|
+
// Same detail keys as the other FILE_TOO_LARGE sites, so anything
|
|
393
|
+
// reading them does not have to special-case where the verdict came
|
|
394
|
+
// from. `sizeMB` is absent on purpose: the decompressed size is the
|
|
395
|
+
// number this bound exists to never find out.
|
|
396
|
+
error: this.createError(FileErrorCode.FILE_TOO_LARGE, {
|
|
397
|
+
maxMB: this.config.maxSizeMB,
|
|
398
|
+
type: this.config.fileTypeName,
|
|
399
|
+
reason: error instanceof Error ? error.message : undefined,
|
|
400
|
+
}),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
363
403
|
return {
|
|
364
404
|
success: false,
|
|
365
405
|
error: this.createError(FileErrorCode.PROCESSING_FAILED, { fileType: this.config.fileTypeName }, error instanceof Error ? error : undefined),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import * as zlib from "node:zlib";
|
|
11
|
-
import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
|
|
11
|
+
import { BaseFileProcessor, contentTooLargeError, isContentTooLarge, } from "../base/BaseFileProcessor.js";
|
|
12
12
|
import { readZipEntryWithinLimit } from "../archive/zipEntryReader.js";
|
|
13
13
|
import { SIZE_LIMITS } from "../config/index.js";
|
|
14
14
|
const require = createRequire(import.meta.url);
|
|
@@ -71,7 +71,10 @@ export class OpenDocumentProcessor extends BaseFileProcessor {
|
|
|
71
71
|
// `maxSizeMB` only ever saw the compressed archive on the way in.
|
|
72
72
|
const read = readZipEntryWithinLimit(contentEntry, SIZE_LIMITS.DOCUMENT_MAX_MB * 1024 * 1024, zlib);
|
|
73
73
|
if (read.status === "too-large") {
|
|
74
|
-
|
|
74
|
+
// Coded, so the bound surfaces as FILE_TOO_LARGE (not retryable)
|
|
75
|
+
// rather than PROCESSING_FAILED (retryable). The file will be
|
|
76
|
+
// exactly as oversized on the next attempt.
|
|
77
|
+
throw contentTooLargeError(`content.xml in "${this.getFilename(fileInfo)}" exceeds the ${SIZE_LIMITS.DOCUMENT_MAX_MB}MB limit for OpenDocument content`);
|
|
75
78
|
}
|
|
76
79
|
if (read.status !== "ok") {
|
|
77
80
|
throw new Error("content.xml could not be read from the archive");
|
|
@@ -93,6 +96,13 @@ export class OpenDocumentProcessor extends BaseFileProcessor {
|
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
catch (error) {
|
|
99
|
+
// A size verdict passes through intact. Re-wrapping it as a plain Error
|
|
100
|
+
// drops the code, and the base class then reports PROCESSING_FAILED —
|
|
101
|
+
// which is retryable, so the caller would refetch and decompress a file
|
|
102
|
+
// guaranteed to be exactly as oversized.
|
|
103
|
+
if (isContentTooLarge(error)) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
96
106
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
97
107
|
throw new Error(`Failed to extract OpenDocument content: ${message}`, {
|
|
98
108
|
cause: error,
|
package/dist/proxy/proxyFetch.js
CHANGED
|
@@ -8,6 +8,7 @@ import { SpanStatusCode, propagation, context } from "@opentelemetry/api";
|
|
|
8
8
|
import { tracers } from "../telemetry/tracers.js";
|
|
9
9
|
import { shouldBypassProxy } from "./utils/noProxyUtils.js";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
|
+
import { TRANSIENT_NETWORK_CODES } from "../constants/networkErrorCodes.js";
|
|
11
12
|
async function getLangfuseContext() {
|
|
12
13
|
try {
|
|
13
14
|
// Dynamic import to avoid hard dependency — getLangfuseContext is only
|
|
@@ -79,15 +80,6 @@ function extractHostname(url) {
|
|
|
79
80
|
return "[unknown]";
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
|
-
/** Error codes classified as transient (module-scope: the retry path is hot). */
|
|
83
|
-
const TRANSIENT_NETWORK_CODES = new Set([
|
|
84
|
-
"ECONNRESET",
|
|
85
|
-
"ETIMEDOUT",
|
|
86
|
-
"ECONNREFUSED",
|
|
87
|
-
"EPIPE",
|
|
88
|
-
"UND_ERR_SOCKET",
|
|
89
|
-
"UND_ERR_CONNECT_TIMEOUT",
|
|
90
|
-
]);
|
|
91
83
|
/**
|
|
92
84
|
* Classify a fetch failure as a transient network error worth retrying.
|
|
93
85
|
*
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -1714,8 +1714,8 @@ export type ProviderDescriptor = {
|
|
|
1714
1714
|
modelFallbacks?: readonly string[];
|
|
1715
1715
|
/** Additional env vars required alongside apiKey (e.g. AWS secret key, Azure endpoint). */
|
|
1716
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[];
|
|
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
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
1720
|
optional?: boolean;
|
|
1721
1721
|
};
|
|
@@ -1741,6 +1741,21 @@ export type ProviderDescriptor = {
|
|
|
1741
1741
|
autoSelectPriority?: number;
|
|
1742
1742
|
/** Format-validation regex sourced from providerConfig.ts's API_KEY_FORMATS, when one exists for this provider. */
|
|
1743
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;
|
|
1744
1759
|
};
|
|
1745
1760
|
/** Minimal NeuroLink-like instance accepted by the image generation service. */
|
|
1746
1761
|
export type NeuroLinkInstance = {
|
|
@@ -13,21 +13,84 @@
|
|
|
13
13
|
import { ProviderError, AuthenticationError, RateLimitError, InvalidModelError, NetworkError, } from "../types/index.js";
|
|
14
14
|
import { TimeoutError } from "./timeout.js";
|
|
15
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
|
+
}
|
|
16
54
|
function buildErrorContext(error, provider, modelName) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
? record.message
|
|
55
|
+
const chain = collectCauseChain(error);
|
|
56
|
+
const top = chain[0];
|
|
57
|
+
const topMessage = typeof top?.message === "string"
|
|
58
|
+
? top.message
|
|
22
59
|
: error instanceof Error
|
|
23
60
|
? error.message
|
|
24
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
|
+
}
|
|
25
88
|
return {
|
|
26
89
|
error,
|
|
27
90
|
message,
|
|
28
|
-
statusCode
|
|
29
|
-
errorName:
|
|
30
|
-
errorCode:
|
|
91
|
+
statusCode,
|
|
92
|
+
errorName: firstString(chain, "name"),
|
|
93
|
+
errorCode: firstString(chain, "code"),
|
|
31
94
|
provider,
|
|
32
95
|
modelName,
|
|
33
96
|
};
|
|
@@ -80,13 +143,39 @@ export const DEFAULT_ERROR_RULES = [
|
|
|
80
143
|
: `${ctx.provider} model not found.`,
|
|
81
144
|
},
|
|
82
145
|
{
|
|
83
|
-
|
|
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)),
|
|
84
155
|
errorClass: NetworkError,
|
|
85
156
|
message: (ctx) => `Connection error: ${ctx.message}`,
|
|
86
157
|
},
|
|
87
158
|
{
|
|
88
|
-
|
|
89
|
-
|
|
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),
|
|
90
179
|
errorClass: ProviderError,
|
|
91
180
|
message: (ctx) => `${ctx.provider} server error: ${ctx.message}`,
|
|
92
181
|
},
|
|
@@ -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
|
// =============================================================================
|