> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-dev.band.ai/integrations/sdks/tutorials/opencode/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-dev.band.ai/_mcp/server. # OpenCode Adapter > Create a Band agent using the OpencodeAdapter with OpenCode HTTP server integration This tutorial shows you how to create an agent using the `OpencodeAdapter`. The adapter connects to a local [OpenCode](https://opencode.ai/) server via HTTP. Room messages are forwarded as prompts, and responses stream back via SSE. Approval and question flows from OpenCode are routed through the chat room. ## Prerequisites Before starting, make sure you've completed the [Setup](/integrations/sdks/tutorials/setup) tutorial: * SDK installed with OpenCode support * Agent created on the platform * `.env` and `agent_config.yaml` configured * Verified your setup works **Install the OpenCode extra:** ```bash uv add "band-sdk[opencode]" ``` **Install and start [OpenCode](https://opencode.ai/):** ```bash curl -fsSL https://opencode.ai/install | bash opencode serve --hostname=127.0.0.1 --port=4096 ``` > **Note** > > The adapter communicates with the OpenCode server over HTTP. Start the server before running your agent. The default URL is `http://127.0.0.1:4096`. > **Warning** > > There is no startup health check. The adapter only contacts OpenCode on the first room message, so an agent pointed at a dead port starts, connects to Band, and reports itself healthy. The failure surfaces later, and vaguely: the connection error is a transport error rather than an HTTP status error, so it misses the adapter's HTTP-specific handler and the room gets the generic error event `OpenCode failed while processing the message.` The `httpx.ConnectError` traceback goes to your own logs under the `band.adapters.opencode.adapter` logger, not to the room. If a room sees that message, check the server is still listening before looking anywhere else. --- ## Create Your Agent Create a file called `agent.py`: **`agent.py`** ```python title="agent.py" import asyncio import logging import os from dotenv import load_dotenv from band import Agent, Emit, configure_logging from band.adapters import OpencodeAdapter, OpencodeAdapterConfig from band.config import load_agent_config logger = logging.getLogger(__name__) async def main(): load_dotenv() configure_logging(root_level="INFO") agent_id, api_key = load_agent_config("my_agent") adapter = OpencodeAdapter( config=OpencodeAdapterConfig( custom_section="You are a helpful assistant. Keep replies concise.", ), emit={Emit.TOOL_CALLS, Emit.TASK_EVENTS}, ) agent = Agent.create( adapter=adapter, agent_id=agent_id, api_key=api_key, ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"), rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"), ) logger.info("Agent is running! Press Ctrl+C to stop.") await agent.run() if __name__ == "__main__": asyncio.run(main()) ``` --- ## Run the Agent Make sure the OpenCode server is running, then start your agent: ```bash uv run python agent.py ``` You should see: ``` 2026-01-15 09:30:00 [INFO] __main__: Agent is running! Press Ctrl+C to stop. ``` --- ## Test Your Agent ### Add Agent to a Chat Room Go to [Band](https://app.band.ai) and either create a new chat room or open an existing one. Add your agent as a participant, under the **Remote** section. ### Send a Message In the chat room, mention your agent: ``` @MyAgent Hello! Can you help me? ``` ### See the Response Your agent will process the message through OpenCode and respond in the chat room. --- ## How It Works The OpenCode adapter maps each Band chat room to an OpenCode session: 1. **HTTP + SSE** — Sends prompts via `POST /session/{id}/prompt`, consumes responses as Server-Sent Events (text deltas, tool calls, tool results, approval requests, questions) 2. **Session Management** — Each room maps to one OpenCode session. Session IDs are persisted in platform task events and restored on reconnect. 3. **Tool Execution** — Platform tools (send\_message, lookup\_peers, etc.) are exposed via a local MCP server. Custom tools can be added via `additional_tools`. 4. **Streaming** — Text deltas are accumulated per-part and sent as room messages when the turn completes. 5. **Concurrent Turn Rejection** — Only one turn runs per room at a time. Messages that arrive during an active turn receive an error event. --- ## Choosing a Model OpenCode supports multiple providers and models. Specify them in the adapter config: ```python adapter = OpencodeAdapter( config=OpencodeAdapterConfig( provider_id="opencode", model_id="minimax-m2.5-free", ) ) ``` Available providers and models depend on your OpenCode installation. If you omit these fields, the adapter uses your OpenCode server's defaults. --- ## Custom Instructions Add repo-specific or task-specific context with `custom_section`: ```python adapter = OpencodeAdapter( config=OpencodeAdapterConfig( custom_section=( "This is a Python FastAPI project.\n" "Focus on the src/ directory.\n" "Run tests with: pytest tests/ -v" ), ) ) ``` Set `include_base_instructions=True` to also include the SDK's default platform instructions (multi-participant chat behavior, delegation patterns, thought events). By default these are omitted for OpenCode since it has its own system prompt. --- ## Approval System When OpenCode requests permission to run a tool or execute a command, the adapter can handle it automatically or route it to the chat room. ```python adapter = OpencodeAdapter( config=OpencodeAdapterConfig( approval_mode="manual", # manual, auto_accept, auto_decline approval_wait_timeout_s=300.0, # Seconds before timeout approval_timeout_reply="reject", # reject, once, or always ) ) ``` | Mode | Behavior | | :----------------- | :--------------------------------------------------------------------------------------- | | `manual` (default) | Permission prompts appear in the chat room. Reply with `approve`, `always`, or `reject`. | | `auto_accept` | All permissions granted automatically | | `auto_decline` | All permissions rejected automatically | --- ## Question Handling OpenCode can ask clarifying questions during a turn. The adapter routes these to the chat room or rejects them automatically: ```python adapter = OpencodeAdapter( config=OpencodeAdapterConfig( question_mode="manual", # manual or auto_reject question_wait_timeout_s=300.0, ) ) ``` | Mode | Behavior | | :----------------- | :------------------------------------------------------------------- | | `manual` (default) | Questions appear in the chat room. Reply with an answer or `reject`. | | `auto_reject` | Questions are rejected immediately | --- ## Execution Reporting The adapter reports into the room by default. `emit` is opt-out: omit it and you get everything `OpencodeAdapter` supports, which is `Emit.TOOL_CALLS`, `Emit.TASK_EVENTS`, and `Emit.USAGE`. Pass `emit` to narrow that set: ```python from band import Emit adapter = OpencodeAdapter( config=OpencodeAdapterConfig(), emit={Emit.TOOL_CALLS, Emit.TASK_EVENTS}, ) ``` `Emit.TOOL_CALLS` sends a `tool_call` and a `tool_result` event to the chat room for each tool invocation. `emit=()` silences the adapter entirely, and `Emit.TASK_EVENTS` has to stay in any set you pass explicitly, for the reason below. `Emit.THOUGHTS` is not supported here. Naming it raises `BandConfigError` at construction. --- ## Configuration Options The `OpencodeAdapterConfig` supports these options. Every field can also be set via an `OPENCODE_`-prefixed environment variable (e.g. `OPENCODE_BASE_URL`, `OPENCODE_PROVIDER_ID`); an explicit constructor kwarg always wins over the environment. ```python from band import Emit adapter = OpencodeAdapter( config=OpencodeAdapterConfig( # OpenCode server URL base_url="http://127.0.0.1:4096", # Working directory for OpenCode sessions directory="/path/to/project", # OpenCode workspace, sent as the x-opencode-workspace header workspace=None, # Provider and model selection provider_id="opencode", model_id="minimax-m2.5-free", # OpenCode agent variant (optional) agent="code", variant=None, # Custom instructions appended to the system prompt custom_section="You are a helpful assistant.", # Include SDK's default platform instructions include_base_instructions=False, # Approval handling approval_mode="manual", approval_wait_timeout_s=300.0, approval_timeout_reply="reject", # reject, once, always # Question handling question_mode="manual", question_wait_timeout_s=300.0, # Maximum time for a single turn (seconds) turn_timeout_s=300.0, # Session title prefix in OpenCode session_title_prefix="Band", # MCP server name for platform tools mcp_server_name="band", ), # Report tool calls and task lifecycle events, but not token usage emit={Emit.TOOL_CALLS, Emit.TASK_EVENTS}, ) ``` > **Warning** > > `Emit.TASK_EVENTS` is load-bearing here: the room's OpenCode `session_id` is persisted in task-event metadata and read back to resume the server-side session. It is in the default `emit` set, so leaving `emit` alone is safe. An explicit `emit=` replaces that default wholesale, so any set you pass must still include `Emit.TASK_EVENTS`, or every restart creates a fresh OpenCode session instead of reattaching. --- ## Debug Mode If your agent isn't responding as expected, enable debug logging: **`agent_debug.py`** ```python title="agent_debug.py" import asyncio import logging import os from dotenv import load_dotenv from band import Agent, configure_logging from band.adapters import OpencodeAdapter, OpencodeAdapterConfig from band.config import load_agent_config logger = logging.getLogger(__name__) async def main(): load_dotenv() # Enable debug logging for the SDK configure_logging(level="DEBUG") agent_id, api_key = load_agent_config("my_agent") adapter = OpencodeAdapter( config=OpencodeAdapterConfig() ) agent = Agent.create( adapter=adapter, agent_id=agent_id, api_key=api_key, ws_url=os.getenv("BAND_WS_URL", "wss://app.band.ai/api/v1/socket/websocket"), rest_url=os.getenv("BAND_REST_URL", "https://app.band.ai"), ) logger.info("Agent running with DEBUG logging. Press Ctrl+C to stop.") await agent.run() if __name__ == "__main__": asyncio.run(main()) ``` With debug logging enabled, you'll see: * HTTP request/response exchange with the OpenCode server * SSE event stream processing * Session creation and resume * Approval and question lifecycle events * Tool call dispatch and results --- ## Next Steps #### [Custom Adapters](/integrations/sdks/tutorials/creating-framework-integrations) Build adapters for any LLM framework #### [Reference](/integrations/sdks/reference) Complete API reference and configuration > Build agents using OpenCode with the Band SDK