@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
604 lines (603 loc) • 33.3 kB
TypeScript
import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
import { ProxyTracer } from "../../proxy/proxyTracer.js";
import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js";
import type { ProxyAccountDirectoryOverride } from "../../types/index.js";
import type { AccountAllowlist, AccountAdmissionLease, JsonObject, AccountCooldownPlan, AccountQuota, AccountQuotaWindow, AccountUsageFetchResult, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicEntitlementFailure, AnthropicInvalidRequestFailure, AnthropicLoopState, AnthropicScopedExhaustion, AnthropicNonOkResult, AnthropicSuccessResult, AnthropicUpstreamBody, AnthropicUpstreamBodyBuilder, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRankingPolicy, ProxyAccountRoutingAffinityEvidence, ProxyAccountRoutingDecision, ProxyAccountRoutingReason, ProxyAccountRoutingSpillEvidence, ProxyAccountSortMetrics, ProxyAffinityPrecedenceSkipReason, ProxyBodyCaptureLogger, ProxyLimitsRefreshResponse, ProxyQuotaCooldownUpdate, ProxyOveragePolicy, ProxyPassthroughAccount, ProxyRoutingPolicySnapshot, QueuedAccountAdmission, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
declare function tryAcquireAccountAdmission(accountKey: string, capacity: number | undefined, generation?: number): AccountAdmissionLease | undefined;
/** Current in-flight request count for one account, unlimited or capped. */
declare function getAccountInflight(accountKey: string): number;
declare function enqueueAccountAdmission(accountKey: string, capacity: number, generation?: number): QueuedAccountAdmission;
declare function acquireAccountAdmission(accountKey: string, capacity: number, abortSignal?: AbortSignal, timeoutMs?: number, generation?: number): Promise<AccountAdmissionLease | undefined>;
declare function acquireFirstAvailableAccountAdmission(accountKeys: string[], capacity: number, abortSignal?: AbortSignal, timeoutMs?: number, generation?: number): Promise<{
accountKey: string;
lease: AccountAdmissionLease;
} | undefined>;
/** 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. */
declare function resolveHomeIndex(enabledAccounts: ProxyPassthroughAccount[], primaryAccountKey: string | undefined): number;
/** 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. */
declare function maybeResetPrimaryToHome(enabledAccounts: ProxyPassthroughAccount[], primaryAccountKey: string | undefined): void;
declare function claimTransientRateLimitRetry(accountKey: string, coolingUntil: number, now?: number): number | undefined;
declare function claimTransientCooldownAdmission(accountKey: string, coolingUntil: number, now?: number): number | undefined;
declare function waitForTransientAccountAvailability(orderedAccounts: ProxyPassthroughAccount[]): Promise<ProxyPassthroughAccount[]>;
/** 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. */
declare function resetEpochToMs(resetEpoch: number | undefined, now: number): number | undefined;
/**
* 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.
*/
declare function planCooldownFor429(quota: AccountQuota | null, retryAfterMs: number, now: number, unifiedStatus?: string | undefined, policy?: ProxyOveragePolicy, requestedModel?: string): AccountCooldownPlan;
/**
* 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.
*/
declare function reconcileCooldownFromQuota(state: RuntimeAccountState, quota: AccountQuota, now: number, policy?: ProxyOveragePolicy): ProxyQuotaCooldownUpdate;
/**
* 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.
*/
declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise<void>;
declare function applyAccountUsageResult(account: ProxyPassthroughAccount, fetchResult: AccountUsageFetchResult, observedAt: number, prior?: AccountQuota | null): Promise<AccountQuota | null>;
/**
* 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.
*/
declare function refreshAccountLimits(options?: {
accountAllowlist?: AccountAllowlist;
accountFilter?: string;
snapshotOnly?: boolean;
}): Promise<ProxyLimitsRefreshResponse>;
/**
* 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.
*/
declare function setOveragePolicy(policy: ProxyOveragePolicy | undefined): void;
/**
* 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.
*/
declare function matchScopedQuotaWindow(quota: AccountQuota | undefined, requestedModel: string | undefined, now?: number): AccountQuotaWindow | null;
/**
* 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.
*/
declare function evaluateScopedExhaustion(accounts: ProxyPassthroughAccount[], requestedModel: string | undefined, now?: number, policy?: ProxyOveragePolicy): {
eligible: ProxyPassthroughAccount[];
exhaustion: AnthropicScopedExhaustion | null;
};
declare function orderAccountsByQuotaWithMetrics(accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit: number, sessionResetToleranceMs: number, options?: {
requestedModel?: string;
policy?: {
ranking?: ProxyAccountRankingPolicy;
affinityKey?: string;
preferPrimary?: boolean;
};
}): {
orderedAccounts: ProxyPassthroughAccount[];
metricsByKey: Map<string, ProxyAccountSortMetrics>;
reason: ProxyAccountRoutingReason;
affinitySkippedReason: ProxyAffinityPrecedenceSkipReason | null;
};
/**
* 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.
*/
declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number, requestedModel?: string): ProxyPassthroughAccount[];
declare function scheduleAdaptiveQuotaRefreshes(accounts: ProxyPassthroughAccount[], orderedAccounts: ProxyPassthroughAccount[], sessionSoftLimit: number, routingMetrics?: ReadonlyMap<string, ProxyAccountSortMetrics>): void;
declare function scheduleHandoffQuotaRefresh(current: ProxyPassthroughAccount, candidate: ProxyPassthroughAccount | undefined, handoffEpoch: number, sessionSoftLimit: number): void;
declare function buildRoutingDecision(args: {
accounts: ProxyPassthroughAccount[];
orderedAccounts: ProxyPassthroughAccount[];
metricsByKey: ReadonlyMap<string, ProxyAccountSortMetrics>;
evaluatedAt: number;
strategy: "round-robin" | "fill-first";
primaryKey: string | undefined;
quotaRoutingEnabled: boolean;
quotaOrdered: boolean;
sessionSoftLimit: number;
sessionResetToleranceMs: number;
rotationOffset: number;
policySelectionReason?: ProxyAccountRoutingReason;
policy?: ProxyRoutingPolicySnapshot;
affinity?: ProxyAccountRoutingAffinityEvidence;
spill?: ProxyAccountRoutingSpillEvidence;
}): ProxyAccountRoutingDecision | undefined;
declare function selectClaudeProxyAccountOrder(args: {
enabledAccounts: ProxyPassthroughAccount[];
accountStrategy: "round-robin" | "fill-first";
primaryAccountKey: string | undefined;
quotaRoutingEnabled: boolean;
sessionSoftLimit: number;
sessionResetToleranceMs: number;
requestedModel?: string;
sessionId?: string;
ranking: ProxyAccountRankingPolicy;
preferPrimary: boolean;
sessionAffinityEnabled: boolean;
sessionAffinityIdleTtlMs: number;
spillInflight: number;
setRoutingDecision: (decision: ProxyAccountRoutingDecision) => void;
}): {
orderedAccounts: ProxyPassthroughAccount[];
metricsByKey: Map<string, ProxyAccountSortMetrics>;
};
declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>): {
stream: ReadableStream<Uint8Array>;
outcome: Promise<StreamTerminalOutcome>;
};
/**
* 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.
*/
declare function redactHeadersForBorrower(headers: Record<string, string>): Record<string, string>;
/**
* 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 declare function resolveServedFallbackModel(metadata: {
codexFallbackModel?: unknown;
vertexFallbackModel?: unknown;
}, accountType: string | undefined): string | undefined;
declare function clearClaudeFallbackTerminalEvidence(ctx: ServerContext): void;
declare function executeClaudeFallbackTranslation(args: {
ctx: ServerContext;
body: ClaudeRequest;
tracer?: ProxyTracer;
requestStartTime: number;
logProxyBody: ProxyBodyCaptureLogger;
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
inputTokens?: number;
outputTokens?: number;
cacheCreationTokens?: number;
cacheReadTokens?: number;
}) => void;
options: Parameters<ServerContext["neurolink"]["stream"]>[0];
providerLabel: string;
/**
* Idle timeout for the fallback stream, in ms. Defaults to
* FALLBACK_STREAM_IDLE_TIMEOUT_MS.
*
* Injectable purely so a test can drive the timeout path in milliseconds
* instead of two minutes. The alternative the coverage used before was
* patching `globalThis.setTimeout` to fire every timer at 0ms for the
* duration of an await — which rewrites the delay of ANY timer created in
* that window, not just this one's, inside a 280-case suite sharing a single
* process. That is not a hypothetical: an attempt to measure the patched
* case with its own `setTimeout`-based watchdog had the watchdog rewritten
* out from under it and reported an instant false hang.
*/
idleTimeoutMs?: number;
}): Promise<unknown>;
declare function executeClaudeFallbackWithRetry(args: Parameters<typeof executeClaudeFallbackTranslation>[0]): Promise<unknown>;
declare function getCodexFallbackInvalidRequestFailure(error: unknown): AnthropicInvalidRequestFailure | null;
declare function buildClaudeAnthropicFailureResponse(args: {
tracer?: ProxyTracer;
requestStartTime: number;
authFailureMessage: string | null;
authCooldownMessage: string | null;
invalidRequestFailure: {
status: number;
body: string;
contentType?: string;
} | null;
entitlementFailure: AnthropicEntitlementFailure | null;
scopedExhaustion: AnthropicScopedExhaustion | null;
sawNetworkError: boolean;
sawTransientFailure: boolean;
sawRateLimit: boolean;
lastError: unknown;
lastTransportErrorCode?: string;
lastTransportScope?: "shared_provider_transport" | "connection_transport";
fallbackFailureMessage?: string;
orderedAccounts: ProxyPassthroughAccount[];
buildLoggedClaudeError: ClaudeLoggedErrorBuilder;
logProxyBody: ProxyBodyCaptureLogger;
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
inputTokens?: number;
outputTokens?: number;
cacheCreationTokens?: number;
cacheReadTokens?: number;
}) => void;
}): unknown;
declare function handleAnthropicStreamingSuccessResponse(args: {
ctx: ServerContext;
body: ClaudeRequest;
account: ProxyPassthroughAccount;
accountState: RuntimeAccountState;
response: Response;
responseHeaders: Record<string, string>;
tracer?: ProxyTracer;
requestStartTime: number;
fetchStartMs: number;
attemptNumber: number;
finalBodyStr: string;
upstreamSpan?: import("@opentelemetry/api").Span;
logAttempt: AnthropicAttemptLogger;
logProxyBody: ProxyBodyCaptureLogger;
onStreamTerminal?: () => void;
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
inputTokens?: number;
outputTokens?: number;
cacheCreationTokens?: number;
cacheReadTokens?: number;
}) => void;
}): Promise<AnthropicSuccessResult>;
declare function getStreamFailureDetails(outcome: StreamTerminalOutcome): {
status: number;
errorType: string;
message: string;
} | undefined;
declare function handleAnthropicAuthRetry(args: {
ctx: ServerContext;
body: ClaudeRequest;
account: ProxyPassthroughAccount;
accountState: RuntimeAccountState;
headers: Record<string, string>;
buildUpstreamBody: AnthropicUpstreamBodyBuilder;
url: string;
enabledAccounts: ProxyPassthroughAccount[];
orderedAccounts: ProxyPassthroughAccount[];
tracer?: ProxyTracer;
requestStartTime: number;
allocateAttemptNumber: () => number;
upstreamSpan?: import("@opentelemetry/api").Span;
logAttempt: AnthropicAttemptLogger;
logProxyBody: ProxyBodyCaptureLogger;
onStreamTerminal?: () => void;
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
inputTokens?: number;
outputTokens?: number;
cacheCreationTokens?: number;
cacheReadTokens?: number;
}) => void;
lastError: unknown;
authFailureMessage: string | null;
entitlementFailure: AnthropicEntitlementFailure | null;
sawRateLimit: boolean;
sawTransientFailure: boolean;
sawNetworkError: boolean;
bindServedSession?: (servingAccountKey: string) => void;
}): Promise<AnthropicAuthRetryResult>;
declare function finalizeAnthropicTerminalFetchError(args: {
terminalError: NonNullable<AnthropicUpstreamFetchResult["terminalError"]>;
account: ProxyPassthroughAccount;
tracer?: ProxyTracer;
requestStartTime: number;
attemptNumber: number;
logProxyBody: ProxyBodyCaptureLogger;
logFinalRequest: ClaudeFinalRequestLogger;
}): Response | unknown;
declare function handleAnthropicNonOkResponse(args: {
response: Response;
account: ProxyPassthroughAccount;
accountState: RuntimeAccountState;
enabledAccounts: ProxyPassthroughAccount[];
orderedAccounts: ProxyPassthroughAccount[];
tracer?: ProxyTracer;
requestStartTime: number;
fetchStartMs: number;
attemptNumber: number;
logAttempt: AnthropicAttemptLogger;
logProxyBody: ProxyBodyCaptureLogger;
logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: {
inputTokens?: number;
outputTokens?: number;
cacheCreationTokens?: number;
cacheReadTokens?: number;
}) => void;
lastError: unknown;
authFailureMessage: string | null;
sawTransientFailure: boolean;
invalidRequestFailure: {
status: number;
body: string;
contentType?: string;
} | null;
entitlementFailure: AnthropicEntitlementFailure | null;
allowConfiguredModelFallback?: boolean;
}): Promise<AnthropicNonOkResult>;
/**
* 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".
*/
declare function isAntiAbuseConstruction429(headers: Record<string, string>, body: string): boolean;
declare function fetchAnthropicAccountResponse(args: {
ctx: ServerContext;
url: string;
headers: Record<string, string>;
finalBodyStr: string;
preparedContext: AnthropicUpstreamBody["preparedContext"];
requestedModel?: string;
account: ProxyPassthroughAccount;
accountState: RuntimeAccountState;
enabledAccounts: ProxyPassthroughAccount[];
orderedAccounts: ProxyPassthroughAccount[];
tracer?: ProxyTracer;
logAttempt: AnthropicAttemptLogger;
logProxyBody: ProxyBodyCaptureLogger;
fetchStartMs: number;
attemptNumber: number;
currentLastError: unknown;
currentSawRateLimit: boolean;
currentSawNetworkError: boolean;
upstreamSpan?: import("@opentelemetry/api").Span;
}): Promise<AnthropicUpstreamFetchResult>;
declare function shouldAttemptClaudeFallback(loopState: AnthropicLoopState): boolean;
/**
* 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.
*/
declare function normalizeQuotaForAccounts(quota: unknown): JsonObject | null;
export declare function createClaudeProxyRoutes(modelRouter?: ModelRouterInterface, basePath?: string, accountStrategy?: "round-robin" | "fill-first", passthroughMode?: boolean, primaryAccountKey?: string, accountAllowlistOrRuntimeOptions?: AccountAllowlist | ClaudeProxyRouteRuntimeOptions): RouteGroup;
declare function reconcileEligibleAccountRuntimeState(account: ProxyPassthroughAccount): void;
export declare function getTransientSameAccountRetryDelayMs(retryNumber: number): number;
export declare function getOverloadRotationDelayMs(attemptNumber: number): number;
declare function describeTransportError(error: unknown): string;
/**
* 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.
*/
declare function isConnectPhaseNetworkError(error: unknown): boolean;
/**
* 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.
*/
declare function isRetryableNetworkError(error: unknown): boolean;
/**
* Parse a Claude error payload when available.
*/
export declare function parseClaudeErrorBody(errBody: string): ParsedClaudeError;
/**
* Detect malformed request errors that should not trigger account/provider failover.
*/
export declare function isInvalidRequestError(status: number, errBody: string): boolean;
/**
* 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.
*/
declare function isAnthropicModelNotFound(status: number, errBody: string): boolean;
/**
* 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 declare function isSubscriptionBetaRejection(status: number, errBody: string): boolean;
/**
* 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 declare function isAccountEntitlementError(status: number, errBody: string): boolean;
/**
* 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 declare function isDurableEntitlementBlock(status: number, errBody: string): boolean;
/**
* Backward-compatible alias — delegates to the shared translation engine.
*/
export declare const buildProxyFallbackOptions: typeof 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 declare function isTransientHttpFailure(status: number, errBody: string): boolean;
/**
* An upstream overload is already a capacity signal. Retrying it on the same
* OAuth account only consumes its remaining concurrency and delays rotation.
*/
export declare function isUpstreamOverload(status: number, errBody: string): boolean;
export declare function redactProviderErrorMessage(message: string): string;
export declare const __testHooks: {
clearClaudeFallbackTerminalEvidence: typeof clearClaudeFallbackTerminalEvidence;
normalizeQuotaForAccounts: typeof normalizeQuotaForAccounts;
resolveHomeIndex: typeof resolveHomeIndex;
maybeResetPrimaryToHome: typeof maybeResetPrimaryToHome;
planCooldownFor429: typeof planCooldownFor429;
reconcileCooldownFromQuota: typeof reconcileCooldownFromQuota;
refreshAccountLimits: typeof refreshAccountLimits;
applyAccountUsageResult: typeof applyAccountUsageResult;
clearLimitsRefreshStateForTests: () => void;
setAccountDirectoryForTests: (override: ProxyAccountDirectoryOverride | null) => void;
isRetryableNetworkError: typeof isRetryableNetworkError;
isConnectPhaseNetworkError: typeof isConnectPhaseNetworkError;
MAX_CONNECT_PHASE_SAME_ACCOUNT_RETRIES: number;
clearProviderTransportCoordinatorForTests: () => void;
isPermanentRefreshFailure: typeof isPermanentRefreshFailure;
getStreamFailureDetails: typeof getStreamFailureDetails;
trackUpstreamReadableStream: typeof trackUpstreamReadableStream;
orderAccountsByQuota: typeof orderAccountsByQuota;
orderAccountsByQuotaWithMetrics: typeof orderAccountsByQuotaWithMetrics;
scheduleAdaptiveQuotaRefreshes: typeof scheduleAdaptiveQuotaRefreshes;
scheduleHandoffQuotaRefresh: typeof scheduleHandoffQuotaRefresh;
getQuotaRefreshState: (key: string) => import("../../index.js").ProxyQuotaRefreshRuntimeState;
buildQuotaRoutingDecision: (accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number, options?: {
requestedModel?: string;
policy?: {
ranking?: ProxyAccountRankingPolicy;
affinityKey?: string;
preferPrimary?: boolean;
};
}) => ProxyAccountRoutingDecision | undefined;
buildRoutingDecision: typeof buildRoutingDecision;
resetEpochToMs: typeof resetEpochToMs;
seedRuntimeQuotasFromDisk: typeof seedRuntimeQuotasFromDisk;
reconcileEligibleAccountRuntimeState: typeof reconcileEligibleAccountRuntimeState;
getAccountRuntimeState: (key: string) => RuntimeAccountState | undefined;
setPrimaryAccountIndex: (index: number) => void;
getPrimaryAccountIndex: () => number;
setAccountRuntimeState: (key: string, state: Partial<RuntimeAccountState>) => void;
resetAllRuntimeState: () => void;
polyfillOAuthBody: (bodyStr: string, isClaudeClientRequest: boolean) => {
bodyStr: string;
sessionId?: string;
};
extractSnapshotBodySessionIdForTests: (body: unknown) => string | undefined;
selectClaudeProxyAccountOrderForTests: typeof selectClaudeProxyAccountOrder;
setForceSpillThrowForTests: (value: boolean) => void;
setForceAffinityLookupThrowForTests: (value: boolean) => void;
setForceBindRecheckThrowForTests: (value: boolean) => void;
isAntiAbuseConstruction429: typeof isAntiAbuseConstruction429;
fetchAnthropicAccountResponse: typeof fetchAnthropicAccountResponse;
finalizeAnthropicTerminalFetchError: typeof finalizeAnthropicTerminalFetchError;
handleAnthropicNonOkResponse: typeof handleAnthropicNonOkResponse;
handleAnthropicAuthRetry: typeof handleAnthropicAuthRetry;
handleAnthropicStreamingSuccessResponse: typeof handleAnthropicStreamingSuccessResponse;
claimTransientRateLimitRetry: typeof claimTransientRateLimitRetry;
claimTransientCooldownAdmission: typeof claimTransientCooldownAdmission;
waitForTransientAccountAvailability: typeof waitForTransientAccountAvailability;
acquireAccountAdmission: typeof acquireAccountAdmission;
acquireFirstAvailableAccountAdmission: typeof acquireFirstAvailableAccountAdmission;
tryAcquireAccountAdmission: typeof tryAcquireAccountAdmission;
enqueueAccountAdmission: typeof enqueueAccountAdmission;
getAccountInflight: typeof getAccountInflight;
getAccountAdmissionSnapshot: (accountKey: string) => {
active: number;
waiting: number;
};
hasAccountAdmissionState: (accountKey: string) => boolean;
describeTransportError: typeof describeTransportError;
redactProviderErrorMessage: typeof redactProviderErrorMessage;
isUpstreamOverload: typeof isUpstreamOverload;
getOverloadRotationDelayMs: typeof getOverloadRotationDelayMs;
redactHeadersForBorrower: typeof redactHeadersForBorrower;
shouldAttemptClaudeFallback: typeof shouldAttemptClaudeFallback;
isAnthropicModelNotFound: typeof isAnthropicModelNotFound;
getCodexFallbackInvalidRequestFailure: typeof getCodexFallbackInvalidRequestFailure;
executeClaudeFallbackWithRetry: typeof executeClaudeFallbackWithRetry;
buildClaudeAnthropicFailureResponse: typeof buildClaudeAnthropicFailureResponse;
isAccountEntitlementError: typeof isAccountEntitlementError;
isDurableEntitlementBlock: typeof isDurableEntitlementBlock;
evaluateScopedExhaustion: typeof evaluateScopedExhaustion;
matchScopedQuotaWindow: typeof matchScopedQuotaWindow;
setOveragePolicy: typeof setOveragePolicy;
};
export {};