/** * 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 { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js"; import type { ModelRouter } from "../../proxy/modelRouter.js"; import { ProxyTracer } from "../../proxy/proxyTracer.js"; import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js"; import type { AccountAllowlist, AccountCooldownPlan, AccountQuota, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicLoopState, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ParsedClaudeError, ProxyBodyCaptureLogger, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js"; /** 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[]): 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[]): 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; /** 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" → rotate * immediately (no same-account retries) and park the account until the ACTUAL * reset — never the legacy 60s hardcap that let us re-hammer a spent account. * * 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): AccountCooldownPlan; /** * Seed each account's runtime quota from the persisted snapshots in * ~/.neurolink/account-quotas.json (keyed by label). 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; stale disk snapshots degrade * gracefully because past reset timestamps are ignored by resetEpochToMs. */ declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise; /** * Order accounts to MAXIMIZE quota utilization (fill-first, smart order): * spend the window that expires SOONEST first, so its about-to-reset * allowance isn't wasted, then move to accounts with longer-dated resets. * * Priority among usable accounts: * 1. no quota data yet — probe first: one request reveals its windows and * self-corrects the ordering. (Ranking unknowns last would starve them * forever: never picked → never observed → never comparable.) * 2. session headroom before session-saturated (>= soft limit or * "throttled") — saturated accounts then follow the same bucketed * session ordering below: soonest back in service first, weekly * deciding same-bucket ties. * 3. soonest SESSION (5h) reset — the capacity expiring soonest; compared * in tolerance buckets so near-simultaneous resets count as equal. * 4. soonest WEEKLY (7d) reset — decides between same-bucket sessions. * 5. highest weekly utilization — finish off the one closest to done * 6. configured primary account, then insertion order * An account with NO ticking session window (fresh, not yet started) has * nothing expiring and sorts after ticking windows in step 3. * Cooling/rejected accounts sort last, soonest-back-to-service first, as * last resort. */ declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number): ProxyPassthroughAccount[]; declare function trackUpstreamReadableStream(source: ReadableStream): { stream: ReadableStream; outcome: Promise; }; 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[0]; providerLabel: string; }): Promise; declare function executeClaudeFallbackWithRetry(args: Parameters[0]): Promise; declare function buildClaudeAnthropicFailureResponse(args: { tracer?: ProxyTracer; requestStartTime: number; authFailureMessage: string | null; authCooldownMessage: string | null; invalidRequestFailure: { status: number; body: string; contentType?: string; } | null; sawNetworkError: boolean; sawTransientFailure: boolean; sawRateLimit: boolean; lastError: unknown; 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; tracer?: ProxyTracer; requestStartTime: number; fetchStartMs: number; attemptNumber: number; finalBodyStr: string; upstreamSpan?: import("@opentelemetry/api").Span; logProxyBody: ProxyBodyCaptureLogger; logFinalRequest: (status: number, accountLabel: string, accountType: string, errorType?: string, errorMessage?: string, extra?: { inputTokens?: number; outputTokens?: number; cacheCreationTokens?: number; cacheReadTokens?: number; }) => void; }): Promise; 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; buildUpstreamBody: (token: string) => { bodyStr: string; sessionId?: string; }; enabledAccounts: ProxyPassthroughAccount[]; orderedAccounts: ProxyPassthroughAccount[]; response: Response; tracer?: ProxyTracer; requestStartTime: number; fetchStartMs: number; attemptNumber: number; finalBodyStr: string; upstreamSpan?: import("@opentelemetry/api").Span; logAttempt: (status: number, errorType?: string, errorMessage?: string, extra?: { inputTokens?: number; outputTokens?: number; cacheCreationTokens?: number; cacheReadTokens?: number; }) => void; 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; sawRateLimit: boolean; sawTransientFailure: boolean; sawNetworkError: boolean; }): Promise; declare function finalizeAnthropicTerminalFetchError(args: { terminalError: NonNullable; account: ProxyPassthroughAccount; tracer?: ProxyTracer; requestStartTime: number; attemptNumber: number; logProxyBody: ProxyBodyCaptureLogger; logFinalRequest: ClaudeFinalRequestLogger; }): Response | unknown; /** * 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, body: string): boolean; declare function fetchAnthropicAccountResponse(args: { url: string; headers: Record; finalBodyStr: 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; declare function shouldAttemptClaudeFallback(loopState: AnthropicLoopState): boolean; /** * 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. */ export declare function createClaudeProxyRoutes(modelRouter?: ModelRouter, basePath?: string, accountStrategy?: "round-robin" | "fill-first", passthroughMode?: boolean, primaryAccountKey?: string, accountAllowlist?: AccountAllowlist): RouteGroup; export declare function getTransientSameAccountRetryDelayMs(retryNumber: number): number; declare function describeTransportError(error: unknown): string; /** * 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; /** * 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; export declare const __testHooks: { resolveHomeIndex: typeof resolveHomeIndex; maybeResetPrimaryToHome: typeof maybeResetPrimaryToHome; planCooldownFor429: typeof planCooldownFor429; isPermanentRefreshFailure: typeof isPermanentRefreshFailure; getStreamFailureDetails: typeof getStreamFailureDetails; trackUpstreamReadableStream: typeof trackUpstreamReadableStream; orderAccountsByQuota: typeof orderAccountsByQuota; resetEpochToMs: typeof resetEpochToMs; seedRuntimeQuotasFromDisk: typeof seedRuntimeQuotasFromDisk; getAccountRuntimeState: (key: string) => RuntimeAccountState | undefined; setConfiguredPrimaryAccountKey: (key: string | undefined) => void; getConfiguredPrimaryAccountKey: () => string | undefined; setConfiguredAccountAllowlist: (allowlist: AccountAllowlist | undefined) => void; getConfiguredAccountAllowlist: () => string[] | undefined; setPrimaryAccountIndex: (index: number) => void; getPrimaryAccountIndex: () => number; setAccountRuntimeState: (key: string, state: Partial) => void; resetAllRuntimeState: () => void; polyfillOAuthBody: (bodyStr: string, isClaudeClientRequest: boolean) => { bodyStr: string; sessionId?: string; }; isAntiAbuseConstruction429: typeof isAntiAbuseConstruction429; fetchAnthropicAccountResponse: typeof fetchAnthropicAccountResponse; finalizeAnthropicTerminalFetchError: typeof finalizeAnthropicTerminalFetchError; handleAnthropicAuthRetry: typeof handleAnthropicAuthRetry; handleAnthropicStreamingSuccessResponse: typeof handleAnthropicStreamingSuccessResponse; claimTransientRateLimitRetry: typeof claimTransientRateLimitRetry; claimTransientCooldownAdmission: typeof claimTransientCooldownAdmission; waitForTransientAccountAvailability: typeof waitForTransientAccountAvailability; describeTransportError: typeof describeTransportError; shouldAttemptClaudeFallback: typeof shouldAttemptClaudeFallback; executeClaudeFallbackWithRetry: typeof executeClaudeFallbackWithRetry; buildClaudeAnthropicFailureResponse: typeof buildClaudeAnthropicFailureResponse; }; export {};