/** * Plan/28 Wave B — typed action handlers. * * Each handler maps one `ClientMessage` action to a public Pi SDK call, * and replies with `action_ok` or `action_error`. Handlers take their * dependencies as parameters so the index.ts wiring is one-liner and * unit tests can pass fakes without touching global state. * * `models_list` lives next door because it shares the `ModelRegistry` * helper and the same wire vocabulary. * * SDK API surface used (see plan/28 Wave 0 for the full table): * * - `ctx.compact()` — non-blocking, fires `session_compact` * event when done * - `ctx.newSession()` — only on `ExtensionCommandContext`; * resolves with `{cancelled}` flag * - `pi.setModel(model)` — returns `false` if no auth configured * - `pi.setThinkingLevel(lvl)` — synchronous * - `ctx.getModel()` — optional, undefined before first turn * - `ModelRegistry.{refresh,getAvailable,find}` — see `registry.ts` */ import type { ClientMessage, ServerMessage, WireModel } from "../protocol/types.js"; /** * Structural subset of the SDK's `Model` interface (defined in * `@earendil-works/pi-ai`, which is a transitive dep — not re-exported by * `@earendil-works/pi-coding-agent`'s main entry). Capturing just the * fields we touch keeps the handler decoupled from the SDK's full Model * surface and avoids a direct dep on `pi-ai`. */ export interface SdkModelLike { id: string; name: string; provider: string; reasoning: boolean; contextWindow: number; /** Plan/30: accepted input modalities. The SDK's `Model.input` is * `("text" | "image")[]`; we read `includes("image")` for the `vision` * flag. Optional here so tests can omit it (treated as text-only). */ input?: ("text" | "image")[]; } type Model<_TApi = unknown> = SdkModelLike; /** * Minimal channel surface needed to reply. Mirrors `PlainPeerChannel`'s * `.send` signature; tests pass an array-backed fake. */ export interface ActionReplySender { send(msg: ServerMessage): void; } /** * Narrow shape of the `ExtensionAPI` surface action handlers actually * call. Lets the test layer stub just these without rebuilding the full * SDK type (which has 30+ methods we don't use here). */ export interface ActionPi { setModel(model: Model): Promise; setThinkingLevel(level: import("../protocol/types.js").ThinkingLevel): void; } /** * Narrow shape of the per-call context. Drawn from the union of * `ExtensionContextActions` (compact, getModel) and * `ExtensionCommandContextActions` (newSession), since index.ts caches * the most-recent ctx and that's typically the command one. * * All fields are optional so a missing method (e.g. when only a plain * `ExtensionContext` was seen) becomes a typed `action_error` instead of * a runtime TypeError. */ export interface ActionCtx { compact?: (options?: object) => void; /** * Starts a new session. `withSession` is the SDK's blessed hook for * post-replacement work: it receives a FRESH, command-capable ctx bound to * the new session. The SDK marks any ctx captured BEFORE this call stale, so * callers must re-capture via `withSession` rather than reuse the old ctx. */ newSession?: (options?: { withSession?: (ctx: ActionCtx) => Promise; }) => Promise<{ cancelled: boolean; }>; getModel?: () => Model | undefined; } /** * Minimal shape of the registry surface. Maps 1:1 onto `ModelRegistry` * but lets tests fake catalogs without instantiating the real one. */ export interface ActionModelRegistry { refresh(): void; getAvailable(): Model[]; find(provider: string, modelId: string): Model | undefined; } /** Project a SDK `Model` onto the wire schema. Shared by list_models * and the `current` echo, so both stay in lockstep. */ export declare function wireFromModel(model: Model): WireModel; type SessionCompactMsg = Extract; type SessionNewMsg = Extract; type ModelSetMsg = Extract; type ThinkingSetMsg = Extract; type ListModelsMsg = Extract; export declare function handleSessionCompact(ctx: ActionCtx | null, sender: ActionReplySender, msg: SessionCompactMsg): void; export declare function handleSessionNew(ctx: ActionCtx | null, sender: ActionReplySender, msg: SessionNewMsg, onReplaced?: (freshCtx: ActionCtx) => void): Promise; export declare function handleThinkingSet(pi: ActionPi, sender: ActionReplySender, msg: ThinkingSetMsg): void; export declare function handleModelSet(pi: ActionPi, reg: ActionModelRegistry, sender: ActionReplySender, msg: ModelSetMsg, onPersist?: (provider: string, modelId: string) => void): Promise; export declare function handleListModels(ctx: ActionCtx | null, reg: ActionModelRegistry, sender: ActionReplySender, msg: ListModelsMsg): void; export {};