console
v0.2.0Web console for iii — bundles the React UI and proxies the engine WebSocket on a single port.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
full markdown
/workers/console.md?version=0.2.0. paste it into an llm prompt or pipe it through curl from a worker.install
configuration
- http_port: 3113dependencies
readme
Why console
- One port, one binary. The React UI is baked into the executable with
rust-embed, and the engine WebSocket is reverse-proxied at/wson the same origin. No CORS preflight, no side-car static server, nodist/directory to deploy. Seesrc/assets.rsandsrc/proxy.rs. - Live engine, live UI. Functions, models, traces, and chat sessions all stream over a single WebSocket via the iii browser SDK. Mention any registered function with
@, switch models on the fly, and watch the trace appear in the panel next to you. Seeweb/src/lib/iii-client.ts. - Built for production. SIGINT and SIGTERM graceful shutdown (so
docker stopandkubectl deleteactually drain), URL credentials redacted from logs, immutable cache headers on content-hashed assets, and a fully self-contained binary with no runtime filesystem deps. Seesrc/main.rs.
Features
Chat
A purpose-built agentic chat UI on top of Lexical. Lives in web/src/components/chat/.
- Three modes —
plan,ask, andagenttoggle right in the composer - Live model picker — provider-grouped from
models::list; static fallback (OpenAI, Anthropic, Google) when the catalog is unreachable @-mentions — fuzzy-search every function registered against the engine/compactslash command — collapses conversation history viacontext-compaction::compact_session- Attachments — multi-file picker with text/image previews
- Function calls — running / pending / error cards, consecutive calls grouped, with approve/deny gating for pending approvals (
approval::resolve) - Streaming — abortable mid-flight; thinking shimmer; collapsible thought messages
- Markdown — GFM, code blocks with prism-react-renderer, syntax-highlighted JSON inputs and outputs
- Conversation sidebar — create, inline rename, delete, auto-title from the first message
- Context-usage meter — token estimate with warn / danger thresholds and a
/compactnudge - Session ID — copyable, deep-links every conversation into the trace explorer via
iii.session.id - Persistence — conversations, active id, last model, sidebar state — all in
localStorage
Traces
Full-fledged OpenTelemetry explorer over engine::traces::* and engine::logs::list. Lives in web/src/pages/Traces/.
- Four visualizations — waterfall (virtualized for huge traces), flame graph, service map, and a
dagre-laid-out flow view via@xyflow/react - Rich filtering — status (ok / error / unset), time presets, sort, min/max duration, service, operation, arbitrary attribute key/value pairs, debounced free-text search
- Group by — none, message, session, function — server-side aggregation with lazy per-group span trees
- Span detail tabs — info, attributes, events, errors, OTel logs, context (baggage), links
- Live polling — pause / resume, hover-deferred refreshes, 25 / 50 / 100 page sizes
- Resizable panels — drag to resize, double-click to reset
- Critical-path breadcrumb with cross-trace parent jump
Live catalogs
The composer's @-mentions and the model picker pull from the engine in real time.
engine::functions::list— TTL-cached function list (VITE_FUNCTIONS_LIST_CACHE_MS, default 10s) →web/src/lib/functions-catalog.tsmodels::list— provider-grouped model catalog →web/src/lib/models-catalog.ts
Theming
Light and dark themes via data-theme + CSS custom properties. Persisted to localStorage with an inline init script in index.html to prevent flash-of-wrong-theme on first paint.
Worker SDK surface
console registers a single function against the engine for health probes and iii worker info smoke tests:
| Function | Input | Output |
|---|---|---|
console::status |
{} |
{ http_port, engine_url, version } |
Defined in src/functions/status.rs.
Install
iii worker add consoleThis fetches the prebuilt binary, writes a console: block into ~/.iii/config.yaml, and the engine launches the worker on the next iii start.
Quickstart
iii start # engine on ws://127.0.0.1:49134
console --http-port 3113 # UI + /ws proxy on :3113
open http://127.0.0.1:3113The browser hits / for the SPA shell and upgrades /ws to the engine WebSocket — one origin, no CORS, no API base URL to configure.
Programmatic check from the SDK
use iii_sdk::{register_worker, InitOptions, TriggerRequest};
use serde_json::json;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let iii = register_worker("ws://localhost:49134", InitOptions::default());
let result = iii.trigger(TriggerRequest {
function_id: "console::status".into(),
payload: json!({}),
action: None,
timeout_ms: Some(5_000),
}).await?;
println!("{result:#?}");
Ok(())
}Returns { http_port, engine_url, version } — useful for liveness and readiness probes.
Architecture
flowchart LR
Browser["Browser SPA<br/>(iii-browser-sdk)"] -->|"HTTP GET /"| Console
Browser -->|"WS /ws"| Console
Console["console binary<br/>(axum + rust-embed)"] -->|"WebSocket"| Engine["iii engine<br/>:49134"]
Console -. "registers console::status" .-> Engineconsole is a thin HTTP server with exactly two jobs: serve the embedded SPA bundle (with appropriate cache headers) and transparently proxy /ws to the iii engine. The browser only ever talks to one origin.
Configuration
config.yaml
http_port: 3113 # port the UI + /ws are served on (default: 3113)| Key | Default | Description |
|---|---|---|
http_port |
3113 |
TCP port the worker binds for /, /assets/*, and /ws |
CLI flags
| Flag | Default | Description |
|---|---|---|
--config |
./config.yaml |
Path to the YAML config |
--url |
ws://127.0.0.1:49134 |
iii engine WebSocket URL (DEFAULT_ENGINE_URL in src/config.rs) |
--http-port |
from config | Overrides http_port from the config file |
--manifest |
— | Print the publish manifest as JSON and exit (used by the registry pipeline) |
Routes
| Path | Behavior |
|---|---|
GET / |
Embedded index.html (SPA shell, hash-routed client-side). Cache-Control: no-cache, must-revalidate |
GET /assets/* |
Embedded JS / CSS, content-hashed filenames. Cache-Control: public, max-age=31536000, immutable |
GET /ws (Upgrade) |
WebSocket upgrade; transparent proxy to engine_url |
| anything else | 404 Not Found |
The SPA bundle is embedded into the binary at compile time via rust-embed — the released console has no separate dist/ directory, no side-car asset server, and no runtime filesystem dependency for the UI.
Tech stack
| Layer | Choice |
|---|---|
| Web server | axum 0.7, tokio, tokio-tungstenite |
| Asset embedding | rust-embed 8, mime_guess |
| Worker SDK | iii-sdk 0.12 |
| UI framework | React 19, Vite 8, TypeScript 6 |
| Styling | Tailwind CSS v4, Radix UI, class-variance-authority, lucide-react |
| Editor | Lexical 0.44 |
| Data fetching | TanStack Query 5 |
| Trace graphs | @xyflow/react 12 + dagre, TanStack Virtual |
| Markdown | react-markdown + remark-gfm, prism-react-renderer |
| Browser SDK | iii-browser-sdk 0.12 |
Build from source (contributors only)
cargo build runs pnpm install --frozen-lockfile && pnpm build inside web/ automatically when the web/dist/ bundle is missing or stale (Node + pnpm must be on PATH). To pre-build the bundle once and skip the embedded-asset rebuild loop:
cd web && pnpm install && pnpm build && cd ..
cargo build --releaseEscape hatches (see build.rs):
SKIP_WEB_BUILD=1— skip the JS build step entirely; the existingweb/dist/(if any) is embedded as-is. Useful in CI when the bundle was built in a previous stage.PNPM=/path/to/pnpm— override pnpm discovery.
Run the test suite:
cargo test # unit + manifest + e2e (e2e self-skips if `iii` isn't on PATH)
cd web && pnpm test # vitest
cd web && pnpm typecheck && pnpm lintLicense
Apache 2.0 — see LICENSE.
api reference (json)
{
"functions": [
{
"description": "Return the console worker's runtime knobs: http_port, engine_url, and version.",
"metadata": {},
"name": "console::status",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StatusInput",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"engine_url": {
"description": "iii engine WebSocket URL the worker is proxying to.",
"type": "string"
},
"http_port": {
"description": "TCP port the worker is serving the UI and `/ws` on.",
"format": "uint16",
"minimum": 0,
"type": "integer"
},
"version": {
"description": "Worker version (matches `Cargo.toml`).",
"type": "string"
}
},
"required": [
"engine_url",
"http_port",
"version"
],
"title": "StatusOutput",
"type": "object"
}
}
],
"triggers": []
}