Reference Examples
The Biosimulant desktop repository includes two reference plugins you can use as starting points. Both live in plugins/ and demonstrate different plugin kinds.
Reference Echo Plugin (Tool Provider)
A minimal tool_provider that verifies the JSON-RPC protocol works end-to-end. It implements health checks, echo, and host communication.
Source: plugins/reference-echo/
Manifest
{
"id": "biosimulant.reference-echo",
"name": "Reference Echo Plugin",
"description": "Reference JSON-RPC sidecar plugin used to verify runtime installation and invocation.",
"version": "0.1.0",
"kind": "tool_provider",
"entrypoint": "bin/reference-echo",
"platforms": ["darwin", "linux"],
"architectures": ["aarch64", "x86_64"],
"permissions": [
{ "key": "workspace.read", "title": "Read workspace" },
{ "key": "events.emit", "title": "Emit plugin events" }
],
"capabilities": ["plugin.health", "echo.repeat", "approval.check"],
"ui_contributions": [],
"auth_modes": [],
"min_desktop_version": "0.1.0",
"update_channel": "stable"
}Implementation
The full implementation is ~100 lines of Python. Here’s the core loop:
#!/usr/bin/env python3
import json
import os
import sys
def send(payload):
sys.stdout.write(json.dumps(payload) + "\n")
sys.stdout.flush()
def recv():
line = sys.stdin.readline()
if not line:
raise EOFError()
return json.loads(line)
def host_call(method, params=None):
request_id = f"host-{os.getpid()}-{method}"
send({
"jsonrpc": "2.0",
"id": request_id,
"method": method,
"params": params or {},
})
while True:
message = recv()
if str(message.get("id")) == request_id:
if "error" in message:
raise RuntimeError(message["error"]["message"])
return message.get("result", {})
def main():
while True:
try:
message = recv()
except EOFError:
return 0
request_id = message.get("id")
method = message.get("method")
params = message.get("params", {})
if method == "plugin/health":
send({
"jsonrpc": "2.0",
"id": request_id,
"result": {
"ok": True,
"plugin_id": "biosimulant.reference-echo",
"version": "0.1.0",
},
})
elif method == "echo/repeat":
host = host_call("host/ping", {"source": "reference-echo"})
send({
"jsonrpc": "2.0",
"id": request_id,
"result": {"echo": params, "host": host},
})
elif method == "approval/check":
approval = host_call("host/request_approval", {"permission": "workspace.read"})
send({
"jsonrpc": "2.0",
"id": request_id,
"result": approval,
})
elif method == "plugin/shutdown":
send({"jsonrpc": "2.0", "id": request_id, "result": {"ok": True}})
return 0
else:
send({
"jsonrpc": "2.0",
"id": request_id,
"error": {"message": f"unsupported method: {method}"},
})
if __name__ == "__main__":
raise SystemExit(main())Key takeaways:
- Minimal setup: just stdin/stdout, no dependencies beyond Python stdlib
- Bidirectional: the plugin calls
host/pingto verify it can talk back to the host - Permission check:
approval/checkdemonstrates requesting user approval through the host - Clean shutdown: responds to
plugin/shutdownthen exits
Codex Plugin (Agent Provider)
A full agent_provider plugin that wraps the Codex AI agent. It manages an embedded subprocess, handles multi-threaded communication, and implements agent sessions, turns, and approval flows.
Source: plugins/openai-codex/
Manifest
{
"id": "openai.codex",
"name": "Codex Workspace Agent",
"description": "Codex packaged as a runtime-installable Biosimulant plugin.",
"version": "0.1.0",
"kind": "agent_provider",
"entrypoint": "main.py",
"platforms": ["darwin", "linux", "windows"],
"architectures": ["aarch64", "x86_64"],
"permissions": [
{ "key": "workspace.read" },
{ "key": "workspace.mutate" },
{ "key": "command.execution" }
],
"capabilities": ["agent.sessions", "agent.turns", "agent.approvals"],
"ui_contributions": [
{ "slot": "sidebar.item", "icon": "wand-sparkles", "label": "Codex" },
{ "slot": "page.main", "component": "codex-page" },
{ "slot": "settings.section", "component": "codex-settings" },
{ "slot": "lab.sidepanel", "component": "codex-panel", "icon": "wand-sparkles", "label": "Codex" }
],
"auth_modes": ["chatgpt", "api_key"],
"min_desktop_version": "0.1.0",
"update_channel": "stable"
}Architecture
The Codex plugin is more complex because it manages its own embedded subprocess:
Desktop Host ◄──stdin/stdout──► Codex Plugin (main.py)
│
│ subprocess
▼
Codex Engine Binary
(codex app-server --listen stdio://)The plugin’s main.py acts as a bridge:
- Receives JSON-RPC requests from the desktop host
- Translates them into Codex engine commands
- Forwards engine notifications back to the host as events
- Handles approval flows between the engine and host
Key Patterns
Thread-safe host bridge separates stdin reading from request processing to avoid deadlocks when making host calls during request handling.
Engine lifecycle management locates the Codex binary across platforms, spawns it as a subprocess, and manages initialization.
Event forwarding: the engine emits notifications (turn started, item completed, agent message deltas) that the plugin forwards to the host as codex/event notifications. The desktop UI listens to these to update in real time.
Approval delegation: when the engine requests user approval (e.g., to execute a command), the plugin forwards it to the host via host/codex/approval_decision, which shows the approval UI to the user.
Agent Provider Methods
Agent provider plugins implement these JSON-RPC methods:
| Method | Description |
|---|---|
plugin/health | Health check with version info |
status/read | Full runtime status (version, auth, CLI adapter) |
session/start | Start or resume an agent session for a lab |
turn/start | Send a user prompt to the agent |
turn/interrupt | Interrupt an in-progress turn |
thread/read | Read the conversation history for a thread |
approval/resolve | Resolve a pending approval request |
auth/connect_chatgpt | Start ChatGPT OAuth flow |
auth/set_api_key | Set an API key for authentication |
auth/logout | Sign out |
plugin/shutdown | Graceful shutdown |
Building Your Own Plugin
Minimal Tool Provider
Start with the reference echo plugin and replace the custom methods:
my-plugin/
├── plugin.json
└── main.py- Copy the reference echo
plugin.jsonand updateid,name,capabilities - Implement your custom methods in
main.py - Package:
python scripts/build_plugin_package.py --manifest my-plugin/plugin.json --payload-dir my-plugin/ --output my-plugin.bsiplugin - Install from Settings > Plugins > Install from file
Minimal Agent Provider
If you’re building an AI agent plugin:
- Set
"kind": "agent_provider"in your manifest - Add UI contributions for
sidebar.item,page.main, and optionallylab.sidepanel - Implement the agent session/turn methods (see the Codex plugin for the full protocol)
- Use
codex/eventnotifications to stream results to the desktop UI
The desktop app renders a generic agent chat UI for all agent_provider plugins. You don’t need to build any frontend. Just implement the JSON-RPC protocol and the desktop app handles the rest.