@juspay/neurolink 12.5.3 → 12.6.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.
@@ -29,6 +29,17 @@ export type CliProxyClientConfigurator = {
29
29
  * alone and return false.
30
30
  */
31
31
  restore: (proxyBaseUrl: string) => Promise<boolean>;
32
+ /**
33
+ * Something the user must still do for apply() to take effect.
34
+ *
35
+ * Writing a file is not the same as being in effect. Copilot reads its
36
+ * provider settings from the environment only, so its configurator writes a
37
+ * script the user has to source; until they do, the proxy reports a green
38
+ * check for a file nothing reads. Returning a string here lets a client say
39
+ * "written, but not yet live, and here is the one line that fixes it".
40
+ * Return null when nothing is outstanding.
41
+ */
42
+ postApplyNote?: (proxyBaseUrl: string) => Promise<string | null>;
32
43
  };
33
44
  /** Outcome of applying one configurator, for per-client CLI reporting. */
34
45
  export type CliProxyClientApplyResult = {
@@ -36,6 +47,12 @@ export type CliProxyClientApplyResult = {
36
47
  displayName: string;
37
48
  /** True only when the configurator actually wrote configuration. */
38
49
  applied: boolean;
50
+ /**
51
+ * Set when the write landed but is not yet in effect — see
52
+ * CliProxyClientConfigurator.postApplyNote. Callers must render this; a
53
+ * silent note is the failure it exists to prevent.
54
+ */
55
+ note?: string;
39
56
  /** Present when the configurator threw; the caller decides how loud to be. */
40
57
  error?: Error;
41
58
  };
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * @module utils/providerRetry
15
15
  */
16
- import { NeuroLinkError } from "./errorHandling.js";
16
+ import { NeuroLinkError, isAbortError } from "./errorHandling.js";
17
17
  import { logger } from "./logger.js";
18
18
  import { APICallError } from "./generationErrors.js";
19
19
  import { parseRetryAfterMs } from "./retryAfter.js";
@@ -143,7 +143,50 @@ export function isOpenAIQuotaExhaustedError(error) {
143
143
  }
144
144
  return false;
145
145
  }
146
+ /**
147
+ * True when `error` is an abort, or wraps one at any depth via `cause`.
148
+ *
149
+ * `isAbortError` alone is not enough here because it inspects one value, and
150
+ * by the time a provider failure reaches the retry wrapper it has usually been
151
+ * re-thrown inside a provider-specific error whose `name` no longer says
152
+ * "AbortError". The depth bound stops a self-referential or maliciously deep
153
+ * `cause` chain from hanging the classifier; nothing legitimate nests further.
154
+ */
155
+ function isAbortLike(error, depth = 0) {
156
+ if (depth > 8 || error === null || typeof error !== "object") {
157
+ return isAbortError(error);
158
+ }
159
+ if (isAbortError(error)) {
160
+ return true;
161
+ }
162
+ const { cause } = error;
163
+ return cause === undefined || cause === error
164
+ ? false
165
+ : isAbortLike(cause, depth + 1);
166
+ }
146
167
  export function isRetryableProviderError(error) {
168
+ // Ahead of everything, for the same reason as the quota branch below but a
169
+ // worse failure: the caller has already walked away. Retrying a cancelled
170
+ // turn cannot succeed — the signal stays aborted, so every further attempt
171
+ // rejects without reaching the network — and it costs the full ladder,
172
+ // 2 retries at the NO_HINT_FLOOR_MS floor, before the turn ends.
173
+ //
174
+ // Measured on SageMaker before this guard: abort at 400ms, provider failed
175
+ // at 232ms with "Request aborted", then two more attempts 10s apart each
176
+ // failing in 1ms without a request leaving the process, and the turn finally
177
+ // threw at 21,996ms — 55x the time the cancellation should have taken, with
178
+ // `name` by then reading "Error" rather than "AbortError".
179
+ //
180
+ // The cause chain matters and is not defensive padding. Providers wrap
181
+ // transport failures in their own error class on the way up: SageMaker's
182
+ // handleSageMakerError() stamps an unrecognised error `statusCode: 500`
183
+ // while keeping the original as `cause`. That fabricated 500 is what made an
184
+ // abort look retryable here — the duck-typed status branch below reads it
185
+ // and returns true, outranking the wrapper's own `retryable: false`. Reading
186
+ // through `cause` finds the abort whatever wrapped it.
187
+ if (isAbortLike(error)) {
188
+ return false;
189
+ }
147
190
  // Before every other branch, including the SDK's own flag: a 429 carrying
148
191
  // `insufficient_quota` is marked retryable by the AI SDK because it only
149
192
  // looks at the status code. Retrying it burns the full ladder — 3 attempts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.5.3",
3
+ "version": "12.6.1",
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": {