import type { CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse, RequestPermissionRequest, RequestPermissionResponse, SessionNotification } from "@agentclientprotocol/sdk"; import type { BackendId } from "./backend.js"; import type { SteeringOutcome } from "./acp-client.js"; /** The ACP session/update discriminated union (every real-time update an agent can stream). */ export type AcpSessionUpdate = SessionNotification["update"]; /** The `sessionUpdate` discriminant strings (agent_message_chunk | tool_call | usage_update | …). */ export type AcpUpdateKind = AcpSessionUpdate["sessionUpdate"]; /** Which run / session / backend an event belongs to. A pooled runner multiplexes many concurrent * sessions over one process, so every event carries this envelope for disambiguation/filtering. */ export interface AcpEventContext { /** ACP session id this event pertains to. */ sessionId: string; /** Registry built-in or custom backend that produced it. */ backendId: BackendId; /** `RunOptions.label` of the originating run(), if one was set. */ label?: string; /** `RunOptions.runId` correlation id, if one was set. */ runId?: string; /** `RunOptions.callIndex` for the agent() call that opened this session, when supplied. */ callIndex?: number; /** Per-session snapshot of initialize-response `_meta`, omitted when absent or null. */ initializeMeta?: Readonly>; } /** Per-discriminant events: key = ACP `sessionUpdate` string, payload = that variant + context. */ type AcpSessionUpdateEvents = { [K in AcpUpdateKind]: Extract & AcpEventContext; }; /** A tool-permission request parked on an async resolver. This is the FIRST phase of the * resolver path only: it fires after the request is tracked for teardown cancellation and before * the host resolver is invoked. It carries no outcome because no ACP response has been returned * yet. Synchronous ToolPolicy decisions never emit this event. */ export interface AcpPermissionPendingEvent extends AcpEventContext { request: RequestPermissionRequest; } /** A tool-permission request the runner answered, paired with the FINAL decision it returned. * This is the SECOND phase for resolver-backed permissions and the ONLY phase for synchronous * ToolPolicy permissions. It fires exactly once per request with the outcome sent to the agent. */ export interface AcpPermissionEvent extends AcpEventContext { request: RequestPermissionRequest; outcome: RequestPermissionResponse; } /** An elicitation/create request parked on an async resolver. Resolver path only; the final * response is reported by elicitation_request exactly once. */ export interface AcpElicitationPendingEvent extends AcpEventContext { request: CreateElicitationRequest; } /** An elicitation/create request the runner answered, paired with the FINAL response returned * to the agent. Fires exactly once for resolver-backed and auto-declined requests. */ export interface AcpElicitationEvent extends AcpEventContext { request: CreateElicitationRequest; outcome: CreateElicitationResponse; } /** Notification that a URL-based elicitation completed, correlated back to the session context * captured when its elicitation/create request arrived. */ export interface AcpElicitationCompleteEvent extends AcpEventContext { notification: CompleteElicitationNotification; } /** A vendor extension notification (e.g. Claude `_claude/sdkMessage`) routed to a session. */ export interface AcpRawMessageEvent extends AcpEventContext { method: string; message: unknown; } /** A resolved `_session/steering` response. The originating prompt and request `_meta` are * deliberately excluded: steering is observable without leaking user content or metadata. */ export interface AcpSteeringEvent extends AcpEventContext { outcome: SteeringOutcome; } /** A pooled backend process crashed (not a graceful dispose). The engine retries the run on a * fresh process; this surfaces the crash for observability. Carries no session context. */ export interface AcpBackendErrorEvent { backendId: BackendId; error: Error; } /** * The full typed event map for AcpAgentRunner — every ACP `session/update` kind, plus the * cross-cutting events. The keys are exactly the strings you pass to `runner.on(...)`, and the * value is the payload your listener receives. */ export type AcpRunnerEventMap = AcpSessionUpdateEvents & { /** Catch-all: fires for EVERY session/update regardless of kind (carries the raw update). */ session_update: { update: AcpSessionUpdate; } & AcpEventContext; /** A permission request parked on an async resolver; resolver path only, no outcome yet. */ permission_pending: AcpPermissionPendingEvent; /** A permission request the runner answered, with the FINAL decision returned. */ permission_request: AcpPermissionEvent; /** An elicitation/create request parked on an async resolver; resolver path only. */ elicitation_pending: AcpElicitationPendingEvent; /** An elicitation/create request the runner answered, with the FINAL response returned. */ elicitation_request: AcpElicitationEvent; /** An elicitation/complete notification correlated to the originating session context. */ elicitation_complete: AcpElicitationCompleteEvent; /** A vendor extension notification arrived for a session. */ raw_message: AcpRawMessageEvent; /** A `_session/steering` request resolved, including the agent's outcome unchanged. */ steering: AcpSteeringEvent; /** A new session was opened on a pooled connection. */ session_open: AcpEventContext; /** A session was released / closed. */ session_close: AcpEventContext; /** A pooled backend process crashed (not a graceful dispose). */ backend_error: AcpBackendErrorEvent; }; export type AcpEventName = keyof AcpRunnerEventMap; export type AcpEventListener = (event: AcpRunnerEventMap[K]) => void; /** Non-session/update runner events. Kept exact by the type-level guard below so a new * cross-cutting event cannot be added to AcpRunnerEventMap without updating forwarders. */ export declare const ACP_CROSS_CUTTING_EVENT_NAMES: readonly ["permission_pending", "permission_request", "elicitation_pending", "elicitation_request", "elicitation_complete", "raw_message", "steering", "session_open", "session_close", "backend_error"]; /** Internal emit boundary handed from the runner down through the pool to each connection. */ export interface AcpEventSink { (name: K, event: AcpRunnerEventMap[K]): void; } /** * A tiny strongly-typed event emitter (no node:events, zero deps). `on()`/`once()` return an * unsubscribe thunk. `emit()` ISOLATES listener exceptions — one bad listener can never break the * run, the synchronous drain, or sibling listeners — mirroring the best-effort contract of * onUsage/onHistory. Generic over any event map `{ name: payload }`. */ export declare class TypedEventEmitter { private readonly listeners; /** Subscribe to `name`. Returns an unsubscribe thunk (calling it is equivalent to `off`). */ on(name: K, listener: (event: EventMap[K]) => void): () => void; /** Subscribe once: the listener auto-unsubscribes after its first delivery. */ once(name: K, listener: (event: EventMap[K]) => void): () => void; off(name: K, listener: (event: EventMap[K]) => void): void; removeAllListeners(name?: keyof EventMap): void; listenerCount(name: keyof EventMap): number; emit(name: K, event: EventMap[K]): void; } /** * Fan ONE ACP session/update out to the typed bus: the `session_update` catch-all first, then the * per-discriminant event. The payload IS the update variant merged with `ctx`, but TS cannot * correlate the runtime discriminant `name` with the mapped payload type at the call site, so the * (name, payload) pair is asserted once here against the precise indexed type — never `any`. */ export declare function emitSessionUpdate(emit: AcpEventSink, update: AcpSessionUpdate, ctx: AcpEventContext): void; export {}; //# sourceMappingURL=events.d.ts.map