import { ComponentType } from 'react'; /** * Audio input handler */ export declare interface AudioInputHandler { type: "audio"; handleInput: (audioData: string, duration: number) => ToolResult; } export declare type BackendType = "textLLM" | "imageGen" | "audio" | "search" | "browse" | "map" | "mulmocast"; declare interface BaseFieldSchema { label: string; description?: string; required?: boolean; } export declare interface BooleanFieldSchema extends BaseFieldSchema { type: "boolean"; } /** * Camera capture input handler */ export declare interface CameraInputHandler { type: "camera"; mode: "photo" | "video"; handleInput: (data: string, metadata?: { duration?: number; }) => ToolResult; } /** * Clipboard image input handler */ export declare interface ClipboardImageInputHandler { type: "clipboard-image"; handleInput: (imageData: string) => ToolResult; } export declare type ConfigFieldSchema = StringFieldSchema | NumberFieldSchema | BooleanFieldSchema | SelectFieldSchema | MultiSelectFieldSchema; export declare type ConfigValue = string | number | boolean | string[]; /** * Base interface for custom input handlers * Apps can extend this to create their own handler types */ export declare interface CustomInputHandler { type: string; handleInput: (...args: unknown[]) => ToolResult; } /** * Default shape for `PluginRuntime['endpoints']` — see * `BrowserPluginRuntime['endpoints']` for the same rationale on the * Vue side. Plugin authors pin a tighter shape via the `E` type * parameter on `PluginRuntime` / `definePlugin<…, E>`. */ export declare type DefaultServerPluginEndpoints = Readonly>; /** * Identity function for type inference. Same philosophy as * `defineComponent` in Vue: it does nothing at runtime, just lets * TypeScript thread the runtime/result types so the plugin author * gets full IntelliSense on `runtime.X` without manual annotations. * * Generic placement (T inferred from setup's return) lets * `StrictPluginResult` extract `TOOL_DEFINITION.name` and require * a matching named handler — Codex review #10 iter-2 caught that an * earlier `>` * shape would not actually narrow `N` at the call site. * * Plugin authors should declare `name` as a literal (`as const`) so * the strict handler check fires: * * TOOL_DEFINITION: { type: "function" as const, name: "myTool" as const, ... } * * Without `as const`, `N` widens to `string` and the strict check * gracefully degrades to the loose runtime warn (see * `StrictPluginResult` above). * * **Annotate the handler parameter explicitly** as `args: unknown`. * TypeScript can't propagate the contextual type into the method * parameter when it's still inferring `T` from the same return value * (circular), so leaving `args` un-annotated trips `noImplicitAny`. * Always: * * async myTool(args: unknown) { ... } * * @example * ```ts * export default definePlugin(({ pubsub, files, locale }) => ({ * TOOL_DEFINITION: { * type: "function" as const, * name: "myTool" as const, * description: "...", * parameters: { type: "object", properties: {}, required: [] }, * }, * async myTool(args: unknown) { * // narrow `args` here — typically with Zod * await files.data.write("state.json", JSON.stringify(args)); * pubsub.publish("changed", {}); * return { ok: true }; * }, * })); * ``` */ export declare function definePlugin(setup: (runtime: PluginRuntime) => T & StrictPluginResult): (runtime: PluginRuntime) => T; /** * File input handler */ export declare interface FileInputHandler { type: "file"; acceptedTypes: string[]; handleInput: (fileData: string, fileName: string) => ToolResult; } /** * File operations scoped to a single root directory (data or config). * All `rel` arguments are POSIX-relative paths. The platform normalises * input (`\` → `/`, `path.posix.normalize`, `ensureInsideBase`) before * touching disk, so misuse of `node:path` on Windows still works and * `"../../etc/passwd"` is rejected. * * Plugin authors should never need `node:fs` or `node:path`. */ export declare interface FileOps { /** Read UTF-8 string. Throws if the file does not exist. */ read(rel: string): Promise; /** Read raw bytes. */ readBytes(rel: string): Promise; /** Write atomically. Creates parent directories as needed. */ write(rel: string, content: string | Uint8Array): Promise; /** List basenames in `rel`. */ readDir(rel: string): Promise; /** mtime (ms since epoch) and byte size. */ stat(rel: string): Promise<{ mtimeMs: number; size: number; }>; /** Existence check (saves try/catch boilerplate). */ exists(rel: string): Promise; /** Delete a file. No-op if it does not exist. */ unlink(rel: string): Promise; } /** * Union of all input handler types */ export declare type InputHandler = FileInputHandler | ClipboardImageInputHandler | UrlInputHandler | TextInputHandler | CameraInputHandler | AudioInputHandler; /** Type guard the runtime loader uses to detect factory-shape vs. legacy * raw-export plugins. Exported so other host code can share the test. * Loosened to the widened `PluginFactoryResult` because the * caller (the runtime loader) doesn't know the plugin's tool name at * this point — it pulls `TOOL_DEFINITION` from the factory's return * value to discover it. The strict, name-narrowed form is enforced at * the `definePlugin` call site instead. */ export declare function isPluginFactory(value: unknown): value is (runtime: PluginRuntime) => PluginFactoryResult; /** * Schema Types (Framework-agnostic) * * JSON Schema based types for tool definitions and plugin settings. */ /** * JSON Schema property definition */ export declare interface JsonSchemaProperty { type?: string; description?: string; enum?: string[]; items?: JsonSchemaProperty; minimum?: number; maximum?: number; minItems?: number; maxItems?: number; properties?: Record; required?: string[]; additionalProperties?: boolean; oneOf?: JsonSchemaProperty[]; [key: string]: unknown; } export declare interface MultiSelectFieldSchema extends BaseFieldSchema { type: "multiselect"; options: SelectOption[]; minItems?: number; maxItems?: number; } export declare interface NumberFieldSchema extends BaseFieldSchema { type: "number"; min?: number; max?: number; step?: number; } /** * Plugin configuration schema (JSON Schema based) */ export declare interface PluginConfigSchema { key: string; defaultValue: ConfigValue; schema: ConfigFieldSchema; } /** * What a plugin's `setup` function returns at the **loose** level — * just `TOOL_DEFINITION`. Whether a matching named handler is * type-required is decided by the wrapper type `StrictPluginResult` * (used by `definePlugin` below) rather than by this base shape, so * the runtime loader's `isPluginFactory` predicate can keep using * the loose form when it doesn't yet know the tool name. * * Codex review #10 caught two iterations of looser-than-intended * checks here; the final form below uses an inference helper so the * strict requirement actually fires at the `definePlugin` call site. */ export declare interface PluginFactoryResult { TOOL_DEFINITION: ToolDefinition; [exportName: string]: unknown; } /** * Subset of `RequestInit` the runtime forwards to the underlying fetch. * Re-declared here so we don't need a `lib.dom` dependency in the core * type declaration. */ export declare interface PluginFetchInit { method?: string; headers?: Record; body?: string | Uint8Array; signal?: AbortSignal; } export declare interface PluginFetchJsonOptions extends PluginFetchOptions { /** * Zod-agnostic validator. Pass a function that returns the narrowed * value (or throws). Idiomatic with Zod: * `parse: (raw) => MySchema.parse(raw)`. * * Required when calling `fetchJson` for any T other than * `unknown` — the overload signatures below enforce this so callers * never get a strongly-typed value out of unvalidated JSON. */ parse: (raw: unknown) => T; } export declare interface PluginFetchOptions extends PluginFetchInit { /** AbortController timeout. Default 10 000 ms. */ timeoutMs?: number; /** When set, requests to a `URL.hostname` not in the list throw. */ allowedHosts?: readonly string[]; } /** * Runtime handed to a plugin's `definePlugin(setup)` factory at load time. * The plugin closes over the destructured fields; handlers reference them * as bare API calls (no `context.` indirection). * * `pubsub` / `files.*` / `log` are scoped per plugin. The plugin cannot * spell another plugin's channel or file path through the API. * * Optional `E` type parameter pins the `endpoints` map's shape. Defaults * to `DefaultServerPluginEndpoints` for backward compatibility — non- * generic usage (`PluginRuntime`) keeps working unchanged (0.3.2). */ export declare interface PluginRuntime { /** * Scoped pub/sub publisher. `publish("foo", payload)` is internally * routed to channel `plugin::foo`. The plugin cannot publish to * another plugin's namespace. */ pubsub: { publish(eventName: string, payload: T): void; }; /** * Locale tag the host detected at startup (`"en"`, `"ja"`, …). The * server side is a snapshot; for reactive updates use the frontend * `BrowserPluginRuntime.locale: Ref` instead. */ locale: string; /** * Scoped file I/O. * - `data`: `~/mulmoclaude/data/plugins//` — per-plugin private backup target * - `config`: `~/mulmoclaude/config/plugins//` — per-plugin private UI state * - `artifacts`: `~/mulmoclaude/artifacts/` — SHARED, user-browsable output area * * Unlike `data`/`config` (each sandboxed to a private per-plugin dir), * `artifacts` is rooted at the host's shared artifacts directory so a * plugin can write outputs the user sees in the Files explorer — e.g. a * chart plugin writing `charts/.chart.json`. Relative paths are * still normalised + traversal-guarded by the host; the plugin owns its * category subdir (`charts/`, `spreadsheets/`, …) by convention. */ files: { data: FileOps; config: FileOps; artifacts: FileOps; }; /** * Logger bridge to the host's logger. Prefix `plugin/` is added * automatically. Use this instead of `console.*` so plugin output * lands in the central log files. */ log: { debug(msg: string, data?: object): void; info(msg: string, data?: object): void; warn(msg: string, data?: object): void; error(msg: string, data?: object): void; }; /** * `fetch` wrapper with timeout and (optional) host allowlist. Use * instead of `globalThis.fetch` so timeouts and allowlists are * applied uniformly across all plugins. */ fetch(url: string, opts?: PluginFetchOptions): Promise; /** * `fetch` + `response.json()` + optional validator. * * - Without `opts.parse`, the result type is `unknown` — callers * must narrow before use. This prevents strongly-typed access to * un-validated remote JSON, which is a frequent type-soundness * bug ("the server promised X, then it didn't"). * - With `opts.parse`, the validator's return type narrows the * promise. Idiomatic with Zod: * `await fetchJson(url, { parse: (raw) => MySchema.parse(raw) })`. */ fetchJson(url: string, opts?: PluginFetchOptions): Promise; fetchJson(url: string, opts: PluginFetchJsonOptions): Promise; /** * Optional URL map mirroring `BrowserPluginRuntime.endpoints` — * see that field for the rationale. Server-side plugin handlers * rarely need this (they typically service the dispatch endpoint * directly), but the field is defined symmetrically so a * cross-cutting plugin (e.g. one that calls into another plugin's * URL via `runtime.fetch`) doesn't have to import the host's * config. * * Single-dispatch plugins (the common runtime-loaded shape) leave * this `undefined`. */ endpoints?: E; } /** * Standard props for Preview components */ export declare interface PreviewComponentProps { result: ToolResultComplete; isSelected?: boolean; onSelect?: () => void; } export declare interface SelectFieldSchema extends BaseFieldSchema { type: "select"; options: SelectOption[]; } export declare interface SelectOption { value: string; label: string; description?: string; disabled?: boolean; } /** * Options for sendTextMessage */ export declare interface SendTextMessageOptions { /** Optional data to pass along with the message (for testing/debugging) */ data?: unknown; } /** * Compile-time strict shape used to constrain `definePlugin`'s setup * return type. Extracts the `name` literal out of `T.TOOL_DEFINITION` * and demands a handler key matching it. When `name` widens to * `string` (no `as const`), the strict check degrades to the loose * `PluginFactoryResult` and the runtime loader's load-time warn is * the safety net. */ export declare type StrictPluginResult = T extends { TOOL_DEFINITION: { name: infer N extends string; }; } ? string extends N ? PluginFactoryResult : T & { [K in N]: (args: unknown) => unknown | Promise; } : never; export declare interface StringFieldSchema extends BaseFieldSchema { type: "string"; placeholder?: string; minLength?: number; maxLength?: number; pattern?: string; } /** * Text input handler */ export declare interface TextInputHandler { type: "text"; patterns?: string[]; handleInput: (text: string) => ToolResult; } /** * Context passed to plugin execute function */ export declare interface ToolContext { currentResult?: ToolResult | null; app?: ToolContextApp; } /** * App interface provided to plugins via context.app * Contains backend functions and config accessors */ export declare interface ToolContextApp extends Record any> { getConfig: (key: string) => T | undefined; setConfig: (key: string, value: unknown) => void; } /** * Tool definition for OpenAI-compatible function calling */ export declare interface ToolDefinition { type: "function"; name: string; description: string; /** System-prompt instruction telling the LLM when/how to use this tool. * Unlike `description` (which is part of the tool schema sent to the LLM), * `prompt` is injected into the system prompt by the host application. */ prompt?: string; parameters?: { type: "object"; properties: Record; required: string[]; additionalProperties?: boolean; }; } /** * Core plugin interface - framework agnostic * Does not include UI components * * @typeParam T - Tool-specific data type (for views) * @typeParam J - JSON data type (passed to LLM) * @typeParam A - Arguments type for execute function * @typeParam H - Input handler type (allows custom handlers) * @typeParam S - Start response type (app-specific server response) */ export declare interface ToolPluginCore> { toolDefinition: ToolDefinition; execute: (context: ToolContext, args: A) => Promise>; generatingMessage: string; waitingMessage?: string; isEnabled: (startResponse?: S | null) => boolean; delayAfterExecution?: number; /** @deprecated Use {@link ToolDefinition.prompt} instead. */ systemPrompt?: string; inputHandlers?: H[]; configSchema?: PluginConfigSchema; samples?: ToolSample[]; backends?: BackendType[]; } /** * React plugin interface - extends core with React components * * @typeParam T - Tool-specific data type (for views) * @typeParam J - JSON data type (passed to LLM) * @typeParam A - Arguments type for execute function * @typeParam H - Input handler type (allows custom handlers) * @typeParam S - Start response type (app-specific server response) */ export declare interface ToolPluginReact> extends ToolPluginCore { ViewComponent?: ComponentType>; PreviewComponent?: ComponentType>; } /** * Result returned from plugin execution. * * ### `data` gates rendering * * `data` is the host's render-eligibility signal: setting it * means "render a GUI card for this result"; omitting it makes * the result *narrate-only* (`message` / `instructions` flow to * the LLM, but no card is shown). Use narrate-only for actions * whose effect is purely informational for the LLM — fetching a * list the LLM will summarize, validation-error returns, etc. * * `jsonData` is orthogonal: it's the JSON-serializable copy * returned to the LLM alongside `message` / `instructions` for * cases where the model needs to read the structured result back * on subsequent turns. Setting `jsonData` does NOT, by itself, * cause a card to render — pair it with `data` if you also want * the view to bind the same shape. * * ### Choosing what to set * * - **`data` only** — render a card; the LLM only sees `message`. * - **Neither** — narrate-only; no card. * - **Both** (`data: payload, jsonData: payload`) — render a card * AND let the LLM read the same payload back. Use this when * the view and the LLM need to reason over the same shape * (e.g. a quiz definition, a form spec). * - **`jsonData` only** — uncommon; the LLM gets a JSON copy with * no card. Equivalent to narrate-only as far as the GUI is * concerned. * * ### Worked examples * * Card with view-only payload (LLM only needs to know it succeeded): * ```ts * return { message: "Generated image", data: { url, prompt } }; * ``` * * Narrate-only (no card): * ```ts * return { message: `Found ${reports.length} reports`, instructions: "..." }; * ``` * * Card + LLM-readable payload (same payload, two audiences): * ```ts * return { message: "Form presented", data: form, jsonData: form, instructions: "..." }; * ``` */ export declare interface ToolResult { toolName?: string; uuid?: string; message: string; title?: string; action?: string; /** * JSON-serializable result the LLM reads back alongside * `message` / `instructions`. Orthogonal to rendering — only * `data` causes a card to render. Set this when the LLM needs * to recall the structured result on subsequent turns; pair * with `data` to also render a card bound to the same shape. */ jsonData?: J; instructions?: string; instructionsRequired?: boolean; updating?: boolean; cancelled?: boolean; /** * Typed payload consumed by the plugin's view / preview * component. Not visible to the LLM. **Setting `data` is the * host's render-eligibility signal** — a result without `data` * is treated as narrate-only and no card is rendered. See the * interface-level docs for the full rule and worked examples. */ data?: T; viewState?: Record; } /** * Complete tool result with required fields */ export declare interface ToolResultComplete extends ToolResult { toolName: string; uuid: string; } /** * Sample arguments for testing */ export declare interface ToolSample { name: string; args: Record; } /** * URL input handler */ export declare interface UrlInputHandler { type: "url"; patterns?: string[]; handleInput: (url: string) => ToolResult; } /** * Standard props for View components */ export declare interface ViewComponentProps { selectedResult: ToolResultComplete; sendTextMessage: (text?: string, options?: SendTextMessageOptions) => void; onUpdateResult?: (result: Partial>) => void; pluginConfigs?: Record; isAudioPlaying?: boolean; } export { }