Official

opencode-v2

OpenCode 2 agent provider: runs opencode serve (2.x) and talks to its /api over HTTP and server-sent events.

The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/opencode-v2@0.1.0

Permissions in 0.1.0

  • Provide agents agents.provideMediumAdds agents to the app.Provide the OpenCode 2 agent, and serve plugin tools to it through the host's loopback MCP server
  • Run named programs processMediumStarts the listed programs.Run the OpenCode 2 server (`opencode serve`, or the binary you choose in Settings) and read its versionPrograms: opencode${settings.binaryPath}
  • Network access netMediumConnects to the listed hosts.Talk to the OpenCode server it starts on this computer, at the port it picks for each launch, and to the external server you choose in SettingsHosts: localhost:*${settings.serverUrl}
  • Environment variables envMediumReads the listed environment variables.Read the user name and password of the OpenCode serverVariables: OPENCODE_SERVER_USERNAMEOPENCODE_SERVER_PASSWORDCONVERGENCE_OPENCODE_SERVER_PASSWORD

Files

NOTES.md10.1 KB
# OpenCode 2 provider plugin — decisions

The reference for every shape here is the OpenCode source at the tag of the
installed version (github.com/anomalyco/opencode, `v2.0.16`):

| What                                                                         | Where in the source                                                                                                              |
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| HTTP routes, query and body shapes                                           | `packages/protocol/src/groups/*.ts`, `packages/protocol/openapi.json` (served at `GET /openapi.json`; `GET /doc` is the web app) |
| Session events                                                               | `packages/schema/src/session-event.ts`                                                                                           |
| Permission, form and catalog events                                          | `packages/schema/src/{permission,form,provider,model,agent,command,skill}.ts`, `event-manifest.ts`                               |
| Session semantics (inbox, steer and queue, execution, retries, instructions) | `specs/v2/session.md`                                                                                                            |
| The event stream                                                             | `specs/v2/event-stream-architecture.md`                                                                                          |
| Catalog lifecycle                                                            | `specs/v2/catalog-config-plugin-lifecycle.md`                                                                                    |
| The subagent tool                                                            | `packages/core/src/tool/plugin/subagent.ts`, `session/subagent-completion.ts`                                                    |
| MCP tools and code mode                                                      | `packages/core/src/tool/mcp.ts`, `mcp/client.ts`                                                                                 |

The 1.x provider (`plugins/opencode`) stays in the repository, off by
default (`disabledByDefault`). The two serve different agent ids (`opencode`,
`opencode-v2`), so a chat started on one never resumes on the other: 2.x
sessions live in a different store.

| File                                       | What it holds                                                                       |
| ------------------------------------------ | ----------------------------------------------------------------------------------- |
| `main.ts`                                  | registration and the settings that pick the binary or an external server            |
| `server.ts`                                | the `opencode serve` child, credentials, requests                                   |
| `api.ts`                                   | Zod schemas of the API shapes and events the plugin reads                           |
| `agent.ts`                                 | the agent: event stream, runs, subagents, approvals, forms, every `agent/*` handler |
| `map.ts`                                   | tool rows, question cards, usage and history (pure)                                 |
| `options.ts`, `prompt.ts`, `permission.ts` | options from the live catalog, prompt bodies, the permission ruleset                |

## Transport

`opencode serve --hostname 127.0.0.1 --port 0` with a password made up for
the plugin's lifetime (2.x refuses anonymous requests). `--port 0` works in
2.x: the startup line `server listening on http://127.0.0.1:<port>` names
the port, and `GET /api/info` confirms the server. The binary must report a
2.x version; a 1.x binary is refused with a message naming the setting to
change.

Everything lives under `/api`. Catalog routes take
`location[directory]=<folder>`; the session list takes `directory`.

## One event stream

`GET /api/event` is one stream for every location (each frame carries its
`location`). It is volatile by contract: a slow reader is cut off and
events during a disconnect are lost. The plugin opens it before the first
prompt and waits for the first frame (`server.connected`). A dropped
connection reconnects with backoff; after it, runs whose session is no
longer active (`GET /api/session/active`) end with the outcome
`GET /api/session/{id}` reports, and pending permissions and forms are read
again (`GET /api/permission/request`, `GET /api/form`) so no card waits for an
answer that cannot come. Only a local server that exited, or an external one
unreachable for a minute, fails the runs.

## The catalog is built after the start

A server builds its providers, models and agents in the background after it
starts (catalog transforms run as plugins activate), so the first reads can
answer empty lists. A catalog with no usable model is read again for up to
20 s, at once when a catalog event (`provider.updated`, `model.updated`,
`agent.updated`, ...) arrives. Those events also drop the cached catalog and
send every chat of that folder its options again (`config_options`).

## Sessions, model and mode

