插件可以暴露自定义方法和事件,这些方法和事件在服务端运行,并可被其他插件或客户端调用。
定义
使用
Rpc.define 来列出 RPC 的方法、错误和事件。import { Rpc } from "@opencode/plugin/rpc"
export const Acme = Rpc.define({
id: "acme",
methods: {
search: {
input: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false,
},
output: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
additionalProperties: false,
},
errors: {
not_found: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false,
},
},
},
},
events: {
updated: {
schema: {
type: "object",
properties: {
itemID: { type: "string" },
text: { type: "string" },
},
required: ["itemID", "text"],
additionalProperties: false,
},
},
},
})
校验
RPC 定义描述了方法、事件和错误的输入与输出结构。它支持两种 schema 格式:
- JSON Schema,简单且无需依赖。
- 任何符合 Standard Schema 的校验器
- Zod
- Valibot
- ArkType
输入与输出
每个方法都可以为其参数定义
input schema,并为其返回值定义 output schema。如果方法不接受或不返回某个值,可以省略其中之一。search: {
input: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
},
output: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
}
JSON Schema 的值在 TypeScript 中是
unknown,因此在使用前需要先做类型收窄。Standard Schema 会推断输入和输出的类型。错误
在方法上添加
errors 映射来声明预期的失败。每个键会成为错误的 type,其 schema 定义错误的 data。errors: {
not_found: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false,
},
}
以
rpc. 开头的错误名称由 OpenCode 保留。事件
将事件添加到顶层的
events 映射中。每个事件都有一个 schema,用于描述发送给订阅者的数据。事件数据必须是对象。当没有数据时使用空对象 schema:
events: {
refreshed: {
schema: { type: "object", additionalProperties: false },
},
}
标量、数组、
null 和 undefined 不是有效的事件数据。实现
在
setup 中注册实现:import { Plugin } from "@opencode/plugin"
import { Acme } from "./rpc.js"
export default Plugin.define({
id: "acme-plugin",
async setup(ctx) {
const registration = await ctx.rpc.register(Acme, {
search: async (input, context) => {
const { query } = input as { query: string }
const text = await findText(query, { signal: context.signal })
if (!text) return context.error("not_found", "Result not found", { query })
return { text }
},
})
const acme = ctx.rpc(Acme)
const result = await acme.search({ query: "hello" })
await registration.events.emit("updated", { itemID: "item-1", text: "ready" })
},
})
注册 RPC 之后,同一插件可以通过
ctx.rpc(Acme) 调用它。第二个参数包含用于取消的
signal,以及用于已声明错误的 context.error(...)。你可以返回或抛出该错误。一个插件可以注册多个 RPC。销毁(dispose)注册会将其移除。
调用
RPC 注册完成后,可以通过 HTTP 调用,也可以从另一个插件调用。
HTTP
创建一个 OpenCode 客户端,然后使用
client.rpc 为该 RPC 创建子客户端:import { OpenCode } from "@opencode/client"
import { Acme } from "opencode-acme-plugin/rpc"
const client = OpenCode.make({
baseUrl: "http://localhost:4096",
})
const acme = client.rpc(Acme)
const result = await acme.search({ query: "hello" })
插件
插件已经拥有一个 OpenCode 客户端。例如,一个 TUI 插件可以从
context.client 创建相同的 RPC 子客户端:import { Plugin } from "@opencode/plugin/tui"
import { Acme } from "opencode-acme-plugin/rpc"
export default Plugin.define({
id: "acme-tui",
async setup(context) {
const acme = context.client.rpc(Acme)
const result = await acme.search({ query: "hello" })
console.log(result)
},
})
订阅
使用
events.on 注册回调,并在不再需要该监听器时取消订阅:const unsubscribe = acme.events.on("updated", (event) => {
console.log(event.type, event.location.directory, event.data.text)
})
unsubscribe()
若要使用异步可迭代对象,请使用
events.subscribe("updated"):for await (const event of acme.events.subscribe("updated")) {
console.log(event.data.text)
}
事件订阅仅为实时推送,因此断开连接的订阅者会错过事件。
- 使用本地名称订阅,例如
updated。 - 接收到的类型带有前缀,例如
rpc.acme.updated。 - 每个事件都包含
data和location。 - 插件卸载时会关闭其订阅。
外部客户端会接收来自所有位置的事件,因此在需要时请检查
event.location。服务端插件仍然需要配置并处于运行状态。