@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.
@@ -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
- return handleTranslatedJsonRequest({
963
- ctx,
964
- format: "claude",
965
- requestModel: body.model,
966
- parsed,
967
- attempts,
968
- tracer,
969
- requestStartTime,
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
- logRequest({
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 { ctx, body, bodyStr, response, tracer, requestStartTime, toolCount, upstreamSpan, upstreamResponseHeaders, logProxyBody, } = args;
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
- const traceCtx = capturedTracer.getTraceContext();
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
- capturedTracer.setError("stream_error", error instanceof Error ? error.message : String(error));
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
- const traceCtx = capturedTracer.getTraceContext();
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
- const traceCtx = tracer.getTraceContext();
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
- logRequest({
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
- logRequest({
1395
- timestamp: new Date().toISOString(),
1396
- requestId: ctx.requestId,
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
- logRequest({
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 { ctx, body, bodyStr, response, tracer, requestStartTime, toolCount, upstreamSpan, upstreamResponseHeaders, logProxyBody, } = args;
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
- const traceCtx = tracer.getTraceContext();
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
- logRequest({
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 refreshToken(tempAccount);
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
- const state = getOrCreateRuntimeState(account.key);
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("stream_error", error instanceof Error ? error.message : String(error));
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
- recordFinalError(500, capturedAccountLabel, account.type);
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 refreshToken(account);
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: accountLabel,
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 refreshToken(account);
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 buildClaudeError(400, "Missing required fields: model, messages");
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 buildClaudeError(404, `Model '${body.model}' is not an Anthropic model. ` +
4315
- `The proxy only supports Claude models. ` +
4316
- `Use a model router to route non-Claude models to other providers.`);
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
- await tokenStore.markDisabled(account.key, reason);
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;