OpenCode ↗ 是可扩展的 Agent harness。Agents SDK 的 OpenCodeHarness 在普通 Durable Object 中运行其 Workerd SDK。嵌入式 host API 请参阅 OpenCode 的 Cloudflare SDK 文档 ↗。
OpenCode 管理 agent loop、transcript、tool 和 session 恢复。OpenCodeHarness 提供 SQLite 存储,并在运行中断后唤醒 object。agents/models/opencode provider 通过你的 AI binding 将 OpenCode 连接到 Workers AI 模型、AI Gateway catalog 模型和 dynamic route。
OpenCodeHarness 是 Lifecycle 功能。Lifecycle 会启动该功能,并提供存储和持久 job queue。
flowchart LR
client["Application or Worker"] -->|HTTP, WebSockets, or RPC| object
subgraph object["Durable Object"]
lifecycle["Lifecycle jobs"] --> harness["OpenCodeHarness"]
harness --> host["OpenCode Workerd host"]
host <--> sqlite[("SQLite: opencode_ tables")]
host <--> plugins["Bundled plugins"]
end
host --> ai["AI binding"]
plugins --> mcp["Remote MCP server"]
plugins --> workspace["External execution environment"]
- 存储。 OpenCode 将其 sessions、messages 和 durable events 保存在对象的 SQLite 数据库中。harness 为 OpenCode 的表和索引添加
opencode_前缀,以避免冲突。 - 唤醒。 每个活动 session 都有一个 Lifecycle job。session 运行时,该 job 保持 heartbeat。若对象崩溃或被 evict,alarm 会重启它,OpenCode 恢复被中断的工作。
- 空闲关闭。 OpenCode 在打开期间运行后台定时器。当没有工作或事件订阅时,harness 会在 30 秒后关闭未使用的 host。下次调用时重新打开 host,并保留 durable 状态。
| 能力 | 支持情况 |
|---|---|
| 持久化 session 与中断 turn 的恢复 | OpenCode 持久化状态;Lifecycle jobs 唤醒对象。 |
| Workers AI 和 AI Gateway 模型 | 注册 agents/models/opencode 插件。 |
| 自定义 tools、agents 和 skills | 将 OpenCode 插件与 Worker 一起打包。 |
| 远程 Model Context Protocol(MCP)servers | 通过插件注册远程 server。 |
| 实时事件与 durable 事件重放 | 使用 session.events() 和 session.log()。 |
安装 Agents SDK 和 OpenCode 软件包:
npm i agents@latest @opencode/sdk@2 @opencode/plugin@2yarn add agents@latest @opencode/sdk@2 @opencode/plugin@2pnpm add agents@latest @opencode/sdk@2 @opencode/plugin@2bun add agents@latest @opencode/sdk@2 @opencode/plugin@2使用最新的 agents release。OpenCode 软件包是此集成所需的可选 peer dependency,支持范围为 ^2.0.24。
将 AI binding、基于 SQLite 的 Durable Object 和 nodejs_compat 添加到 Wrangler 配置:
{
"name": "opencode-agent",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-11-14",
"compatibility_flags": ["nodejs_compat"],
"ai": { "binding": "AI", "remote": true },
"durable_objects": {
"bindings": [{ "name": "Assistant", "class_name": "Assistant" }],
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["Assistant"] }],
}name = "opencode-agent"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-11-14"
compatibility_flags = [ "nodejs_compat" ]
[ai]
binding = "AI"
remote = true
[[durable_objects.bindings]]
name = "Assistant"
class_name = "Assistant"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Assistant" ]在 src/worker-tools.ts 中创建插件,移除 OpenCode 不支持的本地 tools,并注册一个可在 Worker 内运行的 tool:
import { Plugin } from "@opencode/plugin";
export default Plugin.define({
id: "worker-tools",
async setup(ctx) {
await ctx.tool.transform((tools) => {
for (const name of [
"read",
"write",
"edit",
"patch",
"glob",
"grep",
"shell",
]) {
tools.remove(name);
}
tools.add({
name: "current_time",
description: "Return the current UTC time.",
input: { type: "object", properties: {}, additionalProperties: false },
async execute() {
return { content: new Date().toISOString() };
},
});
});
},
});import { Plugin } from "@opencode/plugin";
export default Plugin.define({
id: "worker-tools",
async setup(ctx) {
await ctx.tool.transform((tools) => {
for (const name of [
"read",
"write",
"edit",
"patch",
"glob",
"grep",
"shell",
]) {
tools.remove(name);
}
tools.add({
name: "current_time",
description: "Return the current UTC time.",
input: { type: "object", properties: {}, additionalProperties: false },
async execute() {
return { content: new Date().toISOString() };
},
});
});
},
});使用 OpenCode factory 创建 harness,并在 Durable Object 上安装 Lifecycle:
import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
import { OpenCodeWorkerd } from "@opencode/sdk/workerd";
import { OpenCodeHarness } from "agents/harness/opencode";
import { createAI } from "agents/models/opencode";
import workerTools from "./worker-tools";
export class Assistant extends DurableObject {
ai = createAI({ binding: this.env.AI });
harness = new OpenCodeHarness({
opencode: ({ storage }) =>
OpenCodeWorkerd.create({
storage,
config: { default_agent: "build" },
models: { fetch: false },
plugins: [this.ai.plugin, workerTools],
}),
defaults: {
model: this.ai("@cf/moonshotai/kimi-k2.7-code", {
reasoningEffort: "low",
}),
agent: "build",
},
});
lifecycle = Lifecycle.install(this).use(this.harness);
async ask(prompt) {
const { status, text, reason } = await this.harness.prompt(prompt);
return { status, text, reason };
}
}
export default {
async fetch(_request, env) {
const assistant = env.Assistant.getByName("demo");
return Response.json(await assistant.ask("What is the current UTC time?"));
},
};import { DurableObject } from "cloudflare:workers";
import { Lifecycle } from "agents/lifecycle";
import { OpenCodeWorkerd } from "@opencode/sdk/workerd";
import { OpenCodeHarness } from "agents/harness/opencode";
import { createAI } from "agents/models/opencode";
import workerTools from "./worker-tools";
export class Assistant extends DurableObject<Env> {
ai = createAI({ binding: this.env.AI });
harness = new OpenCodeHarness({
opencode: ({ storage }) =>
OpenCodeWorkerd.create({
storage,
config: { default_agent: "build" },
models: { fetch: false },
plugins: [this.ai.plugin, workerTools],
}),
defaults: {
model: this.ai("@cf/moonshotai/kimi-k2.7-code", {
reasoningEffort: "low",
}),
agent: "build",
},
});
lifecycle = Lifecycle.install(this).use(this.harness);
async ask(prompt: string) {
const { status, text, reason } = await this.harness.prompt(prompt);
return { status, text, reason };
}
}
export default {
async fetch(_request: Request, env: Env) {
const assistant = env.Assistant.getByName("demo");
return Response.json(await assistant.ask("What is the current UTC time?"));
},
} satisfies ExportedHandler<Env>;将 factory 的 storage 传递给 OpenCodeWorkerd.create(),让 OpenCode 使用带 prefix 的数据库。注册 this.ai.plugin,向 OpenCode 添加名为 "Cloudflare"(id 为 cloudflare)的 provider。models: { fetch: false } 会跳过 OpenCode 的外部 model catalog 刷新。provider 会注册你通过 this.ai() 指定的模型。
Lifecycle 管理 host 启动。harness 重新打开空闲 host 时 factory 会再次运行,因此必须支持重复调用。
本示例使用 "demo" 作为对象名称。在应用中,请根据已认证用户或对话选择对象名称。
this.ai() 接受与 AI SDK provider 相同的 model id,并接受其模型选项:
this.ai("@cf/moonshotai/kimi-k2.7-code"); // Workers AI
this.ai("anthropic/claude-opus-5.5"); // AI Gateway catalog
this.ai("dynamic/support"); // AI Gateway dynamic routecatalog id 使用 model catalog 中的拼写,例如 anthropic/claude-opus-5.5。dynamic route 属于一个 gateway,因此请将该 gateway 的 id 传递给 createAI。不属于这三种形式的 id 会在命名它的行抛出 TypeError。catalog 模型和 dynamic route 的 stream 会在连接断开后恢复。
每次调用都会返回 OpenCode 的模型 reference { providerID, id }。将其传递给 defaults.model 或 session.setModel()。要设置 OpenCode 如何分配模型 context,请传递 limit: { context, output }。默认值为 128,000 个 context token 和 8,192 个 output token。
harness.prompt() 向根 session 提交并等待完成。它返回 status、可选的 text 或 reason,以及作为 messages 的当前 transcript。检查 status:"done" 表示已回答;"unanswered" 表示失败、被中断、被撤回或未找到。
对于后台工作,可分开提交和等待:
const receipt = await this.harness.submit("Summarize the report", {
operationId: "report-summary-42",
});
// In a later request to the same Durable Object:
const result = await this.harness.wait(receipt.operationId, {
session: receipt.session,
});const receipt = await this.harness.submit("Summarize the report", {
operationId: "report-summary-42",
});
// In a later request to the same Durable Object:
const result = await this.harness.wait(receipt.operationId, {
session: receipt.session,
});operationId 会在 session 内对提交去重。重复使用会返回带 accepted: false 的 receipt。ID 中可使用字母、数字、.、_、~ 或 -。
session 运行期间提交的输入会作为 follow-up 排队。设置 whenBusy: "steer" 或调用 session.steer(),可在下一步加入正在运行的工作。harness.pending() 列出排队中和运行中的 operations。
harness 会创建 ID 为 "ses_root" 的根 session。使用 harness.session(id) 寻址另一个 session:
const session = await this.harness.sessions.create();
await session.prompt("Draft a release note");
const fork = await this.harness.sessions.fork(session.id);
const all = await this.harness.sessions.list();const session = await this.harness.sessions.create();
await session.prompt("Draft a release note");
const fork = await this.harness.sessions.fork(session.id);
const all = await this.harness.sessions.list();| Session 方法 | 描述 |
|---|---|
submit(input, options) |
持久化提交输入并返回 receipt。 |
prompt(input, options) |
提交、等待,并返回结果和当前 transcript。 |
steer(input, options) |
以 whenBusy: "steer" 提交。 |
wait(operationId, signal) |
等待某个 operation。中止 signal 只会停止等待。 |
abort(operationId) |
撤回排队中的 operation,或中断它已加入的运行。不提供 ID 时,中止该 session 中的所有工作。 |
setModel(model) |
更改模型,使用如 this.ai("@cf/moonshotai/kimi-k2.7-code") 的引用。 |
setAgent(agent) |
更改 OpenCode agent,例如 "build" 或 "plan"。 |
messages() |
读取最近一次 compaction 之后的当前上下文。 |
history() |
读取完整 transcript,包括已 compaction 的 messages。 |
events() |
打开流:先提供 session 快照,然后是实时事件。 |
log({ after, follow, signal }) |
重放 durable 事件,可选择跟随新事件。 |
busy() |
检查是否有运行中、排队中,或等待恢复的已中断工作。 |
sessions.list() 包含由 OpenCode 的 subagent tools 派生的 sessions。同一对象内的 sessions 可独立运行。将 OpenCode messages 映射到客户端的 UI 格式。
session.events() 返回一个流。其 snapshot 保存流打开时的 session 状态:完整 transcript、pending operations、session 是否忙碌,以及 OpenCode 正在等待的 permissions。调用 start() 可在快照之后以批次接收每个事件:
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();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();事件种类与 Pi harness 相同:run_start 和 run_end、operation_start 和 operation_end(带 wait() 返回的结果)、message_start 和 message_end、text_delta、reasoning_delta、tool_start 和 tool_end,以及 permissions 和 compaction 相关事件。事件可能重复快照中已有的内容,因此请按 ID 作为 messages 的键。
对于 OpenCode 自身可持久化、可重放的事件,使用 session.log()。保存 event.durable.seq 游标,并在重连时作为 after 传入。durable log 不会重放未提交的实时 text delta。
两者都会保持 host 打开。不再需要流时调用 stream.stop(),并用 AbortSignal 停止 log()。对象重启后再打开它们。示例的 WebSocket adapter ↗ 展示了快照、事件转发,以及启动后的重新附着。
将插件与 Worker 一起打包,并在 factory 的 plugins 数组中与 this.ai.plugin 和 workerTools 一起传入。不要依赖运行时加载本地插件文件或 npm 包。
OpenCode 会将插件 tools 包装在其 execute code-mode tool 中,因此事件中的 tool 调用可能显示为 execute。schema 和 tool 输出请参阅 OpenCode 插件 tools ↗。
通过插件注册远程 MCP server。本示例连接到 Cloudflare 文档 MCP server:
import { Plugin } from "@opencode/plugin";
export default Plugin.define({
id: "cloudflare-docs-mcp",
async setup(ctx) {
await ctx.mcp.transform((servers) => {
servers.set("cloudflare-docs", {
type: "remote",
url: "https://docs.mcp.cloudflare.com/mcp",
});
});
},
});import { Plugin } from "@opencode/plugin";
export default Plugin.define({
id: "cloudflare-docs-mcp",
async setup(ctx) {
await ctx.mcp.transform((servers) => {
servers.set("cloudflare-docs", {
type: "remote",
url: "https://docs.mcp.cloudflare.com/mcp",
});
});
},
});导入该插件并将其加入 plugins。server 配置请参阅 OpenCode MCP transforms ↗。
通过打包插件注册 skill 内容:
import { Plugin, Skill } from "@opencode/plugin";
export default Plugin.define({
id: "review-skill",
async setup(ctx) {
await ctx.skill.transform((skills) => {
skills.add({
id: Skill.ID.make("review"),
name: Skill.Name.make("Review"),
description:
"Review a proposed change for correctness and missing tests.",
path: Skill.Info.fields.path.make("/skills/review/SKILL.md"),
content:
"Review the proposed change. Report actionable findings with evidence.",
});
});
},
});import { Plugin, Skill } from "@opencode/plugin";
export default Plugin.define({
id: "review-skill",
async setup(ctx) {
await ctx.skill.transform((skills) => {
skills.add({
id: Skill.ID.make("review"),
name: Skill.Name.make("Review"),
description:
"Review a proposed change for correctness and missing tests.",
path: Skill.Info.fields.path.make("/skills/review/SKILL.md"),
content:
"Review the proposed change. Report actionable findings with evidence.",
});
});
},
});导入此 plugin 并将其添加到 plugins。content 会提供说明,无需在运行时读取本地文件。有关注册,请参阅 OpenCode skills ↗;有关编写指南,请参阅 Agent Skills。