@juspay/neurolink 9.87.2 → 9.87.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 +13 -0
- package/dist/browser/neurolink.min.js +381 -382
- package/dist/cli/commands/proxy.d.ts +16 -1
- package/dist/cli/commands/proxy.js +275 -238
- package/dist/lib/proxy/globalInstaller.d.ts +5 -0
- package/dist/lib/proxy/globalInstaller.js +156 -0
- package/dist/lib/proxy/openaiFormat.d.ts +3 -1
- package/dist/lib/proxy/openaiFormat.js +19 -4
- package/dist/lib/proxy/proxyActivity.d.ts +8 -0
- package/dist/lib/proxy/proxyActivity.js +77 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +173 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +487 -159
- package/dist/lib/server/routes/openaiProxyRoutes.js +47 -6
- package/dist/lib/types/cli.d.ts +4 -0
- package/dist/lib/types/proxy.d.ts +62 -0
- package/dist/proxy/globalInstaller.d.ts +5 -0
- package/dist/proxy/globalInstaller.js +155 -0
- package/dist/proxy/openaiFormat.d.ts +3 -1
- package/dist/proxy/openaiFormat.js +19 -4
- package/dist/proxy/proxyActivity.d.ts +8 -0
- package/dist/proxy/proxyActivity.js +76 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +173 -6
- package/dist/server/routes/claudeProxyRoutes.js +487 -159
- package/dist/server/routes/openaiProxyRoutes.js +47 -6
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/proxy.d.ts +62 -0
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GlobalInstallerKind, GlobalInstallerResolution, ResolveGlobalInstallerOptions } from "../types/index.js";
|
|
2
|
+
/** Resolve a package manager that can update the installation currently running. */
|
|
3
|
+
export declare function resolveGlobalInstaller(options?: ResolveGlobalInstallerOptions): GlobalInstallerResolution;
|
|
4
|
+
export declare function getGlobalInstallArgs(kind: GlobalInstallerKind, packageSpec: string): string[];
|
|
5
|
+
export declare function describeInstallFailure(error: unknown): string;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { execFileSync as nodeExecFileSync } from "node:child_process";
|
|
2
|
+
import { accessSync, constants, existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
function runText(execFileSync, bin, args) {
|
|
6
|
+
return String(execFileSync(bin, args, {
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
timeout: 10_000,
|
|
9
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
10
|
+
})).trim();
|
|
11
|
+
}
|
|
12
|
+
function writableDirectory(path) {
|
|
13
|
+
try {
|
|
14
|
+
if (!existsSync(path)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
accessSync(path, constants.W_OK);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isPathInside(path, parent) {
|
|
25
|
+
if (!path) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const candidate = realpathSync(path);
|
|
30
|
+
const root = realpathSync(parent);
|
|
31
|
+
return candidate === root || candidate.startsWith(`${root}/`);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
const candidate = resolve(path);
|
|
35
|
+
const root = resolve(parent);
|
|
36
|
+
return candidate === root || candidate.startsWith(`${root}/`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function probeInstaller(kind, bin, entryScript, execFileSync) {
|
|
40
|
+
const base = {
|
|
41
|
+
kind,
|
|
42
|
+
bin,
|
|
43
|
+
working: false,
|
|
44
|
+
installable: false,
|
|
45
|
+
matchesCurrentInstall: false,
|
|
46
|
+
};
|
|
47
|
+
try {
|
|
48
|
+
base.version = runText(execFileSync, bin, ["--version"]);
|
|
49
|
+
base.working = base.version.length > 0;
|
|
50
|
+
base.globalRoot = runText(execFileSync, bin, ["root", "-g"]);
|
|
51
|
+
base.globalBinDir =
|
|
52
|
+
kind === "pnpm"
|
|
53
|
+
? runText(execFileSync, bin, ["bin", "-g"])
|
|
54
|
+
: join(runText(execFileSync, bin, ["prefix", "-g"]), "bin");
|
|
55
|
+
if (!base.globalRoot || !writableDirectory(base.globalRoot)) {
|
|
56
|
+
base.reason = "global package root is missing or not writable";
|
|
57
|
+
return base;
|
|
58
|
+
}
|
|
59
|
+
if (!base.globalBinDir || !writableDirectory(base.globalBinDir)) {
|
|
60
|
+
base.reason = "global executable directory is missing or not writable";
|
|
61
|
+
return base;
|
|
62
|
+
}
|
|
63
|
+
base.installable = true;
|
|
64
|
+
base.matchesCurrentInstall = isPathInside(entryScript, base.globalRoot);
|
|
65
|
+
return base;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
base.reason = error instanceof Error ? error.message : String(error);
|
|
69
|
+
return base;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function resolveFromPath(command, execFileSync) {
|
|
73
|
+
try {
|
|
74
|
+
const result = runText(execFileSync, "which", [command]);
|
|
75
|
+
return result || undefined;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Resolve a package manager that can update the installation currently running. */
|
|
82
|
+
export function resolveGlobalInstaller(options = {}) {
|
|
83
|
+
const env = options.env ?? process.env;
|
|
84
|
+
const homeDir = options.homeDir ?? homedir();
|
|
85
|
+
const entryScript = options.entryScript ?? process.argv[1];
|
|
86
|
+
const execFileSync = options.execFileSync ?? nodeExecFileSync;
|
|
87
|
+
const candidates = [];
|
|
88
|
+
if (env.NEUROLINK_PACKAGE_MANAGER_PATH) {
|
|
89
|
+
const configuredKind = env.NEUROLINK_PACKAGE_MANAGER?.toLowerCase();
|
|
90
|
+
const inferredName = basename(env.NEUROLINK_PACKAGE_MANAGER_PATH);
|
|
91
|
+
const kind = configuredKind === "npm" || configuredKind === "pnpm"
|
|
92
|
+
? configuredKind
|
|
93
|
+
: inferredName.startsWith("npm")
|
|
94
|
+
? "npm"
|
|
95
|
+
: "pnpm";
|
|
96
|
+
candidates.push({ kind, bin: env.NEUROLINK_PACKAGE_MANAGER_PATH });
|
|
97
|
+
}
|
|
98
|
+
if (env.NEUROLINK_PNPM_PATH) {
|
|
99
|
+
candidates.push({ kind: "pnpm", bin: env.NEUROLINK_PNPM_PATH });
|
|
100
|
+
}
|
|
101
|
+
if (env.PNPM_HOME) {
|
|
102
|
+
candidates.push({ kind: "pnpm", bin: join(env.PNPM_HOME, "pnpm") });
|
|
103
|
+
}
|
|
104
|
+
const nodeBinDir = dirname(process.execPath);
|
|
105
|
+
candidates.push({ kind: "npm", bin: join(nodeBinDir, "npm") });
|
|
106
|
+
const pathPnpm = resolveFromPath("pnpm", execFileSync);
|
|
107
|
+
const pathNpm = resolveFromPath("npm", execFileSync);
|
|
108
|
+
if (pathPnpm) {
|
|
109
|
+
candidates.push({ kind: "pnpm", bin: pathPnpm });
|
|
110
|
+
}
|
|
111
|
+
if (pathNpm) {
|
|
112
|
+
candidates.push({ kind: "npm", bin: pathNpm });
|
|
113
|
+
}
|
|
114
|
+
candidates.push({ kind: "pnpm", bin: join(homeDir, ".local", "share", "pnpm", "pnpm") }, { kind: "pnpm", bin: join(homeDir, "Library", "pnpm", "pnpm") }, { kind: "npm", bin: "/opt/homebrew/bin/npm" }, { kind: "npm", bin: "/usr/local/bin/npm" });
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
const tried = candidates
|
|
117
|
+
.filter(({ kind, bin }) => {
|
|
118
|
+
const key = `${kind}:${bin}`;
|
|
119
|
+
if (!bin || seen.has(key)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
seen.add(key);
|
|
123
|
+
return true;
|
|
124
|
+
})
|
|
125
|
+
.map(({ kind, bin }) => probeInstaller(kind, bin, entryScript, execFileSync));
|
|
126
|
+
const matchingInstaller = tried.find((probe) => probe.installable && probe.matchesCurrentInstall);
|
|
127
|
+
// When the running entry script is known, installing into a different
|
|
128
|
+
// global root cannot update that process and may shadow another install.
|
|
129
|
+
const installer = entryScript
|
|
130
|
+
? matchingInstaller
|
|
131
|
+
: (matchingInstaller ?? tried.find((probe) => probe.installable));
|
|
132
|
+
return { installer, tried };
|
|
133
|
+
}
|
|
134
|
+
export function getGlobalInstallArgs(kind, packageSpec) {
|
|
135
|
+
return kind === "pnpm"
|
|
136
|
+
? ["add", "-g", packageSpec]
|
|
137
|
+
: ["install", "--global", "--no-audit", "--no-fund", packageSpec];
|
|
138
|
+
}
|
|
139
|
+
function capturedOutput(error, key) {
|
|
140
|
+
if (!error || typeof error !== "object" || !(key in error)) {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
const raw = error[key];
|
|
144
|
+
return String(raw ?? "")
|
|
145
|
+
.trim()
|
|
146
|
+
.slice(0, 1_000);
|
|
147
|
+
}
|
|
148
|
+
export function describeInstallFailure(error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
150
|
+
const stdout = capturedOutput(error, "stdout");
|
|
151
|
+
const stderr = capturedOutput(error, "stderr");
|
|
152
|
+
return [message, stdout && `stdout: ${stdout}`, stderr && `stderr: ${stderr}`]
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join("\n");
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=globalInstaller.js.map
|
|
@@ -134,4 +134,6 @@ export declare function convertClaudeToOpenAIResponse(claude: ClaudeResponse, re
|
|
|
134
134
|
* - message_delta -> captures stop_reason and output token usage
|
|
135
135
|
* - message_stop -> emits the final `finish_reason` chunk + `[DONE]`
|
|
136
136
|
*/
|
|
137
|
-
export declare function createClaudeToOpenAIStreamTransform(requestModel: string
|
|
137
|
+
export declare function createClaudeToOpenAIStreamTransform(requestModel: string, options?: {
|
|
138
|
+
onError?: (message: string) => void;
|
|
139
|
+
}): TransformStream<Uint8Array, Uint8Array>;
|
|
@@ -651,7 +651,7 @@ export function convertClaudeToOpenAIResponse(claude, requestModel) {
|
|
|
651
651
|
* - message_delta -> captures stop_reason and output token usage
|
|
652
652
|
* - message_stop -> emits the final `finish_reason` chunk + `[DONE]`
|
|
653
653
|
*/
|
|
654
|
-
export function createClaudeToOpenAIStreamTransform(requestModel) {
|
|
654
|
+
export function createClaudeToOpenAIStreamTransform(requestModel, options = {}) {
|
|
655
655
|
const serializer = new OpenAIStreamSerializer(requestModel);
|
|
656
656
|
const encoder = new TextEncoder();
|
|
657
657
|
const decoder = new TextDecoder();
|
|
@@ -677,6 +677,9 @@ export function createClaudeToOpenAIStreamTransform(requestModel) {
|
|
|
677
677
|
catch {
|
|
678
678
|
return;
|
|
679
679
|
}
|
|
680
|
+
if (finished) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
680
683
|
switch (eventName) {
|
|
681
684
|
case "message_start": {
|
|
682
685
|
const message = (data.message ?? {});
|
|
@@ -749,8 +752,18 @@ export function createClaudeToOpenAIStreamTransform(requestModel) {
|
|
|
749
752
|
}
|
|
750
753
|
return;
|
|
751
754
|
}
|
|
755
|
+
case "error": {
|
|
756
|
+
const error = (data.error ?? {});
|
|
757
|
+
const message = typeof error.message === "string"
|
|
758
|
+
? error.message
|
|
759
|
+
: "Anthropic stream failed";
|
|
760
|
+
finished = true;
|
|
761
|
+
options.onError?.(message);
|
|
762
|
+
emit(controller, serializer.emitError(message));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
752
765
|
default:
|
|
753
|
-
// ping
|
|
766
|
+
// ping and unknown events are ignored.
|
|
754
767
|
return;
|
|
755
768
|
}
|
|
756
769
|
};
|
|
@@ -790,10 +803,12 @@ export function createClaudeToOpenAIStreamTransform(requestModel) {
|
|
|
790
803
|
// closing `\n\n` is not silently lost.
|
|
791
804
|
buffer += decoder.decode();
|
|
792
805
|
drainBufferedEvents(controller);
|
|
793
|
-
//
|
|
806
|
+
// Closing without message_stop is an interrupted stream, not success.
|
|
794
807
|
if (!finished) {
|
|
795
808
|
finished = true;
|
|
796
|
-
|
|
809
|
+
const message = "Anthropic stream ended before message_stop";
|
|
810
|
+
options.onError?.(message);
|
|
811
|
+
emit(controller, serializer.emitError(message));
|
|
797
812
|
}
|
|
798
813
|
},
|
|
799
814
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProxyActivitySnapshot } from "../types/index.js";
|
|
2
|
+
/** Track one client-facing proxy request until its response body settles. */
|
|
3
|
+
export declare function beginProxyRequest(): () => void;
|
|
4
|
+
export declare function getProxyActivitySnapshot(): ProxyActivitySnapshot;
|
|
5
|
+
export declare function isProxyActivityQuiet(snapshot: ProxyActivitySnapshot, quietThresholdMs: number, nowMs?: number): boolean;
|
|
6
|
+
/** Keep activity open until the response body completes, errors, or is cancelled. */
|
|
7
|
+
export declare function trackProxyResponse(response: Response, finishRequest: () => void): Response;
|
|
8
|
+
export declare function resetProxyActivityForTests(): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
let activeRequests = 0;
|
|
2
|
+
let lastActivityAtMs = null;
|
|
3
|
+
function touchActivity() {
|
|
4
|
+
lastActivityAtMs = Date.now();
|
|
5
|
+
}
|
|
6
|
+
/** Track one client-facing proxy request until its response body settles. */
|
|
7
|
+
export function beginProxyRequest() {
|
|
8
|
+
activeRequests += 1;
|
|
9
|
+
touchActivity();
|
|
10
|
+
let finished = false;
|
|
11
|
+
return () => {
|
|
12
|
+
if (finished) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
finished = true;
|
|
16
|
+
activeRequests = Math.max(0, activeRequests - 1);
|
|
17
|
+
touchActivity();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function getProxyActivitySnapshot() {
|
|
21
|
+
return {
|
|
22
|
+
activeRequests,
|
|
23
|
+
lastActivityAt: lastActivityAtMs === null ? null : new Date(lastActivityAtMs),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function isProxyActivityQuiet(snapshot, quietThresholdMs, nowMs = Date.now()) {
|
|
27
|
+
if (snapshot.activeRequests > 0) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (snapshot.lastActivityAt === null) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return nowMs - snapshot.lastActivityAt.getTime() >= quietThresholdMs;
|
|
34
|
+
}
|
|
35
|
+
/** Keep activity open until the response body completes, errors, or is cancelled. */
|
|
36
|
+
export function trackProxyResponse(response, finishRequest) {
|
|
37
|
+
if (!response.body) {
|
|
38
|
+
finishRequest();
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
const reader = response.body.getReader();
|
|
42
|
+
const trackedBody = new ReadableStream({
|
|
43
|
+
async pull(controller) {
|
|
44
|
+
try {
|
|
45
|
+
const { value, done } = await reader.read();
|
|
46
|
+
if (done) {
|
|
47
|
+
finishRequest();
|
|
48
|
+
controller.close();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
controller.enqueue(value);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
finishRequest();
|
|
55
|
+
controller.error(error);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async cancel(reason) {
|
|
59
|
+
try {
|
|
60
|
+
await reader.cancel(reason);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
finishRequest();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return new Response(trackedBody, {
|
|
68
|
+
status: response.status,
|
|
69
|
+
statusText: response.statusText,
|
|
70
|
+
headers: response.headers,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function resetProxyActivityForTests() {
|
|
74
|
+
activeRequests = 0;
|
|
75
|
+
lastActivityAtMs = null;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=proxyActivity.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, ClaudeLoggedErrorBuilder, 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
|
|
@@ -58,17 +62,24 @@ declare function planCooldownFor429(quota: AccountQuota | null, retryAfterMs: nu
|
|
|
58
62
|
declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise<void>;
|
|
59
63
|
/**
|
|
60
64
|
* Order accounts to MAXIMIZE quota utilization (fill-first, smart order):
|
|
61
|
-
* spend the
|
|
65
|
+
* spend the window that expires SOONEST first, so its about-to-reset
|
|
62
66
|
* allowance isn't wasted, then move to accounts with longer-dated resets.
|
|
63
67
|
*
|
|
64
68
|
* Priority among usable accounts:
|
|
65
69
|
* 1. no quota data yet — probe first: one request reveals its windows and
|
|
66
70
|
* self-corrects the ordering. (Ranking unknowns last would starve them
|
|
67
71
|
* forever: never picked → never observed → never comparable.)
|
|
68
|
-
* 2.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
+
* 2. session headroom before session-saturated (>= soft limit or
|
|
73
|
+
* "throttled") — saturated accounts then follow the same bucketed
|
|
74
|
+
* session ordering below: soonest back in service first, weekly
|
|
75
|
+
* deciding same-bucket ties.
|
|
76
|
+
* 3. soonest SESSION (5h) reset — the capacity expiring soonest; compared
|
|
77
|
+
* in tolerance buckets so near-simultaneous resets count as equal.
|
|
78
|
+
* 4. soonest WEEKLY (7d) reset — decides between same-bucket sessions.
|
|
79
|
+
* 5. highest weekly utilization — finish off the one closest to done
|
|
80
|
+
* 6. configured primary account, then insertion order
|
|
81
|
+
* An account with NO ticking session window (fresh, not yet started) has
|
|
82
|
+
* nothing expiring and sorts after ticking windows in step 3.
|
|
72
83
|
* Cooling/rejected accounts sort last, soonest-back-to-service first, as
|
|
73
84
|
* last resort.
|
|
74
85
|
*/
|
|
@@ -77,11 +88,150 @@ declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>)
|
|
|
77
88
|
stream: ReadableStream<Uint8Array>;
|
|
78
89
|
outcome: Promise<StreamTerminalOutcome>;
|
|
79
90
|
};
|
|
91
|
+
declare function executeClaudeFallbackTranslation(args: {
|
|
92
|
+
ctx: ServerContext;
|
|
93
|
+
body: ClaudeRequest;
|
|
94
|
+
tracer?: ProxyTracer;
|
|
95
|
+
requestStartTime: number;
|
|
96
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
97
|
+
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
|
|
98
|
+
inputTokens?: number;
|
|
99
|
+
outputTokens?: number;
|
|
100
|
+
cacheCreationTokens?: number;
|
|
101
|
+
cacheReadTokens?: number;
|
|
102
|
+
}) => void;
|
|
103
|
+
options: Parameters<ServerContext["neurolink"]["stream"]>[0];
|
|
104
|
+
providerLabel: string;
|
|
105
|
+
}): Promise<unknown>;
|
|
106
|
+
declare function executeClaudeFallbackWithRetry(args: Parameters<typeof executeClaudeFallbackTranslation>[0]): Promise<unknown>;
|
|
107
|
+
declare function buildClaudeAnthropicFailureResponse(args: {
|
|
108
|
+
tracer?: ProxyTracer;
|
|
109
|
+
requestStartTime: number;
|
|
110
|
+
authFailureMessage: string | null;
|
|
111
|
+
authCooldownMessage: string | null;
|
|
112
|
+
invalidRequestFailure: {
|
|
113
|
+
status: number;
|
|
114
|
+
body: string;
|
|
115
|
+
contentType?: string;
|
|
116
|
+
} | null;
|
|
117
|
+
sawNetworkError: boolean;
|
|
118
|
+
sawTransientFailure: boolean;
|
|
119
|
+
sawRateLimit: boolean;
|
|
120
|
+
lastError: unknown;
|
|
121
|
+
fallbackFailureMessage?: string;
|
|
122
|
+
orderedAccounts: ProxyPassthroughAccount[];
|
|
123
|
+
buildLoggedClaudeError: ClaudeLoggedErrorBuilder;
|
|
124
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
125
|
+
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
|
|
126
|
+
inputTokens?: number;
|
|
127
|
+
outputTokens?: number;
|
|
128
|
+
cacheCreationTokens?: number;
|
|
129
|
+
cacheReadTokens?: number;
|
|
130
|
+
}) => void;
|
|
131
|
+
}): unknown;
|
|
132
|
+
declare function handleAnthropicStreamingSuccessResponse(args: {
|
|
133
|
+
ctx: ServerContext;
|
|
134
|
+
body: ClaudeRequest;
|
|
135
|
+
account: ProxyPassthroughAccount;
|
|
136
|
+
accountState: RuntimeAccountState;
|
|
137
|
+
response: Response;
|
|
138
|
+
responseHeaders: Record<string, string>;
|
|
139
|
+
tracer?: ProxyTracer;
|
|
140
|
+
requestStartTime: number;
|
|
141
|
+
fetchStartMs: number;
|
|
142
|
+
attemptNumber: number;
|
|
143
|
+
finalBodyStr: string;
|
|
144
|
+
upstreamSpan?: import("@opentelemetry/api").Span;
|
|
145
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
146
|
+
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
|
|
147
|
+
inputTokens?: number;
|
|
148
|
+
outputTokens?: number;
|
|
149
|
+
cacheCreationTokens?: number;
|
|
150
|
+
cacheReadTokens?: number;
|
|
151
|
+
}) => void;
|
|
152
|
+
}): Promise<AnthropicSuccessResult>;
|
|
80
153
|
declare function getStreamFailureDetails(outcome: StreamTerminalOutcome): {
|
|
81
154
|
status: number;
|
|
82
155
|
errorType: string;
|
|
83
156
|
message: string;
|
|
84
157
|
} | undefined;
|
|
158
|
+
declare function handleAnthropicAuthRetry(args: {
|
|
159
|
+
ctx: ServerContext;
|
|
160
|
+
body: ClaudeRequest;
|
|
161
|
+
account: ProxyPassthroughAccount;
|
|
162
|
+
accountState: RuntimeAccountState;
|
|
163
|
+
headers: Record<string, string>;
|
|
164
|
+
buildUpstreamBody: (token: string) => {
|
|
165
|
+
bodyStr: string;
|
|
166
|
+
sessionId?: string;
|
|
167
|
+
};
|
|
168
|
+
enabledAccounts: ProxyPassthroughAccount[];
|
|
169
|
+
orderedAccounts: ProxyPassthroughAccount[];
|
|
170
|
+
response: Response;
|
|
171
|
+
tracer?: ProxyTracer;
|
|
172
|
+
requestStartTime: number;
|
|
173
|
+
fetchStartMs: number;
|
|
174
|
+
attemptNumber: number;
|
|
175
|
+
finalBodyStr: string;
|
|
176
|
+
upstreamSpan?: import("@opentelemetry/api").Span;
|
|
177
|
+
logAttempt: (status: number, errorType?: string, errorMessage?: string, extra?: {
|
|
178
|
+
inputTokens?: number;
|
|
179
|
+
outputTokens?: number;
|
|
180
|
+
cacheCreationTokens?: number;
|
|
181
|
+
cacheReadTokens?: number;
|
|
182
|
+
}) => void;
|
|
183
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
184
|
+
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
|
|
185
|
+
inputTokens?: number;
|
|
186
|
+
outputTokens?: number;
|
|
187
|
+
cacheCreationTokens?: number;
|
|
188
|
+
cacheReadTokens?: number;
|
|
189
|
+
}) => void;
|
|
190
|
+
lastError: unknown;
|
|
191
|
+
authFailureMessage: string | null;
|
|
192
|
+
sawRateLimit: boolean;
|
|
193
|
+
sawTransientFailure: boolean;
|
|
194
|
+
sawNetworkError: boolean;
|
|
195
|
+
}): Promise<AnthropicAuthRetryResult>;
|
|
196
|
+
declare function finalizeAnthropicTerminalFetchError(args: {
|
|
197
|
+
terminalError: NonNullable<AnthropicUpstreamFetchResult["terminalError"]>;
|
|
198
|
+
account: ProxyPassthroughAccount;
|
|
199
|
+
tracer?: ProxyTracer;
|
|
200
|
+
requestStartTime: number;
|
|
201
|
+
attemptNumber: number;
|
|
202
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
203
|
+
logFinalRequest: ClaudeFinalRequestLogger;
|
|
204
|
+
}): Response | unknown;
|
|
205
|
+
/**
|
|
206
|
+
* Detect Anthropic's anti-abuse / request-construction 429.
|
|
207
|
+
*
|
|
208
|
+
* The subscription/OAuth path rejects requests it does not recognise as genuine
|
|
209
|
+
* Claude Code traffic with a 429 `rate_limit_error` whose message is literally
|
|
210
|
+
* "Error" and which carries NONE of the real rate-limit headers (no retry-after,
|
|
211
|
+
* no anthropic-ratelimit-*). This is NOT a capacity limit — retrying or rotating
|
|
212
|
+
* accounts cannot fix it and only burns quota, so the caller must fail fast and
|
|
213
|
+
* return a non-retryable request error instead of "all accounts rate-limited".
|
|
214
|
+
*/
|
|
215
|
+
declare function isAntiAbuseConstruction429(headers: Record<string, string>, body: string): boolean;
|
|
216
|
+
declare function fetchAnthropicAccountResponse(args: {
|
|
217
|
+
url: string;
|
|
218
|
+
headers: Record<string, string>;
|
|
219
|
+
finalBodyStr: string;
|
|
220
|
+
account: ProxyPassthroughAccount;
|
|
221
|
+
accountState: RuntimeAccountState;
|
|
222
|
+
enabledAccounts: ProxyPassthroughAccount[];
|
|
223
|
+
orderedAccounts: ProxyPassthroughAccount[];
|
|
224
|
+
tracer?: ProxyTracer;
|
|
225
|
+
logAttempt: AnthropicAttemptLogger;
|
|
226
|
+
logProxyBody: ProxyBodyCaptureLogger;
|
|
227
|
+
fetchStartMs: number;
|
|
228
|
+
attemptNumber: number;
|
|
229
|
+
currentLastError: unknown;
|
|
230
|
+
currentSawRateLimit: boolean;
|
|
231
|
+
currentSawNetworkError: boolean;
|
|
232
|
+
upstreamSpan?: import("@opentelemetry/api").Span;
|
|
233
|
+
}): Promise<AnthropicUpstreamFetchResult>;
|
|
234
|
+
declare function shouldAttemptClaudeFallback(loopState: AnthropicLoopState): boolean;
|
|
85
235
|
/**
|
|
86
236
|
* Create Claude-compatible proxy routes.
|
|
87
237
|
*
|
|
@@ -94,6 +244,7 @@ declare function getStreamFailureDetails(outcome: StreamTerminalOutcome): {
|
|
|
94
244
|
*/
|
|
95
245
|
export declare function createClaudeProxyRoutes(modelRouter?: ModelRouter, basePath?: string, accountStrategy?: "round-robin" | "fill-first", passthroughMode?: boolean, primaryAccountKey?: string, accountAllowlist?: AccountAllowlist): RouteGroup;
|
|
96
246
|
export declare function getTransientSameAccountRetryDelayMs(retryNumber: number): number;
|
|
247
|
+
declare function describeTransportError(error: unknown): string;
|
|
97
248
|
/**
|
|
98
249
|
* Parse a Claude error payload when available.
|
|
99
250
|
*/
|
|
@@ -132,5 +283,21 @@ export declare const __testHooks: {
|
|
|
132
283
|
getPrimaryAccountIndex: () => number;
|
|
133
284
|
setAccountRuntimeState: (key: string, state: Partial<RuntimeAccountState>) => void;
|
|
134
285
|
resetAllRuntimeState: () => void;
|
|
286
|
+
polyfillOAuthBody: (bodyStr: string, isClaudeClientRequest: boolean) => {
|
|
287
|
+
bodyStr: string;
|
|
288
|
+
sessionId?: string;
|
|
289
|
+
};
|
|
290
|
+
isAntiAbuseConstruction429: typeof isAntiAbuseConstruction429;
|
|
291
|
+
fetchAnthropicAccountResponse: typeof fetchAnthropicAccountResponse;
|
|
292
|
+
finalizeAnthropicTerminalFetchError: typeof finalizeAnthropicTerminalFetchError;
|
|
293
|
+
handleAnthropicAuthRetry: typeof handleAnthropicAuthRetry;
|
|
294
|
+
handleAnthropicStreamingSuccessResponse: typeof handleAnthropicStreamingSuccessResponse;
|
|
295
|
+
claimTransientRateLimitRetry: typeof claimTransientRateLimitRetry;
|
|
296
|
+
claimTransientCooldownAdmission: typeof claimTransientCooldownAdmission;
|
|
297
|
+
waitForTransientAccountAvailability: typeof waitForTransientAccountAvailability;
|
|
298
|
+
describeTransportError: typeof describeTransportError;
|
|
299
|
+
shouldAttemptClaudeFallback: typeof shouldAttemptClaudeFallback;
|
|
300
|
+
executeClaudeFallbackWithRetry: typeof executeClaudeFallbackWithRetry;
|
|
301
|
+
buildClaudeAnthropicFailureResponse: typeof buildClaudeAnthropicFailureResponse;
|
|
135
302
|
};
|
|
136
303
|
export {};
|