@juspay/neurolink 9.91.0 → 9.92.0
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 +352 -351
- package/dist/cli/commands/auth.js +25 -15
- package/dist/cli/commands/proxy.d.ts +4 -3
- package/dist/cli/commands/proxy.js +187 -130
- package/dist/lib/providers/sagemaker/client.d.ts +9 -0
- package/dist/lib/providers/sagemaker/client.js +75 -30
- package/dist/lib/proxy/proxyConfig.js +66 -0
- package/dist/lib/proxy/runtimeConfig.d.ts +26 -0
- package/dist/lib/proxy/runtimeConfig.js +428 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +5 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +68 -37
- package/dist/lib/server/routes/openaiProxyRoutes.d.ts +2 -3
- package/dist/lib/server/routes/openaiProxyRoutes.js +11 -5
- package/dist/lib/types/cli.d.ts +8 -0
- package/dist/lib/types/proxy.d.ts +67 -1
- package/dist/lib/types/subscription.d.ts +6 -0
- package/dist/providers/sagemaker/client.d.ts +9 -0
- package/dist/providers/sagemaker/client.js +75 -30
- package/dist/proxy/proxyConfig.js +66 -0
- package/dist/proxy/runtimeConfig.d.ts +26 -0
- package/dist/proxy/runtimeConfig.js +427 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +5 -6
- package/dist/server/routes/claudeProxyRoutes.js +68 -37
- package/dist/server/routes/openaiProxyRoutes.d.ts +2 -3
- package/dist/server/routes/openaiProxyRoutes.js +11 -5
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/proxy.d.ts +67 -1
- package/dist/types/subscription.d.ts +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { watchFile, unwatchFile } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { createAccountAllowlist, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, } from "./accountSelection.js";
|
|
5
|
+
import { ModelRouter } from "./modelRouter.js";
|
|
6
|
+
import { loadProxyConfig } from "./proxyConfig.js";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
const DEFAULT_WATCH_INTERVAL_MS = 1_000;
|
|
9
|
+
const DEFAULT_WATCH_DEBOUNCE_MS = 100;
|
|
10
|
+
const DEFAULT_SESSION_SOFT_LIMIT = 0.97;
|
|
11
|
+
const DEFAULT_SESSION_RESET_TOLERANCE_MS = 15 * 60 * 1_000;
|
|
12
|
+
function errorCode(error) {
|
|
13
|
+
let current = error;
|
|
14
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
15
|
+
if (!current || typeof current !== "object") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const code = current.code;
|
|
19
|
+
if (typeof code === "string") {
|
|
20
|
+
return code;
|
|
21
|
+
}
|
|
22
|
+
current = current.cause;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function isMissingFileError(error) {
|
|
27
|
+
return errorCode(error) === "ENOENT";
|
|
28
|
+
}
|
|
29
|
+
function normalizeStrategy(configured, override) {
|
|
30
|
+
if (override) {
|
|
31
|
+
return override;
|
|
32
|
+
}
|
|
33
|
+
return configured === "round-robin" ? "round-robin" : "fill-first";
|
|
34
|
+
}
|
|
35
|
+
function resolveQuotaRoutingEnabled(envValue, configured) {
|
|
36
|
+
if (envValue === undefined) {
|
|
37
|
+
return configured ?? true;
|
|
38
|
+
}
|
|
39
|
+
const normalized = envValue.trim().toLowerCase();
|
|
40
|
+
return normalized !== "off" && normalized !== "false" && normalized !== "0";
|
|
41
|
+
}
|
|
42
|
+
function resolveSessionSoftLimit(envValue, configured, rejectInvalid) {
|
|
43
|
+
if (envValue !== undefined) {
|
|
44
|
+
const parsed = Number(envValue);
|
|
45
|
+
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 1) {
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
const message = `Invalid NEUROLINK_PROXY_SESSION_SOFT_LIMIT=${envValue}; ` +
|
|
49
|
+
"expected number in (0, 1]";
|
|
50
|
+
if (rejectInvalid) {
|
|
51
|
+
throw new Error(message);
|
|
52
|
+
}
|
|
53
|
+
logger.warn(`[proxy-config] ${message}; using configured/default value`);
|
|
54
|
+
}
|
|
55
|
+
return configured ?? DEFAULT_SESSION_SOFT_LIMIT;
|
|
56
|
+
}
|
|
57
|
+
function resolveSessionResetToleranceMs(envValue, configured, rejectInvalid) {
|
|
58
|
+
if (envValue !== undefined) {
|
|
59
|
+
const parsed = Number(envValue);
|
|
60
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
const message = `Invalid NEUROLINK_PROXY_SESSION_RESET_TOLERANCE_MS=${envValue}; ` +
|
|
64
|
+
"expected positive integer";
|
|
65
|
+
if (rejectInvalid) {
|
|
66
|
+
throw new Error(message);
|
|
67
|
+
}
|
|
68
|
+
logger.warn(`[proxy-config] ${message}; using configured/default value`);
|
|
69
|
+
}
|
|
70
|
+
return configured ?? DEFAULT_SESSION_RESET_TOLERANCE_MS;
|
|
71
|
+
}
|
|
72
|
+
async function resolvePrimaryAccountKey(primaryEmail) {
|
|
73
|
+
const trimmed = primaryEmail?.trim();
|
|
74
|
+
if (!trimmed) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const key = normalizeAnthropicAccountKey(trimmed);
|
|
78
|
+
try {
|
|
79
|
+
const { tokenStore } = await import("../auth/tokenStore.js");
|
|
80
|
+
const known = await tokenStore.listByPrefix("anthropic:");
|
|
81
|
+
if (!known.some((knownKey) => normalizeAnthropicAccountKey(knownKey) === key)) {
|
|
82
|
+
logger.warn(`[proxy] WARN: configured routing.primaryAccount=${trimmed} not found in token store; it will activate after authentication`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger.debug(`[proxy] could not validate primary account against token store: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
async function resolveAccountAllowlist(configuredAccounts) {
|
|
91
|
+
const allowlist = createAccountAllowlist(configuredAccounts);
|
|
92
|
+
if (allowlist === undefined) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
if (allowlist.size === 0) {
|
|
96
|
+
logger.warn("[proxy] routing.accountAllowlist is empty; all stored Anthropic credentials are denied");
|
|
97
|
+
return allowlist;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const { tokenStore } = await import("../auth/tokenStore.js");
|
|
101
|
+
const known = new Set((await tokenStore.listByPrefix("anthropic:")).map(normalizeAnthropicAccountKey));
|
|
102
|
+
known.add(LEGACY_ANTHROPIC_ACCOUNT_KEY);
|
|
103
|
+
known.add(ENV_ANTHROPIC_ACCOUNT_KEY);
|
|
104
|
+
for (const key of allowlist) {
|
|
105
|
+
if (!known.has(key)) {
|
|
106
|
+
logger.warn(`[proxy] WARN: routing.accountAllowlist entry ${key} is allowed but not currently authenticated`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logger.debug(`[proxy] could not validate account allowlist against token store: ${error instanceof Error ? error.message : String(error)}`);
|
|
112
|
+
}
|
|
113
|
+
return allowlist;
|
|
114
|
+
}
|
|
115
|
+
function cloneLoadedConfig(loaded) {
|
|
116
|
+
if (!loaded) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const routing = loaded.routing;
|
|
120
|
+
if (!routing) {
|
|
121
|
+
return Object.freeze({});
|
|
122
|
+
}
|
|
123
|
+
return Object.freeze({
|
|
124
|
+
routing: Object.freeze({
|
|
125
|
+
...routing,
|
|
126
|
+
...(routing.modelMappings
|
|
127
|
+
? {
|
|
128
|
+
modelMappings: Object.freeze(routing.modelMappings.map((entry) => Object.freeze({ ...entry }))),
|
|
129
|
+
}
|
|
130
|
+
: {}),
|
|
131
|
+
...(routing.fallbackChain
|
|
132
|
+
? {
|
|
133
|
+
fallbackChain: Object.freeze(routing.fallbackChain.map((entry) => Object.freeze({ ...entry }))),
|
|
134
|
+
}
|
|
135
|
+
: {}),
|
|
136
|
+
...(routing.passthroughModels
|
|
137
|
+
? { passthroughModels: Object.freeze([...routing.passthroughModels]) }
|
|
138
|
+
: {}),
|
|
139
|
+
...(routing.accountAllowlist
|
|
140
|
+
? { accountAllowlist: Object.freeze([...routing.accountAllowlist]) }
|
|
141
|
+
: {}),
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function assertResolvedRoutingValues(value, path = "routing") {
|
|
146
|
+
if (typeof value === "string") {
|
|
147
|
+
if (value.includes("${")) {
|
|
148
|
+
throw new Error(`Unresolved environment placeholder at ${path}`);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
value.forEach((entry, index) => assertResolvedRoutingValues(entry, `${path}[${index}]`));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (value && typeof value === "object") {
|
|
157
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
158
|
+
assertResolvedRoutingValues(entry, `${path}.${key}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function buildCandidate(options, generation, allowMissingConfig, allowMissingEnvFile, rejectInvalidHotEnv) {
|
|
163
|
+
const effectiveEnv = { ...options.baseEnv };
|
|
164
|
+
let envFilePresent = false;
|
|
165
|
+
if (options.envFilePath) {
|
|
166
|
+
try {
|
|
167
|
+
const envContent = await readFile(options.envFilePath, "utf8");
|
|
168
|
+
const { parse } = await import("dotenv");
|
|
169
|
+
Object.assign(effectiveEnv, parse(envContent));
|
|
170
|
+
envFilePresent = true;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (!isMissingFileError(error) ||
|
|
174
|
+
options.envFileRequired ||
|
|
175
|
+
!allowMissingEnvFile) {
|
|
176
|
+
throw new Error(`Failed to load proxy env file ${options.envFilePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
let loadedConfig = null;
|
|
181
|
+
let configFilePresent = false;
|
|
182
|
+
try {
|
|
183
|
+
loadedConfig = (await loadProxyConfig(options.configPath, {
|
|
184
|
+
env: effectiveEnv,
|
|
185
|
+
}));
|
|
186
|
+
configFilePresent = true;
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (!isMissingFileError(error) ||
|
|
190
|
+
options.configRequired ||
|
|
191
|
+
!allowMissingConfig) {
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const proxyConfig = cloneLoadedConfig(loadedConfig);
|
|
196
|
+
const routing = proxyConfig?.routing;
|
|
197
|
+
assertResolvedRoutingValues(routing);
|
|
198
|
+
const strategy = normalizeStrategy(routing?.strategy, options.strategyOverride);
|
|
199
|
+
const primaryAccountKey = await resolvePrimaryAccountKey(routing?.primaryAccount);
|
|
200
|
+
const accountAllowlist = await resolveAccountAllowlist(routing?.accountAllowlist);
|
|
201
|
+
if (primaryAccountKey &&
|
|
202
|
+
!isAccountAllowed(primaryAccountKey, accountAllowlist)) {
|
|
203
|
+
throw new Error(`Configured routing.primaryAccount=${routing?.primaryAccount} is excluded by routing.accountAllowlist`);
|
|
204
|
+
}
|
|
205
|
+
const quotaRoutingEnabled = resolveQuotaRoutingEnabled(effectiveEnv.NEUROLINK_PROXY_QUOTA_ROUTING, routing?.quotaRouting);
|
|
206
|
+
const sessionSoftLimit = resolveSessionSoftLimit(effectiveEnv.NEUROLINK_PROXY_SESSION_SOFT_LIMIT, routing?.sessionSoftLimit, rejectInvalidHotEnv);
|
|
207
|
+
const sessionResetToleranceMs = resolveSessionResetToleranceMs(effectiveEnv.NEUROLINK_PROXY_SESSION_RESET_TOLERANCE_MS, routing?.sessionResetToleranceMs, rejectInvalidHotEnv);
|
|
208
|
+
const modelRouter = routing
|
|
209
|
+
? new ModelRouter({
|
|
210
|
+
strategy,
|
|
211
|
+
modelMappings: routing.modelMappings ?? [],
|
|
212
|
+
fallbackChain: routing.fallbackChain ?? [],
|
|
213
|
+
passthroughModels: routing.passthroughModels,
|
|
214
|
+
quotaRouting: routing.quotaRouting,
|
|
215
|
+
sessionSoftLimit: routing.sessionSoftLimit,
|
|
216
|
+
sessionResetToleranceMs: routing.sessionResetToleranceMs,
|
|
217
|
+
primaryAccount: routing.primaryAccount,
|
|
218
|
+
accountAllowlist: routing.accountAllowlist,
|
|
219
|
+
})
|
|
220
|
+
: undefined;
|
|
221
|
+
const fingerprintSource = JSON.stringify({
|
|
222
|
+
strategy,
|
|
223
|
+
passthrough: options.passthrough,
|
|
224
|
+
routing: routing ?? null,
|
|
225
|
+
primaryAccountKey,
|
|
226
|
+
accountAllowlist: accountAllowlist ? [...accountAllowlist].sort() : null,
|
|
227
|
+
quotaRoutingEnabled,
|
|
228
|
+
sessionSoftLimit,
|
|
229
|
+
sessionResetToleranceMs,
|
|
230
|
+
});
|
|
231
|
+
const configHash = createHash("sha256")
|
|
232
|
+
.update(fingerprintSource)
|
|
233
|
+
.digest("hex")
|
|
234
|
+
.slice(0, 16);
|
|
235
|
+
return {
|
|
236
|
+
snapshot: Object.freeze({
|
|
237
|
+
generation,
|
|
238
|
+
loadedAt: new Date().toISOString(),
|
|
239
|
+
configHash,
|
|
240
|
+
proxyConfig,
|
|
241
|
+
strategy,
|
|
242
|
+
modelRouter,
|
|
243
|
+
passthrough: options.passthrough,
|
|
244
|
+
primaryAccountKey,
|
|
245
|
+
accountAllowlist,
|
|
246
|
+
quotaRoutingEnabled,
|
|
247
|
+
sessionSoftLimit,
|
|
248
|
+
sessionResetToleranceMs,
|
|
249
|
+
}),
|
|
250
|
+
configFilePresent,
|
|
251
|
+
envFilePresent,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/** Atomic last-known-good runtime configuration with file-triggered reloads. */
|
|
255
|
+
export class ProxyRuntimeConfigStore {
|
|
256
|
+
options;
|
|
257
|
+
currentSnapshot;
|
|
258
|
+
status;
|
|
259
|
+
listeners = new Set();
|
|
260
|
+
reloadListeners = new Set();
|
|
261
|
+
reloadQueue = Promise.resolve();
|
|
262
|
+
reloadTimer;
|
|
263
|
+
configFileObserved;
|
|
264
|
+
envFileObserved;
|
|
265
|
+
watchListener = (current, previous) => {
|
|
266
|
+
if (current.mtimeMs === previous.mtimeMs &&
|
|
267
|
+
current.size === previous.size &&
|
|
268
|
+
current.nlink === previous.nlink) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.scheduleWatchReload();
|
|
272
|
+
};
|
|
273
|
+
constructor(options, snapshot, configFileObserved, envFileObserved) {
|
|
274
|
+
this.options = { ...options, baseEnv: { ...options.baseEnv } };
|
|
275
|
+
this.currentSnapshot = snapshot;
|
|
276
|
+
this.configFileObserved = configFileObserved;
|
|
277
|
+
this.envFileObserved = envFileObserved;
|
|
278
|
+
this.status = {
|
|
279
|
+
configPath: options.configPath,
|
|
280
|
+
...(options.envFilePath ? { envFilePath: options.envFilePath } : {}),
|
|
281
|
+
generation: snapshot.generation,
|
|
282
|
+
loadedAt: snapshot.loadedAt,
|
|
283
|
+
configHash: snapshot.configHash,
|
|
284
|
+
watching: false,
|
|
285
|
+
consecutiveFailures: 0,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
static async create(options) {
|
|
289
|
+
const candidate = await buildCandidate(options, 1, true, true, false);
|
|
290
|
+
return new ProxyRuntimeConfigStore(options, candidate.snapshot, candidate.configFilePresent, candidate.envFilePresent);
|
|
291
|
+
}
|
|
292
|
+
getSnapshot() {
|
|
293
|
+
return this.currentSnapshot;
|
|
294
|
+
}
|
|
295
|
+
getStatus() {
|
|
296
|
+
return { ...this.status };
|
|
297
|
+
}
|
|
298
|
+
subscribe(listener) {
|
|
299
|
+
this.listeners.add(listener);
|
|
300
|
+
return () => this.listeners.delete(listener);
|
|
301
|
+
}
|
|
302
|
+
subscribeReload(listener) {
|
|
303
|
+
this.reloadListeners.add(listener);
|
|
304
|
+
return () => this.reloadListeners.delete(listener);
|
|
305
|
+
}
|
|
306
|
+
reload(source = "manual") {
|
|
307
|
+
const run = this.reloadQueue.then(() => this.performReload(source), () => this.performReload(source));
|
|
308
|
+
this.reloadQueue = run.then(() => undefined, () => undefined);
|
|
309
|
+
return run;
|
|
310
|
+
}
|
|
311
|
+
startWatching() {
|
|
312
|
+
if (this.status.watching) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const interval = Math.max(50, this.options.watchIntervalMs ?? DEFAULT_WATCH_INTERVAL_MS);
|
|
316
|
+
watchFile(this.options.configPath, { interval, persistent: false }, this.watchListener);
|
|
317
|
+
if (this.options.envFilePath) {
|
|
318
|
+
watchFile(this.options.envFilePath, { interval, persistent: false }, this.watchListener);
|
|
319
|
+
}
|
|
320
|
+
this.status = { ...this.status, watching: true };
|
|
321
|
+
}
|
|
322
|
+
stopWatching() {
|
|
323
|
+
if (!this.status.watching) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
unwatchFile(this.options.configPath, this.watchListener);
|
|
327
|
+
if (this.options.envFilePath) {
|
|
328
|
+
unwatchFile(this.options.envFilePath, this.watchListener);
|
|
329
|
+
}
|
|
330
|
+
if (this.reloadTimer) {
|
|
331
|
+
clearTimeout(this.reloadTimer);
|
|
332
|
+
this.reloadTimer = undefined;
|
|
333
|
+
}
|
|
334
|
+
this.status = { ...this.status, watching: false };
|
|
335
|
+
}
|
|
336
|
+
scheduleWatchReload() {
|
|
337
|
+
if (this.reloadTimer) {
|
|
338
|
+
clearTimeout(this.reloadTimer);
|
|
339
|
+
}
|
|
340
|
+
this.reloadTimer = setTimeout(() => {
|
|
341
|
+
this.reloadTimer = undefined;
|
|
342
|
+
void this.reload("watch");
|
|
343
|
+
}, Math.max(0, this.options.watchDebounceMs ?? DEFAULT_WATCH_DEBOUNCE_MS));
|
|
344
|
+
this.reloadTimer.unref?.();
|
|
345
|
+
}
|
|
346
|
+
async performReload(source) {
|
|
347
|
+
const attemptedAt = new Date().toISOString();
|
|
348
|
+
this.status = {
|
|
349
|
+
...this.status,
|
|
350
|
+
lastReloadAttemptAt: attemptedAt,
|
|
351
|
+
lastReloadSource: source,
|
|
352
|
+
};
|
|
353
|
+
try {
|
|
354
|
+
const candidate = await buildCandidate(this.options, this.currentSnapshot.generation + 1, !this.configFileObserved, !this.envFileObserved, true);
|
|
355
|
+
this.configFileObserved ||= candidate.configFilePresent;
|
|
356
|
+
this.envFileObserved ||= candidate.envFilePresent;
|
|
357
|
+
if (candidate.snapshot.configHash === this.currentSnapshot.configHash) {
|
|
358
|
+
this.status = {
|
|
359
|
+
...this.status,
|
|
360
|
+
lastReloadAt: attemptedAt,
|
|
361
|
+
lastReloadError: undefined,
|
|
362
|
+
consecutiveFailures: 0,
|
|
363
|
+
};
|
|
364
|
+
const result = {
|
|
365
|
+
applied: true,
|
|
366
|
+
changed: false,
|
|
367
|
+
generation: this.currentSnapshot.generation,
|
|
368
|
+
};
|
|
369
|
+
this.notifyReloadListeners(result);
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
this.currentSnapshot = candidate.snapshot;
|
|
373
|
+
this.status = {
|
|
374
|
+
...this.status,
|
|
375
|
+
generation: candidate.snapshot.generation,
|
|
376
|
+
loadedAt: candidate.snapshot.loadedAt,
|
|
377
|
+
configHash: candidate.snapshot.configHash,
|
|
378
|
+
lastReloadAt: attemptedAt,
|
|
379
|
+
lastReloadError: undefined,
|
|
380
|
+
consecutiveFailures: 0,
|
|
381
|
+
};
|
|
382
|
+
logger.always(`[proxy] configuration generation ${candidate.snapshot.generation} applied via ${source}`);
|
|
383
|
+
for (const listener of this.listeners) {
|
|
384
|
+
try {
|
|
385
|
+
listener(candidate.snapshot);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
logger.warn(`[proxy] runtime config listener failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const result = {
|
|
392
|
+
applied: true,
|
|
393
|
+
changed: true,
|
|
394
|
+
generation: candidate.snapshot.generation,
|
|
395
|
+
};
|
|
396
|
+
this.notifyReloadListeners(result);
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
401
|
+
this.status = {
|
|
402
|
+
...this.status,
|
|
403
|
+
lastReloadError: message,
|
|
404
|
+
consecutiveFailures: this.status.consecutiveFailures + 1,
|
|
405
|
+
};
|
|
406
|
+
logger.warn(`[proxy] configuration reload rejected; keeping generation ${this.currentSnapshot.generation}: ${message}`);
|
|
407
|
+
const result = {
|
|
408
|
+
applied: false,
|
|
409
|
+
changed: false,
|
|
410
|
+
generation: this.currentSnapshot.generation,
|
|
411
|
+
error: message,
|
|
412
|
+
};
|
|
413
|
+
this.notifyReloadListeners(result);
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
notifyReloadListeners(result) {
|
|
418
|
+
for (const listener of this.reloadListeners) {
|
|
419
|
+
try {
|
|
420
|
+
listener(result, this.getStatus());
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
logger.warn(`[proxy] runtime config reload listener failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=runtimeConfig.js.map
|
|
@@ -10,21 +10,20 @@
|
|
|
10
10
|
* Without a router, models are passed through to the Anthropic provider.
|
|
11
11
|
*/
|
|
12
12
|
import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
|
|
13
|
-
import type { ModelRouter } from "../../proxy/modelRouter.js";
|
|
14
13
|
import { ProxyTracer } from "../../proxy/proxyTracer.js";
|
|
15
14
|
import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.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";
|
|
15
|
+
import type { AccountAllowlist, AccountCooldownPlan, AccountQuota, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicLoopState, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyBodyCaptureLogger, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
|
|
17
16
|
/** Resolve the configured primary's stable key to its current index in the
|
|
18
17
|
* request's enabledAccounts list. Returns 0 (insertion-order fallback) when
|
|
19
18
|
* no key is configured or the key cannot be matched (account disabled/
|
|
20
19
|
* removed). The resolution is per-request because enabledAccounts membership
|
|
21
20
|
* can shift between requests. */
|
|
22
|
-
declare function resolveHomeIndex(enabledAccounts: ProxyPassthroughAccount[]): number;
|
|
21
|
+
declare function resolveHomeIndex(enabledAccounts: ProxyPassthroughAccount[], primaryAccountKey?: string | undefined): number;
|
|
23
22
|
/** If the configured home primary is no longer cooling, reset
|
|
24
23
|
* primaryAccountIndex back to its index so traffic returns to the preferred
|
|
25
24
|
* account once its rate limit window expires. Called at the start of each
|
|
26
25
|
* request. Home is resolved fresh per call via resolveHomeIndex. */
|
|
27
|
-
declare function maybeResetPrimaryToHome(enabledAccounts: ProxyPassthroughAccount[]): void;
|
|
26
|
+
declare function maybeResetPrimaryToHome(enabledAccounts: ProxyPassthroughAccount[], primaryAccountKey?: string | undefined): void;
|
|
28
27
|
declare function claimTransientRateLimitRetry(accountKey: string, coolingUntil: number, now?: number): number | undefined;
|
|
29
28
|
declare function claimTransientCooldownAdmission(accountKey: string, coolingUntil: number, now?: number): number | undefined;
|
|
30
29
|
declare function waitForTransientAccountAvailability(orderedAccounts: ProxyPassthroughAccount[]): Promise<ProxyPassthroughAccount[]>;
|
|
@@ -83,7 +82,7 @@ declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]):
|
|
|
83
82
|
* Cooling/rejected accounts sort last, soonest-back-to-service first, as
|
|
84
83
|
* last resort.
|
|
85
84
|
*/
|
|
86
|
-
declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number): ProxyPassthroughAccount[];
|
|
85
|
+
declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number, primaryKey?: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number): ProxyPassthroughAccount[];
|
|
87
86
|
declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>): {
|
|
88
87
|
stream: ReadableStream<Uint8Array>;
|
|
89
88
|
outcome: Promise<StreamTerminalOutcome>;
|
|
@@ -234,7 +233,7 @@ declare function shouldAttemptClaudeFallback(loopState: AnthropicLoopState): boo
|
|
|
234
233
|
* @param basePath - Base path prefix (default: "" since Claude API uses /v1/...).
|
|
235
234
|
* @returns RouteGroup with Claude-compatible endpoints.
|
|
236
235
|
*/
|
|
237
|
-
export declare function createClaudeProxyRoutes(modelRouter?:
|
|
236
|
+
export declare function createClaudeProxyRoutes(modelRouter?: ModelRouterInterface, basePath?: string, accountStrategy?: "round-robin" | "fill-first", passthroughMode?: boolean, primaryAccountKey?: string, accountAllowlistOrRuntimeOptions?: AccountAllowlist | ClaudeProxyRouteRuntimeOptions): RouteGroup;
|
|
238
237
|
export declare function getTransientSameAccountRetryDelayMs(retryNumber: number): number;
|
|
239
238
|
declare function describeTransportError(error: unknown): string;
|
|
240
239
|
/**
|