@juspay/neurolink 10.8.1 → 10.8.2

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.
@@ -718,7 +718,7 @@ export declare class NeuroLink {
718
718
  * Curator P2-3: wraps a generate/stream call with the fallback
719
719
  * orchestration (`providerFallback` callback + `modelChain` walker).
720
720
  *
721
- * On a model-access-denied error from the inner call:
721
+ * On a qualifying error from the inner call:
722
722
  * 1. Resolve the effective callback (per-call > instance > synthesised
723
723
  * from modelChain) and the effective chain (per-call > instance).
724
724
  * 2. Walk attempts: invoke callback (or pop next chain entry) → emit
@@ -726,6 +726,12 @@ export declare class NeuroLink {
726
726
  * model}.
727
727
  * 3. Stop on first success, on a callback returning null, or after
728
728
  * exhausting the chain (throw the most recent error).
729
+ *
730
+ * What qualifies depends on how fallback was configured: an EXPLICIT
731
+ * `providerFallback` callback is consulted for any error except client
732
+ * aborts (the callback owns the decision — it receives the error
733
+ * unmodified and can return null to bubble), while modelChain-only
734
+ * configs keep the narrow model-access-denied gate.
729
735
  */
730
736
  private runWithFallbackOrchestration;
731
737
  private attemptInner;
@@ -3261,7 +3261,7 @@ Current user's request: ${currentInput}`;
3261
3261
  * Curator P2-3: wraps a generate/stream call with the fallback
3262
3262
  * orchestration (`providerFallback` callback + `modelChain` walker).
3263
3263
  *
3264
- * On a model-access-denied error from the inner call:
3264
+ * On a qualifying error from the inner call:
3265
3265
  * 1. Resolve the effective callback (per-call > instance > synthesised
3266
3266
  * from modelChain) and the effective chain (per-call > instance).
3267
3267
  * 2. Walk attempts: invoke callback (or pop next chain entry) → emit
@@ -3269,6 +3269,12 @@ Current user's request: ${currentInput}`;
3269
3269
  * model}.
3270
3270
  * 3. Stop on first success, on a callback returning null, or after
3271
3271
  * exhausting the chain (throw the most recent error).
3272
+ *
3273
+ * What qualifies depends on how fallback was configured: an EXPLICIT
3274
+ * `providerFallback` callback is consulted for any error except client
3275
+ * aborts (the callback owns the decision — it receives the error
3276
+ * unmodified and can return null to bubble), while modelChain-only
3277
+ * configs keep the narrow model-access-denied gate.
3272
3278
  */
3273
3279
  async runWithFallbackOrchestration(optionsOrPrompt, kind, inner) {
3274
3280
  const initialAttempt = await this.attemptInner(inner, optionsOrPrompt);
@@ -3276,10 +3282,8 @@ Current user's request: ${currentInput}`;
3276
3282
  return initialAttempt.ok;
3277
3283
  }
3278
3284
  let lastError = initialAttempt.error;
3279
- if (!looksLikeModelAccessDenied(lastError)) {
3280
- throw lastError;
3281
- }
3282
- // Build the chain orchestration.
3285
+ // Resolve the fallback configuration BEFORE gating so the gate can
3286
+ // distinguish an explicit callback from a modelChain-only setup.
3283
3287
  const requestedProvider = (typeof optionsOrPrompt === "object"
3284
3288
  ? optionsOrPrompt.provider
3285
3289
  : undefined);
@@ -3293,6 +3297,14 @@ Current user's request: ${currentInput}`;
3293
3297
  const perCallChain = callOpts.modelChain;
3294
3298
  const effectiveCallback = perCallCallback ?? this.fallbackConfig.providerFallback;
3295
3299
  const effectiveChain = perCallChain ?? this.fallbackConfig.modelChain;
3300
+ // Explicit callback (per-call or instance providerFallback): the callback
3301
+ // owns the decision for any error except client aborts — it can return
3302
+ // null to bubble. modelChain-only keeps the narrow model-access-denied
3303
+ // gate so chain walkers don't retry errors the chain can't fix.
3304
+ const shouldOrchestrateFallback = (err) => effectiveCallback ? !isAbortError(err) : looksLikeModelAccessDenied(err);
3305
+ if (!shouldOrchestrateFallback(lastError)) {
3306
+ throw lastError;
3307
+ }
3296
3308
  if (!effectiveCallback && !effectiveChain) {
3297
3309
  throw lastError;
3298
3310
  }
@@ -3364,7 +3376,7 @@ Current user's request: ${currentInput}`;
3364
3376
  }
