@juspay/neurolink 12.35.1 → 12.35.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.
- package/CHANGELOG.md +2 -2
- package/dist/auth/index.d.ts +4 -14
- package/dist/auth/index.js +5 -18
- package/dist/browser/neurolink.min.js +394 -394
- package/dist/server/routes/claudeProxyRoutes.js +46 -10
- package/dist/utils/fileDetector.js +12 -3
- package/dist/utils/messageBuilder.js +5 -2
- package/dist/utils/redirectDispatcher.d.ts +6 -1
- package/dist/utils/redirectDispatcher.js +7 -2
- package/dist/utils/safeFetch.js +12 -3
- package/package.json +1 -1
|
@@ -13,7 +13,6 @@ import { claudeOutputLogFields, observeClaudeJsonOutput, observeClaudeStreamOutp
|
|
|
13
13
|
import { access, readFile } from "node:fs/promises";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
-
import { Agent } from "undici";
|
|
17
16
|
import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, getOrCreateClaudeCodeIdentity, parseClaudeCodeUserId, } from "../../auth/anthropicOAuth.js";
|
|
18
17
|
import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from "../../proxy/accountCooldown.js";
|
|
19
18
|
import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
@@ -184,6 +183,50 @@ function prepareAnthropicWire(ctx, bodyStr) {
|
|
|
184
183
|
};
|
|
185
184
|
}
|
|
186
185
|
let anthropicUpstreamDispatcher;
|
|
186
|
+
let anthropicUpstreamDispatcherPromise;
|
|
187
|
+
/**
|
|
188
|
+
* Lazily construct (once) and cache the upstream Agent, reused across every
|
|
189
|
+
* request rather than rebuilt per call.
|
|
190
|
+
*
|
|
191
|
+
* `undici` is imported dynamically here — rather than as a static top-level
|
|
192
|
+
* import — so that requiring this module (and transitively, the package
|
|
193
|
+
* entry that re-exports `createClaudeProxyRoutes`) does not force-load undici
|
|
194
|
+
* until an upstream Anthropic request actually dispatches. The construction
|
|
195
|
+
* itself is guarded by a cached promise, not a bare `??=` on the Agent, so
|
|
196
|
+
* concurrent first callers await the same in-flight construction instead of
|
|
197
|
+
* each racing past the `undefined` check and building a duplicate Agent. A
|
|
198
|
+
* rejected construction (a bad undici install, an Agent constructor throwing)
|
|
199
|
+
* clears the cached promise rather than leaving it permanently rejected, so
|
|
200
|
+
* the next request gets a fresh attempt instead of every later request
|
|
201
|
+
* failing forever on a cached failure. See
|
|
202
|
+
* test/continuous-test-suite-import-cost.ts.
|
|
203
|
+
*/
|
|
204
|
+
async function getAnthropicUpstreamDispatcher() {
|
|
205
|
+
if (anthropicUpstreamDispatcher) {
|
|
206
|
+
return anthropicUpstreamDispatcher;
|
|
207
|
+
}
|
|
208
|
+
anthropicUpstreamDispatcherPromise ??= (async () => {
|
|
209
|
+
try {
|
|
210
|
+
const { Agent: UndiciAgent } = await import("undici");
|
|
211
|
+
// Node's global fetch applies Undici's 300s default headers timeout
|
|
212
|
+
// before the route's 15-minute abort signal. Keep both transport
|
|
213
|
+
// deadlines aligned with the proxy contract.
|
|
214
|
+
const agent = new UndiciAgent({
|
|
215
|
+
headersTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
216
|
+
bodyTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
217
|
+
});
|
|
218
|
+
anthropicUpstreamDispatcher = agent;
|
|
219
|
+
return agent;
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
// Let a later request try again instead of every subsequent request
|
|
223
|
+
// failing forever against a cached rejection.
|
|
224
|
+
anthropicUpstreamDispatcherPromise = undefined;
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return anthropicUpstreamDispatcherPromise;
|
|
229
|
+
}
|
|
187
230
|
async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
|
|
188
231
|
clearClaudeFallbackTerminalEvidence(ctx);
|
|
189
232
|
const prepared = dispatch.preparedContext;
|
|
@@ -203,15 +246,8 @@ async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
|
|
|
203
246
|
ctx.metadata.tokenBudget = lease.snapshot;
|
|
204
247
|
throw init.signal?.reason ?? ctx.abortSignal?.reason;
|
|
205
248
|
}
|
|
206
|
-
// Node's global fetch applies Undici's 300s default headers timeout before
|
|
207
|
-
// the route's 15-minute abort signal. Keep both transport deadlines aligned
|
|
208
|
-
// with the proxy contract and instantiate lazily so importing routes has no
|
|
209
|
-
// open transport handles.
|
|
210
|
-
anthropicUpstreamDispatcher ??= new Agent({
|
|
211
|
-
headersTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
212
|
-
bodyTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
|
|
213
|
-
});
|
|
214
249
|
try {
|
|
250
|
+
const upstreamDispatcher = await getAnthropicUpstreamDispatcher();
|
|
215
251
|
const dispatchedBody = String(init.body);
|
|
216
252
|
recordAttempt(dispatch.accountLabel, dispatch.accountType);
|
|
217
253
|
dispatch.tracer?.logUpstreamRequestHeaders(init.headers);
|
|
@@ -230,7 +266,7 @@ async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
|
|
|
230
266
|
const response = await fetch(url, {
|
|
231
267
|
...init,
|
|
232
268
|
body: dispatchedBody,
|
|
233
|
-
dispatcher:
|
|
269
|
+
dispatcher: upstreamDispatcher,
|
|
234
270
|
});
|
|
235
271
|
return observeAnthropicBudgetResponse(response, lease, () => {
|
|
236
272
|
ctx.metadata.tokenBudget = lease.snapshot;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { open, readFile, realpath } from "fs/promises";
|
|
7
7
|
import { STATUS_CODES } from "node:http";
|
|
8
8
|
import { basename, isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath, sep, } from "path";
|
|
9
|
-
import { request } from "undici";
|
|
10
9
|
import { redirectFollowingDispatcher } from "./redirectDispatcher.js";
|
|
11
10
|
// Lazy-loaded processor singletons — avoids loading heavy media deps
|
|
12
11
|
// (mediabunny, fluent-ffmpeg, music-metadata, adm-zip) on every generate() call.
|
|
@@ -1609,8 +1608,13 @@ export class FileDetector {
|
|
|
1609
1608
|
const timeout = options?.timeout || FileDetector.DEFAULT_NETWORK_TIMEOUT;
|
|
1610
1609
|
return withRetry(async () => {
|
|
1611
1610
|
try {
|
|
1611
|
+
// Dynamic import keeps undici off the package's static import
|
|
1612
|
+
// graph — it is only needed once a URL-referenced attachment
|
|
1613
|
+
// actually has to be fetched. See
|
|
1614
|
+
// test/continuous-test-suite-import-cost.ts.
|
|
1615
|
+
const { request } = await import("undici");
|
|
1612
1616
|
const response = await request(url, {
|
|
1613
|
-
dispatcher: redirectFollowingDispatcher(5),
|
|
1617
|
+
dispatcher: await redirectFollowingDispatcher(5),
|
|
1614
1618
|
method: "GET",
|
|
1615
1619
|
headersTimeout: timeout,
|
|
1616
1620
|
bodyTimeout: timeout,
|
|
@@ -2375,8 +2379,13 @@ class MimeTypeStrategy {
|
|
|
2375
2379
|
// Wrap the whole HEAD (request + body drain) in withTimeout so a stalled
|
|
2376
2380
|
// dump() can't hang detection, per the project's async-timeout guideline.
|
|
2377
2381
|
contentType = await withTimeout((async () => {
|
|
2382
|
+
// Dynamic import keeps undici off the package's static import
|
|
2383
|
+
// graph — it is only needed once a URL-referenced attachment
|
|
2384
|
+
// actually has to be fetched. See
|
|
2385
|
+
// test/continuous-test-suite-import-cost.ts.
|
|
2386
|
+
const { request } = await import("undici");
|
|
2378
2387
|
const response = await request(input, {
|
|
2379
|
-
dispatcher: redirectFollowingDispatcher(5),
|
|
2388
|
+
dispatcher: await redirectFollowingDispatcher(5),
|
|
2380
2389
|
method: "HEAD",
|
|
2381
2390
|
headersTimeout: FileDetector.DEFAULT_HEAD_TIMEOUT,
|
|
2382
2391
|
bodyTimeout: FileDetector.DEFAULT_HEAD_TIMEOUT,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
2
2
|
import { open as openAsync, readFile as readFileAsync, stat as statAsync, } from "fs/promises";
|
|
3
3
|
import pLimit from "p-limit";
|
|
4
|
-
import { request } from "undici";
|
|
5
4
|
import { redirectFollowingDispatcher } from "./redirectDispatcher.js";
|
|
6
5
|
import { MultimodalLogger, ProviderImageAdapter, } from "../adapters/providerImageAdapter.js";
|
|
7
6
|
import { CONVERSATION_INSTRUCTIONS, STRUCTURED_OUTPUT_INSTRUCTIONS, } from "../config/conversationMemory.js";
|
|
@@ -1780,8 +1779,12 @@ async function downloadImageFromUrl(url) {
|
|
|
1780
1779
|
// Apply rate limiting only if cache missed
|
|
1781
1780
|
await urlDownloadRateLimiter.acquire();
|
|
1782
1781
|
try {
|
|
1782
|
+
// Dynamic import keeps undici off the package's static import graph —
|
|
1783
|
+
// it is only needed once a URL-referenced image actually has to be
|
|
1784
|
+
// fetched. See test/continuous-test-suite-import-cost.ts.
|
|
1785
|
+
const { request } = await import("undici");
|
|
1783
1786
|
const response = await request(url, {
|
|
1784
|
-
dispatcher: redirectFollowingDispatcher(5),
|
|
1787
|
+
dispatcher: await redirectFollowingDispatcher(5),
|
|
1785
1788
|
method: "GET",
|
|
1786
1789
|
headersTimeout: 10000, // 10 second timeout for headers
|
|
1787
1790
|
bodyTimeout: 30000, // 30 second timeout for body,
|
|
@@ -34,6 +34,11 @@ import type { Dispatcher } from "undici";
|
|
|
34
34
|
* requests. Losing a proxy on one Node version is recoverable; silently failing
|
|
35
35
|
* every redirecting download is not.
|
|
36
36
|
*
|
|
37
|
+
* `undici` itself is imported dynamically here (rather than at module scope)
|
|
38
|
+
* so that requiring this module — and transitively, messageBuilder.ts /
|
|
39
|
+
* fileDetector.ts — does not force-load undici until a URL attachment is
|
|
40
|
+
* actually fetched. See test/continuous-test-suite-import-cost.ts.
|
|
41
|
+
*
|
|
37
42
|
* @param maxRedirections How many redirects to follow before giving up.
|
|
38
43
|
*/
|
|
39
|
-
export declare function redirectFollowingDispatcher(maxRedirections: number): Dispatcher
|
|
44
|
+
export declare function redirectFollowingDispatcher(maxRedirections: number): Promise<Dispatcher>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Agent, getGlobalDispatcher, interceptors } from "undici";
|
|
2
1
|
/**
|
|
3
2
|
* The major of the `undici` this package depends on.
|
|
4
3
|
*
|
|
@@ -42,9 +41,15 @@ const NPM_UNDICI_MAJOR = 7;
|
|
|
42
41
|
* requests. Losing a proxy on one Node version is recoverable; silently failing
|
|
43
42
|
* every redirecting download is not.
|
|
44
43
|
*
|
|
44
|
+
* `undici` itself is imported dynamically here (rather than at module scope)
|
|
45
|
+
* so that requiring this module — and transitively, messageBuilder.ts /
|
|
46
|
+
* fileDetector.ts — does not force-load undici until a URL attachment is
|
|
47
|
+
* actually fetched. See test/continuous-test-suite-import-cost.ts.
|
|
48
|
+
*
|
|
45
49
|
* @param maxRedirections How many redirects to follow before giving up.
|
|
46
50
|
*/
|
|
47
|
-
export function redirectFollowingDispatcher(maxRedirections) {
|
|
51
|
+
export async function redirectFollowingDispatcher(maxRedirections) {
|
|
52
|
+
const { Agent, getGlobalDispatcher, interceptors } = await import("undici");
|
|
48
53
|
const builtinMajor = Number.parseInt(process.versions.undici?.split(".")[0] ?? "", 10);
|
|
49
54
|
const base = Number.isFinite(builtinMajor) && builtinMajor === NPM_UNDICI_MAJOR
|
|
50
55
|
? getGlobalDispatcher()
|
package/dist/utils/safeFetch.js
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @module utils/safeFetch
|
|
18
18
|
*/
|
|
19
|
-
import { Agent, fetch as undiciFetch } from "undici";
|
|
20
19
|
import { readBoundedBuffer } from "./sizeGuard.js";
|
|
21
20
|
import { validateAndResolveUrl } from "./ssrfGuard.js";
|
|
22
21
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
@@ -30,8 +29,14 @@ const DEFAULT_TIMEOUT_MS = 60_000;
|
|
|
30
29
|
* the OS resolver prefers AAAA, that broke every safeDownload (Replicate /
|
|
31
30
|
* Runway / Kling asset fetches) while plain curl of the same URL succeeded —
|
|
32
31
|
* curl races both families (Happy Eyeballs); a one-address pin cannot.
|
|
32
|
+
*
|
|
33
|
+
* `undici` is imported dynamically (this function is only ever called from
|
|
34
|
+
* the already-async `safeDownload`) so that requiring this module does not
|
|
35
|
+
* force-load undici until a download actually happens. See
|
|
36
|
+
* test/continuous-test-suite-import-cost.ts.
|
|
33
37
|
*/
|
|
34
|
-
function buildPinnedAgent(hostname, addresses) {
|
|
38
|
+
async function buildPinnedAgent(hostname, addresses) {
|
|
39
|
+
const { Agent } = await import("undici");
|
|
35
40
|
const primary = addresses[0];
|
|
36
41
|
if (!primary) {
|
|
37
42
|
throw new Error(`safeFetch: no validated addresses to pin for "${hostname}"`);
|
|
@@ -85,7 +90,11 @@ export async function safeDownload(url, options) {
|
|
|
85
90
|
const { url: validatedUrl, addresses } = await validateAndResolveUrl(url);
|
|
86
91
|
const parsed = new URL(validatedUrl);
|
|
87
92
|
const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
88
|
-
|
|
93
|
+
// Dynamic import keeps undici off the package's static import graph — it
|
|
94
|
+
// is only needed once a download actually happens. See
|
|
95
|
+
// test/continuous-test-suite-import-cost.ts.
|
|
96
|
+
const { fetch: undiciFetch } = await import("undici");
|
|
97
|
+
const agent = await buildPinnedAgent(hostname, addresses);
|
|
89
98
|
const timeoutCtrl = new AbortController();
|
|
90
99
|
const timeoutId = setTimeout(() => timeoutCtrl.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
91
100
|
const composedSignal = options.signal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "12.35.
|
|
3
|
+
"version": "12.35.2",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
|
|
6
6
|
"author": {
|