@juspay/neurolink 11.1.1 → 11.2.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 (74) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/auth/codexOAuth.d.ts +67 -0
  3. package/dist/auth/codexOAuth.js +202 -0
  4. package/dist/auth/index.d.ts +1 -0
  5. package/dist/auth/index.js +4 -0
  6. package/dist/browser/neurolink.min.js +401 -401
  7. package/dist/cli/commands/auth.d.ts +27 -8
  8. package/dist/cli/commands/auth.js +425 -6
  9. package/dist/cli/commands/proxy.js +230 -5
  10. package/dist/cli/factories/authCommandFactory.d.ts +8 -0
  11. package/dist/cli/factories/authCommandFactory.js +74 -1
  12. package/dist/lib/auth/codexOAuth.d.ts +67 -0
  13. package/dist/lib/auth/codexOAuth.js +203 -0
  14. package/dist/lib/auth/index.d.ts +1 -0
  15. package/dist/lib/auth/index.js +4 -0
  16. package/dist/lib/providers/configuredOpenAICompat.d.ts +24 -0
  17. package/dist/lib/providers/configuredOpenAICompat.js +60 -0
  18. package/dist/lib/providers/openaiCompatCatalog.d.ts +24 -0
  19. package/dist/lib/providers/openaiCompatCatalog.js +272 -0
  20. package/dist/lib/proxy/accountCooldown.js +35 -2
  21. package/dist/lib/proxy/accountQuota.d.ts +29 -3
  22. package/dist/lib/proxy/accountQuota.js +203 -12
  23. package/dist/lib/proxy/accountUsage.js +15 -2
  24. package/dist/lib/proxy/codexAccountUsage.d.ts +26 -0
  25. package/dist/lib/proxy/codexAccountUsage.js +174 -0
  26. package/dist/lib/proxy/proxyAnalysis.js +12 -1
  27. package/dist/lib/proxy/proxyConfig.js +24 -0
  28. package/dist/lib/proxy/routingEvidence.d.ts +12 -1
  29. package/dist/lib/proxy/routingEvidence.js +23 -0
  30. package/dist/lib/proxy/runtimeConfig.js +3 -0
  31. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +79 -5
  32. package/dist/lib/server/routes/claudeProxyRoutes.js +653 -72
  33. package/dist/lib/server/routes/codexProxyRoutes.d.ts +64 -0
  34. package/dist/lib/server/routes/codexProxyRoutes.js +454 -0
  35. package/dist/lib/types/cli.d.ts +7 -1
  36. package/dist/lib/types/codex.d.ts +95 -0
  37. package/dist/lib/types/codex.js +15 -0
  38. package/dist/lib/types/index.d.ts +1 -0
  39. package/dist/lib/types/index.js +1 -0
  40. package/dist/lib/types/providers.d.ts +99 -0
  41. package/dist/lib/types/proxy.d.ts +83 -0
  42. package/dist/lib/types/subscription.d.ts +13 -0
  43. package/dist/lib/utils/providerConfig.d.ts +23 -1
  44. package/dist/lib/utils/providerConfig.js +60 -0
  45. package/dist/providers/configuredOpenAICompat.d.ts +24 -0
  46. package/dist/providers/configuredOpenAICompat.js +59 -0
  47. package/dist/providers/openaiCompatCatalog.d.ts +24 -0
  48. package/dist/providers/openaiCompatCatalog.js +271 -0
  49. package/dist/proxy/accountCooldown.js +35 -2
  50. package/dist/proxy/accountQuota.d.ts +29 -3
  51. package/dist/proxy/accountQuota.js +203 -12
  52. package/dist/proxy/accountUsage.js +15 -2
  53. package/dist/proxy/codexAccountUsage.d.ts +26 -0
  54. package/dist/proxy/codexAccountUsage.js +173 -0
  55. package/dist/proxy/proxyAnalysis.js +12 -1
  56. package/dist/proxy/proxyConfig.js +24 -0
  57. package/dist/proxy/routingEvidence.d.ts +12 -1
  58. package/dist/proxy/routingEvidence.js +23 -0
  59. package/dist/proxy/runtimeConfig.js +3 -0
  60. package/dist/server/routes/claudeProxyRoutes.d.ts +79 -5
  61. package/dist/server/routes/claudeProxyRoutes.js +653 -72
  62. package/dist/server/routes/codexProxyRoutes.d.ts +64 -0
  63. package/dist/server/routes/codexProxyRoutes.js +453 -0
  64. package/dist/types/cli.d.ts +7 -1
  65. package/dist/types/codex.d.ts +95 -0
  66. package/dist/types/codex.js +14 -0
  67. package/dist/types/index.d.ts +1 -0
  68. package/dist/types/index.js +1 -0
  69. package/dist/types/providers.d.ts +99 -0
  70. package/dist/types/proxy.d.ts +83 -0
  71. package/dist/types/subscription.d.ts +13 -0
  72. package/dist/utils/providerConfig.d.ts +23 -1
  73. package/dist/utils/providerConfig.js +60 -0
  74. package/package.json +3 -1
