cloudaxe-opencode/packages/plugin/src/v2/effect
Dax Raad d972aa9d83 feat(core): tool execute hooks, session instructions, synthetic endpoint
- V2 tool execute hooks: add `ctx.tool.execute.before/after` plugin API,
  core ToolHooks service, and registry wiring so hosted/local tool calls
  run registered before/after hooks; provider-executed calls are excluded.
- Session synthetic endpoint: add `POST /api/session/:sessionID/synthetic`
  with text/description/metadata plus regenerated Promise/Effect/JS client
  surfaces.
- SessionInstructions service: read tool discovers nearby AGENTS.md walking
  up to the Location root (exclusive) and injects them as durable synthetic
  instructions, with lazy history dedup of prior claims.
- to-llm-message: stop forwarding synthetic message metadata to the model so
  bookkeeping annotations stay model-hidden.
- schema changelog entry for synthetic metadata and the metadata leak fix.
2026-07-01 20:50:10 -04:00
..
generated feat(core): tool execute hooks, session instructions, synthetic endpoint 2026-07-01 20:50:10 -04:00
agent.ts feat(plugin): support plugin-provided tools (#34619) 2026-06-30 13:18:56 -04:00
aisdk.ts
catalog.ts
command.ts
context.ts feat(plugin): support plugin-provided tools (#34619) 2026-06-30 13:18:56 -04:00
event.ts
filesystem.ts
index.ts feat(core): tool execute hooks, session instructions, synthetic endpoint 2026-07-01 20:50:10 -04:00
integration.ts chore: generate 2026-06-23 23:46:36 +00:00
location.ts
npm.ts
path.ts
PLAN.md
plugin.ts feat(core): add opencode integration (#33555) 2026-06-23 23:45:02 +00:00
README.md
reference.ts
registration.ts
runtime.ts feat(plugin): support plugin-provided tools (#34619) 2026-06-30 13:18:56 -04:00
skill.ts feat(core): add opencode integration (#33555) 2026-06-23 23:45:02 +00:00
tool.ts feat(core): tool execute hooks, session instructions, synthetic endpoint 2026-07-01 20:50:10 -04:00

OpenCode V2 Effect Plugin API

The Effect plugin API grants plugins two in-process capabilities:

  • hook installs behavior at an OpenCode extension point.
  • reload reruns every transform hook for a stateful domain.

The public server client will be exposed separately. It is intentionally not part of PluginContext yet.

Defining A Plugin

import { define } from "@opencode-ai/plugin/v2/effect"
import { Effect } from "effect"

export const Plugin = define({
  id: "example",
  effect: Effect.fn(function* (ctx) {
    yield* ctx.catalog.transform((catalog) => {
      catalog.provider.update("example", (provider) => {
        provider.name = "Example"
      })
    })
  }),
})

Plugin setup registers hooks imperatively. It does not return a hook object.

Configuration supplied for the plugin is available as ctx.options.

Registrations are owned by the plugin scope. Closing the scope removes them automatically; a registration may also be removed early through dispose.

Transform Hooks

Transform hooks contribute to stateful domains:

yield *
  ctx.agent.transform((agent) => {
    agent.update("reviewer", (item) => {
      item.description = "Reviews code for regressions"
      item.mode = "subagent"
    })
  })

OpenCode rebuilds the domain when a transform is registered or disposed. A rebuild starts from fresh domain state and runs every active transform in registration order.

Available transform hooks are namespaced by domain:

ctx.agent.transform
ctx.catalog.transform
ctx.command.transform
ctx.integration.transform
ctx.reference.transform
ctx.skill.transform

Runtime Hooks

Runtime hooks intercept live operations rather than rebuilding domain state:

yield *
  ctx.aisdk.sdk(
    Effect.fn(function* (event) {
      if (event.package !== "@ai-sdk/xai") return
      const mod = yield* Effect.promise(() => import("@ai-sdk/xai"))
      event.sdk = mod.createXai(event.options)
    }),
  )

yield *
  ctx.aisdk.language((event) => {
    if (event.model.providerID !== "xai") return
    event.language = event.sdk.responses(event.model.api.id)
  })

Hooks run sequentially in registration order. Later hooks observe mutations made by earlier hooks.

Reloading A Domain

When data captured by a transform changes, reload the affected domain:

let data = yield * loadCatalog()

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

data = yield * loadCatalog()
yield * ctx.catalog.reload()

Reload belongs to the domain, not an individual registration. ctx.catalog.reload() reruns every active catalog transform and publishes the rebuilt catalog.

Available reload operations are:

ctx.agent.reload()
ctx.catalog.reload()
ctx.command.reload()
ctx.integration.reload()
ctx.reference.reload()
ctx.skill.reload()