import type { BlockStoreHealth } from "./blocks.js"; import type { AccountPolicies } from "./policy.js"; import type { AuthCredentialStore } from "./store.js"; import type { AuthCredential, AuthCredentialEntry, AuthCredentialSnapshot, AuthCredentialSnapshotEntry, AuthStorageData, CredentialDisabledEvent, CredentialsApi, DisabledCredentialSummary, OAuthCredential, StoredAuthCredential } from "./types.js"; import type { UsageCredential } from "../usage.js"; /** One stored credential row as cached in memory. */ export type StoredCredential = { id: number; credential: AuthCredential; }; /** {@link CredentialDisabledEvent} for a torn-down row, carrying the account identity it was signed in as. */ export declare function credentialDisabledEvent(provider: string, row: StoredCredential, disabledCause: string): CredentialDisabledEvent; /** Credential equality used for snapshot change detection. */ export declare function authCredentialEquals(left: AuthCredential, right: AuthCredential): boolean; /** Dependencies for credential validation, corruption handling, and assignment reset. */ export interface CredentialPoolOptions { policies: AccountPolicies; blockHealth: BlockStoreHealth; /** Called whenever a provider's credential set changed locally. */ onReset: (provider: string) => void; } /** In-memory credential snapshot over an AuthCredentialStore: CRUD, change detection, events. */ export declare class CredentialPool implements CredentialsApi { #private; constructor(store: AuthCredentialStore, options: CredentialPoolOptions); get closed(): boolean; get generation(): number; providers(): IterableIterator; /** Reset session affinity and round-robin state after a local credential change. */ reset(provider: string): void; /** Re-list one provider and adopt its persisted rows in memory. */ reloadProvider(provider: string): StoredAuthCredential[]; /** Persist a login-entered API key. */ storeLoginApiKey(provider: string, key: string): Promise; /** * Close the underlying credential store. * * After calling this, the instance must not be reused. */ close(): void; /** * Reload state after another process commits to the backing store, then * notify snapshot consumers even when only credential blocks changed. */ poll(): Promise; /** * Adopt credentials another process committed before selecting or rotating. * * The store is shared across every omp process, but the pool is an * in-process cache refreshed only by this process's own writes. Without * this a long-running session ranks a stale pool for its whole lifetime: * `omp auth` in another terminal is invisible, rotation reports no usable * sibling while a freshly added account sits unblocked in SQLite, and the * turn degrades to the fallback chain. The auth-broker path already polls; * direct-store sessions had no equivalent. * * A poll is two cheap reads (`PRAGMA data_version` plus the auth revision) * and re-lists credentials only when another connection committed, so it * runs on every resolution rather than on a timer that would make recovery * depend on wall-clock spacing. It sits on the paths that read the pool — * OAuth selection, and the two public usage-limit entry points — and is * idempotent, so a rotation reached through `markUsageLimitReached` costs * one extra `data_version` read and no second reload. */ adoptExternalChanges(): Promise; /** * Take over the subscribers, buffered disable events, and generation counter of * the pool this one replaces (store swap). Listener sets are shared, so * unsubscribe functions handed out by `previous` keep working. */ adoptSubscribers(previous: CredentialPool): void; onGeneration(listener: (generation: number) => void): () => void; bump(reason: string): void; /** * Subscribe to {@link CredentialDisabledEvent}s. Multiple subscribers are supported and * each fires for every disable event; subscribers are invoked in registration order with * exceptions and async rejections isolated per-listener so a misbehaving subscriber * cannot break the disable path or starve the rest of the chain. * * If `credential_disabled` events were emitted while no listener was subscribed, they are * replayed (in insertion order) to the listener that triggers the empty→non-empty * transition. The drain is one-shot — listeners that subscribe after that no longer see * past events. * * Returns an unsubscribe function. The function is idempotent: calling it more than once * is a no-op. After every subscriber has unsubscribed, subsequent disable events buffer * again until the next subscribe. * * @param listener Callback invoked with each disable event. May be sync or async. * @returns A function that removes this listener from the subscriber set. */ onDisabled(listener: (event: CredentialDisabledEvent) => void | Promise): () => void; /** * Reload credentials from storage. */ reload(): Promise; /** * Gets cached credentials for a provider. * @param provider - Provider name (e.g., "anthropic", "openai") * @returns Array of stored credentials, empty if none exist */ entries(provider: string): StoredCredential[]; /** * Updates in-memory credential cache for a provider. * Removes the provider entry entirely if credentials array is empty. * @param provider - Provider name (e.g., "anthropic", "openai") * @param credentials - Array of stored credentials to cache */ replace(provider: string, credentials: StoredCredential[]): void; noteBearer(provider: string, bearer: string, credentialId: number | undefined): void; idForBearer(provider: string, bearer: string): number | undefined; dedupe(provider: string, credentials: AuthCredential[]): AuthCredential[]; pruneDuplicates(provider: string, entries: StoredCredential[]): Promise; /** Returns all credentials for a provider as an array. */ credentials(provider: string): AuthCredential[]; /** * Persist a refreshed credential by id only while the row still matches this * process's snapshot. A peer rotation wins the CAS and is reloaded instead of * being overwritten after this process releases its refresh lease. An * unchanged credential still runs the CAS check (stores skip rewriting * identical bytes), so peer rotations are detected without churning the row. * * Returns the row's current index, or -1 when it was disabled or removed. */ replaceById(provider: string, id: number, credential: AuthCredential): number; /** * CAS-disable the row with `id`, but only if its persisted credential still * matches `expected` — i.e. no peer/login rotated it while we refreshed. * Addresses the row by id (re-resolved here, then matched on `data` in the * store) so a concurrent reorder can't tear down the wrong credential. */ disableIfMatches(provider: string, id: number, expected: AuthCredential, disabledCause: string): boolean; emitDisabled(event: CredentialDisabledEvent): void; /** * Get credential for a provider (first entry if multiple). */ get(provider: string): AuthCredential | undefined; /** * Set credential for a provider. */ set(provider: string, credential: AuthCredentialEntry): Promise; /** * List stored credential rows, optionally filtered by provider. */ list(provider?: string): StoredAuthCredential[]; upsertOAuth(provider: string, credential: OAuthCredential): Promise; /** * Remove credential for a provider. */ remove(provider: string): Promise; /** * Remove one stored credential for a provider. */ removeById(provider: string, credentialId: number): Promise; /** * Check if credentials exist for a provider in storage. */ has(provider: string): boolean; /** * Check if OAuth credentials are configured for a provider. */ hasOAuth(provider: string): boolean; /** * Get OAuth credentials for a provider. */ getOAuth(provider: string): OAuthCredential | undefined; /** * Get all credentials. */ all(): AuthStorageData; /** * Build a redacted snapshot of all loaded credentials for the auth-broker * wire. OAuth refresh tokens are replaced with {@link REMOTE_REFRESH_SENTINEL} * so clients never see the actual refresh token. * * Callers must {@link CredentialPool.reload} first when serving a stale snapshot * (the broker server's HTTP handler does this). */ snapshot(): AuthCredentialSnapshot; /** * Disabled credential tombstones for display surfaces (`omp usage`, * broker `GET /v1/credentials/disabled`). Empty when the backing store * keeps no tombstones or the remote broker predates the endpoint. */ listDisabled(provider?: string, signal?: AbortSignal): Promise; /** * Force the backing store to revalidate its credential snapshot, then * reload. Remote broker stores re-fetch the snapshot; local stores are * always current, so only the reload runs. Callers that pair live * per-credential data with stored identities (`omp usage`) use this so a * disk-cached snapshot cannot misattribute fresh reports. */ revalidate(): Promise; /** * Disable the credential with the given id and emit a * {@link CredentialDisabledEvent}. Used by the auth-broker server to honour * `POST /v1/credential/:id/disable`. Returns `false` when no such row exists. */ disable(id: number, disabledCause: string): Promise; /** * Upsert a credential into the underlying store, refresh the in-memory * snapshot, and return the redacted snapshot entries for the provider. * * Used by the auth-broker server to honour `POST /v1/credential`. The * persistence layer (`SqliteAuthCredentialStore.upsertAuthCredential`) * does identity-key matching, so re-uploading the same email/account replaces * the existing row instead of inserting a duplicate. */ upsert(provider: string, credential: AuthCredential): Promise; /** * Find the stored credential id matching a {@link UsageCredential} so the * refresh override can address the row. Mirrors the matching logic in * `UsageService.persistRefreshedCredential`. */ findIdForUsageCredential(provider: string, previous: UsageCredential): number | undefined; }