chain-plugin-sdk-rust/README.md
flemming-it 3753c6bc37 feat: v0.1.0 — Studio Plugin SDK
First release of the SDK that Studio plugins depend on.

What v0.1 ships:

- WIT contract under wit/ (studio-plugin.wit +
  deps/platform/world.wit, both verbatim from
  fai/platform/wit/).
- wit-bindgen 0.36 generate! call with pub_export_macro
  enabled so the emitted __export_studio_plugin_impl! macro
  is reachable from downstream plugin crates.
- export_plugin!(MyType) convenience macro that wraps the
  bindgen-generated impl macro.
- README documenting usage + v0.1 limits (single-world ->
  all-three-hooks-required, no proc-macro yet, edition
  2021 pinned by wit-bindgen output).

What v0.1 doesn't yet do:

- Plugins still declare all three Guest impls (theme,
  translate, output-view). v0.2 splits the WIT into one
  world per hook.
- No #[plugin(...)] proc-macro. v0.2 wraps generate! +
  Guest impl generation into a single attribute.
- Host-side: the hub doesn't yet load studio-plugin-world
  components or expose InvokePlugin RPC — that's the next
  push.

Validated against the in-tree studio-theme-solarized
plugin: 22 KiB stripped .wasm artefact, valid Component
Model, passes wasm-tools component wit inspection.

Signed-off-by: flemming-it <sf@flemming.it>
2026-05-25 14:31:08 +02:00

3.3 KiB

fai-studio-plugin-sdk

Rust SDK for writing F∆I Studio plugins.

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-modules 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/studio-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-studio-plugin-sdk = { git = "https://git.flemming.ai/fai/studio-plugin-sdk", branch = "main" }
// src/lib.rs of your plugin
use fai_studio_plugin_sdk::{bindings, export_plugin};

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

struct MyPlugin;

impl bindings::exports::fai::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::fai::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::fai::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, fai:studio-plugin@0.1.0
└── deps/
    └── platform/
        └── world.wit       # fai: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.