package witmproxy:plugin@0.0.5;
interface capabilities {
use wasi:http/types@0.3.0-rc-2025-09-16.{request, response};
// TODO:
// resource http-client {} <- an HTTP client for making outbound requests
// resource key-value-client {} <- a simple key-value store client
// resource sql-client {} <- a SQLite/? database client
// resource queue-client {} <- a mpsc queue
// resource custom {} <- for user-defined capabilities
/// A dumb and simple logging resource (for writing to stdout/stderr)
/// TODO: replace with official wasi-logging when available
resource logger {
info: func(message: string);
warn: func(message: string);
error: func(message: string);
debug: func(message: string);
}
/// A resource for annotating/labeling content
resource annotator-client {
/// Annotate the provided content (extract and store features + metadata)
annotate: func(content: content);
}
/// A resource for storing key-value pairs local to the plugin (sandboxed and inaccessible elsewhere)
resource local-storage-client {
set: func(key: string, value: list<u8>);
get: func(key: string) -> option<list<u8>>;
delete: func(key: string);
}
/// A capability provider, which only returns capabilities that have been granted by the user
resource capability-provider {
// http: func() -> option<http-client>;
// kv: func() -> option<key-value-client>;
// sql: func() -> option<sql-client>;
// queue: func() -> option<queue-client>;
logger: func() -> option<logger>;
annotator: func() -> option<annotator-client>;
local-storage: func() -> option<local-storage-client>;
}
/// A type used to limit the scope in which granted capabilities can be used.
/// Used primarily to filter events before more expensive processing and overhead
/// (such as instantiating and executing WASM components), but can also be used to limit
/// the context in which certain capabilities (like local storage) can be used.
///
/// Capability scopes can be configured by the user and may not necessarily match
/// the requested scope from the plugin.
record capability-scope {
/// A CEL expression, evaluating to `true` or `false` (whether or not a given capability should be granted), matching against the context provided to the capability,
/// determining whether the capability applies.
///
/// See [CelRequest], [CelResponse], and [CelContent] for the context available to each expression.
///
/// ```rs
/// fn evaluate(request: CelRequest) -> bool { request.path() == "/example" } // for request events
/// fn evaluate(response: CelResponse, request: CelRequest) -> bool { response.status() == 200 && request.path() == "/example" } // for response events
/// fn evaluate(content: CelContent) -> bool { content.content_type() == "text/html" } // for inbound-content events
/// ```
expression: string,
}
/// The different kinds of events that can be handled by plugins
variant event-kind {
/// The associated capability determines which connections should be intercepted by the proxy
///
/// If you do not request this capability, you are not guaranteed to receive any further
/// request/response events related to connections you wish to handle.
connect,
// The associated capability determines which HTTP requests should be handled by the plugin
request,
// The associated capability determines which HTTP responses should be handled by the plugin
response,
// The associated capability determines which inbound content should be handled by the plugin
inbound-content,
}
/// The different kinds of capabilities that can be requested by plugins
variant capability-kind {
/// A capability to handle specific events
handle-event(event-kind),
/// A capability to log messages
logger,
/// A capability to annotate content (extract and store features + metadata)
annotator,
/// A capability to store local key-value pairs
local-storage,
}
/// A capability requested by the plugin
record capability {
/// The kind of capability being requested
kind: capability-kind,
/// The scope in which the capability applies (may be modified by the user)
scope: capability-scope,
}
type keyed-values = tuple<string, list<string>>;
type request-query = keyed-values;
type request-header = keyed-values;
/// The context of an HTTP request (essentially, everything except the body)
record request-context {
scheme: string,
host: string,
path: string,
query: list<request-query>,
method: string,
headers: list<request-header>,
}
/// A response along with its associated request context
record contextual-response {
/// The HTTP response
response: response,
// TODO: turn request-context into a resource to avoid copying and modifications
request: request-context,
}
/// The different types of events that can be handled (and returned) by plugins
variant event {
request(request),
response(contextual-response),
inbound-content(content),
}
/// A work-in-progress resource representing abstract byte stream content
/// Primarily exists to hide compression of the underlying body,
/// providing a simple interface for plugins to interact with
/// without having to worry about compression details
/// (which could be wasteful or conflicting in the case of serial plugin execution).
///
/// Still requires handling content encodings (UTF-8, UTF-16, etc) manually.
resource content {
/// Returns the content as a stream of bytes
body: func() -> stream<u8>;
/// Returns the content type of the content, ex: "text/html; charset=utf-8"
content-type: func() -> string;
/// Replace the content body with the provided stream of bytes
set-body: func(content: stream<u8>);
}
}
interface witm-plugin {
use capabilities.{capability, capability-provider, event};
/// A tag representing a key-value pair of metadata
record tag {
key: string,
value: string,
}
/// A manifest describing the plugin, any requested capabilities, and metadata.
record plugin-manifest {
/// The name of the plugin
name: string,
/// The plugin author's namespace (use for scoping)
namespace: string,
/// The publishing plugin author
author: string,
/// The plugin version
version: string,
/// A short description of the plugin
description: string,
/// The plugin license
license: string,
/// The plugin homepage URL
url: string,
/// The plugin author's public key (for verifying the plugin)
publickey: list<u8>,
/// The capabilities requested by the plugin
capabilities: list<capability>,
/// Additional plugin metadata
metadata: list<tag>,
}
/// A function called by the host to retrieve the plugins manifest.
///
/// Used by the host to fetch (and verify) all necessary information about the plugin,
/// including requested capabilities, before instantiating and executing it.
///
/// The public key will be used by the host to verify the WASM components signature,
/// ensuring the plugin has not been tampered with.
///
/// If the manifest is invalid or cannot be verified, the host will not load the plugin.
manifest: func() -> plugin-manifest;
/// A function called by the host to handle events granted by the plugin's given capabilities.
///
/// The capability provider can be used to access additional host capabilities (for example, logging or local storage) within the plugin.
/// Because requested capabilities may not necessarily be granted, the plugin is responsible for checking and handling whether a capability they require is available.
///
/// The handler may return the event as is, perform modifications, return a new event entirely (so long as the event output is valid given the input),
/// or return `None` to abandon any further event processing (likely resulting in an HTTP error from the perspective of the host).
///
/// * [Request] events must return either [Request] or [Response] events.
/// * [Response] events must return [Response] events.
/// * [InboundContent] events must return [InboundContent] events.
handle: func(ev: event, cp: capability-provider) -> option<event>;
}
world plugin {
import capabilities;
export witm-plugin;
}