3365
3377
  lastError = retryAttempt.error;
3366
3378
  attemptedRequestedModel = next.model ?? attemptedRequestedModel;
3367
- if (!looksLikeModelAccessDenied(lastError)) {
3379
+ if (!shouldOrchestrateFallback(lastError)) {
3368
3380
  throw lastError;
3369
3381
  }
3370
3382
  }
@@ -724,6 +724,12 @@ export class AnthropicProvider extends BaseProvider {
724
724
  headers["anthropic-beta"] = ANTHROPIC_BETA_HEADERS["anthropic-beta"];
725
725
  }
726
726
  }
727
+ if (usingProxy) {
728
+ // WAFs in front of ANTHROPIC_BASE_URL proxies commonly block the bare
729
+ // SDK UA ("Anthropic/JS x.y.z"); send the claude-cli UA the OAuth path
730
+ // already uses. Direct-to-Anthropic traffic keeps the honest SDK UA.
731
+ headers["User-Agent"] = CLAUDE_CLI_USER_AGENT;
732
+ }
727
733
  // Add subscription-specific headers if applicable
728
734
  if (this.subscriptionTier !== "api") {
729
735
  headers["x-subscription-tier"] = this.subscriptionTier;
@@ -27,10 +27,14 @@ export type NeuroLinkConfig = {
27
27
  [key: string]: unknown;
28
28
  };
29
29
  /**
30
- * Curator P2-3: callback signature for centralized fallback policy. Invoked
31
- * when a generate/stream call fails with what looks like a model-access-denied
32
- * error. Return `{ provider, model }` (either / both optional) to drive a
33
- * retry; return `null` to bubble the original error untouched.
30
+ * Curator P2-3: callback signature for centralized fallback policy. When an
31
+ * explicit callback is configured (per-call or instance), it is invoked for
32
+ * ANY error thrown by a generate/stream call except client aborts network
33
+ * errors, 5xx, timeouts, auth failures included. The callback receives the
34
+ * error unmodified so hosts can classify it themselves (status codes,
35
+ * `isNonRetryableProviderError`, …). Return `{ provider, model }` (either /
36
+ * both optional) to drive a retry; return `null` to bubble the original
37
+ * error untouched.
34
38
  */
35
39
  export type ProviderFallbackCallback = (error: unknown) => Promise<{
36
40
  provider?: string;
@@ -65,16 +69,21 @@ export type NeurolinkConstructorConfig = {
65
69
  */
66
70
  credentials?: NeurolinkCredentials;
67
71
  /**
68
- * Curator P2-3: callback invoked on model-access-denied. Lets a host (e.g.
69
- * Curator) centrally drive fallback policy. The callback receives the
70
- * original error and returns the next `{ provider, model }` to try, or
71
- * `null` to bubble the error.
72
+ * Curator P2-3: callback invoked when a generate/stream call fails with
73
+ * any error except a client abort (network errors, 5xx, timeouts, auth
74
+ * failures, model-access-denied, …). Lets a host (e.g. Curator) centrally
75
+ * drive fallback policy "provider A primary, provider B on failure".
76
+ * The callback receives the original error unmodified and returns the
77
+ * next `{ provider, model }` to try, or `null` to bubble the error.
72
78
  */
73
79
  providerFallback?: ProviderFallbackCallback;
74
80
  /**
75
- * Curator P2-3: ordered list of model names to try in sequence on
76
- * model-access-denied. Sugar over `providerFallback`. The current
77
- * provider is preserved across the chain; only the model name changes.
81
+ * Curator P2-3: ordered list of model names to try in sequence. Sugar
82
+ * over `providerFallback`, but with a narrower trigger: without an
83
+ * explicit callback the chain only advances on model-access-denied
84
+ * errors — other failures (network, 5xx, timeouts) bubble immediately.
85
+ * The current provider is preserved across the chain; only the model
86
+ * name changes.
78
87
  */
79
88
  modelChain?: string[];
80
89
  /**
@@ -577,6 +577,9 @@ export type GenerateOptions = {
577
577
  /**
578
578
  * Curator P2-3: per-call fallback callback. Overrides any
579
579
  * instance-level `providerFallback` set on `new NeuroLink({...})`.
580
+ * Invoked for any error except client aborts (network errors, 5xx,
581
+ * timeouts, auth failures, model-access-denied, …); receives the error
582
+ * unmodified. Return `{ provider, model }` to retry, `null` to bubble.
580
583
  */
581
584
  providerFallback?: (error: unknown) => Promise<{
582
585
  provider?: string;
@@ -584,7 +587,9 @@ export type GenerateOptions = {
584
587
  } | null>;
585
588
  /**
586
589
  * Curator P2-3: per-call ordered model chain. Overrides any
587
- * instance-level `modelChain`. Tried in order on model-access-denied.
590
+ * instance-level `modelChain`. Without an explicit `providerFallback`
591
+ * callback the chain only advances on model-access-denied errors —
592
+ * other failures (network, 5xx, timeouts) bubble immediately.
588
593
  */
589
594
  modelChain?: string[];
590
595
  /**
@@ -487,6 +487,11 @@ export type StreamOptions = {
487
487
  /**
488
488
  * Curator P2-3: per-call fallback callback. Overrides any
489
489
  * instance-level `providerFallback` set on `new NeuroLink({...})`.
490
+ * Invoked for any error thrown while establishing the stream, except
491
+ * client aborts (network errors, 5xx, timeouts, auth failures,
492
+ * model-access-denied, …); receives the error unmodified. There is no
493
+ * mid-stream resume once chunks are flowing. Return `{ provider,
494
+ * model }` to retry, `null` to bubble.
490
495
  */
491
496
  providerFallback?: (error: unknown) => Promise<{
492
497
  provider?: string;
@@ -494,7 +499,9 @@ export type StreamOptions = {
494
499
  } | null>;
495
500
  /**
496
501
  * Curator P2-3: per-call ordered model chain. Overrides any
497
- * instance-level `modelChain`. Tried in order on model-access-denied.
502
+ * instance-level `modelChain`. Without an explicit `providerFallback`
503
+ * callback the chain only advances on model-access-denied errors —
504
+ * other failures (network, 5xx, timeouts) bubble immediately.
498
505
  */
499
506
  modelChain?: string[];
500
507
  /**
@@ -718,7 +718,7 @@ export declare class NeuroLink {
718
718
  * Curator P2-3: wraps a generate/stream call with the fallback
719
719
  * orchestration (`providerFallback` callback + `modelChain` walker).
720
720
  *
721
- * On a model-access-denied error from the inner call:
721
+ * On a qualifying error from the inner call:
722
722
  * 1. Resolve the effective callback (per-call > instance > synthesised
723
723
  * from modelChain) and the effective chain (per-call > instance).
724
724
  * 2. Walk attempts: invoke callback (or pop next chain entry) → emit
@@ -726,6 +726,12 @@ export declare class NeuroLink {
726
726
  * model}.
727
727
  * 3. Stop on first success, on a callback returning null, or after
728
728
  * exhausting the chain (throw the most recent error).
729
+ *
730
+ * What qualifies depends on how fallback was configured: an EXPLICIT
731
+ * `providerFallback` callback is consulted for any error except client
732
+ * aborts (the callback owns the decision — it receives the error
733
+ * unmodified and can return null to bubble), while modelChain-only
734
+ * configs keep the narrow model-access-denied gate.
729
735
  */
730
736
  private runWithFallbackOrchestration;
731
737
  private attemptInner;
package/dist/neurolink.js CHANGED
@@ -3261,7 +3261,7 @@ Current user's request: ${currentInput}`;
3261
3261
  * Curator P2-3: wraps a generate/stream call with the fallback
3262
3262
  * orchestration (`providerFallback` callback + `modelChain` walker).
3263
3263
  *
3264
- * On a model-access-denied error from the inner call:
3264
+ * On a qualifying error from the inner call:
3265
3265
  * 1. Resolve the effective callback (per-call > instance > synthesised
3266
3266
  * from modelChain) and the effective chain (per-call > instance).
3267
3267
  * 2. Walk attempts: invoke callback (or pop next chain entry) → emit
@@ -3269,6 +3269,12 @@ Current user's request: ${currentInput}`;
3269
3269
  * model}.
3270
3270
  * 3. Stop on first success, on a callback returning null, or after
3271
3271
  * exhausting the chain (throw the most recent error).
3272
+ *
3273
+ * What qualifies depends on how fallback was configured: an EXPLICIT
3274
+ * `providerFallback` callback is consulted for any error except client
3275
+ * aborts (the callback owns the decision — it receives the error
3276
+ * unmodified and can return null to bubble), while modelChain-only
3277
+ * configs keep the narrow model-access-denied gate.
3272
3278
  */
3273
3279
  async runWithFallbackOrchestration(optionsOrPrompt, kind, inner) {
3274
3280
  const initialAttempt = await this.attemptInner(inner, optionsOrPrompt);
@@ -3276,10 +3282,8 @@ Current user's request: ${currentInput}`;
3276
3282
  return initialAttempt.ok;
3277
3283
  }
3278
3284
  let lastError = initialAttempt.error;
3279
- if (!looksLikeModelAccessDenied(lastError)) {
3280
- throw lastError;
3281
- }
3282
- // Build the chain orchestration.
3285
+ // Resolve the fallback configuration BEFORE gating so the gate can
3286
+ // distinguish an explicit callback from a modelChain-only setup.
3283
3287
  const requestedProvider = (typeof optionsOrPrompt === "object"
3284
3288
  ? optionsOrPrompt.provider
3285
3289
  : undefined);
@@ -3293,6 +3297,14 @@ Current user's request: ${currentInput}`;
3293
3297
  const perCallChain = callOpts.modelChain;
3294
3298
  const effectiveCallback = perCallCallback ?? this.fallbackConfig.providerFallback;
3295
3299
  const effectiveChain = perCallChain ?? this.fallbackConfig.modelChain;
3300
+ // Explicit callback (per-call or instance providerFallback): the callback
3301
+ // owns the decision for any error except client aborts — it can return
3302
+ // null to bubble. modelChain-only keeps the narrow model-access-denied
3303
+ // gate so chain walkers don't retry errors the chain can't fix.
3304
+ const shouldOrchestrateFallback = (err) => effectiveCallback ? !isAbortError(err) : looksLikeModelAccessDenied(err);
3305
+ if (!shouldOrchestrateFallback(lastError)) {
3306
+ throw lastError;
3307
+ }
3296
3308
  if (!effectiveCallback && !effectiveChain) {
3297
3309
  throw lastError;
3298
3310
  }
@@ -3364,7 +3376,7 @@ Current user's request: ${currentInput}`;
3364
3376
  }
3365
3377
  lastError = retryAttempt.error;
3366
3378
  attemptedRequestedModel = next.model ?? attemptedRequestedModel;
3367
- if (!looksLikeModelAccessDenied(lastError)) {
3379
+ if (!shouldOrchestrateFallback(lastError)) {
3368
3380
  throw lastError;
3369
3381
  }
3370
3382
  }
@@ -724,6 +724,12 @@ export class AnthropicProvider extends BaseProvider {
724
724
  headers["anthropic-beta"] = ANTHROPIC_BETA_HEADERS["anthropic-beta"];
725
725
  }
726
726
  }
727
+ if (usingProxy) {
728
+ // WAFs in front of ANTHROPIC_BASE_URL proxies commonly block the bare
729
+ // SDK UA ("Anthropic/JS x.y.z"); send the claude-cli UA the OAuth path
730
+ // already uses. Direct-to-Anthropic traffic keeps the honest SDK UA.
731
+ headers["User-Agent"] = CLAUDE_CLI_USER_AGENT;
732
+ }
727
733
  // Add subscription-specific headers if applicable
728
734
  if (this.subscriptionTier !== "api") {
729
735
  headers["x-subscription-tier"] = this.subscriptionTier;
@@ -27,10 +27,14 @@ export type NeuroLinkConfig = {
27
27
  [key: string]: unknown;
28
28
  };
29
29
  /**
30
- * Curator P2-3: callback signature for centralized fallback policy. Invoked
31
- * when a generate/stream call fails with what looks like a model-access-denied
32
- * error. Return `{ provider, model }` (either / both optional) to drive a
33
- * retry; return `null` to bubble the original error untouched.
30
+ * Curator P2-3: callback signature for centralized fallback policy. When an
31
+ * explicit callback is configured (per-call or instance), it is invoked for
32
+ * ANY error thrown by a generate/stream call except client aborts network
33
+ * errors, 5xx, timeouts, auth failures included. The callback receives the
34
+ * error unmodified so hosts can classify it themselves (status codes,
35
+ * `isNonRetryableProviderError`, …). Return `{ provider, model }` (either /
36
+ * both optional) to drive a retry; return `null` to bubble the original
37
+ * error untouched.
34
38
  */
35
39
  export type ProviderFallbackCallback = (error: unknown) => Promise<{
36
40
  provider?: string;
@@ -65,16 +69,21 @@ export type NeurolinkConstructorConfig = {
65
69
  */
66
70
  credentials?: NeurolinkCredentials;
67
71
  /**
68
- * Curator P2-3: callback invoked on model-access-denied. Lets a host (e.g.
69
- * Curator) centrally drive fallback policy. The callback receives the
70
- * original error and returns the next `{ provider, model }` to try, or
71
- * `null` to bubble the error.
72
+ * Curator P2-3: callback invoked when a generate/stream call fails with
73
+ * any error except a client abort (network errors, 5xx, timeouts, auth
74
+ * failures, model-access-denied, …). Lets a host (e.g. Curator) centrally
75
+ * drive fallback policy "provider A primary, provider B on failure".
76
+ * The callback receives the original error unmodified and returns the
77
+ * next `{ provider, model }` to try, or `null` to bubble the error.
72
78
  */
73
79
  providerFallback?: ProviderFallbackCallback;
74
80
  /**
75
- * Curator P2-3: ordered list of model names to try in sequence on
76
- * model-access-denied. Sugar over `providerFallback`. The current
77
- * provider is preserved across the chain; only the model name changes.
81
+ * Curator P2-3: ordered list of model names to try in sequence. Sugar
82
+ * over `providerFallback`, but with a narrower trigger: without an
83
+ * explicit callback the chain only advances on model-access-denied
84
+ * errors — other failures (network, 5xx, timeouts) bubble immediately.
85
+ * The current provider is preserved across the chain; only the model
86
+ * name changes.
78
87
  */
79
88
  modelChain?: string[];
80
89
  /**
@@ -577,6 +577,9 @@ export type GenerateOptions = {
577
577
  /**
578
578
  * Curator P2-3: per-call fallback callback. Overrides any
579
579
  * instance-level `providerFallback` set on `new NeuroLink({...})`.
580
+ * Invoked for any error except client aborts (network errors, 5xx,
581
+ * timeouts, auth failures, model-access-denied, …); receives the error
582
+ * unmodified. Return `{ provider, model }` to retry, `null` to bubble.
580
583
  */
581
584
  providerFallback?: (error: unknown) => Promise<{
582
585
  provider?: string;
@@ -584,7 +587,9 @@ export type GenerateOptions = {
584
587
  } | null>;
585
588
  /**
586
589
  * Curator P2-3: per-call ordered model chain. Overrides any
587
- * instance-level `modelChain`. Tried in order on model-access-denied.
590
+ * instance-level `modelChain`. Without an explicit `providerFallback`
591
+ * callback the chain only advances on model-access-denied errors —
592
+ * other failures (network, 5xx, timeouts) bubble immediately.
588
593
  */
589
594
  modelChain?: string[];
590
595
  /**
@@ -487,6 +487,11 @@ export type StreamOptions = {
487
487
  /**
488
488
  * Curator P2-3: per-call fallback callback. Overrides any
489
489
  * instance-level `providerFallback` set on `new NeuroLink({...})`.
490
+ * Invoked for any error thrown while establishing the stream, except
491
+ * client aborts (network errors, 5xx, timeouts, auth failures,
492
+ * model-access-denied, …); receives the error unmodified. There is no
493
+ * mid-stream resume once chunks are flowing. Return `{ provider,
494
+ * model }` to retry, `null` to bubble.
490
495
  */
491
496
  providerFallback?: (error: unknown) => Promise<{
492
497
  provider?: string;
@@ -494,7 +499,9 @@ export type StreamOptions = {
494
499
  } | null>;
495
500
  /**
496
501
  * Curator P2-3: per-call ordered model chain. Overrides any
497
- * instance-level `modelChain`. Tried in order on model-access-denied.
502
+ * instance-level `modelChain`. Without an explicit `providerFallback`
503
+ * callback the chain only advances on model-access-denied errors —
504
+ * other failures (network, 5xx, timeouts) bubble immediately.
498
505
  */
499
506
  modelChain?: string[];
500
507
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.8.1",
3
+ "version": "10.8.2",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {
@@ -94,6 +94,7 @@
94
94
  "test:mcp:limits": "npx tsx test/continuous-test-suite-mcp-output-limits.ts",
95
95
  "test:mcp:infra": "npx tsx test/continuous-test-suite-mcp-infra.ts",
96
96
  "test:providers-mocked": "npx tsx test/continuous-test-suite-providers-mocked.ts",
97
+ "test:provider-fallback": "npx tsx test/continuous-test-suite-provider-fallback.ts",
97
98
  "test:rag": "npx tsx test/continuous-test-suite-rag.ts",
98
99
  "test:vector-pinecone": "npx tsx test/continuous-test-suite-vector-pinecone.ts",
99
100
  "test:vector-pgvector": "npx tsx test/continuous-test-suite-vector-pgvector.ts",