@@ -8,6 +8,7 @@ import type { ValidationSchema } from "./aliases.js";
8
8
  import type { EnhancedGenerateResult, GenerateResult, TextGenerationOptions } from "./generate.js";
9
9
  import type { MultimodalAudioEntry } from "./file.js";
10
10
  import type { StreamOptions, StreamResult } from "./stream.js";
11
+ import type { ProviderErrorRule } from "./errors.js";
11
12
  import type { ExternalMCPToolInfo } from "./externalMcp.js";
12
13
  import type { ClaudeSubscriptionTier, AnthropicAuthMethod, AnthropicAuthConfig, SubscriptionInfo, OAuthToken } from "./subscription.js";
13
14
  import type { Tool } from "./tools.js";
@@ -577,6 +578,104 @@ export type ProviderConfigOptions = {
577
578
  fallbackEnvVars?: string[];
578
579
  optional?: boolean;
579
580
  };
581
+ /**
582
+ * Minimal credential shape accepted by resolveOpenAICompatConfig() and
583
+ * ConfiguredOpenAICompatProvider. A structural superset of every real
584
+ * per-provider NeurolinkCredentials["<key>"] slice in this family (groq,
585
+ * xai, together, fireworks, perplexity, mistral, cloudflare) — all fields
586
+ * optional, so passing e.g. NeurolinkCredentials["groq"] (which has no
587
+ * accountId) here is always structurally valid.
588
+ */
589
+ export type OpenAICompatCredentials = {
590
+ apiKey?: string;
591
+ baseURL?: string;
592
+ accountId?: string;
593
+ };
594
+ /**
595
+ * One row of the config-driven OpenAI-compatible provider catalog
596
+ * (OPENAI_COMPAT_CATALOG, src/lib/providers/openaiCompatCatalog.ts).
597
+ * Replaces a hand-written OpenAIChatCompletionsProvider subclass for
598
+ * providers whose only differences from every sibling are credentials,
599
+ * base URL, model defaults, and error-message classification.
600
+ */
601
+ export type OpenAICompatCatalogEntry = {
602
+ /** Registry key / nl.generate({provider}) value, e.g. "groq". */
603
+ providerName: AIProviderName;
604
+ /** Registry aliases, e.g. ["together-ai", "together"]. */
605
+ aliases: string[];
606
+ /**
607
+ * Env var holding the API key, e.g. "GROQ_API_KEY".
608
+ *
609
+ * Declarative: the key is actually read through `configOptions.envVarName`,
610
+ * which `validateApiKey` consults. This field exists so an entry states its
611
+ * credential source without a caller having to reach into configOptions,
612
+ * and the catalog suite asserts the two always name the same variable — two
613
+ * fields describing one fact are worth nothing if they can disagree.
614
+ */
615
+ apiKeyEnvVar: string;
616
+ /**
617
+ * Env var that can override the base URL, e.g. "GROQ_BASE_URL". Omit
618
+ * for entries that use computedBaseURL instead (e.g. Cloudflare).
619
+ */
620
+ baseURLEnvVar?: string;
621
+ /** Static default base URL. Omit for computedBaseURL entries. */
622
+ defaultBaseURL?: string;
623
+ /**
624
+ * Present only for providers whose base URL is computed from an extra
625
+ * required credential value instead of a static default (Cloudflare's
626
+ * accountId). Deliberately narrow (accountId-shaped) rather than a
627
+ * generic extra-field mechanism — Cloudflare is the only current user.
628
+ */
629
+ computedBaseURL?: {
630
+ /** Env var fallback for the extra value, e.g. "CLOUDFLARE_ACCOUNT_ID". */
631
+ envVar: string;
632
+ /** Thrown when neither credentials.accountId nor envVar supply a value. */
633
+ missingValueMessage: string;
634
+ /** Builds the base URL from the resolved accountId. */
635
+ build: (accountId: string) => string;
636
+ };
637
+ /** Setup/help metadata, passed to validateApiKey(). Not consumed by
638
+ * classifyProviderError() — that function's ProviderErrorContext has no
639
+ * docsUrl field; any URL a rule's message needs is inlined in the rule
640
+ * itself (see Task 4). */
641
+ configOptions: ProviderConfigOptions;
642
+ /** Env var for the default model, e.g. "GROQ_MODEL". */
643
+ modelEnvVar: string;
644
+ /** Default model when modelEnvVar is unset. */
645
+ defaultModel: string;
646
+ /**
647
+ * The literal passed as ProviderFactory.registerProvider()'s defaultModel
648
+ * argument (resolved before the provider is constructed). Preserves each
649
+ * provider's exact pre-migration registry behavior.
650
+ */
651
+ registryDefaultModel: string;
652
+ /**
653
+ * True for every provider except Mistral: whether the registry-level
654
+ * default also consults modelEnvVar before falling back to
655
+ * registryDefaultModel. False is a pre-existing, intentionally-preserved
656
+ * quirk unique to Mistral's registration (see plan's Design reference).
657
+ */
658
+ registryDefaultModelChecksEnvVar: boolean;
659
+ /** Fallback model name (getFallbackModelName()). */
660
+ fallbackModelName: string;
661
+ /** Fallback model list (getFallbackModels()). */
662
+ fallbackModels: string[];
663
+ /**
664
+ * Error-classification rules, consumed by classifyProviderError. Typed
665
+ * as a mutable array — not readonly — because plan 07's
666
+ * `classifyProviderError(error, rules: ProviderErrorRule[], provider, modelName?)`
667
+ * declares `rules` as `ProviderErrorRule[]`; a `readonly` array here
668
+ * would not be assignable to that parameter without a cast, which rule
669
+ * 14 (no double assertions) and general hygiene both rule out. Each
670
+ * entry's array is still constructed as a fresh literal per provider in
671
+ * Task 4, so nothing actually mutates it at runtime.
672
+ */
673
+ errorRules: ProviderErrorRule[];
674
+ };
675
+ /** The subset of OpenAICompatCatalogEntry that resolveOpenAICompatConfig()
676
+ * needs — lets call sites pass a minimal object without the full catalog
677
+ * entry (e.g. in tests, or a future non-catalog caller). */
678
+ export type OpenAICompatConfigInput = Pick<OpenAICompatCatalogEntry, "providerName" | "apiKeyEnvVar" | "baseURLEnvVar" | "defaultBaseURL" | "computedBaseURL" | "configOptions">;
580
679
  /**
581
680
  * AI Provider type with flexible parameter support
582
681
  */
