@juspay/neurolink 10.4.3 → 10.4.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 +6 -0
- package/dist/auth/tokenStore.d.ts +7 -0
- package/dist/auth/tokenStore.js +54 -0
- package/dist/browser/neurolink.min.js +374 -374
- package/dist/cli/commands/proxy.d.ts +1 -0
- package/dist/cli/commands/proxy.js +183 -24
- package/dist/lib/auth/tokenStore.d.ts +7 -0
- package/dist/lib/auth/tokenStore.js +54 -0
- package/dist/lib/proxy/proxyTranslationEngine.d.ts +1 -0
- package/dist/lib/proxy/proxyTranslationEngine.js +56 -12
- package/dist/lib/proxy/rawStreamCapture.js +47 -1
- package/dist/lib/proxy/sseInterceptor.js +47 -1
- package/dist/lib/proxy/tokenRefresh.d.ts +6 -0
- package/dist/lib/proxy/tokenRefresh.js +70 -15
- package/dist/lib/proxy/usageStats.d.ts +25 -3
- package/dist/lib/proxy/usageStats.js +546 -55
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/lib/server/routes/claudeProxyRoutes.js +198 -222
- package/dist/lib/types/proxy.d.ts +60 -1
- package/dist/proxy/proxyTranslationEngine.d.ts +1 -0
- package/dist/proxy/proxyTranslationEngine.js +56 -12
- package/dist/proxy/rawStreamCapture.js +47 -1
- package/dist/proxy/sseInterceptor.js +47 -1
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +70 -15
- package/dist/proxy/usageStats.d.ts +25 -3
- package/dist/proxy/usageStats.js +546 -55
- package/dist/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/server/routes/claudeProxyRoutes.js +198 -222
- package/dist/types/proxy.d.ts +60 -1
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ import { relocateClientSystemIntoMessages } from "../../proxy/systemRelocation.j
|
|
|
26
26
|
import { logBodyCapture, logRequest, logRequestAttempt, } from "../../proxy/requestLogger.js";
|
|
27
27
|
import { createSSEInterceptor } from "../../proxy/sseInterceptor.js";
|
|
28
28
|
import { createStreamTerminalOutcomeTracker, mergeStreamTerminalOutcome, preflightAnthropicStream, } from "../../proxy/streamOutcome.js";
|
|
29
|
-
import { isPermanentRefreshFailure, needsRefresh, persistTokens, refreshToken, } from "../../proxy/tokenRefresh.js";
|
|
29
|
+
import { isPermanentRefreshFailure, needsRefresh, persistTokens, refreshToken, refreshTokenFromLatest, } from "../../proxy/tokenRefresh.js";
|
|
30
30
|
import { buildProxyTranslationPlan, parseRetryAfterMs, } from "../../proxy/routingPolicy.js";
|
|
31
31
|
import { writeJsonSnapshotAtomically } from "../../proxy/snapshotPersistence.js";
|
|
32
32
|
import { recordAttempt, recordAttemptError, recordFinalError, recordFinalSuccess, } from "../../proxy/usageStats.js";
|
|
@@ -45,6 +45,8 @@ const BLOCKED_UPSTREAM_HEADERS = new Set([
|
|
|
45
45
|
"content-length",
|
|
46
46
|
"transfer-encoding",
|
|
47
47
|
]);
|
|
48
|
+
const PROXY_INTERNAL_ACCOUNT_LABEL = "proxy/internal";
|
|
49
|
+
const PROXY_INTERNAL_ACCOUNT_TYPE = "internal";
|
|
48
50
|
// ---------------------------------------------------------------------------
|
|
49
51
|
// Module-level state
|
|
50
52
|
// ---------------------------------------------------------------------------
|
|
@@ -959,15 +961,36 @@ async function handleTranslatedClaudeRequest(args) {
|
|
|
959
961
|
requestStartTime,
|
|
960
962
|
});
|
|
961
963
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
964
|
+
try {
|
|
965
|
+
return await handleTranslatedJsonRequest({
|
|
966
|
+
ctx,
|
|
967
|
+
format: "claude",
|
|
968
|
+
requestModel: body.model,
|
|
969
|
+
parsed,
|
|
970
|
+
attempts,
|
|
971
|
+
tracer,
|
|
972
|
+
requestStartTime,
|
|
973
|
+
terminalFailureStatus: 502,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
978
|
+
logger.error(`[claude-proxy] Translated generation failed for ${body.model}: ${message}`);
|
|
979
|
+
const clientError = buildClaudeError(502, `Generation failed: ${message}`);
|
|
980
|
+
const clientErrorBody = JSON.stringify(clientError);
|
|
981
|
+
logProxyBody({
|
|
982
|
+
phase: "client_response",
|
|
983
|
+
headers: { "content-type": "application/json" },
|
|
984
|
+
body: clientErrorBody,
|
|
985
|
+
bodySize: Buffer.byteLength(clientErrorBody, "utf8"),
|
|
986
|
+
contentType: "application/json",
|
|
987
|
+
account: "translation",
|
|
988
|
+
accountType: "translation",
|
|
989
|
+
responseStatus: 502,
|
|
990
|
+
durationMs: Date.now() - requestStartTime,
|
|
991
|
+
});
|
|
992
|
+
return clientError;
|
|
993
|
+
}
|
|
971
994
|
}
|
|
972
995
|
function logProxyRoutingPlan(logProxyBody, stage, plan) {
|
|
973
996
|
logProxyBody({
|
|
@@ -980,7 +1003,7 @@ function logProxyRoutingPlan(logProxyBody, stage, plan) {
|
|
|
980
1003
|
});
|
|
981
1004
|
}
|
|
982
1005
|
async function handleClaudePassthroughRequest(args) {
|
|
983
|
-
const { ctx, body, clientRequestBody, tracer, requestStartTime, logProxyBody, } = args;
|
|
1006
|
+
const { ctx, body, clientRequestBody, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
|
|
984
1007
|
tracer?.setMode("passthrough-cli");
|
|
985
1008
|
const bodyStr = clientRequestBody;
|
|
986
1009
|
const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
|
|
@@ -1016,6 +1039,7 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
1016
1039
|
upstreamUrl: "https://api.anthropic.com/v1/messages?beta=true",
|
|
1017
1040
|
},
|
|
1018
1041
|
});
|
|
1042
|
+
recordAttempt("passthrough", "passthrough");
|
|
1019
1043
|
let response;
|
|
1020
1044
|
try {
|
|
1021
1045
|
response = await fetch("https://api.anthropic.com/v1/messages?beta=true", {
|
|
@@ -1027,24 +1051,11 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
1027
1051
|
}
|
|
1028
1052
|
catch (fetchErr) {
|
|
1029
1053
|
const errMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
1054
|
+
recordAttemptError("passthrough", "passthrough", 502);
|
|
1030
1055
|
tracer?.setError("network_error", errMsg);
|
|
1031
1056
|
upstreamSpan?.end();
|
|
1032
1057
|
tracer?.end(502, Date.now() - requestStartTime);
|
|
1033
|
-
|
|
1034
|
-
timestamp: new Date().toISOString(),
|
|
1035
|
-
requestId: ctx.requestId,
|
|
1036
|
-
method: ctx.method,
|
|
1037
|
-
path: ctx.path,
|
|
1038
|
-
model: body.model,
|
|
1039
|
-
stream: body.stream ?? false,
|
|
1040
|
-
toolCount,
|
|
1041
|
-
account: "passthrough",
|
|
1042
|
-
accountType: "passthrough",
|
|
1043
|
-
responseStatus: 502,
|
|
1044
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1045
|
-
errorType: "network_error",
|
|
1046
|
-
errorMessage: errMsg,
|
|
1047
|
-
});
|
|
1058
|
+
logFinalRequest(502, "passthrough", "passthrough", "network_error", errMsg);
|
|
1048
1059
|
const errorBody = buildClaudeError(502, `Passthrough fetch failed: ${errMsg}`);
|
|
1049
1060
|
const errorBodyText = JSON.stringify(errorBody);
|
|
1050
1061
|
logProxyBody({
|
|
@@ -1068,6 +1079,7 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
1068
1079
|
tracer?.logUpstreamResponseHeaders(upstreamResponseHeaders);
|
|
1069
1080
|
if (!response.ok) {
|
|
1070
1081
|
const errorText = await response.text();
|
|
1082
|
+
recordAttemptError("passthrough", "passthrough", response.status, response.status === 429 ? "quota" : undefined);
|
|
1071
1083
|
tracer?.logUpstreamResponseBody(errorText);
|
|
1072
1084
|
logProxyBody({
|
|
1073
1085
|
phase: "upstream_response",
|
|
@@ -1096,6 +1108,7 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
1096
1108
|
upstreamSpan?.end();
|
|
1097
1109
|
tracer?.setError("api_error", errorText.slice(0, 500));
|
|
1098
1110
|
tracer?.end(response.status, Date.now() - requestStartTime);
|
|
1111
|
+
logFinalRequest(response.status, "passthrough", "passthrough", response.status === 429 ? "rate_limit_error" : "api_error", errorText);
|
|
1099
1112
|
try {
|
|
1100
1113
|
return JSON.parse(errorText);
|
|
1101
1114
|
}
|
|
@@ -1115,6 +1128,7 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
1115
1128
|
upstreamSpan,
|
|
1116
1129
|
upstreamResponseHeaders,
|
|
1117
1130
|
logProxyBody,
|
|
1131
|
+
logFinalRequest,
|
|
1118
1132
|
});
|
|
1119
1133
|
}
|
|
1120
1134
|
return handleClaudePassthroughJsonResponse({
|
|
@@ -1128,6 +1142,7 @@ async function handleClaudePassthroughRequest(args) {
|
|
|
1128
1142
|
upstreamSpan,
|
|
1129
1143
|
upstreamResponseHeaders,
|
|
1130
1144
|
logProxyBody,
|
|
1145
|
+
logFinalRequest,
|
|
1131
1146
|
});
|
|
1132
1147
|
}
|
|
1133
1148
|
function trackUpstreamReadableStream(source) {
|
|
@@ -1168,15 +1183,28 @@ function trackUpstreamReadableStream(source) {
|
|
|
1168
1183
|
return { stream, outcome: tracker.outcome };
|
|
1169
1184
|
}
|
|
1170
1185
|
async function handleClaudePassthroughStreamResponse(args) {
|
|
1171
|
-
const {
|
|
1186
|
+
const { bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
|
|
1172
1187
|
const responseHeaders = { ...upstreamResponseHeaders };
|
|
1173
1188
|
const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
|
|
1174
1189
|
const responseBody = response.body;
|
|
1175
1190
|
if (!responseBody) {
|
|
1191
|
+
recordAttemptError("passthrough", "passthrough", 502);
|
|
1176
1192
|
throw new Error("Expected passthrough stream response body");
|
|
1177
1193
|
}
|
|
1178
1194
|
const trackedStream = trackUpstreamReadableStream(responseBody);
|
|
1179
1195
|
let streamSource = trackedStream.stream;
|
|
1196
|
+
let streamFinalized = false;
|
|
1197
|
+
const finalizeStream = (status, errorType, errorMessage, usage) => {
|
|
1198
|
+
if (streamFinalized) {
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
streamFinalized = true;
|
|
1202
|
+
if (errorType === "stream_error") {
|
|
1203
|
+
recordAttemptError("passthrough", "passthrough", status);
|
|
1204
|
+
}
|
|
1205
|
+
logFinalRequest(status, "passthrough", "passthrough", errorType, errorMessage, usage);
|
|
1206
|
+
return true;
|
|
1207
|
+
};
|
|
1180
1208
|
if (tracer) {
|
|
1181
1209
|
try {
|
|
1182
1210
|
const { stream: interceptor, telemetry } = createSSEInterceptor({
|
|
@@ -1224,31 +1252,11 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1224
1252
|
capturedTracer.setError(failure.errorType, failure.message);
|
|
1225
1253
|
}
|
|
1226
1254
|
capturedTracer.end(failure?.status ?? response.status, Date.now() - requestStartTime);
|
|
1227
|
-
|
|
1228
|
-
logRequest({
|
|
1229
|
-
timestamp: new Date().toISOString(),
|
|
1230
|
-
requestId: ctx.requestId,
|
|
1231
|
-
method: ctx.method,
|
|
1232
|
-
path: ctx.path,
|
|
1233
|
-
model: body.model,
|
|
1234
|
-
stream: true,
|
|
1235
|
-
toolCount,
|
|
1236
|
-
account: "passthrough",
|
|
1237
|
-
accountType: "passthrough",
|
|
1238
|
-
responseStatus: failure?.status ?? response.status,
|
|
1239
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1255
|
+
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message, {
|
|
1240
1256
|
inputTokens: data.usage.inputTokens,
|
|
1241
1257
|
outputTokens: data.usage.outputTokens,
|
|
1242
1258
|
cacheCreationTokens: data.usage.cacheCreationInputTokens,
|
|
1243
1259
|
cacheReadTokens: data.usage.cacheReadInputTokens,
|
|
1244
|
-
traceId: traceCtx.traceId,
|
|
1245
|
-
spanId: traceCtx.spanId,
|
|
1246
|
-
...(failure
|
|
1247
|
-
? {
|
|
1248
|
-
errorType: failure.errorType,
|
|
1249
|
-
errorMessage: failure.message,
|
|
1250
|
-
}
|
|
1251
|
-
: {}),
|
|
1252
1260
|
});
|
|
1253
1261
|
logProxyBody({
|
|
1254
1262
|
phase: "upstream_response",
|
|
@@ -1276,27 +1284,14 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1276
1284
|
});
|
|
1277
1285
|
})
|
|
1278
1286
|
.catch((error) => {
|
|
1279
|
-
|
|
1287
|
+
if (streamFinalized) {
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1291
|
+
capturedTracer.setError("stream_telemetry_error", message);
|
|
1280
1292
|
capturedUpstreamSpan?.end();
|
|
1281
1293
|
capturedTracer.end(500, Date.now() - requestStartTime);
|
|
1282
|
-
|
|
1283
|
-
logRequest({
|
|
1284
|
-
timestamp: new Date().toISOString(),
|
|
1285
|
-
requestId: ctx.requestId,
|
|
1286
|
-
method: ctx.method,
|
|
1287
|
-
path: ctx.path,
|
|
1288
|
-
model: body.model,
|
|
1289
|
-
stream: true,
|
|
1290
|
-
toolCount,
|
|
1291
|
-
account: "passthrough",
|
|
1292
|
-
accountType: "passthrough",
|
|
1293
|
-
responseStatus: 500,
|
|
1294
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1295
|
-
errorType: "stream_error",
|
|
1296
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
1297
|
-
traceId: traceCtx.traceId,
|
|
1298
|
-
spanId: traceCtx.spanId,
|
|
1299
|
-
});
|
|
1294
|
+
finalizeStream(500, "stream_telemetry_error", message);
|
|
1300
1295
|
});
|
|
1301
1296
|
}
|
|
1302
1297
|
catch {
|
|
@@ -1307,28 +1302,7 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1307
1302
|
tracer.setError(failure.errorType, failure.message);
|
|
1308
1303
|
}
|
|
1309
1304
|
tracer.end(failure?.status ?? response.status, Date.now() - requestStartTime);
|
|
1310
|
-
|
|
1311
|
-
logRequest({
|
|
1312
|
-
timestamp: new Date().toISOString(),
|
|
1313
|
-
requestId: ctx.requestId,
|
|
1314
|
-
method: ctx.method,
|
|
1315
|
-
path: ctx.path,
|
|
1316
|
-
model: body.model,
|
|
1317
|
-
stream: true,
|
|
1318
|
-
toolCount,
|
|
1319
|
-
account: "passthrough",
|
|
1320
|
-
accountType: "passthrough",
|
|
1321
|
-
responseStatus: failure?.status ?? response.status,
|
|
1322
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1323
|
-
traceId: traceCtx.traceId,
|
|
1324
|
-
spanId: traceCtx.spanId,
|
|
1325
|
-
...(failure
|
|
1326
|
-
? {
|
|
1327
|
-
errorType: failure.errorType,
|
|
1328
|
-
errorMessage: failure.message,
|
|
1329
|
-
}
|
|
1330
|
-
: {}),
|
|
1331
|
-
});
|
|
1305
|
+
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message);
|
|
1332
1306
|
});
|
|
1333
1307
|
}
|
|
1334
1308
|
}
|
|
@@ -1342,28 +1316,11 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1342
1316
|
.then(([data, clientBody, rawOutcome]) => {
|
|
1343
1317
|
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
|
|
1344
1318
|
const failure = getStreamFailureDetails(terminalOutcome);
|
|
1345
|
-
|
|
1346
|
-
timestamp: new Date().toISOString(),
|
|
1347
|
-
requestId: ctx.requestId,
|
|
1348
|
-
method: ctx.method,
|
|
1349
|
-
path: ctx.path,
|
|
1350
|
-
model: body.model,
|
|
1351
|
-
stream: true,
|
|
1352
|
-
toolCount,
|
|
1353
|
-
account: "passthrough",
|
|
1354
|
-
accountType: "passthrough",
|
|
1355
|
-
responseStatus: failure?.status ?? response.status,
|
|
1356
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1319
|
+
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message, {
|
|
1357
1320
|
inputTokens: data.usage.inputTokens,
|
|
1358
1321
|
outputTokens: data.usage.outputTokens,
|
|
1359
1322
|
cacheCreationTokens: data.usage.cacheCreationInputTokens,
|
|
1360
1323
|
cacheReadTokens: data.usage.cacheReadInputTokens,
|
|
1361
|
-
...(failure
|
|
1362
|
-
? {
|
|
1363
|
-
errorType: failure.errorType,
|
|
1364
|
-
errorMessage: failure.message,
|
|
1365
|
-
}
|
|
1366
|
-
: {}),
|
|
1367
1324
|
});
|
|
1368
1325
|
logProxyBody({
|
|
1369
1326
|
phase: "upstream_response",
|
|
@@ -1391,21 +1348,9 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1391
1348
|
});
|
|
1392
1349
|
})
|
|
1393
1350
|
.catch((error) => {
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
method: ctx.method,
|
|
1398
|
-
path: ctx.path,
|
|
1399
|
-
model: body.model,
|
|
1400
|
-
stream: true,
|
|
1401
|
-
toolCount,
|
|
1402
|
-
account: "passthrough",
|
|
1403
|
-
accountType: "passthrough",
|
|
1404
|
-
responseStatus: 500,
|
|
1405
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1406
|
-
errorType: "stream_telemetry_error",
|
|
1407
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
1408
|
-
});
|
|
1351
|
+
if (!streamFinalized) {
|
|
1352
|
+
finalizeStream(500, "stream_telemetry_error", error instanceof Error ? error.message : String(error));
|
|
1353
|
+
}
|
|
1409
1354
|
});
|
|
1410
1355
|
}
|
|
1411
1356
|
catch {
|
|
@@ -1413,25 +1358,7 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1413
1358
|
// the transport failure to the client.
|
|
1414
1359
|
trackedStream.outcome.then((outcome) => {
|
|
1415
1360
|
const failure = getStreamFailureDetails(outcome);
|
|
1416
|
-
|
|
1417
|
-
timestamp: new Date().toISOString(),
|
|
1418
|
-
requestId: ctx.requestId,
|
|
1419
|
-
method: ctx.method,
|
|
1420
|
-
path: ctx.path,
|
|
1421
|
-
model: body.model,
|
|
1422
|
-
stream: true,
|
|
1423
|
-
toolCount,
|
|
1424
|
-
account: "passthrough",
|
|
1425
|
-
accountType: "passthrough",
|
|
1426
|
-
responseStatus: failure?.status ?? response.status,
|
|
1427
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1428
|
-
...(failure
|
|
1429
|
-
? {
|
|
1430
|
-
errorType: failure.errorType,
|
|
1431
|
-
errorMessage: failure.message,
|
|
1432
|
-
}
|
|
1433
|
-
: {}),
|
|
1434
|
-
});
|
|
1361
|
+
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message);
|
|
1435
1362
|
});
|
|
1436
1363
|
}
|
|
1437
1364
|
}
|
|
@@ -1442,7 +1369,7 @@ async function handleClaudePassthroughStreamResponse(args) {
|
|
|
1442
1369
|
});
|
|
1443
1370
|
}
|
|
1444
1371
|
async function handleClaudePassthroughJsonResponse(args) {
|
|
1445
|
-
const {
|
|
1372
|
+
const { bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
|
|
1446
1373
|
const responseText = await response.text();
|
|
1447
1374
|
tracer?.logUpstreamResponseBody(responseText);
|
|
1448
1375
|
logProxyBody({
|
|
@@ -1505,43 +1432,17 @@ async function handleClaudePassthroughJsonResponse(args) {
|
|
|
1505
1432
|
tracer.recordBodySizes(bodyStr.length, responseJsonStr.length);
|
|
1506
1433
|
upstreamSpan?.end();
|
|
1507
1434
|
tracer.end(response.status, Date.now() - requestStartTime);
|
|
1508
|
-
|
|
1509
|
-
logRequest({
|
|
1510
|
-
timestamp: new Date().toISOString(),
|
|
1511
|
-
requestId: ctx.requestId,
|
|
1512
|
-
method: ctx.method,
|
|
1513
|
-
path: ctx.path,
|
|
1514
|
-
model: body.model,
|
|
1515
|
-
stream: false,
|
|
1516
|
-
toolCount,
|
|
1517
|
-
account: "passthrough",
|
|
1518
|
-
accountType: "passthrough",
|
|
1519
|
-
responseStatus: response.status,
|
|
1520
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1435
|
+
logFinalRequest(response.status, "passthrough", "passthrough", undefined, undefined, {
|
|
1521
1436
|
inputTokens: usage?.input_tokens,
|
|
1522
1437
|
outputTokens: usage?.output_tokens,
|
|
1523
1438
|
cacheCreationTokens: usage?.cache_creation_input_tokens,
|
|
1524
1439
|
cacheReadTokens: usage?.cache_read_input_tokens,
|
|
1525
|
-
traceId: traceCtx.traceId,
|
|
1526
|
-
spanId: traceCtx.spanId,
|
|
1527
1440
|
});
|
|
1528
1441
|
}
|
|
1529
1442
|
else {
|
|
1530
1443
|
upstreamSpan?.end();
|
|
1531
1444
|
tracer?.end(response.status, Date.now() - requestStartTime);
|
|
1532
|
-
|
|
1533
|
-
timestamp: new Date().toISOString(),
|
|
1534
|
-
requestId: ctx.requestId,
|
|
1535
|
-
method: ctx.method,
|
|
1536
|
-
path: ctx.path,
|
|
1537
|
-
model: body.model,
|
|
1538
|
-
stream: false,
|
|
1539
|
-
toolCount,
|
|
1540
|
-
account: "passthrough",
|
|
1541
|
-
accountType: "passthrough",
|
|
1542
|
-
responseStatus: response.status,
|
|
1543
|
-
responseTimeMs: Date.now() - requestStartTime,
|
|
1544
|
-
});
|
|
1445
|
+
logFinalRequest(response.status, "passthrough", "passthrough");
|
|
1545
1446
|
}
|
|
1546
1447
|
return responseJson;
|
|
1547
1448
|
}
|
|
@@ -1632,7 +1533,9 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1632
1533
|
expiresAt,
|
|
1633
1534
|
label,
|
|
1634
1535
|
};
|
|
1635
|
-
const refreshed = await
|
|
1536
|
+
const refreshed = await refreshTokenFromLatest(tempAccount, {
|
|
1537
|
+
providerKey: key,
|
|
1538
|
+
});
|
|
1636
1539
|
if (!refreshed.success) {
|
|
1637
1540
|
const account = {
|
|
1638
1541
|
key,
|
|
@@ -1701,20 +1604,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1701
1604
|
};
|
|
1702
1605
|
}
|
|
1703
1606
|
for (const account of accounts) {
|
|
1704
|
-
|
|
1705
|
-
const tokenChanged = state.lastToken !== account.token ||
|
|
1706
|
-
state.lastRefreshToken !== account.refreshToken;
|
|
1707
|
-
if (tokenChanged) {
|
|
1708
|
-
if (state.permanentlyDisabled) {
|
|
1709
|
-
logger.always(`[proxy] account=${account.label} eligible credentials reloaded; clearing stale runtime auth-disable state`);
|
|
1710
|
-
}
|
|
1711
|
-
// Eligibility was already resolved while loading accounts. This only
|
|
1712
|
-
// clears stale in-process auth state after an explicit credential reload.
|
|
1713
|
-
state.consecutiveRefreshFailures = 0;
|
|
1714
|
-
state.permanentlyDisabled = false;
|
|
1715
|
-
}
|
|
1716
|
-
state.lastToken = account.token;
|
|
1717
|
-
state.lastRefreshToken = account.refreshToken;
|
|
1607
|
+
reconcileEligibleAccountRuntimeState(account);
|
|
1718
1608
|
}
|
|
1719
1609
|
await seedRuntimeQuotasFromDisk(accounts);
|
|
1720
1610
|
const enabledAccounts = accounts.filter((account) => {
|
|
@@ -1856,7 +1746,6 @@ async function executeClaudeFallbackTranslation(args) {
|
|
|
1856
1746
|
}
|
|
1857
1747
|
// Telemetry AFTER validation — not before like the old lazy path
|
|
1858
1748
|
tracer?.end(200, Date.now() - requestStartTime);
|
|
1859
|
-
recordFinalSuccess();
|
|
1860
1749
|
logFinalRequest(200, "", providerLabel, undefined, undefined, {
|
|
1861
1750
|
inputTokens: resolvedUsage.input,
|
|
1862
1751
|
outputTokens: resolvedUsage.output,
|
|
@@ -1894,7 +1783,6 @@ async function executeClaudeFallbackTranslation(args) {
|
|
|
1894
1783
|
toolCalls: streamResult.toolCalls,
|
|
1895
1784
|
};
|
|
1896
1785
|
tracer?.end(200, Date.now() - requestStartTime);
|
|
1897
|
-
recordFinalSuccess();
|
|
1898
1786
|
const clientResponse = serializeClaudeResponse(internal, body.model);
|
|
1899
1787
|
logFinalRequest(200, "", providerLabel, undefined, undefined, {
|
|
1900
1788
|
inputTokens: internal.usage?.input,
|
|
@@ -2100,7 +1988,6 @@ function buildClaudeAnthropicFailureResponse(args) {
|
|
|
2100
1988
|
if (invalidRequestFailure) {
|
|
2101
1989
|
tracer?.setError("invalid_request_error", summarizeErrorMessage(invalidRequestFailure.body));
|
|
2102
1990
|
tracer?.end(invalidRequestFailure.status, Date.now() - requestStartTime);
|
|
2103
|
-
recordFinalError(invalidRequestFailure.status);
|
|
2104
1991
|
try {
|
|
2105
1992
|
const parsedError = JSON.parse(invalidRequestFailure.body);
|
|
2106
1993
|
logFinalRequest(invalidRequestFailure.status, "", "final", "invalid_request_error", summarizeErrorMessage(invalidRequestFailure.body));
|
|
@@ -2162,7 +2049,6 @@ function buildClaudeAnthropicFailureResponse(args) {
|
|
|
2162
2049
|
const errorBody = buildClaudeError(429, errorMessage, "overloaded_error");
|
|
2163
2050
|
tracer?.setError("rate_limit_error", errorMessage);
|
|
2164
2051
|
tracer?.end(429, Date.now() - requestStartTime);
|
|
2165
|
-
recordFinalError(429);
|
|
2166
2052
|
logFinalRequest(429, "", "final", "rate_limit_error", errorMessage);
|
|
2167
2053
|
const errorBodyText = JSON.stringify(errorBody);
|
|
2168
2054
|
logProxyBody({
|
|
@@ -2254,7 +2140,6 @@ async function handleAnthropicStreamingSuccessResponse(args) {
|
|
|
2254
2140
|
upstreamSpan?.end();
|
|
2255
2141
|
tracer?.setError("stream_error", "No response body from upstream");
|
|
2256
2142
|
tracer?.end(502, Date.now() - requestStartTime);
|
|
2257
|
-
recordFinalError(502, account.label, account.type);
|
|
2258
2143
|
logFinalRequest(502, account.label, account.type, "stream_error", "No response body from upstream");
|
|
2259
2144
|
const clientError = buildClaudeError(502, "No response body from upstream");
|
|
2260
2145
|
const clientErrorBody = JSON.stringify(clientError);
|
|
@@ -2468,6 +2353,11 @@ function getStreamFailureDetails(outcome) {
|
|
|
2468
2353
|
}
|
|
2469
2354
|
return undefined;
|
|
2470
2355
|
}
|
|
2356
|
+
function recordCommittedAnthropicStreamAttemptFailure(outcome, account) {
|
|
2357
|
+
if (outcome.kind === "upstream_error") {
|
|
2358
|
+
recordAttemptError(account.label, account.type, 502);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2471
2361
|
function attachAnthropicSuccessStreamTelemetry(args) {
|
|
2472
2362
|
const { account, response, responseHeaders, remainingStream, streamOutcome, tracer, requestStartTime, attemptNumber, finalBodyStr, upstreamSpan, logProxyBody, logFinalRequest, } = args;
|
|
2473
2363
|
const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
|
|
@@ -2486,6 +2376,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2486
2376
|
Promise.all([telemetry, clientCapture, streamOutcome])
|
|
2487
2377
|
.then(([data, clientBody, rawOutcome]) => {
|
|
2488
2378
|
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
|
|
2379
|
+
recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account);
|
|
2489
2380
|
capturedTracer.setUsage({
|
|
2490
2381
|
inputTokens: data.usage.inputTokens,
|
|
2491
2382
|
outputTokens: data.usage.outputTokens,
|
|
@@ -2525,12 +2416,10 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2525
2416
|
if (failure) {
|
|
2526
2417
|
capturedTracer.setError(failure.errorType, failure.message);
|
|
2527
2418
|
capturedTracer.end(failure.status, Date.now() - requestStartTime);
|
|
2528
|
-
recordFinalError(failure.status, capturedAccountLabel, account.type);
|
|
2529
2419
|
logFinalRequest(failure.status, capturedAccountLabel, account.type, failure.errorType, failure.message, usage);
|
|
2530
2420
|
}
|
|
2531
2421
|
else {
|
|
2532
2422
|
capturedTracer.end(200, Date.now() - requestStartTime);
|
|
2533
|
-
recordFinalSuccess(capturedAccountLabel, account.type);
|
|
2534
2423
|
logFinalRequest(200, capturedAccountLabel, account.type, undefined, undefined, usage);
|
|
2535
2424
|
}
|
|
2536
2425
|
logProxyBody({
|
|
@@ -2559,28 +2448,26 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2559
2448
|
});
|
|
2560
2449
|
})
|
|
2561
2450
|
.catch((error) => {
|
|
2562
|
-
capturedTracer.setError("
|
|
2451
|
+
capturedTracer.setError("stream_telemetry_error", error instanceof Error ? error.message : String(error));
|
|
2563
2452
|
capturedUpstreamSpan?.end();
|
|
2564
2453
|
capturedTracer.end(500, Date.now() - requestStartTime);
|
|
2565
|
-
|
|
2566
|
-
logFinalRequest(500, capturedAccountLabel, account.type, "stream_error", error instanceof Error ? error.message : String(error));
|
|
2454
|
+
logFinalRequest(500, capturedAccountLabel, account.type, "stream_telemetry_error", error instanceof Error ? error.message : String(error));
|
|
2567
2455
|
});
|
|
2568
2456
|
}
|
|
2569
2457
|
catch {
|
|
2570
2458
|
// Interceptor attachment failed after stream setup. Preserve delivery but
|
|
2571
2459
|
// still settle the request from the actual stream terminal outcome.
|
|
2572
2460
|
streamOutcome.then((outcome) => {
|
|
2461
|
+
recordCommittedAnthropicStreamAttemptFailure(outcome, account);
|
|
2573
2462
|
const failure = getStreamFailureDetails(outcome);
|
|
2574
2463
|
upstreamSpan?.end();
|
|
2575
2464
|
if (failure) {
|
|
2576
2465
|
tracer.setError(failure.errorType, failure.message);
|
|
2577
2466
|
tracer.end(failure.status, Date.now() - requestStartTime);
|
|
2578
|
-
recordFinalError(failure.status, account.label, account.type);
|
|
2579
2467
|
logFinalRequest(failure.status, account.label, account.type, failure.errorType, failure.message);
|
|
2580
2468
|
}
|
|
2581
2469
|
else {
|
|
2582
2470
|
tracer.end(response.status, Date.now() - requestStartTime);
|
|
2583
|
-
recordFinalSuccess(account.label, account.type);
|
|
2584
2471
|
logFinalRequest(response.status, account.label, account.type);
|
|
2585
2472
|
}
|
|
2586
2473
|
});
|
|
@@ -2597,6 +2484,7 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2597
2484
|
Promise.all([noTracerTelemetry, clientCapture, streamOutcome])
|
|
2598
2485
|
.then(([data, clientBody, rawOutcome]) => {
|
|
2599
2486
|
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage);
|
|
2487
|
+
recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account);
|
|
2600
2488
|
const failure = getStreamFailureDetails(terminalOutcome);
|
|
2601
2489
|
const usage = {
|
|
2602
2490
|
inputTokens: data.usage.inputTokens,
|
|
@@ -2605,11 +2493,9 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2605
2493
|
cacheReadTokens: data.usage.cacheReadInputTokens,
|
|
2606
2494
|
};
|
|
2607
2495
|
if (failure) {
|
|
2608
|
-
recordFinalError(failure.status, capturedAccountLabel, account.type);
|
|
2609
2496
|
logFinalRequest(failure.status, capturedAccountLabel, account.type, failure.errorType, failure.message, usage);
|
|
2610
2497
|
}
|
|
2611
2498
|
else {
|
|
2612
|
-
recordFinalSuccess(capturedAccountLabel, account.type);
|
|
2613
2499
|
logFinalRequest(200, capturedAccountLabel, account.type, undefined, undefined, usage);
|
|
2614
2500
|
}
|
|
2615
2501
|
logProxyBody({
|
|
@@ -2639,7 +2525,6 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2639
2525
|
})
|
|
2640
2526
|
.catch((error) => {
|
|
2641
2527
|
const message = error instanceof Error ? error.message : String(error);
|
|
2642
|
-
recordFinalError(500, account.label, account.type);
|
|
2643
2528
|
logFinalRequest(500, account.label, account.type, "stream_telemetry_error", message);
|
|
2644
2529
|
});
|
|
2645
2530
|
}
|
|
@@ -2663,13 +2548,12 @@ function attachAnthropicSuccessStreamTelemetry(args) {
|
|
|
2663
2548
|
// Non-fatal
|
|
2664
2549
|
});
|
|
2665
2550
|
streamOutcome.then((outcome) => {
|
|
2551
|
+
recordCommittedAnthropicStreamAttemptFailure(outcome, account);
|
|
2666
2552
|
const failure = getStreamFailureDetails(outcome);
|
|
2667
2553
|
if (failure) {
|
|
2668
|
-
recordFinalError(failure.status, account.label, account.type);
|
|
2669
2554
|
logFinalRequest(failure.status, account.label, account.type, failure.errorType, failure.message);
|
|
2670
2555
|
}
|
|
2671
2556
|
else {
|
|
2672
|
-
recordFinalSuccess(account.label, account.type);
|
|
2673
2557
|
logFinalRequest(response.status, account.label, account.type);
|
|
2674
2558
|
}
|
|
2675
2559
|
});
|
|
@@ -2765,7 +2649,6 @@ async function handleAnthropicJsonSuccessResponse(args) {
|
|
|
2765
2649
|
tracer.recordBodySizes(finalBodyStr.length, responseJsonStr.length);
|
|
2766
2650
|
upstreamSpan?.end();
|
|
2767
2651
|
tracer.end(response.status, Date.now() - requestStartTime);
|
|
2768
|
-
recordFinalSuccess(account.label, account.type);
|
|
2769
2652
|
logFinalRequest(response.status, account.label, account.type, undefined, undefined, {
|
|
2770
2653
|
inputTokens: usage?.input_tokens,
|
|
2771
2654
|
outputTokens: usage?.output_tokens,
|
|
@@ -2778,7 +2661,6 @@ async function handleAnthropicJsonSuccessResponse(args) {
|
|
|
2778
2661
|
const noTracerUsage = responseJson && typeof responseJson === "object"
|
|
2779
2662
|
? responseJson.usage
|
|
2780
2663
|
: undefined;
|
|
2781
|
-
recordFinalSuccess(account.label, account.type);
|
|
2782
2664
|
logFinalRequest(response.status, account.label, account.type, undefined, undefined, {
|
|
2783
2665
|
inputTokens: noTracerUsage?.input_tokens,
|
|
2784
2666
|
outputTokens: noTracerUsage?.output_tokens,
|
|
@@ -2859,7 +2741,6 @@ async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
|
|
|
2859
2741
|
tracer.recordBodySizes(finalBodyStr.length, retryJsonStr.length);
|
|
2860
2742
|
upstreamSpan?.end();
|
|
2861
2743
|
tracer.end(retryResp.status, Date.now() - requestStartTime);
|
|
2862
|
-
recordFinalSuccess(account.label, account.type);
|
|
2863
2744
|
logFinalRequest(retryResp.status, account.label, account.type, undefined, undefined, {
|
|
2864
2745
|
inputTokens: retryUsage?.input_tokens,
|
|
2865
2746
|
outputTokens: retryUsage?.output_tokens,
|
|
@@ -2869,7 +2750,6 @@ async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
|
|
|
2869
2750
|
}
|
|
2870
2751
|
else {
|
|
2871
2752
|
upstreamSpan?.end();
|
|
2872
|
-
recordFinalSuccess(account.label, account.type);
|
|
2873
2753
|
logFinalRequest(retryResp.status, account.label, account.type);
|
|
2874
2754
|
}
|
|
2875
2755
|
return retryJson;
|
|
@@ -2890,7 +2770,7 @@ async function handleAnthropicAuthRetry(args) {
|
|
|
2890
2770
|
let authRetryError = "received 401 from Anthropic";
|
|
2891
2771
|
for (let authRetry = 0; authRetry < MAX_AUTH_RETRIES; authRetry++) {
|
|
2892
2772
|
logger.always(`[proxy] ← 401 account=${account.label} refreshing (attempt ${authRetry + 1}/${MAX_AUTH_RETRIES})`);
|
|
2893
|
-
const refreshSucceeded = await
|
|
2773
|
+
const refreshSucceeded = await refreshTokenFromLatest(account, account.persistTarget);
|
|
2894
2774
|
if (!refreshSucceeded.success) {
|
|
2895
2775
|
authRetryError = `refresh failed for account=${account.label} attempt ${authRetry + 1}/${MAX_AUTH_RETRIES}: ${refreshSucceeded.error?.slice(0, 200) ?? "unknown"}`;
|
|
2896
2776
|
currentLastError = authRetryError;
|
|
@@ -3107,7 +2987,6 @@ async function handleAnthropicAuthRetry(args) {
|
|
|
3107
2987
|
break;
|
|
3108
2988
|
}
|
|
3109
2989
|
retryLogAttempt(retryStatus, "api_error", summarizeErrorMessage(retryBody));
|
|
3110
|
-
recordFinalError(retryStatus, account.label, account.type);
|
|
3111
2990
|
try {
|
|
3112
2991
|
logFinalRequest(retryStatus, account.label, account.type, "api_error", summarizeErrorMessage(retryBody));
|
|
3113
2992
|
return {
|
|
@@ -3213,7 +3092,6 @@ function buildAnthropicTerminalErrorResponse(args) {
|
|
|
3213
3092
|
}
|
|
3214
3093
|
function finalizeAnthropicTerminalFetchError(args) {
|
|
3215
3094
|
const { terminalError, account, tracer, requestStartTime, attemptNumber, logProxyBody, logFinalRequest, } = args;
|
|
3216
|
-
recordFinalError(terminalError.status, account.label, account.type);
|
|
3217
3095
|
tracer?.end(terminalError.status, Date.now() - requestStartTime);
|
|
3218
3096
|
return buildAnthropicTerminalErrorResponse({
|
|
3219
3097
|
responseStatus: terminalError.status,
|
|
@@ -3317,7 +3195,6 @@ async function handleAnthropicNonOkResponse(args) {
|
|
|
3317
3195
|
};
|
|
3318
3196
|
}
|
|
3319
3197
|
if (response.status === 404) {
|
|
3320
|
-
recordFinalError(response.status, account.label, account.type);
|
|
3321
3198
|
logger.always(`[proxy] ← 404 account=${account.label}`);
|
|
3322
3199
|
logAttempt(404, "not_found_error", summarizeErrorMessage(errBody));
|
|
3323
3200
|
tracer?.setError("not_found_error", summarizeErrorMessage(errBody));
|
|
@@ -3366,7 +3243,6 @@ async function handleAnthropicNonOkResponse(args) {
|
|
|
3366
3243
|
upstreamSpan: undefined,
|
|
3367
3244
|
};
|
|
3368
3245
|
}
|
|
3369
|
-
recordFinalError(response.status, account.label, account.type);
|
|
3370
3246
|
logger.always(`[proxy] ← ${response.status} account=${account.label}`);
|
|
3371
3247
|
logger.debug(`[claude-proxy] error body: ${errBody.substring(0, 200)}`);
|
|
3372
3248
|
logAttempt(response.status, "api_error", summarizeErrorMessage(errBody));
|
|
@@ -3437,7 +3313,35 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
3437
3313
|
: {}),
|
|
3438
3314
|
});
|
|
3439
3315
|
};
|
|
3316
|
+
let finalRequestLogged = false;
|
|
3440
3317
|
const logFinalRequest = (status, accountLabel, accountType, errorType, errorMessage, extra) => {
|
|
3318
|
+
if (finalRequestLogged) {
|
|
3319
|
+
logger.debug(`[claude-proxy] ignored duplicate finalization for request ${ctx.requestId}`);
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
finalRequestLogged = true;
|
|
3323
|
+
const finalAccountLabel = accountLabel ||
|
|
3324
|
+
(status >= 400 ? PROXY_INTERNAL_ACCOUNT_LABEL : undefined);
|
|
3325
|
+
const finalAccountType = accountLabel
|
|
3326
|
+
? accountType || undefined
|
|
3327
|
+
: status >= 400
|
|
3328
|
+
? PROXY_INTERNAL_ACCOUNT_TYPE
|
|
3329
|
+
: undefined;
|
|
3330
|
+
if (status >= 400) {
|
|
3331
|
+
recordFinalError(status, finalAccountLabel, finalAccountType, {
|
|
3332
|
+
requestId: ctx.requestId,
|
|
3333
|
+
errorType,
|
|
3334
|
+
terminalOutcome: errorType === "client_cancelled"
|
|
3335
|
+
? "client_cancelled"
|
|
3336
|
+
: errorType?.includes("stream")
|
|
3337
|
+
? "stream_error"
|
|
3338
|
+
: "handler_error",
|
|
3339
|
+
message: errorMessage,
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
else {
|
|
3343
|
+
recordFinalSuccess(finalAccountLabel, finalAccountType);
|
|
3344
|
+
}
|
|
3441
3345
|
const traceCtx = tracer?.getTraceContext();
|
|
3442
3346
|
logRequest({
|
|
3443
3347
|
timestamp: new Date().toISOString(),
|
|
@@ -3447,8 +3351,8 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
3447
3351
|
model: body.model,
|
|
3448
3352
|
stream: !!body.stream,
|
|
3449
3353
|
toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
|
|
3450
|
-
account:
|
|
3451
|
-
accountType,
|
|
3354
|
+
account: finalAccountLabel ?? "",
|
|
3355
|
+
accountType: finalAccountType ?? "",
|
|
3452
3356
|
responseStatus: status,
|
|
3453
3357
|
responseTimeMs: Date.now() - requestStartTime,
|
|
3454
3358
|
...(errorType ? { errorType } : {}),
|
|
@@ -3473,7 +3377,6 @@ function createClaudeRequestRuntimeContext(args) {
|
|
|
3473
3377
|
const buildLoggedClaudeError = (status, message, errorType, extra) => {
|
|
3474
3378
|
const errorBody = buildClaudeError(status, message, errorType);
|
|
3475
3379
|
const errorBodyText = JSON.stringify(errorBody);
|
|
3476
|
-
recordFinalError(status, extra?.account, extra?.accountType);
|
|
3477
3380
|
logFinalRequest(status, extra?.account ?? "", extra?.accountType ?? "final", errorType, message);
|
|
3478
3381
|
logProxyBody({
|
|
3479
3382
|
phase: "client_response",
|
|
@@ -3553,7 +3456,7 @@ async function prepareAnthropicAccountAttempt(args) {
|
|
|
3553
3456
|
let lastError = currentLastError;
|
|
3554
3457
|
let authFailureMessage = currentAuthFailureMessage;
|
|
3555
3458
|
if (needsRefresh(account)) {
|
|
3556
|
-
const refreshed = await
|
|
3459
|
+
const refreshed = await refreshTokenFromLatest(account, account.persistTarget);
|
|
3557
3460
|
if (refreshed.success) {
|
|
3558
3461
|
if (account.persistTarget) {
|
|
3559
3462
|
await persistTokens(account.persistTarget, account);
|
|
@@ -4262,6 +4165,31 @@ function isClaudeProxyRouteRuntimeOptions(value) {
|
|
|
4262
4165
|
"runtimeConfigProvider" in value &&
|
|
4263
4166
|
typeof value.runtimeConfigProvider === "function");
|
|
4264
4167
|
}
|
|
4168
|
+
function buildEarlyClaudeRequestError(args) {
|
|
4169
|
+
const { ctx, body, status, message, errorType } = args;
|
|
4170
|
+
recordFinalError(status, PROXY_INTERNAL_ACCOUNT_LABEL, PROXY_INTERNAL_ACCOUNT_TYPE, {
|
|
4171
|
+
requestId: ctx.requestId,
|
|
4172
|
+
errorType,
|
|
4173
|
+
terminalOutcome: "handler_error",
|
|
4174
|
+
message,
|
|
4175
|
+
});
|
|
4176
|
+
void logRequest({
|
|
4177
|
+
timestamp: new Date().toISOString(),
|
|
4178
|
+
requestId: ctx.requestId,
|
|
4179
|
+
method: ctx.method,
|
|
4180
|
+
path: ctx.path,
|
|
4181
|
+
model: typeof body?.model === "string" ? body.model : "",
|
|
4182
|
+
stream: body?.stream ?? false,
|
|
4183
|
+
toolCount: Array.isArray(body?.tools) ? body.tools.length : 0,
|
|
4184
|
+
account: PROXY_INTERNAL_ACCOUNT_LABEL,
|
|
4185
|
+
accountType: PROXY_INTERNAL_ACCOUNT_TYPE,
|
|
4186
|
+
responseStatus: status,
|
|
4187
|
+
responseTimeMs: 0,
|
|
4188
|
+
errorType,
|
|
4189
|
+
errorMessage: message,
|
|
4190
|
+
});
|
|
4191
|
+
return buildClaudeError(status, message);
|
|
4192
|
+
}
|
|
4265
4193
|
/**
|
|
4266
4194
|
* Create Claude-compatible proxy routes.
|
|
4267
4195
|
*
|
|
@@ -4305,15 +4233,27 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4305
4233
|
// 1. Validate
|
|
4306
4234
|
if (typeof body?.model !== "string" ||
|
|
4307
4235
|
!Array.isArray(body?.messages)) {
|
|
4308
|
-
return
|
|
4236
|
+
return buildEarlyClaudeRequestError({
|
|
4237
|
+
ctx,
|
|
4238
|
+
body,
|
|
4239
|
+
status: 400,
|
|
4240
|
+
message: "Missing required fields: model, messages",
|
|
4241
|
+
errorType: "invalid_request_error",
|
|
4242
|
+
});
|
|
4309
4243
|
}
|
|
4310
4244
|
// 2. Resolve model via router (or pass through to anthropic)
|
|
4311
4245
|
// Guard: without a model router, only Claude models are allowed.
|
|
4312
4246
|
const modelLower = body.model.toLowerCase();
|
|
4313
4247
|
if (!requestModelRouter && !modelLower.startsWith("claude-")) {
|
|
4314
|
-
return
|
|
4315
|
-
|
|
4316
|
-
|
|
4248
|
+
return buildEarlyClaudeRequestError({
|
|
4249
|
+
ctx,
|
|
4250
|
+
body,
|
|
4251
|
+
status: 404,
|
|
4252
|
+
message: `Model '${body.model}' is not an Anthropic model. ` +
|
|
4253
|
+
`The proxy only supports Claude models. ` +
|
|
4254
|
+
`Use a model router to route non-Claude models to other providers.`,
|
|
4255
|
+
errorType: "not_found_error",
|
|
4256
|
+
});
|
|
4317
4257
|
}
|
|
4318
4258
|
const route = requestModelRouter?.resolve(body.model) ?? {
|
|
4319
4259
|
provider: "anthropic",
|
|
@@ -4343,6 +4283,7 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4343
4283
|
tracer,
|
|
4344
4284
|
requestStartTime,
|
|
4345
4285
|
logProxyBody,
|
|
4286
|
+
logFinalRequest,
|
|
4346
4287
|
});
|
|
4347
4288
|
}
|
|
4348
4289
|
return handleAnthropicRoutedClaudeRequest({
|
|
@@ -4464,17 +4405,51 @@ function getOrCreateRuntimeState(accountKey) {
|
|
|
4464
4405
|
accountRuntimeState.set(accountKey, initial);
|
|
4465
4406
|
return initial;
|
|
4466
4407
|
}
|
|
4408
|
+
function reconcileEligibleAccountRuntimeState(account) {
|
|
4409
|
+
const state = getOrCreateRuntimeState(account.key);
|
|
4410
|
+
const tokenChanged = state.lastToken !== account.token ||
|
|
4411
|
+
state.lastRefreshToken !== account.refreshToken;
|
|
4412
|
+
const wasPermanentlyDisabled = state.permanentlyDisabled;
|
|
4413
|
+
if (wasPermanentlyDisabled) {
|
|
4414
|
+
logger.always(`[proxy] account=${account.label} is enabled in the token store; clearing stale runtime auth-disable state`);
|
|
4415
|
+
}
|
|
4416
|
+
state.permanentlyDisabled = false;
|
|
4417
|
+
if (tokenChanged || wasPermanentlyDisabled) {
|
|
4418
|
+
state.consecutiveRefreshFailures = 0;
|
|
4419
|
+
}
|
|
4420
|
+
state.lastToken = account.token;
|
|
4421
|
+
state.lastRefreshToken = account.refreshToken;
|
|
4422
|
+
}
|
|
4467
4423
|
async function disableAccountUntilReauth(account, state, reason) {
|
|
4468
|
-
state.permanentlyDisabled = true;
|
|
4469
|
-
// Decision 7 (usage): Persist disabled state to disk so it survives restarts
|
|
4470
4424
|
try {
|
|
4471
4425
|
const { tokenStore } = await import("../../auth/tokenStore.js");
|
|
4472
|
-
|
|
4426
|
+
const providerKey = account.persistTarget &&
|
|
4427
|
+
typeof account.persistTarget !== "string" &&
|
|
4428
|
+
"providerKey" in account.persistTarget
|
|
4429
|
+
? account.persistTarget.providerKey
|
|
4430
|
+
: undefined;
|
|
4431
|
+
if (providerKey) {
|
|
4432
|
+
const disabled = await tokenStore.markDisabledIfCurrent(providerKey, {
|
|
4433
|
+
accessToken: account.token,
|
|
4434
|
+
refreshToken: account.refreshToken,
|
|
4435
|
+
expiresAt: account.expiresAt ?? 0,
|
|
4436
|
+
}, reason);
|
|
4437
|
+
if (!disabled) {
|
|
4438
|
+
state.permanentlyDisabled = false;
|
|
4439
|
+
logger.always(`[proxy] account=${account.label} credentials changed while authentication was in flight; ignored stale disable`);
|
|
4440
|
+
return false;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
else {
|
|
4444
|
+
await tokenStore.markDisabled(account.key, reason);
|
|
4445
|
+
}
|
|
4473
4446
|
}
|
|
4474
4447
|
catch (e) {
|
|
4475
4448
|
logger.debug(`[proxy] failed to persist disabled state for ${account.label}: ${e instanceof Error ? e.message : String(e)}`);
|
|
4476
4449
|
}
|
|
4450
|
+
state.permanentlyDisabled = true;
|
|
4477
4451
|
logger.always(`[proxy] account=${account.label} disabled until re-authentication. Run: neurolink auth login anthropic --method oauth`);
|
|
4452
|
+
return true;
|
|
4478
4453
|
}
|
|
4479
4454
|
async function coolAccountAfterTransientRefreshFailure(account, state) {
|
|
4480
4455
|
state.consecutiveRefreshFailures += 1;
|
|
@@ -4698,6 +4673,7 @@ export const __testHooks = {
|
|
|
4698
4673
|
orderAccountsByQuota,
|
|
4699
4674
|
resetEpochToMs,
|
|
4700
4675
|
seedRuntimeQuotasFromDisk,
|
|
4676
|
+
reconcileEligibleAccountRuntimeState,
|
|
4701
4677
|
getAccountRuntimeState: (key) => {
|
|
4702
4678
|
const state = accountRuntimeState.get(key);
|
|
4703
4679
|
return state ? { ...state } : undefined;
|