@juspay/neurolink 11.1.0 → 11.2.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/browser/neurolink.min.js +367 -367
  3. package/dist/cli/commands/setup.d.ts +4 -1
  4. package/dist/cli/commands/setup.js +44 -14
  5. package/dist/constants/networkErrorCodes.d.ts +14 -0
  6. package/dist/constants/networkErrorCodes.js +21 -0
  7. package/dist/factories/providerDescriptors.js +13 -3
  8. package/dist/lib/constants/networkErrorCodes.d.ts +14 -0
  9. package/dist/lib/constants/networkErrorCodes.js +22 -0
  10. package/dist/lib/factories/providerDescriptors.js +13 -3
  11. package/dist/lib/processors/base/BaseFileProcessor.d.ts +17 -0
  12. package/dist/lib/processors/base/BaseFileProcessor.js +40 -0
  13. package/dist/lib/processors/document/OpenDocumentProcessor.js +12 -2
  14. package/dist/lib/providers/configuredOpenAICompat.d.ts +24 -0
  15. package/dist/lib/providers/configuredOpenAICompat.js +60 -0
  16. package/dist/lib/providers/openaiCompatCatalog.d.ts +24 -0
  17. package/dist/lib/providers/openaiCompatCatalog.js +272 -0
  18. package/dist/lib/proxy/proxyFetch.js +1 -9
  19. package/dist/lib/types/cli.d.ts +2 -0
  20. package/dist/lib/types/providers.d.ts +116 -2
  21. package/dist/lib/utils/errorClassifier.js +100 -11
  22. package/dist/lib/utils/providerConfig.d.ts +39 -1
  23. package/dist/lib/utils/providerConfig.js +83 -0
  24. package/dist/lib/utils/providerHealth.d.ts +30 -24
  25. package/dist/lib/utils/providerHealth.js +42 -41
  26. package/dist/lib/utils/providerUtils.js +2 -2
  27. package/dist/processors/base/BaseFileProcessor.d.ts +17 -0
  28. package/dist/processors/base/BaseFileProcessor.js +40 -0
  29. package/dist/processors/document/OpenDocumentProcessor.js +12 -2
  30. package/dist/providers/configuredOpenAICompat.d.ts +24 -0
  31. package/dist/providers/configuredOpenAICompat.js +59 -0
  32. package/dist/providers/openaiCompatCatalog.d.ts +24 -0
  33. package/dist/providers/openaiCompatCatalog.js +271 -0
  34. package/dist/proxy/proxyFetch.js +1 -9
  35. package/dist/types/cli.d.ts +2 -0
  36. package/dist/types/providers.d.ts +116 -2
  37. package/dist/utils/errorClassifier.js +100 -11
  38. package/dist/utils/providerConfig.d.ts +39 -1
  39. package/dist/utils/providerConfig.js +83 -0
  40. package/dist/utils/providerHealth.d.ts +30 -24
  41. package/dist/utils/providerHealth.js +42 -41
  42. package/dist/utils/providerUtils.js +2 -2
  43. package/package.json +2 -1
@@ -28,4 +28,7 @@ export declare function checkExistingConfigurations(): Promise<string[]>;
28
28
  /**
29
29
  * Delegate to existing provider setup commands
30
30
  */
31
- export declare function delegateToProviderSetup(providerId: string): Promise<void>;
31
+ export declare function delegateToProviderSetup(providerId: string, flags?: {
32
+ check?: boolean;
33
+ nonInteractive?: boolean;
34
+ }): Promise<void>;
@@ -20,7 +20,7 @@ import { handleGCPSetup } from "./setup-gcp.js";
20
20
  import { handleHuggingFaceSetup } from "./setup-huggingface.js";
21
21
  import { handleMistralSetup } from "./setup-mistral.js";
22
22
  import { PROVIDER_DESCRIPTORS_BY_NAME } from "../../lib/factories/providerDescriptors.js";
23
- import { createCloudflareConfig, createCohereConfig, createDeepSeekConfig, createFireworksConfig, createGroqConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createPerplexityConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createTogetherAIConfig, createVoyageConfig, createXaiConfig, } from "../../lib/utils/providerConfig.js";
23
+ import { createCloudflareConfig, createCohereConfig, createDeepSeekConfig, createFireworksConfig, createGroqConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createPerplexityConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createTogetherAIConfig, createVoyageConfig, createXaiConfig, satisfiesFallbacks, } from "../../lib/utils/providerConfig.js";
24
24
  // Provider information database
