Merge remote-tracking branch 'origin/v2' into search-integration

# Conflicts:
#	packages/client/src/promise/generated/client.ts
#	packages/client/src/promise/generated/types.ts
#	packages/client/test/promise.test.ts
#	packages/core/src/plugin/host.ts
#	packages/core/src/plugin/internal.ts
#	packages/core/src/plugin/promise.ts
#	packages/core/test/plugin/host.ts
#	packages/plugin/src/v2/effect/index.ts
#	packages/plugin/src/v2/effect/integration.ts
#	packages/plugin/src/v2/promise/index.ts
#	packages/plugin/src/v2/promise/integration.ts
#	packages/protocol/src/client.ts
#	packages/schema/src/index.ts
#	packages/sdk/js/src/v2/gen/sdk.gen.ts
This commit is contained in:
Shoubhit Dash 2026-07-08 15:09:39 +05:30
commit a6acc2397d
374 changed files with 15254 additions and 10009 deletions

View file

@ -475,6 +475,7 @@ export type TuiHostSlotMap = {
}
sidebar_footer: {
session_id: string
directory: string
}
}

View file

@ -176,7 +176,7 @@ Both use the same low-level scoped registration registry, but consumers invoke t
```ts
ctx.tool.transform(...) // replayed to build effective tool registry state
ctx.tool.hook(...) // invoked at a live tool operation boundary
ctx.tool.hook(...) // invoked at a live tool operation boundary
```
The shared low-level machinery owns registration order, scope cleanup, disposal, and snapshots. Each domain owns when its transforms or runtime hooks execute.

View file

