feat(client): add root promise entrypoint
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
|
".": "./src/promise/index.ts",
|
||||||
"./promise": "./src/promise/index.ts",
|
"./promise": "./src/promise/index.ts",
|
||||||
"./promise/api": "./src/promise/api.ts",
|
"./promise/api": "./src/promise/api.ts",
|
||||||
"./effect": "./src/effect/index.ts",
|
"./effect": "./src/effect/index.ts",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const server = resolve(import.meta.dir, "../../server")
|
|||||||
|
|
||||||
describe("public import boundaries", () => {
|
describe("public import boundaries", () => {
|
||||||
test("isolates each public entrypoint", async () => {
|
test("isolates each public entrypoint", async () => {
|
||||||
const root = await bundleInputs("@opencode-ai/client/promise", "browser")
|
const root = await bundleInputs("@opencode-ai/client", "browser")
|
||||||
|
|
||||||
expect(within(root, effect)).toEqual([])
|
expect(within(root, effect)).toEqual([])
|
||||||
expect(within(root, schema)).toEqual([])
|
expect(within(root, schema)).toEqual([])
|
||||||
|
|||||||
Vendored
+68
-14
@@ -16,22 +16,15 @@ network. Its types and methods are generated from the same contract as the
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun add @opencode-ai/client
|
bun add @opencode-ai/client@next
|
||||||
```
|
```
|
||||||
|
|
||||||
The package has two entrypoints:
|
## Create a client
|
||||||
|
|
||||||
- `@opencode-ai/client/promise` uses `fetch` and returns Promises or async
|
|
||||||
iterables. It has no Effect runtime dependency.
|
|
||||||
- `@opencode-ai/client/effect` returns Effects and Streams, decodes values into
|
|
||||||
the V2 schema types, and requires an `HttpClient` service from Effect.
|
|
||||||
|
|
||||||
## Promise client
|
|
||||||
|
|
||||||
Create a client with the server URL, then call methods grouped by API resource:
|
Create a client with the server URL, then call methods grouped by API resource:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { OpenCode } from "@opencode-ai/client/promise"
|
import { OpenCode } from "@opencode-ai/client"
|
||||||
|
|
||||||
const client = OpenCode.make({
|
const client = OpenCode.make({
|
||||||
baseUrl: "http://localhost:4096",
|
baseUrl: "http://localhost:4096",
|
||||||
@@ -47,11 +40,28 @@ await client.session.prompt({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Headers and requests
|
||||||
|
|
||||||
Pass default authentication or application headers to `OpenCode.make` with
|
Pass default authentication or application headers to `OpenCode.make` with
|
||||||
`headers`. You can also supply a custom `fetch` implementation. Each operation
|
`headers`. You can also supply a custom `fetch` implementation. Each operation
|
||||||
accepts request options as its final argument for an `AbortSignal` or
|
accepts request options as its final argument for an `AbortSignal` or
|
||||||
per-request headers.
|
per-request headers.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const client = OpenCode.make({
|
||||||
|
baseUrl: "https://opencode.example.com",
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${process.env.OPENCODE_TOKEN}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await client.session.list(undefined, {
|
||||||
|
signal: AbortSignal.timeout(10_000),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stream events
|
||||||
|
|
||||||
Streaming endpoints return async iterables:
|
Streaming endpoints return async iterables:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -60,11 +70,17 @@ for await (const event of client.event.subscribe()) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Effect client
|
## Effect
|
||||||
|
|
||||||
Install the `effect` peer dependency when using the Effect entrypoint. The
|
OpenCode provides a first-class Effect client through the
|
||||||
client uses canonical V2 values such as `Location.Ref` and `Session.ID`, and
|
`@opencode-ai/client/effect` entrypoint. It returns typed Effects and Streams
|
||||||
returns typed failures in the Effect error channel.
|
and decodes responses into OpenCode schema values.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun add @opencode-ai/client@next effect
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a client
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { AbsolutePath, Location, OpenCode } from "@opencode-ai/client/effect"
|
import { AbsolutePath, Location, OpenCode } from "@opencode-ai/client/effect"
|
||||||
@@ -89,3 +105,41 @@ const session = await Effect.runPromise(
|
|||||||
|
|
||||||
Streaming operations, including `client.event.subscribe()` and
|
Streaming operations, including `client.event.subscribe()` and
|
||||||
`client.session.log(...)`, return Effect `Stream` values.
|
`client.session.log(...)`, return Effect `Stream` values.
|
||||||
|
|
||||||
|
### Service
|
||||||
|
|
||||||
|
`Service` discovers and manages the local OpenCode background service from a
|
||||||
|
Node application:
|
||||||
|
|
||||||
|
- `Service.discover()` returns a healthy registered endpoint without starting
|
||||||
|
a process.
|
||||||
|
- `Service.start()` reuses a compatible service or starts one when needed.
|
||||||
|
- `Service.stop()` stops the registered service.
|
||||||
|
- `Service.headers(endpoint)` creates the authentication headers for a client.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun add @effect/platform-node
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { NodeFileSystem } from "@effect/platform-node"
|
||||||
|
import { OpenCode, Service } from "@opencode-ai/client/effect"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { FetchHttpClient } from "effect/unstable/http"
|
||||||
|
|
||||||
|
const program = Effect.gen(function* () {
|
||||||
|
const endpoint = yield* Service.start()
|
||||||
|
const client = yield* OpenCode.make({
|
||||||
|
baseUrl: endpoint.url,
|
||||||
|
headers: Service.headers(endpoint),
|
||||||
|
})
|
||||||
|
return yield* client.health.get()
|
||||||
|
})
|
||||||
|
|
||||||
|
const health = await Effect.runPromise(
|
||||||
|
program.pipe(
|
||||||
|
Effect.provide(FetchHttpClient.layer),
|
||||||
|
Effect.provide(NodeFileSystem.layer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|||||||
Vendored
+86
-41
@@ -5,12 +5,12 @@ description: "Extend OpenCode with plugins."
|
|||||||
|
|
||||||
Plugins extend OpenCode in-process. They can transform agents, models, commands,
|
Plugins extend OpenCode in-process. They can transform agents, models, commands,
|
||||||
integrations, references, skills, and tools; intercept model requests and tool
|
integrations, references, skills, and tools; intercept model requests and tool
|
||||||
execution; and call a location-scoped subset of the V2 client.
|
execution; and call a subset of the V2 client.
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
The V2 plugin API is beta. Entrypoints, hooks, draft shapes, and configuration
|
The V2 plugin API is beta. Entrypoints, hooks, draft shapes, and configuration
|
||||||
may change before the stable release. Use only the `/v2` exports described on
|
may change before the stable release. Use the `/v2` exports described on this
|
||||||
this page; the root `@opencode-ai/plugin` API is the legacy API.
|
page.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
## Load plugins
|
## Load plugins
|
||||||
@@ -112,7 +112,7 @@ visible from the plugin file, for example:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd .opencode
|
cd .opencode
|
||||||
bun add @opencode-ai/plugin
|
bun add @opencode-ai/plugin@next
|
||||||
```
|
```
|
||||||
|
|
||||||
Match the plugin package version to the OpenCode release you target.
|
Match the plugin package version to the OpenCode release you target.
|
||||||
@@ -147,14 +147,15 @@ export default Plugin.define({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
`setup` runs each time the plugin is activated for a Location. Register
|
`setup` runs each time the plugin is activated. Register long-lived behavior
|
||||||
long-lived behavior during setup; do not wait there on an infinite event
|
during setup; do not wait there on an infinite event stream.
|
||||||
stream.
|
|
||||||
|
|
||||||
## Context
|
### Context
|
||||||
|
|
||||||
Context methods return Promises. Read and action methods use the same inputs
|
The plugin context is essentially an [OpenCode server client](/build/client).
|
||||||
and location-aware responses as the V2 client APIs.
|
Its read and action methods use the same inputs and responses as the client. It
|
||||||
|
adds plugin-only methods for transforms, runtime hooks, reloads, registrations,
|
||||||
|
and plugin options.
|
||||||
|
|
||||||
| Capability | Available operations |
|
| Capability | Available operations |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -173,16 +174,11 @@ and location-aware responses as the V2 client APIs.
|
|||||||
| `ctx.event` | `subscribe` to the current public server event stream |
|
| `ctx.event` | `subscribe` to the current public server event stream |
|
||||||
| `ctx.options` | Readonly options from the matching config object |
|
| `ctx.options` | Readonly options from the matching config object |
|
||||||
|
|
||||||
Unlike the legacy API, V2 does not provide `$`, `directory`, `worktree`, or a
|
|
||||||
general SDK client on the context. A plugin is Location-scoped, and the exposed
|
|
||||||
domain clients apply that Location by default.
|
|
||||||
|
|
||||||
### Transform hooks
|
### Transform hooks
|
||||||
|
|
||||||
Transforms synchronously edit a draft whenever a stateful domain is built.
|
Transform hooks let a plugin modify how OpenCode is configured. Use them to add
|
||||||
Registering or disposing a transform rebuilds the domain from fresh state and
|
or remove definitions, override settings, choose defaults, and provide tools or
|
||||||
runs all active transforms in order. Call the domain's `reload()` method when
|
other sources.
|
||||||
external data captured by a transform changes.
|
|
||||||
|
|
||||||
| Transform | Draft operations |
|
| Transform | Draft operations |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -194,10 +190,41 @@ external data captured by a transform changes.
|
|||||||
| `skill.transform` | `source`, `list` |
|
| `skill.transform` | `source`, `list` |
|
||||||
| `tool.transform` | `add` |
|
| `tool.transform` | `add` |
|
||||||
|
|
||||||
Hook registrations are owned by the plugin scope. Transform and runtime hook
|
Here's an example that keeps models synced from a remote source:
|
||||||
calls also return a `Registration` with `dispose` for explicit cleanup. Tool
|
|
||||||
contributions currently remain until the owning plugin scope closes, so prefer
|
```js title=".opencode/plugins/remote-models.js"
|
||||||
scope cleanup for plugin-wide teardown while this API is beta.
|
import { Plugin } from "@opencode-ai/plugin/v2"
|
||||||
|
|
||||||
|
export default Plugin.define({
|
||||||
|
id: "acme.remote-models",
|
||||||
|
setup: async (ctx) => {
|
||||||
|
let models = []
|
||||||
|
|
||||||
|
await ctx.catalog.transform((catalog) => {
|
||||||
|
for (const model of models) {
|
||||||
|
catalog.model.update(model.providerID, model.id, (draft) => Object.assign(draft, model))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
const response = await fetch("https://example.com/opencode/models.json", {
|
||||||
|
signal: AbortSignal.timeout(10_000),
|
||||||
|
})
|
||||||
|
if (!response.ok) return
|
||||||
|
models = await response.json()
|
||||||
|
await ctx.catalog.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
await refresh()
|
||||||
|
setInterval(() => void refresh().catch(console.error), 60_000)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`ctx.catalog.reload()` replays every catalog transform to derive the new
|
||||||
|
catalog. Each plugin's logic remains composed with the others, so a later
|
||||||
|
plugin can still modify models added by an earlier one. The catalog updates
|
||||||
|
without restarting OpenCode.
|
||||||
|
|
||||||
### Runtime hooks
|
### Runtime hooks
|
||||||
|
|
||||||
@@ -236,7 +263,9 @@ export default Plugin.define({
|
|||||||
A hook failure fails the operation it intercepts. Keep runtime hooks fast and
|
A hook failure fails the operation it intercepts. Keep runtime hooks fast and
|
||||||
handle expected errors inside the callback.
|
handle expected errors inside the callback.
|
||||||
|
|
||||||
## Add a tool
|
## Examples
|
||||||
|
|
||||||
|
### Add a tool
|
||||||
|
|
||||||
Pass a tool declaration to `tools.add`. Define its input with JSON Schema and
|
Pass a tool declaration to `tools.add`. Define its input with JSON Schema and
|
||||||
use an async executor:
|
use an async executor:
|
||||||
@@ -284,22 +313,38 @@ configure registration with `{ group, deferred }`:
|
|||||||
The executor receives a second context argument containing `sessionID`,
|
The executor receives a second context argument containing `sessionID`,
|
||||||
`agent`, `assistantMessageID`, and `toolCallID`.
|
`agent`, `assistantMessageID`, and `toolCallID`.
|
||||||
|
|
||||||
## Types
|
### Add a command
|
||||||
|
|
||||||
`Plugin.define` infers the context and callbacks. The package also
|
```js title=".opencode/plugins/review-command.js"
|
||||||
re-exports the canonical `Agent`, `Command`, `Connection`, `Credential`,
|
import { Plugin } from "@opencode-ai/plugin/v2"
|
||||||
`Integration`, `Model`, `Provider`, `Reference`, and `Skill` schema namespaces.
|
|
||||||
Import narrower API types from their public subpaths when needed:
|
|
||||||
|
|
||||||
```ts
|
export default Plugin.define({
|
||||||
import { Plugin, Model } from "@opencode-ai/plugin/v2"
|
id: "acme.review-command",
|
||||||
import type { Context } from "@opencode-ai/plugin/v2/plugin"
|
setup: async (ctx) => {
|
||||||
import type { AgentDraft } from "@opencode-ai/plugin/v2/agent"
|
await ctx.command.transform((commands) => {
|
||||||
import type { ToolExecuteBeforeEvent } from "@opencode-ai/plugin/v2/tool"
|
commands.update("review", (command) => {
|
||||||
|
command.description = "Review the current changes"
|
||||||
|
command.template = "Review the current changes for correctness and missing tests."
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Avoid importing types or runtime values from `@opencode-ai/core` or
|
### Set the default model
|
||||||
`@opencode-ai/server`; those are private host implementation details.
|
|
||||||
|
```js title=".opencode/plugins/default-model.js"
|
||||||
|
import { Plugin } from "@opencode-ai/plugin/v2"
|
||||||
|
|
||||||
|
export default Plugin.define({
|
||||||
|
id: "acme.default-model",
|
||||||
|
setup: async (ctx) => {
|
||||||
|
await ctx.catalog.transform((catalog) => {
|
||||||
|
catalog.model.default.set("anthropic", "claude-sonnet-4-5")
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Publish a package
|
## Publish a package
|
||||||
|
|
||||||
@@ -313,7 +358,7 @@ manifest is:
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./src/index.ts",
|
"exports": "./src/index.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/plugin": "1.17.18"
|
"@opencode-ai/plugin": "next"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -325,7 +370,7 @@ change.
|
|||||||
|
|
||||||
## Verify loading
|
## Verify loading
|
||||||
|
|
||||||
List active plugin IDs for the current Location through the V2 API:
|
List active plugin IDs through the V2 API:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
opencode2 api get /api/plugin
|
opencode2 api get /api/plugin
|
||||||
@@ -338,12 +383,12 @@ being resolved.
|
|||||||
|
|
||||||
## Effect
|
## Effect
|
||||||
|
|
||||||
Plugins built with Effect use the `@opencode-ai/plugin/v2/effect` entrypoint.
|
OpenCode provides a first-class Effect API for plugins through the
|
||||||
Install `effect` alongside the plugin package and export an `effect` function
|
`@opencode-ai/plugin/v2/effect` entrypoint. Install `effect` alongside the
|
||||||
instead of `setup`:
|
plugin package and export an `effect` function instead of `setup`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bun add @opencode-ai/plugin effect
|
bun add @opencode-ai/plugin@next effect
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts title=".opencode/plugins/reviewer-effect.ts"
|
```ts title=".opencode/plugins/reviewer-effect.ts"
|
||||||
|
|||||||
Reference in New Issue
Block a user