Effect 插件可以暴露返回带类型 Effect 与 Stream 的方法和事件。
定义
使用
Rpc.define 配合 Effect Schema 来定义 RPC。import { Rpc } from "@opencode/plugin/rpc"
import { Schema } from "effect"
export const Acme = Rpc.define({
id: "acme",
methods: {
search: {
input: Schema.Struct({ query: Schema.String }),
output: Schema.Struct({ text: Schema.String }),
errors: {
not_found: Schema.Struct({ query: Schema.String }),
},
},
},
events: {
updated: {
schema: Schema.Struct({ itemID: Schema.String, text: Schema.String }),
},
},
})
校验
Effect Schema 会对值进行校验,并推断出它们的 TypeScript 类型。
input: Schema.Struct({ query: Schema.String })
同时支持 JSON Schema 和 Standard Schema。
输入与输出
用
input 表示方法参数,用 output 表示其返回值。若没有值,可省略其中之一。search: {
input: Schema.Struct({ query: Schema.String }),
output: Schema.Struct({ text: Schema.String }),
}
错误
将预期的失败情况添加到
errors 中。每个键会成为错误的 type。errors: {
not_found: Schema.Struct({ query: Schema.String }),
}
事件
将事件添加到顶层的
events 映射中。事件数据必须是对象。events: {
updated: {
schema: Schema.Struct({ itemID: Schema.String, text: Schema.String }),
},
}
实现
在插件的 Effect 中注册实现:
import { Plugin } from "@opencode/plugin/effect"
import { Effect } from "effect"
import { Acme } from "./rpc.js"
export default Plugin.define({
id: "acme-effect-plugin",
effect: (ctx) =>
Effect.gen(function* () {
const registration = yield* ctx.rpc.register(Acme, {
search: ({ query }, context) =>
findText(query).pipe(
Effect.flatMap((text) =>
text
? Effect.succeed({ text })
: Effect.fail(context.error("not_found", "Result not found", { query })),
),
),
})
const acme = ctx.rpc(Acme)
const result = yield* acme.search({ query: "hello" })
yield* registration.events.emit("updated", { itemID: "item-1", text: result.text })
}).pipe(Effect.orDie),
})
错误映射会成为各个方法 Effect 的错误类型。使用
context.error(...) 创建已声明的错误。调用
RPC 注册后,即可通过 HTTP 或从其他插件调用。
HTTP
创建 Effect 客户端,然后创建 RPC 子客户端:
import { OpenCode } from "@opencode/client/effect"
import { Effect } from "effect"
import { FetchHttpClient } from "effect/http"
import { Acme } from "opencode-acme-plugin/rpc"
const program = Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:4096" })
const acme = client.rpc(Acme)
return yield* acme.search({ query: "hello" })
})
const result = await Effect.runPromise(program.pipe(Effect.provide(FetchHttpClient.layer)))
插件
另一个 Effect 插件可以从其上下文创建本地子客户端:
import { Effect } from "effect"
effect: (ctx) =>
Effect.gen(function* () {
const acme = ctx.rpc(Acme)
const result = yield* acme.search({ query: "hello" })
yield* Effect.logInfo(result.text)
}).pipe(Effect.orDie)
订阅
RPC 事件是 Stream。按事件名称订阅,并在有作用域的 fiber 中运行该 Stream:
import { Effect, Stream } from "effect"
const acme = ctx.rpc(Acme)
yield* acme.events.subscribe("updated").pipe(
Stream.runForEach((event) => Effect.logInfo(event.data.text)),
Effect.forkScoped,
)
订阅仅在实时状态下有效,Stream 消费停止时会关闭。