@juspay/neurolink 10.6.5 → 10.7.1
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 +355 -355
- package/dist/cli/commands/proxyAnalyze.js +12 -0
- package/dist/lib/proxy/accountCooldown.js +2 -7
- package/dist/lib/proxy/proxyAnalysis.js +215 -0
- package/dist/lib/proxy/requestLogger.js +11 -0
- package/dist/lib/proxy/routingEvidence.d.ts +6 -0
- package/dist/lib/proxy/routingEvidence.js +33 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +16 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +262 -86
- package/dist/lib/types/proxy.d.ts +100 -2
- package/dist/proxy/accountCooldown.js +2 -7
- package/dist/proxy/proxyAnalysis.js +215 -0
- package/dist/proxy/requestLogger.js +11 -0
- package/dist/proxy/routingEvidence.d.ts +6 -0
- package/dist/proxy/routingEvidence.js +32 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +16 -1
- package/dist/server/routes/claudeProxyRoutes.js +262 -86
- package/dist/types/proxy.d.ts +100 -2
- package/package.json +1 -1
|
@@ -42,6 +42,17 @@ function printAnalysis(report) {
|
|
|
42
42
|
logger.always(chalk.yellow(" Rate limits: unavailable"));
|
|
43
43
|
}
|
|
44
44
|
logger.always("");
|
|
45
|
+
logger.always(chalk.bold(" Account Routing"));
|
|
46
|
+
if (report.coverage.routingDecisions) {
|
|
47
|
+
logger.always(` Decisions: ${report.routing.totalRecords} (${report.routing.records.length} retained), modes: ${JSON.stringify(report.routing.modes)}`);
|
|
48
|
+
logger.always(` Selection reasons: ${JSON.stringify(report.routing.selectionReasons)}`);
|
|
49
|
+
logger.always(` Initial accounts: ${JSON.stringify(report.routing.initialAccounts)}`);
|
|
50
|
+
logger.always(` Final account changed after retry: ${report.routing.finalAccountChanges}, outside candidate set: ${report.routing.finalOutsideCandidateSet}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
logger.always(chalk.yellow(" Routing decisions: unavailable (legacy or no logs)"));
|
|
54
|
+
}
|
|
55
|
+
logger.always("");
|
|
45
56
|
logger.always(chalk.bold(" Latency (ms)"));
|
|
46
57
|
const latencyHeader = `${"METRIC".padEnd(22)} ${"COUNT".padStart(7)} ${"P50".padStart(9)} ${"P95".padStart(9)} ${"P99".padStart(9)} ${"MAX".padStart(9)}`;
|
|
47
58
|
logger.always(` ${chalk.gray(latencyHeader)}`);
|
|
@@ -68,6 +79,7 @@ function printAnalysis(report) {
|
|
|
68
79
|
logger.always("");
|
|
69
80
|
logger.always(chalk.bold(" Data Quality"));
|
|
70
81
|
logger.always(` ${report.dataQuality.linesRead} lines scanned, ${report.dataQuality.malformedLines} malformed, ${report.dataQuality.unsupportedLifecycleLines} unsupported lifecycle, ${report.dataQuality.lifecycleSequenceGaps} sequence gaps, ${report.dataQuality.lifecycleSequenceDuplicates} duplicates`);
|
|
82
|
+
logger.always(` Routing decisions: ${report.dataQuality.routingDecisions.valid} valid, ${report.dataQuality.routingDecisions.invalid} invalid, ${report.dataQuality.routingDecisions.absent} absent`);
|
|
71
83
|
for (const [stream, range] of Object.entries(report.dataQuality.streams)) {
|
|
72
84
|
if (range.observedFrom) {
|
|
73
85
|
logger.always(` ${stream}: ${range.observedFrom} to ${range.observedTo}${range.startsAtOrBeforeRequestedWindow ? "" : chalk.yellow(" (starts after requested window)")}`);
|
|
@@ -2,15 +2,10 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { AsyncMutex } from "../utils/asyncMutex.js";
|
|
5
|
+
import { ACCOUNT_COOLING_REASONS } from "./routingEvidence.js";
|
|
5
6
|
import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
|
|
6
7
|
const COOLDOWN_FILE = "account-cooldowns.json";
|
|
7
|
-
const VALID_REASONS = new Set(
|
|
8
|
-
"weekly",
|
|
9
|
-
"session",
|
|
10
|
-
"unified",
|
|
11
|
-
"transient",
|
|
12
|
-
"auth",
|
|
13
|
-
]);
|
|
8
|
+
const VALID_REASONS = new Set(ACCOUNT_COOLING_REASONS);
|
|
14
9
|
let customCooldownFilePath = null;
|
|
15
10
|
let cacheLoaded = false;
|
|
16
11
|
let cacheLoadPromise = null;
|
|
@@ -3,6 +3,7 @@ import { lstat, readdir, realpath, stat } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { ACCOUNT_COOLING_REASONS, PROXY_ACCOUNT_TYPES, PROXY_ACCOUNT_ROUTING_MODES, PROXY_ACCOUNT_ROUTING_REASONS, PROXY_ACCOUNT_ROUTING_STRATEGIES, } from "./routingEvidence.js";
|
|
6
7
|
const LIFECYCLE_FILE_PATTERN = /^proxy-lifecycle-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
7
8
|
const REQUEST_FILE_PATTERN = /^proxy-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
8
9
|
const ATTEMPT_FILE_PATTERN = /^proxy-attempts-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
@@ -14,6 +15,13 @@ const LIFECYCLE_EVENTS = new Set([
|
|
|
14
15
|
"response_first_chunk",
|
|
15
16
|
"request_terminal",
|
|
16
17
|
]);
|
|
18
|
+
const ROUTING_STRATEGIES = new Set(PROXY_ACCOUNT_ROUTING_STRATEGIES);
|
|
19
|
+
const ROUTING_MODES = new Set(PROXY_ACCOUNT_ROUTING_MODES);
|
|
20
|
+
const ROUTING_REASONS = new Set(PROXY_ACCOUNT_ROUTING_REASONS);
|
|
21
|
+
const ROUTING_ACCOUNT_TYPES = new Set(PROXY_ACCOUNT_TYPES);
|
|
22
|
+
const COOLING_REASONS = new Set(ACCOUNT_COOLING_REASONS);
|
|
23
|
+
const MAX_RETAINED_ROUTING_RECORDS = 200;
|
|
24
|
+
const MAX_ROUTING_RECORDS_BEFORE_COMPACTION = MAX_RETAINED_ROUTING_RECORDS * 2;
|
|
17
25
|
function isContainedPath(root, candidate) {
|
|
18
26
|
const candidateRelative = relative(root, candidate);
|
|
19
27
|
return (candidateRelative.length > 0 &&
|
|
@@ -45,6 +53,130 @@ function finiteNumber(value) {
|
|
|
45
53
|
function stringValue(value) {
|
|
46
54
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
47
55
|
}
|
|
56
|
+
function isNullableString(value) {
|
|
57
|
+
return value === null || (typeof value === "string" && value.length > 0);
|
|
58
|
+
}
|
|
59
|
+
function isNullableFiniteNumber(value) {
|
|
60
|
+
return value === null || finiteNumber(value) !== null;
|
|
61
|
+
}
|
|
62
|
+
function routingCandidateValue(value) {
|
|
63
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const candidate = value;
|
|
67
|
+
const requiredBooleanFields = [
|
|
68
|
+
"configuredPrimary",
|
|
69
|
+
"usable",
|
|
70
|
+
"saturated",
|
|
71
|
+
"quotaObserved",
|
|
72
|
+
"coolingActive",
|
|
73
|
+
];
|
|
74
|
+
const requiredNullableNumberFields = [
|
|
75
|
+
"quotaLastUpdated",
|
|
76
|
+
"quotaAgeMs",
|
|
77
|
+
"coolingUntil",
|
|
78
|
+
"sessionUsed",
|
|
79
|
+
"sessionResetAt",
|
|
80
|
+
"sessionResetBucket",
|
|
81
|
+
"weeklyUsed",
|
|
82
|
+
"weeklyResetAt",
|
|
83
|
+
];
|
|
84
|
+
const requiredNullableStringFields = [
|
|
85
|
+
"unifiedStatus",
|
|
86
|
+
"overageStatus",
|
|
87
|
+
"sessionStatus",
|
|
88
|
+
"weeklyStatus",
|
|
89
|
+
];
|
|
90
|
+
if (!stringValue(candidate.account) ||
|
|
91
|
+
typeof candidate.accountType !== "string" ||
|
|
92
|
+
!ROUTING_ACCOUNT_TYPES.has(candidate.accountType) ||
|
|
93
|
+
!Number.isInteger(candidate.sourceIndex) ||
|
|
94
|
+
candidate.sourceIndex < 0 ||
|
|
95
|
+
!Number.isInteger(candidate.rank) ||
|
|
96
|
+
candidate.rank < 0 ||
|
|
97
|
+
requiredBooleanFields.some((field) => typeof candidate[field] !== "boolean") ||
|
|
98
|
+
requiredNullableNumberFields.some((field) => !isNullableFiniteNumber(candidate[field])) ||
|
|
99
|
+
requiredNullableStringFields.some((field) => !isNullableString(candidate[field])) ||
|
|
100
|
+
!(candidate.coolingReason === null ||
|
|
101
|
+
(typeof candidate.coolingReason === "string" &&
|
|
102
|
+
COOLING_REASONS.has(candidate.coolingReason)))) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
account: candidate.account,
|
|
107
|
+
accountType: candidate.accountType,
|
|
108
|
+
sourceIndex: candidate.sourceIndex,
|
|
109
|
+
rank: candidate.rank,
|
|
110
|
+
configuredPrimary: candidate.configuredPrimary,
|
|
111
|
+
usable: candidate.usable,
|
|
112
|
+
saturated: candidate.saturated,
|
|
113
|
+
quotaObserved: candidate.quotaObserved,
|
|
114
|
+
quotaLastUpdated: candidate.quotaLastUpdated,
|
|
115
|
+
quotaAgeMs: candidate.quotaAgeMs,
|
|
116
|
+
coolingActive: candidate.coolingActive,
|
|
117
|
+
coolingReason: candidate.coolingReason,
|
|
118
|
+
coolingUntil: candidate.coolingUntil,
|
|
119
|
+
unifiedStatus: candidate.unifiedStatus,
|
|
120
|
+
overageStatus: candidate.overageStatus,
|
|
121
|
+
sessionStatus: candidate.sessionStatus,
|
|
122
|
+
sessionUsed: candidate.sessionUsed,
|
|
123
|
+
sessionResetAt: candidate.sessionResetAt,
|
|
124
|
+
sessionResetBucket: candidate.sessionResetBucket,
|
|
125
|
+
weeklyStatus: candidate.weeklyStatus,
|
|
126
|
+
weeklyUsed: candidate.weeklyUsed,
|
|
127
|
+
weeklyResetAt: candidate.weeklyResetAt,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function routingDecisionValue(value) {
|
|
131
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const decision = value;
|
|
135
|
+
const candidates = Array.isArray(decision.candidates)
|
|
136
|
+
? decision.candidates.map(routingCandidateValue)
|
|
137
|
+
: [];
|
|
138
|
+
if (decision.schemaVersion !== 1 ||
|
|
139
|
+
!stringValue(decision.evaluatedAt) ||
|
|
140
|
+
!Number.isFinite(Date.parse(String(decision.evaluatedAt))) ||
|
|
141
|
+
typeof decision.strategy !== "string" ||
|
|
142
|
+
!ROUTING_STRATEGIES.has(decision.strategy) ||
|
|
143
|
+
typeof decision.mode !== "string" ||
|
|
144
|
+
!ROUTING_MODES.has(decision.mode) ||
|
|
145
|
+
typeof decision.selectionReason !== "string" ||
|
|
146
|
+
!ROUTING_REASONS.has(decision.selectionReason) ||
|
|
147
|
+
typeof decision.quotaRoutingEnabled !== "boolean" ||
|
|
148
|
+
typeof decision.quotaInputsUsed !== "boolean" ||
|
|
149
|
+
finiteNumber(decision.sessionSoftLimit) === null ||
|
|
150
|
+
decision.sessionSoftLimit <= 0 ||
|
|
151
|
+
decision.sessionSoftLimit > 1 ||
|
|
152
|
+
!Number.isInteger(decision.sessionResetToleranceMs) ||
|
|
153
|
+
decision.sessionResetToleranceMs <= 0 ||
|
|
154
|
+
!isNullableString(decision.configuredPrimaryAccount) ||
|
|
155
|
+
typeof decision.configuredPrimaryMatched !== "boolean" ||
|
|
156
|
+
!Number.isInteger(decision.rotationOffset) ||
|
|
157
|
+
decision.rotationOffset < 0 ||
|
|
158
|
+
!stringValue(decision.initialAccount) ||
|
|
159
|
+
candidates.length === 0 ||
|
|
160
|
+
candidates.some((candidate) => candidate === null)) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
schemaVersion: 1,
|
|
165
|
+
evaluatedAt: decision.evaluatedAt,
|
|
166
|
+
strategy: decision.strategy,
|
|
167
|
+
mode: decision.mode,
|
|
168
|
+
selectionReason: decision.selectionReason,
|
|
169
|
+
quotaRoutingEnabled: decision.quotaRoutingEnabled,
|
|
170
|
+
quotaInputsUsed: decision.quotaInputsUsed,
|
|
171
|
+
sessionSoftLimit: decision.sessionSoftLimit,
|
|
172
|
+
sessionResetToleranceMs: decision.sessionResetToleranceMs,
|
|
173
|
+
configuredPrimaryAccount: decision.configuredPrimaryAccount,
|
|
174
|
+
configuredPrimaryMatched: decision.configuredPrimaryMatched,
|
|
175
|
+
rotationOffset: decision.rotationOffset,
|
|
176
|
+
initialAccount: decision.initialAccount,
|
|
177
|
+
candidates: candidates,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
48
180
|
function percentile(sorted, fraction) {
|
|
49
181
|
if (sorted.length === 0) {
|
|
50
182
|
return null;
|
|
@@ -204,6 +336,63 @@ function summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByR
|
|
|
204
336
|
singleAttemptDelta,
|
|
205
337
|
};
|
|
206
338
|
}
|
|
339
|
+
function summarizeRouting(finalRequests) {
|
|
340
|
+
const modes = {};
|
|
341
|
+
const selectionReasons = {};
|
|
342
|
+
const initialAccounts = {};
|
|
343
|
+
const retainedRecords = [];
|
|
344
|
+
let totalRecords = 0;
|
|
345
|
+
let finalAccountChanges = 0;
|
|
346
|
+
let finalOutsideCandidateSet = 0;
|
|
347
|
+
for (const [requestId, request] of finalRequests) {
|
|
348
|
+
const decision = request.routingDecision;
|
|
349
|
+
if (!decision) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
increment(modes, decision.mode);
|
|
353
|
+
increment(selectionReasons, decision.selectionReason);
|
|
354
|
+
increment(initialAccounts, decision.initialAccount);
|
|
355
|
+
const finalCandidate = decision.candidates.some((candidate) => candidate.account === request.account &&
|
|
356
|
+
candidate.accountType === request.accountType);
|
|
357
|
+
if (!finalCandidate) {
|
|
358
|
+
finalOutsideCandidateSet += 1;
|
|
359
|
+
}
|
|
360
|
+
else if (decision.initialAccount !== request.account) {
|
|
361
|
+
finalAccountChanges += 1;
|
|
362
|
+
}
|
|
363
|
+
totalRecords += 1;
|
|
364
|
+
retainedRecords.push({
|
|
365
|
+
record: {
|
|
366
|
+
requestId,
|
|
367
|
+
timestamp: request.timestamp,
|
|
368
|
+
responseStatus: request.status,
|
|
369
|
+
finalAccount: request.account,
|
|
370
|
+
finalAccountType: request.accountType,
|
|
371
|
+
decision,
|
|
372
|
+
},
|
|
373
|
+
timestampMs: Date.parse(request.timestamp),
|
|
374
|
+
sequence: totalRecords,
|
|
375
|
+
});
|
|
376
|
+
if (retainedRecords.length > MAX_ROUTING_RECORDS_BEFORE_COMPACTION) {
|
|
377
|
+
retainedRecords.sort((left, right) => left.timestampMs - right.timestampMs ||
|
|
378
|
+
left.sequence - right.sequence);
|
|
379
|
+
retainedRecords.splice(0, retainedRecords.length - MAX_RETAINED_ROUTING_RECORDS);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
retainedRecords.sort((left, right) => left.timestampMs - right.timestampMs || left.sequence - right.sequence);
|
|
383
|
+
const records = retainedRecords
|
|
384
|
+
.slice(-MAX_RETAINED_ROUTING_RECORDS)
|
|
385
|
+
.map(({ record }) => record);
|
|
386
|
+
return {
|
|
387
|
+
modes,
|
|
388
|
+
selectionReasons,
|
|
389
|
+
initialAccounts,
|
|
390
|
+
finalAccountChanges,
|
|
391
|
+
finalOutsideCandidateSet,
|
|
392
|
+
totalRecords,
|
|
393
|
+
records,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
207
396
|
async function discoverLogFiles(logsDir) {
|
|
208
397
|
const entries = await readdir(logsDir, { withFileTypes: true }).catch((error) => {
|
|
209
398
|
throw new Error(`Unable to read proxy logs at ${logsDir}: ${error.message}`);
|
|
@@ -400,6 +589,9 @@ export async function analyzeProxyLogs(options) {
|
|
|
400
589
|
}
|
|
401
590
|
const finalRequests = new Map();
|
|
402
591
|
const terminalStreamErrors = new Set();
|
|
592
|
+
let validRoutingDecisions = 0;
|
|
593
|
+
let invalidRoutingDecisions = 0;
|
|
594
|
+
let absentRoutingDecisions = 0;
|
|
403
595
|
for (const filePath of requestFiles) {
|
|
404
596
|
linesRead += await readJsonLines(filePath, (record) => {
|
|
405
597
|
const timestamp = observeTimestamp("requests", record);
|
|
@@ -418,7 +610,21 @@ export async function analyzeProxyLogs(options) {
|
|
|
418
610
|
if (status === null || !stringValue(record.method)) {
|
|
419
611
|
return;
|
|
420
612
|
}
|
|
613
|
+
const hasRoutingDecision = Object.prototype.hasOwnProperty.call(record, "routingDecision");
|
|
614
|
+
const routingDecision = hasRoutingDecision
|
|
615
|
+
? routingDecisionValue(record.routingDecision)
|
|
616
|
+
: null;
|
|
617
|
+
if (routingDecision) {
|
|
618
|
+
validRoutingDecisions += 1;
|
|
619
|
+
}
|
|
620
|
+
else if (hasRoutingDecision) {
|
|
621
|
+
invalidRoutingDecisions += 1;
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
absentRoutingDecisions += 1;
|
|
625
|
+
}
|
|
421
626
|
finalRequests.set(requestId, {
|
|
627
|
+
timestamp: new Date(timestamp).toISOString(),
|
|
422
628
|
status,
|
|
423
629
|
durationMs: finiteNumber(record.responseTimeMs),
|
|
424
630
|
account: stringValue(record.account) ?? "unknown",
|
|
@@ -428,6 +634,7 @@ export async function analyzeProxyLogs(options) {
|
|
|
428
634
|
cacheCreationTokens: finiteNumber(record.cacheCreationTokens),
|
|
429
635
|
errorType: stringValue(record.errorType),
|
|
430
636
|
errorCode: stringValue(record.errorCode),
|
|
637
|
+
routingDecision,
|
|
431
638
|
});
|
|
432
639
|
}, () => {
|
|
433
640
|
malformedLines += 1;
|
|
@@ -498,6 +705,7 @@ export async function analyzeProxyLogs(options) {
|
|
|
498
705
|
}
|
|
499
706
|
const artifactsReferenced = artifactsPresent + artifactsMissing;
|
|
500
707
|
const finalSummary = summarizeFinalRequests(finalRequests, terminalStreamErrors, attemptsByRequest, accounts);
|
|
708
|
+
const routingSummary = summarizeRouting(finalRequests);
|
|
501
709
|
return {
|
|
502
710
|
generatedAt: new Date(nowMs).toISOString(),
|
|
503
711
|
since: new Date(sinceMs).toISOString(),
|
|
@@ -514,6 +722,7 @@ export async function analyzeProxyLogs(options) {
|
|
|
514
722
|
attempts: totalAttempts > 0,
|
|
515
723
|
attemptLatency: attemptLatency.length > 0,
|
|
516
724
|
cacheUsage: finalSummary.cache.requestsWithUsage > 0,
|
|
725
|
+
routingDecisions: routingSummary.totalRecords > 0,
|
|
517
726
|
},
|
|
518
727
|
dataQuality: {
|
|
519
728
|
linesRead,
|
|
@@ -538,6 +747,11 @@ export async function analyzeProxyLogs(options) {
|
|
|
538
747
|
writeFailures,
|
|
539
748
|
truncatedCaptures,
|
|
540
749
|
},
|
|
750
|
+
routingDecisions: {
|
|
751
|
+
valid: validRoutingDecisions,
|
|
752
|
+
invalid: invalidRoutingDecisions,
|
|
753
|
+
absent: absentRoutingDecisions,
|
|
754
|
+
},
|
|
541
755
|
},
|
|
542
756
|
lifecycle: {
|
|
543
757
|
accepted: accepted.size,
|
|
@@ -572,6 +786,7 @@ export async function analyzeProxyLogs(options) {
|
|
|
572
786
|
singleAttemptDelta: summarizeLatency(finalSummary.singleAttemptDelta),
|
|
573
787
|
},
|
|
574
788
|
cache: finalSummary.cache,
|
|
789
|
+
routing: routingSummary,
|
|
575
790
|
accounts: [...accounts.values()].sort((left, right) => right.attempts - left.attempts),
|
|
576
791
|
};
|
|
577
792
|
}
|
|
@@ -236,6 +236,17 @@ function emitOtlpLogRecord(entry) {
|
|
|
236
236
|
// Account info
|
|
237
237
|
"account.name": entry.account,
|
|
238
238
|
"account.type": entry.accountType,
|
|
239
|
+
// Compact routing summary. Full candidate evidence stays in JSONL.
|
|
240
|
+
...(entry.routingDecision && {
|
|
241
|
+
"routing.mode": entry.routingDecision.mode,
|
|
242
|
+
"routing.strategy": entry.routingDecision.strategy,
|
|
243
|
+
"routing.selection_reason": entry.routingDecision.selectionReason,
|
|
244
|
+
"routing.initial_account": entry.routingDecision.initialAccount,
|
|
245
|
+
"routing.candidate_count": entry.routingDecision.candidates.length,
|
|
246
|
+
"routing.final_account_changed": entry.routingDecision.initialAccount !== entry.account &&
|
|
247
|
+
entry.routingDecision.candidates.some((candidate) => candidate.account === entry.account &&
|
|
248
|
+
candidate.accountType === entry.accountType),
|
|
249
|
+
}),
|
|
239
250
|
// Token usage (when available)
|
|
240
251
|
...(entry.inputTokens !== undefined && {
|
|
241
252
|
"ai.input_tokens": entry.inputTokens,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Schema-v1 routing evidence values shared by emitters and offline readers. */
|
|
2
|
+
export declare const PROXY_ACCOUNT_ROUTING_STRATEGIES: readonly ["round-robin", "fill-first"];
|
|
3
|
+
export declare const PROXY_ACCOUNT_ROUTING_MODES: readonly ["quota", "primary", "round_robin", "single_account"];
|
|
4
|
+
export declare const PROXY_ACCOUNT_ROUTING_REASONS: readonly ["single_account", "round_robin", "configured_primary", "insertion_order", "availability", "cooldown_recovery", "quota_probe", "session_headroom", "session_reset", "weekly_reset", "weekly_utilization"];
|
|
5
|
+
export declare const PROXY_ACCOUNT_TYPES: readonly ["oauth", "api_key"];
|
|
6
|
+
export declare const ACCOUNT_COOLING_REASONS: readonly ["weekly", "session", "unified", "transient", "auth"];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Schema-v1 routing evidence values shared by emitters and offline readers. */
|
|
2
|
+
export const PROXY_ACCOUNT_ROUTING_STRATEGIES = [
|
|
3
|
+
"round-robin",
|
|
4
|
+
"fill-first",
|
|
5
|
+
];
|
|
6
|
+
export const PROXY_ACCOUNT_ROUTING_MODES = [
|
|
7
|
+
"quota",
|
|
8
|
+
"primary",
|
|
9
|
+
"round_robin",
|
|
10
|
+
"single_account",
|
|
11
|
+
];
|
|
12
|
+
export const PROXY_ACCOUNT_ROUTING_REASONS = [
|
|
13
|
+
"single_account",
|
|
14
|
+
"round_robin",
|
|
15
|
+
"configured_primary",
|
|
16
|
+
"insertion_order",
|
|
17
|
+
"availability",
|
|
18
|
+
"cooldown_recovery",
|
|
19
|
+
"quota_probe",
|
|
20
|
+
"session_headroom",
|
|
21
|
+
"session_reset",
|
|
22
|
+
"weekly_reset",
|
|
23
|
+
"weekly_utilization",
|
|
24
|
+
];
|
|
25
|
+
export const PROXY_ACCOUNT_TYPES = ["oauth", "api_key"];
|
|
26
|
+
export const ACCOUNT_COOLING_REASONS = [
|
|
27
|
+
"weekly",
|
|
28
|
+
"session",
|
|
29
|
+
"unified",
|
|
30
|
+
"transient",
|
|
31
|
+
"auth",
|
|
32
|
+
];
|
|
33
|
+
//# sourceMappingURL=routingEvidence.js.map
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
|
|
13
13
|
import { ProxyTracer } from "../../proxy/proxyTracer.js";
|
|
14
14
|
import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.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";
|
|
15
|
+
import type { AccountAllowlist, AccountCooldownPlan, AccountQuota, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicLoopState, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRoutingDecision, ProxyAccountSortMetrics, ProxyBodyCaptureLogger, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
|
|
16
16
|
/** Resolve the configured primary's stable key to its current index in the
|
|
17
17
|
* request's enabledAccounts list. Returns 0 (insertion-order fallback) when
|
|
18
18
|
* no key is configured or the key cannot be matched (account disabled/
|
|
@@ -85,6 +85,19 @@ declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]):
|
|
|
85
85
|
* last resort.
|
|
86
86
|
*/
|
|
87
87
|
declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number): ProxyPassthroughAccount[];
|
|
88
|
+
declare function buildRoutingDecision(args: {
|
|
89
|
+
accounts: ProxyPassthroughAccount[];
|
|
90
|
+
orderedAccounts: ProxyPassthroughAccount[];
|
|
91
|
+
metricsByKey: ReadonlyMap<string, ProxyAccountSortMetrics>;
|
|
92
|
+
evaluatedAt: number;
|
|
93
|
+
strategy: "round-robin" | "fill-first";
|
|
94
|
+
primaryKey: string | undefined;
|
|
95
|
+
quotaRoutingEnabled: boolean;
|
|
96
|
+
quotaOrdered: boolean;
|
|
97
|
+
sessionSoftLimit: number;
|
|
98
|
+
sessionResetToleranceMs: number;
|
|
99
|
+
rotationOffset: number;
|
|
100
|
+
}): ProxyAccountRoutingDecision | undefined;
|
|
88
101
|
declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>): {
|
|
89
102
|
stream: ReadableStream<Uint8Array>;
|
|
90
103
|
outcome: Promise<StreamTerminalOutcome>;
|
|
@@ -279,6 +292,8 @@ export declare const __testHooks: {
|
|
|
279
292
|
getStreamFailureDetails: typeof getStreamFailureDetails;
|
|
280
293
|
trackUpstreamReadableStream: typeof trackUpstreamReadableStream;
|
|
281
294
|
orderAccountsByQuota: typeof orderAccountsByQuota;
|
|
295
|
+
buildQuotaRoutingDecision: (accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number) => ProxyAccountRoutingDecision | undefined;
|
|
296
|
+
buildRoutingDecision: typeof buildRoutingDecision;
|
|
282
297
|
resetEpochToMs: typeof resetEpochToMs;
|
|
283
298
|
seedRuntimeQuotasFromDisk: typeof seedRuntimeQuotasFromDisk;
|
|
284
299
|
reconcileEligibleAccountRuntimeState: typeof reconcileEligibleAccountRuntimeState;
|