From a25c597303be435953e694ac2858a13be42e30da Mon Sep 17 00:00:00 2001 From: Matt Carey Date: Tue, 13 Oct 2026 13:26:12 +0200 Subject: [PATCH] [Agents] Document the OpenCode harness session.events() stream from agents 0.28.1 --- .../agents/2026-10-12-opencode-harness.mdx | 2 +- .../docs/agents/harnesses/opencode.mdx | 46 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/content/changelog/agents/2026-10-12-opencode-harness.mdx b/src/content/changelog/agents/2026-10-12-opencode-harness.mdx index 0f1bccb9d45..2b4872d851a 100644 --- a/src/content/changelog/agents/2026-10-12-opencode-harness.mdx +++ b/src/content/changelog/agents/2026-10-12-opencode-harness.mdx @@ -68,4 +68,4 @@ export class Assistant extends DurableObject { -The [OpenCode harness guide](/agents/harnesses/opencode/) includes the `workerTools` plugin, Wrangler configuration, and session APIs. The [working example](https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/opencode) adds a browser UI, WebSocket streaming, and persistent note tools. +The [OpenCode harness guide](/agents/harnesses/opencode/) includes the `workerTools` plugin, Wrangler configuration, and session APIs. The [working example](https://github.com/cloudflare/agents/tree/main/examples/next/harnesses/opencode) adds a browser UI, WebSocket streaming, and Workspace tools from `@cloudflare/computer` for files and code execution. diff --git a/src/content/docs/agents/harnesses/opencode.mdx b/src/content/docs/agents/harnesses/opencode.mdx index 0c38b2e8d1b..06bd00bd040 100644 --- a/src/content/docs/agents/harnesses/opencode.mdx +++ b/src/content/docs/agents/harnesses/opencode.mdx @@ -49,14 +49,13 @@ flowchart LR - **Wake-up.** Each active session has a Lifecycle job. While the session runs, the job keeps a heartbeat. If the object crashes or is evicted, an alarm restarts it, and OpenCode resumes interrupted work. - **Idle close.** OpenCode runs background timers while open. The harness closes an unused host after 30 seconds when no work or event subscriptions remain. It reopens the host on the next call, retaining durable state. - -| Capability | Support | -| -------------------------------------------------- | -------------------------------------------------------- | -| Durable sessions and interrupted-turn recovery | OpenCode persists state; Lifecycle jobs wake the object. | -| Workers AI models | Register the `agents/models/opencode` plugin. | -| Custom tools, agents, and skills | Bundle OpenCode plugins with your Worker. | -| Remote Model Context Protocol (MCP) servers | Register remote servers through a plugin. | -| Live events and durable event replay | Use `session.events()` and `session.log()`. | +| Capability | Support | +| ---------------------------------------------- | -------------------------------------------------------- | +| Durable sessions and interrupted-turn recovery | OpenCode persists state; Lifecycle jobs wake the object. | +| Workers AI models | Register the `agents/models/opencode` plugin. | +| Custom tools, agents, and skills | Bundle OpenCode plugins with your Worker. | +| Remote Model Context Protocol (MCP) servers | Register remote servers through a plugin. | +| Live events and durable event replay | Use `session.events()` and `session.log()`. | ## Install @@ -64,7 +63,7 @@ Install the Agents SDK and OpenCode packages: -Use `agents` version 0.28.0 or later. The OpenCode packages are optional peer dependencies, required by this integration, with a supported range of `^2.0.24`. +Use `agents` version 0.28.1 or later. The OpenCode packages are optional peer dependencies, required by this integration, with a supported range of `^2.0.24`. Add the Workers AI binding, a SQLite-backed Durable Object, and `nodejs_compat` to your Wrangler configuration: @@ -86,7 +85,6 @@ Add the Workers AI binding, a SQLite-backed Durable Object, and `nodejs_compat` - ## Use it in a Durable Object Create a plugin in `src/worker-tools.ts` to remove OpenCode's unsupported local tools and register a tool that can run inside the Worker: @@ -232,7 +230,7 @@ const all = await this.harness.sessions.list(); | `setAgent(agent)` | Change the OpenCode agent, such as `"build"` or `"plan"`. | | `messages()` | Read the active context after the latest compaction. | | `history()` | Read the full transcript, including compacted messages. | -| `events(signal)` | Subscribe to live and durable events for this session. | +| `events()` | Open a stream with a snapshot of the session, then its live events. | | `log({ after, follow, signal })` | Replay durable events, optionally following new ones. | | `busy()` | Check for running, queued, or interrupted work awaiting recovery. | @@ -240,11 +238,31 @@ const all = await this.harness.sessions.list(); ## Stream events to clients -`session.events()` returns an asynchronous iterable of live events, including text deltas. It does not include a transcript snapshot. Read `session.messages()` or `session.history()` for the transcript, then forward events over your chosen transport. +`session.events()` returns a stream. Its `snapshot` holds the session's state when the stream opened: the full transcript, pending operations, whether the session is busy, and permissions OpenCode is waiting on. Call `start()` to receive every event after the snapshot, in batches: + + + +```ts +const stream = await this.harness.session(sessionId).events(); +socket.send(JSON.stringify({ type: "snapshot", ...stream.snapshot })); + +stream.start((events) => { + for (const event of events) { + socket.send(JSON.stringify({ type: "event", event })); + } +}); + +// When the client disconnects or the session is idle: +await stream.stop(); +``` + + + +Events use the same kinds as the [Pi harness](/agents/harnesses/pi/): `run_start` and `run_end`, `operation_start` and `operation_end` (with the result `wait()` returns), `message_start` and `message_end`, `text_delta`, `reasoning_delta`, `tool_start` and `tool_end`, plus events for permissions and compaction. An event can repeat what the snapshot already shows, so key messages by ID. -For replayable events, use `session.log()`. Save the `event.durable.seq` cursor and pass it as `after` when reconnecting. The durable log does not replay uncommitted live text deltas. +For OpenCode's own events, durable and replayable, use `session.log()`. Save the `event.durable.seq` cursor and pass it as `after` when reconnecting. The durable log does not replay uncommitted live text deltas. -Both subscriptions hold the host open. Stop them with an `AbortSignal` when no longer needed. Recreate subscriptions after the object restarts. The [example's WebSocket adapter](https://github.com/cloudflare/agents/blob/main/examples/next/harnesses/opencode/src/sockets.ts) shows snapshots, event forwarding, and reattachment after startup. +Both hold the host open. Call `stream.stop()` when you no longer need a stream, and stop `log()` with an `AbortSignal`. Open them again after the object restarts. The [example's WebSocket adapter](https://github.com/cloudflare/agents/blob/main/examples/next/harnesses/opencode/src/sockets.ts) shows snapshots, event forwarding, and reattachment after startup. ## Add plugins