Events
The typed AgentEvent union every backend message becomes
Every backend message becomes a typed AgentEvent. Each event extends:
interface EventBase {
provider: ProviderId;
sessionId: string;
turnId?: string;
timestamp: number;
raw?: unknown; // the untouched native payload, always available
}The full union
event.type | Extra fields |
|---|---|
session.started | nativeSessionId?, model?, mode? |
session.ended | reason: "closed" | "exit" | "error" |
session.error | error: ErrorInfo |
turn.started | — |
turn.queued | position: number (1-based) |
turn.completed | result: TurnResult |
turn.failed | error: ErrorInfo |
message.delta | text: string |
message.completed | text: string |
reasoning.started | — |
reasoning.delta | text: string |
reasoning.completed | text?: string |
turn.diff | diff: string (cumulative working-tree diff) |
limits.updated | limits: RateLimit[] |
plan.updated | plan: PlanStep[] |
todo.updated | todos: TodoItem[] |
tool.started | tool: ToolInvocation |
tool.output | toolId: string, chunk: string |
tool.completed | toolId, outcome: "success" | "error" | "canceled", result? |
file.changed | changes: FileChange[] |
permission.requested | request: PermissionRequest |
permission.resolved | requestId, behavior: "allow" | "deny" |
usage.updated | usage: Usage |
subagent.started | subagent: SubagentInfo |
subagent.completed | subagent, outcome: "success" | "error" | "canceled" |
mode.changed | mode: string |
model.changed | model: string |
notice | level: "info" | "warning" | "error", message: string |
unknown | nativeType?: string |
Anything Klein Kit can't classify still reaches you as an unknown event with the native payload on raw — adapters never silently drop protocol traffic.
Supporting types
type PlanStepStatus = "pending" | "in_progress" | "completed";
interface PlanStep { step: string; status: PlanStepStatus }
interface TodoItem { id?: string; text: string; status: PlanStepStatus }
type ToolKind = "shell" | "file_read" | "file_edit" | "search" | "web" | "mcp" | "task" | "other";
interface ToolInvocation { id: string; name: string; kind: ToolKind; input: unknown; title?: string }
type FileChangeKind = "create" | "modify" | "delete" | "rename";
interface FileChange { path: string; kind: FileChangeKind; diff?: string; fromPath?: string }
interface SubagentInfo { id: string; name?: string; task?: string; native: boolean }
interface ErrorInfo { message: string; code?: string }
interface RateLimit { name: string; usedPercent?; windowMinutes?; resetsAt? }
interface Usage {
inputTokens?; outputTokens?; cachedInputTokens?; reasoningTokens?;
contextUsed?; contextWindow?; costUSD?; // all number | undefined
}Consuming events
Three ways, use whichever fits:
// 1. Iterate a single turn (most UIs)
for await (const event of session.prompt("…")) { /* … */ }
// 2. Iterate the whole session
for await (const event of session) { /* … */ }
// 3. Typed listeners — the callback narrows automatically
const off = session.on("tool.started", (e) => {
console.log(e.tool.name, e.tool.kind); // e is AgentEventOf<"tool.started">
});
off(); // unsubscribeAgentEventOf<T> extracts a variant from the union: AgentEventOf<"plan.updated"> is the event with the plan field.