import type { OAuthAccess } from "./auth-storage.js"; import { isAuthRetryableError } from "./error/auth-classify.js"; /** * Context passed to an {@link ApiKeyResolver} on each resolution attempt. * * The `error`/`lastChance` pair preserves the legacy a/b/c resolver contract * shared by streaming ({@link streamSimple}) and non-streaming ({@link withAuth}) * drivers: * - `error === undefined` → **initial resolve** (no force-refresh; cheap, may * return a locally-cached not-yet-expired token). * - `error !== undefined && !lastChance` → **step (b): refresh the SAME * account** (force a token re-mint / await an in-flight broker refresh). * - `error !== undefined && lastChance` → **step (c): switch account** * (invalidate/usage-limit the current credential and rotate to a sibling). * * Current drivers preserve that bounded a/b/c sequence for ordinary 401/auth * failures. Account-scoped policy denials, 403s, and usage-limit failures skip * refresh and may repeat step (c) until the resolver returns `undefined`, * cycles, or hits {@link AUTH_RETRY_MAX_ATTEMPTS}. */ export interface ApiKeyResolveContext { /** True when the resolver should rotate to a sibling credential. */ lastChance: boolean; /** The auth error that triggered this re-resolution, or `undefined` on the initial resolve. */ error: unknown; /** Bearer used by the failed attempt, when the caller can expose it. */ previousKey?: string; /** Caller cancel signal, threaded into any credential refresh / rotation work. */ signal?: AbortSignal; } /** * Resolves the API key to send for a request, retried through the a/b/c policy * described on {@link ApiKeyResolveContext}. */ export type ApiKeyResolver = (ctx: ApiKeyResolveContext) => Promise | string | undefined; /** A static bearer string, or a {@link ApiKeyResolver} that mints/rotates one. */ export type ApiKey = string | ApiKeyResolver; /** Narrows {@link ApiKey} to its resolver form. */ export declare function isApiKeyResolver(key: ApiKey | undefined): key is ApiKeyResolver; /** * Performs the initial resolve of an {@link ApiKey} (`error: undefined`, * `lastChance: false`). Static keys pass through unchanged. */ export declare function resolveApiKeyOnce(key: ApiKey | undefined, signal?: AbortSignal): Promise; /** * Wraps a resolver with a bearer that was already selected for this request. * * Callers that preflight credentials can pass the returned resolver to the * auth-retry driver without making the driver know about that preflight: the * first initial resolution reuses `seed`, and all later resolutions delegate to * `resolver`. */ export declare function seedApiKeyResolver(seed: string | undefined, resolver: ApiKeyResolver): ApiKeyResolver; export { isAuthRetryableError }; /** * Legacy bounded a/b/c retry sequence retained for public compatibility: * `false` → refresh-same, `true` → rotate/switch. Current drivers consume it * once for ordinary 401/auth failures; usage/account-limit failures may repeat * sibling rotation until a termination guard fires. */ export declare const AUTH_RETRY_STEPS: readonly boolean[]; export declare const AUTH_RETRY_MAX_ATTEMPTS = 64; /** Resolve a single retry step, swallowing resolver failures into `undefined`. */ export declare function resolveRetryKey(resolver: ApiKeyResolver, lastChance: boolean, error: unknown, signal?: AbortSignal, previousKey?: string): Promise; export interface AuthRetryKeyState { /** Bearer strings already sent during this logical operation. */ attemptedKeys: Set; /** Bearer used by the most recent failed attempt. */ lastKey: string; /** Whether the current credential already consumed its 401 refresh-same retry. */ refreshedCurrent: boolean; /** Whether the legacy non-usage auth path already switched to one sibling. */ legacyAuthSwitchUsed: boolean; /** Total outbound attempts accepted for this logical operation, including the initial request. */ attempts: number; } export declare function createAuthRetryKeyState(initialKey: string): AuthRetryKeyState; export declare function resolveNextAuthRetryKey(state: AuthRetryKeyState, resolver: ApiKeyResolver, error: unknown, signal?: AbortSignal): Promise; /** * Runs an auth-protected operation through the central a/b/c retry policy. * * - A static string key (or any non-resolver) → a single `attempt` with no * retry (identical to the legacy static-key path). * - A resolver → initial `attempt`, then resolver-driven retries until the * applicable policy is exhausted, the resolver declines or cycles, or the * operation reaches {@link AUTH_RETRY_MAX_ATTEMPTS}. Ordinary 401/auth * failures retain one refresh-same plus one sibling switch; 403/usage-limit * failures rotate directly through distinct siblings. * * Used by non-streaming consumers (image generation, web search, completion * helpers). The streaming driver in `stream.ts` implements the same policy with * its replay-safe buffering machinery. */ export declare function withAuth(key: ApiKey | undefined, attempt: (key: string) => Promise, opts?: { isAuthError?: (error: unknown) => boolean; signal?: AbortSignal; missingKeyMessage?: string; }): Promise; /** * Minimal structural slice of `AuthStorage` consumed by {@link withOAuthAccess}. * Typed structurally (and importing only the `OAuthAccess` type) so this module * never takes a runtime dependency on `./auth-storage`. */ export interface OAuthAccessSource { getOAuthAccess(provider: string, sessionId?: string, options?: { forceRefresh?: boolean; signal?: AbortSignal; }): Promise; rotateSessionCredential(provider: string, sessionId: string | undefined, options?: { error?: unknown; signal?: AbortSignal; apiKey?: string; credentialId?: number; }): Promise; } export interface WithOAuthAccessOptions { /** Session id for credential stickiness, threaded into every resolve. */ sessionId?: string; signal?: AbortSignal; /** Override the retryable-error classifier (default {@link isAuthRetryableError}). */ isAuthError?: (error: unknown) => boolean; /** * Pre-resolved access used for the initial attempt. Callers that already * resolved access for an availability gate pass it here so the helper * doesn't double-resolve (mirrors the gateway resolver's `initialKey`). */ seed?: OAuthAccess; missingAccessMessage?: string; } /** * {@link withAuth} for OAuth-access consumers: runs an auth-protected * operation through the central a/b/c retry policy, handing the attempt the * full {@link OAuthAccess} (bearer + identity metadata: `accountId`, * `projectId`, `enterpriseUrl`) instead of bare API-key bytes. * * - initial → `getOAuthAccess` (or `opts.seed`). * - 401/auth failure → one `getOAuthAccess` with `forceRefresh: true` for the * current account, then sibling rotation. * - 403/usage-limit failure → `rotateSessionCredential` directly, without a * force-refresh detour. * * A refresh-same step may retry a new bearer for the same credential identity; * sibling rotation stops when it yields a credential identity * (`credentialId ?? accessToken`) or bearer already attempted in this turn. * All OAuth attempts share the {@link AUTH_RETRY_MAX_ATTEMPTS} ceiling. * Non-auth errors propagate immediately. Use this instead of hand-rolled * `getOAuthAccess` + fetch flows so 401s and usage-limits rotate credentials * instead of failing the call. */ export declare function withOAuthAccess(storage: OAuthAccessSource, provider: string, attempt: (access: OAuthAccess) => Promise, opts?: WithOAuthAccessOptions): Promise;