@juspay/neurolink 12.7.3 → 12.7.5
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/browser/neurolink.min.js +399 -399
- package/dist/cli/commands/auth.js +252 -125
- package/dist/cli/commands/proxy.js +110 -91
- package/dist/cli/factories/authCommandFactory.js +8 -11
- package/dist/memory/memoryRetrievalTools.d.ts +5 -2
- package/dist/memory/memoryRetrievalTools.js +11 -3
- package/dist/neurolink.js +5 -4
- package/dist/proxy/codexAccountUsage.d.ts +1 -1
- package/dist/proxy/codexAccountUsage.js +13 -1
- package/dist/proxy/proxyActivity.js +12 -1
- package/dist/proxy/usageStats.d.ts +2 -2
- package/dist/proxy/usageStats.js +66 -10
- package/dist/server/routes/claudeProxyRoutes.d.ts +2 -1
- package/dist/server/routes/claudeProxyRoutes.js +62 -12
- package/dist/server/routes/codexProxyRoutes.d.ts +8 -2
- package/dist/server/routes/codexProxyRoutes.js +191 -58
- package/dist/types/cli.d.ts +37 -5
- package/dist/types/proxy.d.ts +37 -0
- package/package.json +2 -2
package/dist/proxy/usageStats.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { mkdir, open, readFile, readdir, rename, rm, stat, } from "node:fs/promises";
|
|
10
10
|
import { basename, dirname, join } from "node:path";
|
|
11
|
+
import { normalizeAnthropicAccountKey } from "./accountSelection.js";
|
|
11
12
|
import { AsyncMutex } from "../utils/asyncMutex.js";
|
|
12
13
|
import { redactUrlsInText, sanitizeForLog } from "../utils/logSanitize.js";
|
|
13
14
|
import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
|
|
@@ -63,6 +64,34 @@ function emptyTerminalErrorJournal(startedAt) {
|
|
|
63
64
|
recent: [],
|
|
64
65
|
};
|
|
65
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Statistics must never use an email/label as their primary map key. One
|
|
69
|
+
* person can authenticate both provider pools with the same email, and a
|
|
70
|
+
* bare key silently merged their attempts, limits, and failures into one row.
|
|
71
|
+
*
|
|
72
|
+
* Keep non-account pseudo rows (passthrough, peer, internal) untouched. The
|
|
73
|
+
* display label remains separate so the CLI stays readable while persisted
|
|
74
|
+
* counters retain a collision-proof identity.
|
|
75
|
+
*/
|
|
76
|
+
function resolveStatsAccountIdentity(label, type) {
|
|
77
|
+
if (type === "codex-oauth") {
|
|
78
|
+
const key = label.startsWith("codex:") ? label : `codex:${label}`;
|
|
79
|
+
return {
|
|
80
|
+
key,
|
|
81
|
+
label: label.startsWith("codex:") ? label.slice("codex:".length) : label,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (type === "oauth" || type === "api_key") {
|
|
85
|
+
const key = normalizeAnthropicAccountKey(label);
|
|
86
|
+
return {
|
|
87
|
+
key,
|
|
88
|
+
label: label.startsWith("anthropic:")
|
|
89
|
+
? label.slice("anthropic:".length)
|
|
90
|
+
: label,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { key: label, label };
|
|
94
|
+
}
|
|
66
95
|
function cloneTerminalErrorSummary(summary) {
|
|
67
96
|
return { ...summary };
|
|
68
97
|
}
|
|
@@ -152,6 +181,9 @@ function terminalErrorCategory(status, errorType) {
|
|
|
152
181
|
}
|
|
153
182
|
function createTerminalErrorSummary(args) {
|
|
154
183
|
const { now, status, accountLabel, accountType, details } = args;
|
|
184
|
+
const accountIdentity = accountLabel && accountType
|
|
185
|
+
? resolveStatsAccountIdentity(accountLabel, accountType)
|
|
186
|
+
: undefined;
|
|
155
187
|
const errorType = clipTerminalErrorField(details?.errorType);
|
|
156
188
|
const message = details?.message
|
|
157
189
|
? stripTerminalErrorControlCharacters(sanitizeForLog(redactUrlsInText(details.message), MAX_TERMINAL_ERROR_MESSAGE_LENGTH + MAX_TERMINAL_ERROR_FIELD_LENGTH))
|
|
@@ -170,6 +202,11 @@ function createTerminalErrorSummary(args) {
|
|
|
170
202
|
...(clipTerminalErrorField(accountLabel)
|
|
171
203
|
? { account: clipTerminalErrorField(accountLabel) }
|
|
172
204
|
: {}),
|
|
205
|
+
...(clipTerminalErrorField(details?.accountKey ?? accountIdentity?.key)
|
|
206
|
+
? {
|
|
207
|
+
accountKey: clipTerminalErrorField(details?.accountKey ?? accountIdentity?.key),
|
|
208
|
+
}
|
|
209
|
+
: {}),
|
|
173
210
|
...(clipTerminalErrorField(accountType)
|
|
174
211
|
? { accountType: clipTerminalErrorField(accountType) }
|
|
175
212
|
: {}),
|
|
@@ -201,7 +238,9 @@ function mergeAccountStats(left, right) {
|
|
|
201
238
|
if (!left) {
|
|
202
239
|
return cloneAccount(right);
|
|
203
240
|
}
|
|
241
|
+
const key = right.key ?? left.key;
|
|
204
242
|
return {
|
|
243
|
+
...(key ? { key } : {}),
|
|
205
244
|
label: right.label || left.label,
|
|
206
245
|
type: right.type || left.type,
|
|
207
246
|
attemptCount: left.attemptCount + right.attemptCount,
|
|
@@ -248,7 +287,9 @@ function validAccountStats(value) {
|
|
|
248
287
|
return false;
|
|
249
288
|
}
|
|
250
289
|
const candidate = value;
|
|
251
|
-
return (
|
|
290
|
+
return ((candidate.key === undefined ||
|
|
291
|
+
(typeof candidate.key === "string" && candidate.key.length > 0)) &&
|
|
292
|
+
typeof candidate.label === "string" &&
|
|
252
293
|
typeof candidate.type === "string" &&
|
|
253
294
|
finiteNonNegativeInteger(candidate.attemptCount) &&
|
|
254
295
|
finiteNonNegativeInteger(candidate.attemptErrorCount) &&
|
|
@@ -282,7 +323,7 @@ function validStats(value) {
|
|
|
282
323
|
typeof candidate.accounts !== "object") {
|
|
283
324
|
return false;
|
|
284
325
|
}
|
|
285
|
-
return Object.entries(candidate.accounts).every(([
|
|
326
|
+
return Object.entries(candidate.accounts).every(([key, account]) => validAccountStats(account) && key === (account.key ?? account.label));
|
|
286
327
|
}
|
|
287
328
|
function validOptionalString(value, maxLength) {
|
|
288
329
|
return (value === undefined ||
|
|
@@ -302,6 +343,7 @@ function validTerminalErrorSummary(value) {
|
|
|
302
343
|
TERMINAL_ERROR_CATEGORIES.includes(candidate.category) &&
|
|
303
344
|
validOptionalString(candidate.requestId, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
304
345
|
validOptionalString(candidate.account, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
346
|
+
validOptionalString(candidate.accountKey, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
305
347
|
validOptionalString(candidate.accountType, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
306
348
|
validOptionalString(candidate.errorType, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
307
349
|
validOptionalString(candidate.errorCode, MAX_TERMINAL_ERROR_FIELD_LENGTH) &&
|
|
@@ -615,8 +657,20 @@ export class ProxyUsageStatsStore {
|
|
|
615
657
|
getStats() {
|
|
616
658
|
return cloneStats(this.stats);
|
|
617
659
|
}
|
|
618
|
-
getAccountStats(label) {
|
|
619
|
-
const
|
|
660
|
+
getAccountStats(label, type) {
|
|
661
|
+
const direct = this.stats.accounts[label];
|
|
662
|
+
if (direct) {
|
|
663
|
+
return cloneAccount(direct);
|
|
664
|
+
}
|
|
665
|
+
const identity = type
|
|
666
|
+
? resolveStatsAccountIdentity(label, type)
|
|
667
|
+
: undefined;
|
|
668
|
+
const account = identity
|
|
669
|
+
? this.stats.accounts[identity.key]
|
|
670
|
+
: (() => {
|
|
671
|
+
const matches = Object.values(this.stats.accounts).filter((candidate) => candidate.label === label);
|
|
672
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
673
|
+
})();
|
|
620
674
|
return account ? cloneAccount(account) : undefined;
|
|
621
675
|
}
|
|
622
676
|
getTerminalErrors() {
|
|
@@ -920,9 +974,11 @@ export class ProxyUsageStatsStore {
|
|
|
920
974
|
}
|
|
921
975
|
}
|
|
922
976
|
ensureAccount(target, label, type) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
977
|
+
const identity = resolveStatsAccountIdentity(label, type);
|
|
978
|
+
if (!target.accounts[identity.key]) {
|
|
979
|
+
target.accounts[identity.key] = {
|
|
980
|
+
key: identity.key,
|
|
981
|
+
label: identity.label,
|
|
926
982
|
type,
|
|
927
983
|
attemptCount: 0,
|
|
928
984
|
attemptErrorCount: 0,
|
|
@@ -934,7 +990,7 @@ export class ProxyUsageStatsStore {
|
|
|
934
990
|
lastAttemptAt: 0,
|
|
935
991
|
};
|
|
936
992
|
}
|
|
937
|
-
return target.accounts[
|
|
993
|
+
return target.accounts[identity.key];
|
|
938
994
|
}
|
|
939
995
|
scheduleFlush() {
|
|
940
996
|
if (!this.filePath || this.flushTimer) {
|
|
@@ -1131,8 +1187,8 @@ export async function getReconciledStats() {
|
|
|
1131
1187
|
export async function getReconciledUsageSnapshot() {
|
|
1132
1188
|
return defaultStore.reconcileUsageSnapshot();
|
|
1133
1189
|
}
|
|
1134
|
-
export function getAccountStats(label) {
|
|
1135
|
-
return defaultStore.getAccountStats(label);
|
|
1190
|
+
export function getAccountStats(label, type) {
|
|
1191
|
+
return defaultStore.getAccountStats(label, type);
|
|
1136
1192
|
}
|
|
1137
1193
|
export function getTerminalErrors() {
|
|
1138
1194
|
return defaultStore.getTerminalErrors();
|
|
@@ -65,7 +65,8 @@ declare function planCooldownFor429(quota: AccountQuota | null, retryAfterMs: nu
|
|
|
65
65
|
declare function reconcileCooldownFromQuota(state: RuntimeAccountState, quota: AccountQuota, now: number, policy?: ProxyOveragePolicy): ProxyQuotaCooldownUpdate;
|
|
66
66
|
/**
|
|
67
67
|
* Seed each account's runtime quota from the persisted snapshots in
|
|
68
|
-
* ~/.neurolink/account-quotas.json (keyed by
|
|
68
|
+
* ~/.neurolink/account-quotas.json (keyed by provider-qualified account key).
|
|
69
|
+
* Runtime state is
|
|
69
70
|
* in-memory only, so without this the quota-aware ordering is blind after a
|
|
70
71
|
* proxy restart: all accounts tie, selection falls back to token-store
|
|
71
72
|
* enumeration order, and the first account served becomes self-reinforcing
|
|
@@ -15,7 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { Agent } from "undici";
|
|
16
16
|
import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, getOrCreateClaudeCodeIdentity, parseClaudeCodeUserId, } from "../../auth/anthropicOAuth.js";
|
|
17
17
|
import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from "../../proxy/accountCooldown.js";
|
|
18
|
-
import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
18
|
+
import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
19
19
|
import { getUnifiedRateLimitStatus, isQuotaOverageAvailable, loadAccountQuotas, mergeQuotaSnapshot, modelFamilyToken, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
|
|
20
20
|
import { fetchAccountUsage, listAnthropicAccountsForUsage, usageToQuota, } from "../../proxy/accountUsage.js";
|
|
21
21
|
import { AccountQuotaRefreshCoordinator } from "../../proxy/accountQuotaRefreshCoordinator.js";
|
|
@@ -72,6 +72,30 @@ const BLOCKED_UPSTREAM_HEADERS = new Set([
|
|
|
72
72
|
]);
|
|
73
73
|
const PROXY_INTERNAL_ACCOUNT_LABEL = "proxy/internal";
|
|
74
74
|
const PROXY_INTERNAL_ACCOUNT_TYPE = "internal";
|
|
75
|
+
function resolveRequestLogAccountIdentity(accountLabel, accountType) {
|
|
76
|
+
if (!accountLabel) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
if (accountType === "codex-oauth") {
|
|
80
|
+
return {
|
|
81
|
+
accountKey: accountLabel.startsWith("codex:")
|
|
82
|
+
? accountLabel
|
|
83
|
+
: `codex:${accountLabel}`,
|
|
84
|
+
provider: "openai",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (accountType === "oauth" ||
|
|
88
|
+
accountType === "api_key" ||
|
|
89
|
+
accountType === "passthrough") {
|
|
90
|
+
return {
|
|
91
|
+
accountKey: accountType === "passthrough"
|
|
92
|
+
? accountLabel
|
|
93
|
+
: normalizeAnthropicAccountKey(accountLabel),
|
|
94
|
+
provider: "anthropic",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
75
99
|
// ---------------------------------------------------------------------------
|
|
76
100
|
// Module-level state
|
|
77
101
|
// ---------------------------------------------------------------------------
|
|
@@ -634,7 +658,8 @@ function reconcileCooldownFromQuota(state, quota, now, policy = overagePolicy) {
|
|
|
634
658
|
}
|
|
635
659
|
/**
|
|
636
660
|
* Seed each account's runtime quota from the persisted snapshots in
|
|
637
|
-
* ~/.neurolink/account-quotas.json (keyed by
|
|
661
|
+
* ~/.neurolink/account-quotas.json (keyed by provider-qualified account key).
|
|
662
|
+
* Runtime state is
|
|
638
663
|
* in-memory only, so without this the quota-aware ordering is blind after a
|
|
639
664
|
* proxy restart: all accounts tie, selection falls back to token-store
|
|
640
665
|
* enumeration order, and the first account served becomes self-reinforcing
|
|
@@ -652,8 +677,13 @@ async function seedRuntimeQuotasFromDisk(accounts) {
|
|
|
652
677
|
const now = Date.now();
|
|
653
678
|
for (const account of accounts) {
|
|
654
679
|
const state = getOrCreateRuntimeState(account.key);
|
|
655
|
-
if (!state.quota
|
|
656
|
-
|
|
680
|
+
if (!state.quota) {
|
|
681
|
+
// Before provider identity was introduced, Anthropic snapshots were
|
|
682
|
+
// written under the bare display label. Keep that reading as an
|
|
683
|
+
// upgrade bridge, but never write new data back under the ambiguous
|
|
684
|
+
// key: a Codex login can legitimately use the same email.
|
|
685
|
+
state.quota =
|
|
686
|
+
persistedQuotas[account.key] ?? persistedQuotas[account.label];
|
|
657
687
|
}
|
|
658
688
|
const persistedCooldown = persistedCooldowns[account.key];
|
|
659
689
|
if (persistedCooldown?.coolingUntil > now &&
|
|
@@ -708,7 +738,7 @@ async function applyAccountUsageResult(account, fetchResult, observedAt, prior)
|
|
|
708
738
|
// Non-fatal: the next successful response will reconcile again.
|
|
709
739
|
});
|
|
710
740
|
}
|
|
711
|
-
await saveAccountQuota(account.
|
|
741
|
+
await saveAccountQuota(account.key, quota).catch(() => {
|
|
712
742
|
// Non-fatal: quota persistence is best-effort.
|
|
713
743
|
});
|
|
714
744
|
return quota;
|
|
@@ -759,7 +789,11 @@ async function refreshAccountLimits(options = {}) {
|
|
|
759
789
|
key: account.key,
|
|
760
790
|
type: account.type,
|
|
761
791
|
status,
|
|
762
|
-
quota: quota ??
|
|
792
|
+
quota: quota ??
|
|
793
|
+
state?.quota ??
|
|
794
|
+
persisted[account.key] ??
|
|
795
|
+
persisted[account.label] ??
|
|
796
|
+
null,
|
|
763
797
|
};
|
|
764
798
|
if (error !== undefined) {
|
|
765
799
|
result.error = error;
|
|
@@ -815,7 +849,7 @@ async function refreshAccountLimits(options = {}) {
|
|
|
815
849
|
results[index] = buildResult(account, "error", null, fetchResult.error);
|
|
816
850
|
continue;
|
|
817
851
|
}
|
|
818
|
-
const quota = await applyAccountUsageResult(account, fetchResult, refresh.startedAt, persisted[account.label] ?? null);
|
|
852
|
+
const quota = await applyAccountUsageResult(account, fetchResult, refresh.startedAt, persisted[account.key] ?? persisted[account.label] ?? null);
|
|
819
853
|
if (!quota) {
|
|
820
854
|
results[index] = buildResult(account, "error", null, "usage payload had no recognizable limit windows");
|
|
821
855
|
continue;
|
|
@@ -2832,7 +2866,7 @@ async function auditCompleteShareHeartbeat(grant, reportedCoins) {
|
|
|
2832
2866
|
return { paused: false, detail: "no provisioned account recorded" };
|
|
2833
2867
|
}
|
|
2834
2868
|
const state = accountRuntimeState.get(`anthropic:${accountLabel}`);
|
|
2835
|
-
const stats = getAccountStats(accountLabel);
|
|
2869
|
+
const stats = getAccountStats(accountLabel, "oauth");
|
|
2836
2870
|
const { verdict, shouldPause } = await recordAuditObservation({
|
|
2837
2871
|
grantId: grant.id,
|
|
2838
2872
|
accountLabel,
|
|
@@ -3453,6 +3487,7 @@ async function executeClaudeCodexFallback(args) {
|
|
|
3453
3487
|
query: {},
|
|
3454
3488
|
params: {},
|
|
3455
3489
|
body: convertClaudeRequestToCodex(body, model),
|
|
3490
|
+
metadata: { ...ctx.metadata, "neurolink.codexFallback": true },
|
|
3456
3491
|
// Keep the child attribution isolated until its stream has passed
|
|
3457
3492
|
// validation. A failed Codex attempt must not look like a served request.
|
|
3458
3493
|
responseHeaders: {},
|
|
@@ -4059,7 +4094,7 @@ async function handleAnthropicSuccessfulResponse(args) {
|
|
|
4059
4094
|
// Non-fatal: the next successful response will reconcile again.
|
|
4060
4095
|
});
|
|
4061
4096
|
}
|
|
4062
|
-
saveAccountQuota(account.
|
|
4097
|
+
saveAccountQuota(account.key, quota).catch(() => {
|
|
4063
4098
|
// Non-fatal: quota persistence is best-effort
|
|
4064
4099
|
});
|
|
4065
4100
|
}
|
|
@@ -4725,7 +4760,7 @@ async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
|
|
|
4725
4760
|
// Non-fatal: the next successful response will reconcile again.
|
|
4726
4761
|
});
|
|
4727
4762
|
}
|
|
4728
|
-
saveAccountQuota(account.
|
|
4763
|
+
saveAccountQuota(account.key, retryQuota).catch((error) => {
|
|
4729
4764
|
logger.debug("[proxy] Failed to persist account quota after auth retry", {
|
|
4730
4765
|
account: account.label,
|
|
4731
4766
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -5016,7 +5051,7 @@ async function handleAnthropicAuthRetry(args) {
|
|
|
5016
5051
|
accountState.coolingReason = retryPlan.reason;
|
|
5017
5052
|
}
|
|
5018
5053
|
if (retryQuota429) {
|
|
5019
|
-
saveAccountQuota(account.
|
|
5054
|
+
saveAccountQuota(account.key, retryQuota429).catch(() => {
|
|
5020
5055
|
// Non-fatal: routing already has the in-memory snapshot.
|
|
5021
5056
|
});
|
|
5022
5057
|
}
|
|
@@ -5580,9 +5615,13 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
5580
5615
|
: status >= 400
|
|
5581
5616
|
? PROXY_INTERNAL_ACCOUNT_TYPE
|
|
5582
5617
|
: undefined;
|
|
5618
|
+
const finalAccountIdentity = resolveRequestLogAccountIdentity(finalAccountLabel, finalAccountType);
|
|
5583
5619
|
if (status >= 400) {
|
|
5584
5620
|
recordFinalError(status, finalAccountLabel, finalAccountType, {
|
|
5585
5621
|
requestId: ctx.requestId,
|
|
5622
|
+
...(finalAccountIdentity.accountKey
|
|
5623
|
+
? { accountKey: finalAccountIdentity.accountKey }
|
|
5624
|
+
: {}),
|
|
5586
5625
|
errorType,
|
|
5587
5626
|
terminalOutcome: errorType === "client_cancelled"
|
|
5588
5627
|
? "client_cancelled"
|
|
@@ -5606,7 +5645,13 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
5606
5645
|
stream: !!body.stream,
|
|
5607
5646
|
toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
|
|
5608
5647
|
account: finalAccountLabel ?? "",
|
|
5648
|
+
...(finalAccountIdentity.accountKey
|
|
5649
|
+
? { accountKey: finalAccountIdentity.accountKey }
|
|
5650
|
+
: {}),
|
|
5609
5651
|
accountType: finalAccountType ?? "",
|
|
5652
|
+
...(finalAccountIdentity.provider
|
|
5653
|
+
? { provider: finalAccountIdentity.provider }
|
|
5654
|
+
: {}),
|
|
5610
5655
|
...buildClientAttribution(ctx.headers),
|
|
5611
5656
|
responseStatus: status,
|
|
5612
5657
|
responseTimeMs: Date.now() - requestStartTime,
|
|
@@ -5669,6 +5714,7 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
5669
5714
|
function createAnthropicAttemptLogger(args) {
|
|
5670
5715
|
const { ctx, body, toolCount, requestStart, tracer, account, attemptNumber } = args;
|
|
5671
5716
|
const attemptStartedAt = Date.now();
|
|
5717
|
+
const accountIdentity = resolveRequestLogAccountIdentity(account.label, account.type);
|
|
5672
5718
|
return (status, errorType, errorMessage, extra) => {
|
|
5673
5719
|
const attemptCompletedAt = Date.now();
|
|
5674
5720
|
const traceCtx = tracer?.getTraceContext();
|
|
@@ -5682,7 +5728,11 @@ function createAnthropicAttemptLogger(args) {
|
|
|
5682
5728
|
stream: !!body.stream,
|
|
5683
5729
|
toolCount,
|
|
5684
5730
|
account: account.label,
|
|
5731
|
+
accountKey: account.key,
|
|
5685
5732
|
accountType: account.type,
|
|
5733
|
+
...(accountIdentity.provider
|
|
5734
|
+
? { provider: accountIdentity.provider }
|
|
5735
|
+
: {}),
|
|
5686
5736
|
responseStatus: status,
|
|
5687
5737
|
responseTimeMs: attemptCompletedAt - requestStart,
|
|
5688
5738
|
attemptDurationMs: extra?.attemptDurationMs ?? attemptCompletedAt - attemptStartedAt,
|
|
@@ -6335,7 +6385,7 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
6335
6385
|
// Refresh the account's quota snapshot for proactive selection.
|
|
6336
6386
|
if (fetchResult.quota) {
|
|
6337
6387
|
accountState.quota = mergeQuotaSnapshot(accountState.quota, fetchResult.quota);
|
|
6338
|
-
saveAccountQuota(account.
|
|
6388
|
+
saveAccountQuota(account.key, fetchResult.quota).catch(() => {
|
|
6339
6389
|
// Non-fatal: routing already has the in-memory snapshot.
|
|
6340
6390
|
});
|
|
6341
6391
|
}
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
* never collides with anthropic entries) and does pre-commit rotation only, not
|
|
18
18
|
* the full transient-budget / admission machinery.
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
20
|
+
import type { AccountQuota, CodexRefreshTokenStore, CodexRuntimeAccount, CodexTokenRefresher, RateLimitCoolingReason, RouteGroup, ServerContext } from "../../types/index.js";
|
|
21
|
+
declare function refreshCodexTokenOnceWithDependencies(key: string, refreshToken: string, store: CodexRefreshTokenStore, refresh: CodexTokenRefresher): Promise<{
|
|
22
|
+
accessToken: string;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
expiresAt?: number;
|
|
25
|
+
}>;
|
|
21
26
|
/** Refresh an account's token at most once at a time. */
|
|
22
27
|
declare function refreshCodexTokenOnce(key: string, refreshToken: string): Promise<{
|
|
23
28
|
accessToken: string;
|
|
@@ -44,7 +49,7 @@ declare function buildCodexUpstreamHeaders(clientHeaders: Record<string, string>
|
|
|
44
49
|
*/
|
|
45
50
|
declare function planCodexCooldown(quota: AccountQuota | null, retryAfterMs: number, now: number): {
|
|
46
51
|
coolingUntil: number;
|
|
47
|
-
reason:
|
|
52
|
+
reason: RateLimitCoolingReason;
|
|
48
53
|
};
|
|
49
54
|
/** Core pooled handler for POST /backend-api/codex/responses. */
|
|
50
55
|
export declare function handleCodexResponsesRequest(ctx: ServerContext): Promise<Response>;
|
|
@@ -61,6 +66,7 @@ export declare const __testHooks: {
|
|
|
61
66
|
buildCodexUpstreamHeaders: typeof buildCodexUpstreamHeaders;
|
|
62
67
|
planCodexCooldown: typeof planCodexCooldown;
|
|
63
68
|
refreshCodexTokenOnce: typeof refreshCodexTokenOnce;
|
|
69
|
+
refreshCodexTokenOnceWithDependencies: typeof refreshCodexTokenOnceWithDependencies;
|
|
64
70
|
codexRefreshInFlightSize: () => number;
|
|
65
71
|
};
|
|
66
72
|
export {};
|