/** * Hand-written structural types for the OpenCode v2 plugin surface. * * We deliberately do NOT import `@opencode/plugin`: it is a separate package * from the v1 SDK, and a v2 SDK that becomes a runtime dependency would break * the dual-personality export for every v1 user who cannot resolve it. The * shapes below cover only the members this plugin actually touches, and they * are transcribed from `@opencode/plugin@2.0.16/dist/promise/*.d.ts` plus two * live-host probes (2026-09-25) — the probe is what proves `hook()` returns a * disposable registration and that `execute.before` can really deny. * * Kept intentionally loose (`unknown` where we only pass values through): a * type that is wider than the host's never fails a build, and every field we * READ is read defensively because the host makes no stability promise. */ export interface V2Registration { readonly dispose: () => Promise; } /** `Hooks` in the SDK: register by name, get a disposable back. */ export type V2Hooks = (name: Name, callback: (input: Spec[Name]) => Promise | void) => Promise; /** `Transform` in the SDK: mutate an in-memory editor. */ export type V2Transform = (callback: (input: Input) => void) => Promise; export interface V2TextContent { type: "text"; text: string; } export interface V2FileContent { type: "file"; uri: string; mime: string; name?: string; } export type V2Content = V2TextContent | V2FileContent; /** * `Tool.Result`. A live probe measured the host's own rejection of the v1 * shape: returning `{output: ""}` with no declared `output` schema came * back as "Tool result declared output without an output schema" for all three * probe tools — so this plugin's v2 adapters return `content` instead. */ export interface V2ToolResult { content?: string | ReadonlyArray; metadata?: Record; } export interface V2ToolContext { readonly sessionID: string; readonly agent: string; readonly messageID?: string; readonly id?: string; readonly signal?: AbortSignal; readonly progress?: (update: unknown) => Promise; } export interface V2ToolInfo { readonly name: string; readonly description: string; /** A JSON Schema object; the host also accepts zod/StandardSchema. */ readonly input: unknown; readonly execute: (args: Record, context: V2ToolContext) => Promise; readonly options?: { readonly namespace?: string; readonly permission?: string; /** * Read from the 2.0.16 binary, NOT from the SDK types (which have no such * word): host-built tools carry `options:{codemode:false}` and a tool whose * codemode is not false is offered only through the Code Mode catalog, where * its description is cut to the first line. This is why a live Team session * showed six tools and zero `tm_*` even though registration succeeded. */ readonly codemode?: boolean; }; } export interface V2ToolEditor { list(): ReadonlyArray; get(id: string): (V2ToolInfo & { id?: string; }) | undefined; add(tool: V2ToolInfo): void; update?(id: string, update: (tool: V2ToolInfo) => void): void; remove?(id: string): void; namespace?(namespace: unknown): void; } export interface V2ToolExecuteBefore { readonly tool: string; readonly sessionID: string; readonly agent?: string; input: unknown; } export interface V2ToolExecuteAfter { readonly tool: string; readonly sessionID: string; readonly agent?: string; readonly status: "completed" | "error"; readonly result?: unknown; readonly error?: unknown; } export interface V2ToolHooks { readonly "execute.before": V2ToolExecuteBefore; readonly "execute.after": V2ToolExecuteAfter; } export interface V2ToolDomain { readonly transform: V2Transform; readonly list: () => Promise>; readonly hook: V2Hooks; readonly reload?: () => Promise; } export interface V2PermissionRule { readonly action: string; readonly resource: string; readonly effect: "allow" | "deny" | "ask"; } export interface V2AgentInfo { id?: string; name?: string; description?: string; /** v2's spelling of v1's `prompt`. */ system?: string; mode?: "subagent" | "primary" | "all"; hidden?: boolean; color?: string; permissions?: V2PermissionRule[]; [key: string]: unknown; } export interface V2AgentEditor { list(): ReadonlyArray; get(id: string): V2AgentInfo | undefined; default(id: string | undefined): void; update(id: string, update: (agent: V2AgentInfo) => void): void; remove(id: string): void; } export interface V2AgentDomain { readonly transform: V2Transform; readonly list?: () => Promise>; } /** * `PermissionEvaluation` — `effect` is the mutable decision. A live probe saw * the host itself produce `effect:"ask"` (for an out-of-workspace * `external_directory` read) and publish `permission.asked` afterwards, so an * `ask` written here is honored by a real dialog. Observed `action` values: * `shell` / `read` / `edit` / `external_directory`, with `resources` carrying * the concrete command line or path. */ export interface V2PermissionEvaluation { readonly sessionID: string; readonly agent?: string; readonly action: string; readonly resources: ReadonlyArray; readonly metadata?: Record; effect: "allow" | "deny" | "ask"; message?: string; } export interface V2PermissionDomain { readonly hook: V2Hooks<{ evaluate: V2PermissionEvaluation; }>; readonly list?: (input?: unknown) => Promise; readonly get?: (input?: unknown) => Promise; readonly reply?: (input?: unknown) => Promise; } export interface V2SessionContext { readonly agent?: string; /** The session this request belongs to; absent on the compaction variant only. */ readonly sessionID?: string; readonly system: unknown[]; readonly messages: unknown[]; readonly tools: Record; options: Record; } export interface V2SessionHooks { readonly context: V2SessionContext; readonly compaction: V2SessionContext & { result?: unknown; }; } export interface V2SessionDomain { readonly hook: V2Hooks; /** * Measured on 2.0.23 (zero-token probe): `compact({})` fails with * `Missing key at ["sessionID"]` and a bogus id with `Session.NotFoundError`, so the * seam is real and its required key is known. The handler behind it is * `a.compact({sessionID, id, delivery})` and a duplicate input id surfaces as * `Session.CompactionConflictError`. * * DECLARED, NOT CALLED: 1.7.0 drove this seam from a 75% usage trigger of our own; that * automatic compaction was removed in 1.7.1 (the host owns when a session is * summarized). The declaration stays because it records what the host offers, and a * future layer would otherwise have to re-probe it — but nothing in this package calls * it, so there is no admission rule to state here. */ readonly compact?: (input: { sessionID: string; id?: string; delivery?: string; }) => Promise; /** * Measured 2026-10-21 on host 2.1.2 (zero-token sandbox probe): the plugin ctx session * domain really exposes list/create/get/remove/switchAgent/switchModel/prompt/generate/ * command/compact/synthetic/interrupt/update/move/wait/context — all functions. * `synthetic({})` fails with `Missing key at ["sessionID"]` and a bogus id with * `Session.NotFoundError`, so the seam dispatches to the session store. The host's own * API text: "Durably admit synthetic session input and schedule execution unless resume * is false" — the retry layer's ③ re-drive uses exactly this. `prompt` exists too and * the retry layer deliberately does NOT use it: synthetic input is the designed * non-user channel; a plugin writing a user-role message would impersonate the user. */ readonly synthetic?: (input: { sessionID: string; text: string; description?: string; metadata?: Record; delivery?: string; resume?: boolean; }) => Promise; readonly prompt?: (input: Record) => Promise; readonly wait?: (input: { sessionID: string; }) => Promise; readonly [key: string]: unknown; } /** * `ctx.model.list({})` measured on 2.0.23: `{location, data: […]}` where an item carries * `id, modelID, providerID, name, package, settings, capabilities, variants, time, cost, * status, enabled, limit{context,output}`. The `limit.context` is the denominator the * desktop's own usage meter reads, so the percent we compute means the same percent. */ export interface V2ModelDomain { readonly list?: (options: unknown) => Promise; readonly default?: (options?: unknown) => Promise; readonly [key: string]: unknown; } export interface V2ShellCreateBefore { command: string; cwd: string; timeout: number; shell: string; env: Record; } export interface V2ShellDomain { readonly hook: V2Hooks<{ "create.before": V2ShellCreateBefore; }>; } export interface V2StorageDomain { readonly get: (key: string) => Promise; readonly set: (key: string, value: unknown) => Promise; readonly remove: (key: string) => Promise; readonly scan: (options: unknown) => Promise; } export interface V2Event { readonly type?: string; readonly data?: unknown; } export interface V2Context { readonly location?: { readonly directory?: string; readonly project?: { readonly directory?: string; readonly id?: string; }; }; readonly options?: Record; readonly tool: V2ToolDomain; readonly agent?: V2AgentDomain; readonly permission?: V2PermissionDomain; readonly session?: V2SessionDomain; readonly model?: V2ModelDomain; readonly shell?: V2ShellDomain; readonly storage?: V2StorageDomain; readonly event?: { readonly subscribe: () => AsyncIterable; }; readonly [key: string]: unknown; } export type V2Cleanup = () => Promise | void; export interface V2Plugin { readonly id: string; readonly setup: (ctx: V2Context) => Promise | V2Cleanup | void; } //# sourceMappingURL=v2-types.d.ts.map