@juspay/neurolink 10.8.2 → 10.8.4
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 +12 -0
- package/dist/adapters/providerImageAdapter.js +35 -0
- package/dist/browser/neurolink.min.js +363 -363
- package/dist/core/baseProvider.js +25 -0
- package/dist/lib/adapters/providerImageAdapter.js +35 -0
- package/dist/lib/core/baseProvider.js +25 -0
- package/dist/lib/neurolink.js +27 -5
- package/dist/lib/providers/anthropic.js +9 -0
- package/dist/lib/providers/googleVertex.js +4 -0
- package/dist/lib/providers/openaiChatCompletionsClient.js +9 -0
- package/dist/lib/types/config.d.ts +16 -11
- package/dist/lib/types/generate.d.ts +5 -3
- package/dist/lib/types/providers.d.ts +2 -0
- package/dist/lib/types/stream.d.ts +6 -5
- package/dist/lib/utils/providerRetry.d.ts +25 -2
- package/dist/lib/utils/providerRetry.js +88 -14
- package/dist/neurolink.js +27 -5
- package/dist/providers/anthropic.js +9 -0
- package/dist/providers/googleVertex.js +4 -0
- package/dist/providers/openaiChatCompletionsClient.js +9 -0
- package/dist/types/config.d.ts +16 -11
- package/dist/types/generate.d.ts +5 -3
- package/dist/types/providers.d.ts +2 -0
- package/dist/types/stream.d.ts +6 -5
- package/dist/utils/providerRetry.d.ts +25 -2
- package/dist/utils/providerRetry.js +88 -14
- package/package.json +2 -1
package/dist/types/config.d.ts
CHANGED
|
@@ -29,12 +29,15 @@ export type NeuroLinkConfig = {
|
|
|
29
29
|
/**
|
|
30
30
|
* Curator P2-3: callback signature for centralized fallback policy. When an
|
|
31
31
|
* explicit callback is configured (per-call or instance), it is invoked for
|
|
32
|
-
* ANY error thrown by a generate/stream call except
|
|
33
|
-
* errors, 5xx, timeouts, auth failures included.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* error
|
|
32
|
+
* ANY error thrown by a generate/stream call except genuine caller cancels —
|
|
33
|
+
* network errors, 5xx, timeouts, auth failures included. A caller cancel is
|
|
34
|
+
* identified by the caller-supplied `abortSignal` having fired, not by error
|
|
35
|
+
* shape: abort-shaped errors from NeuroLink's own turn/stall watchdogs and
|
|
36
|
+
* per-step timeouts DO invoke the callback, so provider hangs can fall back.
|
|
37
|
+
* The callback receives the error unmodified so hosts can classify it
|
|
38
|
+
* themselves (status codes, `isNonRetryableProviderError`, …). Return
|
|
39
|
+
* `{ provider, model }` (either / both optional) to drive a retry; return
|
|
40
|
+
* `null` to bubble the original error untouched.
|
|
38
41
|
*/
|
|
39
42
|
export type ProviderFallbackCallback = (error: unknown) => Promise<{
|
|
40
43
|
provider?: string;
|
|
@@ -70,11 +73,13 @@ export type NeurolinkConstructorConfig = {
|
|
|
70
73
|
credentials?: NeurolinkCredentials;
|
|
71
74
|
/**
|
|
72
75
|
* Curator P2-3: callback invoked when a generate/stream call fails with
|
|
73
|
-
* any error except a
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* any error except a genuine caller cancel — i.e. the caller-supplied
|
|
77
|
+
* `abortSignal` fired (network errors, 5xx, timeouts, auth failures,
|
|
78
|
+
* model-access-denied, and internal watchdog aborts all invoke it). Lets
|
|
79
|
+
* a host (e.g. Curator) centrally drive fallback policy — "provider A
|
|
80
|
+
* primary, provider B on failure". The callback receives the original
|
|
81
|
+
* error unmodified and returns the next `{ provider, model }` to try, or
|
|
82
|
+
* `null` to bubble the error.
|
|
78
83
|
*/
|
|
79
84
|
providerFallback?: ProviderFallbackCallback;
|
|
80
85
|
/**
|
package/dist/types/generate.d.ts
CHANGED
|
@@ -577,9 +577,11 @@ 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
|
|
581
|
-
*
|
|
582
|
-
*
|
|
580
|
+
* Invoked for any error except a genuine caller cancel — i.e. this
|
|
581
|
+
* call's `abortSignal` fired (network errors, 5xx, timeouts, auth
|
|
582
|
+
* failures, model-access-denied, and internal watchdog aborts all invoke
|
|
583
|
+
* it); receives the error unmodified. Return `{ provider, model }` to
|
|
584
|
+
* retry, `null` to bubble.
|
|
583
585
|
*/
|
|
584
586
|
providerFallback?: (error: unknown) => Promise<{
|
|
585
587
|
provider?: string;
|
|
@@ -925,6 +925,8 @@ export type AnthropicVertexSettings = {
|
|
|
925
925
|
region: string;
|
|
926
926
|
/** SDK request timeout in milliseconds */
|
|
927
927
|
timeout?: number;
|
|
928
|
+
/** SDK-internal retry budget (transport retries are the orchestrator's job) */
|
|
929
|
+
maxRetries?: number;
|
|
928
930
|
};
|
|
929
931
|
/**
|
|
930
932
|
* OpenAI-compatible models endpoint response structure
|
package/dist/types/stream.d.ts
CHANGED
|
@@ -487,11 +487,12 @@ 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
|
-
*
|
|
492
|
-
* model-access-denied,
|
|
493
|
-
*
|
|
494
|
-
*
|
|
490
|
+
* Invoked for any error thrown while establishing the stream, except a
|
|
491
|
+
* genuine caller cancel — i.e. this call's `abortSignal` fired (network
|
|
492
|
+
* errors, 5xx, timeouts, auth failures, model-access-denied, and
|
|
493
|
+
* internal watchdog aborts all invoke it); receives the error
|
|
494
|
+
* unmodified. There is no mid-stream resume once chunks are flowing.
|
|
495
|
+
* Return `{ provider, model }` to retry, `null` to bubble.
|
|
495
496
|
*/
|
|
496
497
|
providerFallback?: (error: unknown) => Promise<{
|
|
497
498
|
provider?: string;
|
|
@@ -20,8 +20,16 @@ export declare const MAX_PROVIDER_RETRIES = 2;
|
|
|
20
20
|
export declare const BASE_RETRY_DELAY_MS = 1000;
|
|
21
21
|
/** Minimum delay in ms when a retryable response provides no retry timing. */
|
|
22
22
|
export declare const NO_HINT_FLOOR_MS = 10000;
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Maximum server-requested retry delay honored by provider retries.
|
|
25
|
+
*
|
|
26
|
+
* A hint above this cap is not clamped-and-slept: the server declared an
|
|
27
|
+
* unavailability window we are not willing to wait out, so the error is
|
|
28
|
+
* surfaced immediately instead — fallback orchestration (providerFallback /
|
|
29
|
+
* modelChain) can route to another provider, and callers without fallback
|
|
30
|
+
* get a prompt rate-limit error rather than a silent multi-minute stall.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MAX_RETRY_AFTER_MS = 60000;
|
|
25
33
|
/**
|
|
26
34
|
* Check whether an error thrown by the AI SDK is retryable.
|
|
27
35
|
*
|
|
@@ -29,6 +37,21 @@ export declare const MAX_RETRY_AFTER_MS = 120000;
|
|
|
29
37
|
* uses a branded symbol marker, so `instanceof` doesn't work across package
|
|
30
38
|
* boundaries). Falls back to duck-typing for non-APICallError cases.
|
|
31
39
|
*/
|
|
40
|
+
/**
|
|
41
|
+
* Duck-typed HTTP status extraction for errors of unknown provenance.
|
|
42
|
+
* NeuroLink's hand-rolled clients stamp `.statusCode`; official SDK errors
|
|
43
|
+
* (e.g. @anthropic-ai/sdk APIError) expose `.status`. Shared with
|
|
44
|
+
* baseProvider's handleProviderError metadata preservation.
|
|
45
|
+
*/
|
|
46
|
+
export declare function duckTypedStatusCode(error: unknown): number | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Duck-typed Retry-After extraction for errors of unknown provenance.
|
|
49
|
+
* Reads a pre-parsed `.retryAfterMs` (stamped by metadata-preserving
|
|
50
|
+
* wrappers like handleProviderError), then `.headers` (Headers instance or
|
|
51
|
+
* record — e.g. @anthropic-ai/sdk APIError), then `.responseHeaders` (the
|
|
52
|
+
* hand-rolled OpenAI-compatible client's record). Shared with baseProvider.
|
|
53
|
+
*/
|
|
54
|
+
export declare function extractRetryAfterMsFromError(error: unknown): number | undefined;
|
|
32
55
|
export declare function isRetryableProviderError(error: unknown): boolean;
|
|
33
56
|
/**
|
|
34
57
|
* Extract the HTTP status code from an AI SDK error, if available.
|
|
@@ -23,8 +23,16 @@ export const MAX_PROVIDER_RETRIES = 2;
|
|
|
23
23
|
export const BASE_RETRY_DELAY_MS = 1000;
|
|
24
24
|
/** Minimum delay in ms when a retryable response provides no retry timing. */
|
|
25
25
|
export const NO_HINT_FLOOR_MS = 10_000;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Maximum server-requested retry delay honored by provider retries.
|
|
28
|
+
*
|
|
29
|
+
* A hint above this cap is not clamped-and-slept: the server declared an
|
|
30
|
+
* unavailability window we are not willing to wait out, so the error is
|
|
31
|
+
* surfaced immediately instead — fallback orchestration (providerFallback /
|
|
32
|
+
* modelChain) can route to another provider, and callers without fallback
|
|
33
|
+
* get a prompt rate-limit error rather than a silent multi-minute stall.
|
|
34
|
+
*/
|
|
35
|
+
export const MAX_RETRY_AFTER_MS = 60_000;
|
|
28
36
|
const sleepWithTimeout = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
29
37
|
/**
|
|
30
38
|
* Check whether an error thrown by the AI SDK is retryable.
|
|
@@ -33,6 +41,54 @@ const sleepWithTimeout = (delayMs) => new Promise((resolve) => setTimeout(resolv
|
|
|
33
41
|
* uses a branded symbol marker, so `instanceof` doesn't work across package
|
|
34
42
|
* boundaries). Falls back to duck-typing for non-APICallError cases.
|
|
35
43
|
*/
|
|
44
|
+
/**
|
|
45
|
+
* Duck-typed HTTP status extraction for errors of unknown provenance.
|
|
46
|
+
* NeuroLink's hand-rolled clients stamp `.statusCode`; official SDK errors
|
|
47
|
+
* (e.g. @anthropic-ai/sdk APIError) expose `.status`. Shared with
|
|
48
|
+
* baseProvider's handleProviderError metadata preservation.
|
|
49
|
+
*/
|
|
50
|
+
export function duckTypedStatusCode(error) {
|
|
51
|
+
if (!error || typeof error !== "object") {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const err = error;
|
|
55
|
+
if (typeof err.statusCode === "number") {
|
|
56
|
+
return err.statusCode;
|
|
57
|
+
}
|
|
58
|
+
if (typeof err.status === "number") {
|
|
59
|
+
return err.status;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Duck-typed Retry-After extraction for errors of unknown provenance.
|
|
65
|
+
* Reads a pre-parsed `.retryAfterMs` (stamped by metadata-preserving
|
|
66
|
+
* wrappers like handleProviderError), then `.headers` (Headers instance or
|
|
67
|
+
* record — e.g. @anthropic-ai/sdk APIError), then `.responseHeaders` (the
|
|
68
|
+
* hand-rolled OpenAI-compatible client's record). Shared with baseProvider.
|
|
69
|
+
*/
|
|
70
|
+
export function extractRetryAfterMsFromError(error) {
|
|
71
|
+
if (!error || typeof error !== "object") {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const err = error;
|
|
75
|
+
// Number.isFinite rejects NaN/Infinity: typeof NaN === "number", and a NaN
|
|
76
|
+
// hint would survive Math.max() into sleep(NaN) — an immediate retry
|
|
77
|
+
// instead of the no-hint floor.
|
|
78
|
+
if (typeof err.retryAfterMs === "number" &&
|
|
79
|
+
Number.isFinite(err.retryAfterMs)) {
|
|
80
|
+
return err.retryAfterMs;
|
|
81
|
+
}
|
|
82
|
+
for (const candidate of [err.headers, err.responseHeaders]) {
|
|
83
|
+
if (candidate && typeof candidate === "object") {
|
|
84
|
+
const parsed = parseRetryAfterMs(candidate);
|
|
85
|
+
if (parsed !== undefined) {
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
36
92
|
export function isRetryableProviderError(error) {
|
|
37
93
|
// Preferred path: use the AI SDK's own branded type check + isRetryable flag
|
|
38
94
|
if (APICallError.isInstance(error)) {
|
|
@@ -42,8 +98,8 @@ export function isRetryableProviderError(error) {
|
|
|
42
98
|
return error.retriable;
|
|
43
99
|
}
|
|
44
100
|
// Fallback: duck-type for status codes on errors that aren't APICallError
|
|
45
|
-
|
|
46
|
-
|
|
101
|
+
const statusCode = duckTypedStatusCode(error);
|
|
102
|
+
if (statusCode !== undefined) {
|
|
47
103
|
return statusCode === 429 || statusCode >= 500;
|
|
48
104
|
}
|
|
49
105
|
return false;
|
|
@@ -55,10 +111,7 @@ export function getErrorStatusCode(error) {
|
|
|
55
111
|
if (APICallError.isInstance(error)) {
|
|
56
112
|
return error.statusCode;
|
|
57
113
|
}
|
|
58
|
-
|
|
59
|
-
return error.statusCode;
|
|
60
|
-
}
|
|
61
|
-
return undefined;
|
|
114
|
+
return duckTypedStatusCode(error);
|
|
62
115
|
}
|
|
63
116
|
function getRetryAfterMs(error) {
|
|
64
117
|
if (APICallError.isInstance(error) && error.responseHeaders) {
|
|
@@ -67,10 +120,12 @@ function getRetryAfterMs(error) {
|
|
|
67
120
|
return parsedDelay;
|
|
68
121
|
}
|
|
69
122
|
}
|
|
70
|
-
if (error instanceof NeuroLinkError &&
|
|
123
|
+
if (error instanceof NeuroLinkError &&
|
|
124
|
+
error.retryAfterMs !== undefined &&
|
|
125
|
+
Number.isFinite(error.retryAfterMs)) {
|
|
71
126
|
return error.retryAfterMs;
|
|
72
127
|
}
|
|
73
|
-
return
|
|
128
|
+
return extractRetryAfterMsFromError(error);
|
|
74
129
|
}
|
|
75
130
|
/**
|
|
76
131
|
* Execute a provider call with instrumented retry logic.
|
|
@@ -110,10 +165,29 @@ export async function withProviderRetry(operation, span, label, sleep = sleepWit
|
|
|
110
165
|
throw error;
|
|
111
166
|
}
|
|
112
167
|
const retryAfterMs = getRetryAfterMs(error);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
168
|
+
if (retryAfterMs !== undefined && retryAfterMs > MAX_RETRY_AFTER_MS) {
|
|
169
|
+
span?.setAttribute("gen_ai.provider.total_attempts", attempt + 1);
|
|
170
|
+
span?.addEvent("gen_ai.provider.retry_suppressed", {
|
|
171
|
+
"retry.after_ms": retryAfterMs,
|
|
172
|
+
...(statusCode !== undefined && { "retry.status_code": statusCode }),
|
|
173
|
+
});
|
|
174
|
+
// Best-effort stamp so upper retry layers (the MCP generation loop's
|
|
175
|
+
// isRetryable check) also surface instead of re-running a
|
|
176
|
+
// rate-limited provider.
|
|
177
|
+
try {
|
|
178
|
+
Object.assign(error, {
|
|
179
|
+
isRetryable: false,
|
|
180
|
+
retryAfterMs,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
/* frozen error — the throw below still surfaces it */
|
|
185
|
+
}
|
|
186
|
+
logger.warn(`[providerRetry] ${label} not retrying — server requested a ` +
|
|
187
|
+
`${Math.round(retryAfterMs / 1000)}s wait (cap ${MAX_RETRY_AFTER_MS / 1000}s); surfacing for fallback`, { attempt: attempt + 1, retryAfterMs, statusCode });
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
const delay = (retryAfterMs === undefined ? undefined : Math.max(0, retryAfterMs)) ??
|
|
117
191
|
Math.max(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), NO_HINT_FLOOR_MS);
|
|
118
192
|
// Record retry event on the OTel span
|
|
119
193
|
span?.addEvent("gen_ai.provider.retry", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.8.
|
|
3
|
+
"version": "10.8.4",
|
|
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": {
|
|
@@ -95,6 +95,7 @@
|
|
|
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
97
|
"test:provider-fallback": "npx tsx test/continuous-test-suite-provider-fallback.ts",
|
|
98
|
+
"test:provider-fallback-latency": "npx tsx test/continuous-test-suite-provider-fallback-latency.ts",
|
|
98
99
|
"test:rag": "npx tsx test/continuous-test-suite-rag.ts",
|
|
99
100
|
"test:vector-pinecone": "npx tsx test/continuous-test-suite-vector-pinecone.ts",
|
|
100
101
|
"test:vector-pgvector": "npx tsx test/continuous-test-suite-vector-pgvector.ts",
|