/** * Agent identity resolution. * * The "agent" is the AI process making calls on behalf of the human * user. Unlike the user, the agent never authenticates interactively. * The normal path resolves a noninteractive runtime credential: an injected * request authenticator, `ORY_AGENT_API_KEY`, or a Talos key enrolled with the * already-authenticated user. Enrolled keys remain process-local. * * Legacy DCR and client-credentials helpers remain exported temporarily so the * monorepo and existing consumers compile, but the normal gates do not select * them. */ import { type OryAgentDynamicCredentials, type OryDelegationRecord } from "./config.js"; import { OryAgentClient } from "./client.js"; import { enrollTalosChildRuntimeCredential, enrollTalosRuntimeCredential, type RuntimeCredential, type RuntimeRequestAuthenticator, type TalosCredentialStore } from "./runtime-credential.js"; /** * Delegation context handed to {@link registerAgentClient} so the freshly * minted OAuth2 client is stamped with its delegator at the identity layer. * `delegatedAt` is stamped by the registrar, not the caller. */ export interface DelegationInput { /** The delegator, namespaced: `user:` or `agent:`. */ delegatedBy: string; delegationType: OryDelegationRecord["delegationType"]; subAgentType?: string; } export type AgentCredentialKind = "talos" | "injected" | "client_credentials" | "dynamic" | "none"; export interface AgentCredentials { kind: AgentCredentialKind; /** Bearer token to attach to outgoing Ory API calls. */ token?: string; /** Request-level credential used by Agent Security broker transports. */ runtimeCredential?: RuntimeCredential; /** Subject (sub) the token represents — usually the client_id. */ subject?: string; /** Unix epoch seconds at which the token expires (client_credentials only). */ expiresAt?: number; /** * One-line reason for the resolved kind, suitable for telemetry. * Distinguishes e.g. "no env credentials configured" vs "client_credentials * grant failed: HTTP 401". */ reason: string; /** * Diagnostic warnings raised during resolution (e.g. a client_credentials * grant that had to be retried). Never blocks resolution. */ warnings: string[]; /** * Set on a `kind: "none"` result when the token endpoint rejected the * credentials for an auth reason (401/403) — i.e. the client is genuinely * dead (revoked/invalid). A transient failure (network, rate-limit, 5xx) * leaves this false so callers don't discard otherwise-good persisted * credentials. See the persisted-DCR branch in {@link resolveAgentCredentials}. */ authRejected?: boolean; } export interface ResolveAgentCredentialsOptions { /** Ory project URL — required for client_credentials grant + DCR. */ projectUrl?: string; /** Env override (defaults to process.env). */ env?: NodeJS.ProcessEnv; /** User's bearer token; preferred IAT for dynamic registration. */ userToken?: string; /** * User's subject. When present, a fresh DCR stamps `metadata.delegatedBy` * = `user:` on the agent client so the user→agent delegation * has an identity-layer anchor. Absent ⇒ the client registers without a * delegation anchor (no delegator known). */ userSubject?: string; /** Harness identifier — baked into the registered client_name. */ harness?: string; /** * The harness session this resolution belongs to. Each session gets its own * OAuth2 identity, so this is part of the credential key and of the * registered `client_name`. Omitted ⇒ the caller has no session concept and * gets the single {@link SESSIONLESS_KEY} identity. */ sessionId?: string; /** Abort network work owned by this resolution attempt. */ signal?: AbortSignal; /** Skip fresh DCR when a caller may only recover an existing identity. */ allowRegistration?: boolean; /** Inject the client_credentials token-grant function for tests. */ fetchClientCredentialsTokenFn?: typeof fetchClientCredentialsToken; /** Inject the DCR registration call for tests. */ registerAgentClientFn?: typeof registerAgentClient; /** Inject persisted-credential read/write/clear for tests. */ loadDynamicFn?: typeof loadAgentDynamicCredentials; saveDynamicFn?: typeof saveAgentDynamicCredentials; clearDynamicFn?: typeof clearAgentDynamicCredentials; /** Inject the RFC 7592 revoke used to clean up a lost registration race. */ revokeFn?: typeof revokeAgentDynamicClient; /** Bypass the in-memory token cache (tests). */ skipCache?: boolean; } export interface ResolveRuntimeAgentCredentialsOptions { env?: NodeJS.ProcessEnv; subject?: string; projectUrl?: string; runtimeCredential?: RuntimeCredential; requestAuthenticator?: RuntimeRequestAuthenticator; credentialStore?: TalosCredentialStore; fetchImpl?: typeof fetch; signal?: AbortSignal; harness?: string; sessionId?: string; } /** * Resolve the runtime credential used by the normal Agent Security path. * This intentionally does not inspect OAuth client credentials or persisted DCR. */ export declare function resolveRuntimeAgentCredentials(options?: ResolveRuntimeAgentCredentialsOptions): Promise; /** * Skew window before nominal expiry at which a cached client_credentials * token is considered stale. Matches the user-token skew in auth-store. */ export declare const AGENT_TOKEN_EXPIRY_SKEW_SEC = 60; /** Drop all cached agent client_credentials tokens. Tests only. */ export declare function _resetAgentCredentialsCache(): void; /** Evict a bearer rejected by Agent Security without disturbing other sessions. */ export declare function invalidateAgentAccessToken(rejectedToken: string): void; /** Load persisted sub-agent DCR credentials for one harness + type. */ export declare function loadSubAgentDynamicCredentials(harness: string, sessionOrType: string, subAgentType?: string): OryAgentDynamicCredentials | undefined; /** * Load one harness's persisted sub-agent registrations, keyed by sub-agent type. * Returns an empty object when none have been registered. Used by the status * report to enumerate the sub-agent kinds this harness has resolved. */ export declare function loadAllSubAgentDynamicCredentials(harness: string, sessionKey?: string): Record; /** * Every persisted sub-agent registration, keyed by harness, session, then type. Used by * teardown and reporting paths that must reach beyond the harness they run under. */ export declare function loadSubAgentDynamicCredentialsByHarness(): Record>>; /** Persist sub-agent DCR credentials under the given harness + session + type. */ export declare function saveSubAgentDynamicCredentials(harness: string, subAgentType: string, creds: OryAgentDynamicCredentials): void; export declare function saveSubAgentDynamicCredentials(harness: string, sessionKey: string, subAgentType: string, creds: OryAgentDynamicCredentials): void; /** Remove persisted sub-agent DCR credentials for one harness + type. */ export declare function clearSubAgentDynamicCredentials(harness: string, sessionOrType: string, subAgentType?: string): void; /** Remove every persisted sub-agent registration for one harness. */ export declare function clearSubAgentDynamicCredentialsForHarness(harness: string): void; /** Load persisted DCR credentials for one harness session. */ export declare function loadAgentDynamicCredentials(harness: string, sessionKey?: string): OryAgentDynamicCredentials | undefined; /** * Every persisted agent registration, keyed by harness then session. * reporting paths that must reach beyond the harness they run under. */ export declare function loadAgentDynamicCredentialsByHarness(): Record>; /** * Persist DCR credentials for one harness install. Other harnesses are left * untouched. */ export declare function saveAgentDynamicCredentials(harness: string, creds: OryAgentDynamicCredentials): void; export declare function saveAgentDynamicCredentials(harness: string, sessionKey: string, creds: OryAgentDynamicCredentials): void; /** Remove one session, or every session when no session key is supplied. */ export declare function clearAgentDynamicCredentials(harness: string, sessionKey?: string): void; /** Most recently registered agent session for status/reporting. */ export declare function latestAgentRegistration(harness: string): { sessionKey: string; credentials: OryAgentDynamicCredentials; } | undefined; /** Newest session containing at least one sub-agent registration. */ export declare function latestSubAgentSession(harness: string): string | undefined; export declare function countAgentSessions(harness: string): number; /** * The delegation node the broker assigned to this harness's `user → agent` edge * for one session, or `undefined` if that session hasn't recorded one yet. * * Read by the sub-agent edge to pass as `delegated_by` — which is why it is * looked up by session and not just by harness: the credential is shared across * sessions but the node is not, so a sub-agent must hang off *its own* run's * agent node. */ export declare function loadDelegationNodeId(harness: string, sessionKey: string): string | undefined; /** * Record the broker-assigned delegation node for `(harness, session)`, trimming * the map to the most recent {@link DELEGATION_NODE_HISTORY} entries. * * The anchor is not a credential and is safe to persist independently. A * repeated idempotent edge does not rewrite the config file. */ export declare function saveDelegationNodeId(harness: string, sessionKey: string, nodeId: string): void; /** Registrations queued for best-effort revocation. */ export declare function loadRetiredCredentials(): OryAgentDynamicCredentials[]; /** * Remove `clientIds` from the drain queue. Called with the ones that were * actually revoked; anything left stays queued for the next run, so a transient * failure retries instead of orphaning the client. */ export declare function clearRetiredCredentials(clientIds: readonly string[]): void; /** Automatic retirement is opt-in because age alone cannot prove a session ended. */ export declare const DEFAULT_SESSION_RETENTION = 0; /** `0` disables automatic retirement. */ export declare function sessionRetention(env?: NodeJS.ProcessEnv): number; /** * Move aged-out agent sessions and all of their child credentials to the * retirement queue atomically. They remain addressable until RFC 7592 cleanup * succeeds, avoiding the orphaning behavior of the original pruning path. */ export declare function retireAgentSessions(harness: string, opts: { keep: number; protect?: string; }): number; /** * The credentials a cold-starting session ends up with after registering, and * whether a concurrent peer beat it to the slot. */ export interface AgentRegistrationClaim { /** The credentials now persisted for this key — ours, or the peer's. */ credentials: OryAgentDynamicCredentials; /** * True when a peer registration was already persisted for this key, so the * freshly minted client was *not* stored. The caller should revoke it * (RFC 7592) rather than leave an orphan on the project. */ peer: boolean; } /** * Persist a freshly registered agent client, unless a concurrent peer already * claimed the `(projectUrl, harness)` slot. * * Keying credentials by harness removes the cold-start collision *between* * harnesses, but two instances of the *same* harness starting at once still * target one key. Both POST `/oauth2/register`, and a plain last-writer-wins * save would leave one client orphaned server-side (and its delegation edge * dangling). The read and the write happen inside a single `mutateConfig`, so * the loser sees the winner's registration and reports it back to be revoked. */ export declare function claimAgentDynamicCredentials(harness: string, sessionKey: string, registered: OryAgentDynamicCredentials): AgentRegistrationClaim; /** {@link claimAgentDynamicCredentials} for a `(harness, subAgentType)` slot. */ export declare function claimSubAgentDynamicCredentials(harness: string, sessionKey: string, subAgentType: string, registered: OryAgentDynamicCredentials): AgentRegistrationClaim; /** Outcome of a best-effort RFC 7592 client revocation. */ export interface RevokeAgentClientResult { /** * `deleted` — the server-side client was removed (or was already gone); * `skipped` — no management credentials were persisted, so we couldn't * try; `failed` — the DELETE was attempted but the server rejected it. */ status: "deleted" | "skipped" | "failed"; /** HTTP status when the server responded. */ httpStatus?: number; /** Human-readable detail for warnings (present on `skipped` / `failed`). */ message?: string; } /** * Best-effort RFC 7592 `DELETE` of a dynamically-registered OAuth2 client. * * Requires the registration management URI and access token to have been * persisted at registration time; otherwise returns `{ status: "skipped" }`. * Never throws — a missing client (404) counts as `deleted`, and any other * network/HTTP failure is captured in the result so callers can warn and * still clear local state. */ export declare function revokeAgentDynamicClient(creds: OryAgentDynamicCredentials): Promise; export interface RegisterAgentClientArgs { projectUrl: string; /** Bearer used as the initial access token (RFC 7591 §3). */ auth: string; /** Harness identifier baked into the client_name for audit. */ harness?: string; /** When set, names the client as a sub-agent of the given type. */ subAgentType?: string; /** Session owning this DCR client; included in its display name. */ sessionId?: string; /** Abort the registration request when its owning operation expires. */ signal?: AbortSignal; /** * Delegation edge recorded for this client. When present, the issued * credentials carry a durable record of who delegated to the agent — the * integrity anchor from which the Keto delegation tuple can be rebuilt. * Persisted in local config only; Ory's public DCR endpoint rejects a * `metadata` field, so it is not stored server-side. Omitted when the * delegator isn't known at registration time. */ delegation?: DelegationInput; /** Override hostname for testing. */ hostnameFn?: () => string; /** Inject the wall clock for deterministic `delegatedAt` in tests. */ nowFn?: () => Date; } /** * Register a new OAuth2 client via RFC 7591. Authorization is carried * either by the user's interactive token (the natural bootstrap) or by * a pre-issued initial access token (`ORY_AGENT_REGISTRATION_TOKEN`). * Throws on any non-2xx response so callers can decide whether to * retry, fall back, or surface the error in audit activity. The * `HTTP ` error message shape is preserved so callers and tests * can pattern-match on it regardless of the underlying transport. */ export declare function registerAgentClient(args: RegisterAgentClientArgs): Promise; /** * Legacy OAuth/DCR resolver retained for source compatibility. Normal runtime * gates use {@link resolveRuntimeAgentCredentials} instead. */ export declare function resolveAgentCredentials(options?: ResolveAgentCredentialsOptions): Promise; interface ClientCredentialsTokenArgs { projectUrl: string; clientId: string; clientSecret: string; audience?: string; scope?: string; signal?: AbortSignal; } interface ClientCredentialsTokenResult { accessToken: string; expiresAt: number; scope?: string; } /** POST /oauth2/token with grant_type=client_credentials. */ export declare function fetchClientCredentialsToken(args: ClientCredentialsTokenArgs): Promise; export interface EnsureAgentIdentityOptions { /** Retained for source compatibility; runtime resolution uses the connected client. */ projectUrl?: string; /** Retained for source compatibility and activity attribution. */ harness?: string; /** * Session this identity acts in. Selects the persisted DCR slot and defaults * to the client's ambient session. */ sessionId?: string; /** Abort network work when a bounded caller, such as hook shutdown, expires. */ signal?: AbortSignal; /** @deprecated Runtime resolution never uses DCR. */ allowRegistration?: boolean; /** Inject the RFC 7592 revoke used when draining retired credentials. */ revokeFn?: typeof revokeAgentDynamicClient; /** Env override (defaults to process.env). */ env?: NodeJS.ProcessEnv; /** Inject resolver for tests. */ resolveFn?: (options: ResolveRuntimeAgentCredentialsOptions & ResolveAgentCredentialsOptions) => Promise; /** Inject OS-backed persistence for tests or embedded runtimes. */ credentialStore?: TalosCredentialStore; /** Inject Talos enrollment for tests. */ enrollFn?: typeof enrollTalosRuntimeCredential; /** Inject random idempotency-key generation for tests. */ createIdempotencyKey?: () => string; /** * @deprecated Normal runtime resolution never processes OAuth registrations. */ drainRetired?: boolean; /** * What prompted this resolution, recorded on the `agent.auth` activity event. Absent for * the session-start gate; `read_credential` when the tool gate resolved the * identity because it needed a credential to authenticate a permission read. */ trigger?: string; } /** * Resolve the agent's credentials and attach them to the client. * * Always returns the resolved credentials; never throws and never blocks. * Emits exactly one `agent.auth` activity event so the audit trail records whether * the agent identity was available for the session. */ export declare function ensureAgentIdentity(client: OryAgentClient, options?: EnsureAgentIdentityOptions): Promise; /** * Resolved identity for a sub-agent (a typed worker that the parent * agent delegates to, e.g. Claude Code's `Task` tool launching an * `Explore` sub-agent). The shape mirrors AgentCredentials but the * harness integration reloads the child runtime credential for hooks that * execute inside that child; the secret is never injected into the child process. */ export interface SubAgentIdentity { /** `runtime` in normal use; `dynamic` remains for injected legacy callers. */ kind: "runtime" | "dynamic" | "none"; /** Sub-agent type as reported by the harness (e.g. "Explore"). */ subAgentType: string; /** Issued client_id — also used as the audit subject for this sub-agent. */ subject?: string; /** Access token proving the reported sub-agent client id to the broker. */ token?: string; /** Persisted credentials returned by registration (or reloaded). */ credentials?: OryAgentDynamicCredentials; /** Child-owned credential used for delegation and subsequent child calls. */ runtimeCredential?: RuntimeCredential; /** One-line reason suitable for telemetry. */ reason: string; /** Diagnostic warnings raised during resolution. */ warnings: string[]; } export interface EnsureSubAgentIdentityOptions { /** Sub-agent type identifier (e.g. "Explore", "general-purpose"). */ subAgentType: string; /** Retained for source compatibility; no DCR request is made. */ projectUrl?: string; /** Harness identifier used for activity and delegation context. */ harness?: string; /** Session this sub-agent belongs to. Defaults to the client's ambient session. */ sessionId?: string; /** Stable identifier for this individual child spawn. */ perSpawnId?: string; /** Abort network work owned by this resolution attempt. */ signal?: AbortSignal; /** Let an adapter emit authentication after selecting the Sub-Agent bearer. */ emitActivity?: boolean; /** * @deprecated Runtime sub-agent resolution never registers OAuth clients. */ iat?: string; /** Env override (defaults to process.env). */ env?: NodeJS.ProcessEnv; /** @deprecated Retained for source compatibility and never called. */ registerAgentClientFn?: typeof registerAgentClient; /** @deprecated Retained for source compatibility and never called. */ loadFn?: typeof loadSubAgentDynamicCredentials; saveFn?: typeof saveSubAgentDynamicCredentials; /** @deprecated Retained for source compatibility and never called. */ revokeFn?: typeof revokeAgentDynamicClient; /** @deprecated Retained for source compatibility and never called. */ fetchClientCredentialsTokenFn?: typeof fetchClientCredentialsToken; credentialStore?: TalosCredentialStore; enrollFn?: typeof enrollTalosChildRuntimeCredential; createIdempotencyKey?: () => string; allowEnrollment?: boolean; } /** * Resolve a sub-agent against the process runtime credential. The broker's * `type_name` supplies sub-agent attribution; no separate OAuth identity is * registered, loaded, minted, or persisted. */ export declare function ensureSubAgentIdentity(client: OryAgentClient, options: EnsureSubAgentIdentityOptions): Promise; export {};