/** * Credentials per tenant: each provider's key resolved when a call is made, for the tenant it runs * for, from a secret manager the application supplies, cached for a bounded time and rotated without * a restart. */ import type { CredentialRequest, CredentialResolver, OperationDescriptor, ProviderCredential } from '../types/lifecycle.js'; export type { CredentialRequest, CredentialResolver, ProviderCredential } from '../types/lifecycle.js'; /** Where secrets are read: a secret manager, the environment, or memory. */ export interface SecretSource { /** The secret with this name, or `undefined` when there is none. Throws when the source cannot be read. */ get(name: string, options?: { signal?: AbortSignal; }): Promise | string | undefined; } /** Raised when a call's credential cannot be resolved, so the call is refused rather than sent with another. */ export declare class CredentialError extends Error { /** The provider the call was for. */ readonly provider: string; /** The tenant it ran for. */ readonly tenantId?: string | undefined; constructor(message: string, /** The provider the call was for. */ provider: string, /** The tenant it ran for. */ tenantId?: string | undefined, options?: { cause?: unknown; }); } /** Options for `tenantCredentials()`. */ export interface TenantCredentialsOptions { /** Where the secrets are. */ secrets: SecretSource; /** * The secret's name for a request. Defaults to `/`, or `` for a call * with no tenant. */ name?: (request: CredentialRequest) => string; /** * Reads a secret into a credential. Defaults to a plain string as the key, or a JSON object with * `apiKey` and `headers`. */ parse?: (secret: string, request: CredentialRequest) => ProviderCredential; /** * What happens when a tenant has no secret: `refuse`, the default, fails the call with a * `CredentialError`; `config` sends the provider's configured key. A call with no tenant always * falls back to the configured key. */ missing?: 'refuse' | 'config'; /** How long a resolved credential is reused, in milliseconds. Defaults to 5 minutes. */ cacheMs?: number; /** How long a missing secret is remembered as missing, in milliseconds. Defaults to 30 seconds. */ missingCacheMs?: number; /** * When the source fails, keeps using a credential resolved before, for up to this long after it * was due again, in milliseconds. Defaults to 1 hour; 0 fails the call instead. */ staleMs?: number; /** Most credentials cached at once. Defaults to 1,000. */ maxEntries?: number; /** Hears each failure of the source, including those a stale credential covered. */ onError?: (error: unknown, request: CredentialRequest) => void; /** Replaces the system clock, for tests. */ now?: () => number; } /** A credential resolver over a secret source, with `stats()` and a way to forget what it cached. */ export interface TenantCredentials extends CredentialResolver { /** Forgets every cached credential, or those of one tenant, so the next calls read the source again. */ clear(tenantId?: string): void; /** What the cache did: reads of the source, hits, stale credentials served, and refusals. */ stats(): TenantCredentialStats; } /** Counts from `TenantCredentials.stats()`. */ export interface TenantCredentialStats { /** Times the source was read. */ reads: number; /** Calls answered from the cache. */ hits: number; /** Calls answered with a credential past its time, because the source failed. */ stale: number; /** Calls refused for want of a credential. */ refused: number; /** Credentials cached now. */ cached: number; } /** * Resolves each provider's credential per tenant from a secret source, at call time: a key in the * source is used from the next call that reads it, so a key rotated there takes over within * `cacheMs`, at once after `clear()`, and at once for a call whose provider refused the old one. * Concurrent calls for one credential share one read. * * ```ts * const ai = new NexusAI({ * providers: { openai: { apiKey: process.env.OPENAI_KEY } }, * lifecycle: { credentials: tenantCredentials({ secrets: awsSecrets(new SecretsManager({})) }) }, * }); * await ai.complete({ model: 'gpt-5', tenantId: 'acme', messages }); // acme's own key * ``` */ export declare function tenantCredentials(options: TenantCredentialsOptions): TenantCredentials; /** A secret as a credential: a plain string is the key; a JSON object carries `apiKey` and `headers`. */ export declare function parseSecret(secret: string): ProviderCredential; /** * Secrets in environment variables: `acme/openai` is read from `ACME_OPENAI`, with `prefix` * before it, such as `NEXUS_SECRET_`. */ export declare function envSecrets(options?: { prefix?: string; env?: Record; }): SecretSource; /** Secrets held in this process, which `set()` changes: for tests, and for keys an application rotates itself. */ export interface MemorySecrets extends SecretSource { /** Stores or replaces a secret. */ set(name: string, value: string): void; /** Removes a secret. */ delete(name: string): void; } /** Secrets held in this process. */ export declare function memorySecrets(initial?: Record): MemorySecrets; /** The part of AWS Secrets Manager's client a source needs, as `@aws-sdk/client-secrets-manager`'s `SecretsManager` has it. */ export interface AwsSecretsClient { /** Reads one secret's current value. */ getSecretValue(input: { SecretId: string; VersionStage?: string; }): Promise<{ SecretString?: string; }>; } /** Secrets in AWS Secrets Manager, each named ``. A secret that does not exist reads as none. */ export declare function awsSecrets(client: AwsSecretsClient, options?: { prefix?: string; }): SecretSource; /** The part of Google Secret Manager's client a source needs, as `@google-cloud/secret-manager`'s has it. */ export interface GcpSecretsClient { /** Reads one version of a secret. */ accessSecretVersion(request: { name: string; }): Promise<[{ payload?: { data?: Uint8Array | string | null; } | null; }, ...unknown[]]>; } /** * Secrets in Google Secret Manager under `projects//secrets//versions/`. Names * may hold only letters, digits, `-`, and `_`, so `/` becomes `-`. */ export declare function gcpSecrets(client: GcpSecretsClient, options: { project: string; version?: string; }): SecretSource; /** The part of Azure Key Vault's client a source needs, as `@azure/keyvault-secrets`'s `SecretClient` has it. */ export interface AzureSecretsClient { /** Reads one secret's current value. */ getSecret(name: string): Promise<{ value?: string; }>; } /** Secrets in Azure Key Vault. Names may hold only letters, digits, and `-`, so anything else becomes `-`. */ export declare function azureSecrets(client: AzureSecretsClient): SecretSource; /** Options for `vaultSecrets()`. */ export interface VaultSecretsOptions { /** The Vault server, such as `https://vault.internal:8200`. */ address: string; /** A token, or a function that returns the current one, for a token that is renewed. */ token: string | (() => string | Promise); /** The KV version 2 mount. Defaults to `secret`. */ mount?: string; /** The field of the secret's data that holds the value. Defaults to `value`; with none present, the data as JSON. */ field?: string; /** A Vault Enterprise namespace. */ namespace?: string; /** Replaces the global `fetch`. */ fetch?: typeof globalThis.fetch; } /** Secrets in HashiCorp Vault's KV version 2 engine, over its HTTP API. A secret that does not exist reads as none. */ export declare function vaultSecrets(options: VaultSecretsOptions): SecretSource; /** The parts of an operation a credential request names, for code that resolves one itself. */ export declare function credentialRequest(provider: string, operation: Pick): CredentialRequest;