Tracks the platform WIT ABI rename. Plugin world package becomes chain:studio-plugin and host imports chain:platform/host. Crate name stays fai-plugin-sdk (vendor namespace) for now. Signed-off-by: flemming-it <sf@flemming.it>
187 lines
7.4 KiB
Text
187 lines
7.4 KiB
Text
// =============================================================
|
|
// DRAFT. Studio plugin contract — NOT yet frozen.
|
|
// =============================================================
|
|
//
|
|
// This file is the v0-draft WIT contract for the Studio plugin
|
|
// subsystem described in `docs/advanced/studio-plugins.md`.
|
|
//
|
|
// Status: draft. Subject to break-change until the first
|
|
// in-tree plugin (studio.theme.solarized) round-trips through
|
|
// the Studio plugin-host implementation. Once it does, this
|
|
// file moves to v0.1 and follows the same freeze-rules as
|
|
// `world.wit`.
|
|
//
|
|
// Why a separate WIT file:
|
|
//
|
|
// - `world.wit` defines the host-to-module wire for *flow
|
|
// modules* (text.extract, llm.chat etc.). Those modules
|
|
// speak in payloads + flow capabilities — concepts the
|
|
// plugin layer doesn't need.
|
|
// - Studio plugins are also WASM modules sandboxed by the
|
|
// hub, but their host is the Studio GUI, not the flow
|
|
// engine. They want a different surface: theme tokens,
|
|
// translation calls, output-view widget specs.
|
|
//
|
|
// Plugins target the `studio-plugin` world below. The host
|
|
// (Studio) instantiates the component, calls the relevant
|
|
// hook function per declared `studio_extension.hook` in
|
|
// module.yaml, and renders / wires the result.
|
|
|
|
package chain:studio-plugin@0.1.0;
|
|
|
|
// =============================================================
|
|
// Shared types
|
|
// =============================================================
|
|
|
|
interface plugin-types {
|
|
/// What kind of hook the plugin satisfies. The hub reads
|
|
/// this from `module.yaml#studio_extension.hook`. A single
|
|
/// plugin module can declare multiple hooks (e.g. a theme
|
|
/// plugin that also provides a translation back-end) by
|
|
/// exporting more than one of the hook interfaces; the hub
|
|
/// matches each exported hook against the declared hook
|
|
/// list.
|
|
enum hook-kind {
|
|
translate,
|
|
output-view,
|
|
theme,
|
|
}
|
|
|
|
/// Studio renders in a Material 3 color-scheme. Plugins
|
|
/// produce one for each brightness; both are required so
|
|
/// the operator's light/dark toggle keeps working.
|
|
record color-scheme {
|
|
/// "light" or "dark". The plugin returns one scheme per
|
|
/// brightness via separate `theme-for` calls.
|
|
brightness: string,
|
|
/// 32-bit ARGB packed colors. Order matches Material 3:
|
|
/// primary, on-primary, secondary, on-secondary,
|
|
/// tertiary, on-tertiary, error, on-error, surface,
|
|
/// on-surface, surface-variant, on-surface-variant,
|
|
/// outline, outline-variant.
|
|
/// Studio fills any missing slot from its built-in
|
|
/// fallback before applying.
|
|
tokens: list<u32>,
|
|
}
|
|
|
|
/// Describes how to render a single flow output. Returned
|
|
/// from `output-view.render`. Studio reads `kind` and
|
|
/// builds the matching widget tree.
|
|
variant rendered-output {
|
|
/// Plain text — Studio shows a SelectableText.
|
|
text(string),
|
|
/// Markdown — Studio shows a Markdown widget with the
|
|
/// shared FaiTheme.markdownStyle.
|
|
markdown(string),
|
|
/// Already-pretty-printed JSON — Studio shows it in a
|
|
/// code-block container.
|
|
pretty-json(string),
|
|
/// PNG / JPEG image data, ready to feed Image.memory.
|
|
/// MIME stays out because Flutter sniffs it; the plugin
|
|
/// is responsible for delivering a real image format.
|
|
image(list<u8>),
|
|
/// Bag-of-fields the plugin couldn't render but wants
|
|
/// passed through. Studio falls back to its built-in
|
|
/// renderer. `reason` is operator-visible — explain why
|
|
/// the plugin declined.
|
|
passthrough(string),
|
|
}
|
|
|
|
/// Generic error a plugin can return without panicking.
|
|
variant plugin-error {
|
|
/// The plugin understands the request but refused (e.g.
|
|
/// translate called with from==to). Studio falls back
|
|
/// to the built-in renderer.
|
|
declined(string),
|
|
/// The plugin is mis-configured (e.g. translate plugin
|
|
/// pointed at an unreachable Ollama endpoint). Operator
|
|
/// sees the message in the friendly-error mapper.
|
|
misconfigured(string),
|
|
/// Generic catch-all. Avoid when one of the above fits.
|
|
internal(string),
|
|
}
|
|
}
|
|
|
|
// =============================================================
|
|
// Hook interfaces — each is optional. A plugin exports the
|
|
// ones it satisfies; the hub only calls those.
|
|
// =============================================================
|
|
|
|
/// `translate` hook. Used by Studio to translate server-supplied
|
|
/// English text (MCP tool descriptions, n8n endpoint names, LLM
|
|
/// output) into the active locale. The honest `[EN]` badge stays
|
|
/// the fallback when no translate plugin is installed or every
|
|
/// installed plugin declines.
|
|
interface translate {
|
|
use plugin-types.{plugin-error};
|
|
|
|
/// Translate `text` from `from-locale` to `to-locale`.
|
|
/// BCP-47 codes (e.g. "en", "de", "en-US"). Empty
|
|
/// `from-locale` lets the plugin auto-detect; an empty
|
|
/// `to-locale` is an error — the host always knows the
|
|
/// target locale.
|
|
translate: func(
|
|
text: string,
|
|
from-locale: string,
|
|
to-locale: string,
|
|
) -> result<string, plugin-error>;
|
|
}
|
|
|
|
/// `output-view` hook. Used by Studio's FaiFlowOutput widget
|
|
/// to ask a plugin "do you want to render this payload?". The
|
|
/// plugin returns either a rendered-output variant or
|
|
/// `passthrough` so Studio falls back. Plugins are queried in
|
|
/// operator-configured priority order; the first non-
|
|
/// `passthrough` answer wins.
|
|
interface output-view {
|
|
use plugin-types.{rendered-output, plugin-error};
|
|
|
|
/// `mime-type` is the value from the bytes/file payload
|
|
/// (e.g. "application/pdf", "image/png"). Empty when the
|
|
/// payload kind doesn't carry one (text / json).
|
|
/// `data` is the payload's raw content. Text/json payloads
|
|
/// arrive UTF-8 encoded; bytes/file payloads stay raw.
|
|
/// The plugin decides whether to handle the type.
|
|
render: func(
|
|
mime-type: string,
|
|
data: list<u8>,
|
|
) -> result<rendered-output, plugin-error>;
|
|
}
|
|
|
|
/// `theme` hook. Used by Studio's theme selector at startup.
|
|
/// The plugin returns one color-scheme per brightness; Studio
|
|
/// merges with its built-in fallback for unspecified tokens.
|
|
interface theme {
|
|
use plugin-types.{color-scheme, plugin-error};
|
|
|
|
/// `brightness` is "light" or "dark". The host calls the
|
|
/// function twice at theme-apply time (once per brightness)
|
|
/// so plugins can implement both in one place.
|
|
theme-for: func(brightness: string) -> result<color-scheme, plugin-error>;
|
|
}
|
|
|
|
// =============================================================
|
|
// World — what a plugin component looks like to the host.
|
|
// =============================================================
|
|
|
|
/// A Studio plugin component. Plugins target this world via
|
|
/// `cargo build --target wasm32-wasip2` after declaring it in
|
|
/// their Cargo.toml's `[package.metadata.component]`.
|
|
///
|
|
/// Plugins export only the hooks they implement. The hub
|
|
/// probes which exports exist via component-introspection
|
|
/// before issuing the matching call — so a plugin that only
|
|
/// declares `hook: theme` in module.yaml needs to export only
|
|
/// the `theme` interface, not the others.
|
|
world studio-plugin {
|
|
/// Plugins import the same `host` interface as flow modules,
|
|
/// so they can emit structured logs without a separate
|
|
/// bridge. Logs flow into the hub's event log.
|
|
import chain:platform/host@1.0.0;
|
|
|
|
/// Hook exports. Each is optional; the hub introspects the
|
|
/// component to learn which the plugin supports.
|
|
export translate;
|
|
export output-view;
|
|
export theme;
|
|
}
|