OpenCode V2 Promise Plugin API
The Promise plugin API at @opencode/plugin is the async/await equivalent of @opencode/plugin/effect. It grants plugins the same two in-process capabilities:
hookinstalls behavior at an OpenCode extension point.reloadreruns every transform hook for a stateful domain.
The Promise API uses Promises instead of Effects for setup, runtime hook
callbacks, hook registration, reload, and Registration.dispose. Transform
editor callbacks remain synchronous.
Defining A Plugin
import { Plugin } from "@opencode/plugin"
export default Plugin.define({
id: "example",
setup: async (ctx) => {
await ctx.provider.transform((editor) => {
editor.update("example", (provider) => {
provider.name = "Example"
})
})
},
})
Plugin setup registers hooks imperatively through each domain's hook method.
It may return a synchronous or asynchronous cleanup function. OpenCode awaits
the cleanup when the plugin is unloaded or replaced:
setup: async (ctx) => {
const timer = setInterval(refresh, 60_000)
return () => clearInterval(timer)
}
Configuration supplied for the plugin is available as ctx.options.
A registration may be removed early through dispose:
const registration = await ctx.model.transform(applyModelPolicy)
await registration.dispose()
Transform Hooks
Transform hooks contribute to stateful domains. The editor is synchronous, so load asynchronous data before registering a transform or reloading its domain:
const description = await loadReviewerDescription()
await ctx.agent.transform((agent) => {
agent.update("reviewer", (item) => {
item.description = description
item.mode = "subagent"
})
})
Available transform hooks are namespaced by domain:
ctx.agent.transform
ctx.command.transform
ctx.integration.transform
ctx.mcp.transform
ctx.model.transform
ctx.provider.transform
ctx.reference.transform
ctx.skill.transform
ctx.tool.transform
ctx.vcs.transform
ctx.websearch.transform
Provider transforms contribute provider settings and immutable model definitions. After provider availability is resolved,
model transforms edit the complete active-provider candidate collection in order. Use ctx.model.transform for runtime
model restrictions; editor.provider.get() reads source templates even when their provider is inactive.
await ctx.model.transform((editor) => {
editor
.list()
.filter((model) => model.cost.some((tier) => tier.output > 20))
.forEach((model) => {
editor.remove(model.providerID, model.id)
})
})
Runtime Hooks
Runtime hooks intercept live operations:
await ctx.aisdk.hook("sdk", async (event) => {
if (event.package !== "@ai-sdk/xai") return
const mod = await import("@ai-sdk/xai")
event.sdk = mod.createXai(event.options)
})
await ctx.aisdk.hook("language", (event) => {
if (event.model.providerID !== "xai") return
event.language = event.sdk.responses(event.model.modelID)
})
Session context is mutable immediately before provider dispatch:
await ctx.session.hook("context", (event) => {
event.tools.read.description = "Read a file using narrow line ranges."
delete event.tools.write
})
await ctx.session.hook("retry", (event) => {
if (event.attempt >= 3) event.decision = { retry: false }
})
Promise tools use complete executable tool values with async executors:
import { Schema } from "effect"
await ctx.tool.transform((tools) => {
tools.add({
name: "echo",
options: { codemode: false },
description: "Echo text",
input: Schema.Struct({ text: Schema.String }),
output: Schema.Struct({ text: Schema.String }),
execute: async ({ text }) => ({ output: { text }, content: text }),
})
})
Reloading A Domain
When data captured by a transform changes, reload the affected domain:
const source = { providers: await loadProviders() }
await ctx.provider.transform((editor) => {
source.providers.forEach((provider) => editor.add(provider))
})
source.providers = await loadProviders()
await ctx.provider.reload()
loadProviders() returns entries shaped as { info: Provider.Info, models: readonly Model.Info[] }. Provider reloads
also invalidate the active model result, so every model transform runs again with the refreshed definitions. Model
callbacks edit raw overrides; provider defaults are merged once when the result is committed.
Available reload operations are:
ctx.agent.reload()
ctx.command.reload()
ctx.integration.reload()
ctx.mcp.reload()
ctx.model.reload()
ctx.provider.reload()
ctx.reference.reload()
ctx.skill.reload()
ctx.tool.reload()
ctx.vcs.reload()
ctx.websearch.reload()