opencode/packages/plugin/src
2026-08-28 16:16:19 -05:00
..
effect feat(plugin): add session retry hook (#45999) 2026-08-28 16:16:19 -05:00
promise feat(plugin): add session retry hook (#45999) 2026-08-28 16:16:19 -05:00
tui feat(merman): refine diagram styling (#44815) 2026-08-25 16:07:12 +00:00
app.ts
options.ts
README.md feat(plugin): add session retry hook (#45999) 2026-08-28 16:16:19 -05:00
storage.ts feat(plugin): add durable storage API (#43525) 2026-08-19 21:51:58 +00:00
vcs.ts feat(plugin): add declarative vcs repository markers (#45192) 2026-08-26 13:49:36 +05:30

OpenCode V2 Promise Plugin API

The Promise plugin API at @opencode-ai/plugin is the async/await equivalent of @opencode-ai/plugin/effect. It grants plugins the same two in-process capabilities:

  • hook installs behavior at an OpenCode extension point.
  • reload reruns 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 draft callbacks remain synchronous.

Defining A Plugin

import { Plugin } from "@opencode-ai/plugin"

export default Plugin.define({
  id: "example",
  setup: async (ctx) => {
    await ctx.catalog.transform((catalog) => {
      catalog.provider.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.catalog.transform(applyCatalog)
await registration.dispose()

Transform Hooks

Transform hooks contribute to stateful domains. The draft 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.catalog.transform
ctx.command.transform
ctx.integration.transform
ctx.mcp.transform
ctx.reference.transform
ctx.skill.transform
ctx.tool.transform
ctx.vcs.transform
ctx.websearch.transform

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:

let data = await loadCatalog()

await ctx.catalog.transform((catalog) => {
  applyCatalog(data, catalog)
})

data = await loadCatalog()
await ctx.catalog.reload()

Available reload operations are:

ctx.agent.reload()
ctx.catalog.reload()
ctx.command.reload()
ctx.integration.reload()
ctx.mcp.reload()
ctx.reference.reload()
ctx.skill.reload()
ctx.tool.reload()
ctx.vcs.reload()
ctx.websearch.reload()