F∆I Plugin SDK (Rust) — write Studio plugins
Find a file
flemming-it 0b60b71360 refactor: rename WIT contract fai:platform/fai:studio-plugin->chain:*
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>
2026-06-15 22:18:53 +02:00
src refactor: rename WIT contract fai:platform/fai:studio-plugin->chain:* 2026-06-15 22:18:53 +02:00
wit refactor: rename WIT contract fai:platform/fai:studio-plugin->chain:* 2026-06-15 22:18:53 +02:00
.gitignore feat: v0.1.0 — Studio Plugin SDK 2026-05-25 14:31:08 +02:00
Cargo.toml refactor: rename WIT contract fai:platform/fai:studio-plugin->chain:* 2026-06-15 22:18:53 +02:00
README.md refactor: rename WIT contract fai:platform/fai:studio-plugin->chain:* 2026-06-15 22:18:53 +02:00
rust-toolchain.toml feat: v0.1.0 — Studio Plugin SDK 2026-05-25 14:31:08 +02:00

fai-plugin-sdk (Rust)

Rust SDK for writing F∆I plugins (Studio extensions). One of three SDK families documented in fai/platform/docs/architecture/sdks.md:

  • fai-module-sdk (Rust) — building flow modules
  • fai-plugin-sdk (Rust) — building plugins ← this crate
  • fai-client-sdk (Dart) — building hub-talking clients

Each gets a language-suffix on its dir/repo so polyglot variants can be added later without renaming the existing ones (e.g. a future fai-plugin-sdk-go).

Status

v0.1 builds. Studio plugins targeting the studio-plugin world declared in wit/studio-plugin.wit can be compiled to wasm32-wasip2 against this crate today. The proof-of- contract plugin is studio-theme-solarized in the fai-plugins namespace.

What's NOT yet implemented:

  • Hub side: a Component-Model loader path for studio-plugin-world components + the InvokePlugin gRPC RPC that Studio talks to. Tracked in fai/platform/docs/advanced/plugin-sdk.md.
  • Studio side: plugin_host.dart that discovers installed studio.* capabilities and dispatches.
  • Per-hook worlds: today's v0.1 ships a single world (studio-plugin) that exports all three hooks (theme, translate, output-view). A plugin only caring about one hook still has to declare empty / declined impls for the other two. v0.2 splits into one world per hook.
  • #[plugin(...)] proc-macro: v0.1 ships a plain export_plugin! macro that wraps the wit-bindgen-emitted __export_studio_plugin_impl!. The proc-macro that generates the WIT bindings + the Guest impls in one pass arrives in v0.2.

Usage

# Cargo.toml of your plugin
[package]
name = "my_studio_plugin"
version = "0.1.0"
edition = "2021"   # match SDK edition

[lib]
crate-type = ["cdylib"]

[dependencies]
fai-plugin-sdk = { git = "https://git.flemming.ai/fai/plugin-sdk-rust", branch = "main" }
// src/lib.rs of your plugin
use fai_plugin_sdk::{bindings, export_plugin};

type ColorScheme = bindings::chain::studio_plugin::plugin_types::ColorScheme;
type PluginError = bindings::chain::studio_plugin::plugin_types::PluginError;
type RenderedOutput = bindings::chain::studio_plugin::plugin_types::RenderedOutput;

struct MyPlugin;

impl bindings::exports::chain::studio_plugin::theme::Guest for MyPlugin {
    fn theme_for(brightness: String) -> Result<ColorScheme, PluginError> {
        // Return a ColorScheme or PluginError::Declined.
    }
}

// v0.1: every plugin must declare all three hooks. Plugins
// that don't actually serve a hook return Declined.
impl bindings::exports::chain::studio_plugin::translate::Guest for MyPlugin {
    fn translate(_: String, _: String, _: String) -> Result<String, PluginError> {
        Err(PluginError::Declined("not a translate plugin".to_string()))
    }
}
impl bindings::exports::chain::studio_plugin::output_view::Guest for MyPlugin {
    fn render(_: String, _: Vec<u8>) -> Result<RenderedOutput, PluginError> {
        Err(PluginError::Declined("not a view plugin".to_string()))
    }
}

export_plugin!(MyPlugin);

Then:

cargo build --release --target wasm32-wasip2
fai pack . -o my-plugin-0.1.0.fai

WIT layout

wit/
├── studio-plugin.wit       # main world, chain:studio-plugin@0.1.0
└── deps/
    └── platform/
        └── world.wit       # chain:platform@1.0.0 (host imports)

Both files are verbatim copies of the authoritative versions in fai/platform/wit/. When the contract changes upstream, re-copy here in lock-step.

Why edition 2021

wit-bindgen 0.36 emits #[link_section] / unsafe-attribute syntax that the edition-2024 compiler rejects. Bump when moving to a wit-bindgen version with edition-2024-compatible codegen.