Compare commits

..
Author SHA1 Message Date
Kit Langton 3369c08ffa docs(plugin): update current API examples 2026-08-27 15:00:32 -04:00
5 changed files with 39 additions and 48 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"@opencode-ai/core": patch
---
Forward plugin Session interrupt options so continuation requests reach the Session service.
+1 -1
View File
@@ -76,7 +76,7 @@ export const layerWithCell = (cell: Cell) =>
resume: (sessionID) => require(cell, (runtime) => runtime.session.resume(sessionID)),
switchAgent: (input) => require(cell, (runtime) => runtime.session.switchAgent(input)),
switchModel: (input) => require(cell, (runtime) => runtime.session.switchModel(input)),
interrupt: (sessionID, options) => require(cell, (runtime) => runtime.session.interrupt(sessionID, options)),
interrupt: (sessionID) => require(cell, (runtime) => runtime.session.interrupt(sessionID)),
synthetic: (input) => require(cell, (runtime) => runtime.session.synthetic(input)),
wait: (sessionID) => require(cell, (runtime) => runtime.session.wait(sessionID)),
context: (sessionID) => require(cell, (runtime) => runtime.session.context(sessionID)),
-30
View File
@@ -113,36 +113,6 @@ describe("Plugin", () => {
}),
)
it.effect("forwards session interrupt options through the runtime cell", () =>
Effect.gen(function* () {
const plugins = yield* Plugin.Service
const fallback = yield* PluginRuntime.Service
const cell = PluginRuntime.makeCell()
const sessionID = Session.ID.create()
const calls: Array<{ sessionID: Session.ID; options: { continue?: boolean } | undefined }> = []
cell.runtime = {
...fallback,
session: {
...fallback.session,
interrupt: (id, options) =>
Effect.sync(() => {
calls.push({ sessionID: id, options })
return true
}),
},
}
const runtime = yield* PluginRuntime.Service.pipe(Effect.provide(PluginRuntime.layerWithCell(cell)))
const host = yield* PluginHost.make(plugins).pipe(Effect.provideService(PluginRuntime.Service, runtime))
expect(yield* runtime.session.interrupt(sessionID)).toBe(true)
expect(yield* host.session.interrupt({ sessionID, continue: true })).toEqual({ interrupted: true })
expect(calls).toEqual([
{ sessionID, options: undefined },
{ sessionID, options: { continue: true } },
])
}),
)
it.effect("registers and removes scoped VCS providers", () =>
Effect.gen(function* () {
const plugins = yield* Plugin.Service
+21 -7
View File
@@ -5,7 +5,9 @@ The Promise plugin API at `@opencode-ai/plugin` is the async/await equivalent of
- `hook` installs behavior at an OpenCode extension point.
- `reload` reruns every transform hook for a stateful domain.
The only difference from the Effect API is the async boundary: hook callbacks, hook registration, `reload`, and `Registration.dispose` use Promises instead of Effects.
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
@@ -46,12 +48,15 @@ await registration.dispose()
## Transform Hooks
Transform hooks contribute to stateful domains. The draft editor is synchronous; the callback may be `async` when it needs to await other work:
Transform hooks contribute to stateful domains. The draft editor is synchronous,
so load asynchronous data before registering a transform or reloading its domain:
```ts
const description = await loadReviewerDescription()
await ctx.agent.transform((agent) => {
agent.update("reviewer", (item) => {
item.description = "Reviews code for regressions"
item.description = description
item.mode = "subagent"
})
})
@@ -64,8 +69,12 @@ 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
@@ -81,7 +90,7 @@ await ctx.aisdk.hook("sdk", async (event) => {
await ctx.aisdk.hook("language", (event) => {
if (event.model.providerID !== "xai") return
event.language = event.sdk.responses(event.model.api.id)
event.language = event.sdk.responses(event.model.modelID)
})
```
@@ -94,14 +103,15 @@ await ctx.session.hook("context", (event) => {
})
```
Promise tools use executable tool values with async executors. Registration
supplies the tool's name and options separately:
Promise tools use complete executable tool values with async executors:
```ts
import { Schema } from "effect"
await ctx.tool.transform((tools) => {
tools.add("echo", {
tools.add({
name: "echo",
options: { codemode: false },
description: "Echo text",
input: Schema.Struct({ text: Schema.String }),
output: Schema.Struct({ text: Schema.String }),
@@ -132,6 +142,10 @@ 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()
```
+17 -5
View File
@@ -31,7 +31,9 @@ Registrations are owned by the plugin scope. Closing the scope removes them auto
## Transform Hooks
Transform hooks contribute to stateful domains:
Transform hooks contribute to stateful domains. Their draft callbacks are
synchronous, so load effectful data before registering a transform or reloading
its domain:
```ts
yield *
@@ -52,8 +54,12 @@ 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
@@ -72,10 +78,12 @@ yield *
)
yield *
ctx.aisdk.hook("language", (event) => {
if (event.model.providerID !== "xai") return
event.language = event.sdk.responses(event.model.api.id)
})
ctx.aisdk.hook("language", (event) =>
Effect.sync(() => {
if (event.model.providerID !== "xai") return
event.language = event.sdk.responses(event.model.modelID)
}),
)
```
Hooks run sequentially in registration order. Later hooks observe mutations made by earlier hooks.
@@ -117,6 +125,10 @@ 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()
```