- Create: `POST /api/session` with `location`, `agent`, `model` and the
  permission ruleset.
- Model, variant and mode are session state in 2.x, not prompt fields:
  `POST /api/session/{id}/model` `{model:{providerID,id,variant?}}` and
  `POST /api/session/{id}/agent` `{agent}`. The plugin sends each one before a
  prompt only when it changed since it last told the session.
- The model option value is `<providerID>/<id>`, where `id` is the catalog
  entry's own id (two entries can share one provider model, for example a
  "fast" entry).
- Prompt: `POST /api/session/{id}/prompt` `{id, text, files?, skills?}`. The
  message id is `msg_<host item id>`. A prompt during a run is sent with
  `delivery: "steer"` and returns the active run id (`steer: true`).
- Mentions stay in the text; each file or skill attachment names its span
  (`mention: {start, end, text}`). Images and audio go as `data:` URIs.
- A prompt whose text starts with a command the server lists goes to
  `POST /api/session/{id}/command` `{name, text}`.
- Cancel: `POST /api/session/{id}/interrupt` on the chat and its subagents,
  after pending permissions are rejected and forms dismissed.
- Plugin instructions: the instruction entry `convergence`
  (`PUT /api/experimental/session/{id}/instructions/entries/convergence`).
- Not offered: `rollback` (Divergence owns file restores; 2.x has no way to
  forget messages without its own revert of files), usage limits, sign-in.

## Runs

`session.execution.started` / `succeeded` / `failed` / `interrupted` bound a
run. A run the plugin did not start (a queued prompt, a background subagent
result) is announced with `run_started`. Exactly one `run_finished` per run:
the run is removed before it is reported.

## Event mapping

- Text and reasoning: `session.{text,reasoning}.delta` append to the item
  `<messageID>/<kind>/<ordinal>`; `.ended` replaces it with the whole block,
  so a lost delta does not stay lost.
- Tools: a call id is unique only within a step, so the item id is
  `<assistantMessageID>/<callID>`. `session.tool.input.started` starts the
  row, `session.tool.called` gives its input and title, `.success` and
  `.failed` end it. Edits carry the patches in `metadata.files`; commands
  show their output as a terminal with `metadata.exit`. Plugin tools reach
  OpenCode as `convergence_<tool>`; the row title drops the prefix.
- Usage: `session.usage.updated` (cumulative cost) with the tokens of the
  last `session.step.ended`, and the context window of the model the step
  ran on.
- `session.retry.scheduled` is a warning; `session.step.failed` an error.

## Subagents

The `subagent` tool creates a child session (`session.created` with
`parentID`, titled with the call's `description`), reports it in
`session.tool.progress` metadata (`{sessionID, status}`), and ends with
`<subagent sessionID=".." state="completed">TEXT</subagent>`. A call that
continues an earlier child (`sessionID` input) creates no session: the
progress event names it, and the plugin looks it up. A background call
answers at once (`status: "running"`); its result arrives later as a
`session.synthetic` message in the parent with `metadata.source:
"subagent"`. Every event of a child session goes to the chat with
`taskId` = the child; `cancel_task` interrupts the child and its own
children.

## Approvals and forms

`permission.asked` → an approval with OpenCode's replies (`once`,
`always` when the request names what it saves, `reject`), answered with
`POST /api/session/{id}/permission/{requestID}/reply {decision}`. Full access
answers `once` itself. The question tool asks through a form:
`form.created` → a question card; answered with
`POST .../form/{id}/reply {answer}`, dismissed with `DELETE .../form/{id}`.

## Permissions

The shared modes become a session ruleset (`PATCH /api/session/{id}
{permissions}`; the session's rules follow the agent's and the last match
wins). Supervised asks for everything but reads, searches, the question
tool and starting subagents; Auto-accept edits allows `edit`; Full access
allows everything. Auto is not offered: OpenCode has no approval reviewer.

## Plugin tools

The host serves the session's tools (`host/mcp.serve`); the plugin adds the
address for the folder with `PUT /api/experimental/mcp/convergence
{config:{type:"remote", url, codemode:false}}` — a runtime-only server, not
written to the user's config. `codemode: false` makes them direct tools
instead of entries behind OpenCode's code-mode `execute` tool. The status is
checked with `GET /api/mcp`; a failure is a warning in the chat.

## Tests

`pnpm -C plugins test` runs the mapping and agent tests against a scripted
server (`testing.test.ts`). The live test is
`CONVERGENCE_E2E_OPENCODE_MODEL=opencode/space-bunny-free cargo test -p
convergence-host --test e2e opencode_v2_calls_a_plugin_tool -- --ignored`:
one turn that calls a plugin tool, then the chat imported again from
OpenCode's history.

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.1.0latestOct 5, 2026>=2 <345.1 KB4 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.