diff --git a/README.md b/README.md index f5d7b77..7af6b89 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Sandbox Agent solves three problems: - **Runs Inside Any Sandbox**: Lightweight static Rust binary. One curl command to install inside E2B, Daytona, Vercel Sandboxes, or Docker - **Server or SDK Mode**: Run as an HTTP server or embed with the TypeScript SDK - **OpenAPI Spec**: [Well documented](https://sandboxagent.dev/docs/api-reference) and easy to integrate from any language +- **OpenCode SDK & UI Support** *(Experimental)*: Connect OpenCode CLI, SDK, or web UI to control agents through familiar OpenCode tooling ## Architecture diff --git a/docs/docs.json b/docs/docs.json index 5e71ba9..6c74525 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -63,7 +63,7 @@ "group": "Reference", "pages": [ "cli", - "opencode-compatibility", + "opencode-sdk-ui-support", "inspector", "session-transcript-schema", "cors", diff --git a/docs/opencode-compatibility.mdx b/docs/opencode-compatibility.mdx deleted file mode 100644 index 563d8d6..0000000 --- a/docs/opencode-compatibility.mdx +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: "OpenCode Compatibility" -description: "OpenCode endpoint coverage for /opencode." ---- - -Sandbox Agent exposes OpenCode-compatible endpoints under `/opencode`. -Authentication matches `/v1`: if a token is configured, requests must include `Authorization: Bearer `. - -| Method | Path | Status | Notes | Tests | -|---|---|---|---|---| -| GET | /agent | Stubbed | Single stub agent entry. | E2E: openapi-coverage | -| DELETE | /auth/{providerID} | Stubbed | | E2E: openapi-coverage | -| PUT | /auth/{providerID} | Stubbed | | E2E: openapi-coverage | -| GET | /command | Stubbed | | E2E: openapi-coverage | -| GET | /config | Stubbed | Returns/echoes config payloads. | E2E: openapi-coverage | -| PATCH | /config | Stubbed | Returns/echoes config payloads. | E2E: openapi-coverage | -| GET | /config/providers | Stubbed | Returns/echoes config payloads. | E2E: openapi-coverage | -| GET | /event | SSE stub | Emits compat events for session/message/pty updates only. | E2E: openapi-coverage, events | -| GET | /experimental/resource | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| GET | /experimental/tool | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| GET | /experimental/tool/ids | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| DELETE | /experimental/worktree | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| GET | /experimental/worktree | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| POST | /experimental/worktree | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| POST | /experimental/worktree/reset | Stubbed | Experimental endpoints return empty stubs. | E2E: openapi-coverage | -| GET | /file | Stubbed | Returns empty lists/content; no filesystem access. | E2E: openapi-coverage | -| GET | /file/content | Stubbed | Returns empty lists/content; no filesystem access. | E2E: openapi-coverage | -| GET | /file/status | Stubbed | Returns empty lists/content; no filesystem access. | E2E: openapi-coverage | -| GET | /find | Stubbed | Returns empty results. | E2E: openapi-coverage | -| GET | /find/file | Stubbed | Returns empty results. | E2E: openapi-coverage | -| GET | /find/symbol | Stubbed | Returns empty results. | E2E: openapi-coverage | -| GET | /formatter | Stubbed | | E2E: openapi-coverage | -| GET | /global/config | Stubbed | | E2E: openapi-coverage | -| PATCH | /global/config | Stubbed | | E2E: openapi-coverage | -| POST | /global/dispose | Stubbed | | E2E: openapi-coverage | -| GET | /global/event | SSE stub | Wraps compat events in GlobalEvent; no external sources. | E2E: openapi-coverage, events | -| GET | /global/health | Stubbed | | E2E: openapi-coverage | -| POST | /instance/dispose | Stubbed | | E2E: openapi-coverage | -| POST | /log | Stubbed | | E2E: openapi-coverage | -| GET | /lsp | Stubbed | | E2E: openapi-coverage | -| GET | /mcp | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| POST | /mcp | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| DELETE | /mcp/{name}/auth | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| POST | /mcp/{name}/auth | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| POST | /mcp/{name}/auth/authenticate | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| POST | /mcp/{name}/auth/callback | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| POST | /mcp/{name}/connect | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| POST | /mcp/{name}/disconnect | Stubbed | Returns disabled/needs_auth stubs. | E2E: openapi-coverage | -| GET | /path | Derived stub | | E2E: openapi-coverage | -| GET | /permission | Stubbed | | E2E: openapi-coverage, permissions | -| POST | /permission/{requestID}/reply | Stubbed | | E2E: openapi-coverage, permissions | -| GET | /project | Derived stub | | E2E: openapi-coverage | -| PATCH | /project/{projectID} | Derived stub | | E2E: openapi-coverage | -| GET | /project/current | Derived stub | | E2E: openapi-coverage | -| GET | /provider | Stubbed | Returns empty provider metadata. | E2E: openapi-coverage | -| POST | /provider/{providerID}/oauth/authorize | Stubbed | Returns empty provider metadata. | E2E: openapi-coverage | -| POST | /provider/{providerID}/oauth/callback | Stubbed | Returns empty provider metadata. | E2E: openapi-coverage | -| GET | /provider/auth | Stubbed | Returns empty provider metadata. | E2E: openapi-coverage | -| GET | /pty | Stateful stub | In-memory PTY records; no real PTY process. | E2E: openapi-coverage | -| POST | /pty | Stateful stub | In-memory PTY records; no real PTY process. | E2E: openapi-coverage | -| DELETE | /pty/{ptyID} | Stateful stub | In-memory PTY records; no real PTY process. | E2E: openapi-coverage | -| GET | /pty/{ptyID} | Stateful stub | In-memory PTY records; no real PTY process. | E2E: openapi-coverage | -| PUT | /pty/{ptyID} | Stateful stub | In-memory PTY records; no real PTY process. | E2E: openapi-coverage | -| GET | /pty/{ptyID}/connect | Stateful stub | In-memory PTY records; no real PTY process. | E2E: openapi-coverage | -| GET | /question | Stubbed | | E2E: openapi-coverage, permissions | -| POST | /question/{requestID}/reject | Stubbed | | E2E: openapi-coverage, permissions | -| POST | /question/{requestID}/reply | Stubbed | | E2E: openapi-coverage, permissions | -| GET | /session | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, session | -| POST | /session | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, session | -| DELETE | /session/{sessionID} | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, session | -| GET | /session/{sessionID} | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, session | -| PATCH | /session/{sessionID} | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, session | -| POST | /session/{sessionID}/abort | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, messaging | -| GET | /session/{sessionID}/children | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/command | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| GET | /session/{sessionID}/diff | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/fork | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/init | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| GET | /session/{sessionID}/message | Stateful stub | In-memory messages; assistant replies are stubbed. | E2E: openapi-coverage, messaging, events | -| POST | /session/{sessionID}/message | Stateful stub | In-memory messages; assistant replies are stubbed. | E2E: openapi-coverage, messaging, events | -| GET | /session/{sessionID}/message/{messageID} | Stateful stub | In-memory messages; assistant replies are stubbed. | E2E: openapi-coverage, messaging | -| DELETE | /session/{sessionID}/message/{messageID}/part/{partID} | Stateful stub | In-memory messages; assistant replies are stubbed. | E2E: openapi-coverage, messaging | -| PATCH | /session/{sessionID}/message/{messageID}/part/{partID} | Stateful stub | In-memory messages; assistant replies are stubbed. | E2E: openapi-coverage, messaging | -| POST | /session/{sessionID}/permissions/{permissionID} | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/prompt_async | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, messaging | -| POST | /session/{sessionID}/revert | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| DELETE | /session/{sessionID}/share | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/share | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/shell | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/summarize | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| GET | /session/{sessionID}/todo | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| POST | /session/{sessionID}/unrevert | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage | -| GET | /session/status | Stateful stub | In-memory session store; not persisted or mapped to /v1. | E2E: openapi-coverage, events | -| GET | /skill | Stubbed | Returns empty skills list. | E2E: openapi-coverage | -| POST | /tui/append-prompt | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/clear-prompt | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| GET | /tui/control/next | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/control/response | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/execute-command | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/open-help | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/open-models | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/open-sessions | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/open-themes | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/publish | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/select-session | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/show-toast | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| POST | /tui/submit-prompt | Stubbed | Returns true/empty control payloads. | E2E: openapi-coverage | -| GET | /vcs | Derived stub | | E2E: openapi-coverage | \ No newline at end of file diff --git a/docs/opencode-sdk-ui-support.mdx b/docs/opencode-sdk-ui-support.mdx new file mode 100644 index 0000000..6e579a4 --- /dev/null +++ b/docs/opencode-sdk-ui-support.mdx @@ -0,0 +1,148 @@ +--- +title: "OpenCode SDK & UI Support" +description: "Connect OpenCode clients, SDKs, and web UI to Sandbox Agent." +--- + + + **Experimental**: OpenCode SDK & UI support is experimental and may change without notice. + + +Sandbox Agent exposes an OpenCode-compatible API at `/opencode`, allowing you to connect any OpenCode client, SDK, or web UI to control coding agents running inside sandboxes. + +## Why Use OpenCode Clients with Sandbox Agent? + +OpenCode provides a rich ecosystem of clients: +- **OpenCode CLI** (`opencode attach`) - Terminal-based interface +- **OpenCode SDK** (`@opencode-ai/sdk`) - Programmatic TypeScript access +- **OpenCode Web UI** - Browser-based chat interface + +By connecting these to Sandbox Agent, you get: +- Control over multiple coding agents (Claude Code, Codex, OpenCode, Amp) through familiar OpenCode tooling +- Remote sandbox execution with local UI experience +- Full streaming, permissions, and human-in-the-loop support + +## Quick Start + +### Using OpenCode CLI + +```bash +# Start sandbox-agent +sandbox-agent server --no-token --host 127.0.0.1 --port 2468 + +# Attach OpenCode CLI +opencode attach http://localhost:2468/opencode +``` + +With authentication: + +```bash +# Start with token +sandbox-agent server --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468 + +# Attach with password +opencode attach http://localhost:2468/opencode --password "$SANDBOX_TOKEN" +``` + +### Using the All-in-One Command + +Sandbox Agent can start the server and attach OpenCode automatically: + +```bash +sandbox-agent opencode --port 2468 --no-token +``` + +### Using OpenCode SDK + +```typescript +import { createOpencodeClient } from "@opencode-ai/sdk"; + +const client = createOpencodeClient({ + baseUrl: "http://localhost:2468/opencode", + headers: { Authorization: "Bearer YOUR_TOKEN" }, // if using auth +}); + +// Create a session +const session = await client.session.create(); + +// Send a prompt +await client.session.promptAsync({ + path: { id: session.data.id }, + body: { + parts: [{ type: "text", text: "Hello, write a hello world script" }], + }, +}); + +// Subscribe to events +const events = await client.event.subscribe({}); +for await (const event of events.stream) { + console.log(event); +} +``` + +## Running the OpenCode Web UI + +The OpenCode web UI can connect to Sandbox Agent for a full browser-based experience. + +### 1. Start Sandbox Agent with CORS + +```bash +sandbox-agent server --no-token --host 127.0.0.1 --port 2468 \ + --cors-allow-origin http://127.0.0.1:5173 +``` + +### 2. Start the OpenCode Web App + +Clone and run the OpenCode web app: + +```bash +cd /path/to/opencode/packages/app +VITE_OPENCODE_SERVER_HOST=127.0.0.1 VITE_OPENCODE_SERVER_PORT=2468 \ + bun run dev -- --host 127.0.0.1 --port 5173 +``` + +### 3. Open the UI + +Navigate to `http://127.0.0.1:5173/` in your browser. + +## API Routing + +The OpenCode API is exposed at two locations: + +| Path | Description | +|------|-------------| +| `/opencode/*` | Nested under `/opencode` prefix | +| `/*` | Also available at root level | + +The root-level routing exists because the OpenCode SDK uses absolute paths internally. Both work identically. + +## Notes + +- **Authentication**: If sandbox-agent is started with `--token`, include `Authorization: Bearer ` header or use `--password` flag with CLI +- **CORS**: When using the web UI from a different origin, configure `--cors-allow-origin` +- **Provider Selection**: Use the provider/model selector in the UI to choose which backing agent to use (claude, codex, opencode, amp) + +## Endpoint Coverage + +See the full endpoint compatibility table below. Most endpoints are functional for session management, messaging, and event streaming. Some endpoints return stub responses for features not yet implemented. + + + +| Method | Path | Status | Notes | +|---|---|---|---| +| GET | /event | SSE | Emits events for session/message updates | +| GET | /global/event | SSE | Wraps events in GlobalEvent format | +| GET | /session | Stateful | In-memory session store | +| POST | /session | Stateful | Create new sessions | +| GET | /session/{id} | Stateful | Get session details | +| POST | /session/{id}/message | Stateful | Send messages to session | +| GET | /session/{id}/message | Stateful | Get session messages | +| GET | /permission | Stateful | List pending permissions | +| POST | /permission/{id}/reply | Stateful | Respond to permission requests | +| GET | /question | Stateful | List pending questions | +| POST | /question/{id}/reply | Stateful | Answer agent questions | +| GET | /provider | Stubbed | Returns provider metadata | +| GET | /agent | Stubbed | Returns agent list | +| GET | /config | Stubbed | Returns config | +| *other endpoints* | Stubbed | Return empty/stub responses | + + diff --git a/frontend/packages/website/src/components/FeatureGrid.tsx b/frontend/packages/website/src/components/FeatureGrid.tsx index be17c62..92d3aed 100644 --- a/frontend/packages/website/src/components/FeatureGrid.tsx +++ b/frontend/packages/website/src/components/FeatureGrid.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Workflow, Server, Database, Download, Globe } from 'lucide-react'; +import { Workflow, Server, Database, Download, Globe, Plug } from 'lucide-react'; import { FeatureIcon } from './ui/FeatureIcon'; export function FeatureGrid() { @@ -138,6 +138,31 @@ export function FeatureGrid() { Lightweight static binary. One curl command to install inside E2B, Daytona, Vercel Sandboxes, or Docker.

+ + {/* OpenCode SDK & UI Support */} +
+ {/* Top Shine Highlight */} +
+ {/* Top Left Reflection/Glow */} +
+ {/* Sharp Edge Highlight */} +
+ +
+ +

OpenCode SDK & UI

+ Experimental +
+

+ Connect OpenCode CLI, SDK, or web UI to control agents through familiar OpenCode tooling. +

+