import { DebugLogger } from "./logger.js"; import { type EventReporterOptions } from "./event-reporter.js"; import type { SessionContext } from "./context.js"; import { type RuntimeCredential, type RuntimeRequestAuthenticator } from "./runtime-credential.js"; import type { SessionInfo, OAuth2TokenInfo, PermissionCheck, PermissionResult, BatchPermissionResult, OryError } from "./types.js"; export interface OryAgentConfig { /** Ory project URL, e.g. https://your-project.projects.oryapis.com */ projectUrl: string; /** Agent Security broker URL. Defaults to projectUrl for shipped configs. */ agentSecurityUrl?: string; /** Which harness this client is running in */ harness: string; /** Optional unified activity/debug NDJSON file path. */ logFile?: string; /** * Overrides the namespace the human user is addressed under. Users are always * addressed as a SubjectSet `:` (matching how the tuples * were written in Keto); this only overrides the namespace (default `User`). * Read by {@link resolveUserSubject}; `ORY_USER_SUBJECT_NAMESPACE` overrides it. */ userSubjectNamespace?: string; /** * Session cache TTL in milliseconds. * Set to 0 to disable caching. Default: 60_000 (1 minute). */ sessionCacheTtlMs?: number; /** Noninteractive request authentication, e.g. a SPIFFE mTLS transport. */ runtimeRequestAuthenticator?: RuntimeRequestAuthenticator; /** Optional stable runtime subject supplied by the custom integration. */ runtimeSubject?: string; /** Disabled by default so direct/test clients never perform background network calls. */ eventReporter?: true | Omit; } /** * Inputs for {@link OryAgentClient.recordDelegation}. The plugin supplies only * the *semantic* ends of the edge; the agent-security broker owns the Keto * tuple shape (namespace, `delegate` relation, and the * `||` join-key encoding), so no tuple/join-key logic * lives in the plugin. */ export interface RecordDelegationInput { /** * Which edge to record. `agent` records `user → agent`; `subagent` records * `agent → subagent`. Maps to the broker's `PRINCIPAL_KIND_AGENT` / * `PRINCIPAL_KIND_SUBAGENT`. */ kind: "agent" | "subagent"; /** * Kratos identity the delegation is anchored to (→ `User:`). * Required for the `agent` edge; optional for `subagent` (the broker resolves * the anchor from `delegatedBy`). */ identityId?: string; /** Host the runtime executes on — part of the `agent` edge join key. */ host?: string; /** * Sub-agent runtime type name (e.g. `"explore"`); required for the `subagent` * edge. Sent as `principal.type_name`. */ subAgentType?: string; /** * The parent agent's delegation node (`agent:` or the bare join key), * as returned in {@link RecordDelegationResult.nodeId} from the `agent` edge. * Required for the `subagent` edge; echoed verbatim so the plugin never * reconstructs the server's join-key encoding. */ delegatedBy?: string; /** * The harness session this edge was recorded in — sent as * `principal.session_id`, which is the field the broker's proto defines. The * dynamic OAuth2 credential is keyed by this session, and the broker also * needs the session to build the corresponding delegation node. * * Defaults to the client's ambient session ({@link OryAgentClient.sessionId}, * set by the harness's ambient session context); pass explicitly to override. Absent * for a caller with no session concept, which the broker reads as "keep this * runtime on one stable node" rather than inventing a session for it. */ sessionId?: string; /** * Identifier for one *spawn* of a sub-agent, where the harness exposes one * (Cursor's `subagent_id`, Claude Code's `agent_id`, OpenClaw's `childRunId`). * Sent as `principal.per_spawn_id`; it is what distinguishes two concurrent * same-type sub-agents. Absent on harnesses that expose no such id, in which * case concurrent spawns of a type share a node. */ perSpawnId?: string; } /** The delegation node the broker wrote, plus the resolved chain (root-first). */ export interface RecordDelegationResult { /** The node id written: `agent:` or `subagent:|`. */ nodeId: string; /** Root-first chain, e.g. `["User:", "agent:"]`. */ delegationChain: string[]; } /** * A principal known to the client. Either the human user (interactive * PKCE login) or the agent (machine credentials). */ export interface PrincipalIdentity { /** Subject (sub claim or `session:`) used in audit activity. */ subject?: string; /** Bearer token. Agent's token authenticates outgoing API calls. */ token?: string; } export declare class OryAgentClient { private frontend; private oauth2; private permission; private config; readonly logger: DebugLogger; private readonly eventReporter?; private _runtimeCredential?; private readonly _runtimeCredentialsBySession; private readonly _runtimeCredentialsByPrincipalKey; private sessionCache; private sessionCacheTtlMs; /** * The human user — the principal whose permissions we check. * Populated by `ensureUserAuthenticated` on session start. */ private _userPrincipal; private readonly _userPrincipalsBySession; /** * The AI agent process — the principal that authenticates outgoing * Ory API calls. Populated by `ensureAgentIdentity`. */ private _agentPrincipal; private readonly _agentPrincipalsBySession; private readonly _eventPrincipalsByKey; constructor(config: OryAgentConfig); /** Build a fresh set of Ory API instances using the current agent token. */ private buildApis; /** Canonical broker origin, with the shipped project URL fallback applied. */ get agentSecurityUrl(): string; private buildBrokerPermissionTransport; private postPermissionChecks; private permissionPrincipal; private subAgentTypeForClient; /** Snapshot of the current user principal. */ get userPrincipal(): PrincipalIdentity; /** Snapshot of the user principal bound to one session. */ userPrincipalForSession(sessionId?: string): PrincipalIdentity; /** Snapshot of the current agent principal. */ get agentPrincipal(): PrincipalIdentity; /** Active noninteractive broker credential. It is process memory only. */ get runtimeCredential(): RuntimeCredential | undefined; get hasRuntimeCredential(): boolean; setRuntimeCredential(credential: RuntimeCredential | undefined): void; /** Retain an actor-scoped credential for deferred event delivery without activating it globally. */ rememberRuntimeCredential(credential: RuntimeCredential): void; /** Run one hook with session/credential state isolated from concurrent hooks. */ withRuntimeContext(context: SessionContext, work: () => T): T; /** Snapshot of the agent principal bound to one session. */ agentPrincipalForSession(sessionId?: string): PrincipalIdentity; private eventPrincipal; /** * The harness this client runs in (`"claude-code"`, `"codex"`, …). Exposed * because persisted agent / sub-agent credentials and delegation anchors are * keyed first by harness and then by session. */ get harness(): string; /** * The harness session currently in scope, or `undefined` outside one. * * Read from standalone ambient session context, which every harness sets from its * own session identifier at the top of a lifecycle invocation — so identity * and delegation code can select the session's credential and node without * each plugin threading the id through its own call chain. */ get sessionId(): string | undefined; /** Best-effort bounded flush for short-lived hook subprocesses and tests. */ flushEvents(timeoutMs?: number): Promise; /** * Configured user-subject namespace (from config, resolved at construction). * `resolveUserSubject` uses this as the SubjectSet namespace when the * `ORY_USER_SUBJECT_NAMESPACE` env var isn't set. Undefined ⇒ direct * SubjectID resolution. */ get userSubjectNamespace(): string | undefined; /** * Set or update the human user principal. The user is the subject of * permission checks. Its token does not authenticate agent-attributed calls * (OAuth2 / Frontend / Relationship), but it *is* the fallback credential for * Keto permission reads when no agent token is present (see `buildApis`) — so * a change to the user token rebuilds the API instances, otherwise the * rehydrated user token would never reach the Permission API in a fresh * subprocess. Pass `{}` (or fields set to undefined) to clear. */ setUserPrincipal(principal: PrincipalIdentity, sessionId?: string | undefined): void; /** * Set or update the AI agent principal. The agent's token (when present) * authenticates *every* outgoing Ory call — OAuth2 / Frontend and Keto * (Permission / Relationship) alike — and carries audit attribution ("agent X * acting on behalf of user Y"). Rebuilds the underlying Ory API instances when * the token actually changes so subsequent calls pick it up; otherwise leaves * the API instances alone (so test stubs survive a no-op update). */ setAgentPrincipal(principal: PrincipalIdentity, sessionId?: string | undefined): void; /** * Verify an existing session token. Returns cached result if available. */ verifySession(sessionToken: string): Promise; /** * Introspect an OAuth2 access token. Use this when agents authenticate * via client credentials flow instead of session tokens. */ introspectToken(token: string, scope?: string): Promise; /** * Check if a subject has permission to perform an action. * * `options.activityAttributes` are merged into the `permission.check` activity * event so callers can attach harness-side context (toolName, mcpServer, * etc.) that the wire-level `PermissionCheck` shape doesn't carry. * * `options.informational` marks a check whose `false` result is *not* a * denial — e.g. the permission-mode probe, where "enforce not granted" simply * selects `observe`. Such an event ends `ok` (the read succeeded) rather than * `denied`, so a reader watching activity doesn't mistake a routine mode read * for a blocked tool. The boolean is still recorded as the `allowed` * attribute either way. */ checkPermission(check: PermissionCheck, options?: { activityAttributes?: Record; informational?: boolean; }): Promise; /** * Check multiple permissions in a single request. * * `options.activityAttributes` are merged into the `permission.batch_check` * activity event, for the same purpose as `checkPermission`'s attributes. */ batchCheckPermissions(checks: PermissionCheck[], options?: { activityAttributes?: Record; }): Promise; /** * Record a delegation edge through the Ory Agent Security broker * (`POST /agent-security/v1/delegations:record`), authenticated * with the process runtime credential. The broker constructs and writes the Keto tuple — the plugin * sends only the semantic inputs, so the join-key/tuple encoding stays * server-side and cannot drift here. Returns the node id + chain the broker * assigned. Throws a classified {@link OryError} on any non-2xx; callers wrap * in fail-open semantics (delegation is audit-only). * * The broker exists only on hosted Ory; against the local dev stack the call * returns 404 and callers treat it as a best-effort no-op. */ recordDelegation(input: RecordDelegationInput, options?: { fetchImpl?: typeof fetch; activityAttributes?: Record; agentToken?: string; runtimeCredential?: RuntimeCredential; signal?: AbortSignal; }): Promise; /** * Activity attributes describing which principals are attached to the * client. Useful for correlating audit logs * across the user/agent split. */ private principalActivityAttributes; /** * Classify an error from any Ory API call into a structured OryError. */ classifyError(err: unknown): OryError; private isAxiosError; /** * Create a client from environment variables. */ /** * Create a client from environment variables, falling back to the * shared config file at ~/.config/ory-agent-plugins/config.json. * * Resolution order for projectUrl: * 1. Environment variables (ORY_PROJECT_URL / ORY_SDK_URL) * 2. Config file (~/.config/ory-agent-plugins/config.json) * 3. Placeholder (fail-open pass-through mode) */ static fromEnv(harness: string, runtime?: Pick): OryAgentClient; } /** Default unified activity/debug log path under the shared per-harness data dir. */ export declare const DEFAULT_DEBUG_LOG_FILENAME = "ory-agent-debug.log"; /** * Unified activity/debug log path. Activity is always appended; verbose * debug/info/warn/error entries are appended only when `ORY_AGENT_DEBUG=true`. */ export declare function resolveDebugLogPath(harness: string): string | undefined; /** * Pull the Ory backend request ID from a response headers bag (axios returns * lowercase header names). Returns undefined when not present. */ export declare function extractOryRequestId(headers: unknown): string | undefined;