@juspay/neurolink 9.87.1 → 9.87.3
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/browser/neurolink.min.js +365 -366
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +118 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +282 -112
- package/dist/lib/types/proxy.d.ts +13 -0
- package/dist/lib/types/safeFetch.d.ts +9 -0
- package/dist/lib/utils/safeFetch.d.ts +3 -3
- package/dist/lib/utils/safeFetch.js +23 -15
- package/dist/lib/utils/ssrfGuard.d.ts +13 -5
- package/dist/lib/utils/ssrfGuard.js +48 -13
- package/dist/server/routes/claudeProxyRoutes.d.ts +118 -1
- package/dist/server/routes/claudeProxyRoutes.js +282 -112
- package/dist/types/proxy.d.ts +13 -0
- package/dist/types/safeFetch.d.ts +9 -0
- package/dist/utils/safeFetch.d.ts +3 -3
- package/dist/utils/safeFetch.js +23 -15
- package/dist/utils/ssrfGuard.d.ts +13 -5
- package/dist/utils/ssrfGuard.js +48 -13
- package/package.json +1 -1
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Combines:
|
|
5
5
|
* - `assertSafeUrl` (validates and rejects blocked IPs)
|
|
6
6
|
* - undici `Agent` with custom `connect.lookup` so the actual connection
|
|
7
|
-
*
|
|
8
|
-
* resolver returns a public IP for the guard but a private IP
|
|
9
|
-
* real request).
|
|
7
|
+
* only ever dials addresses we validated (closes the DNS-rebinding window
|
|
8
|
+
* where the resolver returns a public IP for the guard but a private IP
|
|
9
|
+
* for the real request).
|
|
10
10
|
* - `readBoundedBuffer` for size cap.
|
|
11
11
|
* - `redirect: "manual"` so a 3xx → private-IP redirect can't bypass
|
|
12
12
|
* the guard.
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Combines:
|
|
5
5
|
* - `assertSafeUrl` (validates and rejects blocked IPs)
|
|
6
6
|
* - undici `Agent` with custom `connect.lookup` so the actual connection
|
|
7
|
-
*
|
|
8
|
-
* resolver returns a public IP for the guard but a private IP
|
|
9
|
-
* real request).
|
|
7
|
+
* only ever dials addresses we validated (closes the DNS-rebinding window
|
|
8
|
+
* where the resolver returns a public IP for the guard but a private IP
|
|
9
|
+
* for the real request).
|
|
10
10
|
* - `readBoundedBuffer` for size cap.
|
|
11
11
|
* - `redirect: "manual"` so a 3xx → private-IP redirect can't bypass
|
|
12
12
|
* the guard.
|
|
@@ -21,11 +21,21 @@ import { readBoundedBuffer } from "./sizeGuard.js";
|
|
|
21
21
|
import { validateAndResolveUrl } from "./ssrfGuard.js";
|
|
22
22
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
23
23
|
/**
|
|
24
|
-
* Build a once-off undici Agent whose connect lookup resolves `hostname` to
|
|
25
|
-
*
|
|
26
|
-
* removing the DNS-rebinding window.
|
|
24
|
+
* Build a once-off undici Agent whose connect lookup resolves `hostname` to
|
|
25
|
+
* the validated address set. The actual TCP connection can only ever go to
|
|
26
|
+
* an address the SSRF guard cleared, removing the DNS-rebinding window.
|
|
27
|
+
*
|
|
28
|
+
* The full set (IPv4 first) matters: pinning a single address turns one
|
|
29
|
+
* unroutable family into a hard connect timeout. On IPv4-only networks where
|
|
30
|
+
* the OS resolver prefers AAAA, that broke every safeDownload (Replicate /
|
|
31
|
+
* Runway / Kling asset fetches) while plain curl of the same URL succeeded —
|
|
32
|
+
* curl races both families (Happy Eyeballs); a one-address pin cannot.
|
|
27
33
|
*/
|
|
28
|
-
function buildPinnedAgent(hostname,
|
|
34
|
+
function buildPinnedAgent(hostname, addresses) {
|
|
35
|
+
const primary = addresses[0];
|
|
36
|
+
if (!primary) {
|
|
37
|
+
throw new Error(`safeFetch: no validated addresses to pin for "${hostname}"`);
|
|
38
|
+
}
|
|
29
39
|
return new Agent({
|
|
30
40
|
connect: {
|
|
31
41
|
lookup: (host, options, callback) => {
|
|
@@ -37,15 +47,13 @@ function buildPinnedAgent(hostname, ip, family) {
|
|
|
37
47
|
}
|
|
38
48
|
// Node ≥20 enables autoSelectFamily (Happy Eyeballs) by default, and
|
|
39
49
|
// its lookup contract passes `all: true` expecting an address ARRAY.
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// (Replicate / Runway / Kling asset fetches) on modern Node while
|
|
43
|
-
// plain curl of the same URL succeeded.
|
|
50
|
+
// Hand it every validated address so it can race families and fall
|
|
51
|
+
// back when one is unroutable.
|
|
44
52
|
if (options?.all) {
|
|
45
|
-
callback(null,
|
|
53
|
+
callback(null, addresses.map((a) => ({ address: a.ip, family: a.family })));
|
|
46
54
|
return;
|
|
47
55
|
}
|
|
48
|
-
callback(null, ip, family);
|
|
56
|
+
callback(null, primary.ip, primary.family);
|
|
49
57
|
},
|
|
50
58
|
},
|
|
51
59
|
});
|
|
@@ -57,10 +65,10 @@ function buildPinnedAgent(hostname, ip, family) {
|
|
|
57
65
|
* is encountered, or the HTTP status indicates failure.
|
|
58
66
|
*/
|
|
59
67
|
export async function safeDownload(url, options) {
|
|
60
|
-
const { url: validatedUrl,
|
|
68
|
+
const { url: validatedUrl, addresses } = await validateAndResolveUrl(url);
|
|
61
69
|
const parsed = new URL(validatedUrl);
|
|
62
70
|
const hostname = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
63
|
-
const agent = buildPinnedAgent(hostname,
|
|
71
|
+
const agent = buildPinnedAgent(hostname, addresses);
|
|
64
72
|
const timeoutCtrl = new AbortController();
|
|
65
73
|
const timeoutId = setTimeout(() => timeoutCtrl.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
66
74
|
const composedSignal = options.signal
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module utils/ssrfGuard
|
|
26
26
|
*/
|
|
27
|
+
import type { PinnedAddress } from "../types/index.js";
|
|
27
28
|
/**
|
|
28
29
|
* Assert that `url` is safe to fetch server-side.
|
|
29
30
|
*
|
|
@@ -35,12 +36,18 @@
|
|
|
35
36
|
*/
|
|
36
37
|
export declare function assertSafeUrl(url: string): Promise<void>;
|
|
37
38
|
/**
|
|
38
|
-
* Validate `url` and return the resolved
|
|
39
|
-
* actual fetch (companion to `safeFetch.ts:safeDownload`).
|
|
39
|
+
* Validate `url` and return the resolved address set that should be used for
|
|
40
|
+
* the actual fetch (companion to `safeFetch.ts:safeDownload`).
|
|
40
41
|
*
|
|
41
|
-
* For IP-literal hosts,
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* For IP-literal hosts, `addresses` is the normalised IP as a singleton. For
|
|
43
|
+
* hostnames it is **every** validated address, IPv4 first, so the connect
|
|
44
|
+
* layer can race families (Happy Eyeballs). Pinning a single OS-preferred
|
|
45
|
+
* address breaks every download on IPv4-only networks whenever the resolver
|
|
46
|
+
* prefers AAAA: the pinned connection has no second address to fall back to,
|
|
47
|
+
* so it times out where plain `curl` (which races both families) succeeds.
|
|
48
|
+
* `ip`/`family` mirror the first entry for callers that need a single pin.
|
|
49
|
+
*
|
|
50
|
+
* Same throw semantics as {@link assertSafeUrl}.
|
|
44
51
|
*
|
|
45
52
|
* This is the canonical entry point for binary downloads where DNS-rebinding
|
|
46
53
|
* pinning matters — see `safeFetch.ts`.
|
|
@@ -49,4 +56,5 @@ export declare function validateAndResolveUrl(url: string): Promise<{
|
|
|
49
56
|
url: string;
|
|
50
57
|
ip: string;
|
|
51
58
|
family: 4 | 6;
|
|
59
|
+
addresses: readonly PinnedAddress[];
|
|
52
60
|
}>;
|
|
@@ -306,6 +306,17 @@ export async function assertSafeUrl(url) {
|
|
|
306
306
|
}
|
|
307
307
|
// Hostname — resolve BOTH A and AAAA. Reject if either family yields a
|
|
308
308
|
// blocked address (closes off the "publish AAAA public, A private" attack).
|
|
309
|
+
await resolveAndValidateHost(url, host);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Resolve `host` (both A and AAAA) and validate every returned address
|
|
313
|
+
* against the block lists. Returns the full validated set so callers that
|
|
314
|
+
* pin connections can offer the connect layer more than one address.
|
|
315
|
+
*
|
|
316
|
+
* @throws {Error} when resolution fails entirely or any address is blocked
|
|
317
|
+
* (all-must-pass — the caller may connect to any address in the set).
|
|
318
|
+
*/
|
|
319
|
+
async function resolveAndValidateHost(url, host) {
|
|
309
320
|
const [a, aaaa] = await Promise.allSettled([
|
|
310
321
|
lookup(host, { family: 4, all: true }),
|
|
311
322
|
lookup(host, { family: 6, all: true }),
|
|
@@ -354,14 +365,21 @@ export async function assertSafeUrl(url) {
|
|
|
354
365
|
throw new Error(`URL "${url}" rejected: hostname ${host} resolves to ${addr} (IPv6 ${reason})`);
|
|
355
366
|
}
|
|
356
367
|
}
|
|
368
|
+
return { v4: v4Addresses, v6: v6Addresses };
|
|
357
369
|
}
|
|
358
370
|
/**
|
|
359
|
-
* Validate `url` and return the resolved
|
|
360
|
-
* actual fetch (companion to `safeFetch.ts:safeDownload`).
|
|
371
|
+
* Validate `url` and return the resolved address set that should be used for
|
|
372
|
+
* the actual fetch (companion to `safeFetch.ts:safeDownload`).
|
|
373
|
+
*
|
|
374
|
+
* For IP-literal hosts, `addresses` is the normalised IP as a singleton. For
|
|
375
|
+
* hostnames it is **every** validated address, IPv4 first, so the connect
|
|
376
|
+
* layer can race families (Happy Eyeballs). Pinning a single OS-preferred
|
|
377
|
+
* address breaks every download on IPv4-only networks whenever the resolver
|
|
378
|
+
* prefers AAAA: the pinned connection has no second address to fall back to,
|
|
379
|
+
* so it times out where plain `curl` (which races both families) succeeds.
|
|
380
|
+
* `ip`/`family` mirror the first entry for callers that need a single pin.
|
|
361
381
|
*
|
|
362
|
-
*
|
|
363
|
-
* returns the first acceptable IP from the resolver. Same throw semantics as
|
|
364
|
-
* {@link assertSafeUrl}.
|
|
382
|
+
* Same throw semantics as {@link assertSafeUrl}.
|
|
365
383
|
*
|
|
366
384
|
* This is the canonical entry point for binary downloads where DNS-rebinding
|
|
367
385
|
* pinning matters — see `safeFetch.ts`.
|
|
@@ -384,7 +402,7 @@ export async function validateAndResolveUrl(url) {
|
|
|
384
402
|
if (isBlockedIPv4(v4)) {
|
|
385
403
|
throw new Error(`URL "${url}" rejected: IPv4 ${host} → ${v4} is in a blocked range`);
|
|
386
404
|
}
|
|
387
|
-
return { url, ip: v4, family: 4 };
|
|
405
|
+
return { url, ip: v4, family: 4, addresses: [{ ip: v4, family: 4 }] };
|
|
388
406
|
}
|
|
389
407
|
if (host.includes(":")) {
|
|
390
408
|
const v4FromMapped = extractIPv4FromMapped(host);
|
|
@@ -392,7 +410,12 @@ export async function validateAndResolveUrl(url) {
|
|
|
392
410
|
if (isBlockedIPv4(v4FromMapped)) {
|
|
393
411
|
throw new Error(`URL "${url}" rejected: IPv4-mapped IPv6 ${host} → ${v4FromMapped} is in a blocked range`);
|
|
394
412
|
}
|
|
395
|
-
return {
|
|
413
|
+
return {
|
|
414
|
+
url,
|
|
415
|
+
ip: v4FromMapped,
|
|
416
|
+
family: 4,
|
|
417
|
+
addresses: [{ ip: v4FromMapped, family: 4 }],
|
|
418
|
+
};
|
|
396
419
|
}
|
|
397
420
|
const expanded = expandIPv6(host);
|
|
398
421
|
if (!expanded) {
|
|
@@ -401,11 +424,23 @@ export async function validateAndResolveUrl(url) {
|
|
|
401
424
|
if (isBlockedIPv6(expanded)) {
|
|
402
425
|
throw new Error(`URL "${url}" rejected: IPv6 ${host} is in a blocked range`);
|
|
403
426
|
}
|
|
404
|
-
return { url, ip: host, family: 6 };
|
|
405
|
-
}
|
|
406
|
-
// Hostname — resolve and
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
427
|
+
return { url, ip: host, family: 6, addresses: [{ ip: host, family: 6 }] };
|
|
428
|
+
}
|
|
429
|
+
// Hostname — resolve once, validate every address, and return the whole
|
|
430
|
+
// set. Validating the same answer we hand to the connect layer also
|
|
431
|
+
// removes the rebinding window the previous separate `lookup()` left
|
|
432
|
+
// between validation and pinning.
|
|
433
|
+
const { v4: v4Addresses, v6: v6Addresses } = await resolveAndValidateHost(url, host);
|
|
434
|
+
const addresses = [
|
|
435
|
+
...v4Addresses.map((ip) => ({ ip, family: 4 })),
|
|
436
|
+
...v6Addresses.map((ip) => ({ ip, family: 6 })),
|
|
437
|
+
];
|
|
438
|
+
const first = addresses[0];
|
|
439
|
+
if (!first) {
|
|
440
|
+
// Both lookups "succeeded" but returned zero addresses — treat exactly
|
|
441
|
+
// like a resolution failure rather than pinning nothing.
|
|
442
|
+
throw new Error(`URL "${url}" rejected: hostname ${host} resolved to an empty address set`);
|
|
443
|
+
}
|
|
444
|
+
return { url, ip: first.ip, family: first.family, addresses };
|
|
410
445
|
}
|
|
411
446
|
//# sourceMappingURL=ssrfGuard.js.map
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
|
|
13
13
|
import type { ModelRouter } from "../../proxy/modelRouter.js";
|
|
14
|
+
import { ProxyTracer } from "../../proxy/proxyTracer.js";
|
|
14
15
|
import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js";
|
|
15
|
-
import type { AccountAllowlist, AccountCooldownPlan, AccountQuota, ParsedClaudeError, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState, StreamTerminalOutcome } from "../../types/index.js";
|
|
16
|
+
import type { AccountAllowlist, AccountCooldownPlan, AccountQuota, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicLoopState, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeRequest, ParsedClaudeError, ProxyBodyCaptureLogger, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
|
|
16
17
|
/** Resolve the configured primary's stable key to its current index in the
|
|
17
18
|
* request's enabledAccounts list. Returns 0 (insertion-order fallback) when
|
|
18
19
|
* no key is configured or the key cannot be matched (account disabled/
|
|
@@ -24,6 +25,9 @@ declare function resolveHomeIndex(enabledAccounts: ProxyPassthroughAccount[]): n
|
|
|
24
25
|
* account once its rate limit window expires. Called at the start of each
|
|
25
26
|
* request. Home is resolved fresh per call via resolveHomeIndex. */
|
|
26
27
|
declare function maybeResetPrimaryToHome(enabledAccounts: ProxyPassthroughAccount[]): void;
|
|
28
|
+
declare function claimTransientRateLimitRetry(accountKey: string, coolingUntil: number, now?: number): number | undefined;
|
|
29
|
+
declare function claimTransientCooldownAdmission(accountKey: string, coolingUntil: number, now?: number): number | undefined;
|
|
30
|
+
declare function waitForTransientAccountAvailability(orderedAccounts: ProxyPassthroughAccount[]): Promise<ProxyPassthroughAccount[]>;
|
|
27
31
|
/** Convert an Anthropic unified-window reset (Unix epoch SECONDS, per the
|
|
28
32
|
* `anthropic-ratelimit-unified-*-reset` headers) into epoch-ms. Tolerates a
|
|
29
33
|
* value already expressed in ms (some intermediaries normalise it). Returns
|
|
@@ -77,11 +81,109 @@ declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>)
|
|
|
77
81
|
stream: ReadableStream<Uint8Array>;
|
|
78
82
|
outcome: Promise<StreamTerminalOutcome>;
|
|
79
83
|
};
|
|
84
|
+
declare function handleAnthropicStreamingSuccessResponse(args: {
|
|
85
|
+
ctx: ServerContext;
|
|
86
|
+
body: ClaudeRequest;
|
|
87
|
+
account: ProxyPassthroughAccount;
|
|
88
|
+
accountState: RuntimeAccountState;
|
|
89
|
+
response: Response;
|
|
90
|
+
responseHeaders: Record<string, string>;
|
|
91
|
+
tracer?: ProxyTracer;
|
|
92
|
+
requestStartTime: number;
|
|
93
|
+
fetchStartMs: number;
|
|
94
|
+
attemptNumber: number;
|
|
95
|
+
finalBodyStr: string;
|
|
96
|
+
upstreamSpan?: import("@opentelemetry/api").Span;
|
|
97
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
98
|
+
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
|
|
99
|
+
inputTokens?: number;
|
|
100
|
+
outputTokens?: number;
|
|
101
|
+
cacheCreationTokens?: number;
|
|
102
|
+
cacheReadTokens?: number;
|
|
103
|
+
}) => void;
|
|
104
|
+
}): Promise<AnthropicSuccessResult>;
|
|
80
105
|
declare function getStreamFailureDetails(outcome: StreamTerminalOutcome): {
|
|
81
106
|
status: number;
|
|
82
107
|
errorType: string;
|
|
83
108
|
message: string;
|
|
84
109
|
} | undefined;
|
|
110
|
+
declare function handleAnthropicAuthRetry(args: {
|
|
111
|
+
ctx: ServerContext;
|
|
112
|
+
body: ClaudeRequest;
|
|
113
|
+
account: ProxyPassthroughAccount;
|
|
114
|
+
accountState: RuntimeAccountState;
|
|
115
|
+
headers: Record<string, string>;
|
|
116
|
+
buildUpstreamBody: (token: string) => {
|
|
117
|
+
bodyStr: string;
|
|
118
|
+
sessionId?: string;
|
|
119
|
+
};
|
|
120
|
+
enabledAccounts: ProxyPassthroughAccount[];
|
|
121
|
+
orderedAccounts: ProxyPassthroughAccount[];
|
|
122
|
+
response: Response;
|
|
123
|
+
tracer?: ProxyTracer;
|
|
124
|
+
requestStartTime: number;
|
|
125
|
+
fetchStartMs: number;
|
|
126
|
+
attemptNumber: number;
|
|
127
|
+
finalBodyStr: string;
|
|
128
|
+
upstreamSpan?: import("@opentelemetry/api").Span;
|
|
129
|
+
logAttempt: (status: number, errorType?: string, errorMessage?: string, extra?: {
|
|
130
|
+
inputTokens?: number;
|
|
131
|
+
outputTokens?: number;
|
|
132
|
+
cacheCreationTokens?: number;
|
|
133
|
+
cacheReadTokens?: number;
|
|
134
|
+
}) => void;
|
|
135
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
136
|
+
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
|
|
137
|
+
inputTokens?: number;
|
|
138
|
+
outputTokens?: number;
|
|
139
|
+
cacheCreationTokens?: number;
|
|
140
|
+
cacheReadTokens?: number;
|
|
141
|
+
}) => void;
|
|
142
|
+
lastError: unknown;
|
|
143
|
+
authFailureMessage: string | null;
|
|
144
|
+
sawRateLimit: boolean;
|
|
145
|
+
sawTransientFailure: boolean;
|
|
146
|
+
sawNetworkError: boolean;
|
|
147
|
+
}): Promise<AnthropicAuthRetryResult>;
|
|
148
|
+
declare function finalizeAnthropicTerminalFetchError(args: {
|
|
149
|
+
terminalError: NonNullable<AnthropicUpstreamFetchResult["terminalError"]>;
|
|
150
|
+
account: ProxyPassthroughAccount;
|
|
151
|
+
tracer?: ProxyTracer;
|
|
152
|
+
requestStartTime: number;
|
|
153
|
+
attemptNumber: number;
|
|
154
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
155
|
+
logFinalRequest: ClaudeFinalRequestLogger;
|
|
156
|
+
}): Response | unknown;
|
|
157
|
+
/**
|
|
158
|
+
* Detect Anthropic's anti-abuse / request-construction 429.
|
|
159
|
+
*
|
|
160
|
+
* The subscription/OAuth path rejects requests it does not recognise as genuine
|
|
161
|
+
* Claude Code traffic with a 429 `rate_limit_error` whose message is literally
|
|
162
|
+
* "Error" and which carries NONE of the real rate-limit headers (no retry-after,
|
|
163
|
+
* no anthropic-ratelimit-*). This is NOT a capacity limit — retrying or rotating
|
|
164
|
+
* accounts cannot fix it and only burns quota, so the caller must fail fast and
|
|
165
|
+
* return a non-retryable request error instead of "all accounts rate-limited".
|
|
166
|
+
*/
|
|
167
|
+
declare function isAntiAbuseConstruction429(headers: Record<string, string>, body: string): boolean;
|
|
168
|
+
declare function fetchAnthropicAccountResponse(args: {
|
|
169
|
+
url: string;
|
|
170
|
+
headers: Record<string, string>;
|
|
171
|
+
finalBodyStr: string;
|
|
172
|
+
account: ProxyPassthroughAccount;
|
|
173
|
+
accountState: RuntimeAccountState;
|
|
174
|
+
enabledAccounts: ProxyPassthroughAccount[];
|
|
175
|
+
orderedAccounts: ProxyPassthroughAccount[];
|
|
176
|
+
tracer?: ProxyTracer;
|
|
177
|
+
logAttempt: AnthropicAttemptLogger;
|
|
178
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
179
|
+
fetchStartMs: number;
|
|
180
|
+
attemptNumber: number;
|
|
181
|
+
currentLastError: unknown;
|
|
182
|
+
currentSawRateLimit: boolean;
|
|
183
|
+
currentSawNetworkError: boolean;
|
|
184
|
+
upstreamSpan?: import("@opentelemetry/api").Span;
|
|
185
|
+
}): Promise<AnthropicUpstreamFetchResult>;
|
|
186
|
+
declare function shouldAttemptClaudeFallback(loopState: AnthropicLoopState): boolean;
|
|
85
187
|
/**
|
|
86
188
|
* Create Claude-compatible proxy routes.
|
|
87
189
|
*
|
|
@@ -94,6 +196,7 @@ declare function getStreamFailureDetails(outcome: StreamTerminalOutcome): {
|
|
|
94
196
|
*/
|
|
95
197
|
export declare function createClaudeProxyRoutes(modelRouter?: ModelRouter, basePath?: string, accountStrategy?: "round-robin" | "fill-first", passthroughMode?: boolean, primaryAccountKey?: string, accountAllowlist?: AccountAllowlist): RouteGroup;
|
|
96
198
|
export declare function getTransientSameAccountRetryDelayMs(retryNumber: number): number;
|
|
199
|
+
declare function describeTransportError(error: unknown): string;
|
|
97
200
|
/**
|
|
98
201
|
* Parse a Claude error payload when available.
|
|
99
202
|
*/
|
|
@@ -132,5 +235,19 @@ export declare const __testHooks: {
|
|
|
132
235
|
getPrimaryAccountIndex: () => number;
|
|
133
236
|
setAccountRuntimeState: (key: string, state: Partial<RuntimeAccountState>) => void;
|
|
134
237
|
resetAllRuntimeState: () => void;
|
|
238
|
+
polyfillOAuthBody: (bodyStr: string, isClaudeClientRequest: boolean) => {
|
|
239
|
+
bodyStr: string;
|
|
240
|
+
sessionId?: string;
|
|
241
|
+
};
|
|
242
|
+
isAntiAbuseConstruction429: typeof isAntiAbuseConstruction429;
|
|
243
|
+
fetchAnthropicAccountResponse: typeof fetchAnthropicAccountResponse;
|
|
244
|
+
finalizeAnthropicTerminalFetchError: typeof finalizeAnthropicTerminalFetchError;
|
|
245
|
+
handleAnthropicAuthRetry: typeof handleAnthropicAuthRetry;
|
|
246
|
+
handleAnthropicStreamingSuccessResponse: typeof handleAnthropicStreamingSuccessResponse;
|
|
247
|
+
claimTransientRateLimitRetry: typeof claimTransientRateLimitRetry;
|
|
248
|
+
claimTransientCooldownAdmission: typeof claimTransientCooldownAdmission;
|
|
249
|
+
waitForTransientAccountAvailability: typeof waitForTransientAccountAvailability;
|
|
250
|
+
describeTransportError: typeof describeTransportError;
|
|
251
|
+
shouldAttemptClaudeFallback: typeof shouldAttemptClaudeFallback;
|
|
135
252
|
};
|
|
136
253
|
export {};
|