Skip to content

Render A2UI in gpui

Two crates: eui-core (the core) and eui-gpui (the painter and host runtime). The painter depends on gpui and gpui-component as git dependencies, which crates.io refuses, so an app takes both crates from the release tag. Your app links the same gpui as the painter: the same gpui-component rev, zed locked at the SDK’s rev and the ztracing patch, all copied from one tag (the gpui revision policy). gpui needs Rust 1.96: pin it in rust-toolchain.toml.

[dependencies]
eui-core = { git = "https://github.com/Niach/exponential", tag = "eui-v0.12.0" }
eui-gpui = { git = "https://github.com/Niach/exponential", tag = "eui-v0.12.0" }
gpui = { version = "0.2.2", git = "https://github.com/zed-industries/zed" }
gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "da4f93696dc2b2b4d91bcc42412b9053a3d24de8" }
[patch."https://github.com/zed-industries/zed"]
ztracing = { git = "https://github.com/Niach/exponential", tag = "eui-v0.12.0" }
ztracing_macro = { git = "https://github.com/Niach/exponential", tag = "eui-v0.12.0" }
Terminal window
cargo update gpui --precise cc053a4a6fa2fd0e8793201ed9099466af1be0b1

The guide’s manifest uses path dependencies in place of the git ones:

guides/gpui/Cargo.toml
[package]
name = "exponential-ui-guide-gpui"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
publish = false
# A third-party app: its own workspace, outside apps/desktop's build graph.
[workspace]
[dependencies]
# The SDK. The painter is not on crates.io (gpui + gpui-component are git
# dependencies) and wants the core at its own revision, so an app takes both
# from the release tag on github.com/Niach/exponential (`tag = "eui-v0.12.0"`).
# Path dependencies stand in for it here. The gpui pins below follow the
# painter README's "gpui revisions" (gated by check-gpui-revs.ts).
eui-core = { path = "../../../../apps/desktop/crates/eui-core" }
eui-gpui = { path = "../../../../apps/desktop/crates/eui-gpui" }
# gpui + gpui-component at the SDK's revisions (Cargo.lock pins zed's commit).
gpui = { version = "0.2.2", git = "https://github.com/zed-industries/zed" }
gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland", "runtime_shaders"] }
gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "da4f93696dc2b2b4d91bcc42412b9053a3d24de8" }
gpui-component-assets = { git = "https://github.com/longbridge/gpui-component", rev = "da4f93696dc2b2b4d91bcc42412b9053a3d24de8" }
# zed's ztracing is GPL-3.0-or-later; any app linking gpui at this revision
# swaps in the SDK's no-op shims.
[patch."https://github.com/zed-industries/zed"]
ztracing = { path = "../../../../apps/desktop/crates/ztracing" }
ztracing_macro = { path = "../../../../apps/desktop/crates/ztracing_macro" }
[profile.dev.package."*"]
debug = false

The host is a gpui entity. host.surface(id) returns the surface’s view once the agent has created it; it is full width and as tall as its content, so wrap it in your own scroller.

guides/gpui/src/main.rs
use eui_core::theme::Mode;
use eui_gpui::runtime::{ExponentialHost, HostOptions};
use eui_gpui::transport::{HttpTransportOptions, JsonlStreamTransport};
use gpui::{div, prelude::*, px, size, App, Bounds, Context, Entity, Window, WindowBounds, WindowOptions};
struct Main {
host: Entity<ExponentialHost>,
}
impl Render for Main {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// The surface the agent creates, once it exists; you own the scroller.
div().id("main").size_full().overflow_y_scroll().p(px(16.0)).children(self.host.read(cx).surface("main"))
}
}
fn main() {
gpui_platform::application().with_assets(gpui_component_assets::Assets).run(|cx: &mut App| {
gpui_component::init(cx);
// One host per app: A2UI messages in over a transport, actions back out.
let transport = JsonlStreamTransport::new(HttpTransportOptions::new("http://localhost:4300/a2ui.jsonl").post_url("http://localhost:4300/action"));
let options = HostOptions { transport: Some(Box::new(transport)), theme: eui_core::themes::builtin_theme("exponential"), mode: Mode::Dark, ..Default::default() };
let host = cx.new(|cx| ExponentialHost::new(options, cx));
host.update(cx, |host, cx| host.connect(cx));
let window = WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds::centered(None, size(px(540.0), px(640.0)), cx))), ..Default::default() };
cx.open_window(window, |_, cx| cx.new(|cx| {
cx.observe(&host, |_, _, cx| cx.notify()).detach();
Main { host: host.clone() }
}))
.expect("a window");
});
}

Underneath, a SurfaceView takes a HostPlugin implementation: catalog icon names to SVG asset paths, font families, actions and input. Register the theme’s fonts with the text system before the first frame, and extension natives with register_painter. Details in the gpui painter’s README.

bun packages/exponential-ui/guides/check.ts gpui builds guides/gpui in an empty directory against the path crates.