grain-plugin-wasm 0.0.1-beta3

WebAssembly Component Model plugin runtime for grain. Loads `.wasm` plugins at runtime and exposes their tools through the `AgentTool` trait — no recompiling grain to add a plugin.
Documentation
// WIT contract for grain WASM plugins (Component Model).
//
// Plugins implement the `grain-plugin` world: they export `plugin`,
// which provides `init`, `list-tools`, and `call-tool`. The host
// provides logging, env-var access, and HTTP primitives — each
// gated by the plugin's declared capabilities in `plugin.toml`.

package grain:plugin;

/// Host-provided functions imported by the plugin.
interface host {
    /// Log severity levels.
    enum log-level {
        debug,
        info,
        warn,
        error,
    }

    /// HTTP response returned by host HTTP functions.
    record http-response {
        status: u16,
        headers: list<tuple<string, string>>,
        body: string,
    }

    /// Emit a log line at the given severity.
    log: func(level: log-level, msg: string);

    /// Read an environment variable. Returns `none` when unset.
    env-get: func(key: string) -> option<string>;

    /// HTTP GET. Returns an error string on transport failure.
    http-get: func(url: string, headers: list<tuple<string, string>>)
        -> result<http-response, string>;

    /// HTTP POST. Returns an error string on transport failure.
    http-post: func(url: string, headers: list<tuple<string, string>>, body: string)
        -> result<http-response, string>;
}

/// Plugin-exported interface.
interface plugin {
    /// Describes one tool the plugin provides.
    record tool-def {
        /// Unique tool name (snake_case by convention).
        name: string,
        /// Human-readable label shown in the UI.
        label: string,
        /// One-paragraph description for the LLM.
        description: string,
        /// JSON Schema string describing the tool's parameters.
        parameters-json: string,
    }

    /// Result returned from a tool invocation.
    record tool-result {
        /// JSON-encoded content value.
        content-json: string,
        /// Whether this result represents an error.
        is-error: bool,
    }

    /// Metadata returned by `init`.
    record plugin-info {
        /// Human-readable plugin name.
        name: string,
        /// SemVer version string.
        version: string,
    }

    /// Called once after instantiation. Returns plugin metadata or
    /// an error string that aborts loading.
    init: func() -> result<plugin-info, string>;

    /// Enumerate all tools the plugin provides.
    list-tools: func() -> list<tool-def>;

    /// Invoke a tool by name with JSON-encoded arguments.
    call-tool: func(name: string, args-json: string) -> tool-result;
}

/// Optional orchestration surface for plugins that want to declare
/// model-role slots and participate in host-controlled lifecycle hooks.
///
/// Compatibility note: this interface is exported only by the v2 world
/// below. The original `grain-plugin` world intentionally stays unchanged
/// so existing plugins keep loading without recompilation.
interface orchestration {
    /// Lifecycle points a plugin may observe. The host owns when these
    /// hooks are called and validates every returned action before applying it.
    enum hook-point {
        before-agent-start,
        after-tool-call,
        prepare-next-turn,
        should-stop-after-turn,
    }

    /// One model-role slot declared by the plugin.
    record role-def {
        /// Stable role name, e.g. "designer" or "coder".
        name: string,
        /// grain-llm-models model id, e.g. "openai/gpt-4o".
        model: string,
        /// System prompt text or a host-understood prompt key.
        prompt: string,
        /// Tool names allowed while this role is active.
        tools: list<string>,
        /// Optional thinking level string: off/minimal/low/medium/high/xhigh.
        thinking-level: option<string>,
    }

    /// One hook subscription declared by the plugin.
    record hook-def {
        point: hook-point,
        /// Human-readable hook name for diagnostics.
        name: string,
    }

    /// Optional UI header override. The host decides which surfaces honor
    /// this; terminal UIs use it for the provider/model header.
    record ui-header {
        provider: option<string>,
        model: option<string>,
    }

    /// Host action requested by a hook. The host treats these as intent,
    /// not authority: model ids, roles, tools, and timing are validated
    /// outside the guest before any agent state changes.
    variant host-action {
        switch-role(string),
        switch-model(string),
        set-system-prompt(string),
        set-active-tools(list<string>),
        inject-user-message(string),
        stop-after-turn(bool),
        emit-custom(string),
        set-ui-header(ui-header),
        set-ui-status(string),
    }

    /// Enumerate model-role slots this plugin contributes.
    list-roles: func() -> list<role-def>;

    /// Enumerate lifecycle hooks this plugin wants called.
    list-hooks: func() -> list<hook-def>;

    /// Invoke one hook with a JSON-encoded context payload. Returns a list
    /// of requested host actions.
    call-hook: func(point: hook-point, context-json: string)
        -> result<list<host-action>, string>;
}

/// The world a grain plugin must target.
world grain-plugin {
    import host;
    export plugin;
}

/// Extended world for orchestration-aware plugins. This is deliberately
/// separate from `grain-plugin` so v1 plugins are not forced to export the
/// orchestration interface.
world grain-plugin-v2 {
    import host;
    export plugin;
    export orchestration;
}