import { type SecretStorage } from "./secretStorage.js"; import type { RefreshablePythiaKey } from "./types.js"; /** * The consumer's OWN signing injection point (e.g. a wrapper around Codex's * `autoSignApolloChallenge`, out of scope for this SDK). `PythiaConnector` * never holds key material and signs nothing itself — mirrors * `PythiaClient.send`'s existing "caller signs, we relay" philosophy. */ export interface ApolloSigner { sign(input: { apolloAccount: string; nonce: string; rp: string; }): Promise<{ signature: string; }>; } /** The typed result of `ensureSecret()`/`refresh()`. A 202 "pending" verify * outcome is a legitimate steady state, not an error — see * `docs/work/pythia-client-connector-sdk/design.md` Decision 3. */ export type ConnectorSecretResult = { status: "active"; secret: string; expiresAt: number; } | { status: "pending"; }; /** Constructor options for {@link PythiaConnector}. `storage` defaults to a * fresh `InMemorySecretStorage`; `refreshMarginMs` defaults to 5 minutes. */ export interface PythiaConnectorOptions { baseUrl: string; apolloAccount: string; signer: ApolloSigner; storage?: SecretStorage; fetchImpl?: typeof fetch; refreshMarginMs?: number; /** Called with any error `keyProvider()`'s closure swallows (a thrown * `PythiaConnectorError`, OR anything thrown by your OWN injected `signer`/ * `storage` implementation — this catch is not scoped to this SDK's own * error types). Defaults to `console.error`. Set this if you need to * redact, filter, or route these errors through your own logging/APM * instead of an unconditional global `console.error` call — e.g. a buggy * signer that embeds sensitive material in a thrown `Error` message would * otherwise reach whatever's capturing `console.error` in production. */ onKeyProviderError?: (error: unknown) => void; } /** * Orchestrates Pythia's headless connector-auth protocol: challenge → sign → * verify → store. Exposes a single pull-based primitive, `ensureSecret()`, * that returns a cached still-valid secret or transparently refreshes — no * built-in timer/loop (see design Decision 1); a consumer drives refresh * cadence from whatever scheduling mechanism it already has. */ export declare class PythiaConnector { private readonly transport; private readonly apolloAccount; private readonly signer; private readonly storage; private readonly refreshMarginMs; private readonly onKeyProviderError; /** The in-flight `refresh()` call, if any — memoized so concurrent * `ensureSecret()` callers (e.g. two simultaneous `PythiaClient` requests * both consulting `keyProvider()`) await the SAME round trip instead of * each independently firing its own challenge+verify (and its own signer * invocation, which may be expensive/rate-limited/user-facing). Cleared in * a `finally` regardless of outcome so a failed refresh doesn't wedge * every subsequent call behind a rejected promise forever. */ private refreshing; constructor(options: PythiaConnectorOptions); /** * Returns a cached still-valid secret without any network call when one * exists and isn't within `refreshMarginMs` of expiring; otherwise * transparently performs a full `refresh()` (deduped against any already * in-flight `refresh()` — see `refreshing`). */ ensureSecret(): Promise; /** * Drop the cached secret so the next `ensureSecret()` performs a full * `refresh()` (re-mint). Used by `PythiaClient`'s 401 self-heal when the * gateway rejects an orphaned key. Idempotent; safe to call concurrently * (the subsequent `refresh()` is in-flight-deduped, so a burst of 401s * collapses to ONE re-mint). */ invalidate(): Promise; /** * A {@link RefreshablePythiaKey} for `PythiaClient`'s `pythiaKey` option — * `get()` returns the live secret (minting/refreshing as needed), `invalidate()` * drops it. Wire `new PythiaClient({ pythiaKey: connector.asKeySource() })` to * get automatic 401 self-heal. */ asKeySource(): RefreshablePythiaKey; /** * Unconditionally drives the full challenge → sign → verify round trip * against the injected `Transport`/`ApolloSigner`, mapping the verify * response to a typed result or a typed thrown error per the locked wire * contract. Concurrent calls share one in-flight round trip — see * `refreshing`. */ refresh(): Promise; private doRefresh; /** * Returns a closure suitable for `PythiaClientOptions.pythiaKey`: calls * `ensureSecret()` and resolves to the live secret when active, else * `undefined`. ANY error `ensureSecret()`/`refresh()` throws is caught here * — not just this SDK's own `PythiaConnectorError`s, but also whatever your * injected `signer`/`storage` implementations throw — and reported via * `onKeyProviderError` (defaults to `console.error`) rather than * propagated: this closure must never break an unrelated read/send/poll * call by rejecting. See `PythiaConnectorOptions.onKeyProviderError` if you * need to redact/filter/route these errors instead of the default. */ keyProvider(): () => Promise; }