@ -5,15 +5,13 @@ 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
```ts
import { define } from "@opencode-ai/plugin/v2/effect"
import { Plugin } from "@opencode-ai/plugin/v2/effect"
import { Effect } from "effect"
export const Plugin = define({
export default Plugin.define({
id: "example",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform((catalog) => {
@ -25,7 +23,7 @@ export const Plugin = define({
})
```
Plugin setup registers hooks imperatively. It does not return a hook object.
Plugin setup registers hooks imperatively through each domain's `hook` method.
Configuration supplied for the plugin is available as `ctx.options`.
@ -64,7 +62,8 @@ Runtime hooks intercept live operations rather than rebuilding domain state:
```ts
yield *
ctx.aisdk.sdk(
ctx.aisdk.hook(
"sdk",
Effect.fn(function* (event) {
if (event.package !== "@ai-sdk/xai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/xai"))
@ -73,7 +72,7 @@ yield *
)
yield *
ctx.aisdk.language((event) => {
ctx.aisdk.hook("language", (event) => {
if (event.model.providerID !== "xai") return
event.language = event.sdk.responses(event.model.api.id)
})
@ -81,6 +80,16 @@ yield *
Hooks run sequentially in registration order. Later hooks observe mutations made by earlier hooks.
Session request context is mutable immediately before provider dispatch:
```ts
yield *
ctx.session.hook("request", (event) => {
event.tools.read.description = "Read a file using narrow line ranges."
delete event.tools.write
})
```
## Reloading A Domain
When data captured by a transform changes, reload the affected domain:

View file

@ -1,17 +1,17 @@
import type { AgentApi } from "@opencode-ai/client/effect/api"
import type { AgentV2Info } from "@opencode-ai/sdk/v2/types"
import type { AgentInfo } from "@opencode-ai/sdk/v2/types"
import type { Effect } from "effect"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export interface AgentDraft {
list(): readonly AgentV2Info[]
get(id: string): AgentV2Info | undefined
list(): readonly AgentInfo[]
get(id: string): AgentInfo | undefined
default(id: string | undefined): void
update(id: string, update: (agent: AgentV2Info) => void): void
update(id: string, update: (agent: AgentInfo) => void): void
remove(id: string): void
}
export interface AgentHooks extends AgentApi<unknown> {
readonly transform: TransformHook<AgentDraft>
export interface AgentDomain extends AgentApi<unknown> {
readonly transform: Transform<AgentDraft>
readonly reload: () => Effect.Effect<void>
}

View file

@ -2,7 +2,7 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"
import type { Model } from "@opencode-ai/schema/model"
import type { Hooks } from "./registration.js"
export type AISDKHooks = Hooks<{
export interface AISDKHooks {
sdk: {
readonly model: Model.Info
readonly package: string
@ -15,4 +15,8 @@ export type AISDKHooks = Hooks<{
readonly options: Record<string, any>
language?: LanguageModelV3
}
}>
}
export interface AISDKDomain {
readonly hook: Hooks<AISDKHooks>
}

View file

@ -1,11 +1,11 @@
import type { ModelV2Info, ProviderV2Info } from "@opencode-ai/sdk/v2/types"
import type { ModelInfo, ProviderV2Info } from "@opencode-ai/sdk/v2/types"
import type { CatalogApi } from "@opencode-ai/client/effect/api"
import type { Effect } from "effect"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export interface CatalogProviderRecord {
readonly provider: ProviderV2Info
readonly models: ReadonlyMap<string, ModelV2Info>
readonly models: ReadonlyMap<string, ModelInfo>
}
export interface CatalogDraft {
@ -16,8 +16,8 @@ export interface CatalogDraft {
remove(providerID: string): void
}
readonly model: {
get(providerID: string, modelID: string): ModelV2Info | undefined
update(providerID: string, modelID: string, update: (model: ModelV2Info) => void): void
get(providerID: string, modelID: string): ModelInfo | undefined
update(providerID: string, modelID: string, update: (model: ModelInfo) => void): void
remove(providerID: string, modelID: string): void
readonly default: {
get(): { providerID: string; modelID: string } | undefined
@ -26,7 +26,7 @@ export interface CatalogDraft {
}
}
export interface CatalogHooks extends CatalogApi<unknown> {
readonly transform: TransformHook<CatalogDraft>
export interface CatalogDomain extends CatalogApi<unknown> {
readonly transform: Transform<CatalogDraft>
readonly reload: () => Effect.Effect<void>
}

View file

@ -1,16 +1,16 @@
import type { CommandV2Info } from "@opencode-ai/sdk/v2/types"
import type { CommandInfo } from "@opencode-ai/sdk/v2/types"
import type { CommandApi } from "@opencode-ai/client/effect/api"
import type { Effect } from "effect"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export interface CommandDraft {
list(): readonly CommandV2Info[]
get(name: string): CommandV2Info | undefined
update(name: string, update: (command: CommandV2Info) => void): void
list(): readonly CommandInfo[]
get(name: string): CommandInfo | undefined
update(name: string, update: (command: CommandInfo) => void): void
remove(name: string): void
}
export interface CommandHooks extends CommandApi<unknown> {
readonly transform: TransformHook<CommandDraft>
export interface CommandDomain extends CommandApi<unknown> {
readonly transform: Transform<CommandDraft>
readonly reload: () => Effect.Effect<void>
}

View file

@ -1,27 +0,0 @@
import type { PluginOptions } from "../options.js"
import type { AgentHooks } from "./agent.js"
import type { AISDKHooks } from "./aisdk.js"
import type { CatalogHooks } from "./catalog.js"
import type { CommandHooks } from "./command.js"
import type { EventHooks } from "./event.js"
import type { IntegrationHooks } from "./integration.js"
import type { PluginDomain } from "./plugin.js"
import type { ReferenceHooks } from "./reference.js"
import type { SkillHooks } from "./skill.js"
import type { ToolDomain } from "./tool.js"
import type { SessionHooks } from "./runtime.js"
export interface PluginContext {
readonly options: PluginOptions
readonly agent: AgentHooks
readonly aisdk: AISDKHooks
readonly catalog: CatalogHooks
readonly command: CommandHooks
readonly event: EventHooks
readonly integration: IntegrationHooks
readonly plugin: PluginDomain
readonly reference: ReferenceHooks
readonly skill: SkillHooks
readonly tool: ToolDomain
readonly session: SessionHooks
}

View file

@ -1,3 +1,3 @@
import type { EventApi } from "@opencode-ai/client/effect/api"
export interface EventHooks extends Pick<EventApi<unknown>, "subscribe"> {}
export interface EventDomain extends Pick<EventApi<unknown>, "subscribe"> {}

View file

@ -1,25 +1,4 @@
export type { PluginContext } from "./context.js"
export { define } from "./plugin.js"
export type { Plugin, PluginDomain } from "./plugin.js"
export type { AgentDraft, AgentHooks } from "./agent.js"
export type { AISDKHooks } from "./aisdk.js"
export type { CatalogDraft, CatalogHooks, CatalogProviderRecord } from "./catalog.js"
export type { CommandDraft, CommandHooks } from "./command.js"
export type { EventHooks } from "./event.js"
export type {
IntegrationDraft,
IntegrationDefinition,
IntegrationHooks,
IntegrationMethodDefinition,
IntegrationMethodRegistration,
IntegrationOAuthMethodDefinition,
IntegrationSearchDefinition,
} from "./integration.js"
export type { ReferenceDraft, ReferenceHooks } from "./reference.js"
export type { SkillDraft, SkillHooks } from "./skill.js"
export * as Tool from "./tool.js"
export type { ToolDomain, ToolDraft, ToolExecuteBeforeEvent, ToolExecuteAfterEvent } from "./tool.js"
export type { SessionHooks } from "./runtime.js"
export * as Plugin from "./plugin.js"
export { Agent } from "@opencode-ai/schema/agent"
export { Command } from "@opencode-ai/schema/command"

View file

@ -12,7 +12,7 @@ import type {
import type { IntegrationApi } from "@opencode-ai/client/effect/api"
import type { Search } from "@opencode-ai/schema/search"
import type { Effect, Scope } from "effect"
import type { Registration, TransformHook } from "./registration.js"
import type { Registration, Transform } from "./registration.js"
export type IntegrationOAuthAuthorization = {
readonly url: string
@ -80,9 +80,9 @@ export interface IntegrationDraft {
}
}
export interface IntegrationHooks extends IntegrationApi<unknown> {
export interface IntegrationDomain extends IntegrationApi<unknown> {
readonly register: (definition: IntegrationDefinition) => Effect.Effect<Registration, never, Scope.Scope>
readonly transform: TransformHook<IntegrationDraft>
readonly transform: Transform<IntegrationDraft>
readonly reload: () => Effect.Effect<void>
readonly connection: {
readonly active: (integrationID: string) => Effect.Effect<ConnectionInfo | undefined>

View file

@ -1,14 +1,37 @@
import type { PluginApi } from "@opencode-ai/client/effect/api"
import type { Effect, Scope } from "effect"
import type { PluginContext } from "./context.js"
import type { PluginOptions } from "../options.js"
import type { AgentDomain } from "./agent.js"
import type { AISDKDomain } from "./aisdk.js"
import type { CatalogDomain } from "./catalog.js"
import type { CommandDomain } from "./command.js"
import type { EventDomain } from "./event.js"
import type { IntegrationDomain } from "./integration.js"
import type { ReferenceDomain } from "./reference.js"
import type { SessionDomain } from "./session.js"
import type { SkillDomain } from "./skill.js"
import type { ToolDomain } from "./tool.js"
export interface Context {
readonly options: PluginOptions
readonly agent: AgentDomain
readonly aisdk: AISDKDomain
readonly catalog: CatalogDomain
readonly command: CommandDomain
readonly event: EventDomain
readonly integration: IntegrationDomain
readonly plugin: PluginApi<unknown>
readonly reference: ReferenceDomain
readonly session: SessionDomain
readonly skill: SkillDomain
readonly tool: ToolDomain
}
export interface Plugin<R = Scope.Scope> {
readonly id: string
readonly effect: (context: PluginContext) => Effect.Effect<void, never, R>
readonly effect: (context: Context) => Effect.Effect<void, never, R>
}
export function define<R = Scope.Scope>(plugin: Plugin<R>) {
return plugin
}
export interface PluginDomain extends PluginApi<unknown> {}

View file

@ -1,7 +1,7 @@
import type { ReferenceGitSource, ReferenceLocalSource } from "@opencode-ai/sdk/v2/types"
import type { ReferenceApi } from "@opencode-ai/client/effect/api"
import type { Effect } from "effect"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export interface ReferenceDraft {
add(name: string, source: ReferenceLocalSource | ReferenceGitSource): void
@ -9,7 +9,7 @@ export interface ReferenceDraft {
list(): readonly (readonly [string, ReferenceLocalSource | ReferenceGitSource])[]
}
export interface ReferenceHooks extends ReferenceApi<unknown> {
readonly transform: TransformHook<ReferenceDraft>
export interface ReferenceDomain extends ReferenceApi<unknown> {
readonly transform: Transform<ReferenceDraft>
readonly reload: () => Effect.Effect<void>
}

View file

@ -4,10 +4,9 @@ export interface Registration {
readonly dispose: Effect.Effect<void>
}
export type Hooks<Spec> = {
readonly [Name in keyof Spec]: (
callback: (input: Spec[Name]) => Effect.Effect<void> | void,
) => Effect.Effect<Registration, never, Scope.Scope>
}
export type Hooks<Spec> = <Name extends keyof Spec>(
name: Name,
callback: (input: Spec[Name]) => Effect.Effect<void>,
) => Effect.Effect<Registration, never, Scope.Scope>
export type TransformHook<Input> = (callback: (input: Input) => void) => Effect.Effect<Registration, never, Scope.Scope>
export type Transform<Input> = (callback: (input: Input) => void) => Effect.Effect<Registration, never, Scope.Scope>

View file

@ -1,4 +0,0 @@
import type { SessionApi } from "@opencode-ai/client/effect/api"
export interface SessionHooks
extends Pick<SessionApi<unknown>, "create" | "get" | "prompt" | "command" | "interrupt"> {}

View file

@ -0,0 +1,25 @@
import type { SessionApi } from "@opencode-ai/client/effect/api"
import type { Message, SystemPart } from "@opencode-ai/llm"
import type { Agent } from "@opencode-ai/schema/agent"
import type { Model } from "@opencode-ai/schema/model"
import type { Session } from "@opencode-ai/schema/session"
import type { JsonSchema } from "effect"
import type { Hooks } from "./registration.js"
export interface SessionRequestBeforeEvent {
readonly sessionID: Session.ID
readonly agent: Agent.ID
readonly model: Model.Ref
system: Array<SystemPart>
messages: Array<Message>
tools: Record<string, { description: string; input: JsonSchema.JsonSchema }>
}
export interface SessionHooks {
readonly request: SessionRequestBeforeEvent
}
export interface SessionDomain
extends Pick<SessionApi<unknown>, "create" | "get" | "prompt" | "command" | "interrupt"> {
readonly hook: Hooks<SessionHooks>
}

View file

@ -1,14 +1,14 @@
import type { SkillV2Source } from "@opencode-ai/sdk/v2/types"
import type { SkillSource } from "@opencode-ai/sdk/v2/types"
import type { SkillApi } from "@opencode-ai/client/effect/api"
import type { Effect } from "effect"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export interface SkillDraft {
source(source: SkillV2Source): void
list(): readonly SkillV2Source[]
source(source: SkillSource): void
list(): readonly SkillSource[]
}
export interface SkillHooks extends SkillApi<unknown> {
readonly transform: TransformHook<SkillDraft>
export interface SkillDomain extends SkillApi<unknown> {
readonly transform: Transform<SkillDraft>
readonly reload: () => Effect.Effect<void>
}

View file

@ -5,7 +5,7 @@ import { Agent } from "@opencode-ai/schema/agent"
import { Session } from "@opencode-ai/schema/session"
import { SessionMessage } from "@opencode-ai/schema/session-message"
import { Effect, JsonSchema, Schema, type Scope } from "effect"
import type { Hooks } from "./registration.js"
import type { Hooks, Transform } from "./registration.js"
export interface Context {
readonly sessionID: Session.ID
@ -253,7 +253,12 @@ export interface ToolDraft {
add(name: string, tool: AnyTool, options?: RegisterOptions): void
}
export interface ToolDomain {
readonly transform: (callback: (draft: ToolDraft) => void) => Effect.Effect<void, RegistrationError, Scope.Scope>
readonly execute: Hooks<{ before: ToolExecuteBeforeEvent; after: ToolExecuteAfterEvent }>
export interface ToolHooks {
readonly "execute.before": ToolExecuteBeforeEvent
readonly "execute.after": ToolExecuteAfterEvent
}
export interface ToolDomain {
readonly transform: Transform<ToolDraft>
readonly hook: Hooks<ToolHooks>
}

View file

@ -1,6 +1,6 @@
# OpenCode V2 Promise Plugin API
The Promise plugin API is the async/await equivalent of `@opencode-ai/plugin/v2/effect`. It grants plugins the same two in-process capabilities:
The Promise plugin API at `@opencode-ai/plugin/v2` is the async/await equivalent of `@opencode-ai/plugin/v2/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.
@ -10,9 +10,9 @@ The only difference from the Effect API is the async boundary: hook callbacks, h
## Defining A Plugin
```ts
import { define } from "@opencode-ai/plugin/v2/promise"
import { Plugin } from "@opencode-ai/plugin/v2"
export const Plugin = define({
export default Plugin.define({
id: "example",
setup: async (ctx) => {
await ctx.catalog.transform((catalog) => {
@ -24,7 +24,7 @@ export const Plugin = define({
})
```
Plugin setup registers hooks imperatively. It does not return a hook object.
Plugin setup registers hooks imperatively through each domain's `hook` method.
Configuration supplied for the plugin is available as `ctx.options`.
@ -64,18 +64,43 @@ ctx.skill.transform
Runtime hooks intercept live operations:
```ts
await ctx.aisdk.sdk(async (event) => {
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.language((event) => {
await ctx.aisdk.hook("language", (event) => {
if (event.model.providerID !== "xai") return
event.language = event.sdk.responses(event.model.api.id)
})
```
Session request context is mutable immediately before provider dispatch:
```ts
await ctx.session.hook("request", (event) => {
event.tools.read.description = "Read a file using narrow line ranges."
delete event.tools.write
})
```
Promise tools use the same schemas and registration model as Effect tools, with async executors:
```ts
import { Schema } from "effect"
import { Tool } from "@opencode-ai/plugin/v2/tool"
const echo = Tool.make({
description: "Echo text",
input: Schema.Struct({ text: Schema.String }),
output: Schema.Struct({ text: Schema.String }),
execute: async ({ text }) => ({ text }),
})
await ctx.tool.transform((tools) => tools.add("echo", echo))
```
## Reloading A Domain
When data captured by a transform changes, reload the affected domain:

