/** * Client-side {@link AuthCredentialStore} that mirrors a remote broker's * snapshot. Refresh tokens never leave the broker; mutating methods (`replace*`, * `upsert*`, `delete*ForProvider`) throw because login flows are server-side. * * Cache (`getCache`/`setCache`/`cleanExpiredCache`) is in-memory and ephemeral — * usage reports cache TTL is 5 minutes per credential, so durability across * runs isn't required. */ import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type CachedCredentialHealth, type CachedUsagePresentation, type CredentialInventoryRecord, type MCPOAuthRefreshClient, type OAuthCredential, type SafeUsageReport, type StoredAuthCredential } from "../auth-storage"; import type { Provider } from "../types"; import type { UsageReport } from "../usage"; import type { OAuthCredentials } from "../utils/oauth/types"; import { type AuthBrokerClient } from "./client"; import type { SnapshotResponse } from "./types"; export type CredentialInventoryMetadataCapability = "pending" | "supported" | "unsupported" | "mismatch" | "failed"; export interface CachedInventoryNotice { status: Exclude; reason: string; generation?: number; } export interface CredentialInventoryMetadataState { capability: CredentialInventoryMetadataCapability; generation: number; records: readonly CredentialInventoryRecord[]; notice?: CachedInventoryNotice; } export interface RemoteAuthCredentialStoreOptions { client: AuthBrokerClient; /** * Initial snapshot. When omitted, callers must call * {@link RemoteAuthCredentialStore.refreshSnapshot} before the first read. */ initialSnapshot?: SnapshotResponse; /** * Subscribe to the broker's SSE snapshot stream when available. Falls back * to long-poll permanently when the broker returns 404. Default `true`. */ streamSnapshots?: boolean; /** Override the local redacted presentation sidecar path (primarily for tests). */ presentationPath?: string; } export declare class RemoteAuthCredentialStore implements AuthCredentialStore { #private; constructor(opts: RemoteAuthCredentialStoreOptions); get client(): AuthBrokerClient; /** Wait for redacted presentation hydration and initial inventory metadata. */ waitForReady(): Promise; /** Await pending atomic sidecar writes (useful to bounded shutdown callers). */ flushPresentationPersistence(): Promise; get snapshot(): SnapshotResponse; getInventoryMetadataState(): Readonly; syncInventoryMetadata(): Promise>; /** * Payload-free inventory view. This method never performs network I/O; metadata * rows appear only after an explicit or background metadata synchronization for * the current snapshot generation. */ listCredentialInventory(provider?: string): CredentialInventoryRecord[]; /** Re-hydrate the in-memory snapshot from the broker. */ refreshSnapshot(): Promise; listAuthCredentials(provider?: string): StoredAuthCredential[]; /** * In-memory update from a successful refresh through the broker. AuthStorage * calls this after `#replaceCredentialAt`; the broker already persisted the * authoritative row, so we just mirror it. */ updateAuthCredential(id: number, credential: AuthCredential): void; deleteAuthCredential(_id: number, _disabledCause: string): void; tryDisableAuthCredentialIfMatches(_id: number, _expectedData: string, _disabledCause: string): boolean; waitForFreshSnapshot(maxWaitMs: number, opts?: { signal?: AbortSignal; }): Promise; prepareForRequest(credentialId: number, opts?: { signal?: AbortSignal; }): Promise; markCredentialSuspect(credentialId: number, opts?: { signal?: AbortSignal; }): Promise; replaceAuthCredentialsForProvider(_provider: string, _credentials: AuthCredential[]): StoredAuthCredential[]; upsertAuthCredentialForProvider(_provider: string, _credential: AuthCredential): StoredAuthCredential[]; upsertAuthCredentialForProviderIfAbsent(_provider: string, _credential: AuthCredential): AuthCredentialIfAbsentResult; deleteAuthCredentialsForProvider(_provider: string, _disabledCause: string): void; /** Logout authority remains on the broker; the client only mirrors its result. */ deleteAuthCredentialsRemote(provider: string, disabledCause: string): Promise; /** * Upsert a single credential through the broker. The broker server is the * canonical writer — see `POST /v1/credential`. The redacted snapshot * entries returned by the server replace the provider's rows in our local * snapshot, and the global snapshot is then refreshed in the background so * any concurrent peer (refresh, generation bump) stays in sync. */ upsertAuthCredentialRemote(provider: string, credential: AuthCredential): Promise; upsertAuthCredentialRemoteIfAbsent(provider: string, credential: AuthCredential): Promise; /** * Replace-all semantics: disable every active credential for the provider, * then upload each of the new credentials. Used by API-key login so a new * key clobbers any previously stored key for the same provider. */ replaceAuthCredentialsRemote(provider: string, credentials: AuthCredential[]): Promise; getCache(key: string): string | null; setCache(key: string, value: string, expiresAtSec: number): void; cleanExpiredCache(): void; deleteCachePrefix(prefix: string): void; /** * Store-level hook consumed by `AuthStorage` — routes refresh through the * broker so the actual refresh token never leaves the broker host. Returns * the broker-redacted credential with {@link REMOTE_REFRESH_SENTINEL} in * the `refresh` slot. */ refreshOAuthCredential(_provider: Provider, credentialId: number, _credential: OAuthCredential, signal?: AbortSignal): Promise; refreshMCPOAuthCredential(credentialId: number, credential: OAuthCredential, client: MCPOAuthRefreshClient, signal?: AbortSignal): Promise; /** * Store-level hook consumed by `AuthStorage.fetchUsageReports()` — proxies * to the broker's `/v1/usage` endpoint. The broker's egress IP isn't * rate-limited by Anthropic's per-IP `/usage` cap the way a heavy * residential laptop is, so all credentials surface every cycle. */ fetchUsageReports(signal?: AbortSignal): Promise; /** Synchronous, zero-network usage presentation read. */ peekCachedUsagePresentation(provider: Provider, credentialId: number): CachedUsagePresentation | undefined; /** Synchronous, zero-network health presentation read backed by the redacted sidecar. */ peekCachedCredentialHealth(provider: Provider, credentialId: number): CachedCredentialHealth | undefined; /** Persist a safe health result without credential or bearer-token material. */ recordCredentialHealth(provider: Provider, credentialId: number, health: CachedCredentialHealth): void; /** Record a safe usage observation after an explicit broker usage/check call. */ recordUsagePresentation(observation: CachedUsagePresentation): void; /** Persist an explicit usage/check report for the current credential identity. */ recordCredentialUsage(provider: Provider, credentialId: number, report: SafeUsageReport): void; /** * Per-credential usage hook consumed by `AuthStorage.#getUsageReport`. Pulls * the aggregate broker `/v1/usage` once and serves all callers from the * same response (coalesced + cached), then matches the credential to a * report by provider + identity (accountId / email / projectId). * * The broker already aggregates with its own 30s TTL on the server side; our * 15s client TTL is below that so we usually re-use the broker's cache too. */ getUsageReport(provider: Provider, credential: OAuthCredential, signal?: AbortSignal): Promise; close(): void; }