跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

OpenCode

最后更新 查看 MarkdownAgent 设置

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。

部署到 Cloudflare

工作原理

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@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" ]

在 Durable Object 中使用

在 src/worker-tools.ts 中创建插件,移除 OpenCode 不支持的本地 tools,并注册一个可在 Worker 内运行的 tool:

src/worker-tools.jsjs
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() };
				},
			});
		});
	},
});
src/worker-tools.tsts
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:

src/index.jsjs
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?"));
	},
};
src/index.tsts
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 route

catalog 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。

发送 prompts

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。

使用 sessions

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

通过插件注册远程 MCP server。本示例连接到 Cloudflare 文档 MCP server:

src/cloudflare-docs-mcp.jsjs
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",
			});
		});
	},
});
src/cloudflare-docs-mcp.tsts
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 ↗。

添加 Agent Skill

通过打包插件注册 skill 内容:

src/review-skill.jsjs
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.",
			});
		});
	},
});
src/review-skill.tsts
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。

这篇文档对您有帮助吗?