feat(plugin): add session request hook (#35794)

This commit is contained in:
Dax 2026-07-07 19:56:47 -04:00 committed by GitHub
commit 4f976bcf1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
124 changed files with 7743 additions and 5620 deletions

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,7 +1,7 @@
import type { AgentApi } from "@opencode-ai/client/effect/api"
import type { AgentV2Info } 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[]
@ -11,7 +11,7 @@ export interface AgentDraft {
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,7 +1,7 @@
import type { ModelV2Info, 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
@ -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,7 +1,7 @@
import type { CommandV2Info } 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[]
@ -10,7 +10,7 @@ export interface CommandDraft {
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,17 +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, IntegrationHooks, IntegrationMethodRegistration } 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

@ -11,7 +11,7 @@ import type {
} from "@opencode-ai/sdk/v2/types"
import type { IntegrationApi } from "@opencode-ai/client/effect/api"
import type { Effect, Scope } from "effect"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type IntegrationOAuthAuthorization = {
readonly url: string
@ -56,8 +56,8 @@ export interface IntegrationDraft {
}
}
export interface IntegrationHooks extends IntegrationApi<unknown> {
readonly transform: TransformHook<IntegrationDraft>
export interface IntegrationDomain extends IntegrationApi<unknown> {
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 { 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[]
}
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,16 +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, IntegrationHooks, IntegrationMethodRegistration } 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,12 +1,12 @@
import type { IntegrationApi } from "@opencode-ai/client/promise/api"
import type { IntegrationDraft, IntegrationMethodRegistration } from "../effect/integration.js"
import type { CredentialValue } from "@opencode-ai/sdk/v2/types"
import type { TransformHook } from "./registration.js"
import type { Transform } from "./registration.js"
export type { IntegrationDraft, IntegrationMethodRegistration }
export interface IntegrationHooks extends IntegrationApi {
readonly transform: TransformHook<IntegrationDraft>
export interface IntegrationDomain extends IntegrationApi {
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>
}