@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
9,398 lines • 449 kB
JavaScript
import { claudeOutputLogFields, observeClaudeJsonOutput, observeClaudeStreamOutput, } from "../../proxy/claudeOutputObservation.js";
/**
* Claude-Compatible Proxy Routes
*
* Exposes Anthropic-compatible /v1/messages, /v1/models, and /v1/messages/count_tokens
* endpoints. ALL requests are routed through ctx.neurolink.generate() / ctx.neurolink.stream()
* -- no direct HTTP calls to Anthropic.
*
* An optional ModelRouter can remap incoming model names to different
* provider/model pairs (e.g. "claude-sonnet-4-20250514" -> vertex/gemini-2.5-pro).
* Without a router, models are passed through to the Anthropic provider.
*/
import { access, readFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join } from "node:path";
import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, getOrCreateClaudeCodeIdentity, parseClaudeCodeUserId, } from "../../auth/anthropicOAuth.js";
import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from "../../proxy/accountCooldown.js";
import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, normalizeAnthropicAccountKey, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
import { getUnifiedRateLimitStatus, isQuotaOverageAvailable, loadAccountQuotas, mergeQuotaSnapshot, modelFamilyToken, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
import { fetchAccountUsage, listAnthropicAccountsForUsage, usageToQuota, } from "../../proxy/accountUsage.js";
import { tokenStore } from "../../auth/tokenStore.js";
import { fetchCodexAccountUsage, listCodexAccountsForUsage, resolveProxyStatusAccountIdentity, } from "../../proxy/codexAccountUsage.js";
import { AccountQuotaRefreshCoordinator } from "../../proxy/accountQuotaRefreshCoordinator.js";
import { ProviderTransportCoordinator } from "../../proxy/providerTransportCoordinator.js";
import { MAX_COOLDOWN_MS_BY_REASON } from "../../proxy/routingEvidence.js";
import { applyAffinityAndPrimary, applySpill, compareExpiryFirst, isPrecedenceEligible, rankAccounts, } from "../../proxy/accountRanking.js";
import { sessionAffinity } from "../../proxy/sessionAffinity.js";
import { buildProxyLimitHeaders, summarizePoolHeadroom, } from "../../proxy/quotaHeaders.js";
import { buildClaudeError, ClaudeStreamSerializer, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
import { getProxyUpstreamFailure } from "../../proxy/proxyFailureDetails.js";
import { CodexFallbackResponseError, CodexFallbackStreamError, consumeCodexFallbackResponse, createCodexFallbackStream, convertClaudeRequestToCodex, } from "../../proxy/codexFallback.js";
import { executeVertexAnthropicFallback, isVertexAnthropicModel, } from "../../proxy/vertexAnthropicFallback.js";
import { isProxyRequestFinalized, getProxyRequestAccounting, registerProxyResponseObserver, takeProxyResponseObservers, trackProxyResponse, } from "../../proxy/proxyActivity.js";
import { buildAnthropicModelsListResponse, buildTranslationOptions, handleTranslatedJsonRequest, handleTranslatedStreamRequest, } from "../../proxy/proxyTranslationEngine.js";
import { tracers } from "../../telemetry/tracers.js";
import { withSpan } from "../../telemetry/withSpan.js";
import { ProxyTracer, recordFallbackAttempt } from "../../proxy/proxyTracer.js";
import { createRawStreamCapture } from "../../proxy/rawStreamCapture.js";
import { relocateClientSystemIntoMessages } from "../../proxy/systemRelocation.js";
import { logBodyCapture, logRequest, logRequestAttempt, } from "../../proxy/requestLogger.js";
import { buildClientAttribution } from "../../proxy/clientAttribution.js";
import { createSSEInterceptor } from "../../proxy/sseInterceptor.js";
import { selectBorrowablePeers } from "../../proxy/peerStore.js";
import { evaluateResidentAccount, getResidentGrantForAccount, recordResidentSpend, } from "../../proxy/residentGrants.js";
import { buildProvisionClaim, isLeaseRefusal, issueLease, } from "../../proxy/shareLease.js";
import { forwardToPeer } from "../../proxy/peerTransport.js";
import { getShareContext } from "../../proxy/shareContext.js";
import { buildShareRefusal, extractShareToken } from "../../proxy/shareGate.js";
import { debitShareGrantCoins, getNodePublicUrl, getNoteSecret, getShareGrant, resolveShareToken, setShareGrantState, } from "../../proxy/shareGrants.js";
import { recordAuditObservation } from "../../proxy/shareAudit.js";
import { claimProvisionRequest, openProvisionRequest, } from "../../proxy/shareProvisioning.js";
import { applyReciprocalNetting, listShareReceipts, } from "../../proxy/shareReceipts.js";
import { decodeShareNote, inspectShareNote, redeemShareNote, } from "../../proxy/shareNotes.js";
import { verifySharePayload } from "../../proxy/shareSigning.js";
import { availableCoins, readSharePoolWindowUsage, readShareWindowUsage, recordShareWindowDelta, settleShareUsage, usageToCoins, } from "../../proxy/shareLedger.js";
import { accountsInGrantScope, filterAccountsForGrant, isModelAllowed, isWithinSchedule, shareRefusalStatus, summarizeAccountExclusions, } from "../../proxy/sharePolicy.js";
import { createStreamTerminalOutcomeTracker, mergeStreamTerminalOutcome, preflightAnthropicStream, } from "../../proxy/streamOutcome.js";
import { isPermanentRefreshFailure, needsRefresh, persistTokens, refreshToken, refreshTokenFromLatest, } from "../../proxy/tokenRefresh.js";
import { buildProxyTranslationPlan, parseRetryAfterMs, } from "../../proxy/routingPolicy.js";
import { normalizeMaxInflightPerAccount } from "../../proxy/modelRouter.js";
import { writeJsonSnapshotAtomically } from "../../proxy/snapshotPersistence.js";
import { getAccountStats, recordAttempt, recordAttemptError, recordFinalError, recordFinalSuccess, } from "../../proxy/usageStats.js";
import { sanitizeForLog } from "../../utils/logSanitize.js";
import { logger } from "../../utils/logger.js";
import { raceWithAbort, withTimeout } from "../../utils/async/withTimeout.js";
import { ProviderHealthChecker } from "../../utils/providerHealth.js";
import { handleCodexResponsesRequest } from "./codexProxyRoutes.js";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/** Headers that must never be forwarded upstream to Anthropic. */
const BLOCKED_UPSTREAM_HEADERS = new Set([
"cookie",
"proxy-authorization",
"host",
"connection",
"content-length",
"transfer-encoding",
]);
const PROXY_INTERNAL_ACCOUNT_LABEL = "proxy/internal";
const PROXY_INTERNAL_ACCOUNT_TYPE = "internal";
function resolveRequestLogAccountIdentity(accountLabel, accountType) {
if (!accountLabel) {
return {};
}
if (accountType === "codex-oauth") {
return {
accountKey: accountLabel.startsWith("codex:")
? accountLabel
: `codex:${accountLabel}`,
provider: "openai",
};
}
if (accountType === "oauth" ||
accountType === "api_key" ||
accountType === "passthrough") {
return {
accountKey: accountType === "passthrough"
? accountLabel
: normalizeAnthropicAccountKey(accountLabel),
provider: "anthropic",
};
}
if (accountType === "vertex") {
// A native fallback leg has no OAuth login — the account IS the served
// model, e.g. "vertex/claude-opus-4-6". Without this branch `provider`
// stays unset, `annotateRequestPricing` falls back to
// "openai-compatible", and that resolves to a cross-provider scan which
// prices the turn from the *requested* alias in the Anthropic table
// instead of the served model in the Vertex one — a 2.5x under-report on
// the only leg that bills in real currency.
return { accountKey: accountLabel, provider: "vertex" };
}
return {};
}
// ---------------------------------------------------------------------------
// Module-level state
// ---------------------------------------------------------------------------
/** Fill-first: index of the current primary account. Only advances when
* the current account exhausts 429 retries or auth retries fail. */
let primaryAccountIndex = 0;
/** Track account count so we can reset primaryAccountIndex when it changes. */
let lastKnownAccountCount = 0;
const MAX_AUTH_RETRIES = 5;
const MAX_TRANSIENT_SAME_ACCOUNT_RETRIES = 2;
const TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS = [250, 1_000];
/**
* Retry budget for failures that happened before any request byte was sent
* (a SYN lost on a lossy uplink). Nothing was dispatched, so each retry is
* free of duplicate-work risk; on a link losing one connect in five, four
* retries take the per-request failure rate from about 20% to under 1%.
* Delays follow TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS, clamped to its last
* entry.
*/
const MAX_CONNECT_PHASE_SAME_ACCOUNT_RETRIES = 4;
const OVERLOAD_ACCOUNT_ROTATION_DELAYS_MS = [250, 500, 1_000, 2_000];
const MAX_FALLBACK_NETWORK_RETRIES = 1;
const FALLBACK_STREAM_IDLE_TIMEOUT_MS = 2 * 60 * 1000;
/** Maximum upstream 429 attempts per account before rotating — for a TRANSIENT
* burst 429 only (window still "allowed", short retry-after). An exhaustion 429
* (5h/7d window "rejected") rotates immediately with zero same-account retries,
* because retrying is futile until the window resets. Kept small: retrying a
* rate-limited account more than a couple of times only burns the client's
* wall-clock and, under fill-first, delays reaching a healthy account. */
const MAX_RATE_LIMIT_SAME_ACCOUNT_RETRIES = 2;
/** Max time to sleep between transient 429 retries. Caps large upstream
* retry-after values so we don't hold the client connection open for minutes. */
const MAX_RATE_LIMIT_RETRY_DELAY_MS = 30_000;
/** Upper bound on any cooldown, as a sanity clamp against a garbage/huge reset
* epoch. Must exceed the 7-day weekly window so a genuine weekly-exhaustion
* cooldown is never truncated. */
const MAX_COOLDOWN_MS = 8 * 24 * 60 * 60 * 1000; // 8 days
/** Lower bound on any cooldown so we never busy-loop back onto a spent account. */
const MIN_COOLDOWN_MS = 5_000;
/** Cap for transient (per-minute burst) cooldowns — these recover quickly, so
* we should return to the account soon rather than parking it for the full
* reset window. */
const TRANSIENT_MAX_COOLDOWN_MS = 15 * 60 * 1000; // 15 minutes
/** Fallback for authoritative rejected states that do not provide a usable
* reset or retry-after. These are not transient burst limits. */
const DEFAULT_HARD_COOLDOWN_MS = 15 * 60 * 1000; // 15 minutes
const AUTH_REFRESH_BASE_COOLDOWN_MS = 30_000;
const AUTH_REFRESH_MAX_COOLDOWN_MS = 5 * 60 * 1000;
/** Timeout for upstream requests to Anthropic. Must be generous enough
* to cover the full lifecycle of streaming responses, including extended
* thinking from Opus models (which can exceed 5 minutes for large contexts). */
const UPSTREAM_FETCH_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
// Context checks and spending reservations belong immediately before dispatch.
import { prepareProxyRequestContext, ProxyContextPreflightError, } from "../../proxy/proxyContextPreflight.js";
import { reserveProxyTokenBudget, getProxyTokenBudgetError, getProxyTokenBudgetSessionKey, } from "../../proxy/proxyTokenBudget.js";
import { observeAnthropicBudgetResponse } from "../../proxy/anthropicBudgetResponse.js";
import { readCacheCreation1hTokens, oneHourCacheWriteFields, } from "../../proxy/proxyTokenUsage.js";
function prepareAnthropicWire(ctx, bodyStr) {
const body = JSON.parse(bodyStr);
const prepared = prepareProxyRequestContext({
provider: "anthropic",
model: body.model,
body,
});
ctx.metadata.contextPreflight = prepared.evidence;
return {
bodyStr: prepared.body === body ? bodyStr : JSON.stringify(prepared.body),
preparedContext: prepared,
};
}
let anthropicUpstreamDispatcher;
let anthropicUpstreamDispatcherPromise;
/**
* Lazily construct (once) and cache the upstream Agent, reused across every
* request rather than rebuilt per call.
*
* `undici` is imported dynamically here — rather than as a static top-level
* import — so that requiring this module (and transitively, the package
* entry that re-exports `createClaudeProxyRoutes`) does not force-load undici
* until an upstream Anthropic request actually dispatches. The construction
* itself is guarded by a cached promise, not a bare `??=` on the Agent, so
* concurrent first callers await the same in-flight construction instead of
* each racing past the `undefined` check and building a duplicate Agent. A
* rejected construction (a bad undici install, an Agent constructor throwing)
* clears the cached promise rather than leaving it permanently rejected, so
* the next request gets a fresh attempt instead of every later request
* failing forever on a cached failure. See
* test/continuous-test-suite-import-cost.ts.
*/
async function getAnthropicUpstreamDispatcher() {
if (anthropicUpstreamDispatcher) {
return anthropicUpstreamDispatcher;
}
anthropicUpstreamDispatcherPromise ??= (async () => {
try {
const { Agent: UndiciAgent } = await import("undici");
// Node's global fetch applies Undici's 300s default headers timeout
// before the route's 15-minute abort signal. Keep both transport
// deadlines aligned with the proxy contract.
const agent = new UndiciAgent({
headersTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
bodyTimeout: UPSTREAM_FETCH_TIMEOUT_MS,
});
anthropicUpstreamDispatcher = agent;
return agent;
}
catch (error) {
// Let a later request try again instead of every subsequent request
// failing forever against a cached rejection.
anthropicUpstreamDispatcherPromise = undefined;
throw error;
}
})();
return anthropicUpstreamDispatcherPromise;
}
async function fetchAnthropicUpstream(url, init, ctx, accountKey, dispatch) {
clearClaudeFallbackTerminalEvidence(ctx);
const prepared = dispatch.preparedContext;
const lease = await reserveProxyTokenBudget({
provider: "anthropic",
accountKey,
requestId: ctx.requestId,
sessionKey: getProxyTokenBudgetSessionKey({
get: (name) => ctx.headers[name] ?? null,
}),
reservationTokens: prepared.totalTokensReservation,
estimateProvenance: "estimated_input_plus_output_reserve",
});
ctx.metadata.tokenBudget = lease.snapshot;
if (init.signal?.aborted || ctx.abortSignal?.aborted) {
await lease.cancelBeforeDispatch();
ctx.metadata.tokenBudget = lease.snapshot;
throw init.signal?.reason ?? ctx.abortSignal?.reason;
}
try {
const upstreamDispatcher = await getAnthropicUpstreamDispatcher();
const dispatchedBody = String(init.body);
recordAttempt(dispatch.accountLabel, dispatch.accountType);
dispatch.tracer?.logUpstreamRequestHeaders(init.headers);
dispatch.tracer?.logUpstreamRequestBody(dispatchedBody);
dispatch.logProxyBody({
phase: "upstream_request",
headers: init.headers,
body: dispatchedBody,
bodySize: Buffer.byteLength(dispatchedBody, "utf8"),
contentType: "application/json",
account: dispatch.accountLabel,
accountType: dispatch.accountType,
attempt: dispatch.attemptNumber,
metadata: { upstreamMethod: "POST", upstreamUrl: url },
});
const response = await fetch(url, {
...init,
body: dispatchedBody,
dispatcher: upstreamDispatcher,
});
return observeAnthropicBudgetResponse(response, lease, () => {
ctx.metadata.tokenBudget = lease.snapshot;
}, init.signal ?? undefined);
}
catch (error) {
await lease.settle().catch(() => undefined);
ctx.metadata.tokenBudget = lease.snapshot;
throw error;
}
}
const accountRuntimeState = new Map();
const accountQuotaRefreshCoordinator = new AccountQuotaRefreshCoordinator();
const providerTransportCoordinator = new ProviderTransportCoordinator();
/** Snapshot age is diagnostic and controls lightweight refresh eligibility. It
* must never discard known healthy routing evidence or trigger a user request
* on another account solely to rediscover quota. */
const QUOTA_SNAPSHOT_FRESHNESS_MS = 15 * 60 * 1000;
/** Shared across requests so a concurrent burst gets at most two retries for
* the account/window, rather than every request starting its own retry chain. */
const transientRateLimitRetryBudgets = new Map();
/** Pace requests that arrive while the last usable account has a short
* transient cooldown. This converts a local 429/retry storm into a bounded
* queue and avoids releasing every waiting request at the same millisecond. */
const transientCooldownAdmissionSchedules = new Map();
/**
* Central per-account admission protects an OAuth account from a concurrent
* request herd. A lease stays held until a JSON response completes or an SSE
* response reaches a terminal state, so starting a stream cannot immediately
* make room for another stream on the same account.
*/
const accountAdmissionStates = new Map();
function getAccountAdmissionState(accountKey) {
let state = accountAdmissionStates.get(accountKey);
if (!state) {
state = { active: 0, waiters: [] };
accountAdmissionStates.set(accountKey, state);
}
return state;
}
function drainAccountAdmissionWaiters(accountKey, state) {
while (state.waiters.length > 0 && state.active < state.waiters[0].capacity) {
const waiter = state.waiters.shift();
if (!waiter) {
return;
}
state.active += 1;
waiter.resolve(createAccountAdmissionLease(accountKey, state));
}
}
/**
* Admits, in FIFO order and ignoring the capacity each one queued with, every
* waiter that queued under an older runtime-config generation than the
* uncapped request now acquiring. Each request reads the cap from the snapshot
* it arrived with, so only a NEWER uncapped snapshot shows that a reload
* removed the waiter's cap — and uncapped arrivals count toward `active`, so
* that waiter would otherwise never drain. An uncapped request from an older
* snapshot (one that predates a reload adding the cap) or from the same one
* leaves waiters queued: their cap is still current. With no generation,
* which only direct callers such as test hooks pass (the route passes 0 from
* its fallback snapshot when no runtime config store is attached), there is
* nothing to compare against and nothing is admitted.
*/
function admitWaitersOfRemovedCap(accountKey, state, generation) {
if (generation === undefined) {
return;
}
const queuedUnderOlderSnapshot = (waiter) => waiter.generation !== undefined && waiter.generation < generation;
const admitted = state.waiters.filter(queuedUnderOlderSnapshot);
if (admitted.length === 0) {
return;
}
state.waiters = state.waiters.filter((waiter) => !queuedUnderOlderSnapshot(waiter));
for (const waiter of admitted) {
state.active += 1;
waiter.resolve(createAccountAdmissionLease(accountKey, state));
}
}
function createAccountAdmissionLease(accountKey, state) {
let released = false;
return {
release: () => {
if (released) {
return;
}
released = true;
state.active = Math.max(0, state.active - 1);
drainAccountAdmissionWaiters(accountKey, state);
discardAccountAdmissionState(accountKey, state);
},
};
}
function discardAccountAdmissionState(accountKey, state) {
if (state.active === 0 && state.waiters.length === 0) {
accountAdmissionStates.delete(accountKey);
}
}
function tryAcquireAccountAdmission(accountKey, capacity, generation) {
const normalizedCapacity = normalizeMaxInflightPerAccount(capacity);
const state = getAccountAdmissionState(accountKey);
if (normalizedCapacity === undefined) {
admitWaitersOfRemovedCap(accountKey, state, generation);
state.active += 1;
return createAccountAdmissionLease(accountKey, state);
}
if (state.waiters.length > 0 || state.active >= normalizedCapacity) {
return undefined;
}
state.active += 1;
return createAccountAdmissionLease(accountKey, state);
}
/** Current in-flight request count for one account, unlimited or capped. */
function getAccountInflight(accountKey) {
return accountAdmissionStates.get(accountKey)?.active ?? 0;
}
function isAccountAdmissionAvailable(accountKey, capacity) {
const normalizedCapacity = normalizeMaxInflightPerAccount(capacity);
if (normalizedCapacity === undefined) {
return true;
}
const state = accountAdmissionStates.get(accountKey);
return (!state || (state.waiters.length === 0 && state.active < normalizedCapacity));
}
function enqueueAccountAdmission(accountKey, capacity, generation) {
// Validate BEFORE getAccountAdmissionState(), which inserts into the map as a
// side effect. Throwing after it would strand an empty entry for an account
// that never got admitted — and the throw path never calls
// discardAccountAdmissionState() to reap it.
const normalizedCapacity = normalizeMaxInflightPerAccount(capacity);
if (normalizedCapacity === undefined) {
throw new Error("Account admission queue requires an explicit capacity");
}
const state = getAccountAdmissionState(accountKey);
let queued = true;
let grantedLease;
let resolveAdmission;
const promise = new Promise((resolve) => {
resolveAdmission = resolve;
});
const waiter = {
capacity: normalizedCapacity,
generation,
resolve: (lease) => {
queued = false;
grantedLease = lease;
resolveAdmission(lease);
},
};
state.waiters.push(waiter);
drainAccountAdmissionWaiters(accountKey, state);
return {
accountKey,
promise,
cancel: () => {
if (grantedLease) {
const lease = grantedLease;
grantedLease = undefined;
lease.release();
return;
}
if (!queued) {
return;
}
queued = false;
const index = state.waiters.indexOf(waiter);
if (index >= 0) {
state.waiters.splice(index, 1);
drainAccountAdmissionWaiters(accountKey, state);
}
discardAccountAdmissionState(accountKey, state);
},
};
}
async function acquireAccountAdmission(accountKey, capacity, abortSignal, timeoutMs = MAX_TRANSIENT_QUEUE_WAIT_MS, generation) {
const queued = enqueueAccountAdmission(accountKey, capacity, generation);
try {
return await withTimeout(raceWithAbort(queued.promise, abortSignal), timeoutMs, `Account admission for ${accountKey} timed out after ${timeoutMs}ms`);
}
catch (error) {
queued.cancel();
if (abortSignal?.aborted) {
throw error;
}
return undefined;
}
}
async function acquireFirstAvailableAccountAdmission(accountKeys, capacity, abortSignal, timeoutMs = MAX_TRANSIENT_QUEUE_WAIT_MS, generation) {
const queuedAdmissions = [...new Set(accountKeys)].map((accountKey) => enqueueAccountAdmission(accountKey, capacity, generation));
let winnerKey;
try {
return await withTimeout(raceWithAbort(Promise.race(queuedAdmissions.map((queued) => queued.promise.then((lease) => {
if (winnerKey) {
lease.release();
return new Promise(() => undefined);
}
winnerKey = queued.accountKey;
return { accountKey: queued.accountKey, lease };
}))), abortSignal), timeoutMs, `Account admission timed out after ${timeoutMs}ms`);
}
catch (error) {
if (abortSignal?.aborted) {
throw error;
}
return undefined;
}
finally {
for (const queued of queuedAdmissions) {
if (queued.accountKey !== winnerKey) {
queued.cancel();
}
}
}
}
/** Track whether we've run the one-time startup prune. */
let startupPruneDone = false;
let startupPrune;
/** Default cooling period when retries are exhausted and upstream didn't
* provide a retry-after header. Short enough to recover quickly, long
* enough to avoid immediately hammering the same account. */
const DEFAULT_COOLING_PERIOD_MS = 60_000;
const MAX_TRANSIENT_QUEUE_WAIT_MS = 90_000;
const MAX_TRANSIENT_TOTAL_QUEUE_WAIT_MS = 120_000;
const TRANSIENT_ADMISSION_SPACING_MS = 250;
const MAX_TRANSIENT_ADMISSION_SPREAD_MS = 15_000;
/** Advance the primary account index when the current primary is exhausted
* (429 retries exhausted or auth failure). This is what makes fill-first work:
* we stick to one account until it's unusable. Only advances when the exhausted
* account IS the current primary; otherwise it's already a fallback. */
function advancePrimaryIfCurrent(accountKey, enabledCount, primaryAccountKey) {
if (enabledCount <= 1) {
return;
}
// Only advance if the cooled account is the current primary
if (accountKey !== primaryAccountKey) {
return;
}
primaryAccountIndex = (primaryAccountIndex + 1) % enabledCount;
}
/** Resolve the configured primary's stable key to its current index in the
* request's enabledAccounts list. Returns 0 (insertion-order fallback) when
* no key is configured or the key cannot be matched (account disabled/
* removed). The resolution is per-request because enabledAccounts membership
* can shift between requests. */
function resolveHomeIndex(enabledAccounts, primaryAccountKey) {
if (!primaryAccountKey) {
return 0;
}
const idx = enabledAccounts.findIndex((a) => anthropicAccountKeysEqual(a.key, primaryAccountKey));
return idx >= 0 ? idx : 0;
}
/** If the configured home primary is no longer cooling, reset
* primaryAccountIndex back to its index so traffic returns to the preferred
* account once its rate limit window expires. Called at the start of each
* request. Home is resolved fresh per call via resolveHomeIndex. */
function maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey) {
if (enabledAccounts.length <= 1) {
return;
}
const homeIndex = resolveHomeIndex(enabledAccounts, primaryAccountKey);
if (primaryAccountIndex === homeIndex) {
return;
}
const homeAccount = enabledAccounts[homeIndex];
const homeState = accountRuntimeState.get(homeAccount.key);
if (!homeState ||
!homeState.coolingUntil ||
Date.now() >= homeState.coolingUntil) {
// Home account is no longer cooling — reset to it
primaryAccountIndex = homeIndex;
if (homeState?.coolingUntil) {
const expiredCooldown = homeState.coolingUntil;
homeState.coolingUntil = undefined;
homeState.coolingReason = undefined;
clearAccountCooldown(homeAccount.key, expiredCooldown).catch(() => {
// Best-effort cleanup; an expired entry is ignored on the next boot.
});
logger.always(`[proxy] home primary account=${homeAccount.label} cooling expired, resetting primaryAccountIndex to ${homeIndex}`);
}
}
}
/** Check if an account is currently in its cooling window. */
function isAccountCooling(accountKey) {
const state = accountRuntimeState.get(accountKey);
return !!state?.coolingUntil && Date.now() < state.coolingUntil;
}
function claimTransientRateLimitRetry(accountKey, coolingUntil, now = Date.now()) {
let budget = transientRateLimitRetryBudgets.get(accountKey);
if (!budget || now >= budget.coolingUntil) {
budget = { coolingUntil, retriesClaimed: 0 };
transientRateLimitRetryBudgets.set(accountKey, budget);
}
else if (coolingUntil > budget.coolingUntil) {
budget.coolingUntil = coolingUntil;
}
if (budget.retriesClaimed >= MAX_RATE_LIMIT_SAME_ACCOUNT_RETRIES) {
return undefined;
}
budget.retriesClaimed += 1;
return budget.retriesClaimed;
}
/**
* Claims one same-account retry after a transient rate limit and returns its
* delay, or undefined once this request's retries or the account's shared
* budget are spent. Both the HTTP 429 and the in-stream rate-limit paths use
* it, so a burst is paced the same way however it arrives.
*/
function claimTransientRateLimitRetryDelay(accountKey, coolingUntil, retryAfterMs, retriesThisRequest) {
const slot = claimTransientRateLimitRetry(accountKey, coolingUntil);
if (retryAfterMs === undefined ||
retriesThisRequest >= MAX_RATE_LIMIT_SAME_ACCOUNT_RETRIES ||
slot === undefined) {
return undefined;
}
const base = Math.min(retryAfterMs || 1_000, MAX_RATE_LIMIT_RETRY_DELAY_MS);
// Stagger the two shared slots, then cap after jitter so the final sleep
// never exceeds the configured maximum.
return {
slot,
delayMs: Math.min(MAX_RATE_LIMIT_RETRY_DELAY_MS, jitteredDelay(base * slot)),
};
}
/**
* A same-account retry that was served shows the burst is over. Without this
* the burst cooldown outlives it, the session's next request goes to another
* account and loses its prompt cache — the A→B→A bounce this retry exists to
* prevent. Only a transient cooldown is lifted: an exhaustion cooldown that a
* concurrent request recorded meanwhile stays.
*/
function liftTransientCooldownAfterServedRetry(account, state) {
transientRateLimitRetryBudgets.delete(account.key);
if (state.coolingReason !== "transient" || !state.coolingUntil) {
return;
}
const previousCoolingUntil = state.coolingUntil;
state.coolingUntil = undefined;
state.coolingReason = undefined;
clearAccountCooldown(account.key, previousCoolingUntil).catch(() => {
// Best-effort: routing already reads the cleared in-memory state.
});
}
function claimTransientCooldownAdmission(accountKey, coolingUntil, now = Date.now()) {
if (coolingUntil <= now) {
return 0;
}
if (coolingUntil - now > MAX_TRANSIENT_QUEUE_WAIT_MS) {
return undefined;
}
let schedule = transientCooldownAdmissionSchedules.get(accountKey);
if (!schedule || now >= schedule.coolingUntil) {
schedule = { coolingUntil, nextAdmissionAt: coolingUntil };
transientCooldownAdmissionSchedules.set(accountKey, schedule);
}
else if (coolingUntil > schedule.coolingUntil) {
schedule.coolingUntil = coolingUntil;
schedule.nextAdmissionAt = Math.max(schedule.nextAdmissionAt, coolingUntil);
}
const admissionAt = Math.max(now, schedule.coolingUntil, schedule.nextAdmissionAt);
if (admissionAt - schedule.coolingUntil > MAX_TRANSIENT_ADMISSION_SPREAD_MS) {
return undefined;
}
schedule.nextAdmissionAt = admissionAt + TRANSIENT_ADMISSION_SPACING_MS;
return admissionAt - now;
}
async function waitForTransientAccountAvailability(orderedAccounts) {
const deadline = Date.now() + MAX_TRANSIENT_TOTAL_QUEUE_WAIT_MS;
while (Date.now() < deadline) {
const available = orderedAccounts.filter((account) => !isAccountCooling(account.key));
if (available.length > 0) {
return available;
}
const now = Date.now();
const transientCandidate = orderedAccounts
.map((account) => ({
account,
state: getOrCreateRuntimeState(account.key),
}))
.filter(({ state }) => state.coolingReason === "transient" &&
state.coolingUntil !== undefined &&
state.coolingUntil > now)
.sort((a, b) => (a.state.coolingUntil ?? Number.POSITIVE_INFINITY) -
(b.state.coolingUntil ?? Number.POSITIVE_INFINITY))[0];
if (!transientCandidate?.state.coolingUntil) {
return [];
}
const waitMs = claimTransientCooldownAdmission(transientCandidate.account.key, transientCandidate.state.coolingUntil, now);
if (waitMs === undefined || now + waitMs > deadline) {
return [];
}
logger.always(`[proxy] all usable accounts are transiently cooling; queueing request for account=${transientCandidate.account.label} in ${waitMs}ms`);
await sleep(waitMs);
}
return [];
}
// ---------------------------------------------------------------------------
// Quota-aware cooldown helpers
// ---------------------------------------------------------------------------
/** Convert an Anthropic unified-window reset (Unix epoch SECONDS, per the
* `anthropic-ratelimit-unified-*-reset` headers) into epoch-ms. Tolerates a
* value already expressed in ms (some intermediaries normalise it). Returns
* undefined for absent/zero/past-or-garbage timestamps so callers can fall
* back to retry-after. */
function resetEpochToMs(resetEpoch, now) {
if (!resetEpoch || resetEpoch <= 0) {
return undefined;
}
// Heuristic: a value beyond ~year 2100 in seconds (4102444800) is already ms.
const ms = resetEpoch > 4_102_444_800 ? resetEpoch : resetEpoch * 1000;
return ms > now ? ms : undefined;
}
/**
* Publish limit/quota headers for this response onto the request context.
*
* Every response path funnels through here so the header contract is defined
* once. The proxy runtime copies `ctx.responseHeaders` onto JSON and error
* responses, and merges them into streaming Responses for keys those don't
* already set — so a single call covers both shapes.
*/
function publishLimitHeaders(ctx, args) {
try {
const pool = args.poolAccounts
? summarizePoolHeadroom(args.poolAccounts.map((account) => {
const state = accountRuntimeState.get(account.key);
return {
...(state?.coolingUntil !== undefined
? { coolingUntil: state.coolingUntil }
: {}),
...(state?.quota ? { quota: state.quota } : {}),
};
}))
: undefined;
const headers = buildProxyLimitHeaders({
...(args.upstreamHeaders
? { upstreamHeaders: args.upstreamHeaders }
: {}),
context: {
quota: args.quota ?? null,
source: args.source,
...(args.account ? { accountLabel: args.account.label } : {}),
...((args.accountType ?? args.account?.type)
? { accountType: args.accountType ?? args.account?.type }
: {}),
...(args.servedBy ? { servedBy: args.servedBy } : {}),
...(args.attempt !== undefined ? { attempt: args.attempt } : {}),
...(args.accountState?.coolingUntil !== undefined
? { coolingUntil: args.accountState.coolingUntil }
: {}),
...(args.accountState?.coolingReason
? { coolingReason: args.accountState.coolingReason }
: {}),
...(pool ? { pool } : {}),
},
});
ctx.responseHeaders = {
...(ctx.responseHeaders ?? {}),
...redactHeadersForBorrower(headers),
};
}
catch (error) {
// Diagnostics must never break a response that is otherwise fine.
logger.debug(`[proxy] failed to publish limit headers: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Clamp a cooldown target epoch-ms into [now+MIN, now+MAX], additionally capped
* by what the reason can plausibly mean — a "session" cooldown describes a
* 5-hour window, so a stale or malformed reset must not be able to park the
* account for days under that label.
*/
function clampCooldownUntil(untilMs, now, reason) {
const ceiling = Math.min(MAX_COOLDOWN_MS, (reason && MAX_COOLDOWN_MS_BY_REASON[reason]) ?? MAX_COOLDOWN_MS);
return Math.min(Math.max(untilMs, now + MIN_COOLDOWN_MS), now + ceiling);
}
function isAllowedQuotaStatus(status) {
return status?.trim().toLowerCase() === "allowed";
}
function isScopedWindowExhausted(window, now) {
if (!window || resetEpochToMs(window.resetsAt, now) === undefined) {
return false;
}
return (window.status?.trim().toLowerCase() === "rejected" ||
(window.used ?? 0) >= 1);
}
/**
* Anthropic represents some model-specific limits through a rejected top-level
* unified status. The scope window is the authoritative discriminator: only
* treat that response as model-scoped when both account-wide windows remain
* explicitly allowed and the requested model's window is exhausted.
*/
function getScopedOnlyExhaustion(quota, requestedModel, now, policy = overagePolicy) {
if (!quota ||
!requestedModel ||
!isAllowedQuotaStatus(quota.sessionStatus) ||
!isAllowedQuotaStatus(quota.weeklyStatus) ||
isOverageUsable(quota, policy)) {
return null;
}
const scopedWindow = matchScopedQuotaWindow(quota, requestedModel, now);
return isScopedWindowExhausted(scopedWindow, now) ? scopedWindow : null;
}
function hasScopedOnlyExhaustion(quota, now, policy = overagePolicy) {
if (!isAllowedQuotaStatus(quota.sessionStatus) ||
!isAllowedQuotaStatus(quota.weeklyStatus) ||
isOverageUsable(quota, policy)) {
return false;
}
// Same notion of "scoped" as the request path: header windows (which carry
// both a display scope and the wire id) and usage-API windows (often only
// one of them) must classify alike, or a 429 is scoped when routed and
// account-wide when reconciled.
return (quota.windows ?? []).some((window) => (window.kind === "weekly_scoped" || window.kind === "session_scoped") &&
(typeof window.scopeModel === "string" ||
typeof window.scopeModelId === "string") &&
now - scopedWindowObservedAt(quota, window) <=
QUOTA_SNAPSHOT_FRESHNESS_MS &&
isScopedWindowExhausted(window, now));
}
/**
* Decide how to cool an account after a genuine (non-anti-abuse) 429.
*
* The unified subscription limits expose per-window status + reset:
* - weekly (7d) "rejected" → hard cap for the week; cool until the 7d reset.
* - session (5h) "rejected" → paced out for this session; cool until the 5h reset.
* Both mean "retrying this account is futile until its window resets" unless
* the provider explicitly enables overage. In that case, the subscription
* window is exhausted but the account remains usable for paid fallback.
*
* Anything else (window still "allowed" but momentarily 429'd — a per-minute
* burst / acceleration limit) is transient: honor retry-after as a floor,
* allow a couple of jittered same-account retries, then a short cooldown.
*/
function planCooldownFor429(quota, retryAfterMs, now, unifiedStatus = quota?.unifiedStatus, policy = overagePolicy, requestedModel) {
// Weekly exhaustion takes precedence — it's the longest, hardest ceiling.
if (quota && quota.weeklyStatus === "rejected") {
const reset = resetEpochToMs(quota.weeklyResetAt, now) ??
(retryAfterMs > 0 ? now + retryAfterMs : now + DEFAULT_COOLING_PERIOD_MS);
return {
reason: "weekly",
scope: "account",
coolingUntil: clampCooldownUntil(reset, now, "weekly"),
rotateImmediately: true,
};
}
const overageAvailable = isOverageUsable(quota, policy);
if (quota && quota.sessionStatus === "rejected" && !overageAvailable) {
const reset = resetEpochToMs(quota.sessionResetAt, now) ??
(retryAfterMs > 0 ? now + retryAfterMs : now + DEFAULT_COOLING_PERIOD_MS);
return {
reason: "session",
scope: "account",
coolingUntil: clampCooldownUntil(reset, now, "session"),
rotateImmediately: true,
};
}
const scopedOnlyExhaustion = getScopedOnlyExhaustion(quota, requestedModel, now, policy);
if (scopedOnlyExhaustion) {
const reset = resetEpochToMs(scopedOnlyExhaustion.resetsAt, now) ??
(retryAfterMs > 0 ? now + retryAfterMs : now + DEFAULT_HARD_COOLDOWN_MS);
return {
// Keep the provider's top-level classification for logs, but do not
// persist it as an account cooldown. The scoped quota window gates only
// this model on subsequent requests.
reason: "unified",
scope: "model",
coolingUntil: reset,
rotateImmediately: true,
};
}
// Anthropic may reject the authoritative top-level unified limit while both
// 5h and 7d sub-window statuses still say "allowed". Treating this as a
// transient burst retries a known-exhausted account and delays failover.
if (unifiedStatus?.trim().toLowerCase() === "rejected" && !overageAvailable) {
const reset = retryAfterMs > 0 ? now + retryAfterMs : now + DEFAULT_HARD_COOLDOWN_MS;
return {
reason: "unified",
scope: "account",
coolingUntil: clampCooldownUntil(reset, now, "unified"),
rotateImmediately: true,
};
}
// Transient burst: cool only for retry-after, floored at MIN_COOLDOWN_MS so a
// tiny retry-after can't make the account eligible almost immediately, and
// capped so it recovers quickly.
const base = retryAfterMs > 0 ? retryAfterMs : DEFAULT_COOLING_PERIOD_MS;
return {
reason: "transient",
scope: "account",
coolingUntil: now +
Math.max(MIN_COOLDOWN_MS, Math.min(base, TRANSIENT_MAX_COOLDOWN_MS)),
rotateImmediately: false,
};
}
/** Human-readable minutes-until for cooldown log lines. */
function minutesUntil(untilMs, now) {
return Math.max(0, Math.round((untilMs - now) / 60000));
}
/**
* Reconcile quota-backed cooldowns against a fresh provider observation. A
* rejected window parks the account until its reset; an allowed observation for
* that same window releases an earlier cooldown whose reset timestamp was
* stale. Transient and authentication cooldowns are intentionally untouched.
*/
function reconcileCooldownFromQuota(state, quota, now, policy = overagePolicy) {
const overageAvailable = isOverageUsable(quota, policy);
const scopedOnlyExhaustion = hasScopedOnlyExhaustion(quota, now, policy);
let until;
let reason;
if (quota.weeklyStatus === "rejected") {
until = resetEpochToMs(quota.weeklyResetAt, now);
reason = "weekly";
}
if (until === undefined &&
overageAvailable &&
state.coolingUntil &&
(state.coolingReason === "session" || state.coolingReason === "unified")) {
const previousCoolingUntil = state.coolingUntil;
state.coolingUntil = undefined;
state.coolingReason = undefined;
logger.always("[proxy] clearing subscription cooldown because Anthropic explicitly permits overage");
return { kind: "cleared", coolingUntil: previousCoolingUntil };
}
if (until === undefined &&
quota.sessionStatus === "rejected" &&
!overageAvailable) {
until = resetEpochToMs(quota.sessionResetAt, now);
reason = "session";
}
else if (until === undefined &&
quota.unifiedStatus === "rejected" &&
!overageAvailable &&
!scopedOnlyExhaustion) {
until = now + DEFAULT_HARD_COOLDOWN_MS;
reason = "unified";
}
if (until === undefined) {
const recoveredQuotaCooldown = !!state.coolingUntil &&
((state.coolingReason === "weekly" && quota.weeklyStatus === "allowed") ||
(state.coolingReason === "session" &&
quota.sessionStatus === "allowed") ||
(state.coolingReason === "unified" &&
(quota.unifiedStatus === "allowed" || scopedOnlyExhaustion)));
if (recoveredQuotaCooldown && state.coolingUntil) {
const previousCoolingUntil = state.coolingUntil;
const previousCoolingReason = state.coolingReason;
state.coolingUntil = undefined;
state.coolingReason = undefined;
logger.always(`[proxy] clearing ${previousCoolingReason} cooldown from fresh provider quota`);
return { kind: "cleared", coolingUntil: previousCoolingUntil };
}
return null;
}
const clamped = clampCooldownUntil(until, now, reason);
if (!state.coolingUntil || clamped > state.coolingUntil) {
state.coolingUntil = clamped;
state.coolingReason = reason;
logger.always(`[proxy] proactively cooling account (${reason}) ~${minutesUntil(clamped, now)}m from success-response quota (status rejected)`);
return {
kind: "cooled",
coolingUntil: clamped,
coolingReason: reason ?? "unified",
};
}
return null;
}
/**
* Seed each account's runtime quota from the persisted snapshots in
* ~/.neurolink/account-quotas.json (keyed by provider-qualified account key).
* Runtime state is
* in-memory only, so without this the quota-aware ordering is blind after a
* proxy restart: all accounts tie, selection falls back to token-store
* enumeration order, and the first account served becomes self-reinforcing
* (it alone has data) — starving the others regardless of their resets.
* Never overwrites fresher in-memory quota. Persisted quota cannot create or
* clear a cooldown: only an existing cooldown or fresh upstream response
* headers can change admission state.
*/
async function seedRuntimeQuotasFromDisk(accounts) {
try {
const [persistedQuotas, persistedCooldowns] = await Promise.all([
loadAccountQuotas(),
loadAccountCooldowns(),
]);
const now = Date.now();
for (const account of accounts) {
const state = getOrCreateRuntimeState(account.key);
if (!state.quota) {
// Before provider identity was introduced, Anthropic snapshots were
// written under the bare display label. Keep that reading as an
// upgrade bridge, but never write new data back under the ambiguous
// key: a Codex login can legitimately use the same email.
state.quota =
persistedQuotas[account.key] ?? persistedQuotas[account.label];
}
const persistedCooldown = persistedCooldowns[account.key];
if (persistedCooldown?.coolingUntil > now &&
(!state.coolingUntil ||
persistedCooldown.coolingUntil > state.coolingUntil)) {
state.coolingUntil = persistedCooldown.coolingUntil;
state.coolingReason = persistedCooldown.reason;
}
}
}
catch {
// Non-fatal: seeding is best-effort; ordering falls back to probe-first.
}
}
// ---------------------------------------------------------------------------
// Manual limits refresh (GET /limits)
// ---------------------------------------------------------------------------
/** Minimum spacing between usage-endpoint fetches for one account. Bounds
* abuse of the ungated endpoint; inside the window the last reading is
* returned as "throttled" (still fresher than any passive snapshot). */
const MIN_USAGE_REFETCH_INTERVAL_MS = 15_000;
const lastUsageFetchAt = new Map();
let limitsRefreshInFlight = null;
const USAGE_REFRESH_CONCURRENCY = 4;
async function applyAccountUsageResult(account, fetchResult, observedAt, prior) {
if (fetchResult.ok === false) {
return null;
}
const state = getOrCreateRuntimeState(account.key);
const quota = usageToQuota(fetchResult.usage, {
now: observedAt,
prior: state.quota ?? prior ?? null,
});
if (!quota) {
return null;
}
// The usage payload cannot be newer than the request that fetched it. A
// passive response captured after that request started wins, including its
// cooldown decision and persisted snapshot.
if (quota.lastUpdated < (state.quota?.lastUpdated ?? 0)) {
return state.quota ?? null;
}
state.quota = mergeQuotaSnapshot(state.quota, quota);
const cooldownUpdate = reconcileCooldownFromQuota(state, quota, Date.now());
if (cooldownUpdate?.kind === "cooled") {
await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
// Non-fatal: the cooldown is already active in memory.
});
}
else if (cooldownUpdate?.kind === "cleared") {
await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
// Non-fatal: the next successful response will reconcile again.
});
}
await saveAccountQuota(account.key, quota).catch(() => {
// Non-fatal: quota persistence is best-effort.
});
return quota;
}
async function fetchValidatedAccountUsage(account) {
const result = await fetchAccountUsage(account);
if (result.ok === false) {
return result;
}
const recognizable = usageToQuota(result.usage, {
now: Date.now(),
prior: accountRuntimeState.get(account.key)?.quota ?? null,
});
return recognizable
? result
: {
ok: false,
reason: "parse",
error: "usage payload had no recognizable limit windows",
};
}
async function refreshAccountQuotaInBackground(account, trigger) {
const refresh = await accountQuotaRefreshCoordinator.run(account, trigger, fetchValidatedAccountUsage);
if (refresh.kind !== "completed" || refresh.result.ok === false) {
return;
}
await applyAccountUsageResult(account, refresh.result, refresh.startedAt);
}
/**
* The logins the account-exposing routes enumerate, per engine, plus every
* key the token store holds at all (disabled ones included). Overridable by
* the suite because the token store is a singleton bound to the real home at
* import — a case cannot point it elsewhere, and reading the operator's own
* logins inside a fixture test is exactly how a phantom would hide.
*/
let accountDirectoryOverride = null;
async function listRoutableAccountsByEngine(allowlist) {
if (accountDirectoryOverride) {
return {
anthropic: accountDirectoryOverride.anthropic,
codex: accountDirectoryOverride.codex,
};
}
// The allowlist is an Anthropic routing concept, keyed by anthropic:
// prefixes; applying it to Codex keys would exclude every Codex login.
const [anthropic, codex] = await Promise.all([
listAnthropicAccountsForUsage(allowlist),
listCodexAccountsForUsage(),
]);
return { anthropic, codex };
}
/**
* Every login the token store knows, routable or not. This is what separates
* a DISABLED login (still here, shown as unrouted) from a REMOVED one (gone,
* and not shown): usage counters and quota snapshots outlive a logout, and
* without this check a deleted login renders as an unrouted account forever.
*/
async function listKnownAccountKeys() {
if (accountDirectoryOverride) {
return accountDirectoryOverride.knownKeys;
}
const [anthropic, codex] = await Promise.all([
tokenStore.listByPrefix("anthropic:"),
tokenStore.listByPrefix("codex:"),
]);
return new Set([...anthropic.map(normalizeAnthropicAccountKey), ...codex]);
}
/**
* Fetch fresh limits from Anthropic's usage endpoint for every eligible OAuth
* account and write them through the exact same chain the passive header
* capture uses (runtime state → cooldown reconciliation → debounced disk
* snapshot), so routing and `auth list` see the refreshed windows and the
* automatic path keeps working unchanged on top of them.
*/
async function refreshAccountLimits(options = {}) {
const fetchedAt = Date.now();
const directory = await listRoutableAccountsByEngine(options.accountAllowlist);
const allAccounts = [
...directory.anthropic.map((account) => ({
account,
provider: "anthropic",
})),
...directory.codex.map((account) => ({
account,
provider: "codex",
})),
];
const accounts = options.accountFilter
? allAccounts.filter(({ account }) => account.label === options.accountFilter ||
account.key === options.accountFilter)
: allAccounts;
const persisted = await loadAccountQuotas().catch(() => ({}));
const buildResult = (account, provider, status, quota, error) => {
const state = accountRuntimeState.get(account.key);
// The quota store keys Anthropic snapshots by bare label for historical
// reasons (see CLAUDE.md) and Codex snapshots by full key. A Codex login
// must never fall back to the bare label: with one email on both engines
// that label holds the ANTHROPIC account's windows.
const persistedQuota = provider === "anthropic"
? (persisted[account.key] ?? persisted[account.label] ?? null)
: (persisted[account.key] ?? null);
const result = {
account: account.label,
key: account.key,
provider,
type: account.type,
status,
quota: quota ?? state?.quota ?? persistedQuota,
};
if (error !== undefined) {
result.error = error;
}
if (state?.coolingUntil && state.coolingUntil > Date.now()) {
result.coolingUntil = state.coolingUntil;
if (state.coolingReason) {
result.coolingReason = state.coolingReason;
}
}
return result;
};
if (options.snapshotOnly) {
return {
fetchedAt,
snapshot: true,
results: accounts.map(({ account, provider }) => buildResult(account, provider, "snapshot", null)),
refreshMetrics: accountQuotaRefreshCoordinator.getMetrics(),
};
}
const results = new Array(accounts.length);
let nextIndex = 0;
const worker = async () => {
for (;;) {
const index = nextIndex++;
if (index >= accounts.length) {
return;
}
const { account, provider } = accounts[index];
if (account.type !== "oauth") {
results[index] = buildResult(account, provider, "skipped_api_key", null);
continue;
}
const lastFetch = lastUsageFetchAt.get(account.key) ?? 0;
if (Date.now() - lastFetch < MIN_USAGE_REFETCH_INTERVAL_MS) {
results[index] = buildResult(account, provider, "throttled", null);
continue;
}
lastUsageFetchAt.set(account.key, Date.now());
if (provider === "codex") {
// Codex has its own usage endpoint and no overage/cooldown
// reconciliation to run; the snapshot is written under the full key,
// which is the only key the Codex engine ever reads it back by.
try {
const fetched = await fetchCodexAccountUsage(account);
if (fetched.ok === false) {
results[index] = buildResult(account, provider, "error", null, `codex usage fetch failed: ${fetched.reason}`);
continue;
}
await saveAccountQuota(account.key, fetched.quota);
results[index] = buildResult(account, provider, "refreshed", fetched.quota);
}
catch (err) {
results[index] = buildResult(account, provider, "error", null, err instanceof Error ? err.message : String(err));
}
continue;
}
// Isolate failures per account: an unexpected rejection must not abort
// the Promise.all sweep and turn the whole /limits response into a 502.
try {
const refresh = await accountQuotaRefreshCoordinator.run(account, `manual:${account.key}`, fetchValidatedAccountUsage, { force: true });
if (refresh.kind !== "completed") {
results[index] = buildResult(account, provider, "throttled", null);
continue;
}
const fetchResult = refresh.result;
// `=== false` (not `!ok`) — the react-hooks sub-build compiles this
// file without strictNullChecks, where negated boolean-discriminant
// narrowing does not apply.
if (fetchResult.ok === false) {
results[index] = buildResult(account, provider, "error", null, fetchResult.error);
continue;
}
const quota = await applyAccountUsageResult(account, fetchResult, refresh.startedAt, persisted[account.key] ?? persisted[account.label] ?? null);
if (!quota) {
results[index] = buildResult(account, provider, "error", null, "usage payload had no recognizable limit windows");
continue;
}
results[index] = buildResult(account, provider, "refreshed", quota);
}
catch (err) {
results[index] = buildResult(account, provider, "error", null, err instanceof Error ? err.message : String(err));
}
}
};
await Promise.all(Array.from({ length: Math.min(USAGE_REFRESH_CONCURRENCY, accounts.length || 1) }, () => worker()));
return {
fetchedAt,
snapshot: false,
results,
refreshMetrics: accountQuotaRefreshCoordinator.getMetrics(),
};
}
/** Quota-aware selection is on by default; disable with
* NEUROLINK_PROXY_QUOTA_ROUTING=off|false|0. Only affects the fill-first
* strategy (round-robin keeps strict rotation). */
function isQuotaRoutingEnabled() {
const v = (process.env.NEUROLINK_PROXY_QUOTA_ROUTING ?? "").toLowerCase();
return v !== "off" && v !== "false" && v !== "0";
}
/** Session-utilization soft limit above which an account is demoted below
* accounts with headroom (never made unusable). The utilization snapshot is
* always at least one response stale and requests land in concurrent bursts,
* so a small buffer under 100% turns the window-exhaustion handoff proactive
* (scheduled between requests) instead of reactive (a burst of 429s).
* Override with NEUROLINK_PROXY_SESSION_SOFT_LIMIT (0 < x <= 1; 1 disables). */
function getSessionSoftLimit() {
// Number() rejects partially numeric values ("0.5oops") that parseFloat
// would silently truncate.
const raw = Number(process.env.NEUROLINK_PROXY_SESSION_SOFT_LIMIT ?? "");
return Number.isFinite(raw) && raw > 0 && raw <= 1 ? raw : 0.97;
}
/** Width of the bucket within which two session resets count as "the same
* time", letting the weekly reset decide. Exact epoch equality never occurs,
* and bucketing (unlike pairwise closeness) keeps the comparator transitive.
* Override with NEUROLINK_PROXY_SESSION_RESET_TOLERANCE_MS. */
function getSessionResetToleranceMs() {
// Number() + isInteger rejects partially numeric values ("900000oops")
// that parseInt would silently truncate.
const raw = Number(process.env.NEUROLINK_PROXY_SESSION_RESET_TOLERANCE_MS ?? "");
return Number.isInteger(raw) && raw > 0 ? raw : 15 * 60 * 1000;
}
/**
* Operator policy on spending paid extra usage, refreshed from the runtime
* config snapshot on every request so a config edit applies mid-flight.
*
* Module-level rather than threaded through because every routing and cooldown
* decision consults it, and the value is uniform for a given proxy generation.
*/
let overagePolicy = "auto";
/**
* Publish the operator's extra-usage policy for paths that cannot receive it
* explicitly. Callers that make a routing or cooldown decision take it as a
* parameter instead — see {@link isOverageUsable} — so a concurrent request or
* a hot config reload cannot change the answer mid-flight.
*
* `undefined` means "no runtime config on this path", which is not a request to
* clear an operator's setting, so the current value is kept.
*/
function setOveragePolicy(policy) {
if (policy !== undefined) {
overagePolicy = policy;
}
}
/** The policy in force for a request that did not capture one explicitly. */
function currentOveragePolicy() {
return overagePolicy;
}
/**
* Whether this account may keep serving on paid extra usage.
*
* The provider decides what is possible; the operator decides what is
* permitted. `never` therefore vetoes an enabled account, while `always` can
* only confirm a signal Anthropic already gives — nothing here can switch on
* extra usage the organization has disabled.
*/
function isOverageUsable(quota, policy = overagePolicy) {
if (policy === "never") {
return false;
}
return isQuotaOverageAvailable(quota);
}
/**
* Derive the ordering signals for an account from its latest runtime quota.
*
* Reset freshening: a window whose reset epoch has already passed is treated
* as FRESH (0 used, "allowed", reset unknown) at read time — stale snapshot
* numbers from before the reset can never saturate or reject an account whose
* window Anthropic has since renewed. The snapshot itself is refreshed from
* live response headers the next time the account serves a request.
*/
/** Shortest scope token we will match on, so a degenerately broad scope name
* (e.g. "Claude") cannot silently apply a per-model cap to every model. */
const MIN_SCOPE_TOKEN_LENGTH = 4;
function normalizeModelToken(value) {
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
}
/**
* The distinguishing part of a scope display name. The shared vendor prefix is
* dropped so a window scoped to plain "Claude" collapses to "" and is rejected
* by the length guard rather than matching every Claude model.
*/
function scopeMatchToken(scopeModel) {
const normalized = normalizeModelToken(scopeModel);
return normalized.startsWith("claude") ? normalized.slice(6) : normalized;
}
/**
* How well a window's scope identifies the requested model, or null when it does
* not apply. Higher wins; an exact wire-id agreement is unambiguous and so
* outranks every display-name match.
*/
function scopeMatchScore(window, normalizedModel, requestedFamily) {
if (window.scopeModelId &&
normalizeModelToken(modelFamilyToken(window.scopeModelId)) ===
requestedFamily) {
return Number.MAX_SAFE_INTEGER;
}
if (!window.scopeModel) {
return null;
}
const scopeToken = scopeMatchToken(window.scopeModel);
if (scopeToken.length < MIN_SCOPE_TOKEN_LENGTH ||
!normalizedModel.includes(scopeToken)) {
return null;
}
return scopeToken.length;
}
/**
* When a scoped window was last observed. Falls back through the usage-API
* sweep timestamp to the flat snapshot time.
*
* Needed because the flat fields refresh on every response while `windows` only
* changes when the served model has a scoped cap — so a days-old scoped window
* rides along on a `lastUpdated` from seconds ago and looks current.
*/
function scopedWindowObservedAt(quota, window) {
return window.updatedAt ?? quota.windowsUpdatedAt ?? quota.lastUpdated;
}
/**
* Find the model-scoped quota window that applies to the requested model.
*
* Two sources describe the same cap differently: response headers carry the wire
* id of the model just served, while the usage API reports a DISPLAY name
* ("Fable", "Claude Opus 4.6"). So an exact wire-id match is tried first and
* display-name containment over alphanumeric-normalized forms is the fallback.
* Among equally specific matches the freshest observation wins, which favours
* the continuously-updated header window over a manually-refreshed one.
*
* Returns null whenever the account reports no applicable, still-fresh scoped
* cap — the common case.
*/
function matchScopedQuotaWindow(quota, requestedModel, now = Date.now()) {
if (!quota?.windows?.length || !requestedModel) {
return null;
}
const normalizedModel = normalizeModelToken(requestedModel);
if (!normalizedModel) {
return null;
}
const requestedFamily = normalizeModelToken(modelFamilyToken(requestedModel));
let best = null;
let bestScore = -1;
let bestObservedAt = -1;
for (const window of quota.windows) {
// `isActive` marks which window the provider considers binding right now,
// not whether the cap applies — a scoped cap reads as inactive until it is
// the tightest constraint, which is far too late to route around it.
if (now - scopedWindowObservedAt(quota, window) >
QUOTA_SNAPSHOT_FRESHNESS_MS) {
continue;
}
const score = scopeMatchScore(window, normalizedModel, requestedFamily);
if (score === null) {
continue;
}
const observedAt = scopedWindowObservedAt(quota, window);
if (score > bestScore ||
(score === bestScore && observedAt > bestObservedAt)) {
best = window;
bestScore = score;
bestObservedAt = observedAt;
}
}
return best;
}
/**
* Split a candidate set by whether each account can still serve the requested
* model under its model-scoped cap.
*
* An account is scoped out only on evidence strong enough to act on: a fresh
* window, a reset that is actually ticking, a rejected/spent reading, and no
* paid extra usage to absorb the overflow. Anything weaker leaves the account
* eligible — a stale or mis-parsed window must never be able to empty the pool.
*
* `exhaustion` is populated only when every candidate is scoped out AND the
* evidence is trustworthy, which is what lets the caller report "switch model"
* instead of a generic rate limit.
*/
function evaluateScopedExhaustion(accounts, requestedModel, now = Date.now(), policy = overagePolicy) {
if (!requestedModel || accounts.length === 0) {
return { eligible: accounts, exhaustion: null };
}
const eligible = [];
const exhausted = [];
let overageDisabledReason;
for (const account of accounts) {
const quota = accountRuntimeState.get(account.key)?.quota;
const window = matchScopedQuotaWindow(quota, requestedModel, now);
const reset = window ? resetEpochToMs(window.resetsAt, now) : undefined;
const spent = window !== null &&
reset !== undefined &&
((window.status ?? "").trim().toLowerCase() === "rejected" ||
(window.used ?? 0) >= 1) &&
!isOverageUsable(quota, policy);
if (!spent || !window) {
eligible.push(account);
continue;
}
exhausted.push({ label: account.label, window });
overageDisabledReason ??= quota?.overageDisabledReason;
}
if (eligible.length > 0 || exhausted.length === 0) {
return { eligible, exhaustion: null };
}
const earliestResetMs = Math.min(...exhausted.map((entry) => resetEpochToMs(entry.window.resetsAt, now) ?? Number.POSITIVE_INFINITY));
return {
eligible,
exhaustion: {
model: requestedModel,
scopeModel: exhausted[0]?.window.scopeModel ?? modelFamilyToken(requestedModel),
earliestResetMs,
accounts: exhausted.map((entry) => entry.label),
...(overageDisabledReason ? { overageDisabledReason } : {}),
},
};
}
function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToleranceMs, requestedModel, policy = overagePolicy) {
const st = accountRuntimeState.get(accountKey);
const q = st?.quota;
const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
const quotaAgeMs = quotaLastUpdated === null ? null : Math.max(0, now - quotaLastUpdated);
const refreshState = accountQuotaRefreshCoordinator.getState(accountKey);
const quotaStale = quotaAgeMs !== null && quotaAgeMs > QUOTA_SNAPSHOT_FRESHNESS_MS;
const normalizeStatus = (status) => status.trim().toLowerCase();
const rawOverageEligible = isQuotaOverageAvailable(q);
const observedStatuses = [
q?.unifiedStatus,
q?.sessionStatus,
q?.weeklyStatus,
].filter((status) => !!status?.trim());
const staleHardOrAmbiguous = quotaStale &&
!!q &&
!rawOverageEligible &&
observedStatuses.some((status) => normalizeStatus(status) !== "allowed");
const quotaFreshness = !q
? "unknown"
: !quotaStale
? "fresh"
: staleHardOrAmbiguous
? "refresh_due"
: "stale_known";
// A stale rejection is advisory after restart and cannot quarantine an
// account. Keep it as evidence, rank it behind known-healthy accounts, and
// refresh it through the usage endpoint before relying on the old status.
const routingQuota = staleHardOrAmbiguous ? undefined : q;
const coolingActive = !!st?.coolingUntil && now < st.coolingUntil;
// resetEpochToMs returns undefined for absent OR passed resets, so a
// ticking window is exactly "reset !== undefined".
const weeklyReset = resetEpochToMs(routingQuota?.weeklyResetAt, now);
const sessionReset = resetEpochToMs(routingQuota?.sessionResetAt, now);
const sessionTicking = sessionReset !== undefined;
const weeklyTicking = weeklyReset !== undefined;
const sessionUsed = routingQuota
? sessionTicking
? (routingQuota.sessionUsed ?? 0)
: 0
: null;
const weeklyUsed = routingQuota
? weeklyTicking
? (routingQuota.weeklyUsed ?? null)
: 0
: null;
const sessionStatus = routingQuota
? sessionTicking
? (routingQuota.sessionStatus ?? "unknown")
: "allowed"
: null;
const weeklyStatus = routingQuota
? weeklyTicking
? (routingQuota.weeklyStatus ?? "unknown")
: "allowed"
: null;
// isOverageUsable layers the operator's routing.use-overage policy over the
// provider signal isQuotaOverageAvailable reads.
const overageEligible = isOverageUsable(routingQuota, policy);
const hardSaturated = !overageEligible &&
(sessionStatus === "rejected" ||
sessionStatus === "throttled" ||
weeklyStatus === "rejected" ||
weeklyStatus === "throttled" ||
routingQuota?.unifiedStatus?.trim().toLowerCase() === "rejected" ||
(sessionTicking && (sessionUsed ?? 0) >= 1) ||
(weeklyTicking && (weeklyUsed ?? 0) >= 1));
const softSaturated = !hardSaturated &&
!overageEligible &&
sessionTicking &&
(sessionUsed ?? 0) >= sessionSoftLimit;
const saturated = hardSaturated || softSaturated;
// Model-scoped weekly cap for the model this request actually asks for.
// Reset-freshened exactly like session/weekly: a window whose reset has
// passed reads as renewed, never as spent.
const scopedWindow = matchScopedQuotaWindow(routingQuota, requestedModel, now);
const scopedReset = scopedWindow
? resetEpochToMs(scopedWindow.resetsAt, now)
: undefined;
const scopedTicking = scopedReset !== undefined;
const scopedUsed = scopedWindow
? scopedTicking
? scopedWindow.used
: 0
: null;
const scopedStatus = scopedWindow
? scopedTicking
? (scopedWindow.status ?? "unknown")
: "allowed"
: null;
const scopedSaturated = !quotaStale && scopedTicking && (scopedUsed ?? 0) >= sessionSoftLimit;
return {
// A rejected model-scoped window means this account cannot serve THIS
// model, even though it may be perfectly healthy for others. Excluding it
// here only affects ordering — the real gate is evaluateScopedExhaustion,
// and neither cools the account, which would wrongly withhold it from
// every other model.
usable: !coolingActive &&
!hardSaturated &&
(scopedStatus !== "rejected" || overageEligible),
saturated,
scopedModel: scopedWindow?.scopeModel ?? null,
scopedStatus,
scopedUsed,
scopedReset: scopedReset ?? Number.POSITIVE_INFINITY,
scopedUsedForSort: scopedUsed ?? -1,
scopedSaturated,
hasQuota: !!q,
quotaEvidenceRank: quotaFreshness === "fresh" || quotaFreshness === "stale_known"
? 0
: quotaFreshness === "refresh_due"
? 1
: 2,
quotaStale,
quotaFreshness,
refreshNeeded: quotaFreshness === "unknown" || quotaFreshness === "refresh_due",
refreshReason: quotaFreshness === "unknown"
? "startup_unknown"
: quotaFreshness === "refresh_due"
? "ambiguous_snapshot"
: null,
refreshInFlight: refreshState.inFlight,
lastRefreshAttemptAt: refreshState.lastAttemptAt ?? null,
lastRefreshSuccessAt: refreshState.lastSuccessAt ?? null,
nextRefreshEligibleAt: refreshState.nextEligibleAt ?? null,
saturationKind: hardSaturated ? "hard" : softSaturated ? "soft" : "none",
softLimitOverrideReason: overageEligible &&
sessionTicking &&
(sessionUsed ?? 0) >= sessionSoftLimit
? "overage"
: null,
quotaLastUpdated,
quotaAgeMs,
coolingActive,
coolingReason: st?.coolingReason ?? null,
coolingUntil: st?.coolingUntil ?? 0,
unifiedStatus: routingQuota?.unifiedStatus ?? null,
fallbackStatus: routingQuota?.fallbackStatus ?? null,
upgradePaths: routingQuota?.upgradePaths ?? null,
overageEligible,
overageStatus: routingQuota?.overageStatus ?? null,
sessionStatus,
sessionUsed,
sessionResetBucket: sessionTicking
? Math.floor(sessionReset / sessionResetToleranceMs)
: Number.POSITIVE_INFINITY,
sessionReset: sessionReset ?? Number.POSITIVE_INFINITY,
weeklyStatus,
weeklyReset: weeklyReset ?? Number.POSITIVE_INFINITY,
weeklyUsed,
weeklyUsedForSort: weeklyUsed ?? -1,
};
}
function buildAccountSortMetricsByKey(accounts, now, sessionSoftLimit, sessionResetToleranceMs, requestedModel) {
return new Map(accounts.map((account) => [
account.key,
accountSortMetrics(account.key, now, sessionSoftLimit, sessionResetToleranceMs, requestedModel),
]));
}
function orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftLimit, sessionResetToleranceMs, options) {
const policy = options?.policy;
const metricsByKey = buildAccountSortMetricsByKey(accounts, now, sessionSoftLimit, sessionResetToleranceMs, options?.requestedModel);
const { orderedAccounts, reason, affinitySkippedReason } = rankAccounts({
accounts,
metricsByKey,
primaryKey,
ranking: policy?.ranking,
affinityKey: policy?.affinityKey,
preferPrimary: policy?.preferPrimary,
});
return { orderedAccounts, metricsByKey, reason, affinitySkippedReason };
}
/**
* Order accounts to MAXIMIZE quota utilization (fill-first, smart order):
* spend the overall weekly allowance that expires SOONEST first, so quota
* cannot disappear while traffic is consuming a newer weekly window. The 5h
* window remains an availability boundary: a session at its soft limit is
* temporarily demoted until that session resets.
*
* Priority among usable accounts:
* 1. known healthy quota before unknown or ambiguous stale evidence. Quota
* discovery is handled by a lightweight usage GET, never a user request.
* 2. session headroom before session-saturated (>= soft limit or
* "throttled") — do not re-hammer an urgent weekly account while its 5h
* capacity is temporarily unavailable.
* 3. soonest WEEKLY (7d) reset — consume the oldest overall allowance
* before it expires.
* 4. soonest SESSION (5h) reset — decides equal-weekly or fresh-weekly ties,
* using tolerance buckets for comparator stability.
* 5. highest weekly utilization — finish off the one closest to done.
* 6. configured primary account, then insertion order
* Saturated pairs are ordered by soonest 5h recovery first, then weekly reset,
* because neither can consume more quota until session capacity returns.
* Cooling/rejected accounts sort last, soonest-back-to-service first, as
* last resort.
*/
function orderAccountsByQuota(accounts, now, primaryKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), requestedModel) {
return orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftLimit, sessionResetToleranceMs, { requestedModel }).orderedAccounts;
}
function scheduleAdaptiveQuotaRefreshes(accounts, orderedAccounts, sessionSoftLimit, routingMetrics) {
for (const account of accounts) {
if (account.type !== "oauth") {
continue;
}
const metrics = routingMetrics?.get(account.key) ??
accountSortMetrics(account.key, Date.now(), sessionSoftLimit, getSessionResetToleranceMs());
if (metrics.quotaFreshness === "unknown") {
void refreshAccountQuotaInBackground(account, `startup-unknown:${account.key}`).catch((error) => {
logger.debug(`[proxy] background quota discovery failed account=${account.label}: ${describeTransportError(error)}`);
});
}
else if (metrics.quotaFreshness === "refresh_due") {
void refreshAccountQuotaInBackground(account, `ambiguous:${metrics.quotaLastUpdated ?? 0}`).catch((error) => {
logger.debug(`[proxy] ambiguous quota refresh failed account=${account.label}: ${describeTransportError(error)}`);
});
}
}
const active = orderedAccounts[0];
const candidate = orderedAccounts[1];
if (!active || !candidate || candidate.type !== "oauth") {
return;
}
const activeQuota = accountRuntimeState.get(active.key)?.quota;
if (!activeQuota) {
return;
}
const sessionPrewarmAt = Math.max(0, sessionSoftLimit - 0.05);
const needsPrewarm = (activeQuota.sessionUsed ?? 0) >= sessionPrewarmAt ||
(activeQuota.weeklyUsed ?? 0) >= 0.9;
if (!needsPrewarm) {
return;
}
const trigger = [
"handoff",
active.key,
activeQuota.sessionResetAt ?? 0,
activeQuota.weeklyResetAt ?? 0,
candidate.key,
].join(":");
void refreshAccountQuotaInBackground(candidate, trigger).catch((error) => {
logger.debug(`[proxy] quota handoff prewarm failed account=${candidate.label}: ${describeTransportError(error)}`);
});
}
function scheduleHandoffQuotaRefresh(current, candidate, handoffEpoch, sessionSoftLimit) {
if (!candidate || candidate.type !== "oauth") {
return;
}
const metrics = accountSortMetrics(candidate.key, Date.now(), sessionSoftLimit, getSessionResetToleranceMs());
if (metrics.quotaFreshness === "fresh") {
return;
}
void refreshAccountQuotaInBackground(candidate, `hard-handoff:${current.key}:${handoffEpoch}:${candidate.key}`).catch((error) => {
logger.debug(`[proxy] hard-handoff quota refresh failed account=${candidate.label}: ${describeTransportError(error)}`);
});
}
function buildRoutingDecision(args) {
const { accounts, orderedAccounts, metricsByKey, evaluatedAt, strategy, primaryKey, quotaRoutingEnabled, quotaOrdered, sessionSoftLimit, sessionResetToleranceMs, rotationOffset, } = args;
const sourceIndexes = new Map(accounts.map((account, index) => [account.key, index]));
const configuredPrimaryMatched = !!primaryKey && accounts.some((account) => account.key === primaryKey);
const candidates = [];
for (const account of orderedAccounts) {
const metrics = metricsByKey.get(account.key);
if (!metrics) {
logger.warn(`[proxy] routing evidence omitted because metrics are missing for account=${account.label}`);
return undefined;
}
candidates.push({
account: account.label,
accountType: account.type,
sourceIndex: sourceIndexes.get(account.key) ?? candidates.length,
rank: candidates.length,
configuredPrimary: !!primaryKey && account.key === primaryKey,
usable: metrics.usable,
saturated: metrics.saturated,
quotaObserved: metrics.hasQuota,
quotaStale: metrics.quotaStale,
quotaFreshness: metrics.quotaFreshness,
refreshNeeded: metrics.refreshNeeded,
refreshReason: metrics.refreshReason,
refreshInFlight: metrics.refreshInFlight,
lastRefreshAttemptAt: metrics.lastRefreshAttemptAt,
lastRefreshSuccessAt: metrics.lastRefreshSuccessAt,
nextRefreshEligibleAt: metrics.nextRefreshEligibleAt,
saturationKind: metrics.saturationKind,
softLimitOverrideReason: metrics.softLimitOverrideReason,
quotaLastUpdated: metrics.quotaLastUpdated,
quotaAgeMs: metrics.quotaAgeMs,
coolingActive: metrics.coolingActive,
coolingReason: metrics.coolingReason,
coolingUntil: metrics.coolingUntil > 0 && Number.isFinite(metrics.coolingUntil)
? metrics.coolingUntil
: null,
unifiedStatus: metrics.unifiedStatus,
fallbackStatus: metrics.fallbackStatus,
upgradePaths: metrics.upgradePaths,
overageEligible: metrics.overageEligible,
overageStatus: metrics.overageStatus,
sessionStatus: metrics.sessionStatus,
sessionUsed: metrics.sessionUsed,
sessionResetAt: Number.isFinite(metrics.sessionReset)
? metrics.sessionReset
: null,
sessionResetBucket: Number.isFinite(metrics.sessionResetBucket)
? metrics.sessionResetBucket
: null,
weeklyStatus: metrics.weeklyStatus,
weeklyUsed: metrics.weeklyUsed,
weeklyResetAt: Number.isFinite(metrics.weeklyReset)
? metrics.weeklyReset
: null,
scopedModel: metrics.scopedModel,
scopedStatus: metrics.scopedStatus,
scopedUsed: metrics.scopedUsed,
scopedResetAt: Number.isFinite(metrics.scopedReset)
? metrics.scopedReset
: null,
});
}
const initialAccount = orderedAccounts[0];
let mode;
let selectionReason;
if (orderedAccounts.length === 1) {
mode = "single_account";
selectionReason = "single_account";
}
else if (quotaOrdered) {
mode = "quota";
selectionReason =
args.policySelectionReason ??
compareExpiryFirst(orderedAccounts[0], orderedAccounts[1], metricsByKey, primaryKey)[1];
}
else if (strategy === "round-robin") {
mode = "round_robin";
selectionReason = "round_robin";
}
else {
mode = "primary";
selectionReason =
args.policySelectionReason ??
(configuredPrimaryMatched && initialAccount?.key === primaryKey
? "configured_primary"
: "insertion_order");
}
return {
schemaVersion: 1,
evaluatedAt: new Date(evaluatedAt).toISOString(),
strategy,
mode,
selectionReason,
quotaRoutingEnabled,
quotaInputsUsed: quotaOrdered,
sessionSoftLimit,
sessionResetToleranceMs,
configuredPrimaryAccount: primaryKey ?? null,
configuredPrimaryMatched,
rotationOffset,
initialAccount: initialAccount?.label ?? "",
candidates,
...(args.policy ? { policy: args.policy } : {}),
...(args.affinity ? { affinity: args.affinity } : {}),
...(args.spill ? { spill: args.spill } : {}),
};
}
// A policy error can repeat on every request, so each (site, error) pair is
// logged once. Keyed per pair rather than once per worker, so a later,
// unrelated failure still reaches the log. Insertion order is eviction order.
const MAX_LOGGED_ROUTING_POLICY_ERRORS = 32;
const loggedRoutingPolicyErrors = new Set();
function logRoutingPolicyErrorOnce(error, fallbackOrder) {
const message = error instanceof Error ? error.message : String(error);
const signature = JSON.stringify([
fallbackOrder,
error instanceof Error ? error.name : typeof error,
message,
]);
if (loggedRoutingPolicyErrors.has(signature)) {
return;
}
loggedRoutingPolicyErrors.add(signature);
if (loggedRoutingPolicyErrors.size > MAX_LOGGED_ROUTING_POLICY_ERRORS) {
const oldest = loggedRoutingPolicyErrors.values().next().value;
if (oldest !== undefined) {
loggedRoutingPolicyErrors.delete(oldest);
}
}
logger.always(`[proxy] routing policy threw; falling back to ${fallbackOrder}: ${message}`);
}
// Test-only: makes maybeSpill() throw before its own guard, including on a
// "session_affinity" first choice it would otherwise skip. That is the only
// deterministic way to reach the policy catch after a genuine reorder, since
// the policy steps return the input order unchanged when nothing applies.
let forceSpillThrowForTests = false;
// Test-only: make the selection-time affinity lookup, or the post-serve
// binding re-check, throw. Each enters its own policy catch without reaching
// into the binding store, so the store can stay frozen.
let forceAffinityLookupThrowForTests = false;
let forceBindRecheckThrowForTests = false;
function selectClaudeProxyAccountOrder(args) {
const { enabledAccounts, accountStrategy, quotaRoutingEnabled, sessionSoftLimit, sessionResetToleranceMs, requestedModel, sessionId, ranking, preferPrimary, sessionAffinityEnabled, sessionAffinityIdleTtlMs, spillInflight, setRoutingDecision, } = args;
// The configured primary arrives normalized (lower-cased, prefixed), while
// account keys keep the token store's spelling, and every primary check
// below compares with ===. Resolve it to the loaded account's own key once.
const configuredPrimaryKey = args.primaryAccountKey;
const primaryAccountKey = configuredPrimaryKey === undefined
? undefined
: (enabledAccounts.find((account) => anthropicAccountKeysEqual(account.key, configuredPrimaryKey))?.key ?? configuredPrimaryKey);
let orderedAccounts = [...enabledAccounts];
const evaluatedAt = Date.now();
let metricsByKey;
let rotationOffset = 0;
const quotaOrdered = accountStrategy === "fill-first" &&
orderedAccounts.length > 1 &&
quotaRoutingEnabled;
// strategy: round-robin ignores every routing policy key.
const policiesApply = accountStrategy === "fill-first" && orderedAccounts.length > 1;
const policy = policiesApply
? {
ranking,
preferPrimary,
sessionAffinity: sessionAffinityEnabled,
sessionAffinityIdleTtlMs,
spillInflight,
}
: undefined;
let policySelectionReason;
let affinity;
let spill;
const resolveAffinityKey = () => {
if (!sessionAffinityEnabled || !sessionId) {
return undefined;
}
if (forceAffinityLookupThrowForTests) {
throw new Error("forced failure for affinity lookup throw test");
}
return sessionAffinity.get(sessionId, evaluatedAt, sessionAffinityIdleTtlMs);
};
const buildAffinityEvidence = (boundAccountKey, applied, skippedFromPrecedence) => ({
sessionBound: !!boundAccountKey,
boundAccount: (boundAccountKey &&
enabledAccounts.find((account) => account.key === boundAccountKey)
?.label) ??
null,
applied,
skippedReason: !sessionAffinityEnabled
? "disabled"
: !sessionId
? "no_session"
: !boundAccountKey
? "expired"
: applied
? null
: skippedFromPrecedence,
});
const labelOf = (accountKey) => enabledAccounts.find((account) => account.key === accountKey)?.label ??
accountKey;
const maybeSpill = (order, reason, metrics) => {
if (forceSpillThrowForTests) {
throw new Error("forced failure for spill throw test");
}
// Spill never splits a bound session, but a preferred-primary first
// choice has no binding and can still be relieved.
if (spillInflight <= 0 || reason === "session_affinity") {
return undefined;
}
const inflightByKey = new Map(order.map((account) => [account.key, getAccountInflight(account.key)]));
const spillResult = applySpill({
orderedAccounts: order,
inflightByKey,
metricsByKey: metrics,
spillInflight,
});
const move = spillResult.spill;
if (!move) {
return undefined;
}
return {
orderedAccounts: spillResult.orderedAccounts,
spill: {
from: labelOf(move.from),
to: labelOf(move.to),
inflight: move.inflight,
},
};
};
if (!quotaOrdered && accountStrategy === "fill-first") {
// A hot-reloaded primary change must apply to this request.
maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey);
}
if (quotaOrdered) {
// Metrics are not a policy step: a throw here propagates, as it did
// before the policies existed, instead of reading as a policy failure.
metricsByKey = buildAccountSortMetricsByKey(enabledAccounts, evaluatedAt, sessionSoftLimit, sessionResetToleranceMs, requestedModel);
try {
const boundAccountKey = resolveAffinityKey();
const quotaOrder = rankAccounts({
accounts: enabledAccounts,
metricsByKey,
primaryKey: primaryAccountKey,
ranking,
affinityKey: boundAccountKey,
preferPrimary,
});
const spilled = maybeSpill(quotaOrder.orderedAccounts, quotaOrder.reason, metricsByKey);
const affinityEvidence = buildAffinityEvidence(boundAccountKey, quotaOrder.reason === "session_affinity", quotaOrder.affinitySkippedReason);
orderedAccounts = spilled?.orderedAccounts ?? quotaOrder.orderedAccounts;
policySelectionReason = spilled ? "spill_inflight" : quotaOrder.reason;
affinity = affinityEvidence;
spill = spilled?.spill;
}
catch (error) {
logRoutingPolicyErrorOnce(error, "expiry-first order");
orderedAccounts = rankAccounts({
accounts: enabledAccounts,
metricsByKey,
primaryKey: primaryAccountKey,
}).orderedAccounts;
policySelectionReason = "routing_policy_error";
}
if (logger.shouldLog("debug")) {
logger.debug(`[proxy] quota-ordered fill sequence: ${orderedAccounts
.map((account) => account.label)
.join(" → ")}`);
}
}
else {
if (accountStrategy === "round-robin" &&
orderedAccounts.length !== lastKnownAccountCount) {
primaryAccountIndex = resolveHomeIndex(orderedAccounts, primaryAccountKey);
lastKnownAccountCount = orderedAccounts.length;
}
if (orderedAccounts.length > 1) {
rotationOffset = primaryAccountIndex % orderedAccounts.length;
if (accountStrategy === "round-robin") {
primaryAccountIndex =
(primaryAccountIndex + 1) % orderedAccounts.length;
}
if (rotationOffset > 0) {
const head = orderedAccounts.splice(0, rotationOffset);
orderedAccounts.push(...head);
}
}
metricsByKey = buildAccountSortMetricsByKey(enabledAccounts, evaluatedAt, sessionSoftLimit, sessionResetToleranceMs, requestedModel);
// Quota routing off: the rotation above stays the base order, and
// affinity/prefer-primary/spill still apply on top of it. Nothing is
// assigned until every policy step has succeeded, so a throw leaves the
// pre-policy order in place.
if (policiesApply) {
try {
const boundAccountKey = resolveAffinityKey();
const precedence = applyAffinityAndPrimary({
orderedAccounts,
metricsByKey,
affinityKey: boundAccountKey,
primaryKey: primaryAccountKey,
preferPrimary,
});
const spilled = maybeSpill(precedence.orderedAccounts, precedence.reason, metricsByKey);
const affinityEvidence = buildAffinityEvidence(boundAccountKey, precedence.reason === "session_affinity", precedence.affinitySkippedReason);
orderedAccounts =
spilled?.orderedAccounts ?? precedence.orderedAccounts;
policySelectionReason = spilled
? "spill_inflight"
: (precedence.reason ?? undefined);
affinity = affinityEvidence;
spill = spilled?.spill;
}
catch (error) {
logRoutingPolicyErrorOnce(error, "pre-policy order");
policySelectionReason = "routing_policy_error";
}
}
}
const routingDecision = buildRoutingDecision({
accounts: enabledAccounts,
orderedAccounts,
metricsByKey,
evaluatedAt,
strategy: accountStrategy,
primaryKey: primaryAccountKey,
quotaRoutingEnabled,
quotaOrdered,
sessionSoftLimit,
sessionResetToleranceMs,
rotationOffset,
policySelectionReason,
policy,
affinity,
spill,
});
if (routingDecision) {
setRoutingDecision(routingDecision);
}
return { orderedAccounts, metricsByKey };
}
/**
* Records that a session's request was served. A session keeps its current
* binding only when this request never tried the bound account and that
* account is still eligible: a request that overflowed its bound account's
* admission cap onto another account must not move the session off its warm
* prompt cache. Otherwise the serving account becomes the binding. Tried
* outranks eligible because an account that failed this request can look
* eligible again by now: a short 429 cooldown can lapse while the next
* account is still answering.
*/
function bindSessionAfterServe(args) {
const now = Date.now();
// This runs after the response has already been served (and, on the
// stream path, after admission-lease ownership has transferred to the
// stream): a throw here must never drop that response or leak the lease,
// so eligibility re-checks fall back to binding the serving account.
let accountKey = args.servingAccountKey;
try {
if (forceBindRecheckThrowForTests) {
throw new Error("forced failure for bind recheck throw test");
}
const boundKey = sessionAffinity.get(args.sessionId, now, args.idleTtlMs);
if (boundKey !== undefined &&
!args.attemptedAccountKeys.has(boundKey) &&
args.enabledAccounts.some((account) => account.key === boundKey) &&
isPrecedenceEligible(accountSortMetrics(boundKey, now, args.sessionSoftLimit, args.sessionResetToleranceMs, args.requestedModel))) {
accountKey = boundKey;
}
}
catch (error) {
logRoutingPolicyErrorOnce(error, "serving-account binding");
}
sessionAffinity.bind(args.sessionId, accountKey, now);
}
// ---------------------------------------------------------------------------
// OAuth polyfill helpers (extracted to reduce block nesting)
// ---------------------------------------------------------------------------
const snapshotCache = new Map();
const SNAPSHOT_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
const SNAPSHOT_STABLE_HEADERS = new Set([
"accept",
"accept-encoding",
"accept-language",
"anthropic-beta",
"anthropic-dangerous-direct-browser-access",
"anthropic-version",
"sec-fetch-mode",
"user-agent",
"x-app",
"x-stainless-arch",
"x-stainless-lang",
"x-stainless-os",
"x-stainless-package-version",
"x-stainless-retry-count",
"x-stainless-runtime",
"x-stainless-runtime-version",
"x-stainless-timeout",
"x-subscription-tier",
]);
const NON_CLAUDE_OAUTH_BETAS = [
"oauth-2025-04-20",
"claude-code-20250219",
"fine-grained-tool-streaming-2025-05-14",
];
function getSnapshotSafeLabel(accountLabel) {
return accountLabel.replace(/[^a-zA-Z0-9._@-]/g, "_");
}
function getSnapshotPath(accountLabel) {
return join(homedir(), ".neurolink", "header-snapshots", `anthropic_${getSnapshotSafeLabel(accountLabel)}.json`);
}
function applySnapshotHeaders(headers, snapshot) {
if (!snapshot?.headers) {
return;
}
for (const [sk, sv] of Object.entries(snapshot.headers)) {
const lower = sk.toLowerCase();
if (typeof sv === "string" &&
!headers[lower] &&
!BLOCKED_UPSTREAM_HEADERS.has(lower) &&
lower !== "authorization" &&
lower !== "x-api-key" &&
lower !== "x-claude-code-session-id") {
headers[lower] = sv;
}
}
}
async function loadClaudeSnapshot(accountLabel) {
try {
const safeLabel = getSnapshotSafeLabel(accountLabel);
const cached = snapshotCache.get(safeLabel);
if (cached && Date.now() - cached.loadedAt < SNAPSHOT_CACHE_TTL_MS) {
return cached.snapshot;
}
const snapshotPath = getSnapshotPath(accountLabel);
try {
await access(snapshotPath);
}
catch {
return null;
}
const snapshot = JSON.parse(await readFile(snapshotPath, "utf8"));
if (!snapshot || typeof snapshot !== "object") {
return null;
}
const normalized = {
accountKey: "accountKey" in snapshot && typeof snapshot.accountKey === "string"
? snapshot.accountKey
: `anthropic:${accountLabel}`,
capturedAt: "capturedAt" in snapshot && typeof snapshot.capturedAt === "string"
? snapshot.capturedAt
: new Date(0).toISOString(),
source: "claude-code",
headers: "headers" in snapshot && snapshot.headers ? snapshot.headers : {},
...(snapshot.body ? { body: snapshot.body } : {}),
};
if (Object.keys(normalized.headers).length === 0 &&
Object.keys(normalized.body ?? {}).length === 0) {
return null;
}
snapshotCache.set(safeLabel, {
snapshot: normalized,
loadedAt: Date.now(),
});
return normalized;
}
catch {
return null;
}
}
function buildSnapshotHeaders(headers, existingHeaders) {
const merged = { ...(existingHeaders ?? {}) };
for (const [key, value] of Object.entries(headers)) {
const lower = key.toLowerCase();
if (typeof value === "string" &&
SNAPSHOT_STABLE_HEADERS.has(lower) &&
!BLOCKED_UPSTREAM_HEADERS.has(lower) &&
lower !== "authorization" &&
lower !== "x-api-key" &&
lower !== "x-claude-code-session-id") {
merged[lower] = value;
}
}
return merged;
}
function extractSnapshotBody(body) {
if (!body || typeof body !== "object") {
return undefined;
}
const parsed = body;
const identity = parseClaudeCodeUserId(parsed.metadata?.user_id);
const systemBlocks = Array.isArray(parsed.system)
? parsed.system
: typeof parsed.system === "string"
? [{ type: "text", text: parsed.system }]
: [];
const billingHeader = systemBlocks.find((block) => typeof block?.text === "string" &&
block.text.includes("x-anthropic-billing-header"))?.text;
const agentBlock = systemBlocks.find((block) => typeof block?.text === "string" &&
block.text.includes("Claude Agent SDK"))?.text;
if (!identity && !billingHeader && !agentBlock) {
return undefined;
}
return {
...(identity ? { metadataUserId: identity.metadataUserId } : {}),
...(identity ? { sessionId: identity.sessionId } : {}),
...(billingHeader ? { billingHeader } : {}),
...(agentBlock ? { agentBlock } : {}),
};
}
function isLikelyClaudeClient(headers, snapshotBody) {
return (typeof headers["x-claude-code-session-id"] === "string" ||
headers["user-agent"]?.startsWith("claude-cli/") ||
!!snapshotBody?.metadataUserId ||
!!snapshotBody?.billingHeader ||
!!snapshotBody?.agentBlock);
}
function snapshotsMatch(existing, next) {
if (!existing) {
return false;
}
return (JSON.stringify(existing.headers ?? {}) ===
JSON.stringify(next.headers ?? {}) &&
JSON.stringify(existing.body ?? {}) === JSON.stringify(next.body ?? {}));
}
async function persistClaudeSnapshot(accountLabel, snapshot) {
const snapshotPath = getSnapshotPath(accountLabel);
await writeJsonSnapshotAtomically(snapshotPath, snapshot, 0o600);
snapshotCache.set(getSnapshotSafeLabel(accountLabel), {
snapshot,
loadedAt: Date.now(),
});
}
async function maybeRefreshClaudeSnapshot(accountLabel, accountKey, headers, bodyStr) {
const existing = await loadClaudeSnapshot(accountLabel);
let parsedBody;
try {
parsedBody = JSON.parse(bodyStr);
}
catch {
return existing;
}
const body = extractSnapshotBody(parsedBody);
if (!isLikelyClaudeClient(headers, body)) {
return existing;
}
const next = {
accountKey,
capturedAt: new Date().toISOString(),
source: "claude-code",
headers: buildSnapshotHeaders(headers, existing?.headers),
body: {
...(existing?.body ?? {}),
...(body ?? {}),
...(typeof headers["x-claude-code-session-id"] === "string"
? { sessionId: headers["x-claude-code-session-id"] }
: {}),
},
};
if (snapshotsMatch(existing, next)) {
return existing;
}
try {
await persistClaudeSnapshot(accountLabel, next);
}
catch (error) {
logger.warn("[proxy] failed to persist Claude snapshot", {
accountLabel,
error: error instanceof Error ? error.message : String(error),
});
snapshotCache.set(getSnapshotSafeLabel(accountLabel), {
snapshot: next,
loadedAt: Date.now(),
});
}
return next;
}
/**
* Parse response-side details (model, finish reason, invoked tools) from a
* non-streaming Anthropic reply so they can be recorded on the trace span.
*/
function extractResponseInfo(responseJson) {
const info = {};
if (!responseJson || typeof responseJson !== "object") {
return info;
}
const r = responseJson;
if (typeof r.model === "string") {
info.responseModel = r.model;
}
if (typeof r.stop_reason === "string") {
info.finishReason = r.stop_reason;
}
if (typeof r.stop_sequence === "string") {
info.stopSequence = r.stop_sequence;
}
if (Array.isArray(r.content)) {
const toolCalls = r.content
.filter((b) => !!b &&
typeof b === "object" &&
b.type === "tool_use")
.map((b) => String(b.name ?? ""))
.filter((n) => n.length > 0);
if (toolCalls.length > 0) {
info.toolCalls = toolCalls;
}
}
return info;
}
/**
* Build response-info from streaming telemetry (responding model, finish
* reason, and the tools the model invoked via tool_use content blocks) so the
* streaming path records the same gen_ai.response.* attributes as non-streaming.
*/
function responseInfoFromStream(data) {
const info = {};
if (data.model) {
info.responseModel = data.model;
}
if (data.stopReason) {
info.finishReason = data.stopReason;
}
if (data.stopSequence) {
info.stopSequence = data.stopSequence;
}
const toolCalls = (data.contentBlocks ?? [])
.filter((b) => b.type === "tool_use")
.map((b) => String(b.toolName ?? ""))
.filter((n) => n.length > 0);
if (toolCalls.length > 0) {
info.toolCalls = toolCalls;
}
return info;
}
/**
* Polyfill the request body for OAuth accounts.
* Claude Code injects a billing header, agent block, and metadata.user_id
* into the body. Non-CC clients (Curator, custom apps) don't send these —
* Anthropic rejects without them.
*/
function polyfillOAuthBody(bodyStr, accountToken, snapshot, isClaudeClientRequest, preferredSessionId) {
try {
const parsed = JSON.parse(bodyStr);
// Billing header block synthesized for clients that do not already provide
// the genuine Claude Code identity shape.
const agentBlock = {
type: "text",
text: snapshot?.body?.agentBlock ||
"You are a Claude agent, built on Anthropic's Claude Agent SDK.",
};
// Normalise system to an array, then route by client type.
//
// The subscription/OAuth path only accepts a `system` it recognises as the
// genuine Claude Code prompt. A real CC client sends its own billing + agent
// identity blocks alongside the canonical prompt, which must pass through
// unchanged. A custom client (Curator) sends its own arbitrary system prompt
// with NO agent block; left in `system` it is rejected as
// `rate_limit_error: "Error"`, so we relocate it into the message stream and
// send only the recognised billing + agent blocks as `system`.
if (parsed.system) {
if (typeof parsed.system === "string") {
parsed.system = [{ type: "text", text: parsed.system }];
}
if (Array.isArray(parsed.system)) {
// Find existing billing/agent blocks wherever the client placed them.
const billingIdx = parsed.system.findIndex((b) => typeof b.text === "string" &&
b.text.includes("x-anthropic-billing-header"));
const agentIdx = parsed.system.findIndex((b) => typeof b.text === "string" && b.text.includes("Claude Agent SDK"));
const billingBlock = {
type: "text",
text: buildStableClaudeCodeBillingHeader(parsed.system[billingIdx]?.text ?? snapshot?.body?.billingHeader),
};
const hasClaudeCodeSystemIdentity = isClaudeClientRequest && agentIdx >= 0;
if (hasClaudeCodeSystemIdentity) {
// Claude Code's subagent billing marker, block order, and cache
// controls are part of the accepted request shape. Preserve every
// supplied block exactly; only synthesize a billing block if absent.
if (billingIdx < 0) {
parsed.system.unshift(billingBlock);
}
}
else {
// Strip generated identity blocks before relocating a custom
// client's actual system instructions into the message stream.
const indicesToRemove = [billingIdx, agentIdx]
.filter((i) => i >= 0)
.sort((a, b) => b - a);
for (const idx of indicesToRemove) {
parsed.system.splice(idx, 1);
}
if (parsed.system.length > 0) {
relocateClientSystemIntoMessages(parsed, parsed.system);
}
parsed.system = [billingBlock, agentBlock];
}
}
}
else {
const billingBlock = {
type: "text",
text: buildStableClaudeCodeBillingHeader(snapshot?.body?.billingHeader),
};
parsed.system = [billingBlock, agentBlock];
}
// Inject Claude-Code-shaped metadata.user_id (required for OAuth).
const tokenPrefix = accountToken.substring(0, Math.min(20, accountToken.length));
const identity = getOrCreateClaudeCodeIdentity(tokenPrefix, {
existingUserId: parsed.metadata?.user_id ?? snapshot?.body?.metadataUserId,
preferredSessionId: preferredSessionId ?? snapshot?.body?.sessionId,
});
parsed.metadata = {
...parsed.metadata,
user_id: identity.metadataUserId,
};
return { bodyStr: JSON.stringify(parsed), sessionId: identity.sessionId };
}
catch {
return { bodyStr }; // JSON parse failed — use original body
}
}
// ---------------------------------------------------------------------------
// Legacy credential refresh helper (extracted to reduce block nesting)
// ---------------------------------------------------------------------------
async function tryLoadLegacyAccount(creds, legacyCredPath) {
if (!creds.oauth?.accessToken) {
return undefined;
}
let legacyToken = creds.oauth.accessToken;
let legacyRefresh = creds.oauth.refreshToken;
let legacyExpiry = creds.oauth.expiresAt;
const legacyLabel = creds.email?.trim() || "legacy-default";
const legacyExpired = legacyExpiry ? legacyExpiry < Date.now() : false;
const legacyAccount = () => ({
key: LEGACY_ANTHROPIC_ACCOUNT_KEY,
label: legacyLabel,
token: legacyToken,
refreshToken: legacyRefresh,
expiresAt: legacyExpiry,
type: "oauth",
persistTarget: { credPath: legacyCredPath },
});
if (!legacyExpired) {
return legacyAccount();
}
const state = getOrCreateRuntimeState(LEGACY_ANTHROPIC_ACCOUNT_KEY);
if (state.permanentlyDisabled) {
return undefined;
}
const persistedCooldown = (await loadAccountCooldowns())[LEGACY_ANTHROPIC_ACCOUNT_KEY];
if (persistedCooldown?.coolingUntil > Date.now() &&
(!state.coolingUntil || persistedCooldown.coolingUntil > state.coolingUntil)) {
state.coolingUntil = persistedCooldown.coolingUntil;
state.coolingReason = persistedCooldown.reason;
}
if (state.coolingReason === "auth" &&
state.coolingUntil &&
state.coolingUntil > Date.now()) {
return legacyAccount();
}
if (!legacyRefresh) {
logger.always("[proxy] skipping legacy account (expired, no refresh token)");
await disableAccountUntilReauth(legacyAccount(), state, "missing_refresh_token");
return undefined;
}
const tmp = {
token: legacyToken,
refreshToken: legacyRefresh,
expiresAt: legacyExpiry,
label: legacyLabel,
};
const ok = await refreshToken(tmp);
if (!ok.success) {
if (isPermanentRefreshFailure(ok)) {
await disableAccountUntilReauth(legacyAccount(), state, "refresh_invalid");
return undefined;
}
const coolingUntil = await coolAccountAfterTransientRefreshFailure(legacyAccount(), state);
logger.always(`[proxy] legacy refresh temporarily unavailable (${ok.status ?? "network"}); cooling until ${new Date(coolingUntil).toISOString()}`);
return legacyAccount();
}
legacyToken = tmp.token;
legacyRefresh = tmp.refreshToken;
legacyExpiry = tmp.expiresAt;
await persistTokens(legacyCredPath, tmp);
await clearAuthCooldownAfterRefresh(legacyAccount(), state);
logger.always("[proxy] refreshed legacy account at startup");
return legacyAccount();
}
async function handleTranslatedClaudeRequest(args) {
const { ctx, body, route, modelRouter, tracer, requestStartTime, logProxyBody, } = args;
tracer?.setMode("full");
const parsed = parseClaudeRequest(body);
const plan = buildProxyTranslationPlan({
provider: route.provider,
model: route.model,
}, modelRouter?.getFallbackChain() ?? [], body.model, parsed, modelRouter?.isAutoFallbackEnabled?.() ?? false);
logProxyRoutingPlan(logProxyBody, "translated_request", plan);
const attempts = plan.attempts;
if (body.stream) {
return handleTranslatedStreamRequest({
ctx,
format: "claude",
requestModel: body.model,
parsed,
attempts,
tracer,
requestStartTime,
});
}
try {
return await handleTranslatedJsonRequest({
ctx,
format: "claude",
requestModel: body.model,
parsed,
attempts,
tracer,
requestStartTime,
terminalFailureStatus: 502,
});
}
catch (error) {
const message = error instanceof Error ? error.message : "unknown error";
logger.error(`[claude-proxy] Translated generation failed for ${body.model}: ${message}`);
const clientError = buildClaudeError(502, `Generation failed: ${message}`);
const clientErrorBody = JSON.stringify(clientError);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: clientErrorBody,
bodySize: Buffer.byteLength(clientErrorBody, "utf8"),
contentType: "application/json",
account: "translation",
accountType: "translation",
responseStatus: 502,
durationMs: Date.now() - requestStartTime,
});
return clientError;
}
}
function logProxyRoutingPlan(logProxyBody, stage, plan) {
logProxyBody({
phase: "routing_decision",
contentType: "application/json",
body: {
stage,
attempts: plan.attempts,
},
});
}
async function handleClaudePassthroughRequest(args) {
const { ctx, body, clientRequestBody, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
tracer?.setMode("passthrough-cli");
const { bodyStr, preparedContext } = prepareAnthropicWire(ctx, clientRequestBody);
const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
const upstreamHeaders = {};
for (const [key, value] of Object.entries(ctx.headers)) {
if (!BLOCKED_UPSTREAM_HEADERS.has(key.toLowerCase()) && value) {
upstreamHeaders[key] = value;
}
}
if (!upstreamHeaders["content-type"]) {
upstreamHeaders["content-type"] = "application/json";
}
const upstreamSpan = tracer?.startUpstreamAttempt({
account: "passthrough",
attempt: 1,
polyfillHeaders: false,
polyfillBody: false,
upstreamUrl: "https://api.anthropic.com/v1/messages?beta=true",
});
let response;
try {
response = await fetchAnthropicUpstream("https://api.anthropic.com/v1/messages?beta=true", {
method: "POST",
headers: upstreamHeaders,
body: bodyStr,
signal: AbortSignal.any([
...(ctx.abortSignal ? [ctx.abortSignal] : []),
AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS),
]),
}, ctx, "anthropic:passthrough", {
logProxyBody,
tracer,
attemptNumber: 1,
accountLabel: "passthrough",
accountType: "passthrough",
preparedContext,
});
}
catch (fetchErr) {
if (fetchErr instanceof ProxyContextPreflightError ||
getProxyTokenBudgetError(fetchErr) ||
ctx.abortSignal?.aborted) {
upstreamSpan?.end();
throw fetchErr;
}
const errMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
recordAttemptError("passthrough", "passthrough", 502);
tracer?.setError("network_error", errMsg);
upstreamSpan?.end();
tracer?.end(502, Date.now() - requestStartTime);
logFinalRequest(502, "passthrough", "passthrough", "network_error", errMsg);
const errorBody = buildClaudeError(502, `Passthrough fetch failed: ${errMsg}`);
const errorBodyText = JSON.stringify(errorBody);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: errorBodyText,
bodySize: Buffer.byteLength(errorBodyText, "utf8"),
contentType: "application/json",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: 502,
durationMs: Date.now() - requestStartTime,
});
return errorBody;
}
const upstreamResponseHeaders = {};
response.headers.forEach((value, key) => {
upstreamResponseHeaders[key] = value;
});
tracer?.logUpstreamResponseHeaders(upstreamResponseHeaders);
// Passthrough uses the caller's own credentials, so there is no account pool
// to report — but the upstream quota headers are still the caller's real
// limits. Published before the ok/non-ok split so a 429 carries them too.
{
const passthroughQuota = parseQuotaHeaders(response.headers, {
model: body.model,
});
publishLimitHeaders(ctx, {
upstreamHeaders: response.headers,
quota: passthroughQuota,
source: passthroughQuota ? "live" : "none",
accountType: "passthrough",
servedBy: "anthropic",
attempt: 1,
});
}
if (!response.ok) {
const errorText = await response.text();
recordAttemptError("passthrough", "passthrough", response.status, response.status === 429 ? "quota" : undefined);
tracer?.logUpstreamResponseBody(errorText);
logProxyBody({
phase: "upstream_response",
headers: upstreamResponseHeaders,
body: errorText,
bodySize: Buffer.byteLength(errorText, "utf8"),
contentType: upstreamResponseHeaders["content-type"] ?? "application/json",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
logProxyBody({
phase: "client_response",
headers: upstreamResponseHeaders,
body: errorText,
bodySize: Buffer.byteLength(errorText, "utf8"),
contentType: upstreamResponseHeaders["content-type"] ?? "application/json",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
upstreamSpan?.end();
tracer?.setError("api_error", errorText.slice(0, 500));
tracer?.end(response.status, Date.now() - requestStartTime);
logFinalRequest(response.status, "passthrough", "passthrough", response.status === 429 ? "rate_limit_error" : "api_error", errorText);
try {
return JSON.parse(errorText);
}
catch {
return buildClaudeError(response.status, errorText);
}
}
if (body.stream && response.body) {
return handleClaudePassthroughStreamResponse({
ctx,
body,
bodyStr,
response,
tracer,
requestStartTime,
toolCount,
upstreamSpan,
upstreamResponseHeaders,
logProxyBody,
logFinalRequest,
});
}
return handleClaudePassthroughJsonResponse({
ctx,
body,
bodyStr,
response,
tracer,
requestStartTime,
toolCount,
upstreamSpan,
upstreamResponseHeaders,
logProxyBody,
logFinalRequest,
});
}
function trackUpstreamReadableStream(source) {
const reader = source.getReader();
const tracker = createStreamTerminalOutcomeTracker();
let closed = false;
const stream = new ReadableStream({
async pull(controller) {
if (closed) {
return;
}
try {
const { done, value } = await reader.read();
if (closed) {
return;
}
if (done) {
closed = true;
tracker.complete();
controller.close();
return;
}
controller.enqueue(value);
}
catch (error) {
closed = true;
const message = error instanceof Error ? error.message : String(error);
tracker.fail(message);
controller.error(error);
}
},
cancel(reason) {
closed = true;
tracker.cancel();
return reader.cancel(reason);
},
});
return { stream, outcome: tracker.outcome };
}
async function handleClaudePassthroughStreamResponse(args) {
const { ctx, bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
const responseHeaders = { ...upstreamResponseHeaders };
const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
const responseBody = response.body;
if (!responseBody) {
recordAttemptError("passthrough", "passthrough", 502);
throw new Error("Expected passthrough stream response body");
}
const trackedStream = trackUpstreamReadableStream(responseBody);
let streamSource = trackedStream.stream;
let streamFinalized = false;
let telemetryDone;
const finalizeStream = (status, errorType, errorMessage, usage) => {
if (streamFinalized) {
return false;
}
streamFinalized = true;
if (errorType === "stream_error") {
recordAttemptError("passthrough", "passthrough", status);
}
logFinalRequest(status, "passthrough", "passthrough", errorType, errorMessage, usage);
return true;
};
if (tracer) {
try {
const { stream: interceptor, telemetry } = createSSEInterceptor({
captureRawText: true,
});
streamSource = streamSource.pipeThrough(interceptor);
const capturedTracer = tracer;
const capturedUpstreamSpan = upstreamSpan;
const capturedResponse = response;
const capturedRequestBytes = bodyStr.length;
telemetryDone = Promise.all([
telemetry,
clientCapture,
trackedStream.outcome,
])
.then(([data, clientBody, rawOutcome]) => {
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
observeClaudeStreamOutput(ctx.metadata, data);
const failure = getStreamFailureDetails(terminalOutcome);
capturedTracer.setUsage({
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
});
capturedTracer.logStreamEvents(data.events);
capturedTracer.setResponseInfo(responseInfoFromStream(data));
const rateLimit5h = parseFloat(capturedResponse.headers.get("anthropic-ratelimit-unified-5h-utilization") ?? "");
const rateLimit7d = parseFloat(capturedResponse.headers.get("anthropic-ratelimit-unified-7d-utilization") ?? "");
const usageUpdate = {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
};
if (!isNaN(rateLimit5h)) {
usageUpdate.rateLimitAfter5h = rateLimit5h;
}
if (!isNaN(rateLimit7d)) {
usageUpdate.rateLimitAfter7d = rateLimit7d;
}
if (!isNaN(rateLimit5h) || !isNaN(rateLimit7d)) {
capturedTracer.setUsage(usageUpdate);
}
capturedTracer.logUpstreamResponseBody(data.rawText ?? "");
capturedTracer.recordMetrics();
capturedTracer.recordBodySizes(capturedRequestBytes, data.totalBytesReceived);
capturedUpstreamSpan?.end();
if (failure) {
capturedTracer.setError(failure.errorType, failure.message);
}
capturedTracer.end(failure?.status ?? response.status, Date.now() - requestStartTime);
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message, {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
});
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: data.rawText ?? "",
sourceTruncated: data.rawTextTruncated,
bodySize: data.totalBytesReceived,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
logProxyBody({
phase: "client_response",
headers: responseHeaders,
body: clientBody.text,
sourceTruncated: clientBody.truncated,
bodySize: clientBody.totalBytes,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
})
.catch((error) => {
if (streamFinalized) {
return;
}
const message = error instanceof Error ? error.message : String(error);
capturedTracer.setError("stream_telemetry_error", message);
capturedUpstreamSpan?.end();
capturedTracer.end(500, Date.now() - requestStartTime);
finalizeStream(500, "stream_telemetry_error", message);
});
}
catch {
telemetryDone = trackedStream.outcome.then((outcome) => {
const failure = getStreamFailureDetails(outcome);
upstreamSpan?.end();
if (failure) {
tracer.setError(failure.errorType, failure.message);
}
tracer.end(failure?.status ?? response.status, Date.now() - requestStartTime);
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message);
});
}
}
else {
try {
const { stream: interceptor, telemetry } = createSSEInterceptor({
captureRawText: true,
});
streamSource = streamSource.pipeThrough(interceptor);
telemetryDone = Promise.all([
telemetry,
clientCapture,
trackedStream.outcome,
])
.then(([data, clientBody, rawOutcome]) => {
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
observeClaudeStreamOutput(ctx.metadata, data);
const failure = getStreamFailureDetails(terminalOutcome);
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message, {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
});
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: data.rawText ?? "",
sourceTruncated: data.rawTextTruncated,
bodySize: data.totalBytesReceived,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
logProxyBody({
phase: "client_response",
headers: responseHeaders,
body: clientBody.text,
sourceTruncated: clientBody.truncated,
bodySize: clientBody.totalBytes,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
})
.catch((error) => {
if (!streamFinalized) {
finalizeStream(500, "stream_telemetry_error", error instanceof Error ? error.message : String(error));
}
});
}
catch {
// Streaming capture is best-effort; the tracked source still propagates
// the transport failure to the client.
telemetryDone = trackedStream.outcome.then((outcome) => {
const failure = getStreamFailureDetails(outcome);
finalizeStream(failure?.status ?? response.status, failure?.errorType, failure?.message);
});
}
}
const clientStream = streamSource.pipeThrough(clientCaptureStream);
registerProxyResponseObserver(ctx.metadata, {
onTerminal: () => telemetryDone,
});
return new Response(clientStream, {
status: response.status,
// Upstream headers first, then the proxy's own — passthrough already
// forwarded everything Anthropic sent, so this only adds the x-neurolink-*
// fields the client cannot derive on its own.
headers: {
...responseHeaders,
...(ctx.responseHeaders ?? {}),
},
});
}
async function handleClaudePassthroughJsonResponse(args) {
const { ctx, bodyStr, response, tracer, requestStartTime, upstreamSpan, upstreamResponseHeaders, logProxyBody, logFinalRequest, } = args;
const responseText = await response.text();
tracer?.logUpstreamResponseBody(responseText);
logProxyBody({
phase: "upstream_response",
headers: upstreamResponseHeaders,
body: responseText,
bodySize: Buffer.byteLength(responseText, "utf8"),
contentType: upstreamResponseHeaders["content-type"] ?? "application/json",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
logProxyBody({
phase: "client_response",
headers: upstreamResponseHeaders,
body: responseText,
bodySize: Buffer.byteLength(responseText, "utf8"),
contentType: upstreamResponseHeaders["content-type"] ?? "application/json",
account: "passthrough",
accountType: "passthrough",
attempt: 1,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
const responseJson = JSON.parse(responseText);
observeClaudeJsonOutput(ctx.metadata, responseJson);
if (tracer && responseJson && typeof responseJson === "object") {
const usage = responseJson.usage;
if (usage) {
tracer.setUsage({
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
...oneHourCacheWriteFields(readCacheCreation1hTokens(usage)),
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
});
const rateLimit5h = parseFloat(response.headers.get("anthropic-ratelimit-unified-5h-utilization") ??
"");
const rateLimit7d = parseFloat(response.headers.get("anthropic-ratelimit-unified-7d-utilization") ??
"");
if (!isNaN(rateLimit5h) || !isNaN(rateLimit7d)) {
const usageWithRates = {
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
...oneHourCacheWriteFields(readCacheCreation1hTokens(usage)),
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
};
if (!isNaN(rateLimit5h)) {
usageWithRates.rateLimitAfter5h = rateLimit5h;
}
if (!isNaN(rateLimit7d)) {
usageWithRates.rateLimitAfter7d = rateLimit7d;
}
tracer.setUsage(usageWithRates);
}
}
tracer.setResponseInfo(extractResponseInfo(responseJson));
tracer.recordMetrics();
const responseJsonStr = JSON.stringify(responseJson);
tracer.recordBodySizes(bodyStr.length, responseJsonStr.length);
upstreamSpan?.end();
tracer.end(response.status, Date.now() - requestStartTime);
logFinalRequest(response.status, "passthrough", "passthrough", undefined, undefined, {
inputTokens: usage?.input_tokens,
outputTokens: usage?.output_tokens,
cacheCreationTokens: usage?.cache_creation_input_tokens,
...oneHourCacheWriteFields(readCacheCreation1hTokens(usage)),
cacheReadTokens: usage?.cache_read_input_tokens,
});
}
else {
upstreamSpan?.end();
tracer?.end(response.status, Date.now() - requestStartTime);
logFinalRequest(response.status, "passthrough", "passthrough");
}
return responseJson;
}
/**
* Narrow the pool to what the current borrowed request's grant may use.
*
* A no-op — and one map lookup — for the node's own traffic, which is every
* request on a proxy that shares nothing.
*
* When the filter empties the pool the refusal must be share-shaped, not the
* generic "no credentials" 401: the borrower has perfectly good credentials,
* the lender is simply holding capacity back. Saying it precisely is what lets
* the borrower fall through to another peer instead of re-authenticating.
*/
async function applyShareAccountGates(args) {
const share = getShareContext();
if (!share) {
return { accounts: args.accounts };
}
if (args.accounts.length === 0) {
// Nothing to withhold. Claiming a share refusal here would tell the
// borrower the lender is holding capacity back, when in fact the lender has
// no usable accounts at all — a different problem with a different fix,
// and the existing no-credentials path already words it correctly.
return { accounts: args.accounts };
}
const now = Date.now();
const views = await Promise.all(args.accounts.map(async (account) => {
const quota = getOrCreateRuntimeState(account.key).quota;
const sessionResetAt = resetEpochToMs(quota?.sessionResetAt, now) ?? null;
const weeklyResetAt = resetEpochToMs(quota?.weeklyResetAt, now) ?? null;
const borrowed = await readShareWindowUsage(share.grantId, account.key, sessionResetAt, weeklyResetAt);
return {
accountKey: account.key,
sessionUsed: quota?.sessionUsed ?? null,
weeklyUsed: quota?.weeklyUsed ?? null,
sessionResetAt,
weeklyResetAt,
borrowedSessionFraction: borrowed.sessionFraction,
borrowedWeeklyFraction: borrowed.weeklyFraction,
};
}));
const grant = await getShareGrant(share.grantId);
if (!grant) {
return { accounts: args.accounts };
}
// Pool-wide first: how much of the pool this grant has already taken. The
// denominator is the accounts the grant may actually draw on — an
// `--accounts`-restricted grant divides by those, not by every credential
// this node holds, or its ceiling would scale with the size of a pool it
// cannot reach.
const poolUsage = await readSharePoolWindowUsage(share.grantId, accountsInGrantScope(grant.gates, views).inScope);
const decision = filterAccountsForGrant(grant, views, now, poolUsage);
const allowed = new Set(decision.allowed);
const survivors = args.accounts.filter((account) => allowed.has(account.key));
if (survivors.length > 0) {
return { accounts: survivors };
}
const reason = summarizeAccountExclusions(decision.excluded);
const refusal = buildShareRefusal(reason, {
status: shareRefusalStatus(reason),
grant,
retryAfterSeconds: earliestShareRecoverySeconds(views, now),
});
logger.always(`[proxy] share ${share.peerLabel} withheld: ${reason} (${decision.excluded.length} accounts)`);
return {
accounts: [],
refusal: {
status: refusal.status,
message: refusal.body.error.message,
errorType: refusal.body.error.type,
responseHeaders: refusal.headers,
},
};
}
/**
* Withhold credentials a lender provisioned here whose lease has lapsed.
*
* A complete-mode credential keeps working while the lender is unreachable —
* that is what it is for — but only for as long as the lease allows. Past that,
* the honest thing is to stop using it, and the borrower is the only party in a
* position to do so.
*
* A no-op for a node with no resident grants, which is every node until someone
* provisions one.
*/
async function dropExpiredResidentAccounts(accounts) {
const survivors = [];
const withheld = [];
for (const account of accounts) {
const verdict = await evaluateResidentAccount(account.key);
if (!verdict) {
survivors.push(account);
continue;
}
if (!isLeaseRefusal(verdict)) {
survivors.push(account);
continue;
}
logger.always(`[proxy] withholding leased account=${account.label}: ${verdict.reason}`);
withheld.push({ label: account.label, reason: verdict.reason });
}
return { accounts: survivors, withheld };
}
/**
* Enforce a lender's model allowlist on the credential they provisioned here.
*
* A live share checks this at the lender's gate. A complete share has no gate in
* the request path, so the borrower has to hold the line itself — otherwise a
* grant that says "Sonnet only" becomes "anything" the moment it is provisioned,
* which is exactly the property that would make complete mode unshippable.
*/
async function dropLeaseDisallowedAccounts(accounts, requestedModel) {
const survivors = [];
const withheld = [];
const now = Date.now();
for (const account of accounts) {
const resident = await getResidentGrantForAccount(account.key);
if (!resident) {
survivors.push(account);
continue;
}
const gates = resident.lease.gates;
// Request-level gates first: they do not depend on any window figure, and
// saying "this share does not cover Opus" is more useful than saying the
// slice is spent when both are true.
if (!isModelAllowed(gates.models, requestedModel)) {
logger.always(`[proxy] leased account=${account.label} does not cover model ${sanitizeForLog(requestedModel ?? "unknown")}`);
withheld.push({ label: account.label, reason: "model_not_allowed" });
continue;
}
if (gates.schedule && !isWithinSchedule(gates.schedule, now)) {
logger.always(`[proxy] leased account=${account.label} is outside its allowed hours`);
withheld.push({ label: account.label, reason: "out_of_window" });
continue;
}
// Account-level gates through the lender's own evaluator, on the lender's
// own numbers. A resident credential is minted from exactly one account, so
// the pool of the pool-wide slice is that one account and the formula
// collapses to the per-account case — which is precisely why the same code
// can serve both sides.
const quota = getOrCreateRuntimeState(account.key).quota;
const sessionResetAt = resetEpochToMs(quota?.sessionResetAt, now) ?? null;
const weeklyResetAt = resetEpochToMs(quota?.weeklyResetAt, now) ?? null;
const borrowed = await readShareWindowUsage(resident.grantId, account.key, sessionResetAt, weeklyResetAt);
const view = {
accountKey: account.key,
sessionUsed: quota?.sessionUsed ?? null,
weeklyUsed: quota?.weeklyUsed ?? null,
sessionResetAt,
weeklyResetAt,
borrowedSessionFraction: borrowed.sessionFraction,
borrowedWeeklyFraction: borrowed.weeklyFraction,
};
const poolUsage = await readSharePoolWindowUsage(resident.grantId, [view]);
// `gates.accounts` names the *lender's* accounts and means nothing here —
// the credential this lease governs is the only account in scope by
// construction. Carrying it over would filter the account out by its local
// label and withhold every leased request.
const { accounts: _lenderAccounts, ...localGates } = gates;
const decision = filterAccountsForGrant(leaseAsGrant(resident, localGates), [view], now, poolUsage);
if (decision.allowed.length === 0) {
const reason = summarizeAccountExclusions(decision.excluded);
logger.always(`[proxy] withholding leased account=${account.label}: ${reason}`);
withheld.push({ label: account.label, reason });
continue;
}
survivors.push(account);
}
return { accounts: survivors, withheld };
}
/**
* Dress a lease up as the grant its gates came from.
*
* The borrower has no grant record — only the signed projection of one — but the
* admission evaluator takes a grant. Rebuilding one here means both sides run
* the identical gate code rather than a borrower-side reimplementation that
* drifts from the lender's the first time a gate is added.
*/
function leaseAsGrant(resident, gates) {
const snapshot = resident.lease.entitlementSnapshot;
return {
schemaVersion: 1,
id: resident.grantId,
peerLabel: resident.lease.peerLabel,
// Never compared: the lease's own signature is what authenticates it, and
// it was checked before this account was allowed to reach here at all.
tokenHash: "",
tokenSalt: "",
level: "complete",
state: "active",
entitlement: snapshot === "unlimited"
? { ledger: "unlimited" }
: { ledger: "coins", coins: snapshot },
gates,
createdAt: resident.lease.issuedAt,
updatedAt: resident.lease.issuedAt,
};
}
/**
* Attribute a window movement to the grant that caused it.
*
* A no-op for the node's own traffic. Fire-and-forget: bookkeeping must never
* delay or fail a response the borrower is already receiving.
*/
function recordBorrowedWindowDelta(accountKey, before, after) {
const now = Date.now();
const observe = (grantId) => {
void recordShareWindowDelta({
grantId,
accountKey,
sessionBefore: before?.sessionUsed ?? null,
sessionAfter: after.sessionUsed,
sessionResetAt: resetEpochToMs(after.sessionResetAt, now) ?? null,
weeklyBefore: before?.weeklyUsed ?? null,
weeklyAfter: after.weeklyUsed,
weeklyResetAt: resetEpochToMs(after.weeklyResetAt, now) ?? null,
});
};
const share = getShareContext();
if (share) {
observe(share.grantId);
return;
}
// Not borrowed *from* us — but it may be borrowed *by* us. A leased account's
// windows move because this node spent them, and the lease's slice ceiling has
// nothing to measure unless that movement is recorded against the grant. The
// lender keeps the same book on its side; both read it with the same formula.
void getResidentGrantForAccount(accountKey)
.then((resident) => {
if (resident) {
observe(resident.grantId);
}
})
.catch(() => {
// Bookkeeping only — never fail a response the client already has.
});
}
/**
* Charge a completed borrowed request against its grant.
*
* A no-op for the node's own traffic, and fire-and-forget for the same reason
* as the window delta above.
*/
/**
* Bill a request served by a credential a lender provisioned here.
*
* The mirror of `settleBorrowedRequest`: that one charges a borrower on the
* lender's node, this one records what *this* node owes a lender whose
* credential it is holding. Without it a complete-mode heartbeat reports zero
* forever and the lender's balance never moves.
*/
function recordLeasedAccountSpend(accountLabel, model, usage) {
const coins = usageToCoins({
inputTokens: usage.inputTokens ?? 0,
outputTokens: usage.outputTokens ?? 0,
...(usage.cacheCreationTokens !== undefined
? { cacheCreationTokens: usage.cacheCreationTokens }
: {}),
...(usage.cacheReadTokens !== undefined
? { cacheReadTokens: usage.cacheReadTokens }
: {}),
}, model);
void recordResidentSpend(accountLabel, coins).catch(() => {
// Bookkeeping only — never fail a response the client already has.
});
}
/**
* Charge a served non-streaming response to whichever ledger owns the account.
*
* Both sides are no-ops unless the account is actually borrowed or leased, so
* this is safe to call on every response — which is the point: gating it on
* anything else is how it came to be skipped.
*/
function settleFromResponseUsage(account, responseJson) {
if (!responseJson || typeof responseJson !== "object") {
return;
}
const usage = responseJson.usage;
if (!usage) {
return;
}
const model = servedModelName(responseJson);
const tokens = {
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
};
settleBorrowedRequest(account.key, model, tokens);
recordLeasedAccountSpend(account.label, model, tokens);
}
function settleBorrowedRequest(accountKey, model, usage) {
const share = getShareContext();
if (!share) {
return;
}
void settleShareUsage({
grantId: share.grantId,
accountKey,
model: model ?? share.model,
usage: {
inputTokens: usage.inputTokens ?? 0,
outputTokens: usage.outputTokens ?? 0,
...(usage.cacheCreationTokens !== undefined
? { cacheCreationTokens: usage.cacheCreationTokens }
: {}),
...(usage.cacheReadTokens !== undefined
? { cacheReadTokens: usage.cacheReadTokens }
: {}),
},
...(share.holdId ? { holdId: share.holdId } : {}),
});
}
/**
* Remove what a borrower has no business seeing.
*
* `x-neurolink-account` carries the lender's account label, which for an OAuth
* account is their email address; the pool counters describe the shape of a
* pool that is not the borrower's. The borrower's own routing needs the quota
* and grant headers, and nothing else here.
*
* A no-op for the node's own traffic, where these headers are exactly the
* diagnostics the operator wants.
*/
function redactHeadersForBorrower(headers) {
if (!getShareContext()) {
return headers;
}
const redacted = {};
for (const [key, value] of Object.entries(headers)) {
const lower = key.toLowerCase();
if (lower === "x-neurolink-account" ||
lower === "x-neurolink-account-type") {
continue;
}
if (lower.startsWith("x-neurolink-pool-")) {
continue;
}
redacted[key] = value;
}
return redacted;
}
/**
* Record a heartbeat against the account's real utilization, and pause the grant
* when the two have disagreed too many times in a row.
*
* The lender's own request count on that account is read from its usage stats:
* an interval this node also used is not evidence about anybody, so the audit
* abstains rather than guessing.
*/
async function auditCompleteShareHeartbeat(grant, reportedCoins) {
const accountLabel = grant.provisionedAccount;
if (!accountLabel) {
// Nothing to audit against: the grant predates provisioning or was attached
// by hand. Say nothing rather than inventing a baseline.
return { paused: false, detail: "no provisioned account recorded" };
}
const state = accountRuntimeState.get(`anthropic:${accountLabel}`);
const stats = getAccountStats(accountLabel, "oauth");
const { verdict, shouldPause } = await recordAuditObservation({
grantId: grant.id,
accountLabel,
lenderRequestsTotal: stats?.successCount ?? 0,
observation: {
at: Date.now(),
sessionUsed: state?.quota?.sessionUsed ?? null,
weeklyUsed: state?.quota?.weeklyUsed ?? null,
reportedCoins,
// Replaced with the per-interval delta by the recorder.
lenderRequests: 0,
},
});
if (shouldPause) {
await setShareGrantState(grant.id, "paused");
return {
paused: true,
detail: verdict.drifted ? verdict.detail : "repeated unexplained usage",
};
}
return {
paused: false,
detail: verdict.drifted ? verdict.detail : "consistent",
};
}
/**
* Apply a complete-mode borrower's self-reported spend to their balance.
*
* Deliberately trusting at this layer and deliberately verified elsewhere: the
* lender cannot see a resident credential's traffic directly, but it can see the
* account's true utilization through the usage API, so under-reporting shows up
* as drift rather than as free capacity.
*/
async function recordReportedResidentSpend(grant, coins) {
await debitShareGrantCoins(grant.id, coins);
}
/**
* Compare a presented secret against the stored one without leaking where they
* diverge. Hand-rolled rather than `crypto.timingSafeEqual` for the same reason
* as `shareGrants.digestsMatch`: this module is reachable from a build whose
* `node:crypto` stub does not carry it.
*/
function secretsMatch(expected, presented) {
if (expected.length !== presented.length || expected.length === 0) {
return false;
}
let difference = 0;
for (let index = 0; index < expected.length; index += 1) {
difference |= expected.charCodeAt(index) ^ presented.charCodeAt(index);
}
return difference === 0;
}
/**
* Peer wire protocol version. Bumped when a borrower would misread an older
* node's answers — not when a field is added, which every version tolerates.
*/
const PEER_PROTOCOL_VERSION = 1;
/** What this node can do for a peer, so a borrower need not probe to find out. */
const PEER_CAPABILITIES = ["live", "complete", "handshake", "limits"];
/**
* What a borrower may know about the lender's pool.
*
* Deliberately shaped as "what is left for *you*", never "what the lender has":
* no labels, no per-account figures, no counts that would let a borrower infer
* how many credentials sit behind the tunnel. Enough to route on and nothing
* more, which is what `/peer/limits` is for.
*/
async function buildPeerLimitsSnapshot(grant, allowlist) {
const now = Date.now();
const accounts = await listAnthropicAccountsForUsage(allowlist);
const views = await Promise.all(accounts.map(async (account) => {
const quota = getOrCreateRuntimeState(account.key).quota;
const sessionResetAt = resetEpochToMs(quota?.sessionResetAt, now) ?? null;
const weeklyResetAt = resetEpochToMs(quota?.weeklyResetAt, now) ?? null;
const borrowed = await readShareWindowUsage(grant.id, account.key, sessionResetAt, weeklyResetAt);
return {
accountKey: account.key,
sessionUsed: quota?.sessionUsed ?? null,
weeklyUsed: quota?.weeklyUsed ?? null,
sessionResetAt,
weeklyResetAt,
borrowedSessionFraction: borrowed.sessionFraction,
borrowedWeeklyFraction: borrowed.weeklyFraction,
};
}));
const inScope = accountsInGrantScope(grant.gates, views).inScope;
const poolUsage = await readSharePoolWindowUsage(grant.id, inScope);
const decision = filterAccountsForGrant(grant, views, now, poolUsage);
const left = (ceiling, taken) => ceiling === undefined ? null : Math.max(0, ceiling - taken * 100);
return {
grantState: grant.state,
level: grant.level,
ledger: grant.entitlement.ledger,
...(grant.entitlement.ledger === "coins"
? { remainingCoins: Math.max(0, Math.floor(availableCoins(grant))) }
: {}),
servable: decision.allowed.length > 0,
...(decision.allowed.length === 0 && decision.excluded.length > 0
? { withheldReason: summarizeAccountExclusions(decision.excluded) }
: {}),
sliceLeftPct: {
session: left(grant.gates.maxSlice?.session5hPct, poolUsage.sessionFraction),
weekly: left(grant.gates.maxSlice?.weekly7dPct, poolUsage.weeklyFraction),
},
...(decision.allowed.length === 0
? (() => {
const retry = earliestShareRecoverySeconds(views, now);
return retry === undefined ? {} : { retryAfterSeconds: retry };
})()
: {}),
};
}
/**
* Resolve the share token on a `/peer/*` call.
*
* These routes sit outside the request gate — they consume no capacity, and
* running them through it would spend the grant's rate allowance on a call that
* exists to ask whether spending is possible — so each one authenticates itself.
*/
/**
* Narrow a peer-auth outcome to its refusing half.
*
* Explicit rather than `!auth.ok`, because one of the package's build steps
* compiles this file without `strictNullChecks`, where TypeScript will not
* narrow a boolean discriminant at all — the same reason `isShareRefusal` and
* `isLeaseRefusal` exist.
*/
function isPeerAuthRefusal(outcome) {
return !outcome.ok;
}
async function authenticatePeerRequest(headers) {
const token = extractShareToken(headers);
if (!token) {
return {
ok: false,
body: buildShareRefusal("missing_token", { status: 401 }).body,
};
}
const grant = await resolveShareToken(token);
if (!grant) {
return {
ok: false,
body: buildShareRefusal("unknown_token", { status: 401 }).body,
};
}
return { ok: true, grant };
}
/** The model an upstream JSON response says it served, when it says so. */
function servedModelName(payload) {
if (!payload || typeof payload !== "object") {
return undefined;
}
const model = payload.model;
return typeof model === "string" ? model : undefined;
}
/**
* Soonest window reset across the pool, in seconds.
*
* A withheld borrower's honest answer to "when should I come back" is when the
* lender's tightest window turns over — anything sooner is a guess that invites
* a retry storm.
*/
function earliestShareRecoverySeconds(views, now) {
const resets = views
.flatMap((view) => [view.sessionResetAt, view.weeklyResetAt])
.filter((value) => value !== null && value > now);
if (resets.length === 0) {
return undefined;
}
return Math.max(1, Math.round((Math.min(...resets) - now) / 1000));
}
async function loadClaudeProxyAccounts(args) {
const { ctx, body, accountStrategy, primaryAccountKey, accountAllowlist, quotaRoutingEnabled = isQuotaRoutingEnabled(), sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), ranking = "expiry-first", preferPrimary = false, sessionAffinityEnabled = false, sessionAffinityIdleTtlMs = 3_600_000, spillInflight = 0, setRoutingDecision, } = args;
const fs = await import("fs");
const os = await import("os");
const accounts = [];
const legacyCredPath = `${os.homedir()}/.neurolink/anthropic-credentials.json`;
const { tokenStore } = await import("../../auth/tokenStore.js");
const persistedCooldowns = await loadAccountCooldowns();
if (!startupPruneDone) {
startupPrune ??= tokenStore
.pruneExpired()
.then(() => {
startupPruneDone = true;
})
.finally(() => {
startupPrune = undefined;
});
await startupPrune;
}
const inventory = await tokenStore.getProviderSnapshot();
const compoundKeys = Object.keys(inventory).filter((key) => key.startsWith("anthropic:"));
// Tracked so an empty pool can name the real cause: "every account is
// entitlement-blocked" is a different problem from "no credentials".
const entitlementBlockedLabels = [];
let skippedDisabledCount = 0;
let skippedForOtherReasons = 0;
for (const key of compoundKeys) {
if (!isAccountAllowed(key, accountAllowlist)) {
logger.debug(`[proxy] skipping account=${key} (not in account allowlist)`);
continue;
}
if (inventory[key].disabled) {
const existingState = getOrCreateRuntimeState(key);
const disabledReason = inventory[key].disabledReason;
// Older releases permanently disabled accounts after any refresh error,
// including timeouts, 429s and 5xx responses. Re-evaluate those legacy
// entries once under the terminal/transient classifier below.
const legacyTransientDisable = disabledReason === "refresh_failed";
if (legacyTransientDisable) {
await tokenStore.markEnabled(key);
logger.always(`[proxy] account=${key.split(":")[1] ?? key} re-enabled for legacy refresh failure recheck`);
existingState.permanentlyDisabled = false;
existingState.consecutiveRefreshFailures = 0;
}
else {
const label = key.split(":")[1] ?? key;
if (existingState.permanentlyDisabled) {
logger.debug(`[proxy] skipping disabled account=${label}`);
}
else {
// A flag set outside this worker (the CLI, another worker, an older
// build) is otherwise invisible at the default level. Say once that
// the account left the pool; re-enabling clears the marker, so the
// next disable is announced again.
const disabledAt = inventory[key].disabledAt;
logger.always(`[proxy] account=${label} is disabled in the token store (reason=${disabledReason ?? "unknown"}${disabledAt ? `, since ${new Date(disabledAt).toISOString()}` : ""}); left out of the pool. Re-enable with: neurolink auth enable ${key}`);
}
existingState.permanentlyDisabled = true;
if (disabledReason === "entitlement_blocked") {
entitlementBlockedLabels.push(key.split(":")[1] ?? key);
}
skippedDisabledCount += 1;
continue;
}
}
const tokens = inventory[key].tokens;
if (!tokens) {
skippedForOtherReasons += 1;
continue;
}
let accessToken = tokens.accessToken;
let refreshTok = tokens.refreshToken;
let expiresAt = tokens.expiresAt;
const label = key.split(":")[1] ?? key;
const accountType = tokens.tokenType === "Bearer" ? "oauth" : "api_key";
const existingState = getOrCreateRuntimeState(key);
const persistedCooldown = persistedCooldowns[key];
if (persistedCooldown?.coolingUntil > Date.now() &&
(!existingState.coolingUntil ||
persistedCooldown.coolingUntil > existingState.coolingUntil)) {
existingState.coolingUntil = persistedCooldown.coolingUntil;
existingState.coolingReason = persistedCooldown.reason;
}
const addAccount = () => {
accounts.push({
key,
label,
token: accessToken,
refreshToken: refreshTok,
expiresAt,
type: accountType,
persistTarget: { providerKey: key },
});
};
const isExpired = expiresAt ? expiresAt < Date.now() : false;
if (isExpired) {
if (existingState.permanentlyDisabled) {
continue;
}
if (existingState.coolingReason === "auth" &&
existingState.coolingUntil &&
existingState.coolingUntil > Date.now()) {
addAccount();
continue;
}
if (!refreshTok) {
logger.always(`[proxy] skipping account=${label} (expired, no refresh token)`);
await disableAccountUntilReauth({ key, label, token: accessToken, type: "oauth" }, existingState, "missing_refresh_token");
continue;
}
const tempAccount = {
token: accessToken,
refreshToken: refreshTok,
expiresAt,
label,
};
const refreshed = await refreshTokenFromLatest(tempAccount, {
providerKey: key,
});
if (!refreshed.success) {
const account = {
key,
label,
token: accessToken,
type: "oauth",
};
if (isPermanentRefreshFailure(refreshed)) {
logger.always(`[proxy] skipping account=${label} (refresh token rejected: ${refreshed.error?.slice(0, 200) ?? "unknown"})`);
await disableAccountUntilReauth(account, existingState, "refresh_invalid");
}
else {
const coolingUntil = await coolAccountAfterTransientRefreshFailure(account, existingState);
logger.always(`[proxy] account=${label} refresh temporarily unavailable (${refreshed.status ?? "network"}); cooling until ${new Date(coolingUntil).toISOString()} and rotating`);
addAccount();
}
continue;
}
accessToken = tempAccount.token;
refreshTok = tempAccount.refreshToken;
expiresAt = tempAccount.expiresAt;
await tokenStore.saveTokens(key, {
accessToken,
refreshToken: refreshTok,
expiresAt: expiresAt ?? Date.now() + 3600_000,
tokenType: "Bearer",
});
logger.always(`[proxy] refreshed expired account=${key.split(":")[1] ?? key} at startup`);
await clearAuthCooldownAfterRefresh({ key }, existingState);
}
addAccount();
}
if (accounts.length === 0 &&
shouldLoadFallbackCredential(compoundKeys.length, LEGACY_ANTHROPIC_ACCOUNT_KEY, accountAllowlist)) {
try {
const creds = JSON.parse(fs.readFileSync(legacyCredPath, "utf8"));
const legacyAccount = await tryLoadLegacyAccount(creds, legacyCredPath);
if (legacyAccount) {
accounts.push(legacyAccount);
}
}
catch {
// file absent or invalid
}
}
if (process.env.ANTHROPIC_API_KEY &&
accounts.length === 0 &&
shouldLoadFallbackCredential(compoundKeys.length, ENV_ANTHROPIC_ACCOUNT_KEY, accountAllowlist)) {
accounts.push({
key: ENV_ANTHROPIC_ACCOUNT_KEY,
label: "env",
token: process.env.ANTHROPIC_API_KEY,
type: "api_key",
});
}
if (accounts.length === 0) {
// Once every account has been disabled on entitlement the loop never runs,
// so this is the only place the client can learn why. A flat 401 here would
// send the user to re-authenticate, which cannot fix an org policy.
if (entitlementBlockedLabels.length > 0 &&
entitlementBlockedLabels.length === skippedDisabledCount &&
skippedForOtherReasons === 0) {
const entitlementMessage = buildEntitlementErrorMessage({
status: 403,
accounts: entitlementBlockedLabels,
message: "OAuth authentication is not allowed for this organization.",
errorCode: "oauth_not_allowed_for_organization",
});
return {
failure: {
status: 403,
message: entitlementMessage,
errorType: "permission_error",
},
};
}
const noCredentialsMessage = accountAllowlist
? "No allowed Anthropic credentials are currently available"
: compoundKeys.length > 0
? "Configured Anthropic accounts are disabled or unavailable"
: "No Anthropic credentials found";
return {
failure: {
status: 401,
message: noCredentialsMessage,
errorType: "authentication_error",
},
};
}
for (const account of accounts) {
reconcileEligibleAccountRuntimeState(account);
}
await seedRuntimeQuotasFromDisk(accounts);
const eligibleAccounts = accounts.filter((account) => {
return !getOrCreateRuntimeState(account.key).permanentlyDisabled;
});
// Peer-sharing account gates. A borrowed request may only draw on accounts
// its grant allows, that still leave the lender's reserved headroom intact,
// and whose window slice the grant has not already spent. These are account
// properties rather than request properties, which is why they filter the
// pool here instead of refusing at the inbound gate.
const leaseExpiryFiltered = await dropExpiredResidentAccounts(eligibleAccounts);
const leaseScopeFiltered = await dropLeaseDisallowedAccounts(leaseExpiryFiltered.accounts, typeof body.model === "string" ? body.model : undefined);
const leaseFiltered = {
accounts: leaseScopeFiltered.accounts,
withheld: [...leaseExpiryFiltered.withheld, ...leaseScopeFiltered.withheld],
};
const leasedAccounts = leaseFiltered.accounts;
if (leasedAccounts.length === 0 && leaseFiltered.withheld.length > 0) {
// Every account here belongs to a lender whose lease has lapsed. The
// re-authentication message below would be actively wrong — it would send
// the borrower to OAuth into somebody else's account, which cannot work and
// should not be attempted. Say what actually happened instead.
const detail = leaseFiltered.withheld
.map((entry) => `${entry.label} (${entry.reason})`)
.join(", ");
// Scope and lifetime need different advice. Telling someone to re-sync when
// the lender simply never lent them this model sends them in circles.
const scopeOnly = leaseFiltered.withheld.every((entry) => entry.reason === "model_not_allowed");
const leaseMessage = scopeOnly
? `Borrowed account(s) do not cover the requested model: ${detail}. ` +
`Ask the lender to widen the share, or use a model it allows.`
: `Borrowed account(s) are no longer covered by a lease: ${detail}. ` +
`Run 'neurolink proxy peer sync' to check in with the lender, or ask them to resume the share.`;
return {
failure: {
status: 403,
message: leaseMessage,
errorType: "permission_error",
},
};
}
const shareFiltered = await applyShareAccountGates({
accounts: leasedAccounts,
});
if (shareFiltered.refusal) {
return { failure: shareFiltered.refusal };
}
const enabledAccounts = shareFiltered.accounts;
if (enabledAccounts.length === 0) {
const reauthMsg = formatReauthMessage(accounts.map((account) => account.label));
return {
failure: {
status: 401,
message: reauthMsg,
errorType: "authentication_error",
},
};
}
// Resolved ahead of account selection (rather than after, as before) so
// sessionId is available to selectClaudeProxyAccountOrder for session
// affinity. normalizeClaudeRequestForAnthropic below returns a
// shallow clone and never mutates `body`, so extracting earlier sees
// exactly the same snapshot extractSnapshotBody always saw.
const clientHeaders = ctx.headers ?? {};
const clientSnapshotBody = extractSnapshotBody(body);
const { orderedAccounts, metricsByKey } = selectClaudeProxyAccountOrder({
enabledAccounts,
accountStrategy,
primaryAccountKey,
quotaRoutingEnabled,
sessionSoftLimit,
sessionResetToleranceMs,
requestedModel: typeof body.model === "string" ? body.model : undefined,
sessionId: clientSnapshotBody?.sessionId,
ranking,
preferPrimary,
sessionAffinityEnabled,
sessionAffinityIdleTtlMs,
spillInflight,
setRoutingDecision,
});
if (accountStrategy === "fill-first" &&
quotaRoutingEnabled &&
enabledAccounts.length > 1) {
scheduleAdaptiveQuotaRefreshes(enabledAccounts, orderedAccounts, sessionSoftLimit, metricsByKey);
}
const normalizedAnthropicBody = normalizeClaudeRequestForAnthropic(body);
const bodyStr = JSON.stringify(normalizedAnthropicBody);
const requestStart = Date.now();
const toolCount = Array.isArray(body.tools) ? body.tools.length : 0;
const url = "https://api.anthropic.com/v1/messages?beta=true";
return {
accounts,
enabledAccounts,
orderedAccounts,
bodyStr,
requestStart,
toolCount,
url,
clientHeaders,
isClaudeClientRequest: isLikelyClaudeClient(clientHeaders, clientSnapshotBody),
sessionId: clientSnapshotBody?.sessionId,
};
}
/**
* The model that actually served a fallback turn.
*
* One request can leave more than one leg's model in metadata: the Codex leg
* records its model on the failure path before the chain moves on, and the
* configured chain tries Codex first. Choosing by key precedence therefore
* billed a Vertex-served turn as the Codex model — the exact misattribution
* this file's pricing fix exists to prevent. The account type that reached
* `logFinalRequest` is the only authoritative signal for which leg answered,
* so select on that and ignore the other leg's leftovers.
*/
export function resolveServedFallbackModel(metadata, accountType) {
const served = accountType === "vertex"
? metadata.vertexFallbackModel
: metadata.codexFallbackModel;
return typeof served === "string" && served.length > 0 ? served : undefined;
}
function clearClaudeFallbackTerminalEvidence(ctx) {
for (const key of [
"codexFallbackAccount",
"codexFallbackAccountType",
"codexFallbackModel",
"vertexFallbackModel",
"codexFallbackFailureUsage",
"codexFallbackDeferredFailure",
"sdkFallbackFailure",
]) {
delete ctx.metadata[key];
}
}
async function executeClaudeFallbackTranslation(args) {
const { ctx, body, tracer, requestStartTime, options, providerLabel, idleTimeoutMs = FALLBACK_STREAM_IDLE_TIMEOUT_MS, } = args;
clearClaudeFallbackTerminalEvidence(ctx);
const parsed = parseClaudeRequest(body);
const provider = typeof options.provider === "string" ? options.provider : undefined;
const model = typeof options.model === "string" ? options.model : undefined;
const shared = {
ctx,
format: "claude",
requestModel: body.model,
parsed,
attempts: [{ provider, model, label: providerLabel }],
tracer,
requestStartTime,
options,
attemptTimeoutMs: idleTimeoutMs,
};
if (body.stream) {
// Prime only the first actual upstream output. Errors before it still reach
// the configured fallback chain; after return no delivered output is replayed.
return handleTranslatedStreamRequest({
...shared,
prefetchFirstOutput: true,
});
}
return handleTranslatedJsonRequest({
...shared,
deferFinalOnError: true,
});
}
async function executeClaudeFallbackWithRetry(args) {
let lastError;
for (let retry = 0; retry <= MAX_FALLBACK_NETWORK_RETRIES; retry += 1) {
try {
return await executeClaudeFallbackTranslation(args);
}
catch (error) {
lastError = error;
if (getProxyUpstreamFailure(error)?.retryable === false ||
!isRetryableNetworkError(error) ||
retry === MAX_FALLBACK_NETWORK_RETRIES) {
throw error;
}
const delayMs = TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS[retry] ?? 250;
logger.always(`[proxy] retrying fallback=${args.providerLabel} after transient network error (${retry + 1}/${MAX_FALLBACK_NETWORK_RETRIES}) in ${delayMs}ms: ${redactProviderErrorMessage(describeTransportError(error))}`);
await sleep(delayMs);
}
}
throw lastError;
}
/**
* Run the configured `codex` fallback through the native pooled Codex route.
*
* Streaming clients receive incremental output. Once the stream is returned,
* failures are terminal SSE errors; only pre-output failures may try a fallback.
*/
async function executeClaudeCodexFallback(args) {
const { ctx, body, model, reasoningEffort, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
clearClaudeFallbackTerminalEvidence(ctx);
const codexCtx = {
...ctx,
requestId: `${ctx.requestId}:codex-fallback`,
method: "POST",
path: "/backend-api/codex/responses",
headers: {
...ctx.headers,
"content-type": "application/json",
accept: "text/event-stream",
},
query: {},
params: {},
body: convertClaudeRequestToCodex(body, model, reasoningEffort),
metadata: { ...ctx.metadata, "neurolink.codexFallback": true },
// Keep the child attribution isolated until its stream has passed
// validation. A failed Codex attempt must not look like a served request.
responseHeaders: {},
};
const childResponse = await handleCodexResponsesRequest(codexCtx);
const childObservers = takeProxyResponseObservers(codexCtx.metadata);
let finishChildAccounting = () => { };
const childAccounting = new Promise((resolve) => {
finishChildAccounting = resolve;
});
// The translated child has no HTTP runtime of its own. Drive its terminal
// observers here so stream failures enrich the actual Codex attempt, while
// the parent remains the sole client request in final success/error totals.
const codexResponse = trackProxyResponse(childResponse, finishChildAccounting, {
onTerminal: (details) => Promise.allSettled(childObservers.map(async (observer) => observer.onTerminal?.(details))),
});
registerProxyResponseObserver(ctx.metadata, {
onTerminal: () => childAccounting,
});
const recordCodexUsage = (result, terminal = true) => {
ctx.metadata.codexFallbackModel = model;
if (!terminal) {
return;
}
try {
tracer?.setModelSubstitution(body.model, model, "openai");
tracer?.setServedAccount(codexHeaders["x-neurolink-account"] ?? "unknown", "codex-oauth");
if (result?.usage?.inputTokensObserved &&
result.usage.outputTokensObserved) {
tracer?.setUsage({
inputTokens: result.usage.input,
outputTokens: result.usage.output,
// `?? 0` alone would record a Codex reply that carried no
// `input_tokens_details` as an observed cache miss. This is the
// leg most fallback traffic actually takes, so it is the one that
// skews the measured Codex cache rate.
cacheReadTokens: result.usage.cacheReadTokens ?? 0,
cacheCreationTokens: result.usage.cacheCreationTokens ?? 0,
cacheReadTokensObserved: result.usage.cacheReadTokensObserved === true,
cacheCreationTokensObserved: result.usage.cacheCreationTokensObserved === true,
reasoningTokens: result.usage.reasoning,
}, { recordMetrics: false });
}
}
catch {
// Instrumentation must never turn completed output into a failed stream.
}
};
const codexHeaders = { ...(codexCtx.responseHeaders ?? {}) };
ctx.metadata.codexFallbackAccount = codexHeaders["x-neurolink-account"];
ctx.metadata.codexFallbackAccountType =
codexHeaders["x-neurolink-account-type"] ?? "codex-oauth";
if (body.stream) {
const bridge = await createCodexFallbackStream(codexResponse, body.model);
ctx.responseHeaders ??= {};
Object.assign(ctx.responseHeaders, redactHeadersForBorrower(codexHeaders));
const account = codexHeaders["x-neurolink-account"] ?? "";
const accountType = codexHeaders["x-neurolink-account-type"] ?? "codex-oauth";
let settled = false;
let captured = "";
let responseBytes = 0;
let captureTruncated = false;
const finish = (status, result, errorType, message, failure) => {
if (settled) {
return;
}
settled = true;
ctx.abortSignal?.removeEventListener("abort", cancel);
if (status >= 400) {
ctx.metadata.terminalErrorType = errorType;
}
recordCodexUsage(result);
tracer?.end(status, Date.now() - requestStartTime);
logFinalRequest(status, account, accountType, errorType, message, {
inputTokens: result?.usage?.inputTokensObserved
? result.usage.input
: undefined,
outputTokens: result?.usage?.outputTokensObserved
? result.usage.output
: undefined,
cacheCreationTokens: result?.usage?.cacheCreationTokensObserved
? result.usage.cacheCreationTokens
: undefined,
cacheReadTokens: result?.usage?.cacheReadTokensObserved
? result.usage.cacheReadTokens
: undefined,
cacheReadTokensObserved: result?.usage?.cacheReadTokensObserved === true,
cacheCreationTokensObserved: result?.usage?.cacheCreationTokensObserved === true,
reasoningTokens: result?.usage?.reasoning,
errorCode: failure?.code,
retryable: failure?.retryable,
});
recordFallbackAttempt({
provider: "codex",
model,
status: status < 400 ? "success" : "failure",
durationMs: Date.now() - requestStartTime,
...(message ? { errorMessage: message } : {}),
});
logProxyBody({
phase: "client_response",
contentType: "text/event-stream",
body: captured,
bodySize: responseBytes,
sourceTruncated: captureTruncated,
responseStatus: status,
durationMs: Date.now() - requestStartTime,
});
};
const cancel = () => {
finish(499, undefined, "client_cancelled", "Client cancelled Codex fallback stream");
void bridge.cancel();
};
ctx.abortSignal?.addEventListener("abort", cancel, { once: true });
registerProxyResponseObserver(ctx.metadata, {
onTerminal: ({ outcome }) => {
if (outcome === "client_cancelled") {
cancel();
}
else if (outcome === "stream_error") {
finish(502, undefined, "stream_error", "Codex fallback stream failed");
void bridge.cancel();
}
},
});
const capture = (frame) => {
responseBytes += Buffer.byteLength(frame);
if (captured.length + frame.length > 1024 * 1024) {
captureTruncated = true;
}
if (captured.length < 1024 * 1024) {
captured += frame.slice(0, 1024 * 1024 - captured.length);
}
return frame;
};
async function* relay() {
try {
if (ctx.abortSignal?.aborted) {
cancel();
return;
}
let pending = bridge.frames.next();
while (!settled) {
let timer;
const heartbeat = new Promise((resolve) => {
timer = setTimeout(() => resolve(null), 15_000);
timer.unref();
});
let next;
try {
next = await Promise.race([pending, heartbeat]);
}
finally {
clearTimeout(timer);
}
if (settled) {
return;
}
if (next === null) {
yield capture(ClaudeStreamSerializer.pingEvent());
continue;
}
if (next.done === true) {
finish(200, next.value);
return;
}
const frame = capture(next.value);
if (frame.startsWith("event: content_block_delta\n") &&
/"(?:text|partial_json)":"(?:[^"\\]|\\.)+"/.test(frame)) {
ctx.metadata.firstUsefulOutputAt ??= Date.now();
}
if (frame.startsWith("event: message_stop\n")) {
// Finalize before exposing the terminal frame: a client can close
// immediately after receiving it without making another pull.
const completion = await bridge.frames.next();
if (completion.done !== true) {
throw new Error("Codex fallback emitted output after message_stop");
}
if (settled) {
return;
}
finish(200, completion.value);
yield frame;
return;
}
yield frame;
pending = bridge.frames.next();
}
}
catch (error) {
if (!settled) {
const detail = redactProviderErrorMessage(describeTransportError(error));
logger.always(`[proxy] Codex fallback stream failed: ${detail}`);
const failure = error instanceof CodexFallbackStreamError ? error : undefined;
const status = failure?.status ?? 502;
const errorBody = buildClaudeError(status, detail);
Object.assign(errorBody.error, {
code: failure?.code,
retryable: failure?.retryable,
});
const frames = [
capture(`event: error\ndata: ${JSON.stringify(errorBody)}\n\n`),
];
finish(status, failure?.usage
? {
text: "",
toolCalls: [],
finishReason: "end_turn",
usage: failure.usage,
}
: undefined, "stream_error", detail, failure);
yield* frames;
}
}
finally {
ctx.abortSignal?.removeEventListener("abort", cancel);
if (!settled) {
cancel();
}
await bridge.cancel();
await bridge.frames
.return({ text: "", toolCalls: [], finishReason: "end_turn" })
.catch(() => undefined);
}
}
return relay();
}
let parsed;
try {
parsed = await consumeCodexFallbackResponse(codexResponse);
}
catch (error) {
ctx.metadata.codexFallbackModel = model;
ctx.metadata.codexFallbackDeferredFailure = true;
if (error instanceof CodexFallbackResponseError) {
logger.always(`[proxy] Codex fallback returned ${error.status}: ${sanitizeForLog(error.responseBody, 500)}`);
}
if (error instanceof CodexFallbackStreamError) {
recordCodexUsage(error.usage
? {
text: "",
toolCalls: [],
finishReason: "end_turn",
usage: error.usage,
}
: undefined, false);
ctx.metadata.codexFallbackFailureUsage = error.usage;
}
throw error;
}
if (Object.keys(codexHeaders).length > 0) {
ctx.responseHeaders ??= {};
Object.assign(ctx.responseHeaders, redactHeadersForBorrower(codexHeaders));
}
const accountLabel = codexHeaders["x-neurolink-account"] ?? "";
const accountType = codexHeaders["x-neurolink-account-type"] ?? "codex-oauth";
const internal = {
content: parsed.text,
// Keep the original Anthropic model in the client wire response. The
// attribution headers above expose that Codex served the fallback.
model: body.model,
finishReason: parsed.finishReason,
...(parsed.usage ? { usage: parsed.usage } : {}),
toolCalls: parsed.toolCalls,
};
recordCodexUsage(parsed);
tracer?.end(200, Date.now() - requestStartTime);
const clientResponse = serializeClaudeResponse(internal, body.model);
observeClaudeJsonOutput(ctx.metadata, clientResponse);
logFinalRequest(200, accountLabel, accountType, undefined, undefined, {
inputTokens: parsed.usage?.inputTokensObserved
? parsed.usage.input
: undefined,
outputTokens: parsed.usage?.outputTokensObserved
? parsed.usage.output
: undefined,
cacheCreationTokens: parsed.usage?.cacheCreationTokens,
cacheReadTokens: parsed.usage?.cacheReadTokens,
reasoningTokens: parsed.usage?.reasoning,
});
const clientResponseText = JSON.stringify(clientResponse);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: clientResponseText,
bodySize: Buffer.byteLength(clientResponseText, "utf8"),
contentType: "application/json",
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
return clientResponse;
}
/**
* Try each borrowable peer in priority order once the local pool is spent.
*
* Returns a `Response` for a stream — which must keep streaming — the parsed
* JSON body otherwise, or `null` when no peer served, leaving the provider
* fallback chain to take over. The two success shapes are what the rest of this
* module returns as well; the route adapter tells them apart.
*
* Deliberately one attempt per peer: this path only runs after every local
* account has already been tried, so the request has spent most of its latency
* budget. Re-trying a peer that just declined would spend the rest of it.
*
* A borrowed request is never itself forwarded to a peer. Chaining a lend onto
* a lend would spend a third party's capacity under a grant that says nothing
* about them, and a cycle between two nodes would bounce a single request
* between them until something timed out.
*/
async function tryBorrowFromPeers(args) {
if (getShareContext()) {
return null;
}
const peers = await selectBorrowablePeers();
if (peers.length === 0) {
return null;
}
const stream = args.body.stream === true;
// Re-serialize the request as the client shaped it. The peer runs its own
// routing and its own pool, so forwarding our resolved account or attempt
// state would mean nothing to it.
const forwardBody = JSON.stringify(args.body);
for (const peer of peers) {
logger.always(`[proxy] local pool spent — trying peer=${peer.name}`);
const attempt = await forwardToPeer({ peer, body: forwardBody, stream });
if (attempt.ok) {
logger.always(`[proxy] served by peer=${peer.name}`);
args.logFinalRequest(attempt.response.status, `peer:${peer.name}`, "peer");
// Hand back what the rest of this module hands back: a locally
// constructed Response for a stream, a parsed object for JSON. Returning
// the fetch Response itself would be serialized to `{}` by the route
// adapter, which only recognizes Responses this process created.
if (stream) {
return new Response(attempt.response.body, {
status: attempt.response.status,
headers: {
"content-type": attempt.response.headers.get("content-type") ??
"text/event-stream",
"cache-control": "no-cache",
connection: "keep-alive",
},
});
}
return (await attempt.response.json().catch(() => null)) ?? null;
}
}
return null;
}
function getCodexFallbackInvalidRequestFailure(error) {
if (!(error instanceof CodexFallbackResponseError) || error.status !== 400) {
return null;
}
return {
status: error.status,
body: error.responseBody,
contentType: "application/json",
};
}
async function tryConfiguredClaudeFallbackChain(args) {
const { ctx, body, parsedFallbackRequest, fallbackPlan: providedFallbackPlan, modelRouter, tracer, requestStartTime, logProxyBody, logFinalRequest, } = args;
const fallbackPlan = providedFallbackPlan ??
buildProxyTranslationPlan({ provider: "anthropic", model: body.model }, modelRouter?.getFallbackChain() ?? [], body.model, parsedFallbackRequest);
ctx.metadata.fallbackPlan = fallbackPlan.attempts
.slice(1)
.map(({ provider, model, reasoningEffort }) => ({
provider,
model,
...(reasoningEffort ? { reasoningEffort } : {}),
}));
logProxyBody({
phase: "routing_decision",
contentType: "application/json",
body: {
stage: "anthropic_fallback",
attempts: fallbackPlan.attempts.slice(1),
},
});
tracer?.setFallbackInfo({
triggered: true,
attemptCount: fallbackPlan.attempts.slice(1).length,
reason: "all_anthropic_accounts_exhausted",
});
let lastFallbackError;
let terminalFailure;
let invalidRequestFailure;
for (const fallback of fallbackPlan.attempts.slice(1)) {
if (!fallback.provider || !fallback.model) {
continue;
}
const fallbackStart = Date.now();
try {
logger.always(`[proxy] fallback → ${fallback.provider}/${fallback.model}${fallback.reasoningEffort ? ` reasoning=${fallback.reasoningEffort}` : ""}`);
let response;
if (fallback.provider === "codex") {
// Codex is a local OAuth account pool, not a generic SDK provider.
// Calling its native route preserves account rotation and cooldowns.
response = await executeClaudeCodexFallback({
ctx,
body,
model: fallback.model,
reasoningEffort: fallback.reasoningEffort,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
});
}
else if (fallback.provider === "vertex" &&
isVertexAnthropicModel(fallback.model)) {
// Claude on Vertex speaks the Anthropic Messages API, so the request
// goes up as-is. The SDK path below runs its own tool loop and drops
// the caller's pending tool call, which strands every agentic turn.
const vertexModel = fallback.model;
const vertexAccount = `vertex/${vertexModel}`;
// A Codex leg that already failed left its own terminal evidence
// behind — its model, account, deferred-failure flag and failure
// usage. Every one of those is read again when this turn finalizes:
// the model would be logged and priced in place of the Vertex one,
// the deferred-failure branch would re-attribute the tracer to
// "openai" after the Vertex leg set it, and the failure usage would
// merge its reasoning tokens into this turn's counts. Start the leg
// clean, the same way a fresh upstream dispatch does.
clearClaudeFallbackTerminalEvidence(ctx);
// Billing follows the model that served the turn. The request log
// otherwise records `body.model` — the alias the client asked for —
// and prices Opus-on-Vertex at Sonnet rates.
ctx.metadata.vertexFallbackModel = vertexModel;
response = await executeVertexAnthropicFallback({
body,
model: vertexModel,
...(ctx.abortSignal ? { signal: ctx.abortSignal } : {}),
onTerminal: ({ usage, status, errorMessage }) => {
// The hop finalizes itself, like the Codex path does. Nothing else
// ends this tracer: the route returns before the stream drains, so
// without this the root span stays open and cost, request and
// duration metrics are never emitted.
//
// Billing has to follow the model that served the turn. Left on the
// requested alias, the pricing lookup misses and the charge is
// silently dropped, which is why the Vertex hop had token counters
// but no `proxy_cost_usd_total` series at all.
tracer?.setModelSubstitution(body.model, vertexModel, "vertex");
// Without this the spend lands under the Anthropic account that
// never served the turn, hiding Vertex cost inside a subscription
// account's figures.
tracer?.setServedAccount(vertexAccount, "vertex");
// setUsage must precede end(), which is what turns usage into cost.
tracer?.setUsage(usage);
tracer?.end(status, Date.now() - requestStartTime);
logFinalRequest(status, vertexAccount, "vertex", status === 200 ? undefined : "vertex_fallback_stream", errorMessage, {
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
cacheCreationTokens: usage.cacheCreationTokens,
cacheReadTokens: usage.cacheReadTokens,
});
},
});
}
else {
const availability = await ProviderHealthChecker.checkFallbackProviderAvailability(fallback.provider, fallback.model);
if (!availability.available) {
const reason = availability.reason ?? "provider unavailable";
logger.always(`[proxy] fallback ${fallback.provider}/${fallback.model} health-check failed (${reason}), skipping`);
recordFallbackAttempt({
provider: fallback.provider,
model: fallback.model,
status: "failure",
errorMessage: `[unavailable] ${reason}`,
durationMs: 0,
});
lastFallbackError = `[${fallback.provider}/${fallback.model}] unavailable: ${reason}`;
continue;
}
const options = buildProxyFallbackOptions(parsedFallbackRequest, {
provider: fallback.provider,
model: fallback.model,
});
response = await executeClaudeFallbackWithRetry({
ctx,
body,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
options: options,
providerLabel: fallback.provider,
});
}
if (fallback.provider === "codex" && body.stream) {
return { response };
}
recordFallbackAttempt({
provider: fallback.provider,
model: fallback.model,
status: "success",
durationMs: Date.now() - fallbackStart,
});
tracer?.setFallbackInfo({
triggered: true,
provider: fallback.provider,
model: fallback.model,
attemptCount: fallbackPlan.attempts.slice(1).length,
reason: "fallback_success",
});
if (fallback.provider !== "codex") {
// A different provider produced this response — say so, and report no
// quota. Emitting the last Anthropic snapshot here would attribute one
// provider's capacity to another's output.
publishLimitHeaders(ctx, {
quota: null,
source: "none",
servedBy: fallback.provider,
});
}
return { response };
}
catch (fallbackErr) {
if (fallbackErr instanceof ProxyContextPreflightError ||
getProxyTokenBudgetError(fallbackErr)) {
throw fallbackErr;
}
const failure = getProxyUpstreamFailure(fallbackErr);
const status = ctx.abortSignal?.aborted ? 499 : (failure?.status ?? 502);
terminalFailure = {
status,
message: failure?.message
? sanitizeForLog(`Configured fallback ${fallback.provider}/${fallback.model} failed: ${failure.message}`, 500)
: `Configured fallback ${fallback.provider}/${fallback.model} failed (HTTP ${status})`,
...(failure
? { errorCode: failure.code, retryable: failure.retryable }
: {}),
errorType: status === 499
? "client_cancelled"
: status === 504
? "upstream_timeout"
: status === 429
? "rate_limit_error"
: status === 401
? "authentication_error"
: status === 403
? "permission_error"
: status === 400
? "invalid_request_error"
: "api_error",
};
invalidRequestFailure =
getCodexFallbackInvalidRequestFailure(fallbackErr) ?? undefined;
const errMsg = redactProviderErrorMessage(fallbackErr instanceof Error
? fallbackErr.message
: String(fallbackErr));
let errorClass = "unknown";
if (errMsg.includes("Rate limit") ||
errMsg.includes("rate_limit") ||
errMsg.includes("max_parallel_requests")) {
errorClass = "rate_limit";
}
else if (errMsg.includes("context length") ||
errMsg.includes("ContextWindowExceeded")) {
errorClass = "context_overflow";
}
else if (errMsg.includes("no content or tool calls") ||
errMsg.includes("NoOutputGenerated")) {
errorClass = "empty_response";
}
else if (errMsg.includes("thinking_level") ||
errMsg.includes("Field required")) {
errorClass = "schema_mismatch";
}
else if (errMsg.includes("Resource exhausted")) {
errorClass = "provider_quota";
}
logger.always(`[proxy] fallback ${fallback.provider}/${fallback.model} failed [${errorClass}]: ${redactProviderErrorMessage(describeTransportError(fallbackErr))}`);
recordFallbackAttempt({
provider: fallback.provider,
model: fallback.model,
status: "failure",
errorMessage: `[${errorClass}] ${errMsg}`,
durationMs: Date.now() - fallbackStart,
});
lastFallbackError = `[${fallback.provider}/${fallback.model}] ${redactProviderErrorMessage(describeTransportError(fallbackErr))}`;
if (ctx.abortSignal?.aborted || terminalFailure.retryable === false) {
break;
}
}
}
return {
response: null,
lastErrorMessage: lastFallbackError,
terminalFailure,
...(invalidRequestFailure ? { invalidRequestFailure } : {}),
};
}
/** Preserve the final fallback status through every HTTP route adapter. */
function buildConfiguredClaudeFallbackFailure(args) {
const { failure, buildLoggedClaudeError, tracer, requestStartTime } = args;
tracer?.setError(failure.errorType, failure.message);
const body = buildLoggedClaudeError(failure.status, failure.message, failure.errorType, { errorCode: failure.errorCode, retryable: failure.retryable });
tracer?.end(failure.status, Date.now() - requestStartTime);
Object.assign(body.error, {
code: failure.errorCode,
retryable: failure.retryable,
});
return new Response(JSON.stringify(body), {
status: failure.status,
headers: { "content-type": "application/json" },
});
}
async function tryAutoClaudeFallback(args) {
const { ctx, body, tracer, requestStartTime, logProxyBody, logFinalRequest } = args;
const fallbackStart = Date.now();
try {
const parsed = parseClaudeRequest(body);
const plan = buildProxyTranslationPlan({ provider: "anthropic", model: body.model }, [], body.model, parsed, true);
logProxyRoutingPlan(logProxyBody, "auto_fallback", plan);
const autoAttempt = plan.attempts.find((attempt) => attempt.label === "auto-provider");
if (!autoAttempt) {
return { response: null };
}
logger.always("[proxy] fallback → auto-provider");
const options = buildProxyFallbackOptions(parsed);
const response = await executeClaudeFallbackWithRetry({
ctx,
body,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
options: options,
providerLabel: "auto-provider",
});
recordFallbackAttempt({
provider: "auto-provider",
model: body.model,
status: "success",
durationMs: Date.now() - fallbackStart,
});
tracer?.setFallbackInfo({
triggered: true,
provider: "auto-provider",
model: body.model,
attemptCount: 1,
reason: "fallback_success",
});
// See the configured-chain path: never attribute Anthropic quota to a
// response another provider produced.
publishLimitHeaders(ctx, {
quota: null,
source: "none",
servedBy: "auto-provider",
});
return { response };
}
catch (fallbackErr) {
if (fallbackErr instanceof ProxyContextPreflightError ||
getProxyTokenBudgetError(fallbackErr) ||
ctx.abortSignal?.aborted) {
throw fallbackErr;
}
const errorMessage = redactProviderErrorMessage(describeTransportError(fallbackErr));
logger.always(`[proxy] fallback auto-provider failed: ${errorMessage}`);
recordFallbackAttempt({
provider: "auto-provider",
model: body.model,
status: "failure",
errorMessage,
durationMs: Date.now() - fallbackStart,
});
tracer?.setFallbackInfo({
triggered: true,
provider: "auto-provider",
model: body.model,
attemptCount: 1,
reason: "fallback_failure",
});
const failure = getProxyUpstreamFailure(fallbackErr);
const status = failure?.status ?? 502;
return {
response: null,
lastErrorMessage: errorMessage,
terminalFailure: {
status,
message: sanitizeForLog(`Automatic fallback failed: ${failure?.message ?? errorMessage}`, 500),
errorCode: failure?.code,
retryable: failure?.retryable,
errorType: status === 504
? "upstream_timeout"
: status === 429
? "rate_limit_error"
: status === 401
? "authentication_error"
: status === 403
? "permission_error"
: status === 400
? "invalid_request_error"
: "api_error",
},
};
}
}
/** Human-readable list that stays short when the pool is large. */
function joinAccountLabels(labels, max = 5) {
const unique = [...new Set(labels)];
if (unique.length <= max) {
return unique.join(", ");
}
return `${unique.slice(0, max).join(", ")} and ${unique.length - max} more`;
}
/**
* The client-facing text for "every account is blocked by an organization
* entitlement policy". Names the accounts and the remedy, because the upstream
* message alone ("OAuth authentication is currently not allowed…") gives no
* indication that a pool was involved or which credential to fix.
*/
function buildEntitlementErrorMessage(failure) {
const count = new Set(failure.accounts).size;
const code = failure.errorCode ? ` (${failure.errorCode})` : "";
return (`All ${count} Anthropic account${count === 1 ? "" : "s"} are blocked by an ` +
`organization entitlement policy${code}. Accounts: ${joinAccountLabels(failure.accounts)}. Upstream: "${failure.message}". Ask an organization admin to re-enable ` +
`Claude Code OAuth access, then run: neurolink auth enable anthropic:<account>.`);
}
/** Name the subscription window a set of cooldowns is waiting on. */
function describeCoolingWindow(states) {
const reasons = new Set(states.map((state) => state.coolingReason));
if (reasons.size === 1) {
switch ([...reasons][0]) {
case "session":
return "5-hour subscription window";
case "weekly":
return "7-day subscription window";
case "unified":
return "subscription limit";
case "transient":
return "upstream burst limit";
default:
break;
}
}
return "upstream rate limits";
}
/**
* The provider's stated reason that paid extra usage is unavailable, taken from
* the first account that reports one. Anthropic sends this on every response
* (e.g. "org_level_disabled"); without it an exhausted pool looks like a proxy
* failure rather than a billing policy.
*/
function findOverageDisabledReason(accounts) {
for (const account of accounts) {
const quota = accountRuntimeState.get(account.key)?.quota;
if (quota?.overageDisabledReason &&
quota.overageStatus?.trim().toLowerCase() !== "allowed") {
return quota.overageDisabledReason;
}
}
return undefined;
}
/** Suffix explaining why paid overage cannot absorb an exhausted window. */
function buildOverageUnavailableSuffix(reason) {
if (!reason) {
return "";
}
return (` Paid extra usage is unavailable for this organization (${reason}), so ` +
`there is no additional capacity before the window resets.`);
}
/**
* The client-facing text for "every account has spent its model-scoped cap".
* Distinct from a normal rate limit: the same accounts remain healthy for every
* other model, so switching model is a real remedy and backing off is not.
*/
function buildScopedExhaustionMessage(exhaustion) {
const count = new Set(exhaustion.accounts).size;
return (`All ${count} Anthropic account${count === 1 ? "" : "s"} have exhausted the ` +
`model-scoped limit for ${exhaustion.model} (window "${exhaustion.scopeModel}"). ` +
`Other models remain available on this pool — switch model, or add an account ` +
`with remaining ${exhaustion.scopeModel} capacity. Earliest reset at ` +
`${new Date(exhaustion.earliestResetMs).toISOString()}.` +
buildOverageUnavailableSuffix(exhaustion.overageDisabledReason));
}
function buildClaudeAnthropicFailureResponse(args) {
const { tracer, requestStartTime, authFailureMessage, authCooldownMessage, invalidRequestFailure, entitlementFailure, scopedExhaustion, sawNetworkError, sawTransientFailure, sawRateLimit, lastError, lastTransportErrorCode, lastTransportScope, fallbackFailureMessage, orderedAccounts, buildLoggedClaudeError, logProxyBody, logFinalRequest, } = args;
// Ranked above the auth rung on purpose: an organization policy block is a
// more specific diagnosis than "authentication failed". But only when the
// policy explains the *whole* pool — otherwise a single blocked account would
// mask four genuine 5xx failures behind a 403, telling the client not to retry
// when retrying is exactly right. No retry-after: waiting cannot fix a policy.
const entitlementExplainsPool = entitlementFailure !== null &&
orderedAccounts.length > 0 &&
new Set(entitlementFailure.accounts).size >= orderedAccounts.length;
if (entitlementExplainsPool && entitlementFailure && !sawRateLimit) {
const message = buildEntitlementErrorMessage(entitlementFailure);
tracer?.setError("permission_error", message);
tracer?.end(403, Date.now() - requestStartTime);
return buildLoggedClaudeError(403, message, "permission_error");
}
if (authFailureMessage && !sawRateLimit) {
tracer?.setError("authentication_error", authFailureMessage);
tracer?.end(401, Date.now() - requestStartTime);
return buildLoggedClaudeError(401, authFailureMessage);
}
if (authCooldownMessage && !sawRateLimit) {
tracer?.setError("token_refresh_unavailable", authCooldownMessage);
tracer?.end(503, Date.now() - requestStartTime);
return buildLoggedClaudeError(503, authCooldownMessage, "token_refresh_unavailable");
}
if (invalidRequestFailure && !sawRateLimit) {
const parsedUpstream = parseClaudeErrorBody(invalidRequestFailure.body);
const preserveUpstreamBody = parsedUpstream.message !== undefined;
const message = summarizeErrorMessage(parsedUpstream.message ?? invalidRequestFailure.body);
const errorBodyText = preserveUpstreamBody
? invalidRequestFailure.body
: JSON.stringify(buildClaudeError(invalidRequestFailure.status, message, "invalid_request_error"));
const contentType = preserveUpstreamBody
? (invalidRequestFailure.contentType ?? "application/json")
: "application/json";
tracer?.setError("invalid_request_error", message);
tracer?.end(invalidRequestFailure.status, Date.now() - requestStartTime);
logFinalRequest(invalidRequestFailure.status, "", "final", "invalid_request_error", message);
logProxyBody({
phase: "client_response",
headers: { "content-type": contentType },
body: errorBodyText,
bodySize: Buffer.byteLength(errorBodyText, "utf8"),
contentType,
responseStatus: invalidRequestFailure.status,
durationMs: Date.now() - requestStartTime,
});
return new Response(errorBodyText, {
status: invalidRequestFailure.status,
headers: { "content-type": contentType },
});
}
if ((sawNetworkError || sawTransientFailure) && !sawRateLimit) {
const fallbackSuffix = fallbackFailureMessage
? ` Fallback also failed: ${fallbackFailureMessage}`
: "";
const transportDescription = lastTransportScope === "shared_provider_transport"
? "shared Anthropic network"
: lastTransportScope === "connection_transport"
? "Anthropic connection"
: null;
const msg = `${transportDescription ? `${transportDescription} failure prevented safe cross-account rotation` : "All Anthropic accounts failed due to transient upstream/network errors"}. Last error${lastTransportErrorCode ? ` (${lastTransportErrorCode})` : ""}: ${lastError instanceof Error
? lastError.message
: String(lastError ?? "unknown")}.${fallbackSuffix}`;
tracer?.setError("transient_error", msg.slice(0, 500));
tracer?.end(502, Date.now() - requestStartTime);
return buildLoggedClaudeError(502, msg, fallbackFailureMessage ? "fallback_exhausted" : "transient_error", {
...(lastTransportErrorCode
? { errorCode: lastTransportErrorCode }
: {}),
...(lastTransportScope ? { transportScope: lastTransportScope } : {}),
});
}
/** Emit the 429 the client sees, with an honest retry-after. */
const respondRateLimited = (message, retryAfterSec) => {
const errorBody = buildClaudeError(429, message, "overloaded_error");
tracer?.setError("rate_limit_error", message);
tracer?.end(429, Date.now() - requestStartTime);
logFinalRequest(429, "", "final", "rate_limit_error", message);
const errorBodyText = JSON.stringify(errorBody);
const headers = {
"content-type": "application/json",
"retry-after": String(retryAfterSec),
};
logProxyBody({
phase: "client_response",
headers,
body: errorBodyText,
bodySize: Buffer.byteLength(errorBodyText, "utf8"),
contentType: "application/json",
responseStatus: 429,
durationMs: Date.now() - requestStartTime,
});
return new Response(errorBodyText, { status: 429, headers });
};
// A model-scoped cap is spent on every account. No upstream call was made, so
// sawRateLimit is false and this must be caught before the generic
// "all accounts failed" 502 below, which would report the wrong cause and
// invite an immediate, guaranteed-to-fail retry.
if (scopedExhaustion) {
const message = buildScopedExhaustionMessage(scopedExhaustion);
logger.always(`[proxy] model-scoped limit exhausted for ${scopedExhaustion.model} on all accounts`);
return respondRateLimited(message, Math.max(1, Math.ceil((scopedExhaustion.earliestResetMs - Date.now()) / 1000)));
}
if (!sawRateLimit) {
const fallbackSuffix = fallbackFailureMessage
? ` Fallback also failed: ${fallbackFailureMessage}`
: "";
const msg = `All Anthropic accounts failed. Last error: ${lastError instanceof Error
? lastError.message
: String(lastError ?? "unknown")}.${fallbackSuffix}`;
tracer?.setError("all_accounts_failed", msg.slice(0, 500));
tracer?.end(502, Date.now() - requestStartTime);
return buildLoggedClaudeError(502, msg, fallbackFailureMessage ? "fallback_exhausted" : "all_accounts_failed");
}
const now = Date.now();
const activeRateLimitCooldowns = orderedAccounts
.map((account) => getOrCreateRuntimeState(account.key))
.filter((state) => state.coolingUntil &&
state.coolingUntil > now &&
state.coolingReason !== "auth");
const earliestRetryAt = Math.min(...activeRateLimitCooldowns.map((state) => state.coolingUntil ?? Number.POSITIVE_INFINITY));
const allAccountsCooling = orderedAccounts.length > 0 &&
activeRateLimitCooldowns.length === orderedAccounts.length;
const retryAfterSec = allAccountsCooling
? Math.max(1, Math.ceil((earliestRetryAt - now) / 1000))
: 1;
// Name the window that actually ran out. "rate limits" alone cannot be acted
// on: a 5-hour session window and a 7-day weekly window call for very
// different responses from the caller.
const windowLabel = describeCoolingWindow(activeRateLimitCooldowns);
const overageSuffix = buildOverageUnavailableSuffix(findOverageDisabledReason(orderedAccounts));
const errorMessage = allAccountsCooling
? `All ${orderedAccounts.length} Anthropic accounts are cooling after the ${windowLabel} was exhausted. Earliest retry at ${new Date(earliestRetryAt).toISOString()}.${overageSuffix}`
: `All ${orderedAccounts.length} accounts rate-limited after per-account retries.${overageSuffix}`;
logger.always(`[proxy] all accounts rate-limited, retry in ${retryAfterSec}s`);
return respondRateLimited(errorMessage, retryAfterSec);
}
async function handleAnthropicSuccessfulResponse(args) {
const { ctx, body, account, accountState, response, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, poolAccounts, logFinalRequest, } = args;
accountState.consecutiveRefreshFailures = 0;
logger.always(`[proxy] ← ${response.status} account=${account.label}`);
const quota = parseQuotaHeaders(response.headers, { model: body.model });
if (quota) {
// Attribute the window movement to the borrowing grant before the snapshot
// is overwritten — this is the only moment both the previous and the new
// utilization are in hand, and a slice ceiling is meaningless without it.
recordBorrowedWindowDelta(account.key, accountState.quota, quota);
// Stash the latest quota on runtime state so the next request can pick the
// account whose window resets soonest (max-utilization) and proactively
// skip rejected windows unless Anthropic explicitly permits overage.
accountState.quota = mergeQuotaSnapshot(accountState.quota, quota);
const cooldownUpdate = reconcileCooldownFromQuota(accountState, quota, Date.now());
if (cooldownUpdate?.kind === "cooled") {
saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
// Non-fatal: cooldown is already active in memory.
});
}
else if (cooldownUpdate?.kind === "cleared") {
clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
// Non-fatal: the next successful response will reconcile again.
});
}
saveAccountQuota(account.key, quota).catch(() => {
// Non-fatal: quota persistence is best-effort
});
}
const responseHeaders = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
tracer?.logUpstreamResponseHeaders(responseHeaders);
// Surface limits to the client. `quota` is non-null only when this upstream
// response actually carried the unified headers; otherwise fall back to the
// account's last snapshot and label it as such, so a consumer never mistakes
// a carried-over reading for a fresh one.
publishLimitHeaders(ctx, {
upstreamHeaders: response.headers,
quota: quota ?? accountState.quota ?? null,
source: quota ? "live" : accountState.quota ? "snapshot" : "none",
account,
accountState,
servedBy: "anthropic",
attempt: attemptNumber,
...(poolAccounts ? { poolAccounts } : {}),
});
if (body.stream) {
return handleAnthropicStreamingSuccessResponse({
ctx,
body,
account,
accountState,
response,
responseHeaders,
tracer,
requestStartTime,
fetchStartMs,
attemptNumber,
finalBodyStr,
upstreamSpan,
logAttempt,
logProxyBody,
logFinalRequest,
onStreamTerminal,
});
}
return handleAnthropicJsonSuccessResponse({
ctx,
account,
response,
responseHeaders,
tracer,
requestStartTime,
fetchStartMs,
attemptNumber,
finalBodyStr,
upstreamSpan,
logAttempt,
logProxyBody,
logFinalRequest,
});
}
async function handleAnthropicStreamingSuccessResponse(args) {
const { ctx, body, account, accountState, response, responseHeaders, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, logFinalRequest, } = args;
if (!response.body) {
recordAttemptError(account.label, account.type, 502);
logAttempt(502, "stream_error", "No response body from upstream");
upstreamSpan?.end();
tracer?.setError("stream_error", "No response body from upstream");
tracer?.end(502, Date.now() - requestStartTime);
logFinalRequest(502, account.label, account.type, "stream_error", "No response body from upstream");
const clientError = buildClaudeError(502, "No response body from upstream");
const clientErrorBody = JSON.stringify(clientError);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: clientErrorBody,
bodySize: Buffer.byteLength(clientErrorBody, "utf8"),
contentType: "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 502,
durationMs: Date.now() - requestStartTime,
});
return { response: clientError, served: false };
}
const reader = response.body.getReader();
const preflight = await preflightAnthropicStream(reader);
if (preflight.kind === "transport_error") {
const message = describeTransportError(preflight.error);
const partialBody = Buffer.concat(preflight.chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
// The POST has already returned a response. The upstream may have started
// processing it, so replaying it on another account could duplicate work.
logger.always(`[proxy] stream failed before first chunk account=${account.label}: ${message}; returning terminal error to avoid replaying an ambiguous request`);
recordAttemptError(account.label, account.type, 502);
logAttempt(502, "stream_error", message, { retryable: false });
upstreamSpan?.end();
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: partialBody,
bodySize: Buffer.byteLength(partialBody, "utf8"),
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: response.status,
durationMs: Date.now() - fetchStartMs,
metadata: { logicalStatus: 502, transportError: message },
});
return {
response: finalizeAnthropicTerminalTransportError({
account,
tracer,
requestStartTime,
attemptNumber,
logProxyBody,
logFinalRequest,
errorType: "stream_error",
message,
}),
served: false,
};
}
if (preflight.kind === "empty") {
await reader.cancel().catch(() => {
// Best-effort release before rotating to another account.
});
logger.always(`[proxy] ← empty stream from account=${account.label}, trying next`);
recordAttemptError(account.label, account.type, 502);
logAttempt(502, "empty_stream", "Empty upstream stream", {
retryable: true,
});
tracer?.recordRetry(account.label, "empty_stream");
upstreamSpan?.end();
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: "",
bodySize: 0,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: response.status,
durationMs: Date.now() - fetchStartMs,
metadata: { logicalStatus: 502, upstreamErrorType: "empty_stream" },
});
return {
retryNextAccount: true,
failure: { message: "Empty upstream stream", rateLimit: false },
};
}
if (preflight.kind === "sse_error") {
await reader.cancel().catch(() => {
// Best-effort release before rotating to another account.
});
const bodyText = Buffer.concat(preflight.chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
const isRateLimit = preflight.errorType === "rate_limit_error";
const logicalStatus = isRateLimit ? 429 : 502;
const quota = parseQuotaHeaders(responseHeaders, { model: body.model });
const now = Date.now();
let sameAccountRetry;
if (isRateLimit) {
const retryAfterMs = parseRetryAfterMs(responseHeaders["retry-after"] ?? null);
const cooldownPlan = planCooldownFor429(quota, retryAfterMs, now, getUnifiedRateLimitStatus(responseHeaders), overagePolicy, typeof body.model === "string" ? body.model : undefined);
accountState.quota = quota
? mergeQuotaSnapshot(accountState.quota, quota)
: accountState.quota;
const rateLimitKind = cooldownPlan.reason === "transient" ? "transient" : "quota";
if (cooldownPlan.scope === "account" &&
(!accountState.coolingUntil ||
cooldownPlan.coolingUntil > accountState.coolingUntil)) {
accountState.coolingUntil = cooldownPlan.coolingUntil;
accountState.coolingReason = cooldownPlan.reason;
await saveAccountCooldown(account.key, cooldownPlan.coolingUntil, cooldownPlan.reason).catch(() => {
// Non-fatal: routing already has the in-memory cooldown.
});
}
recordAttemptError(account.label, account.type, 429, rateLimitKind);
logAttempt(429, "rate_limit_error", preflight.message, {
retryable: true,
rateLimitKind,
cooldownReason: cooldownPlan.reason,
});
if (!cooldownPlan.rotateImmediately) {
sameAccountRetry = {
coolingUntil: cooldownPlan.coolingUntil,
retryAfterMs,
};
}
}
else {
recordAttemptError(account.label, account.type, logicalStatus);
logAttempt(logicalStatus, preflight.errorType, preflight.message, {
retryable: true,
});
}
logger.always(`[proxy] immediate SSE ${preflight.errorType} account=${account.label}: ${preflight.message}; ${sameAccountRetry ? "transient, same account may retry" : "rotating"} before client commit`);
tracer?.recordRetry(account.label, isRateLimit
? "stream_rate_limit_before_commit"
: "stream_error_before_commit");
upstreamSpan?.end();
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: bodyText,
bodySize: Buffer.byteLength(bodyText, "utf8"),
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: response.status,
durationMs: Date.now() - fetchStartMs,
metadata: {
logicalStatus,
upstreamErrorType: preflight.errorType,
},
});
return {
retryNextAccount: true,
failure: {
message: preflight.message,
rateLimit: isRateLimit,
...(preflight.errorType === "overloaded_error"
? { retryDelayMs: getOverloadRotationDelayMs(attemptNumber) }
: {}),
...(sameAccountRetry ? { sameAccountRetry } : {}),
},
};
}
logAttempt(response.status, undefined, undefined, {
attemptDurationMs: Date.now() - fetchStartMs,
});
const streamOutcomeTracker = createStreamTerminalOutcomeTracker();
let mainStreamClosed = false;
const remainingStream = new ReadableStream({
start(controller) {
for (const chunk of preflight.chunks) {
controller.enqueue(chunk);
}
},
async pull(controller) {
if (mainStreamClosed) {
return;
}
try {
const { done, value } = await reader.read();
if (mainStreamClosed) {
return;
}
if (done) {
mainStreamClosed = true;
streamOutcomeTracker.complete();
controller.close();
return;
}
controller.enqueue(value);
}
catch (streamErr) {
const errMsg = describeTransportError(streamErr);
logger.always(`[proxy] mid-stream error account=${account.label}: ${errMsg}`);
streamOutcomeTracker.fail(errMsg);
if (!mainStreamClosed) {
mainStreamClosed = true;
const errorEvent = `event: error\ndata: ${JSON.stringify({ type: "error", error: { type: "api_error", message: `Upstream stream interrupted: ${errMsg}` } })}\n\n`;
controller.enqueue(new TextEncoder().encode(errorEvent));
controller.close();
}
}
},
cancel() {
mainStreamClosed = true;
streamOutcomeTracker.cancel();
return reader.cancel();
},
});
const { response: result, telemetryDone } = attachAnthropicSuccessStreamTelemetry({
ctx,
account,
response,
responseHeaders,
remainingStream,
streamOutcome: streamOutcomeTracker.outcome,
tracer,
requestStartTime,
attemptNumber,
finalBodyStr,
upstreamSpan,
logAttempt,
logProxyBody,
logFinalRequest,
});
void telemetryDone.then(() => onStreamTerminal?.(), () => onStreamTerminal?.());
return { response: result, holdsAccountAdmission: true, served: true };
}
function getStreamFailureDetails(outcome) {
if (outcome.kind === "upstream_error") {
return {
status: 502,
errorType: "stream_error",
message: outcome.message,
};
}
if (outcome.kind === "client_cancelled") {
return {
status: 499,
errorType: "client_cancelled",
message: "Client cancelled the streaming response",
};
}
return undefined;
}
function recordCommittedAnthropicStreamAttemptFailure(outcome, account, logAttempt) {
if (outcome.kind === "upstream_error") {
recordAttemptError(account.label, account.type, 502);
logAttempt(502, "stream_error", outcome.message, { retryable: false });
}
}
function attachAnthropicSuccessStreamTelemetry(args) {
const { ctx, account, response, responseHeaders, remainingStream, streamOutcome, tracer, requestStartTime, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
const { stream: clientCaptureStream, capture: clientCapture } = createRawStreamCapture();
let streamSource = remainingStream;
let telemetryDone;
if (tracer) {
try {
const { stream: interceptor, telemetry } = createSSEInterceptor({
captureRawText: true,
});
streamSource = streamSource.pipeThrough(interceptor);
const capturedTracer = tracer;
const capturedUpstreamSpan = upstreamSpan;
const capturedResponse = response;
const capturedRequestBytes = finalBodyStr.length;
const capturedAccountLabel = account.label;
const capturedAccountKey = account.key;
telemetryDone = Promise.all([telemetry, clientCapture, streamOutcome])
.then(([data, clientBody, rawOutcome]) => {
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
observeClaudeStreamOutput(ctx.metadata, data);
recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account, logAttempt);
capturedTracer.setUsage({
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
});
// Bill the borrowing grant from the same totals. A stream's usage is
// only final at message_delta, which is exactly here.
settleBorrowedRequest(capturedAccountKey, data.model, {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
cacheReadTokens: data.usage.cacheReadInputTokens,
});
recordLeasedAccountSpend(capturedAccountLabel, data.model, {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
cacheReadTokens: data.usage.cacheReadInputTokens,
});
capturedTracer.logStreamEvents(data.events);
capturedTracer.setResponseInfo(responseInfoFromStream(data));
const rateLimit5h = parseFloat(capturedResponse.headers.get("anthropic-ratelimit-unified-5h-utilization") ?? "");
const rateLimit7d = parseFloat(capturedResponse.headers.get("anthropic-ratelimit-unified-7d-utilization") ?? "");
const usageUpdate = {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
};
if (!isNaN(rateLimit5h)) {
usageUpdate.rateLimitAfter5h = rateLimit5h;
}
if (!isNaN(rateLimit7d)) {
usageUpdate.rateLimitAfter7d = rateLimit7d;
}
if (!isNaN(rateLimit5h) || !isNaN(rateLimit7d)) {
capturedTracer.setUsage(usageUpdate);
}
capturedTracer.logUpstreamResponseBody(data.rawText ?? "");
capturedTracer.recordMetrics();
capturedTracer.recordBodySizes(capturedRequestBytes, data.totalBytesReceived);
capturedUpstreamSpan?.end();
const failure = getStreamFailureDetails(terminalOutcome);
const usage = {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
};
if (failure) {
capturedTracer.setError(failure.errorType, failure.message);
capturedTracer.end(failure.status, Date.now() - requestStartTime);
logFinalRequest(failure.status, capturedAccountLabel, account.type, failure.errorType, failure.message, usage);
}
else {
capturedTracer.end(200, Date.now() - requestStartTime);
logFinalRequest(200, capturedAccountLabel, account.type, undefined, undefined, usage);
}
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: data.rawText ?? "",
sourceTruncated: data.rawTextTruncated,
bodySize: data.totalBytesReceived,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: capturedAccountLabel,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
logProxyBody({
phase: "client_response",
headers: responseHeaders,
body: clientBody.text,
sourceTruncated: clientBody.truncated,
bodySize: clientBody.totalBytes,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: capturedAccountLabel,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
})
.catch((error) => {
capturedTracer.setError("stream_telemetry_error", error instanceof Error ? error.message : String(error));
capturedUpstreamSpan?.end();
capturedTracer.end(500, Date.now() - requestStartTime);
logFinalRequest(500, capturedAccountLabel, account.type, "stream_telemetry_error", error instanceof Error ? error.message : String(error));
});
}
catch {
// Interceptor attachment failed after stream setup. Preserve delivery but
// still settle the request from the actual stream terminal outcome.
telemetryDone = streamOutcome
.then((outcome) => {
recordCommittedAnthropicStreamAttemptFailure(outcome, account, logAttempt);
const failure = getStreamFailureDetails(outcome);
upstreamSpan?.end();
if (failure) {
tracer.setError(failure.errorType, failure.message);
tracer.end(failure.status, Date.now() - requestStartTime);
logFinalRequest(failure.status, account.label, account.type, failure.errorType, failure.message);
}
else {
tracer.end(response.status, Date.now() - requestStartTime);
logFinalRequest(response.status, account.label, account.type);
}
})
.catch(() => undefined);
}
}
else {
upstreamSpan?.end();
try {
const { stream: noTracerInterceptor, telemetry: noTracerTelemetry } = createSSEInterceptor({
captureRawText: true,
});
streamSource = streamSource.pipeThrough(noTracerInterceptor);
const capturedAccountLabel = account.label;
const capturedAccountKey = account.key;
telemetryDone = Promise.all([
noTracerTelemetry,
clientCapture,
streamOutcome,
])
.then(([data, clientBody, rawOutcome]) => {
const terminalOutcome = mergeStreamTerminalOutcome(rawOutcome, data.streamErrorMessage, data.messageStopReceived);
observeClaudeStreamOutput(ctx.metadata, data);
recordCommittedAnthropicStreamAttemptFailure(terminalOutcome, account, logAttempt);
const failure = getStreamFailureDetails(terminalOutcome);
const usage = {
inputTokens: data.usage.inputTokens,
outputTokens: data.usage.outputTokens,
cacheCreationTokens: data.usage.cacheCreationInputTokens,
...oneHourCacheWriteFields(data.usage.cacheCreation1hInputTokens),
cacheReadTokens: data.usage.cacheReadInputTokens,
};
// Settled on the untraced path too: whether telemetry is exported has
// nothing to do with whether a borrower should be charged.
settleBorrowedRequest(capturedAccountKey, data.model, usage);
recordLeasedAccountSpend(capturedAccountLabel, data.model, usage);
if (failure) {
logFinalRequest(failure.status, capturedAccountLabel, account.type, failure.errorType, failure.message, usage);
}
else {
logFinalRequest(200, capturedAccountLabel, account.type, undefined, undefined, usage);
}
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: data.rawText ?? "",
sourceTruncated: data.rawTextTruncated,
bodySize: data.totalBytesReceived,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: capturedAccountLabel,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
logProxyBody({
phase: "client_response",
headers: responseHeaders,
body: clientBody.text,
sourceTruncated: clientBody.truncated,
bodySize: clientBody.totalBytes,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: capturedAccountLabel,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
logFinalRequest(500, account.label, account.type, "stream_telemetry_error", message);
});
}
catch {
clientCapture
.then((clientBody) => {
logProxyBody({
phase: "client_response",
headers: responseHeaders,
body: clientBody.text,
sourceTruncated: clientBody.truncated,
bodySize: clientBody.totalBytes,
contentType: responseHeaders["content-type"] ?? "text/event-stream",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 200,
durationMs: Date.now() - requestStartTime,
});
})
.catch(() => {
// Non-fatal
});
telemetryDone = streamOutcome
.then((outcome) => {
recordCommittedAnthropicStreamAttemptFailure(outcome, account, logAttempt);
const failure = getStreamFailureDetails(outcome);
if (failure) {
logFinalRequest(failure.status, account.label, account.type, failure.errorType, failure.message);
}
else {
logFinalRequest(response.status, account.label, account.type);
}
})
.catch(() => undefined);
}
}
const clientStream = streamSource.pipeThrough(clientCaptureStream);
registerProxyResponseObserver(ctx.metadata, {
onTerminal: () => telemetryDone,
});
// Limit headers published on the context are applied here rather than left
// to the runtime wrapper: this Response goes straight to the client on every
// mount (proxy runtime and the generic server adapters alike), so the
// streaming path has to carry them itself. The previous five-name allowlist
// forwarded only the legacy counters and dropped the unified subscription
// windows entirely.
const clientResponseHeaders = {
...(ctx.responseHeaders ?? {}),
"content-type": "text/event-stream",
"cache-control": "no-cache",
connection: "keep-alive",
};
return {
response: new Response(clientStream, {
status: response.status,
headers: clientResponseHeaders,
}),
telemetryDone,
};
}
async function handleAnthropicJsonSuccessResponse(args) {
const { ctx, account, response, responseHeaders, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
const responseText = await response.text();
logAttempt(response.status, undefined, undefined, {
attemptDurationMs: Date.now() - fetchStartMs,
});
tracer?.logUpstreamResponseBody(responseText);
logProxyBody({
phase: "upstream_response",
headers: responseHeaders,
body: responseText,
bodySize: Buffer.byteLength(responseText, "utf8"),
contentType: responseHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: response.status,
durationMs: Date.now() - fetchStartMs,
});
logProxyBody({
phase: "client_response",
headers: responseHeaders,
body: responseText,
bodySize: Buffer.byteLength(responseText, "utf8"),
contentType: responseHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: response.status,
durationMs: Date.now() - requestStartTime,
});
const responseJson = JSON.parse(responseText);
observeClaudeJsonOutput(ctx.metadata, responseJson);
// Settlement is not diagnostics. It ran inside the tracer branch, so a node
// with tracing off served every borrowed request for free and the lender's
// ledger never moved — settle from the response itself instead.
settleFromResponseUsage(account, responseJson);
if (tracer && responseJson && typeof responseJson === "object") {
const usage = responseJson.usage;
if (usage) {
tracer.setUsage({
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
...oneHourCacheWriteFields(readCacheCreation1hTokens(usage)),
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
});
const rateLimit5h = parseFloat(response.headers.get("anthropic-ratelimit-unified-5h-utilization") ??
"");
const rateLimit7d = parseFloat(response.headers.get("anthropic-ratelimit-unified-7d-utilization") ??
"");
if (!isNaN(rateLimit5h) || !isNaN(rateLimit7d)) {
const usageWithRates = {
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
...oneHourCacheWriteFields(readCacheCreation1hTokens(usage)),
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
};
if (!isNaN(rateLimit5h)) {
usageWithRates.rateLimitAfter5h = rateLimit5h;
}
if (!isNaN(rateLimit7d)) {
usageWithRates.rateLimitAfter7d = rateLimit7d;
}
tracer.setUsage(usageWithRates);
}
}
tracer.setResponseInfo(extractResponseInfo(responseJson));
tracer.recordMetrics();
const responseJsonStr = JSON.stringify(responseJson);
tracer.recordBodySizes(finalBodyStr.length, responseJsonStr.length);
upstreamSpan?.end();
tracer.end(response.status, Date.now() - requestStartTime);
logFinalRequest(response.status, account.label, account.type, undefined, undefined, {
inputTokens: usage?.input_tokens,
outputTokens: usage?.output_tokens,
cacheCreationTokens: usage?.cache_creation_input_tokens,
...oneHourCacheWriteFields(readCacheCreation1hTokens(usage)),
cacheReadTokens: usage?.cache_read_input_tokens,
});
}
else {
upstreamSpan?.end();
const noTracerUsage = responseJson && typeof responseJson === "object"
? responseJson.usage
: undefined;
logFinalRequest(response.status, account.label, account.type, undefined, undefined, {
inputTokens: noTracerUsage?.input_tokens,
outputTokens: noTracerUsage?.output_tokens,
cacheCreationTokens: noTracerUsage?.cache_creation_input_tokens,
...oneHourCacheWriteFields(readCacheCreation1hTokens(noTracerUsage)),
cacheReadTokens: noTracerUsage?.cache_read_input_tokens,
});
}
return { response: responseJson, served: true };
}
async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
const { ctx, account, accountState, requestedModel, retryResp, tracer, requestStartTime, fetchStartMs, attemptNumber, finalBodyStr, upstreamSpan, logAttempt, logProxyBody, logFinalRequest, } = args;
const retryQuota = parseQuotaHeaders(retryResp.headers, {
model: requestedModel,
});
if (retryQuota) {
// Keep the auth-retry success path in parity with the main success path:
// stash quota for proactive selection and reconcile a rejected window.
accountState.quota = mergeQuotaSnapshot(accountState.quota, retryQuota);
const cooldownUpdate = reconcileCooldownFromQuota(accountState, retryQuota, Date.now());
if (cooldownUpdate?.kind === "cooled") {
saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
// Non-fatal: cooldown is already active in memory.
});
}
else if (cooldownUpdate?.kind === "cleared") {
clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
// Non-fatal: the next successful response will reconcile again.
});
}
saveAccountQuota(account.key, retryQuota).catch((error) => {
logger.debug("[proxy] Failed to persist account quota after auth retry", {
account: account.label,
error: error instanceof Error ? error.message : String(error),
});
});
}
const retryRespHeaders = Object.fromEntries([...retryResp.headers.entries()]);
const retryText = await retryResp.text();
logAttempt(retryResp.status, undefined, undefined, {
attemptDurationMs: Date.now() - fetchStartMs,
});
tracer?.logUpstreamResponseHeaders(retryRespHeaders);
tracer?.logUpstreamResponseBody(retryText);
logProxyBody({
phase: "upstream_response",
headers: retryRespHeaders,
body: retryText,
bodySize: Buffer.byteLength(retryText, "utf8"),
contentType: retryRespHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: retryResp.status,
durationMs: Date.now() - fetchStartMs,
});
logProxyBody({
phase: "client_response",
headers: retryRespHeaders,
body: retryText,
bodySize: Buffer.byteLength(retryText, "utf8"),
contentType: retryRespHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: retryResp.status,
durationMs: Date.now() - requestStartTime,
});
const retryJson = JSON.parse(retryText);
observeClaudeJsonOutput(ctx.metadata, retryJson);
// A response served after an auth retry is a served response: it costs the
// lender's account exactly what any other one does.
settleFromResponseUsage(account, retryJson);
if (tracer && retryJson && typeof retryJson === "object") {
const retryUsage = retryJson.usage;
if (retryUsage) {
tracer.setUsage({
inputTokens: retryUsage.input_tokens ?? 0,
outputTokens: retryUsage.output_tokens ?? 0,
cacheCreationTokens: retryUsage.cache_creation_input_tokens ?? 0,
...oneHourCacheWriteFields(readCacheCreation1hTokens(retryUsage)),
cacheReadTokens: retryUsage.cache_read_input_tokens ?? 0,
});
}
tracer.setResponseInfo(extractResponseInfo(retryJson));
tracer.recordMetrics();
const retryJsonStr = JSON.stringify(retryJson);
tracer.recordBodySizes(finalBodyStr.length, retryJsonStr.length);
upstreamSpan?.end();
tracer.end(retryResp.status, Date.now() - requestStartTime);
logFinalRequest(retryResp.status, account.label, account.type, undefined, undefined, {
inputTokens: retryUsage?.input_tokens,
outputTokens: retryUsage?.output_tokens,
cacheCreationTokens: retryUsage?.cache_creation_input_tokens,
...oneHourCacheWriteFields(readCacheCreation1hTokens(retryUsage)),
cacheReadTokens: retryUsage?.cache_read_input_tokens,
});
}
else {
upstreamSpan?.end();
logFinalRequest(retryResp.status, account.label, account.type);
}
return retryJson;
}
async function handleAnthropicAuthRetry(args) {
const { ctx, body, account, accountState, headers, buildUpstreamBody, url, enabledAccounts, orderedAccounts, tracer, requestStartTime, allocateAttemptNumber, upstreamSpan, logAttempt, logProxyBody, onStreamTerminal, logFinalRequest, lastError, authFailureMessage, entitlementFailure, sawRateLimit, sawTransientFailure, sawNetworkError, bindServedSession, } = args;
recordAttemptError(account.label, account.type, 401);
logAttempt(401, "authentication_error", "received 401 from Anthropic", {
retryable: true,
});
let currentLastError = lastError;
let currentAuthFailureMessage = authFailureMessage;
let currentEntitlementFailure = entitlementFailure;
let currentSawRateLimit = sawRateLimit;
let currentSawTransientFailure = sawTransientFailure;
let currentSawNetworkError = sawNetworkError;
let currentUpstreamSpan = upstreamSpan;
let authRetrySucceeded = false;
let authRetryError = "received 401 from Anthropic";
for (let authRetry = 0; authRetry < MAX_AUTH_RETRIES; authRetry++) {
logger.always(`[proxy] ← 401 account=${account.label} refreshing (attempt ${authRetry + 1}/${MAX_AUTH_RETRIES})`);
const refreshSucceeded = await refreshTokenFromLatest(account, account.persistTarget);
if (!refreshSucceeded.success) {
authRetryError = `refresh failed for account=${account.label} attempt ${authRetry + 1}/${MAX_AUTH_RETRIES}: ${refreshSucceeded.error?.slice(0, 200) ?? "unknown"}`;
currentLastError = authRetryError;
if (isPermanentRefreshFailure(refreshSucceeded)) {
await disableAccountUntilReauth(account, accountState, "refresh_invalid");
currentAuthFailureMessage = formatReauthMessage(account.label);
logger.always(`[proxy] account=${account.label} refresh token rejected; disabled until re-authentication`);
}
else {
const coolingUntil = await coolAccountAfterTransientRefreshFailure(account, accountState);
currentSawTransientFailure = true;
logger.always(`[proxy] account=${account.label} refresh temporarily unavailable (${refreshSucceeded.status ?? "network"}); cooling until ${new Date(coolingUntil).toISOString()} and rotating`);
}
break;
}
if (account.persistTarget) {
await persistTokens(account.persistTarget, account);
}
await clearAuthCooldownAfterRefresh(account, accountState);
headers.authorization = `Bearer ${account.token}`;
const retryAttemptNumber = allocateAttemptNumber();
const retryAttemptStartedAt = Date.now();
const retryLogAttempt = (status, errorType, errorMessage, extra) => logAttempt(status, errorType, errorMessage, {
...extra,
attempt: retryAttemptNumber,
attemptDurationMs: extra?.attemptDurationMs ?? Date.now() - retryAttemptStartedAt,
});
const preparedRetryBody = buildUpstreamBody(account.token);
const retryFetchStartMs = Date.now();
try {
const retryResp = await fetchAnthropicUpstream(url, {
method: "POST",
headers,
body: preparedRetryBody.bodyStr,
signal: AbortSignal.any([
...(ctx.abortSignal ? [ctx.abortSignal] : []),
AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS),
]),
}, ctx, account.key, {
logProxyBody,
tracer,
attemptNumber: retryAttemptNumber,
accountLabel: account.label,
accountType: account.type,
preparedContext: preparedRetryBody.preparedContext,
});
if (retryResp.ok) {
authRetrySucceeded = true;
accountState.consecutiveRefreshFailures = 0;
logger.always(`[proxy] ← 200 account=${account.label} (after ${authRetry + 1} refresh(es))`);
const successResult = body.stream
? await handleAnthropicSuccessfulResponse({
ctx,
body,
account,
accountState,
response: retryResp,
tracer,
requestStartTime,
fetchStartMs: retryFetchStartMs,
attemptNumber: retryAttemptNumber,
finalBodyStr: preparedRetryBody.bodyStr,
upstreamSpan: currentUpstreamSpan,
logAttempt: retryLogAttempt,
logProxyBody,
logFinalRequest,
onStreamTerminal,
})
: {
response: await handleAnthropicSuccessfulNonStreamRetryResponse({
ctx,
account,
accountState,
requestedModel: body.model,
retryResp,
tracer,
requestStartTime,
fetchStartMs: retryFetchStartMs,
attemptNumber: retryAttemptNumber,
finalBodyStr: preparedRetryBody.bodyStr,
upstreamSpan: currentUpstreamSpan,
logAttempt: retryLogAttempt,
logProxyBody,
logFinalRequest,
}),
// handleAnthropicSuccessfulNonStreamRetryResponse is only ever
// called when retryResp.ok — unlike the streaming path, it has
// no terminal-error return sharing this shape.
served: true,
};
if ("retryNextAccount" in successResult) {
const failure = successResult.failure;
return {
continueLoop: true,
...(failure?.retryDelayMs
? { retryDelayMs: failure.retryDelayMs }
: {}),
lastError: failure?.message ?? currentLastError,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit || Boolean(failure?.rateLimit),
sawTransientFailure: currentSawTransientFailure ||
Boolean(failure && !failure.rateLimit),
sawNetworkError: currentSawNetworkError,
upstreamSpan: undefined,
};
}
if (bindServedSession && successResult.served) {
bindServedSession(account.key);
}
return {
response: successResult.response,
holdsAccountAdmission: successResult.holdsAccountAdmission,
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit,
sawTransientFailure: currentSawTransientFailure,
sawNetworkError: currentSawNetworkError,
upstreamSpan: undefined,
};
}
const retryStatus = retryResp.status;
const retryBody = await retryResp.text();
// Capture full response headers and body for all auth-retry errors.
// Redact sensitive headers and cap body size before persisting.
const retryRespHeaders = {};
retryResp.headers.forEach((value, key) => {
retryRespHeaders[key] = value;
});
const safeRetryHeaders = { ...retryRespHeaders };
delete safeRetryHeaders["authorization"];
delete safeRetryHeaders["x-api-key"];
const cappedRetryBody = retryBody.length > 4000
? retryBody.slice(0, 4000) + "...[truncated]"
: retryBody;
tracer?.logUpstreamResponseHeaders(safeRetryHeaders);
tracer?.logUpstreamResponseBody(cappedRetryBody);
logProxyBody({
phase: "upstream_response",
headers: safeRetryHeaders,
body: cappedRetryBody,
bodySize: Buffer.byteLength(retryBody, "utf8"),
contentType: retryRespHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: retryAttemptNumber,
responseStatus: retryStatus,
durationMs: Date.now() - retryFetchStartMs,
});
authRetryError = `retry ${authRetry + 1}/${MAX_AUTH_RETRIES} failed with status ${retryStatus}`;
currentLastError = retryBody;
logger.debug(`[proxy] retry ${authRetry + 1} failed: ${retryStatus} ${retryBody.substring(0, 120)}`);
if (retryStatus === 429 &&
isAntiAbuseConstruction429(retryRespHeaders, retryBody)) {
logger.always(`[proxy] ← 429 account=${account.label} anti-abuse/construction rejection after OAuth refresh — returning non-retryable request error`);
retryLogAttempt(429, "construction_rejection", retryBody);
tracer?.setError("construction_rejection", retryBody.slice(0, 500));
currentUpstreamSpan?.end();
return {
response: finalizeAnthropicTerminalFetchError({
terminalError: buildAnthropicConstructionRejectionTerminalError(),
account,
tracer,
requestStartTime,
attemptNumber: retryAttemptNumber,
logProxyBody,
logFinalRequest,
}),
continueLoop: false,
lastError: retryBody,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit,
sawTransientFailure: currentSawTransientFailure,
sawNetworkError: currentSawNetworkError,
upstreamSpan: undefined,
};
}
// Construction rejections return through the terminal 400 path above.
// Every 429 reaching this branch is a genuine rate limit and must cool
// the account according to its reset window before rotating.
if (retryStatus === 429) {
currentSawRateLimit = true;
// Cool the account per its real reset window before rotating, so a
// session/weekly-exhausted account isn't re-selected next request.
const nowRetry = Date.now();
const retryQuota429 = parseQuotaHeaders(retryRespHeaders, {
model: body.model,
});
if (retryQuota429) {
accountState.quota = mergeQuotaSnapshot(accountState.quota, retryQuota429);
}
const retryPlan = planCooldownFor429(retryQuota429, parseRetryAfterMs(retryRespHeaders["retry-after"] ?? null), nowRetry, getUnifiedRateLimitStatus(retryRespHeaders), overagePolicy, typeof body.model === "string" ? body.model : undefined);
const rateLimitKind = retryPlan.reason === "transient" ? "transient" : "quota";
recordAttemptError(account.label, account.type, retryStatus, rateLimitKind);
retryLogAttempt(429, "rate_limit_error", retryBody, {
retryable: true,
rateLimitKind,
cooldownReason: retryPlan.reason,
});
if (retryPlan.scope === "account" &&
(!accountState.coolingUntil ||
retryPlan.coolingUntil > accountState.coolingUntil)) {
accountState.coolingUntil = retryPlan.coolingUntil;
accountState.coolingReason = retryPlan.reason;
}
if (retryQuota429) {
saveAccountQuota(account.key, retryQuota429).catch(() => {
// Non-fatal: routing already has the in-memory snapshot.
});
}
if (retryPlan.scope === "account") {
await saveAccountCooldown(account.key, accountState.coolingUntil ?? retryPlan.coolingUntil, accountState.coolingReason ?? retryPlan.reason).catch(() => {
// Non-fatal: routing already has the in-memory cooldown.
});
}
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
break;
}
if (retryStatus === 401 || retryStatus === 402 || retryStatus === 403) {
// An organization/plan entitlement refusal is not an authentication
// problem: the token just minted is valid and refreshing again cannot
// change the verdict. Rotate now instead of burning the remaining
// refresh cycles and their one-second waits on a certain failure.
if (isAccountEntitlementError(retryStatus, retryBody)) {
const parsedRetry = parseClaudeErrorBody(retryBody);
recordAttemptError(account.label, account.type, retryStatus);
retryLogAttempt(retryStatus, "permission_error", summarizeErrorMessage(retryBody));
currentEntitlementFailure = {
status: retryStatus,
accounts: [
...(currentEntitlementFailure?.accounts ?? []),
account.label,
],
message: currentEntitlementFailure?.message ??
parsedRetry.message ??
summarizeErrorMessage(retryBody),
...(parsedRetry.errorCode
? { errorCode: parsedRetry.errorCode }
: {}),
};
if (account.type === "oauth" &&
isDurableEntitlementBlock(retryStatus, retryBody)) {
await disableAccountUntilReauth(account, accountState, "entitlement_blocked");
}
authRetryError = `entitlement blocked for account=${account.label}`;
currentLastError = authRetryError;
logger.always(`[proxy] ← ${retryStatus} account=${account.label} entitlement blocked after refresh; advancing to next account`);
break;
}
recordAttemptError(account.label, account.type, retryStatus);
retryLogAttempt(retryStatus, "authentication_error", summarizeErrorMessage(retryBody), { retryable: true });
if (authRetry < MAX_AUTH_RETRIES - 1) {
await sleep(1000);
}
continue;
}
if (isTransientHttpFailure(retryStatus, retryBody)) {
recordAttemptError(account.label, account.type, retryStatus);
retryLogAttempt(retryStatus, "api_error", summarizeErrorMessage(retryBody), { retryable: true });
currentSawTransientFailure = true;
break;
}
retryLogAttempt(retryStatus, "api_error", summarizeErrorMessage(retryBody));
try {
logFinalRequest(retryStatus, account.label, account.type, "api_error", summarizeErrorMessage(retryBody));
return {
response: JSON.parse(retryBody),
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit,
sawTransientFailure: currentSawTransientFailure,
sawNetworkError: currentSawNetworkError,
upstreamSpan: currentUpstreamSpan,
};
}
catch {
logFinalRequest(retryStatus, account.label, account.type, "api_error", summarizeErrorMessage(retryBody));
return {
response: buildClaudeError(retryStatus, retryBody),
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit,
sawTransientFailure: currentSawTransientFailure,
sawNetworkError: currentSawNetworkError,
upstreamSpan: currentUpstreamSpan,
};
}
}
catch (retryFetchErr) {
if (retryFetchErr instanceof ProxyContextPreflightError ||
getProxyTokenBudgetError(retryFetchErr)) {
currentUpstreamSpan?.end();
throw retryFetchErr;
}
if (ctx.abortSignal?.aborted) {
retryLogAttempt(499, "client_cancelled", "Client cancelled upstream request", { retryable: false });
currentUpstreamSpan?.end();
throw retryFetchErr;
}
currentSawNetworkError = true;
recordAttemptError(account.label, account.type, 502);
const message = retryFetchErr instanceof Error
? retryFetchErr.message
: String(retryFetchErr);
authRetryError = `network error on retry ${authRetry + 1}: ${message}`;
currentLastError = authRetryError;
const retryable = isRetryableNetworkError(retryFetchErr);
retryLogAttempt(502, "network_error", message, {
retryable,
errorCode: getErrorCode(retryFetchErr) ?? "unknown",
});
logger.debug(`[proxy] ${authRetryError}`);
if (!retryable) {
// Once a POST has left this process, a reset/timeout or unknown fetch
// failure is ambiguous: retrying it on another account can duplicate
// the request. Only connection-establishment failures are replay-safe.
currentUpstreamSpan?.end();
return {
response: finalizeAnthropicTerminalTransportError({
account,
tracer,
requestStartTime,
attemptNumber: retryAttemptNumber,
logProxyBody,
logFinalRequest,
errorType: "network_error",
message,
}),
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit,
sawTransientFailure: currentSawTransientFailure,
sawNetworkError: currentSawNetworkError,
upstreamSpan: undefined,
};
}
break;
}
}
if (!authRetrySucceeded) {
// No persistent cooldown — just move to next account for this request.
currentLastError = authRetryError;
logger.always(`[proxy] ⚠ account=${account.label} auth retries exhausted, rotating to next account`);
tracer?.setError("authentication_error", authRetryError);
tracer?.recordRetry(account.label, "auth_exhausted");
currentUpstreamSpan?.end();
currentUpstreamSpan = undefined;
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
}
return {
continueLoop: true,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
entitlementFailure: currentEntitlementFailure,
sawRateLimit: currentSawRateLimit,
sawTransientFailure: currentSawTransientFailure,
sawNetworkError: currentSawNetworkError,
upstreamSpan: currentUpstreamSpan,
};
}
function buildAnthropicTerminalErrorResponse(args) {
const { responseStatus, account, errBody, errRespHeaders, requestStartTime, attemptNumber, logProxyBody, logFinalRequest, errorType, } = args;
try {
const parsedError = JSON.parse(errBody);
logFinalRequest(responseStatus, account.label, account.type, errorType, summarizeErrorMessage(errBody));
logProxyBody({
phase: "client_response",
headers: {
"content-type": errRespHeaders["content-type"] ?? "application/json",
},
body: errBody,
bodySize: Buffer.byteLength(errBody, "utf8"),
contentType: errRespHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus,
durationMs: Date.now() - requestStartTime,
});
return parsedError;
}
catch {
logFinalRequest(responseStatus, account.label, account.type, errorType, summarizeErrorMessage(errBody));
const clientError = buildClaudeError(responseStatus, errBody);
const clientErrorBody = JSON.stringify(clientError);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: clientErrorBody,
bodySize: Buffer.byteLength(clientErrorBody, "utf8"),
contentType: "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus,
durationMs: Date.now() - requestStartTime,
});
return clientError;
}
}
function finalizeAnthropicTerminalFetchError(args) {
const { terminalError, account, tracer, requestStartTime, attemptNumber, logProxyBody, logFinalRequest, } = args;
tracer?.end(terminalError.status, Date.now() - requestStartTime);
return buildAnthropicTerminalErrorResponse({
responseStatus: terminalError.status,
account,
errBody: terminalError.body,
errRespHeaders: terminalError.headers,
requestStartTime,
attemptNumber,
logProxyBody,
logFinalRequest,
errorType: terminalError.errorType,
});
}
function finalizeAnthropicTerminalTransportError(args) {
const { account, tracer, requestStartTime, attemptNumber, logProxyBody, logFinalRequest, errorType, message, } = args;
tracer?.setError(errorType, message);
tracer?.end(502, Date.now() - requestStartTime);
logFinalRequest(502, account.label, account.type, errorType, message);
const clientError = buildClaudeError(502, message);
const clientErrorBody = JSON.stringify(clientError);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: clientErrorBody,
bodySize: Buffer.byteLength(clientErrorBody, "utf8"),
contentType: "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 502,
durationMs: Date.now() - requestStartTime,
});
return clientError;
}
async function handleAnthropicNonOkResponse(args) {
const { response, account, accountState, enabledAccounts, orderedAccounts, tracer, requestStartTime, fetchStartMs, attemptNumber, logAttempt, logProxyBody, logFinalRequest, lastError, authFailureMessage, sawTransientFailure, invalidRequestFailure, entitlementFailure, allowConfiguredModelFallback = false, } = args;
let currentLastError = lastError;
let currentAuthFailureMessage = authFailureMessage;
let currentSawTransientFailure = sawTransientFailure;
let currentInvalidRequestFailure = invalidRequestFailure;
let currentEntitlementFailure = entitlementFailure;
const errBody = await response.text();
const errRespHeaders = {};
response.headers.forEach((value, key) => {
errRespHeaders[key] = value;
});
tracer?.logUpstreamResponseHeaders(errRespHeaders);
tracer?.logUpstreamResponseBody(errBody);
logProxyBody({
phase: "upstream_response",
headers: errRespHeaders,
body: errBody,
bodySize: Buffer.byteLength(errBody, "utf8"),
contentType: errRespHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: response.status,
durationMs: Date.now() - fetchStartMs,
});
if (isInvalidRequestError(response.status, errBody)) {
if (isSubscriptionBetaRejection(response.status, errBody)) {
// Subscription-specific beta rejection (an account whose plan tier lacks
// an optional beta the proxy injected). The SAME request can succeed on
// another account whose tier grants the beta — or on a fallback provider —
// so advance to the next account instead of failing the client here.
//
// Deliberately do NOT store this in `currentInvalidRequestFailure`: that
// field is a deterministic "malformed request" signal that both
// suppresses provider fallback (shouldAttemptClaudeFallback) and outranks
// transient/rate-limit failures in the final response. A beta rejection is
// neither terminal nor higher-priority — a later account's real 429/5xx
// must still take precedence, and fallback must stay eligible. The reason
// is carried in `lastError`, so if every account rejects the beta the
// exhaustion response still explains why.
logger.always(`[proxy] ← ${response.status} account=${account.label} beta unavailable for subscription; advancing to next account`);
logAttempt(response.status, "invalid_request_error", summarizeErrorMessage(errBody));
tracer?.setError("invalid_request_error", summarizeErrorMessage(errBody));
tracer?.recordRetry(account.label, "beta_unavailable");
// A tier's lack of a beta is stable, not transient — advance the
// fill-first primary pointer (like the auth/rate-limit rotation paths) so
// this account stops being retried first on every future request.
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
currentLastError = summarizeErrorMessage(errBody);
return {
continueLoop: true,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
logger.always(`[proxy] ← ${response.status} upstream invalid_request_error`);
logAttempt(response.status, "invalid_request_error", summarizeErrorMessage(errBody));
tracer?.setError("invalid_request_error", summarizeErrorMessage(errBody));
currentInvalidRequestFailure = {
status: response.status,
body: errBody,
contentType: errRespHeaders["content-type"],
};
currentLastError = summarizeErrorMessage(errBody);
return {
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
if (account.type === "oauth" &&
isAccountEntitlementError(response.status, errBody)) {
// Anthropic refuses this credential on organization/plan policy, e.g.
// "OAuth authentication is currently not allowed for this organization."
// Neither a retry nor a token refresh can fix it, but another account may
// not be subject to the same policy — so rotate rather than fail the client.
//
// Must precede the no-refresh-token branch below: that branch matches the
// same 401/402/403 statuses and would disable the account as
// `missing_refresh_token`, telling the user to re-login — which cannot help
// and burns a working credential.
//
// Like the beta-rejection branch above, deliberately sets neither
// `currentInvalidRequestFailure` (would suppress provider fallback and
// outrank a later account's real 429) nor `currentAuthFailureMessage`
// (would surface a misleading "re-authenticate" 401).
const parsed = parseClaudeErrorBody(errBody);
recordAttemptError(account.label, account.type, response.status);
currentEntitlementFailure = {
status: response.status,
accounts: [...(currentEntitlementFailure?.accounts ?? []), account.label],
message: currentEntitlementFailure?.message ??
parsed.message ??
summarizeErrorMessage(errBody),
...(parsed.errorCode ? { errorCode: parsed.errorCode } : {}),
};
logger.always(`[proxy] ← ${response.status} account=${account.label} entitlement blocked${parsed.errorCode ? ` (${parsed.errorCode})` : ""}; advancing to next account`);
logAttempt(response.status, "permission_error", summarizeErrorMessage(errBody));
tracer?.setError("permission_error", summarizeErrorMessage(errBody));
tracer?.recordRetry(account.label, "entitlement_blocked");
if (account.type === "oauth" &&
isDurableEntitlementBlock(response.status, errBody)) {
await disableAccountUntilReauth(account, accountState, "entitlement_blocked");
}
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
currentLastError = summarizeErrorMessage(errBody);
return {
continueLoop: true,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
if ((response.status === 401 ||
response.status === 402 ||
response.status === 403) &&
account.type === "oauth" &&
!account.refreshToken) {
recordAttemptError(account.label, account.type, response.status);
await disableAccountUntilReauth(account, accountState, "missing_refresh_token");
currentAuthFailureMessage = formatReauthMessage(account.label);
logger.always(`[proxy] ← ${response.status} account=${account.label} (auth failure, no refresh token)`);
currentLastError = errBody;
logAttempt(response.status, "authentication_error", summarizeErrorMessage(errBody));
tracer?.setError("authentication_error", summarizeErrorMessage(errBody));
tracer?.recordRetry(account.label, "auth_no_refresh");
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
return {
continueLoop: true,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
if ((response.status === 401 ||
response.status === 402 ||
response.status === 403) &&
account.type === "api_key") {
recordAttemptError(account.label, account.type, response.status);
currentAuthFailureMessage =
"Authentication failed for Anthropic API key credentials. Update ANTHROPIC_API_KEY or re-login with OAuth.";
logger.always(`[proxy] ← ${response.status} account=${account.label} (auth failure, api_key)`);
currentLastError = errBody;
logAttempt(response.status, "authentication_error", summarizeErrorMessage(errBody));
tracer?.setError("authentication_error", summarizeErrorMessage(errBody));
tracer?.recordRetry(account.label, "auth_api_key");
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
return {
continueLoop: true,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
if (response.status === 404) {
if (allowConfiguredModelFallback &&
isAnthropicModelNotFound(response.status, errBody)) {
// An upstream model retirement is provider-wide, not an account failure.
// Do not cool or disable an account; leave the configured translation
// fallback eligible so legacy Anthropic aliases can use the Codex target.
logger.always(`[proxy] ← 404 account=${account.label} model unavailable; trying configured fallback`);
logAttempt(404, "not_found_error", summarizeErrorMessage(errBody));
tracer?.setError("not_found_error", summarizeErrorMessage(errBody));
tracer?.recordRetry(account.label, "model_not_found");
currentLastError = summarizeErrorMessage(errBody);
return {
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
logger.always(`[proxy] ← 404 account=${account.label}`);
logAttempt(404, "not_found_error", summarizeErrorMessage(errBody));
tracer?.setError("not_found_error", summarizeErrorMessage(errBody));
tracer?.end(404, Date.now() - requestStartTime);
return {
response: buildAnthropicTerminalErrorResponse({
responseStatus: 404,
account,
errBody,
errRespHeaders,
requestStartTime,
attemptNumber,
logProxyBody,
logFinalRequest,
errorType: "not_found_error",
}),
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
if (isTransientHttpFailure(response.status, errBody)) {
const upstreamOverload = isUpstreamOverload(response.status, errBody);
recordAttemptError(account.label, account.type, response.status, response.status === 429 ? "transient" : undefined);
currentSawTransientFailure = true;
logger.always(`[proxy] ← ${response.status} account=${account.label} (${upstreamOverload ? "overloaded" : "transient"})`);
currentLastError = errBody;
logAttempt(response.status, "api_error", summarizeErrorMessage(errBody), response.status === 429
? {
retryable: true,
rateLimitKind: "transient",
cooldownReason: "transient",
}
: undefined);
tracer?.setError(upstreamOverload ? "overloaded_error" : "transient_error", summarizeErrorMessage(errBody));
tracer?.recordRetry(account.label, upstreamOverload ? "overloaded" : "transient");
return {
continueLoop: true,
retrySameAccount: !upstreamOverload,
...(upstreamOverload
? { retryDelayMs: getOverloadRotationDelayMs(attemptNumber) }
: {}),
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
logger.always(`[proxy] ← ${response.status} account=${account.label}`);
logger.debug(`[claude-proxy] error body: ${errBody.substring(0, 200)}`);
logAttempt(response.status, "api_error", summarizeErrorMessage(errBody));
tracer?.setError("api_error", summarizeErrorMessage(errBody));
tracer?.end(response.status, Date.now() - requestStartTime);
return {
response: buildAnthropicTerminalErrorResponse({
responseStatus: response.status,
account,
errBody,
errRespHeaders,
requestStartTime,
attemptNumber,
logProxyBody,
logFinalRequest,
errorType: "api_error",
}),
continueLoop: false,
lastError: currentLastError,
authFailureMessage: currentAuthFailureMessage,
sawTransientFailure: currentSawTransientFailure,
invalidRequestFailure: currentInvalidRequestFailure,
entitlementFailure: currentEntitlementFailure,
upstreamSpan: undefined,
};
}
function createClaudeRequestRuntimeContext(args) {
const { ctx, body, clientRequestBody } = args;
let tracer;
try {
tracer = ProxyTracer.startRequest({
recordRequestMetrics: getProxyRequestAccounting(ctx.requestId)?.accountingScope !==
"internal",
recordUsageMetrics: true,
requestId: ctx.requestId,
method: ctx.method,
path: ctx.path,
model: body.model,
stream: body.stream ?? false,
toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
toolNames: Array.isArray(body.tools)
? body.tools
.map((t) => t && typeof t === "object" && "name" in t
? String(t.name ?? "")
: "")
.filter((n) => n.length > 0)
: undefined,
sessionId: ctx.headers["x-neurolink-session-id"] ??
ctx.headers["x-claude-code-session-id"] ??
undefined,
userAgent: ctx.headers["user-agent"] ?? undefined,
}, ctx.headers);
const receiveSpan = tracer.startReceive();
tracer.logRequestHeaders(ctx.headers);
tracer.logRequestBody(clientRequestBody);
receiveSpan.end();
}
catch {
tracer = undefined;
}
const requestStartTime = Date.now();
const logProxyBody = (capture) => {
const traceCtx = tracer?.getTraceContext();
void logBodyCapture({
timestamp: new Date().toISOString(),
requestId: ctx.requestId,
model: body.model,
stream: body.stream ?? false,
...capture,
...(traceCtx
? { traceId: traceCtx.traceId, spanId: traceCtx.spanId }
: {}),
});
};
let routingDecision;
const setRoutingDecision = (decision) => {
if (routingDecision) {
logger.debug(`[claude-proxy] ignored duplicate routing decision for request ${ctx.requestId}`);
return;
}
routingDecision = decision;
};
let finalRequestLogged = false;
const logFinalRequest = (status, accountLabel, accountType, errorType, errorMessage, extra) => {
if (finalRequestLogged || isProxyRequestFinalized(ctx.requestId)) {
logger.debug(`[claude-proxy] ignored duplicate finalization for request ${ctx.requestId}`);
return;
}
finalRequestLogged = true;
if (ctx.abortSignal?.aborted) {
status = 499;
errorType = "client_cancelled";
errorMessage = "Client cancelled request";
}
const sdkFallbackFailure = ctx.metadata.sdkFallbackFailure;
if (sdkFallbackFailure) {
accountLabel = "unknown";
accountType = "translation";
tracer?.setServedAccount(accountLabel, accountType);
if (sdkFallbackFailure.model) {
tracer?.setModelSubstitution(body.model, sdkFallbackFailure.model, sdkFallbackFailure.provider);
}
if (sdkFallbackFailure.inputTokens !== undefined &&
sdkFallbackFailure.outputTokens !== undefined) {
tracer?.setUsage({
inputTokens: sdkFallbackFailure.inputTokens,
outputTokens: sdkFallbackFailure.outputTokens,
cacheReadTokens: sdkFallbackFailure.cacheReadTokens ?? 0,
cacheCreationTokens: sdkFallbackFailure.cacheCreationTokens ?? 0,
inputIncludesCachedTokens: sdkFallbackFailure.inputIncludesCachedTokens,
reasoningTokens: sdkFallbackFailure.reasoningTokens,
});
}
}
const fallbackFailureUsage = ctx.metadata
.codexFallbackFailureUsage;
if (fallbackFailureUsage) {
extra = {
inputTokens: fallbackFailureUsage.inputTokensObserved
? fallbackFailureUsage.input
: undefined,
outputTokens: fallbackFailureUsage.outputTokensObserved
? fallbackFailureUsage.output
: undefined,
cacheReadTokens: fallbackFailureUsage.cacheReadTokensObserved
? fallbackFailureUsage.cacheReadTokens
: undefined,
cacheCreationTokens: fallbackFailureUsage.cacheCreationTokensObserved
? fallbackFailureUsage.cacheCreationTokens
: undefined,
cacheReadTokensObserved: fallbackFailureUsage.cacheReadTokensObserved === true,
cacheCreationTokensObserved: fallbackFailureUsage.cacheCreationTokensObserved === true,
reasoningTokens: fallbackFailureUsage.reasoning,
...extra,
};
}
if (!accountLabel &&
typeof ctx.metadata.codexFallbackAccount === "string" &&
ctx.metadata.codexFallbackAccount) {
accountLabel = ctx.metadata.codexFallbackAccount;
if (typeof ctx.metadata.codexFallbackAccountType === "string") {
accountType = ctx.metadata.codexFallbackAccountType;
}
}
if (!sdkFallbackFailure &&
ctx.metadata.codexFallbackDeferredFailure === true &&
typeof ctx.metadata.codexFallbackModel === "string") {
tracer?.setModelSubstitution(body.model, ctx.metadata.codexFallbackModel, "openai");
tracer?.setServedAccount(accountLabel || "unknown", accountType);
if (fallbackFailureUsage?.inputTokensObserved &&
fallbackFailureUsage.outputTokensObserved) {
tracer?.setUsage({
inputTokens: fallbackFailureUsage.input,
outputTokens: fallbackFailureUsage.output,
cacheReadTokens: fallbackFailureUsage.cacheReadTokens ?? 0,
cacheCreationTokens: fallbackFailureUsage.cacheCreationTokens ?? 0,
cacheReadTokensObserved: fallbackFailureUsage.cacheReadTokensObserved === true,
cacheCreationTokensObserved: fallbackFailureUsage.cacheCreationTokensObserved === true,
reasoningTokens: fallbackFailureUsage.reasoning,
}, { recordMetrics: false });
}
}
const finalAccountLabel = accountLabel ||
(status >= 400 ? PROXY_INTERNAL_ACCOUNT_LABEL : undefined);
const finalAccountType = accountLabel
? accountType || undefined
: status >= 400
? PROXY_INTERNAL_ACCOUNT_TYPE
: undefined;
const finalAccountIdentity = sdkFallbackFailure
? {
accountKey: sdkFallbackFailure.accountKey,
provider: sdkFallbackFailure.provider,
}
: resolveRequestLogAccountIdentity(finalAccountLabel, finalAccountType);
const ownsClientOutcome = getProxyRequestAccounting(ctx.requestId)?.accountingScope !== "internal";
if (ownsClientOutcome && status >= 400) {
recordFinalError(status, finalAccountLabel, finalAccountType, {
requestId: ctx.requestId,
...(finalAccountIdentity.accountKey
? { accountKey: finalAccountIdentity.accountKey }
: {}),
errorType,
terminalOutcome: errorType === "client_cancelled"
? "client_cancelled"
: errorType?.includes("stream")
? "stream_error"
: "handler_error",
message: errorMessage,
errorCode: extra?.errorCode,
});
}
else if (ownsClientOutcome) {
recordFinalSuccess(finalAccountLabel, finalAccountType);
}
const traceCtx = tracer?.getTraceContext();
const servedModel = resolveServedFallbackModel(ctx.metadata, finalAccountType);
logRequest({
timestamp: new Date().toISOString(),
requestId: ctx.requestId,
method: ctx.method,
path: ctx.path,
model: servedModel ?? body.model,
...(servedModel
? {
requestedModel: body.model,
// Codex reports input inclusive of cached tokens; the Vertex
// Anthropic wire reports them separately, exactly as the native
// path does, so the flag stays Codex-only. Keyed off the serving
// account for the same reason `servedModel` is: a failed Codex leg
// leaves its model behind on a turn Vertex went on to serve.
...(finalAccountType === "vertex"
? {}
: { inputIncludesCachedTokens: false }),
}
: {}),
...(ctx.metadata.contextPreflight
? {
contextPreflight: ctx.metadata
.contextPreflight,
}
: {}),
...(ctx.metadata.tokenBudget
? {
tokenBudget: ctx.metadata
.tokenBudget,
}
: {}),
stream: !!body.stream,
toolCount: Array.isArray(body.tools) ? body.tools.length : 0,
account: finalAccountLabel ?? "",
...(finalAccountIdentity.accountKey
? { accountKey: finalAccountIdentity.accountKey }
: {}),
accountType: finalAccountType ?? "",
...(finalAccountIdentity.provider
? { provider: finalAccountIdentity.provider }
: {}),
...sdkFallbackFailure,
...buildClientAttribution(ctx.headers),
responseStatus: status,
responseTimeMs: Date.now() - requestStartTime,
...claudeOutputLogFields(ctx.metadata, requestStartTime),
...(errorType ? { errorType } : {}),
...(errorMessage ? { errorMessage } : {}),
...(extra?.errorCode ? { errorCode: extra.errorCode } : {}),
...(extra?.retryable !== undefined ? { retryable: extra.retryable } : {}),
...(extra?.transportScope
? { transportScope: extra.transportScope }
: {}),
...(extra?.inputTokens !== undefined
? { inputTokens: extra.inputTokens }
: {}),
...(extra?.outputTokens !== undefined
? { outputTokens: extra.outputTokens }
: {}),
...(extra?.cacheCreationTokens !== undefined
? { cacheCreationTokens: extra.cacheCreationTokens }
: {}),
...(extra?.cacheCreation1hTokens !== undefined
? { cacheCreation1hTokens: extra.cacheCreation1hTokens }
: {}),
...(extra?.cacheReadTokens !== undefined
? { cacheReadTokens: extra.cacheReadTokens }
: {}),
...(extra?.reasoningTokens !== undefined
? { reasoningTokens: extra.reasoningTokens }
: {}),
...(traceCtx
? { traceId: traceCtx.traceId, spanId: traceCtx.spanId }
: {}),
...(routingDecision ? { routingDecision } : {}),
...(Array.isArray(ctx.metadata.fallbackPlan)
? { fallbackPlan: ctx.metadata.fallbackPlan }
: {}),
});
};
const buildLoggedClaudeError = (status, message, errorType, extra) => {
const errorBody = buildClaudeError(status, message, errorType);
const errorBodyText = JSON.stringify(errorBody);
logFinalRequest(status, extra?.account ?? "", extra?.accountType ?? "final", errorType, message, extra);
logProxyBody({
phase: "client_response",
headers: { "content-type": "application/json" },
body: errorBodyText,
bodySize: Buffer.byteLength(errorBodyText, "utf8"),
contentType: "application/json",
responseStatus: status,
durationMs: Date.now() - requestStartTime,
...extra,
});
return errorBody;
};
logProxyBody({
phase: "client_request",
headers: ctx.headers,
body: clientRequestBody,
bodySize: Buffer.byteLength(clientRequestBody, "utf8"),
contentType: ctx.headers["content-type"] ?? "application/json",
});
return {
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
buildLoggedClaudeError,
setRoutingDecision,
};
}
function createAnthropicAttemptLogger(args) {
const { ctx, body, toolCount, requestStart, tracer, account, attemptNumber } = args;
const attemptStartedAt = Date.now();
const accountIdentity = resolveRequestLogAccountIdentity(account.label, account.type);
return (status, errorType, errorMessage, extra) => {
const attemptCompletedAt = Date.now();
const traceCtx = tracer?.getTraceContext();
logRequestAttempt({
timestamp: new Date().toISOString(),
requestId: ctx.requestId,
attempt: extra?.attempt ?? attemptNumber,
method: ctx.method,
path: ctx.path,
model: body.model,
...(ctx.metadata.contextPreflight
? {
contextPreflight: ctx.metadata
.contextPreflight,
}
: {}),
...(ctx.metadata.tokenBudget
? {
tokenBudget: ctx.metadata
.tokenBudget,
}
: {}),
stream: !!body.stream,
toolCount,
account: account.label,
accountKey: account.key,
accountType: account.type,
...(accountIdentity.provider
? { provider: accountIdentity.provider }
: {}),
responseStatus: status,
responseTimeMs: attemptCompletedAt - requestStart,
attemptDurationMs: extra?.attemptDurationMs ?? attemptCompletedAt - attemptStartedAt,
...(errorType ? { errorType } : {}),
...(errorMessage ? { errorMessage } : {}),
...(extra?.errorCode ? { errorCode: extra.errorCode } : {}),
...(extra?.transportScope
? { transportScope: extra.transportScope }
: {}),
...(extra?.inputTokens !== undefined
? { inputTokens: extra.inputTokens }
: {}),
...(extra?.outputTokens !== undefined
? { outputTokens: extra.outputTokens }
: {}),
...(extra?.cacheCreationTokens !== undefined
? { cacheCreationTokens: extra.cacheCreationTokens }
: {}),
...(extra?.cacheCreation1hTokens !== undefined
? { cacheCreation1hTokens: extra.cacheCreation1hTokens }
: {}),
...(extra?.cacheReadTokens !== undefined
? { cacheReadTokens: extra.cacheReadTokens }
: {}),
...(extra?.retryable !== undefined ? { retryable: extra.retryable } : {}),
...(extra?.connectPhase !== undefined
? { connectPhase: extra.connectPhase }
: {}),
...(extra?.rateLimitKind ? { rateLimitKind: extra.rateLimitKind } : {}),
...(extra?.cooldownReason
? { cooldownReason: extra.cooldownReason }
: {}),
...(traceCtx
? { traceId: traceCtx.traceId, spanId: traceCtx.spanId }
: {}),
});
};
}
async function prepareAnthropicAccountAttempt(args) {
const { ctx, account, accountState, bodyStr, clientHeaders, isClaudeClientRequest, url, tracer, attemptNumber, currentLastError, currentAuthFailureMessage, logAttempt, logProxyBody: _logProxyBody, } = args;
let lastError = currentLastError;
let authFailureMessage = currentAuthFailureMessage;
if (needsRefresh(account)) {
const refreshed = await refreshTokenFromLatest(account, account.persistTarget);
if (refreshed.success) {
if (account.persistTarget) {
await persistTokens(account.persistTarget, account);
}
await clearAuthCooldownAfterRefresh(account, accountState);
}
else {
lastError = `token refresh failed for account=${account.label}: ${refreshed.error?.slice(0, 200) ?? "unknown"}`;
if (isPermanentRefreshFailure(refreshed)) {
await disableAccountUntilReauth(account, accountState, "refresh_invalid");
authFailureMessage = formatReauthMessage(account.label);
}
else {
await coolAccountAfterTransientRefreshFailure(account, accountState);
}
logAttempt(isPermanentRefreshFailure(refreshed) ? 401 : 503, isPermanentRefreshFailure(refreshed)
? "authentication_error"
: "token_refresh_unavailable", String(lastError));
return {
continueLoop: true,
lastError,
authFailureMessage,
};
}
}
const isOAuth = account.type === "oauth";
const filteredHeaders = {};
for (const [k, v] of Object.entries(clientHeaders)) {
if (typeof v === "string") {
filteredHeaders[k] = v;
}
}
const snapshot = isOAuth
? await maybeRefreshClaudeSnapshot(account.label, account.key, filteredHeaders, bodyStr)
: null;
const headers = {};
for (const [headerKey, headerValue] of Object.entries(clientHeaders)) {
const lower = headerKey.toLowerCase();
if (typeof headerValue === "string" &&
!BLOCKED_UPSTREAM_HEADERS.has(lower)) {
headers[lower] = headerValue;
}
}
headers["content-type"] = "application/json";
if (isOAuth) {
headers.authorization = `Bearer ${account.token}`;
delete headers["x-api-key"];
applySnapshotHeaders(headers, snapshot);
}
else {
headers["x-api-key"] = account.token;
delete headers.authorization;
}
if (!headers["user-agent"]) {
headers["user-agent"] = CLAUDE_CLI_USER_AGENT;
}
if (!headers["anthropic-version"]) {
headers["anthropic-version"] = "2023-06-01";
}
if (!headers["anthropic-dangerous-direct-browser-access"]) {
headers["anthropic-dangerous-direct-browser-access"] = "true";
}
if (!headers["x-app"]) {
headers["x-app"] = "cli";
}
if (!headers.accept) {
headers.accept = "application/json";
}
if (isOAuth) {
const betaSeed = isClaudeClientRequest
? (headers["anthropic-beta"] ?? "")
: (clientHeaders["anthropic-beta"] ?? "");
const existing = new Set(betaSeed
.split(",")
.map((value) => value.trim())
.filter(Boolean));
for (const beta of isClaudeClientRequest
? CLAUDE_CODE_OAUTH_BETAS
: NON_CLAUDE_OAUTH_BETAS) {
existing.add(beta);
}
headers["anthropic-beta"] = [...existing].join(",");
}
else {
const cleaned = (headers["anthropic-beta"] ?? "")
.split(",")
.map((value) => value.trim())
.filter((value) => value && !CLAUDE_CODE_OAUTH_BETAS.includes(value))
.join(",");
if (cleaned) {
headers["anthropic-beta"] = cleaned;
}
else {
delete headers["anthropic-beta"];
}
}
const buildUpstreamBody = (token) => {
const built = isOAuth
? polyfillOAuthBody(bodyStr, token, snapshot, isClaudeClientRequest, headers["x-claude-code-session-id"])
: { bodyStr };
return { ...built, ...prepareAnthropicWire(ctx, built.bodyStr) };
};
const polyfilledBody = buildUpstreamBody(account.token);
if (isOAuth &&
polyfilledBody.sessionId &&
!headers["x-claude-code-session-id"]) {
headers["x-claude-code-session-id"] = polyfilledBody.sessionId;
}
const finalBodyStr = polyfilledBody.bodyStr;
logger.always(`[proxy] → account=${account.label} (${account.type})`);
const fetchStartMs = Date.now();
let upstreamSpan;
if (tracer) {
upstreamSpan = tracer.startUpstreamAttempt({
attempt: attemptNumber,
account: account.label,
polyfillHeaders: isOAuth,
polyfillBody: isOAuth,
upstreamUrl: url,
});
Object.assign(headers, tracer.getTraceHeaders());
}
return {
continueLoop: false,
lastError,
authFailureMessage,
headers,
buildUpstreamBody,
finalBodyStr,
preparedContext: polyfilledBody.preparedContext,
fetchStartMs,
upstreamSpan,
};
}
/**
* Detect Anthropic's anti-abuse / request-construction 429.
*
* The subscription/OAuth path rejects requests it does not recognise as genuine
* Claude Code traffic with a 429 `rate_limit_error` whose message is literally
* "Error" and which carries NONE of the real rate-limit headers (no retry-after,
* no anthropic-ratelimit-*). This is NOT a capacity limit — retrying or rotating
* accounts cannot fix it and only burns quota, so the caller must fail fast and
* return a non-retryable request error instead of "all accounts rate-limited".
*/
function isAntiAbuseConstruction429(headers, body) {
const hasRetryAfter = !!headers["retry-after"];
const hasRateLimitHeaders = Object.keys(headers).some((k) => k.toLowerCase().startsWith("anthropic-ratelimit-"));
if (hasRetryAfter || hasRateLimitHeaders) {
return false;
}
return (body.includes("rate_limit_error") && /"message"\s*:\s*"Error"/.test(body));
}
function buildAnthropicConstructionRejectionTerminalError() {
return {
status: 400,
body: JSON.stringify(buildClaudeError(400, "Anthropic rejected the OAuth request shape. This is not an account rate limit.", "invalid_request_error")),
headers: { "content-type": "application/json" },
errorType: "construction_rejection",
};
}
async function fetchAnthropicAccountResponse(args) {
const { ctx, url, headers, finalBodyStr, preparedContext, requestedModel, account, accountState: _accountState2, enabledAccounts: _enabledAccounts, orderedAccounts: _orderedAccounts, tracer, logAttempt, logProxyBody, fetchStartMs, attemptNumber, currentLastError, currentSawRateLimit, currentSawNetworkError, upstreamSpan, } = args;
let lastError = currentLastError;
let sawRateLimit = currentSawRateLimit;
let sawNetworkError = currentSawNetworkError;
const currentUpstreamSpan = upstreamSpan;
let response;
try {
response = await fetchAnthropicUpstream(url, {
method: "POST",
headers,
body: finalBodyStr,
signal: AbortSignal.any([
...(ctx.abortSignal ? [ctx.abortSignal] : []),
AbortSignal.timeout(UPSTREAM_FETCH_TIMEOUT_MS),
]),
}, ctx, account.key, {
logProxyBody,
tracer,
attemptNumber,
accountLabel: account.label,
accountType: account.type,
preparedContext,
});
}
catch (fetchErr) {
if (fetchErr instanceof ProxyContextPreflightError ||
getProxyTokenBudgetError(fetchErr)) {
currentUpstreamSpan?.end();
throw fetchErr;
}
if (ctx.abortSignal?.aborted) {
logAttempt(499, "client_cancelled", "Client cancelled upstream request", {
retryable: false,
});
currentUpstreamSpan?.end();
throw ctx.abortSignal.reason;
}
const retryable = isRetryableNetworkError(fetchErr);
const connectPhase = isConnectPhaseNetworkError(fetchErr);
const transportScope = classifyNetworkTransportScope(fetchErr);
// Every dispatched upstream request is an attempt, including terminal
// transport failures. Record it once before preserving the throw behavior.
sawNetworkError = true;
recordAttemptError(account.label, account.type, 502);
const errorCode = getErrorCode(fetchErr) ?? "unknown";
const errorMessage = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
lastError = errorMessage;
logger.always(`[proxy] fetch error account=${account.label} code=${errorCode} (${retryable ? "retryable" : "terminal"}): ${errorMessage}`);
logAttempt(502, "network_error", errorMessage, {
retryable,
connectPhase,
errorCode,
transportScope,
});
tracer?.setError("network_error", errorMessage);
if (retryable) {
tracer?.recordRetry(account.label, "network_error");
}
currentUpstreamSpan?.end();
if (!retryable) {
throw fetchErr;
}
return {
continueLoop: true,
retrySameAccount: true,
transportScope,
errorCode,
connectPhase,
lastError,
sawRateLimit,
sawNetworkError,
upstreamSpan: undefined,
};
}
if (response.status === 429) {
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
// Capture full response headers and body for diagnostics (parity with
// handleAnthropicNonOkResponse which does this for all other error statuses).
const errRespHeaders = {};
response.headers.forEach((value, key) => {
errRespHeaders[key] = value;
});
lastError = await response.text();
// Redact sensitive headers and cap body before persisting
const safe429Headers = { ...errRespHeaders };
delete safe429Headers["authorization"];
delete safe429Headers["x-api-key"];
const capped429Body = String(lastError).length > 4000
? String(lastError).slice(0, 4000) + "...[truncated]"
: String(lastError);
tracer?.logUpstreamResponseHeaders(safe429Headers);
tracer?.logUpstreamResponseBody(capped429Body);
logProxyBody({
phase: "upstream_response",
headers: safe429Headers,
body: capped429Body,
bodySize: Buffer.byteLength(String(lastError), "utf8"),
contentType: errRespHeaders["content-type"] ?? "application/json",
account: account.label,
accountType: account.type,
attempt: attemptNumber,
responseStatus: 429,
durationMs: Date.now() - fetchStartMs,
});
// Anti-abuse / request-construction 429 (no rate-limit headers, body
// "Error"): rotating accounts cannot help and only burns quota. Fail fast
// and return a non-retryable request error instead of prompting the client
// to repeat the same malformed request as a genuine rate limit.
if (isAntiAbuseConstruction429(errRespHeaders, String(lastError))) {
logger.always(`[proxy] ← 429 account=${account.label} anti-abuse/construction rejection (no ratelimit headers, body="Error") — NOT a real rate limit; returning non-retryable request error`);
logAttempt(429, "construction_rejection", String(lastError));
tracer?.setError("construction_rejection", String(lastError).slice(0, 500));
currentUpstreamSpan?.end();
return {
continueLoop: false,
terminalError: buildAnthropicConstructionRejectionTerminalError(),
lastError,
sawRateLimit,
sawNetworkError,
upstreamSpan: undefined,
};
}
sawRateLimit = true;
// Parse the unified-window quota headers (present on real rate-limit 429s)
// and derive a reset-aware cooldown plan. This is the fix for "kept
// hammering the 5h/7d-exhausted account instead of switching": on an
// exhaustion 429 we rotate immediately and park the account until its
// ACTUAL reset, not a 60s hardcap.
const now = Date.now();
const quota = parseQuotaHeaders(errRespHeaders, { model: requestedModel });
const unifiedStatus = getUnifiedRateLimitStatus(errRespHeaders);
const cooldownPlan = planCooldownFor429(quota, retryAfterMs, now, unifiedStatus, overagePolicy, requestedModel);
const rateLimitKind = cooldownPlan.reason === "transient" ? "transient" : "quota";
recordAttemptError(account.label, account.type, 429, rateLimitKind);
logger.always(`[proxy] ← 429 account=${account.label} reason=${cooldownPlan.reason} scope=${cooldownPlan.scope} ` +
`retry-after=${retryAfterMs}ms 5h-status=${errRespHeaders["anthropic-ratelimit-unified-5h-status"] ?? "unknown"} ` +
`7d-status=${errRespHeaders["anthropic-ratelimit-unified-7d-status"] ?? "unknown"} ` +
`unified-status=${unifiedStatus ?? "unknown"} ` +
`→ ${cooldownPlan.rotateImmediately ? `rotate now, cool ${minutesUntil(cooldownPlan.coolingUntil, now)}m` : "retry same account (transient)"}`);
logAttempt(429, "rate_limit_error", String(lastError), {
retryable: true,
rateLimitKind,
cooldownReason: cooldownPlan.reason,
});
tracer?.setError("rate_limit_error", String(lastError).slice(0, 500));
tracer?.recordRetry(account.label, "rate_limit");
currentUpstreamSpan?.end();
return {
continueLoop: true,
retrySameAccount: !cooldownPlan.rotateImmediately,
retryAfterMs,
cooldownPlan,
...(quota ? { quota } : {}),
lastError,
sawRateLimit,
sawNetworkError,
upstreamSpan: undefined,
};
}
return {
continueLoop: false,
response,
lastError,
sawRateLimit,
sawNetworkError,
upstreamSpan: currentUpstreamSpan,
};
}
function shouldAttemptClaudeFallback(loopState) {
return loopState.invalidRequestFailure === null;
}
function buildDeferredClaudeAccountFailureResponse(args) {
const { ctx, tracer, requestStartTime, failure, buildLoggedClaudeError } = args;
if (failure.responseHeaders) {
ctx.responseHeaders = {
...(ctx.responseHeaders ?? {}),
...failure.responseHeaders,
};
}
tracer?.setError(failure.errorType, failure.message);
tracer?.end(failure.status, Date.now() - requestStartTime);
return buildLoggedClaudeError(failure.status, failure.message, failure.errorType);
}
async function handleAnthropicRoutedClaudeRequest(args) {
const { ctx, body, modelRouter, configGeneration, tracer, requestStartTime, accountStrategy, primaryAccountKey, accountAllowlist, quotaRoutingEnabled = isQuotaRoutingEnabled(), sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), ranking = "expiry-first", preferPrimary = false, sessionAffinityEnabled = false, sessionAffinityIdleTtlMs = 3_600_000, spillInflight = 0, buildLoggedClaudeError, logProxyBody, logFinalRequest, setRoutingDecision, } = args;
const parsedRequest = parseClaudeRequest(body);
// Loop prevention for the Codex-outbound-fallback leg (stage-c-trigger.md
// §3): a request that reached this handler AS a Codex-outbound fallback
// loopback call must never be offered a fallback chain that routes back
// into Codex, or Codex(exhausted) -> Anthropic -> Codex could cycle.
//
// The `x-neurolink-internal-origin` header alone is NOT the security
// boundary — any external client can send an arbitrary header. The real
// boundary is `consumeInternalProxyRequest`: it looks the caller-supplied
// `x-neurolink-internal-request` token up in a server-only, in-memory map
// keyed by a `crypto.randomUUID()` this same process minted (in
// `registerInternalProxyRequest`, codexProxyRoutes.ts) immediately before
// issuing its own loopback fetch, and returns a value only when that
// exact token is present. An external client cannot guess or observe
// that token — it never appears in any response the proxy sends a
// client — so it cannot forge a match.
//
// That call is single-use (deleted on read) and already made exactly
// once per request, earlier, by this worker's request-tracking
// middleware (`registerProxyRequestTracking` in proxy.ts) — which needs
// the same answer to set `RuntimeRequestMetadata.accountingScope` before
// any route handler runs. Calling `consumeInternalProxyRequest` a SECOND
// time here, with the same token, always finds it already deleted and
// returns `undefined` — silently leaving this filter permanently
// disengaged regardless of whether the request was genuinely internal.
// Read the middleware's already-authoritative result instead, via
// `getProxyRequestAccounting(ctx.requestId)` — the same lookup this file
// already uses elsewhere (~9322, ~9510) to distinguish an internal leg
// from a client request, and which is populated only when the
// middleware's own `consumeInternalProxyRequest` call found a genuine
// token match. This preserves the same unforgeable boundary without
// consuming the token twice.
const isCodexOutboundFallbackLeg = ctx.headers["x-neurolink-internal-origin"] === "codex-outbound-fallback" &&
getProxyRequestAccounting(ctx.requestId)?.accountingScope === "internal";
// The loopback leg owns the turn's usage metrics (its Codex parent records
// none), so the fallback origin label has to be stamped here.
if (isCodexOutboundFallbackLeg) {
tracer?.setRequestOrigin("codex-fallback");
}
const configuredFallbackPlan = buildProxyTranslationPlan({ provider: "anthropic", model: body.model }, isCodexOutboundFallbackLeg
? (modelRouter?.getFallbackChain() ?? []).filter((entry) => entry.provider !== "codex")
: (modelRouter?.getFallbackChain() ?? []), body.model, parsedRequest);
const hasConfiguredFallback = configuredFallbackPlan.attempts
.slice(1)
.some((attempt) => Boolean(attempt.provider && attempt.model));
const loadedAccounts = await loadClaudeProxyAccounts({
ctx,
body,
accountStrategy,
primaryAccountKey,
accountAllowlist,
quotaRoutingEnabled,
sessionSoftLimit,
sessionResetToleranceMs,
ranking,
preferPrimary,
sessionAffinityEnabled,
sessionAffinityIdleTtlMs,
spillInflight,
setRoutingDecision,
});
if ("failure" in loadedAccounts) {
// No usable local account. A node that has none of its own — or whose only
// accounts are disabled — is still entitled to borrow: that is the whole
// point of being lent capacity. Peers and explicitly configured fallbacks
// are tried before returning the credential error.
const peerOnlyResult = await tryBorrowFromPeers({ body, logFinalRequest });
if (peerOnlyResult) {
return peerOnlyResult;
}
const configuredFallbackResult = await tryConfiguredClaudeFallbackChain({
ctx,
body,
parsedFallbackRequest: parsedRequest,
fallbackPlan: configuredFallbackPlan,
modelRouter,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
});
if (configuredFallbackResult.response) {
return configuredFallbackResult.response;
}
if (configuredFallbackResult.invalidRequestFailure) {
return buildClaudeAnthropicFailureResponse({
tracer,
requestStartTime,
authFailureMessage: null,
authCooldownMessage: null,
invalidRequestFailure: configuredFallbackResult.invalidRequestFailure,
entitlementFailure: null,
scopedExhaustion: null,
sawNetworkError: false,
sawTransientFailure: false,
sawRateLimit: false,
lastError: undefined,
orderedAccounts: [],
buildLoggedClaudeError,
logProxyBody,
logFinalRequest,
});
}
if (configuredFallbackResult.terminalFailure) {
const failure = configuredFallbackResult.terminalFailure;
return buildConfiguredClaudeFallbackFailure({
failure,
buildLoggedClaudeError,
tracer,
requestStartTime,
});
}
return buildDeferredClaudeAccountFailureResponse({
ctx,
tracer,
requestStartTime,
failure: loadedAccounts.failure,
buildLoggedClaudeError,
});
}
const { accounts, enabledAccounts, orderedAccounts, bodyStr, requestStart, toolCount, url, clientHeaders, isClaudeClientRequest, sessionId, } = loadedAccounts;
// Accounts this request sent an upstream attempt to or whose token refresh
// failed; one skipped only for its admission cap is not among them.
const attemptedAccountKeys = new Set();
// Only fill-first over more than one account reads a binding (see
// selectClaudeProxyAccountOrder), so nothing else records one.
const bindServedSession = sessionAffinityEnabled &&
sessionId &&
accountStrategy === "fill-first" &&
enabledAccounts.length > 1
? (servingAccountKey) => bindSessionAfterServe({
sessionId,
servingAccountKey,
attemptedAccountKeys,
enabledAccounts,
idleTtlMs: sessionAffinityIdleTtlMs,
sessionSoftLimit,
sessionResetToleranceMs,
requestedModel: typeof body.model === "string" ? body.model : undefined,
})
: undefined;
// Snapshot the operator policy once. Reading the module value later would let
// a concurrent /limits call or a hot config reload change this request's
// answer partway through its own account loop.
const requestOveragePolicy = currentOveragePolicy();
const loopState = {
lastError: undefined,
sawRateLimit: false,
sawNetworkError: false,
sawTransientFailure: false,
invalidRequestFailure: null,
authFailureMessage: null,
authCooldownMessage: null,
entitlementFailure: null,
scopedExhaustion: null,
attemptNumber: 0,
};
const acctSelectionSpan = tracer?.startAccountSelection();
// Never re-hammer accounts with a known active cooldown. When every account
// is cooling, report the earliest persisted retry time without an upstream
// call; restarting the proxy must not erase or bypass this quarantine.
const nonCoolingAccounts = orderedAccounts.filter((a) => !isAccountCooling(a.key));
let effectiveAccounts = nonCoolingAccounts;
// If every usable account is cooling and the soonest recovery is a short
// transient burst cooldown, hold this request and pace its admission instead
// of returning a proxy-local 429 that makes the client retry in a herd. The
// helper rechecks because an in-flight retry can extend the cooldown while
// this request is waiting.
if (effectiveAccounts.length === 0) {
effectiveAccounts =
await waitForTransientAccountAvailability(orderedAccounts);
}
if (effectiveAccounts.length === 0 && orderedAccounts.length > 0) {
const coolingStates = orderedAccounts.map((account) => getOrCreateRuntimeState(account.key));
const hasRateLimitCooldown = coolingStates.some((state) => state.coolingReason !== "auth");
loopState.sawRateLimit = hasRateLimitCooldown;
loopState.sawTransientFailure = !hasRateLimitCooldown;
loopState.lastError = hasRateLimitCooldown
? "All Anthropic accounts have active rate-limit cooldowns"
: "All Anthropic accounts have active authentication cooldowns";
if (!hasRateLimitCooldown) {
const earliestRetryAt = Math.min(...coolingStates.map((state) => state.coolingUntil ?? Number.POSITIVE_INFINITY));
loopState.authCooldownMessage = `All ${orderedAccounts.length} Anthropic accounts are temporarily unavailable while OAuth refresh is cooling. Earliest retry at ${new Date(earliestRetryAt).toISOString()}.`;
}
}
// Second eligibility gate, alongside cooldowns: an account whose model-scoped
// cap for THIS model is spent will 429 with certainty. Unlike a cooldown the
// condition is per-model, so it must not park the account — it stays fully
// available for every other model.
const scopedExhaustion = evaluateScopedExhaustion(effectiveAccounts, typeof body.model === "string" ? body.model : undefined, Date.now(), requestOveragePolicy);
if (scopedExhaustion.eligible.length > 0) {
effectiveAccounts = scopedExhaustion.eligible;
}
else if (scopedExhaustion.exhaustion) {
// Every account is scoped out on evidence we trust. Record it and let the
// loop fall through to the post-loop path, so a configured fallback chain
// still runs and only the terminal message changes. Returning here instead
// would silently drop that fallback.
loopState.scopedExhaustion = scopedExhaustion.exhaustion;
loopState.lastError = `Model-scoped limit exhausted for ${scopedExhaustion.exhaustion.model}`;
effectiveAccounts = [];
}
// Otherwise the scoped evidence was stale or absent: attempt the request
// anyway rather than let a mis-parsed window take the pool down.
const accountAdmissionCapacity = modelRouter?.getMaxInflightPerAccount?.();
// When every eligible account is busy, reserve the first account that frees
// instead of arbitrarily waiting behind the last configured account.
let queuedAccountAdmission;
try {
if (accountAdmissionCapacity !== undefined &&
effectiveAccounts.length > 0 &&
effectiveAccounts.every((account) => !isAccountAdmissionAvailable(account.key, accountAdmissionCapacity))) {
queuedAccountAdmission = await acquireFirstAvailableAccountAdmission(effectiveAccounts.map((account) => account.key), accountAdmissionCapacity, ctx.abortSignal, MAX_TRANSIENT_QUEUE_WAIT_MS, configGeneration);
}
accountLoop: for (const [accountIndex, account,] of effectiveAccounts.entries()) {
const hasNextAccount = accountIndex < effectiveAccounts.length - 1;
const accountState = getOrCreateRuntimeState(account.key);
let transientSameAccountRetries = 0;
let rateLimitSameAccountRetries = 0;
while (true) {
const transportPermit = await providerTransportCoordinator.acquire(ctx.abortSignal);
if (transportPermit.allowed === false) {
loopState.sawNetworkError = true;
loopState.lastTransportErrorCode =
transportPermit.errorCode ?? undefined;
loopState.lastTransportScope = transportPermit.transportScope;
loopState.lastError = `Anthropic transport recovery probe failed (${transportPermit.errorCode ?? "unknown"})`;
// The probe that failed was another request's attempt; this one has
// sent nothing. Give it the same bounded same-account budget a direct
// transport failure gets, instead of failing every queued request the
// moment one probe times out.
const probeRetryBudget = transportPermit.connectPhase
? MAX_CONNECT_PHASE_SAME_ACCOUNT_RETRIES
: MAX_TRANSIENT_SAME_ACCOUNT_RETRIES;
if (transientSameAccountRetries < probeRetryBudget) {
transientSameAccountRetries += 1;
const delayMs = getTransientSameAccountRetryDelayMs(transientSameAccountRetries);
logger.always(`[proxy] retrying same account=${account.label} after failed transport recovery probe (${transientSameAccountRetries}/${probeRetryBudget}) in ${delayMs}ms`);
await sleep(delayMs);
continue;
}
break accountLoop;
}
loopState.attemptNumber += 1;
if (tracer && loopState.attemptNumber === 1 && acctSelectionSpan) {
tracer.setAccountSelection({
strategy: accountStrategy,
accountsTotal: accounts.length,
accountsHealthy: enabledAccounts.length,
selectedAccount: account.label,
accountType: account.type,
});
acctSelectionSpan.end();
}
const logAttempt = createAnthropicAttemptLogger({
ctx,
body,
toolCount,
requestStart,
tracer,
account,
attemptNumber: loopState.attemptNumber,
});
const preparedAttempt = await prepareAnthropicAccountAttempt({
ctx,
account,
accountState,
bodyStr,
clientHeaders,
isClaudeClientRequest,
url,
tracer,
attemptNumber: loopState.attemptNumber,
currentLastError: loopState.lastError,
currentAuthFailureMessage: loopState.authFailureMessage,
logAttempt,
logProxyBody,
});
loopState.lastError = preparedAttempt.lastError;
loopState.authFailureMessage = preparedAttempt.authFailureMessage;
if (preparedAttempt.continueLoop ||
!preparedAttempt.headers ||
!preparedAttempt.buildUpstreamBody ||
!preparedAttempt.finalBodyStr ||
!preparedAttempt.preparedContext ||
preparedAttempt.fetchStartMs === undefined) {
// A failed token refresh is this account failing the request, so a
// session bound to it must not stay there when another one serves.
attemptedAccountKeys.add(account.key);
if (transportPermit.probe) {
providerTransportCoordinator.reportProbeAbandoned(transportPermit);
}
continue accountLoop;
}
let admissionLease;
if (queuedAccountAdmission?.accountKey === account.key) {
admissionLease = queuedAccountAdmission.lease;
queuedAccountAdmission = undefined;
}
else {
admissionLease = tryAcquireAccountAdmission(account.key, accountAdmissionCapacity, configGeneration);
if (admissionLease && queuedAccountAdmission) {
// A preferred account became available while the race was being
// established; release the lower-priority reservation.
queuedAccountAdmission.lease.release();
queuedAccountAdmission = undefined;
}
}
if (!admissionLease) {
// A later account may be immediately available. Preserve the routing
// order but do not leave a request queued behind a busy first choice.
if (transportPermit.probe) {
providerTransportCoordinator.reportProbeAbandoned(transportPermit);
}
continue accountLoop;
}
attemptedAccountKeys.add(account.key);
let admissionTransferredToStream = false;
try {
let fetchResult;
try {
fetchResult = await fetchAnthropicAccountResponse({
ctx,
url,
headers: preparedAttempt.headers,
finalBodyStr: preparedAttempt.finalBodyStr,
preparedContext: preparedAttempt.preparedContext,
requestedModel: body.model,
account,
accountState,
enabledAccounts,
orderedAccounts,
tracer,
logAttempt,
logProxyBody,
fetchStartMs: preparedAttempt.fetchStartMs,
attemptNumber: loopState.attemptNumber,
currentLastError: loopState.lastError,
currentSawRateLimit: loopState.sawRateLimit,
currentSawNetworkError: loopState.sawNetworkError,
upstreamSpan: preparedAttempt.upstreamSpan,
});
}
catch (error) {
if (transportPermit.probe) {
providerTransportCoordinator.reportProbeAbandoned(transportPermit);
}
throw error;
}
if (fetchResult.transportScope) {
providerTransportCoordinator.reportTransportFailure(fetchResult.errorCode, fetchResult.transportScope, transportPermit, fetchResult.connectPhase === true);
}
else {
providerTransportCoordinator.reportSuccess(transportPermit);
}
loopState.lastError = fetchResult.lastError;
loopState.sawRateLimit = fetchResult.sawRateLimit;
loopState.sawNetworkError = fetchResult.sawNetworkError;
if (fetchResult.transportScope) {
loopState.lastTransportScope = fetchResult.transportScope;
loopState.lastTransportErrorCode = fetchResult.errorCode;
}
if (fetchResult.terminalError) {
return finalizeAnthropicTerminalFetchError({
terminalError: fetchResult.terminalError,
account,
tracer,
requestStartTime,
attemptNumber: loopState.attemptNumber,
logProxyBody,
logFinalRequest,
});
}
if (fetchResult.continueLoop || !fetchResult.response) {
// Genuine 429 (carries a cooldown plan derived from quota headers).
if (fetchResult.cooldownPlan) {
const plan = fetchResult.cooldownPlan;
// Refresh the account's quota snapshot for proactive selection.
if (fetchResult.quota) {
accountState.quota = mergeQuotaSnapshot(accountState.quota, fetchResult.quota);
saveAccountQuota(account.key, fetchResult.quota).catch(() => {
// Non-fatal: routing already has the in-memory snapshot.
});
}
// Publish the cooldown before retrying so requests arriving behind
// this one skip the throttled account instead of joining the burst.
let cooldownExtended = false;
if (plan.scope === "account" &&
(!accountState.coolingUntil ||
plan.coolingUntil > accountState.coolingUntil)) {
accountState.coolingUntil = plan.coolingUntil;
accountState.coolingReason = plan.reason;
cooldownExtended = true;
}
if (cooldownExtended && plan.scope === "account") {
await saveAccountCooldown(account.key, accountState.coolingUntil ?? plan.coolingUntil, accountState.coolingReason ?? plan.reason).catch(() => {
// Non-fatal: routing already has the in-memory cooldown.
});
}
// Transient retries are budgeted across all concurrent requests for
// this account/window. Exhaustion plans rotate immediately and never
// claim this budget.
const retry = fetchResult.retrySameAccount
? claimTransientRateLimitRetryDelay(account.key, plan.coolingUntil, fetchResult.retryAfterMs, rateLimitSameAccountRetries)
: undefined;
if (retry) {
rateLimitSameAccountRetries += 1;
logger.always(`[proxy] retrying same account=${account.label} after transient 429 (shared slot ${retry.slot}/${MAX_RATE_LIMIT_SAME_ACCOUNT_RETRIES}) in ${retry.delayMs}ms`);
await sleep(retry.delayMs);
continue;
}
// Exhaustion, or the shared transient retry budget being used up:
// rotate while the already-published cooldown remains active.
advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
logger.always(`[proxy] account=${account.label} rate-limited (${plan.reason}); cooling ~${minutesUntil(plan.coolingUntil, Date.now())}m until ${new Date(plan.coolingUntil).toISOString()}, rotating`);
if (plan.rotateImmediately) {
scheduleHandoffQuotaRefresh(account, effectiveAccounts[accountIndex + 1], plan.coolingUntil, sessionSoftLimit);
}
continue accountLoop;
}
// Transient error retry (network errors, 529 overloaded). A failure
// from the connect phase sent nothing, so it earns the larger budget.
const sameAccountRetryBudget = fetchResult.connectPhase
? MAX_CONNECT_PHASE_SAME_ACCOUNT_RETRIES
: MAX_TRANSIENT_SAME_ACCOUNT_RETRIES;
if (fetchResult.retrySameAccount &&
transientSameAccountRetries < sameAccountRetryBudget) {
transientSameAccountRetries += 1;
const delayMs = getTransientSameAccountRetryDelayMs(transientSameAccountRetries);
logger.always(`[proxy] retrying same account=${account.label} after transient network error (${transientSameAccountRetries}/${sameAccountRetryBudget}) in ${delayMs}ms`);
await sleep(delayMs);
continue;
}
if (fetchResult.retrySameAccount && !fetchResult.transportScope) {
logger.always(`[proxy] exhausted transient same-account retries for account=${account.label}; rotating`);
}
if (fetchResult.transportScope) {
logger.always(`[proxy] Anthropic ${fetchResult.transportScope} failure code=${fetchResult.errorCode ?? "unknown"}; suppressing cross-account rotation after ${transientSameAccountRetries + 1} attempts`);
break accountLoop;
}
continue accountLoop;
}
let upstreamSpan = fetchResult.upstreamSpan;
const response = fetchResult.response;
if (response.status === 401 &&
account.type === "oauth" &&
account.refreshToken) {
const authRetryResult = await handleAnthropicAuthRetry({
ctx,
body,
account,
accountState,
headers: preparedAttempt.headers,
buildUpstreamBody: preparedAttempt.buildUpstreamBody,
url,
enabledAccounts,
orderedAccounts,
tracer,
requestStartTime,
allocateAttemptNumber: () => {
loopState.attemptNumber += 1;
return loopState.attemptNumber;
},
upstreamSpan,
logAttempt,
logProxyBody,
logFinalRequest,
onStreamTerminal: admissionLease.release,
lastError: loopState.lastError,
authFailureMessage: loopState.authFailureMessage,
entitlementFailure: loopState.entitlementFailure,
sawRateLimit: loopState.sawRateLimit,
sawTransientFailure: loopState.sawTransientFailure,
sawNetworkError: loopState.sawNetworkError,
bindServedSession,
});
loopState.lastError = authRetryResult.lastError;
loopState.authFailureMessage = authRetryResult.authFailureMessage;
loopState.entitlementFailure = authRetryResult.entitlementFailure;
loopState.sawRateLimit = authRetryResult.sawRateLimit;
loopState.sawTransientFailure = authRetryResult.sawTransientFailure;
loopState.sawNetworkError = authRetryResult.sawNetworkError;
upstreamSpan = authRetryResult.upstreamSpan;
if (authRetryResult.response !== undefined) {
admissionTransferredToStream =
authRetryResult.holdsAccountAdmission === true;
return authRetryResult.response;
}
if (authRetryResult.continueLoop) {
if (hasNextAccount && authRetryResult.retryDelayMs) {
logger.always(`[proxy] pacing cross-account SSE overload rotation for ${authRetryResult.retryDelayMs}ms after auth refresh`);
await sleep(authRetryResult.retryDelayMs);
}
continue accountLoop;
}
}
if (!response.ok) {
const nonOkResult = await handleAnthropicNonOkResponse({
response,
account,
accountState,
enabledAccounts,
orderedAccounts,
tracer,
requestStartTime,
fetchStartMs: preparedAttempt.fetchStartMs,
attemptNumber: loopState.attemptNumber,
logAttempt,
logProxyBody,
logFinalRequest,
lastError: loopState.lastError,
authFailureMessage: loopState.authFailureMessage,
sawTransientFailure: loopState.sawTransientFailure,
invalidRequestFailure: loopState.invalidRequestFailure,
entitlementFailure: loopState.entitlementFailure,
allowConfiguredModelFallback: hasConfiguredFallback,
});
loopState.lastError = nonOkResult.lastError;
loopState.authFailureMessage = nonOkResult.authFailureMessage;
loopState.sawTransientFailure = nonOkResult.sawTransientFailure;
loopState.invalidRequestFailure = nonOkResult.invalidRequestFailure;
loopState.entitlementFailure = nonOkResult.entitlementFailure;
if (nonOkResult.response !== undefined) {
return nonOkResult.response;
}
if (nonOkResult.continueLoop) {
if (nonOkResult.retrySameAccount &&
transientSameAccountRetries < MAX_TRANSIENT_SAME_ACCOUNT_RETRIES) {
transientSameAccountRetries += 1;
const delayMs = getTransientSameAccountRetryDelayMs(transientSameAccountRetries);
logger.always(`[proxy] retrying same account=${account.label} after transient upstream ${response.status} (${transientSameAccountRetries}/${MAX_TRANSIENT_SAME_ACCOUNT_RETRIES}) in ${delayMs}ms`);
await sleep(delayMs);
continue;
}
if (nonOkResult.retrySameAccount) {
logger.always(`[proxy] exhausted transient same-account retries for account=${account.label}; rotating`);
}
if (hasNextAccount && nonOkResult.retryDelayMs) {
logger.always(`[proxy] pacing cross-account overload rotation for ${nonOkResult.retryDelayMs}ms`);
await sleep(nonOkResult.retryDelayMs);
}
continue accountLoop;
}
break accountLoop;
}
// Clear cooling on success — but only if the stored cooldown has already
// expired, so an older in-flight success can't wipe an active exhaustion
// cooldown just set by a concurrent 429. The success handler re-applies a
// cooldown via reconcileCooldownFromQuota when fresh quota headers
// report a rejected window without explicit overage availability.
if (accountState.coolingUntil &&
Date.now() >= accountState.coolingUntil) {
const expiredCooldown = accountState.coolingUntil;
accountState.coolingUntil = undefined;
accountState.coolingReason = undefined;
clearAccountCooldown(account.key, expiredCooldown).catch(() => {
// Best-effort cleanup; expired entries are ignored during seeding.
});
}
const successResult = await handleAnthropicSuccessfulResponse({
ctx,
body,
account,
accountState,
response,
tracer,
requestStartTime,
fetchStartMs: preparedAttempt.fetchStartMs,
attemptNumber: loopState.attemptNumber,
finalBodyStr: preparedAttempt.finalBodyStr,
upstreamSpan,
logAttempt,
logProxyBody,
logFinalRequest,
onStreamTerminal: admissionLease.release,
poolAccounts: enabledAccounts,
});
if ("retryNextAccount" in successResult) {
if (successResult.failure) {
loopState.lastError = successResult.failure.message;
loopState.sawRateLimit ||= successResult.failure.rateLimit;
loopState.sawTransientFailure ||=
!successResult.failure.rateLimit;
const { sameAccountRetry } = successResult.failure;
const retry = sameAccountRetry
? claimTransientRateLimitRetryDelay(account.key, sameAccountRetry.coolingUntil, sameAccountRetry.retryAfterMs, rateLimitSameAccountRetries)
: undefined;
if (retry) {
rateLimitSameAccountRetries += 1;
logger.always(`[proxy] retrying same account=${account.label} after transient in-stream rate limit (shared slot ${retry.slot}/${MAX_RATE_LIMIT_SAME_ACCOUNT_RETRIES}) in ${retry.delayMs}ms`);
await sleep(retry.delayMs);
continue;
}
if (hasNextAccount && successResult.failure.retryDelayMs) {
logger.always(`[proxy] pacing cross-account SSE overload rotation for ${successResult.failure.retryDelayMs}ms`);
await sleep(successResult.failure.retryDelayMs);
}
}
continue accountLoop;
}
admissionTransferredToStream =
successResult.holdsAccountAdmission === true;
if (successResult.served && rateLimitSameAccountRetries > 0) {
liftTransientCooldownAfterServedRetry(account, accountState);
}
if (bindServedSession && successResult.served) {
bindServedSession(account.key);
}
return successResult.response;
}
finally {
if (!admissionTransferredToStream) {
admissionLease.release();
}
}
}
}
}
finally {
queuedAccountAdmission?.lease.release();
if (loopState.attemptNumber === 0) {
acctSelectionSpan?.end();
}
}
// Deterministic invalid requests (for example, prompt-too-long) cannot be
// repaired by changing providers. Preserve the authoritative Anthropic 400
// rather than delaying it or returning unrelated fallback output.
if (shouldAttemptClaudeFallback(loopState)) {
let fallbackFailureMessage;
// Peers first. A peer serves the same models over the same wire format, so
// borrowing costs one extra hop, while the provider chain below has to
// reshape the request for a different API and answers as a different model.
const peerResult = await tryBorrowFromPeers({ body, logFinalRequest });
if (peerResult) {
return peerResult;
}
const configuredFallbackResult = await tryConfiguredClaudeFallbackChain({
ctx,
body,
parsedFallbackRequest: parsedRequest,
fallbackPlan: configuredFallbackPlan,
modelRouter,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
});
if (configuredFallbackResult.response) {
return configuredFallbackResult.response;
}
if (configuredFallbackResult.invalidRequestFailure) {
// Surface the failure of the provider actually attempted last.
return buildClaudeAnthropicFailureResponse({
tracer,
requestStartTime,
authFailureMessage: null,
authCooldownMessage: null,
invalidRequestFailure: configuredFallbackResult.invalidRequestFailure,
entitlementFailure: null,
scopedExhaustion: null,
sawNetworkError: false,
sawTransientFailure: false,
sawRateLimit: false,
lastError: undefined,
orderedAccounts: [],
buildLoggedClaudeError,
logProxyBody,
logFinalRequest,
});
}
fallbackFailureMessage = configuredFallbackResult.lastErrorMessage;
let terminalFallbackFailure = configuredFallbackResult.terminalFailure;
// A translation-layer-selected provider is only permitted by an explicit
// routing setting. Empty fallback chains otherwise stay within OAuth.
if (loopState.invalidRequestFailure === null &&
!loopState.sawRateLimit &&
!ctx.abortSignal?.aborted &&
terminalFallbackFailure?.retryable !== false &&
modelRouter?.isAutoFallbackEnabled?.()) {
const autoFallbackResult = await tryAutoClaudeFallback({
ctx,
body,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
});
if (autoFallbackResult.response) {
return autoFallbackResult.response;
}
fallbackFailureMessage =
autoFallbackResult.lastErrorMessage ?? fallbackFailureMessage;
terminalFallbackFailure =
autoFallbackResult.terminalFailure ?? terminalFallbackFailure;
}
if (terminalFallbackFailure) {
return buildConfiguredClaudeFallbackFailure({
failure: terminalFallbackFailure,
buildLoggedClaudeError,
tracer,
requestStartTime,
});
}
loopState.fallbackFailureMessage = fallbackFailureMessage;
}
// Terminal failure — usually "every account is rate-limited". This is the
// response a caller most needs limit data on, and historically the one that
// carried none. No single account served it, so report the account we would
// have tried first plus pool headroom, explicitly marked as a snapshot.
{
const primary = orderedAccounts[0];
const primaryState = primary
? accountRuntimeState.get(primary.key)
: undefined;
publishLimitHeaders(ctx, {
quota: primaryState?.quota ?? null,
source: primaryState?.quota ? "snapshot" : "none",
...(primary ? { account: primary } : {}),
...(primaryState ? { accountState: primaryState } : {}),
attempt: loopState.attemptNumber,
poolAccounts: enabledAccounts,
});
}
return buildClaudeAnthropicFailureResponse({
tracer,
requestStartTime,
authFailureMessage: loopState.authFailureMessage,
authCooldownMessage: loopState.authCooldownMessage,
invalidRequestFailure: loopState.invalidRequestFailure,
entitlementFailure: loopState.entitlementFailure,
scopedExhaustion: loopState.scopedExhaustion,
sawNetworkError: loopState.sawNetworkError,
sawTransientFailure: loopState.sawTransientFailure,
sawRateLimit: loopState.sawRateLimit,
lastError: loopState.lastError,
lastTransportErrorCode: loopState.lastTransportErrorCode,
lastTransportScope: loopState.lastTransportScope,
fallbackFailureMessage: loopState.fallbackFailureMessage,
orderedAccounts,
buildLoggedClaudeError,
logProxyBody,
logFinalRequest,
});
}
// ---------------------------------------------------------------------------
// Route factory
// ---------------------------------------------------------------------------
function isClaudeProxyRouteRuntimeOptions(value) {
return (value !== undefined &&
"runtimeConfigProvider" in value &&
typeof value.runtimeConfigProvider === "function");
}
function buildEarlyClaudeRequestError(args) {
const { ctx, body, status, message, errorType } = args;
if (isProxyRequestFinalized(ctx.requestId)) {
return buildClaudeError(status, message);
}
recordFinalError(status, PROXY_INTERNAL_ACCOUNT_LABEL, PROXY_INTERNAL_ACCOUNT_TYPE, {
requestId: ctx.requestId,
errorType,
terminalOutcome: "handler_error",
message,
});
void logRequest({
timestamp: new Date().toISOString(),
requestId: ctx.requestId,
method: ctx.method,
path: ctx.path,
model: typeof body?.model === "string" ? body.model : "",
stream: body?.stream ?? false,
toolCount: Array.isArray(body?.tools) ? body.tools.length : 0,
account: PROXY_INTERNAL_ACCOUNT_LABEL,
accountType: PROXY_INTERNAL_ACCOUNT_TYPE,
responseStatus: status,
responseTimeMs: 0,
errorType,
errorMessage: message,
});
return buildClaudeError(status, message);
}
/**
* Create Claude-compatible proxy routes.
*
* Every request flows through ctx.neurolink.generate() or ctx.neurolink.stream().
* No direct fetch() calls to api.anthropic.com.
*
* @param modelRouter - Optional model router for remapping model names.
* @param basePath - Base path prefix (default: "" since Claude API uses /v1/...).
* @returns RouteGroup with Claude-compatible endpoints.
*/
/**
* Quota timestamps arrive in two units. `sessionResetAt`, `weeklyResetAt` and
* each window's `resetsAt` are unix SECONDS; `lastUpdated`, `updatedAt` and
* `coolingUntil` are already MILLISECONDS. Blanket-multiplying would push the
* millisecond fields tens of thousands of years into the future, so only the
* seconds fields are converted, and 0 becomes null rather than epoch zero.
*/
function toMillis(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0
? Math.round(value * 1000)
: null;
}
/**
* Normalise one account's quota for a dashboard consumer.
*
* `severity` and `isActive` are absent on header-sourced windows — a
* structural property of how those rows are parsed, not a transient gap — so
* every consumer would otherwise need the same fallback branch.
*/
function normalizeQuotaForAccounts(quota) {
if (!quota || typeof quota !== "object") {
return null;
}
const q = { ...quota };
q.sessionResetAtMs = toMillis(q.sessionResetAt);
q.weeklyResetAtMs = toMillis(q.weeklyResetAt);
if (Array.isArray(q.windows)) {
q.windows = q.windows.map((raw) => {
const w = { ...raw };
w.resetsAtMs = toMillis(w.resetsAt);
w.severity =
w.severity ?? (w.status === "rejected" ? "critical" : "normal");
w.isActive = w.isActive ?? false;
return w;
});
}
return q;
}
/** "rejected" and "throttled" both mean degraded; unknown strings stay unknown. */
function quotaHealth(status) {
if (status === "allowed") {
return "ok";
}
if (status === "rejected" || status === "throttled") {
return "degraded";
}
return "unknown";
}
export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrategy = "fill-first", passthroughMode = false, primaryAccountKey, accountAllowlistOrRuntimeOptions) {
const accountAllowlist = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
? accountAllowlistOrRuntimeOptions.accountAllowlist
: accountAllowlistOrRuntimeOptions;
const runtimeConfigProvider = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
? accountAllowlistOrRuntimeOptions.runtimeConfigProvider
: undefined;
return {
prefix: `${basePath}/v1`,
routes: [
// =====================================================================
// POST /v1/messages -- Main chat completions endpoint
// =====================================================================
{
method: "POST",
path: `${basePath}/v1/messages`,
handler: async (ctx) => {
const requestRouting = runtimeConfigProvider?.() ?? {
generation: 0,
strategy: accountStrategy,
modelRouter,
passthrough: passthroughMode,
primaryAccountKey,
accountAllowlist,
quotaRoutingEnabled: isQuotaRoutingEnabled(),
sessionSoftLimit: getSessionSoftLimit(),
sessionResetToleranceMs: getSessionResetToleranceMs(),
useOverage: "auto",
accountRanking: "expiry-first",
preferPrimary: false,
sessionAffinity: false,
sessionAffinityIdleTtlMs: 3_600_000,
spillInflight: 0,
};
setOveragePolicy(requestRouting.useOverage);
const requestModelRouter = requestRouting.modelRouter;
const body = ctx.body;
// 1. Validate
if (typeof body?.model !== "string" ||
!Array.isArray(body?.messages)) {
return buildEarlyClaudeRequestError({
ctx,
body,
status: 400,
message: "Missing required fields: model, messages",
errorType: "invalid_request_error",
});
}
// 2. Resolve model via router (or pass through to anthropic)
// Guard: without a model router, only Claude models are allowed.
const modelLower = body.model.toLowerCase();
if (!requestModelRouter && !modelLower.startsWith("claude-")) {
return buildEarlyClaudeRequestError({
ctx,
body,
status: 404,
message: `Model '${body.model}' is not an Anthropic model. ` +
`The proxy only supports Claude models. ` +
`Use a model router to route non-Claude models to other providers.`,
errorType: "not_found_error",
});
}
const route = requestModelRouter?.resolve(body.model) ?? {
provider: "anthropic",
model: body.model,
};
const clientRequestBody = JSON.stringify(body);
// 3. Create request runtime context (tracer, loggers, error builder)
const { tracer, requestStartTime, logProxyBody, logFinalRequest, buildLoggedClaudeError, setRoutingDecision, } = createClaudeRequestRuntimeContext({
ctx,
body,
clientRequestBody,
});
try {
// 4. Route based on target provider
if (route.provider === null) {
tracer?.setError("not_found_error", `Model '${body.model}' is not a Claude model.`);
tracer?.end(404, Date.now() - requestStartTime);
return buildLoggedClaudeError(404, `Model '${body.model}' is not a Claude model. Use a model router to route it to another provider.`);
}
if (route.provider === "anthropic") {
tracer?.setMode("passthrough");
if (requestRouting.passthrough) {
return await handleClaudePassthroughRequest({
ctx,
body,
clientRequestBody,
tracer,
requestStartTime,
logProxyBody,
logFinalRequest,
});
}
return await handleAnthropicRoutedClaudeRequest({
ctx,
body,
modelRouter: requestModelRouter,
configGeneration: requestRouting.generation,
tracer,
requestStartTime,
accountStrategy: requestRouting.strategy,
primaryAccountKey: requestRouting.primaryAccountKey,
accountAllowlist: requestRouting.accountAllowlist,
quotaRoutingEnabled: requestRouting.quotaRoutingEnabled,
sessionSoftLimit: requestRouting.sessionSoftLimit,
sessionResetToleranceMs: requestRouting.sessionResetToleranceMs,
ranking: requestRouting.accountRanking,
preferPrimary: requestRouting.preferPrimary,
sessionAffinityEnabled: requestRouting.sessionAffinity,
sessionAffinityIdleTtlMs: requestRouting.sessionAffinityIdleTtlMs,
spillInflight: requestRouting.spillInflight,
buildLoggedClaudeError,
logProxyBody,
logFinalRequest,
setRoutingDecision,
});
}
else {
return await handleTranslatedClaudeRequest({
ctx,
body,
route: {
provider: route.provider,
model: route.model,
},
modelRouter: requestModelRouter,
tracer,
requestStartTime,
logProxyBody,
});
}
}
catch (error) {
if (ctx.abortSignal?.aborted) {
return new Response(JSON.stringify(buildLoggedClaudeError(499, "Client cancelled request", "client_cancelled")), {
status: 499,
headers: { "content-type": "application/json" },
});
}
const localRefusal = error instanceof ProxyContextPreflightError
? error
: getProxyTokenBudgetError(error);
if (localRefusal) {
if (error instanceof ProxyContextPreflightError &&
error.evidence) {
ctx.metadata.contextPreflight = error.evidence;
}
const refusal = buildLoggedClaudeError(localRefusal.status, localRefusal.message, localRefusal.code, { errorCode: localRefusal.code, retryable: false });
return new Response(JSON.stringify({
...refusal,
error: {
...refusal.error,
code: localRefusal.code,
retryable: false,
},
}), {
status: localRefusal.status,
headers: { "content-type": "application/json" },
});
}
const errMsg = error instanceof Error ? error.message : String(error);
logger.error(`[claude-proxy] Generation error for ${body.model}: ${errMsg}`);
tracer?.setError("generation_error", errMsg.slice(0, 500));
tracer?.end(502, Date.now() - requestStartTime);
// Native transport errors retain their original code/cause and
// last-attempt account through the runtime error boundary. Only
// explicit local admission refusals are converted above.
if (route.provider === "anthropic") {
throw error;
}
return buildLoggedClaudeError(502, `Generation failed: ${error instanceof Error ? error.message : "unknown error"}`);
}
},
description: "Claude-compatible messages endpoint routed through NeuroLink",
tags: ["claude-proxy", "messages"],
streaming: { enabled: true, contentType: "text/event-stream" },
},
// =====================================================================
// POST /peer/heartbeat -- complete-mode check-in
//
// The only control surface a complete-mode borrower still touches. It
// answers with a fresh lease while the grant is active, and with a stop
// once it is not — which is how a pause reaches a borrower whose requests
// never come through here at all.
// =====================================================================
{
method: "POST",
path: `${basePath}/peer/heartbeat`,
handler: async (ctx) => {
const token = ctx.headers["x-neurolink-share-token"];
const grantId = ctx.headers["x-neurolink-grant-id"];
if (!token || !grantId) {
// A stop, not an HTTP error: the borrower reads `ok`/`stop` from
// the body, and a status code here would be discarded by the route
// adaptor while reading as though it were enforced.
return { ok: false, stop: true, reason: "no grant identified" };
}
const grant = await getShareGrant(grantId);
// The lease secret is the shared credential for this surface: the
// borrower proves identity with the same secret it verifies leases
// with, so a heartbeat needs no separate token to leak.
if (!grant ||
!grant.leaseSecret ||
!secretsMatch(grant.leaseSecret, token)) {
return { ok: false, stop: true, reason: "grant not recognized" };
}
if (grant.level !== "complete") {
return {
ok: false,
stop: true,
reason: "grant is not a complete share",
};
}
if (grant.state !== "active") {
return { ok: false, stop: true, reason: grant.state };
}
// Fold in what the borrower says it spent. This is self-reported and
// treated as such — `share status` reconciles it against the
// account's real utilization, which the borrower cannot influence.
const body = ctx.body;
const reported = Number(body?.coinsSpent ?? 0);
if (Number.isFinite(reported) && reported > 0) {
await recordReportedResidentSpend(grant, reported);
}
// Weigh the claim against what the account actually did. A borrower
// that stops reporting is invisible in every other signal we have.
const drift = await auditCompleteShareHeartbeat(grant, Number.isFinite(reported) ? reported : 0);
if (drift.paused) {
logger.always(`[proxy] auto-paused share ${grant.peerLabel}: ${drift.detail}`);
return { ok: false, stop: true, reason: "usage drift" };
}
return { ok: true, lease: issueLease(grant) };
},
description: "Complete-share heartbeat: report spend, renew the lease",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// GET /peer/handshake -- version and capability negotiation
//
// The cheapest possible "are we still on speaking terms": it touches no
// account, spends no capacity and reaches no upstream, so a borrower can
// call it on a timer. It reports the grant's own state and nothing about
// the pool behind it.
// =====================================================================
{
method: "GET",
path: `${basePath}/peer/handshake`,
handler: async (ctx) => {
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
return {
ok: true,
protocol: PEER_PROTOCOL_VERSION,
capabilities: PEER_CAPABILITIES,
grant: {
peerLabel: auth.grant.peerLabel,
level: auth.grant.level,
state: auth.grant.state,
ledger: auth.grant.entitlement.ledger,
},
};
},
description: "Peer handshake: protocol version, capabilities, grant state",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// POST /peer/provision -- borrower lodges a PKCE challenge
//
// Split provisioning: the borrower keeps the verifier and sends only its
// digest, so the lender is never in possession of anything that could
// become a credential. The lender authorizes in its own browser and
// relays back a code that is useless without the verifier.
// =====================================================================
{
method: "POST",
path: `${basePath}/peer/provision`,
handler: async (ctx) => {
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
if (auth.grant.state !== "active") {
return buildShareRefusal(auth.grant.state === "paused" ? "paused" : "revoked", { status: 403, grant: auth.grant }).body;
}
if (auth.grant.level !== "complete") {
return {
type: "error",
error: {
type: "invalid_request_error",
message: "This is a live share. Ask the lender to run " +
"`neurolink proxy share level --to complete` first.",
},
};
}
const body = ctx.body;
const opened = await openProvisionRequest({
grantId: auth.grant.id,
codeChallenge: String(body?.codeChallenge ?? ""),
state: String(body?.state ?? ""),
});
if (opened.ok !== true) {
return {
type: "error",
error: {
type: "invalid_request_error",
message: opened.reason,
},
};
}
logger.always(`[proxy] ${auth.grant.peerLabel} asked to be provisioned — run ` +
`\`neurolink proxy share provision --peer ${auth.grant.peerLabel}\``);
return {
ok: true,
status: "pending",
expiresAt: opened.request.expiresAt,
};
},
description: "Lodge a PKCE challenge for a resident credential (complete shares)",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// GET /peer/provision -- borrower collects its authorization code
//
// Answers once. A code that could be claimed twice would let a replay
// mint a second credential on the lender's account, so consumption is
// recorded before the value is handed over.
// =====================================================================
{
method: "GET",
path: `${basePath}/peer/provision`,
handler: async (ctx) => {
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
const claimed = await claimProvisionRequest(auth.grant.id);
if (claimed.status !== "ready") {
return { ok: true, status: claimed.status };
}
// The heartbeat address the borrower will call home on. Without it a
// resident grant can never renew and stops at its offline grace.
const lenderUrl = await getNodePublicUrl();
return {
ok: true,
status: "ready",
claim: buildProvisionClaim({
grant: auth.grant,
// The borrower renames this locally at `peer request --name`;
// here it only has to make the token-store label unique.
lenderName: "lender",
...(lenderUrl ? { lenderUrl } : {}),
code: claimed.code,
state: claimed.state,
}),
};
},
description: "Collect the authorization code the lender produced",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// GET /peer/receipts -- collect signed statements of what was charged
//
// So the lender's word is not the only record. Each receipt carries the
// usage it was computed from, and sequences are contiguous, so a borrower
// can recompute every charge and see a withheld one as a gap.
// =====================================================================
{
method: "GET",
path: `${basePath}/peer/receipts`,
handler: async (ctx) => {
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
const since = Number(ctx.query?.since ?? 0);
const collected = await listShareReceipts(auth.grant.id, Number.isFinite(since) ? since : 0);
return { ok: true, receipts: collected };
},
description: "Collect signed receipts for this grant's settled charges",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// POST /peer/net -- settle one round of reciprocal netting
//
// Both sides state cumulative positions, and the round forgives the
// overlap not yet forgiven. Cumulative rather than incremental is what
// makes a replayed round free rather than a second payout.
// =====================================================================
{
method: "POST",
path: `${basePath}/peer/net`,
handler: async (ctx) => {
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
const secret = auth.grant.receiptSecret;
if (!secret) {
return {
type: "error",
error: {
type: "invalid_request_error",
message: "This grant predates receipts and cannot be netted. " +
"Ask the lender to re-issue it.",
},
};
}
const body = ctx.body;
const consumedByYou = Number(body?.consumedByYou ?? NaN);
const alreadyNetted = Number(body?.alreadyNetted ?? NaN);
if (!Number.isFinite(consumedByYou) ||
!Number.isFinite(alreadyNetted)) {
return {
type: "error",
error: {
type: "invalid_request_error",
message: "A netting claim needs both cumulative totals.",
},
};
}
// The claim is the peer's own accounting, so it is signed: a figure
// that credits the caller must not be forgeable by anyone who merely
// reaches the port.
const authentic = verifySharePayload({ consumedByYou, alreadyNetted, grantId: auth.grant.id }, String(body?.signature ?? ""), secret);
if (!authentic) {
return buildShareRefusal("unknown_token", { status: 401 }).body;
}
const result = await applyReciprocalNetting({
grantId: auth.grant.id,
consumedFromPeer: consumedByYou,
peerAlreadyNetted: alreadyNetted,
});
return { ok: true, ...result };
},
description: "Settle one round of reciprocal netting against this grant",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// POST /peer/note -- check or redeem a transferable coin note
//
// Holding the note is the credential for a check; redeeming additionally
// needs a grant to credit. Marking spent and crediting happen under one
// lock, so two holders racing the same note produce exactly one credit.
// =====================================================================
{
method: "POST",
path: `${basePath}/peer/note`,
handler: async (ctx) => {
const body = ctx.body;
const note = decodeShareNote(String(body?.note ?? ""));
if (!note) {
return {
type: "error",
error: {
type: "invalid_request_error",
message: "That is not a NeuroLink coin note.",
},
};
}
const secret = await getNoteSecret();
if (!body?.redeem) {
// A status check needs no grant: the note itself is the credential,
// and the answer tells a stranger nothing they did not already hold.
const inspected = await inspectShareNote(note, secret);
return { ok: true, ...inspected };
}
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
if (auth.grant.entitlement.ledger !== "coins") {
return {
type: "error",
error: {
type: "invalid_request_error",
message: "This grant is unlimited — there is no balance to redeem into.",
},
};
}
const redeemed = await redeemShareNote({
note,
grantId: auth.grant.id,
secret,
});
if (redeemed.ok !== true) {
return {
ok: false,
status: redeemed.status,
error: {
type: "invalid_request_error",
message: `That note is ${redeemed.status}.`,
},
};
}
return {
ok: true,
status: "redeemed",
coins: redeemed.coins,
balance: redeemed.balance ?? null,
};
},
description: "Check or redeem a transferable coin note",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// GET /peer/limits -- what this grant may still do
//
// So a borrower can decide *before* spending a request whether this peer
// is worth the extra hop. Everything here is scoped to the caller's own
// grant: no account labels, no per-account figures, nothing that would
// describe the lender's pool.
// =====================================================================
{
method: "GET",
path: `${basePath}/peer/limits`,
handler: async (ctx) => {
const auth = await authenticatePeerRequest(ctx.headers);
if (isPeerAuthRefusal(auth)) {
return auth.body;
}
const limitsRouting = runtimeConfigProvider?.();
const snapshot = await buildPeerLimitsSnapshot(auth.grant, limitsRouting?.accountAllowlist ?? accountAllowlist);
return { ok: true, ...snapshot };
},
description: "Peer limits: remaining coins, slice left, whether the grant can be served",
tags: ["claude-proxy", "sharing"],
},
// =====================================================================
// GET /v1/models -- List available models (Anthropic schema)
//
// Returns the Anthropic-shaped list response (`type`, `display_name`,
// `created_at`, `first_id`, `last_id`, `has_more`) so Anthropic SDK
// consumers calling this Claude-compatible surface get the schema they
// expect. The OpenAI route serves the same data in OpenAI list format
// via its own builder.
// =====================================================================
{
method: "GET",
path: `${basePath}/v1/models`,
handler: async (_ctx) => {
const requestModelRouter = runtimeConfigProvider
? runtimeConfigProvider().modelRouter
: modelRouter;
return withSpan({
name: "neurolink.http.claudeProxy.listModels",
tracer: tracers.http,
attributes: { "http.route": `${basePath}/v1/models` },
}, async () => buildAnthropicModelsListResponse(requestModelRouter));
},
description: "List available models (Anthropic schema)",
tags: ["claude-proxy", "models"],
},
// =====================================================================
// POST /v1/messages/count_tokens -- Token counting endpoint
// =====================================================================
{
method: "POST",
path: `${basePath}/v1/messages/count_tokens`,
handler: async (ctx) => withSpan({
name: "neurolink.http.claudeProxy.countTokens",
tracer: tracers.http,
attributes: {
"http.route": `${basePath}/v1/messages/count_tokens`,
},
}, async (span) => {
const body = ctx.body;
if (typeof body?.model !== "string" ||
!Array.isArray(body?.messages)) {
return buildClaudeError(400, "Missing required fields: model, messages");
}
// Simple estimation using character-to-token heuristic
const text = body.messages
.map((m) => typeof m.content === "string"
? m.content
: JSON.stringify(m.content))
.join(" ");
const inputTokens = Math.ceil(text.length / 4);
span.setAttribute("ai.model", body.model);
span.setAttribute("gen_ai.usage.input_tokens", inputTokens);
return { input_tokens: inputTokens };
}),
description: "Count tokens for a messages request",
tags: ["claude-proxy", "tokens"],
},
// =====================================================================
// GET /limits -- Fresh account limits from Anthropic's usage endpoint
// =====================================================================
{
method: "GET",
path: `${basePath}/limits`,
handler: async (ctx) => {
// `/limits` names every account and its quota — for an OAuth account
// the label is the operator's email. That is an operator diagnostic,
// not something a borrower may read, and a refresh also drives real
// usage-API calls on the lender's accounts. Borrowed traffic gets
// `/peer/limits`, which answers the same routing question about the
// borrower's own grant without describing the pool.
if (getShareContext()) {
return buildShareRefusal("no_capacity", {
status: 403,
message: "This proxy does not expose pool limits to peers. " +
"Use GET /peer/limits for what your grant may still do.",
}).body;
}
const limitsRouting = runtimeConfigProvider?.();
const effectiveAllowlist = limitsRouting?.accountAllowlist ?? accountAllowlist;
// A refresh reconciles cooldowns from the fetched quota, so it makes
// the same overage judgement the request path does and needs the same
// operator policy in scope.
setOveragePolicy(limitsRouting?.useOverage);
const snapshotOnly = ctx.query?.snapshot === "true" || ctx.query?.snapshot === "1";
const accountFilter = ctx.query?.account;
return withSpan({
name: "neurolink.http.claudeProxy.limits",
tracer: tracers.http,
attributes: { "http.route": `${basePath}/limits` },
}, async () => {
// Single-flight: concurrent full refreshes share one sweep.
if (!snapshotOnly && !accountFilter) {
if (!limitsRefreshInFlight) {
limitsRefreshInFlight = refreshAccountLimits({
accountAllowlist: effectiveAllowlist,
}).finally(() => {
limitsRefreshInFlight = null;
});
}
return limitsRefreshInFlight;
}
return refreshAccountLimits({
accountAllowlist: effectiveAllowlist,
accountFilter,
snapshotOnly,
});
});
},
description: "Fetch fresh per-account limits from Anthropic (usage API). " +
"?account=<label> for one account, ?snapshot=true for stored state",
tags: ["claude-proxy", "limits"],
},
// =====================================================================
// GET /accounts -- One dashboard-shaped row per account
// =====================================================================
// Joins what previously took two calls with incompatible schemas plus a
// client-side merge: request counters from the usage snapshot, quota from
// the limits snapshot, and today's tokens and cost from the request log.
{
method: "GET",
path: `${basePath}/accounts`,
handler: async (ctx) => {
const routing = runtimeConfigProvider?.();
const effectiveAllowlist = routing?.accountAllowlist ?? accountAllowlist;
// Hot-reloaded routing wins over the value baked in at construction,
// the same precedence the request path uses.
const effectivePrimaryKey = routing?.primaryAccountKey ?? primaryAccountKey;
// Rows carry a bare label in one loop and a full pool key in the
// other; anthropicAccountKeysEqual normalises both, so neither can
// miss the match on prefix alone.
const isPrimaryAccount = (keyOrLabel) => !!effectivePrimaryKey &&
!!keyOrLabel &&
anthropicAccountKeysEqual(keyOrLabel, effectivePrimaryKey);
// Cached by default. This endpoint is built to be polled, and a live
// refresh spends the user's own OAuth credentials against Anthropic's
// usage API — one dashboard on a short interval would hammer it.
const live = ctx.query?.refresh === "true";
// A live refresh reconciles cooldowns from the fetched quota, so it
// makes the same overage judgement the request path does and needs
// the same operator policy in scope — exactly as /limits sets it.
if (live) {
setOveragePolicy(routing?.useOverage);
}
// Every other source in this handler degrades to a partial row on
// failure; quota must too, or one upstream hiccup 500s a dashboard.
let limits = {
fetchedAt: Date.now(),
snapshot: !live,
results: [],
};
let quotaError = null;
try {
limits = await refreshAccountLimits({
accountAllowlist: effectiveAllowlist,
snapshotOnly: !live,
});
}
catch (error) {
quotaError = error instanceof Error ? error.message : String(error);
}
const { getUsageSnapshot } = await import("../../proxy/usageStats.js");
const statsAccounts = getUsageSnapshot().stats.accounts;
// `cooling` is the field an operator actually acts on, and one small
// file read answers it. `allowed`/`expired` additionally need the
// token store, which /status guards behind its own timeouts — this
// route reports them as null rather than taking that latency.
const now = Date.now();
// Typed from the loader, NOT re-asserted into a local shape. The
// first version cast this to `{ until?: number }` and read
// `.until` — a field PersistedAccountCooldown does not have, so
// every row reported cooling: false and the assertion is precisely
// what stopped the compiler from saying so.
let cooldowns = {};
try {
cooldowns = await loadAccountCooldowns();
}
catch {
// Non-fatal: every row simply reports cooling: false.
}
const isCooling = (key) => {
if (!key) {
return false;
}
const until = cooldowns[key]?.coolingUntil;
return typeof until === "number" && until > now;
};
let usageByAccount = new Map();
let usageError = null;
let usageDate = "";
try {
const { readAccountUsage, currentUsageDate } = await import("../../proxy/accountLedger.js");
usageDate = currentUsageDate();
usageByAccount = await readAccountUsage(usageDate);
}
catch (error) {
// Usage is the optional half; quota and status must still render.
usageError = error instanceof Error ? error.message : String(error);
}
// Joined by provider-qualified KEY, never by label. One email can
// be logged in to both engines; joined by label, the Codex login was
// swallowed by the Anthropic row (and its counters could land on
// that row, last write winning). Legacy stats entries that predate
// the key carry only a label and a type, which is enough to derive
// it; a keyed entry for the same login wins over a legacy one.
const statsByKey = new Map();
for (const [mapKey, entry] of Object.entries(statsAccounts)) {
const identity = resolveProxyStatusAccountIdentity(entry.label, entry.type, entry.key ?? mapKey);
const identityKey = identity.key ?? mapKey;
const existing = statsByKey.get(identityKey);
if (!existing || (entry.key && !existing.key)) {
statsByKey.set(identityKey, { ...entry, identityKey });
}
}
// Which logins exist at all, disabled ones included. A stats entry
// with no login behind it is a REMOVED account, not an unrouted one.
// If the store cannot be read, err towards showing rows: hiding a
// real login is the worse mistake, and the old behaviour.
let knownKeys;
try {
knownKeys = await listKnownAccountKeys();
}
catch {
knownKeys = null;
}
const rows = [];
const claimed = new Set();
// Real logins drive the row set. Building it from the log instead
// would silently drop any account that happened to serve no traffic
// today, which is exactly when an operator most wants to see it.
for (const result of limits.results) {
const label = result.account;
claimed.add(result.key);
const stat = statsByKey.get(result.key);
const quota = normalizeQuotaForAccounts(result.quota);
const cooling = isCooling(result.key ?? null);
rows.push({
label,
key: result.key ?? null,
provider: result.provider,
kind: "account",
type: result.type ?? stat?.type ?? "oauth",
// result.status describes how the quota was obtained
// ("snapshot"/"fetched"), not the account's health, so it must
// not leak into a field consumers read as health.
// BOTH quota windows, not just the weekly one. A session
// window that is rejected or throttled stops the account
// serving right now — this same file gates routing on
// `sessionStatus` (see the overage checks above) — so an
// account with a healthy weekly window and a degraded session
// window would otherwise be reported "active" while being
// unable to take work.
status: cooling
? "cooling"
: quotaHealth(quota?.weeklyStatus) === "degraded" ||
quotaHealth(quota?.sessionStatus) === "degraded"
? "exhausted"
: "active",
cooling,
allowed: null,
expired: null,
isPrimary: isPrimaryAccount(result.key ?? label),
requests: stat ? stat.successCount + stat.errorCount : null,
errors: stat?.errorCount ?? null,
rateLimits: stat?.rateLimitCount ?? null,
quotaRateLimits: stat?.quotaRateLimitCount ?? null,
quota: quota
? {
...quota,
sessionHealth: quotaHealth(quota.sessionStatus),
weeklyHealth: quotaHealth(quota.weeklyStatus),
}
: null,
usage: usageByAccount.get(result.key) ?? null,
});
}
// Plumbing rows are still reported, but tagged, so a consumer can
// show or hide them rather than rendering them as credentials.
//
// A real login can land here too: the listers skip accounts the
// token store has disabled or the allowlist excludes, so a login
// with real usage history but no current route is absent from
// limits.results. Tagging that as plumbing hid the one account an
// operator is looking for when they ask why traffic stopped. It
// stays kind "account", with a status saying why it has no quota
// block — but only while the login still EXISTS. A stats entry whose
// login has been removed from the store is history, not a
// credential, and rendering it as "unrouted" put a phantom account
// on every dashboard for as long as the counters file lived.
for (const entry of statsByKey.values()) {
const identity = resolveProxyStatusAccountIdentity(entry.label, entry.type, entry.identityKey);
if (identity.key !== null && claimed.has(identity.key)) {
continue;
}
const isLogin = identity.provider !== "other";
if (isLogin &&
identity.key !== null &&
knownKeys !== null &&
!knownKeys.has(identity.key)) {
continue;
}
rows.push({
label: entry.label,
key: isLogin ? identity.key : null,
...(isLogin ? { provider: identity.provider } : {}),
kind: isLogin
? "account"
: entry.type === "translation"
? "translation"
: "internal",
// The row's type is the credential kind; the engine is
// `provider`. Stats record Codex logins as "codex-oauth", which
// consumers that only know the credential kinds read as
// plumbing, so it is reported as the OAuth login it is.
type: identity.provider === "codex" ? "oauth" : entry.type,
status: isLogin ? "unrouted" : null,
cooling: false,
allowed: null,
expired: null,
isPrimary: isLogin ? isPrimaryAccount(identity.key) : false,
requests: entry.successCount + entry.errorCount,
errors: entry.errorCount,
rateLimits: entry.rateLimitCount,
quotaRateLimits: entry.quotaRateLimitCount,
quota: null,
// Looked up the same way the routed rows do. These accounts are
// built FROM today's usage stats, so an account that served
// requests and was then disabled or excluded still has tokens
// and cost in the ledger — hardcoding null here discarded
// exactly the usage an operator is looking for when they ask
// why traffic stopped.
usage: identity.key !== null
? (usageByAccount.get(identity.key) ?? null)
: null,
});
}
const response = {
generatedAt: Date.now(),
usageDate,
quotaFromSnapshot: !live,
usageError,
quotaError,
// Pooled accounts bill by subscription. This is what the recorded
// tokens would have cost at published rates — a value estimate, not
// an invoice — and consumers must label it that way.
costBasis: "api-equivalent",
accounts: rows,
};
return response;
},
description: "One row per account: status, quota, and today's tokens and " +
"API-equivalent cost. ?refresh=true forces a live quota fetch",
tags: ["claude-proxy", "accounts"],
},
],
};
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function getOrCreateRuntimeState(accountKey) {
const existing = accountRuntimeState.get(accountKey);
if (existing) {
return existing;
}
const initial = {
consecutiveRefreshFailures: 0,
permanentlyDisabled: false,
};
accountRuntimeState.set(accountKey, initial);
return initial;
}
function reconcileEligibleAccountRuntimeState(account) {
const state = getOrCreateRuntimeState(account.key);
const tokenChanged = state.lastToken !== account.token ||
state.lastRefreshToken !== account.refreshToken;
const wasPermanentlyDisabled = state.permanentlyDisabled;
if (wasPermanentlyDisabled) {
logger.always(`[proxy] account=${account.label} is enabled in the token store; clearing stale runtime auth-disable state`);
}
state.permanentlyDisabled = false;
if (tokenChanged || wasPermanentlyDisabled) {
state.consecutiveRefreshFailures = 0;
}
state.lastToken = account.token;
state.lastRefreshToken = account.refreshToken;
}
async function disableAccountUntilReauth(account, state, reason) {
try {
const { tokenStore } = await import("../../auth/tokenStore.js");
const providerKey = account.persistTarget &&
typeof account.persistTarget !== "string" &&
"providerKey" in account.persistTarget
? account.persistTarget.providerKey
: undefined;
if (providerKey) {
const disabled = await tokenStore.markDisabledIfCurrent(providerKey, {
accessToken: account.token,
refreshToken: account.refreshToken,
expiresAt: account.expiresAt ?? 0,
}, reason);
if (!disabled) {
state.permanentlyDisabled = false;
logger.always(`[proxy] account=${account.label} credentials changed while authentication was in flight; ignored stale disable`);
return false;
}
}
else {
await tokenStore.markDisabled(account.key, reason);
}
}
catch (e) {
logger.debug(`[proxy] failed to persist disabled state for ${account.label}: ${e instanceof Error ? e.message : String(e)}`);
}
state.permanentlyDisabled = true;
logger.always(reason === "entitlement_blocked"
? `[proxy] account=${account.label} disabled: the organization blocks Claude Code OAuth. Ask an admin to re-enable it, then run: neurolink auth enable ${account.key}`
: `[proxy] account=${account.label} disabled until re-authentication. Run: neurolink auth login anthropic --method oauth`);
return true;
}
async function coolAccountAfterTransientRefreshFailure(account, state) {
state.consecutiveRefreshFailures += 1;
const exponent = Math.min(state.consecutiveRefreshFailures - 1, 4);
const delayMs = Math.min(AUTH_REFRESH_MAX_COOLDOWN_MS, AUTH_REFRESH_BASE_COOLDOWN_MS * 2 ** exponent);
const coolingUntil = Date.now() + delayMs;
if (!state.coolingUntil || coolingUntil > state.coolingUntil) {
state.coolingUntil = coolingUntil;
state.coolingReason = "auth";
}
const effectiveCoolingUntil = state.coolingUntil ?? coolingUntil;
const effectiveReason = state.coolingReason ?? "auth";
state.coolingUntil = effectiveCoolingUntil;
state.coolingReason = effectiveReason;
await saveAccountCooldown(account.key, effectiveCoolingUntil, effectiveReason).catch(() => {
// Non-fatal: the in-memory cooldown still prevents immediate re-hammering.
});
return effectiveCoolingUntil;
}
async function clearAuthCooldownAfterRefresh(account, state) {
state.consecutiveRefreshFailures = 0;
if (state.coolingReason !== "auth" || !state.coolingUntil) {
return;
}
const previousCoolingUntil = state.coolingUntil;
state.coolingUntil = undefined;
state.coolingReason = undefined;
await clearAccountCooldown(account.key, previousCoolingUntil).catch(() => {
// Best-effort cleanup; an expired auth cooldown is harmless on next boot.
});
}
function formatReauthMessage(labels) {
const value = Array.isArray(labels) ? labels.join(", ") : labels;
return `Account(s) require re-authentication: ${value}. Run: neurolink auth login anthropic --method oauth`;
}
function summarizeErrorMessage(message, maxLength = 180) {
const compact = message.replace(/\s+/g, " ").trim();
if (compact.length <= maxLength) {
return compact;
}
return `${compact.slice(0, maxLength)}...`;
}
export function getTransientSameAccountRetryDelayMs(retryNumber) {
const index = Math.min(Math.max(retryNumber - 1, 0), TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS.length - 1);
return TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS[index] ?? 0;
}
export function getOverloadRotationDelayMs(attemptNumber) {
const index = Math.min(Math.max(attemptNumber - 1, 0), OVERLOAD_ACCOUNT_ROTATION_DELAYS_MS.length - 1);
return jitteredDelay(OVERLOAD_ACCOUNT_ROTATION_DELAYS_MS[index] ?? 250);
}
async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
/** Honor `base` (e.g. retry-after) as a floor and add positive jitter on top,
* so a fleet of concurrent requests (parallel subagents) doesn't wake and
* retry in the same millisecond and re-trip the limit together. */
function jitteredDelay(base) {
return base + Math.floor(Math.random() * base * 0.25);
}
/**
* Get low-level network error code from an unknown error shape.
*/
function getErrorCode(error) {
if (!error || typeof error !== "object") {
return undefined;
}
const directCode = error.code;
if (typeof directCode === "string") {
return directCode;
}
const cause = error.cause;
if (!cause || typeof cause !== "object") {
return undefined;
}
const causeCode = cause.code;
return typeof causeCode === "string" ? causeCode : undefined;
}
function describeTransportError(error) {
const message = error instanceof Error ? error.message : String(error);
if (!error || typeof error !== "object") {
return message;
}
const cause = error.cause;
const causeMessage = cause instanceof Error
? cause.message
: cause && typeof cause === "object"
? String(cause.message ?? "")
: "";
const code = getErrorCode(error);
const detail = [
code,
causeMessage && causeMessage !== message && causeMessage,
]
.filter(Boolean)
.join(": ");
return detail ? `${message} (${detail})` : message;
}
/**
* Whether a transport failure provably happened while connecting, before any
* byte of the request left the process.
*
* Node tags its connect errors with `syscall: "connect"`; with happy-eyeballs
* enabled (the default) the error is an AggregateError whose every entry is
* one such attempt, and undici wraps either as `fetch failed` with the
* original as `cause`. The code alone cannot make this call: an `ETIMEDOUT`
* from the connect timer and an `ETIMEDOUT` from a socket that went quiet
* after dispatch look identical by code, and only the first is safe to
* retry. Both proxies behind one lossy Wi-Fi uplink returned 116 terminal
* 502s in a day on exactly that ambiguity.
*/
function isConnectPhaseNetworkError(error) {
const seen = new Set();
let current = error;
while (current && typeof current === "object" && !seen.has(current)) {
seen.add(current);
const candidate = current;
if (candidate.syscall === "connect") {
return true;
}
if (Array.isArray(candidate.errors) && candidate.errors.length > 0) {
return candidate.errors.every((entry) => entry !== null &&
typeof entry === "object" &&
entry.syscall === "connect");
}
current = candidate.cause;
}
return false;
}
/**
* Determine whether a POST can be retried without risking duplicate provider
* work. Only failures that prove connection establishment did not complete are
* safe; a reset, socket error, or response timeout can happen after dispatch.
*/
function isRetryableNetworkError(error) {
if (isConnectPhaseNetworkError(error)) {
return true;
}
const code = getErrorCode(error);
return (code !== undefined &&
[
"ECONNREFUSED",
"EADDRNOTAVAIL",
// The Anthropic host is fixed, so ENOTFOUND can be a transient resolver
// outage. Keep it inside the existing bounded same-account retry budget.
"ENOTFOUND",
"EAI_AGAIN",
"EHOSTUNREACH",
"UND_ERR_CONNECT_TIMEOUT",
"UND_ERR_CONNECT",
].includes(code));
}
function classifyNetworkTransportScope(error) {
const code = getErrorCode(error);
return code === "ENOTFOUND" || code === "EAI_AGAIN"
? "shared_provider_transport"
: "connection_transport";
}
const TRANSIENT_HTTP_STATUSES = new Set([
408, 500, 502, 503, 504, 520, 521, 522, 523, 524, 525, 526, 529,
]);
/**
* Parse a Claude error payload when available.
*/
export function parseClaudeErrorBody(errBody) {
try {
const parsed = JSON.parse(errBody);
if (parsed &&
parsed.type === "error" &&
parsed.error &&
typeof parsed.error === "object") {
const errorCode = parsed.error.details?.error_code;
return {
errorType: typeof parsed.error.type === "string" ? parsed.error.type : undefined,
message: typeof parsed.error.message === "string"
? parsed.error.message
: undefined,
...(typeof errorCode === "string" ? { errorCode } : {}),
};
}
}
catch {
// ignore parse errors; caller will use heuristics
}
return {};
}
/**
* Detect malformed request errors that should not trigger account/provider failover.
*/
export function isInvalidRequestError(status, errBody) {
if (status === 422) {
return true;
}
const parsed = parseClaudeErrorBody(errBody);
return (parsed.errorType === "invalid_request_error" ||
errBody.includes("invalid_request_error"));
}
/**
* A 404 for a retired model can be served by an explicitly configured fallback;
* other 404s remain terminal so a bad endpoint or resource is never disguised.
*/
function isAnthropicModelNotFound(status, errBody) {
if (status !== 404) {
return false;
}
const parsed = parseClaudeErrorBody(errBody);
return (parsed.errorType === "not_found_error" &&
(parsed.message ?? "").toLowerCase().includes("model"));
}
/**
* A subscription-specific beta rejection. Anthropic returns
* `400 invalid_request_error` with a message like
* "The long context beta is not yet available for this subscription." when an
* account's plan tier does not grant an optional beta the proxy injected (see
* CLAUDE_CODE_OAUTH_BETAS in anthropicOAuth.ts). Unlike a genuinely malformed
* request, the SAME request can succeed on a different account whose tier
* grants the beta — so this must be treated as retryable on the next account
* rather than a terminal client-facing 400.
*/
export function isSubscriptionBetaRejection(status, errBody) {
if (status !== 400) {
return false;
}
const parsed = parseClaudeErrorBody(errBody);
if (parsed.errorType !== "invalid_request_error") {
return false;
}
const message = (parsed.message ?? "").toLowerCase();
return (message.includes("beta") &&
message.includes("subscription") &&
message.includes("available"));
}
/**
* Organization/plan entitlement codes Anthropic reports in `error.details`.
* Membership here is what promotes a rejection from "rotate" to "remember".
*/
const ENTITLEMENT_ERROR_CODES = new Set([
"oauth_not_allowed_for_organization",
"oauth_not_allowed",
"organization_disabled",
]);
/**
* An entitlement rejection: Anthropic refuses THIS credential on organization
* or plan policy, e.g. `403 permission_error` /
* "OAuth authentication is currently not allowed for this organization."
*
* The taxonomy makes this safe to rotate on: `permission_error` is reserved for
* credential/organization permission, distinct from `invalid_request_error`
* (request shape) and `not_found_error`, both already terminal above. No
* `permission_error` is caused by the request body, so the identical request
* can succeed on a different account.
*
* Deliberately broad — rotation is cheap and reversible. Persisting the block
* is gated on the narrower {@link isDurableEntitlementBlock}.
*/
export function isAccountEntitlementError(status, errBody) {
if (status !== 401 && status !== 402 && status !== 403) {
return false;
}
const parsed = parseClaudeErrorBody(errBody);
if (parsed.errorType === "permission_error") {
return true;
}
return (parsed.errorCode !== undefined &&
ENTITLEMENT_ERROR_CODES.has(parsed.errorCode));
}
/**
* Whether an entitlement rejection is specific enough to disable the account
* until someone re-enables it. Narrower than {@link isAccountEntitlementError}
* because disabling is sticky and user-visible: an unrecognised
* `permission_error` should rotate through the pool and surface a 403, never
* durably remove a credential on a guess.
*/
export function isDurableEntitlementBlock(status, errBody) {
if (!isAccountEntitlementError(status, errBody)) {
return false;
}
const parsed = parseClaudeErrorBody(errBody);
return (parsed.errorCode !== undefined &&
ENTITLEMENT_ERROR_CODES.has(parsed.errorCode));
}
function normalizeClaudeRequestForAnthropic(body) {
return {
...body,
messages: body.messages.map((msg) => {
if (typeof msg.content !== "string") {
return msg;
}
return {
...msg,
content: [{ type: "text", text: msg.content }],
};
}),
};
}
/**
* Backward-compatible alias — delegates to the shared translation engine.
*/
export const buildProxyFallbackOptions = buildTranslationOptions;
/**
* Detect transient upstream failures that should trigger account/provider failover.
*
* Includes Cloudflare 52x statuses and Anthropic 400/api_error wrappers that
* carry transient HTML responses (e.g. 520 pages) inside `error.message`.
*/
export function isTransientHttpFailure(status, errBody) {
if (TRANSIENT_HTTP_STATUSES.has(status)) {
return true;
}
if (status !== 400) {
return false;
}
const parsed = parseClaudeErrorBody(errBody);
if (parsed.errorType === "overloaded_error") {
return true;
}
if (parsed.errorType !== "api_error") {
return false;
}
const normalized = (parsed.message ?? errBody).toLowerCase();
return (normalized.includes("<!doctype html") ||
normalized.includes("error code 520") ||
normalized.includes("web server is returning an unknown error") ||
normalized.includes("cloudflare") ||
normalized.includes("internal server error"));
}
/**
* An upstream overload is already a capacity signal. Retrying it on the same
* OAuth account only consumes its remaining concurrency and delays rotation.
*/
export function isUpstreamOverload(status, errBody) {
return (status === 529 ||
parseClaudeErrorBody(errBody).errorType === "overloaded_error");
}
/** Remove provider credentials before they enter persistent proxy diagnostics. */
const MAX_PROVIDER_ERROR_MESSAGE_LENGTH = 500;
export function redactProviderErrorMessage(message) {
return sanitizeForLog(message, MAX_PROVIDER_ERROR_MESSAGE_LENGTH);
}
// ---------------------------------------------------------------------------
// Test hooks (not part of the public SDK API). Only consumed by the proxy
// continuous-test-suite to drive the in-process resolver/reset logic without
// spinning up a full proxy. Keep this surface small.
// ---------------------------------------------------------------------------
export const __testHooks = {
clearClaudeFallbackTerminalEvidence,
normalizeQuotaForAccounts,
resolveHomeIndex,
maybeResetPrimaryToHome,
planCooldownFor429,
reconcileCooldownFromQuota,
refreshAccountLimits,
applyAccountUsageResult,
clearLimitsRefreshStateForTests: () => {
lastUsageFetchAt.clear();
limitsRefreshInFlight = null;
accountQuotaRefreshCoordinator.clear();
},
setAccountDirectoryForTests: (override) => {
accountDirectoryOverride = override;
},
isRetryableNetworkError,
isConnectPhaseNetworkError,
MAX_CONNECT_PHASE_SAME_ACCOUNT_RETRIES,
clearProviderTransportCoordinatorForTests: () => {
providerTransportCoordinator.clear();
},
isPermanentRefreshFailure,
getStreamFailureDetails,
trackUpstreamReadableStream,
orderAccountsByQuota,
orderAccountsByQuotaWithMetrics,
scheduleAdaptiveQuotaRefreshes,
scheduleHandoffQuotaRefresh,
getQuotaRefreshState: (key) => accountQuotaRefreshCoordinator.getState(key),
buildQuotaRoutingDecision: (accounts, now, primaryKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), options) => {
const order = orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftLimit, sessionResetToleranceMs, options);
return buildRoutingDecision({
accounts,
orderedAccounts: order.orderedAccounts,
metricsByKey: order.metricsByKey,
evaluatedAt: now,
strategy: "fill-first",
primaryKey,
quotaRoutingEnabled: true,
quotaOrdered: accounts.length > 1,
sessionSoftLimit,
sessionResetToleranceMs,
rotationOffset: 0,
policySelectionReason: order.reason,
});
},
buildRoutingDecision,
resetEpochToMs,
seedRuntimeQuotasFromDisk,
reconcileEligibleAccountRuntimeState,
getAccountRuntimeState: (key) => {
const state = accountRuntimeState.get(key);
return state ? { ...state } : undefined;
},
setPrimaryAccountIndex: (index) => {
primaryAccountIndex = index;
},
getPrimaryAccountIndex: () => primaryAccountIndex,
setAccountRuntimeState: (key, state) => {
const existing = accountRuntimeState.get(key) ?? getOrCreateRuntimeState(key);
Object.assign(existing, state);
accountRuntimeState.set(key, existing);
},
resetAllRuntimeState: () => {
accountRuntimeState.clear();
transientRateLimitRetryBudgets.clear();
transientCooldownAdmissionSchedules.clear();
accountAdmissionStates.clear();
accountQuotaRefreshCoordinator.clear();
providerTransportCoordinator.clear();
loggedRoutingPolicyErrors.clear();
forceSpillThrowForTests = false;
forceAffinityLookupThrowForTests = false;
forceBindRecheckThrowForTests = false;
primaryAccountIndex = 0;
lastKnownAccountCount = 0;
},
polyfillOAuthBody: (bodyStr, isClaudeClientRequest) => polyfillOAuthBody(bodyStr, "test-account-token", null, isClaudeClientRequest),
// Pins the sessionId path loadClaudeProxyAccounts resolves ahead of
// account selection and feeds into selectClaudeProxyAccountOrder for
// session affinity.
extractSnapshotBodySessionIdForTests: (body) => extractSnapshotBody(body)?.sessionId,
selectClaudeProxyAccountOrderForTests: selectClaudeProxyAccountOrder,
setForceSpillThrowForTests: (value) => {
forceSpillThrowForTests = value;
},
setForceAffinityLookupThrowForTests: (value) => {
forceAffinityLookupThrowForTests = value;
},
setForceBindRecheckThrowForTests: (value) => {
forceBindRecheckThrowForTests = value;
},
isAntiAbuseConstruction429,
fetchAnthropicAccountResponse,
finalizeAnthropicTerminalFetchError,
handleAnthropicNonOkResponse,
handleAnthropicAuthRetry,
handleAnthropicStreamingSuccessResponse,
claimTransientRateLimitRetry,
claimTransientCooldownAdmission,
waitForTransientAccountAvailability,
acquireAccountAdmission,
acquireFirstAvailableAccountAdmission,
tryAcquireAccountAdmission,
enqueueAccountAdmission,
getAccountInflight,
getAccountAdmissionSnapshot: (accountKey) => {
const state = accountAdmissionStates.get(accountKey);
return state
? { active: state.active, waiting: state.waiters.length }
: { active: 0, waiting: 0 };
},
// The snapshot above reports {active:0, waiting:0} both for "no entry" and
// for "empty entry", so it cannot see a stranded allocation. This can.
hasAccountAdmissionState: (accountKey) => accountAdmissionStates.has(accountKey),
describeTransportError,
redactProviderErrorMessage,
isUpstreamOverload,
getOverloadRotationDelayMs,
redactHeadersForBorrower,
shouldAttemptClaudeFallback,
isAnthropicModelNotFound,
getCodexFallbackInvalidRequestFailure,
executeClaudeFallbackWithRetry,
buildClaudeAnthropicFailureResponse,
isAccountEntitlementError,
isDurableEntitlementBlock,
evaluateScopedExhaustion,
matchScopedQuotaWindow,
setOveragePolicy,
};