25
25
  const PROVIDERS = [
26
26
  {
@@ -277,15 +277,22 @@ function printGenericProviderSetup(providerId, config) {
277
277
  */
278
278
  export async function handleSetup(argv) {
279
279
  try {
280
- // Handle specific flags
280
+ // Handle specific flags. `--list`/`--status` are documented as one-shot,
281
+ // non-interactive informational commands (see the `setup --list` /
282
+ // `setup --status` examples in setupCommandFactory.ts) — pass
283
+ // interactive: false so they print and return instead of chaining into
284
+ // the wizard's follow-up inquirer prompt below.
281
285
  if (argv.list) {
282
- return await showProviderList();
286
+ return await showProviderList(false);
283
287
  }
284
288
  if (argv.status) {
285
- return await showProviderStatus();
289
+ return await showProviderStatus(false);
286
290
  }
287
291
  if (argv.provider && argv.provider !== "auto") {
288
- return await delegateToProviderSetup(argv.provider);
292
+ return await delegateToProviderSetup(argv.provider, {
293
+ check: argv.check,
294
+ nonInteractive: argv.nonInteractive,
295
+ });
289
296
  }
290
297
  // Main setup wizard
291
298
  await showWelcomeScreen();
@@ -399,7 +406,7 @@ export async function checkExistingConfigurations() {
399
406
  continue;
400
407
  }
401
408
  const requiredOk = (extraRequired ?? []).every((v) => !!process.env[v]) ||
402
- (extraRequiredFallbacks ?? []).some((v) => !!process.env[v]);
409
+ satisfiesFallbacks(extraRequiredFallbacks, process.env);
403
410
  if (requiredOk) {
404
411
  configured.push(p.id);
405
412
  }
@@ -482,11 +489,16 @@ async function runProviderSelection() {
482
489
  /**
483
490
  * Delegate to existing provider setup commands
484
491
  */
485
- export async function delegateToProviderSetup(providerId) {
492
+ export async function delegateToProviderSetup(providerId, flags = {
493
+ check: false,
494
+ nonInteractive: false,
495
+ }) {
496
+ const nonInteractive = flags.nonInteractive ?? false;
497
+ const check = flags.check ?? false;
486
498
  const setupArgs = {
487
- nonInteractive: false,
488
- "non-interactive": false,
489
- check: false,
499
+ nonInteractive,
500
+ "non-interactive": nonInteractive,
501
+ check,
490
502
  _: [],
491
503
  $0: "neurolink",
492
504
  };
@@ -580,9 +592,15 @@ async function showSetupCompletion(providerId) {
580
592
  logger.always(chalk.blue("Happy generating! 🚀"));
581
593
  }
582
594
  /**
583
- * Show detailed provider information
595
+ * Show detailed provider information.
596
+ *
597
+ * @param interactive - When true (the default, used by the interactive
598
+ * wizard's "learn more" menu choice), follows up with a prompt offering to
599
+ * launch the setup wizard. When false (used by the direct `setup --list`
600
+ * flag), returns immediately after printing so the command exits cleanly
601
+ * without waiting on stdin.
584
602
  */
585
- async function showProviderList() {
603
+ async function showProviderList(interactive = true) {
586
604
  logger.always(chalk.blue("📚 NeuroLink Supported AI Providers"));
587
605
  logger.always("");
588
606
  for (const provider of PROVIDERS) {
@@ -612,6 +630,9 @@ async function showProviderList() {
612
630
  logger.always("• neurolink setup --provider azure");
613
631
  logger.always("• neurolink setup --provider bedrock");
614
632
  logger.always("");
633
+ if (!interactive) {
634
+ return;
635
+ }
615
636
  const { action } = await inquirer.prompt([
616
637
  {
617
638
  type: "select",
@@ -628,9 +649,15 @@ async function showProviderList() {
628
649
  }
629
650
  }
630
651
  /**
631
- * Show provider status with detailed information
652
+ * Show provider status with detailed information.
653
+ *
654
+ * @param interactive - When true (the default, used by the interactive
655
+ * wizard's "check status" menu choice), follows up with a prompt offering to
656
+ * set up another provider. When false (used by the direct `setup --status`
657
+ * flag), returns immediately after printing so the command exits cleanly
658
+ * without waiting on stdin.
632
659
  */
633
- async function showProviderStatus() {
660
+ async function showProviderStatus(interactive = true) {
634
661
  const spinner = ora("🔍 Checking all AI provider configurations...").start();
635
662
  try {
636
663
  // Get provider status using NeuroLink SDK
@@ -691,6 +718,9 @@ async function showProviderStatus() {
691
718
  logger.always("• Check performance: neurolink provider status");
692
719
  logger.always("");
693
720
  }
721
+ if (!interactive) {
722
+ return;
723
+ }
694
724
  const { setupAnother } = await inquirer.prompt([
695
725
  {
696
726
  type: "confirm",
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Error codes that indicate a transient transport failure (dropped
3
+ * connection, socket reset, connect timeout) rather than a permanent
4
+ * misconfiguration. Shared between `proxy/proxyFetch.ts` (retry gating) and
5
+ * `utils/errorClassifier.ts` (NetworkError classification) so both stay in
6
+ * sync — a second hand-maintained copy would drift the two apart the same
7
+ * way the "5xx literal text vs statusCode" split did.
8
+ *
9
+ * undici's native `fetch()` wraps the real transport failure in
10
+ * `TypeError: fetch failed`, with the actionable code on `error.cause`
11
+ * (sometimes nested another level deep, e.g. a SocketError inside a
12
+ * ConnectTimeoutError) — never on the outer TypeError itself.
13
+ */
14
+ export declare const TRANSIENT_NETWORK_CODES: ReadonlySet<string>;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Error codes that indicate a transient transport failure (dropped
3
+ * connection, socket reset, connect timeout) rather than a permanent
4
+ * misconfiguration. Shared between `proxy/proxyFetch.ts` (retry gating) and
5
+ * `utils/errorClassifier.ts` (NetworkError classification) so both stay in
6
+ * sync — a second hand-maintained copy would drift the two apart the same
7
+ * way the "5xx literal text vs statusCode" split did.
8
+ *
9
+ * undici's native `fetch()` wraps the real transport failure in
10
+ * `TypeError: fetch failed`, with the actionable code on `error.cause`
11
+ * (sometimes nested another level deep, e.g. a SocketError inside a
12
+ * ConnectTimeoutError) — never on the outer TypeError itself.
13
+ */
14
+ export const TRANSIENT_NETWORK_CODES = new Set([
15
+ "ECONNRESET",
16
+ "ETIMEDOUT",
17
+ "ECONNREFUSED",
18
+ "EPIPE",
19
+ "UND_ERR_SOCKET",
20
+ "UND_ERR_CONNECT_TIMEOUT",
21
+ ]);
@@ -26,6 +26,9 @@ export const PROVIDER_DESCRIPTORS = [
26
26
  timeouts: { generateMs: 45_000, streamMs: 120_000 },
27
27
  autoSelectPriority: 7,
28
28
  apiKeyFormatPattern: API_KEY_FORMATS.bedrock,
29
+ // Falls back to the AWS SDK's own default credential chain (shared
30
+ // profile, IAM role) when these env vars are absent — see field JSDoc.
31
+ credentialsResolvedExternally: true,
29
32
  },
30
33
  {
31
34
  name: AIProviderName.OPENAI,
@@ -89,12 +92,14 @@ export const PROVIDER_DESCRIPTORS = [
89
92
  // priority than GOOGLE_APPLICATION_CREDENTIALS by the real gating
90
93
  // logic (hasGoogleCredentials() in googleVertex/client.ts and
91
94
  // googleVertex/utils.ts) — corrected here vs. the plan snippet, which
92
- // omitted it.
95
+ // omitted it. GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY are
96
+ // nested together because hasGoogleCredentials() only accepts them as
97
+ // a pair — either alone is not valid auth, unlike the other flat
98
+ // entries here which are each independently sufficient.
93
99
  extraRequiredFallbacks: [
94
100
  "GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK",
95
101
  "GOOGLE_SERVICE_ACCOUNT_KEY",
96
- "GOOGLE_AUTH_CLIENT_EMAIL",
97
- "GOOGLE_AUTH_PRIVATE_KEY",
102
+ ["GOOGLE_AUTH_CLIENT_EMAIL", "GOOGLE_AUTH_PRIVATE_KEY"],
98
103
  ],
99
104
  },
100
105
  defaultModel: VertexModels.CLAUDE_4_6_SONNET,
@@ -104,6 +109,9 @@ export const PROVIDER_DESCRIPTORS = [
104
109
  setupUrl: "https://console.cloud.google.com/",
105
110
  timeouts: { generateMs: 60_000, streamMs: 120_000 },
106
111
  autoSelectPriority: 3,
112
+ // OR-of-multiple-auth-paths (file / individual fields / base64 key) —
113
+ // not a flat AND-list of required env vars. See field JSDoc.
114
+ credentialsResolvedExternally: true,
107
115
  },
108
116
  {
109
117
  name: AIProviderName.ANTHROPIC,
@@ -241,6 +249,8 @@ export const PROVIDER_DESCRIPTORS = [
241
249
  setupUrl: "https://docs.litellm.ai/docs/proxy/quick_start",
242
250
  timeouts: { generateMs: 300_000, streamMs: 120_000 },
243
251
  autoSelectPriority: 1,
252
+ // Documented zero-config local proxy — see field JSDoc.
253
+ credentialsResolvedExternally: true,
244
254
  },
245
255
  {
246
256
  name: AIProviderName.SAGEMAKER,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Error codes that indicate a transient transport failure (dropped
3
+ * connection, socket reset, connect timeout) rather than a permanent
4
+ * misconfiguration. Shared between `proxy/proxyFetch.ts` (retry gating) and
5
+ * `utils/errorClassifier.ts` (NetworkError classification) so both stay in
6
+ * sync — a second hand-maintained copy would drift the two apart the same
7
+ * way the "5xx literal text vs statusCode" split did.
8
+ *
9
+ * undici's native `fetch()` wraps the real transport failure in
10
+ * `TypeError: fetch failed`, with the actionable code on `error.cause`
11
+ * (sometimes nested another level deep, e.g. a SocketError inside a
12
+ * ConnectTimeoutError) — never on the outer TypeError itself.
13
+ */
14
+ export declare const TRANSIENT_NETWORK_CODES: ReadonlySet<string>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Error codes that indicate a transient transport failure (dropped
3
+ * connection, socket reset, connect timeout) rather than a permanent
4
+ * misconfiguration. Shared between `proxy/proxyFetch.ts` (retry gating) and
5
+ * `utils/errorClassifier.ts` (NetworkError classification) so both stay in
6
+ * sync — a second hand-maintained copy would drift the two apart the same
7
+ * way the "5xx literal text vs statusCode" split did.
8
+ *
9
+ * undici's native `fetch()` wraps the real transport failure in
10
+ * `TypeError: fetch failed`, with the actionable code on `error.cause`
11
+ * (sometimes nested another level deep, e.g. a SocketError inside a
12
+ * ConnectTimeoutError) — never on the outer TypeError itself.
13
+ */
14
+ export const TRANSIENT_NETWORK_CODES = new Set([
15
+ "ECONNRESET",
16
+ "ETIMEDOUT",
17
+ "ECONNREFUSED",
18
+ "EPIPE",
19
+ "UND_ERR_SOCKET",
20
+ "UND_ERR_CONNECT_TIMEOUT",
21
+ ]);
22
+ //# sourceMappingURL=networkErrorCodes.js.map
@@ -26,6 +26,9 @@ export const PROVIDER_DESCRIPTORS = [
26
26
  timeouts: { generateMs: 45_000, streamMs: 120_000 },
27
27
  autoSelectPriority: 7,
28
28
  apiKeyFormatPattern: API_KEY_FORMATS.bedrock,
29
+ // Falls back to the AWS SDK's own default credential chain (shared
30
+ // profile, IAM role) when these env vars are absent — see field JSDoc.
31
+ credentialsResolvedExternally: true,
29
32
  },
30
33
  {
31
34
  name: AIProviderName.OPENAI,
@@ -89,12 +92,14 @@ export const PROVIDER_DESCRIPTORS = [
89
92
  // priority than GOOGLE_APPLICATION_CREDENTIALS by the real gating
90
93
  // logic (hasGoogleCredentials() in googleVertex/client.ts and
91
94
  // googleVertex/utils.ts) — corrected here vs. the plan snippet, which
92
- // omitted it.
95
+ // omitted it. GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY are
96
+ // nested together because hasGoogleCredentials() only accepts them as
97
+ // a pair — either alone is not valid auth, unlike the other flat
98
+ // entries here which are each independently sufficient.
93
99
  extraRequiredFallbacks: [
94
100
  "GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK",
95
101
  "GOOGLE_SERVICE_ACCOUNT_KEY",
96
- "GOOGLE_AUTH_CLIENT_EMAIL",
97
- "GOOGLE_AUTH_PRIVATE_KEY",
102
+ ["GOOGLE_AUTH_CLIENT_EMAIL", "GOOGLE_AUTH_PRIVATE_KEY"],
98
103
  ],
99
104
  },
100
105
  defaultModel: VertexModels.CLAUDE_4_6_SONNET,
@@ -104,6 +109,9 @@ export const PROVIDER_DESCRIPTORS = [
104
109
  setupUrl: "https://console.cloud.google.com/",
105
110
  timeouts: { generateMs: 60_000, streamMs: 120_000 },
106
111
  autoSelectPriority: 3,
112
+ // OR-of-multiple-auth-paths (file / individual fields / base64 key) —
113
+ // not a flat AND-list of required env vars. See field JSDoc.
114
+ credentialsResolvedExternally: true,
107
115
  },
108
116
  {
109
117
  name: AIProviderName.ANTHROPIC,
@@ -241,6 +249,8 @@ export const PROVIDER_DESCRIPTORS = [
241
249
  setupUrl: "https://docs.litellm.ai/docs/proxy/quick_start",
242
250
  timeouts: { generateMs: 300_000, streamMs: 120_000 },
243
251
  autoSelectPriority: 1,
252
+ // Documented zero-config local proxy — see field JSDoc.
253
+ credentialsResolvedExternally: true,
244
254
  },
245
255
  {
246
256
  name: AIProviderName.SAGEMAKER,
@@ -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
- throw new Error(`content.xml exceeds the ${SIZE_LIMITS.DOCUMENT_MAX_MB}MB limit for OpenDocument content`);
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,
@@ -0,0 +1,24 @@
1
+ import type { AIProviderName } from "../constants/enums.js";
2
+ import type { OpenAICompatCatalogEntry, OpenAICompatCredentials } from "../types/index.js";
3
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
4
+ /**
5
+ * Generic OpenAI-compatible provider driven entirely by an
6
+ * OpenAICompatCatalogEntry. Replaces a hand-written subclass for any
7
+ * provider whose only differences from its siblings are credentials, base
8
+ * URL, model defaults, and error-classification rules — see
9
+ * OPENAI_COMPAT_CATALOG in openaiCompatCatalog.ts for the entries.
10
+ *
11
+ * If a provider needs a real hook override (adjustRequestBody,
12
+ * adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
13
+ * suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
14
+ * write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
15
+ */
16
+ export declare class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
17
+ private readonly entry;
18
+ constructor(entry: OpenAICompatCatalogEntry, modelName?: string, sdk?: unknown, credentials?: OpenAICompatCredentials);
19
+ protected getProviderName(): AIProviderName;
20
+ protected getDefaultModel(): string;
21
+ protected getFallbackModelName(): string;
22
+ protected getFallbackModels(): string[];
23
+ protected formatProviderError(error: unknown): Error;
24
+ }
@@ -0,0 +1,60 @@
1
+ import { logger } from "../utils/logger.js";
2
+ import { redactUrlCredentials } from "../utils/logSanitize.js";
3
+ import { getProviderModel, resolveOpenAICompatConfig, } from "../utils/providerConfig.js";
4
+ import { classifyProviderError } from "../utils/errorClassifier.js";
5
+ import { OpenAIChatCompletionsProvider } from "./openaiChatCompletionsBase.js";
6
+ /**
7
+ * Generic OpenAI-compatible provider driven entirely by an
8
+ * OpenAICompatCatalogEntry. Replaces a hand-written subclass for any
9
+ * provider whose only differences from its siblings are credentials, base
10
+ * URL, model defaults, and error-classification rules — see
11
+ * OPENAI_COMPAT_CATALOG in openaiCompatCatalog.ts for the entries.
12
+ *
13
+ * If a provider needs a real hook override (adjustRequestBody,
14
+ * adjustBodyAfter400, getChatCompletionsURL, getAuthHeaders,
15
+ * suppressResponseFormatWithTools, ...) it does NOT belong in the catalog —
16
+ * write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts).
17
+ */
18
+ export class ConfiguredOpenAICompatProvider extends OpenAIChatCompletionsProvider {
19
+ entry;
20
+ constructor(entry, modelName, sdk, credentials) {
21
+ const { apiKey, baseURL } = resolveOpenAICompatConfig(entry, credentials);
22
+ // BaseProvider's constructor calls `this.getDefaultModel()` /
23
+ // `this.getProviderName()` synchronously inside `super()`, before this
24
+ // class's own constructor body (or field initializers) ever run — so
25
+ // `this.entry` is not yet assigned at that point and those overrides
26
+ // would read `undefined.modelEnvVar`. `entry.providerName` is always
27
+ // defined, so passing it straight through makes the base constructor's
28
+ // `providerName || this.getProviderName()` short-circuit; resolving the
29
+ // model up front and always passing a truthy `modelName` does the same
30
+ // for `getDefaultModel()`. Both overrides remain correct for any call
31
+ // made after construction, once `this.entry` is set below.
32
+ const resolvedModelName = modelName || getProviderModel(entry.modelEnvVar, entry.defaultModel);
33
+ super(entry.providerName, resolvedModelName, sdk, { baseURL, apiKey });
34
+ this.entry = entry;
35
+ logger.debug(`${entry.configOptions.providerName} Provider initialized`, {
36
+ modelName: this.modelName,
37
+ providerName: this.providerName,
38
+ baseURL: redactUrlCredentials(this.config.baseURL),
39
+ });
40
+ }
41
+ getProviderName() {
42
+ return this.entry.providerName;
43
+ }
44
+ getDefaultModel() {
45
+ return getProviderModel(this.entry.modelEnvVar, this.entry.defaultModel);
46
+ }
47
+ getFallbackModelName() {
48
+ return this.entry.fallbackModelName;
49
+ }
50
+ getFallbackModels() {
51
+ return this.entry.fallbackModels;
52
+ }
53
+ formatProviderError(error) {
54
+ // classifyProviderError handles TimeoutError internally (always maps
55
+ // to NetworkError, ahead of any rule table) — no local pre-check
56
+ // needed or wanted here; see this task's design note.
57
+ return classifyProviderError(error, this.entry.errorRules, this.entry.providerName, this.modelName);
58
+ }
59
+ }
60
+ //# sourceMappingURL=configuredOpenAICompat.js.map
@@ -0,0 +1,24 @@
1
+ import type { OpenAICompatCatalogEntry } from "../types/index.js";
2
+ /**
3
+ * Config-driven catalog of the 7 zero-quirk OpenAI-compatible providers.
4
+ * Each entry fully replaces what used to be a hand-written
5
+ * OpenAIChatCompletionsProvider subclass — see ConfiguredOpenAICompatProvider
6
+ * for the class that reads these entries, and providerRegistry.ts for the
7
+ * registration loop that consumes this array.
8
+ *
9
+ * `errorRules` mirrors each provider's LIVE `formatProviderError` rule array
10
+ * (post plan-07/wave-2 migration), not the original hand-rolled ladder these
11
+ * providers had when plan 05 was first drafted: every provider below now
12
+ * keeps only its bespoke rule(s) — auth, plus Groq's model_decommissioned and
13
+ * xAI's insufficient_quota — before spreading the SAME exported
14
+ * `DEFAULT_ERROR_RULES` constant that the live subclasses spread (never an
15
+ * inlined copy, so this catalog cannot drift from that table independently).
16
+ * See plan-05/progress.md Ruling R4 for the full rationale.
17
+ *
18
+ * To add a new zero-quirk OpenAI-compatible provider: add one entry here.
19
+ * Do NOT add a provider here if it needs any hook override beyond the 3
20
+ * mandatory ones (getProviderName/getDefaultModel/formatProviderError) —
21
+ * write a dedicated subclass instead (see deepseek.ts, azureOpenai.ts, and
22
+ * Task 14's docs task for the deciding criteria).
23
+ */
24
+ export declare const OPENAI_COMPAT_CATALOG: readonly OpenAICompatCatalogEntry[];