ExamplesDesktop Plugin Samples

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/ping to verify it can talk back to the host
  • Permission check: approval/check demonstrates requesting user approval through the host
  • Clean shutdown: responds to plugin/shutdown then 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:

  1. Receives JSON-RPC requests from the desktop host
  2. Translates them into Codex engine commands
  3. Forwards engine notifications back to the host as events
  4. 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:

MethodDescription
plugin/healthHealth check with version info
status/readFull runtime status (version, auth, CLI adapter)
session/startStart or resume an agent session for a lab
turn/startSend a user prompt to the agent
turn/interruptInterrupt an in-progress turn
thread/readRead the conversation history for a thread
approval/resolveResolve a pending approval request
auth/connect_chatgptStart ChatGPT OAuth flow
auth/set_api_keySet an API key for authentication
auth/logoutSign out
plugin/shutdownGraceful 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
  1. Copy the reference echo plugin.json and update id, name, capabilities
  2. Implement your custom methods in main.py
  3. Package: python scripts/build_plugin_package.py --manifest my-plugin/plugin.json --payload-dir my-plugin/ --output my-plugin.bsiplugin
  4. Install from Settings > Plugins > Install from file

Minimal Agent Provider

If you’re building an AI agent plugin:

  1. Set "kind": "agent_provider" in your manifest
  2. Add UI contributions for sidebar.item, page.main, and optionally lab.sidepanel
  3. Implement the agent session/turn methods (see the Codex plugin for the full protocol)
  4. Use codex/event notifications 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.