@@ -481,6 +481,13 @@ export type ProxyAccountRoutingCandidate = {
481
481
  weeklyStatus: string | null;
482
482
  weeklyUsed: number | null;
483
483
  weeklyResetAt: number | null;
484
+ /** Display name of the model-scoped window that matched the requested model
485
+ * (e.g. "Fable"), or null when the account reports no scoped cap for it.
486
+ * Optional so schema-v1 readers of older records stay valid. */
487
+ scopedModel?: string | null;
488
+ scopedStatus?: string | null;
489
+ scopedUsed?: number | null;
490
+ scopedResetAt?: number | null;
484
491
  };
485
492
  export type ProxyAccountRoutingDecision = {
486
493
  schemaVersion: 1;
@@ -531,6 +538,15 @@ export type ProxyAccountSortMetrics = {
531
538
  weeklyReset: number;
532
539
  weeklyUsed: number | null;
533
540
  weeklyUsedForSort: number;
541
+ /** Model-scoped weekly window matching the requested model. All null/false
542
+ * when the account reports no scoped cap for it (the common case), which
543
+ * makes every scoped comparator rung a no-op for unscoped traffic. */
544
+ scopedModel: string | null;
545
+ scopedStatus: string | null;
546
+ scopedUsed: number | null;
547
+ scopedReset: number;
548
+ scopedUsedForSort: number;
549
+ scopedSaturated: boolean;
534
550
  };
535
551
  export type RequestLogEntry = {
536
552
  timestamp: string;
@@ -665,6 +681,8 @@ export type AnthropicLoopState = {
665
681
  } | null;
666
682
  authFailureMessage: string | null;
667
683
  authCooldownMessage: string | null;
684
+ entitlementFailure: AnthropicEntitlementFailure | null;
685
+ scopedExhaustion: AnthropicScopedExhaustion | null;
668
686
  fallbackFailureMessage?: string;
669
687
  attemptNumber: number;
670
688
  lastTransportErrorCode?: string;
@@ -752,6 +770,7 @@ export type AnthropicAuthRetryResult = {
752
770
  retryDelayMs?: number;
753
771
  lastError: unknown;
754
772
  authFailureMessage: string | null;
773
+ entitlementFailure: AnthropicEntitlementFailure | null;
755
774
  sawRateLimit: boolean;
756
775
  sawTransientFailure: boolean;
757
776
  sawNetworkError: boolean;
@@ -771,6 +790,7 @@ export type AnthropicNonOkResult = {
771
790
  body: string;
772
791
  contentType?: string;
773
792
  } | null;
793
+ entitlementFailure: AnthropicEntitlementFailure | null;
774
794
  upstreamSpan?: Span;
775
795
  };
776
796
  export type PreparedAnthropicAccountAttempt = {
@@ -997,6 +1017,17 @@ export type AccountQuota = {
997
1017
  overageStatus: string;
998
1018
  /** Whether Anthropic reports that paid overage is actively serving traffic. */
999
1019
  overageInUse?: boolean;
1020
+ /** Why overage is unavailable, verbatim from
1021
+ * anthropic-ratelimit-unified-overage-disabled-reason (e.g.
1022
+ * "org_level_disabled"). Present only when the provider states one. */
1023
+ overageDisabledReason?: string;
1024
+ /** Authoritative extra-usage switch from the usage API's
1025
+ * `extra_usage.is_enabled`. Unlike the header trio this is reported even for
1026
+ * an account that has never served a request. */
1027
+ overageEnabled?: boolean;
1028
+ /** Which window Anthropic considers binding right now, verbatim from
1029
+ * anthropic-ratelimit-unified-representative-claim (e.g. "five_hour"). */
1030
+ representativeClaim?: string;
1000
1031
  /** Epoch ms when we last captured this data */
1001
1032
  lastUpdated: number;
1002
1033
  /** Dynamic per-plan limit buckets from the usage API `limits[]` array
@@ -1033,8 +1064,21 @@ export type AccountQuotaWindow = {
1033
1064
  isActive?: boolean;
1034
1065
  /** Model display name for model-scoped windows (e.g. "Fable"). */
1035
1066
  scopeModel?: string;
1067
+ /** Wire model id for the scope when the provider reports one
1068
+ * (`scope.model.id`), which matches a request's `model` exactly and so beats
1069
+ * display-name matching. Often null in practice. */
1070
+ scopeModelId?: string;
1036
1071
  /** Surface scope when the provider reports one. */
1037
1072
  scopeSurface?: string;
1073
+ /** Epoch ms this individual window was observed. Lets a header-derived window
1074
+ * and a usage-API window on the same account age independently — the flat
1075
+ * `lastUpdated` refreshes on every response and would otherwise make a
1076
+ * days-old scoped window look current. */
1077
+ updatedAt?: number;
1078
+ /** Provenance of this window, mirroring AccountQuotaSource. */
1079
+ source?: AccountQuotaSource;
1080
+ /** Raw unified header token for header-derived windows, e.g. "7d_oi". */
1081
+ headerWindow?: string;
1038
1082
  };
1039
1083
  /** One utilization window from the OAuth usage endpoint (wire shape, loose). */
1040
1084
  export type AnthropicUsageWindow = {
@@ -2316,6 +2360,40 @@ export type ClaudeSnapshot = {
2316
2360
  export type ParsedClaudeError = {
2317
2361
  errorType?: string;
2318
2362
  message?: string;
2363
+ /** `error.details.error_code`, e.g. "oauth_not_allowed_for_organization".
2364
+ * Absent on payloads that carry no details object. */
2365
+ errorCode?: string;
2366
+ };
2367
+ /**
2368
+ * Accounts rejected by an organization/plan entitlement policy during a single
2369
+ * request. Anthropic answers such an account with a `permission_error` that no
2370
+ * amount of retrying or token refreshing can fix, but which a *different*
2371
+ * account may not hit at all — so it drives rotation, and is reported to the
2372
+ * client only once every account has been tried.
2373
+ */
2374
+ export type AnthropicEntitlementFailure = {
2375
+ status: number;
2376
+ /** Labels of every account that rejected this request on entitlement. */
2377
+ accounts: string[];
2378
+ /** Upstream message from the first such rejection. */
2379
+ message: string;
2380
+ errorCode?: string;
2381
+ };
2382
+ /**
2383
+ * Every account's model-scoped window for the requested model is spent. Unlike
2384
+ * a cooldown this is per-model: the same accounts stay healthy for every other
2385
+ * model, so the client is told to switch model rather than to back off.
2386
+ */
2387
+ export type AnthropicScopedExhaustion = {
2388
+ /** Wire model id from the request. */
2389
+ model: string;
2390
+ /** Display name of the exhausted window, e.g. "Fable". */
2391
+ scopeModel: string;
2392
+ /** Epoch ms of the soonest reset across the exhausted accounts. */
2393
+ earliestResetMs: number;
2394
+ accounts: string[];
2395
+ /** Provider reason overage is unavailable, e.g. "org_level_disabled". */
2396
+ overageDisabledReason?: string;
2319
2397
  };
2320
2398
  /** ora spinner instance held by proxy CLI commands, nullable when --quiet. */
2321
2399
  export type ProxySpinner = Ora | null;
@@ -2340,7 +2418,12 @@ export type ProxyRequestRoutingSnapshot = {
2340
2418
  quotaRoutingEnabled: boolean;
2341
2419
  sessionSoftLimit: number;
2342
2420
  sessionResetToleranceMs: number;
2421
+ /** Operator policy on spending paid extra usage once a subscription window is
2422
+ * spent. Only "never" can override the provider's own signal. */
2423
+ useOverage: ProxyOveragePolicy;
2343
2424
  };
2425
+ /** Operator policy for paid extra usage. */
2426
+ export type ProxyOveragePolicy = "auto" | "always" | "never";
2344
2427
  /** Immutable last-known-good proxy configuration published at runtime. */
2345
2428
  export type ProxyRuntimeConfigSnapshot = ProxyRequestRoutingSnapshot & {
2346
2429
  loadedAt: string;
@@ -998,6 +998,19 @@ export type ProxyRoutingConfig = {
998
998
  passthroughModels?: string[];
999
999
  /** Enable quota-aware fill-first account ordering. Defaults to true. */
1000
1000
  quotaRouting?: boolean;
1001
+ /**
1002
+ * Whether an account may keep serving on paid extra usage once its
1003
+ * subscription window is spent.
1004
+ *
1005
+ * - `auto` (default): follow whatever Anthropic reports for the account.
1006
+ * - `never`: park the account at the subscription limit even when extra usage
1007
+ * is enabled, so the pool can never spend credits.
1008
+ * - `always`: keep serving whenever the provider permits extra usage.
1009
+ *
1010
+ * Only `never` can override the provider — nothing here can enable extra usage
1011
+ * that Anthropic has disabled (e.g. `org_level_disabled`).
1012
+ */
1013
+ useOverage?: "auto" | "always" | "never";
1001
1014
  /** Session utilization threshold used to proactively demote an account. */
1002
1015
  sessionSoftLimit?: number;
1003
1016
  /** Reset-time bucket width used when ordering quota windows. */
@@ -5,7 +5,7 @@
5
5
  * Enhanced with format validation and advanced error classification
6
6
  * Extended with Claude subscription OAuth support
7
7
  */
8
- import type { APIValidationResult, ProviderConfigOptions, AnthropicAuthMethod, ClaudeSubscriptionTier, AnthropicAuthConfig, AnthropicAuthConfigResult } from "../types/index.js";
8
+ import type { APIValidationResult, ProviderConfigOptions, AnthropicAuthMethod, ClaudeSubscriptionTier, AnthropicAuthConfig, AnthropicAuthConfigResult, OpenAICompatConfigInput, OpenAICompatCredentials } from "../types/index.js";
9
9
  /**
10
10
  * API key format validation patterns (extracted from advanced validation system)
11
11
  * Exported for use across the codebase to replace scattered regex patterns
@@ -369,3 +369,25 @@ export declare function hasSubscriptionFeature(feature: "extended_thinking" | "p
369
369
  * @returns Human-readable configuration description
370
370
  */
371
371
  export declare function describeAnthropicConfig(): string;
372
+ /**
373
+ * Resolves the {apiKey, baseURL} pair for a config-driven OpenAI-compatible
374
+ * catalog entry (see OpenAICompatCatalogEntry in types/providers.ts).
375
+ *
376
+ * Extracted from the identical 6-line precedence block that was copy-pasted
377
+ * across groq.ts, xai.ts, togetherAi.ts, fireworks.ts, perplexity.ts, and
378
+ * mistral.ts, plus Cloudflare's accountId-computed-baseURL variant.
379
+ *
380
+ * Precedence (matches every ported subclass's original behavior exactly):
381
+ * apiKey: credentials.apiKey (trimmed, non-blank) > env var > throw
382
+ * baseURL: credentials.baseURL (trimmed, non-blank)
383
+ * > env var (if entry.baseURLEnvVar is set, trimmed, non-blank)
384
+ * > entry.defaultBaseURL
385
+ * baseURL (computedBaseURL entries, e.g. Cloudflare):
386
+ * credentials.baseURL > computedBaseURL.build(accountId), where
387
+ * accountId = credentials.accountId (trimmed) > env var (trimmed)
388
+ * > throw computedBaseURL.missingValueMessage
389
+ */
390
+ export declare function resolveOpenAICompatConfig(entry: OpenAICompatConfigInput, credentials?: OpenAICompatCredentials): {
391
+ apiKey: string;
392
+ baseURL: string;
393
+ };
@@ -1300,4 +1300,64 @@ export function describeAnthropicConfig() {
1300
1300
  lines.push(`Priority Access: ${config.limits.priorityAccess ? "Yes" : "No"}`);
1301
1301
  return lines.join("\n");
1302
1302
  }
1303
+ /**
1304
+ * Resolves the {apiKey, baseURL} pair for a config-driven OpenAI-compatible
1305
+ * catalog entry (see OpenAICompatCatalogEntry in types/providers.ts).
1306
+ *
1307
+ * Extracted from the identical 6-line precedence block that was copy-pasted
1308
+ * across groq.ts, xai.ts, togetherAi.ts, fireworks.ts, perplexity.ts, and
1309
+ * mistral.ts, plus Cloudflare's accountId-computed-baseURL variant.
1310
+ *
1311
+ * Precedence (matches every ported subclass's original behavior exactly):
1312
+ * apiKey: credentials.apiKey (trimmed, non-blank) > env var > throw
1313
+ * baseURL: credentials.baseURL (trimmed, non-blank)
1314
+ * > env var (if entry.baseURLEnvVar is set, trimmed, non-blank)
1315
+ * > entry.defaultBaseURL
1316
+ * baseURL (computedBaseURL entries, e.g. Cloudflare):
1317
+ * credentials.baseURL > computedBaseURL.build(accountId), where
1318
+ * accountId = credentials.accountId (trimmed) > env var (trimmed)
1319
+ * > throw computedBaseURL.missingValueMessage
1320
+ */
1321
+ export function resolveOpenAICompatConfig(entry, credentials) {
1322
+ const overrideApiKey = credentials?.apiKey?.trim();
1323
+ const apiKey = overrideApiKey && overrideApiKey.length > 0
1324
+ ? overrideApiKey
1325
+ : validateApiKey(entry.configOptions);
1326
+ if (entry.computedBaseURL) {
1327
+ const { envVar, missingValueMessage, build } = entry.computedBaseURL;
1328
+ // An explicit base URL is checked first and trimmed the same way the
1329
+ // static branch trims it. It makes the account id irrelevant — there is
1330
+ // nothing left to build — so demanding one anyway would reject a fully
1331
+ // specified override.
1332
+ const overrideComputedBaseURL = credentials?.baseURL?.trim();
1333
+ if (overrideComputedBaseURL && overrideComputedBaseURL.length > 0) {
1334
+ return { apiKey, baseURL: overrideComputedBaseURL };
1335
+ }
1336
+ const extraValue = (credentials?.accountId ??
1337
+ process.env[envVar] ??
1338
+ "").trim();
1339
+ if (!extraValue) {
1340
+ throw new Error(missingValueMessage);
1341
+ }
1342
+ return { apiKey, baseURL: build(extraValue) };
1343
+ }
1344
+ const overrideBaseURL = credentials?.baseURL?.trim();
1345
+ const envBaseURL = entry.baseURLEnvVar
1346
+ ? process.env[entry.baseURLEnvVar]?.trim()
1347
+ : undefined;
1348
+ const baseURL = (overrideBaseURL && overrideBaseURL.length > 0
1349
+ ? overrideBaseURL
1350
+ : undefined) ??
1351
+ (envBaseURL && envBaseURL.length > 0 ? envBaseURL : undefined) ??
1352
+ entry.defaultBaseURL;
1353
+ if (!baseURL) {
1354
+ // Reachable only for an entry that sets neither defaultBaseURL nor
1355
+ // computedBaseURL. Returning "" instead would hand the SDK an empty base
1356
+ // URL and surface as a confusing request failure far from the cause.
1357
+ throw new Error(`${entry.providerName}: no base URL. Set one in credentials` +
1358
+ (entry.baseURLEnvVar ? `, set ${entry.baseURLEnvVar}` : "") +
1359
+ `, or give the catalog entry a defaultBaseURL.`);
1360
+ }
1361
+ return { apiKey, baseURL };
1362
+ }
1303
1363
  //# sourceMappingURL=providerConfig.js.map
@@ -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,59 @@
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
+ }
@@ -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[];