View file

@ -1,10 +1,10 @@
import type { AgentApi } from "@opencode-ai/client/promise/api"
import type { AgentDraft } from "../effect/agent.js"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type { AgentDraft }
export interface AgentHooks extends AgentApi {
readonly transform: TransformHook<AgentDraft>
export interface AgentDomain extends AgentApi {
readonly transform: Transform<AgentDraft>
readonly reload: () => Promise<void>
}

View file

@ -2,7 +2,7 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"
import type { Model } from "@opencode-ai/schema/model"
import type { Hooks } from "./registration.js"
export type AISDKHooks = Hooks<{
export interface AISDKHooks {
sdk: {
readonly model: Model.Info
readonly package: string
@ -15,4 +15,8 @@ export type AISDKHooks = Hooks<{
readonly options: Record<string, any>
language?: LanguageModelV3
}
}>
}
export interface AISDKDomain {
readonly hook: Hooks<AISDKHooks>
}

View file

@ -1,10 +1,10 @@
import type { CatalogApi } from "@opencode-ai/client/promise/api"
import type { CatalogDraft, CatalogProviderRecord } from "../effect/catalog.js"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type { CatalogDraft, CatalogProviderRecord }
export interface CatalogHooks extends CatalogApi {
readonly transform: TransformHook<CatalogDraft>
export interface CatalogDomain extends CatalogApi {
readonly transform: Transform<CatalogDraft>
readonly reload: () => Promise<void>
}

View file

@ -1,10 +1,10 @@
import type { CommandApi } from "@opencode-ai/client/promise/api"
import type { CommandDraft } from "../effect/command.js"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type { CommandDraft }
export interface CommandHooks extends CommandApi {
readonly transform: TransformHook<CommandDraft>
export interface CommandDomain extends CommandApi {
readonly transform: Transform<CommandDraft>
readonly reload: () => Promise<void>
}

View file

@ -1,25 +0,0 @@
import type { PluginOptions } from "../options.js"
import type { AgentHooks } from "./agent.js"
import type { AISDKHooks } from "./aisdk.js"
import type { CatalogHooks } from "./catalog.js"
import type { CommandHooks } from "./command.js"
import type { EventHooks } from "./event.js"
import type { IntegrationHooks } from "./integration.js"
import type { PluginDomain } from "./plugin.js"
import type { ReferenceHooks } from "./reference.js"
import type { SessionHooks } from "./runtime.js"
import type { SkillHooks } from "./skill.js"
export interface PluginContext {
readonly options: PluginOptions
readonly agent: AgentHooks
readonly aisdk: AISDKHooks
readonly catalog: CatalogHooks
readonly command: CommandHooks
readonly event: EventHooks
readonly integration: IntegrationHooks
readonly plugin: PluginDomain
readonly reference: ReferenceHooks
readonly session: SessionHooks
readonly skill: SkillHooks
}

View file

@ -1,3 +1,3 @@
import type { EventApi } from "@opencode-ai/client/promise/api"
export interface EventHooks extends Pick<EventApi, "subscribe"> {}
export interface EventDomain extends Pick<EventApi, "subscribe"> {}

View file

@ -1,25 +1,5 @@
export type { PluginContext } from "./context.js"
export type { PluginOptions } from "../options.js"
export { define } from "./plugin.js"
export type { Plugin, PluginDomain } from "./plugin.js"
export type { AgentDraft, AgentHooks } from "./agent.js"
export type { AISDKHooks } from "./aisdk.js"
export type { CatalogDraft, CatalogHooks, CatalogProviderRecord } from "./catalog.js"
export type { CommandDraft, CommandHooks } from "./command.js"
export type { EventHooks } from "./event.js"
export type {
IntegrationDraft,
IntegrationDefinition,
IntegrationHooks,
IntegrationMethodDefinition,
IntegrationMethodRegistration,
IntegrationOAuthAuthorization,
IntegrationOAuthMethodDefinition,
IntegrationSearchDefinition,
} from "./integration.js"
export type { ReferenceDraft, ReferenceHooks } from "./reference.js"
export type { SessionHooks } from "./runtime.js"
export type { SkillDraft, SkillHooks } from "./skill.js"
export * as Plugin from "./plugin.js"
export { Agent } from "@opencode-ai/schema/agent"
export { Command } from "@opencode-ai/schema/command"

View file

@ -1,5 +1,5 @@
import type { IntegrationApi } from "@opencode-ai/client/promise/api"
import type { IntegrationMethodRegistration } from "../effect/integration.js"
import type { IntegrationDraft, IntegrationMethodRegistration } from "../effect/integration.js"
import type {
CredentialOAuth,
CredentialValue,
@ -9,9 +9,9 @@ import type {
IntegrationOAuthMethod,
} from "@opencode-ai/sdk/v2/types"
import type { Search } from "@opencode-ai/schema/search"
import type { Registration, TransformHook } from "./registration.js"
import type { Registration, Transform } from "./registration.js"
export type { IntegrationMethodRegistration }
export type { IntegrationDraft, IntegrationMethodRegistration }
export type IntegrationOAuthAuthorization = {
readonly url: string
@ -50,11 +50,9 @@ export interface IntegrationDefinition {
readonly search?: IntegrationSearchDefinition
}
export type IntegrationDraft = import("../effect/integration.js").IntegrationDraft
export interface IntegrationHooks extends IntegrationApi {
export interface IntegrationDomain extends IntegrationApi {
readonly register: (definition: IntegrationDefinition) => Promise<Registration>
readonly transform: TransformHook<IntegrationDraft>
readonly transform: Transform<IntegrationDraft>
readonly reload: () => Promise<void>
readonly connection: {
readonly active: (integrationID: string) => Promise<import("@opencode-ai/sdk/v2/types").ConnectionInfo | undefined>

View file

@ -1,13 +1,36 @@
import type { PluginApi } from "@opencode-ai/client/promise/api"
import type { PluginContext } from "./context.js"
import type { PluginOptions } from "../options.js"
import type { AgentDomain } from "./agent.js"
import type { AISDKDomain } from "./aisdk.js"
import type { CatalogDomain } from "./catalog.js"
import type { CommandDomain } from "./command.js"
import type { EventDomain } from "./event.js"
import type { IntegrationDomain } from "./integration.js"
import type { ReferenceDomain } from "./reference.js"
import type { SessionDomain } from "./session.js"
import type { SkillDomain } from "./skill.js"
import type { ToolDomain } from "./tool.js"
export interface Context {
readonly options: PluginOptions
readonly agent: AgentDomain
readonly aisdk: AISDKDomain
readonly catalog: CatalogDomain
readonly command: CommandDomain
readonly event: EventDomain
readonly integration: IntegrationDomain
readonly plugin: PluginApi
readonly reference: ReferenceDomain
readonly session: SessionDomain
readonly skill: SkillDomain
readonly tool: ToolDomain
}
export interface Plugin {
readonly id: string
readonly setup: (context: PluginContext) => Promise<void> | void
readonly setup: (context: Context) => Promise<void> | void
}
export function define(plugin: Plugin) {
return plugin
}
export interface PluginDomain extends PluginApi {}

View file

@ -1,10 +1,10 @@
import type { ReferenceApi } from "@opencode-ai/client/promise/api"
import type { ReferenceDraft } from "../effect/reference.js"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type { ReferenceDraft }
export interface ReferenceHooks extends ReferenceApi {
readonly transform: TransformHook<ReferenceDraft>
export interface ReferenceDomain extends ReferenceApi {
readonly transform: Transform<ReferenceDraft>
readonly reload: () => Promise<void>
}

View file

@ -2,8 +2,9 @@ export interface Registration {
readonly dispose: () => Promise<void>
}
export type Hooks<Spec> = {
readonly [Name in keyof Spec]: (callback: (input: Spec[Name]) => Promise<void> | void) => Promise<Registration>
}
export type Hooks<Spec> = <Name extends keyof Spec>(
name: Name,
callback: (input: Spec[Name]) => Promise<void> | void,
) => Promise<Registration>
export type TransformHook<Input> = (callback: (input: Input) => void) => Promise<Registration>
export type Transform<Input> = (callback: (input: Input) => void) => Promise<Registration>

View file

@ -1,3 +0,0 @@
import type { SessionApi } from "@opencode-ai/client/promise/api"
export interface SessionHooks extends Pick<SessionApi, "create" | "get" | "prompt" | "command" | "interrupt"> {}

View file

@ -0,0 +1,24 @@
import type { SessionApi } from "@opencode-ai/client/promise/api"
import type { Message, SystemPart } from "@opencode-ai/llm"
import type { Agent } from "@opencode-ai/schema/agent"
import type { Model } from "@opencode-ai/schema/model"
import type { Session } from "@opencode-ai/schema/session"
import type { JsonSchema } from "effect"
import type { Hooks } from "./registration.js"
export interface SessionRequestBeforeEvent {
readonly sessionID: Session.ID
readonly agent: Agent.ID
readonly model: Model.Ref
system: Array<SystemPart>
messages: Array<Message>
tools: Record<string, { description: string; input: JsonSchema.JsonSchema }>
}
export interface SessionHooks {
readonly request: SessionRequestBeforeEvent
}
export interface SessionDomain extends Pick<SessionApi, "create" | "get" | "prompt" | "command" | "interrupt"> {
readonly hook: Hooks<SessionHooks>
}

View file

@ -1,10 +1,10 @@
import type { SkillApi } from "@opencode-ai/client/promise/api"
import type { SkillDraft } from "../effect/skill.js"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type { SkillDraft }
export interface SkillHooks extends SkillApi {
readonly transform: TransformHook<SkillDraft>
export interface SkillDomain extends SkillApi {
readonly transform: Transform<SkillDraft>
readonly reload: () => Promise<void>
}

View file

@ -0,0 +1,110 @@
export * as Tool from "./tool.js"
import { Tool } from "../effect/tool.js"
import type { ToolOutput, ToolResultValue } from "@opencode-ai/llm"
import type { Agent } from "@opencode-ai/schema/agent"
import type { Session } from "@opencode-ai/schema/session"
import type { SessionMessage } from "@opencode-ai/schema/session-message"
import { Effect, type JsonSchema, type Schema } from "effect"
import type { Hooks, Transform } from "./registration.js"
export type Context = Tool.Context
export type SchemaType<A> = Tool.SchemaType<A>
export type Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> = Tool.Definition<Input, Output>
export type AnyTool = Tool.AnyTool
export const Failure = Tool.Failure
export type Failure = Tool.Failure
export const RegistrationError = Tool.RegistrationError
export type RegistrationError = Tool.RegistrationError
export type Content = Tool.Content
export type DynamicOutput = Tool.DynamicOutput
type Config<
Input extends SchemaType<any>,
Output extends SchemaType<any>,
Structured extends SchemaType<any> = Output,
> = {
readonly description: string
readonly input: Input
readonly output: Output
readonly structured?: Structured
readonly toStructuredOutput?: (input: {
readonly input: Schema.Schema.Type<Input>
readonly output: Output["Encoded"]
}) => Schema.Schema.Type<Structured>
readonly execute: (
input: Schema.Schema.Type<Input>,
context: Context,
) => Promise<Schema.Schema.Type<Output>>
readonly toModelOutput?: (input: {
readonly input: Schema.Schema.Type<Input>
readonly output: Output["Encoded"]
}) => ReadonlyArray<Content>
}
type DynamicConfig = {
readonly description: string
readonly jsonSchema: JsonSchema.JsonSchema
readonly outputSchema?: JsonSchema.JsonSchema
readonly execute: (input: unknown, context: Context) => Promise<DynamicOutput>
}
export function make<
Input extends SchemaType<any>,
Output extends SchemaType<any>,
Structured extends SchemaType<any> = Output,
>(config: Config<Input, Output, Structured>): Definition<Input, Structured>
export function make(config: DynamicConfig): AnyTool
export function make(config: Config<any, any, any> | DynamicConfig): AnyTool {
if ("jsonSchema" in config)
return Tool.make({
...config,
execute: (input, context) => Effect.promise(() => config.execute(input, context)),
})
return Tool.make({
...config,
execute: (input, context) => Effect.promise(() => config.execute(input, context)),
})
}
export const withPermission = Tool.withPermission
export interface ToolExecuteBeforeEvent {
readonly tool: string
readonly sessionID: Session.ID
readonly agent: Agent.ID
readonly assistantMessageID: SessionMessage.ID
readonly toolCallID: string
input: unknown
}
export interface ToolExecuteAfterEvent {
readonly tool: string
readonly sessionID: Session.ID
readonly agent: Agent.ID
readonly assistantMessageID: SessionMessage.ID
readonly toolCallID: string
readonly input: unknown
result: ToolResultValue
output?: ToolOutput
outputPaths?: ReadonlyArray<string>
}
export interface RegisterOptions {
readonly group?: string
readonly deferred?: boolean
}
export interface ToolDraft {
add(name: string, tool: AnyTool, options?: RegisterOptions): void
}
export interface ToolHooks {
readonly "execute.before": ToolExecuteBeforeEvent
readonly "execute.after": ToolExecuteAfterEvent
}
export interface ToolDomain {
readonly transform: Transform<ToolDraft>
readonly hook: Hooks<ToolHooks>
}