mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-06 21:43:22 +00:00
refactor(plugin): group HTTP under session hooks
This commit is contained in:
parent
43a1c74dd0
commit
4b19e9ce27
10 changed files with 66 additions and 25 deletions
|
|
@ -2,6 +2,7 @@ export * as PluginHost from "./host"
|
|||
|
||||
import { Plugin } from "@opencode-ai/plugin/effect"
|
||||
import type { IntegrationMethodRegistration } from "@opencode-ai/plugin/effect/integration"
|
||||
import type { SessionHookRegistration } from "@opencode-ai/plugin/effect/session"
|
||||
import type { CredentialOAuth } from "@opencode-ai/sdk/v2/types"
|
||||
import { EventManifest } from "@opencode-ai/schema/event-manifest"
|
||||
import { App } from "../app"
|
||||
|
|
@ -337,14 +338,16 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../p
|
|||
}),
|
||||
},
|
||||
session: {
|
||||
hook: (name, callback) => hooks.register("session", name, callback),
|
||||
http: (middleware) =>
|
||||
hooks.register("session", "http", (event) =>
|
||||
hook: (...registration: SessionHookRegistration) => {
|
||||
if (registration[0] === "context") return hooks.register("session", "context", registration[1])
|
||||
const middleware = registration[1]
|
||||
return hooks.register("session", "http", (event) =>
|
||||
Effect.sync(() => {
|
||||
const next = event.request
|
||||
event.request = (request) => middleware(event, request, next)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
create: (input) =>
|
||||
runtime.session.create({
|
||||
id: input?.id,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export * as PluginPromise from "./promise"
|
|||
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import type { Context, Plugin } from "@opencode-ai/plugin/promise/plugin"
|
||||
import type { SessionHookRegistration } from "@opencode-ai/plugin/promise/session"
|
||||
import type { Info } from "@opencode-ai/plugin/promise/tool"
|
||||
import { Agent } from "@opencode-ai/schema/agent"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
|
|
@ -265,11 +266,16 @@ export function fromPromise(plugin: Plugin) {
|
|||
),
|
||||
},
|
||||
session: {
|
||||
hook: (name, callback) =>
|
||||
register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
|
||||
http: (middleware) =>
|
||||
register(
|
||||
host.session.http((event, input, next) =>
|
||||
hook: (...registration: SessionHookRegistration) => {
|
||||
if (registration[0] === "context")
|
||||
return register(
|
||||
host.session.hook("context", (event) =>
|
||||
Effect.promise(() => Promise.resolve(registration[1](event))),
|
||||
),
|
||||
)
|
||||
const middleware = registration[1]
|
||||
return register(
|
||||
host.session.hook("http", (event, input, next) =>
|
||||
Effect.tryPromise({
|
||||
try: (signal) =>
|
||||
Promise.resolve(
|
||||
|
|
@ -280,7 +286,8 @@ export function fromPromise(plugin: Plugin) {
|
|||
catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
create: (input) =>
|
||||
run(
|
||||
host.session.create(
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ export const OpenAIPlugin = define({
|
|||
})
|
||||
}
|
||||
})
|
||||
yield* ctx.session.http((evt, request, next) => {
|
||||
yield* ctx.session.hook("http", (evt, request, next) => {
|
||||
if (!chatgpt || evt.model.providerID !== Provider.ID.openai) return next(request)
|
||||
const url = new URL(request.url)
|
||||
request.headers.set("originator", "opencode")
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ export function host(overrides: Overrides = {}): Plugin.Context {
|
|||
},
|
||||
session: {
|
||||
hook: overrides.session?.hook ?? (() => Effect.die("unused session.hook")),
|
||||
http: overrides.session?.http ?? (() => Effect.die("unused session.http")),
|
||||
create: overrides.session?.create ?? (() => Effect.die("unused session.create")),
|
||||
get: overrides.session?.get ?? (() => Effect.die("unused session.get")),
|
||||
prompt: overrides.session?.prompt ?? (() => Effect.die("unused session.prompt")),
|
||||
|
|
|
|||
|
|
@ -232,12 +232,12 @@ describe("fromPromise", () => {
|
|||
define({
|
||||
id: "promise-session-http",
|
||||
setup: async (ctx) => {
|
||||
await ctx.session.http(async (_event, request, next) => {
|
||||
await ctx.session.hook("http", async (_event, request, next) => {
|
||||
request.headers.set("x-hook", "promise")
|
||||
const response = await next(request)
|
||||
return new Response(`${await response.text()}-response`)
|
||||
})
|
||||
await ctx.session.http(async (_event, request, next) => {
|
||||
await ctx.session.hook("http", async (_event, request, next) => {
|
||||
const response = await next(request)
|
||||
return new Response(`${await response.text()}-outer`)
|
||||
})
|
||||
|
|
@ -267,7 +267,7 @@ describe("fromPromise", () => {
|
|||
define({
|
||||
id: "promise-session-http-interrupt",
|
||||
setup: async (ctx) => {
|
||||
await ctx.session.http((_event, request, next) => next(request))
|
||||
await ctx.session.hook("http", (_event, request, next) => next(request))
|
||||
},
|
||||
}),
|
||||
).effect(host)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { HttpRecorder } from "@opencode-ai/http-recorder"
|
||||
import type { SessionHookRegistration } from "@opencode-ai/plugin/effect/session"
|
||||
import * as OpenAIChat from "@opencode-ai/ai/protocols/openai-chat"
|
||||
import { Auth, LLMClient, RequestExecutor } from "@opencode-ai/ai/route"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
|
|
@ -189,7 +190,12 @@ describe("SessionRunnerLLM recorded", () => {
|
|||
const pluginHost = host({
|
||||
agent: agentHost(agents),
|
||||
catalog: catalogHost(catalog),
|
||||
session: { hook: (name, callback) => hooks.register("session", name, callback) },
|
||||
session: {
|
||||
hook: (...registration: SessionHookRegistration) => {
|
||||
if (registration[0] === "http") return Effect.die("unused session HTTP hook")
|
||||
return hooks.register("session", "context", registration[1])
|
||||
},
|
||||
},
|
||||
})
|
||||
yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), { discard: true })
|
||||
const { db } = yield* Database.Service
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from "@opencode-ai/ai"
|
||||
import * as OpenAIChat from "@opencode-ai/ai/protocols/openai-chat"
|
||||
import { TestLLM } from "@opencode-ai/ai/testing"
|
||||
import type { SessionHookRegistration } from "@opencode-ai/plugin/effect/session"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
|
|
@ -474,7 +475,12 @@ const setup = Effect.gen(function* () {
|
|||
const pluginHost = host({
|
||||
agent: agentHost(agents),
|
||||
catalog: catalogHost(catalog),
|
||||
session: { hook: (name, callback) => hooks.register("session", name, callback) },
|
||||
session: {
|
||||
hook: (...registration: SessionHookRegistration) => {
|
||||
if (registration[0] === "http") return Effect.die("unused session HTTP hook")
|
||||
return hooks.register("session", "context", registration[1])
|
||||
},
|
||||
},
|
||||
})
|
||||
yield* Effect.forEach(SystemPromptPlugin.Plugins, (plugin) => plugin.effect(pluginHost), {
|
||||
discard: true,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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 { Effect, JsonSchema, Scope } from "effect"
|
||||
import type { Hooks, Registration } from "./registration.js"
|
||||
import type { Registration } from "./registration.js"
|
||||
|
||||
export interface SessionContext {
|
||||
readonly sessionID: Session.ID
|
||||
|
|
@ -31,10 +31,22 @@ export interface SessionHooks {
|
|||
readonly context: SessionContext
|
||||
}
|
||||
|
||||
export type SessionHookRegistration =
|
||||
| [name: "context", callback: (event: SessionContext) => Effect.Effect<void>]
|
||||
| [name: "http", middleware: SessionHttpMiddleware]
|
||||
|
||||
export interface SessionHook {
|
||||
(name: "context", callback: (event: SessionContext) => Effect.Effect<void>): Effect.Effect<
|
||||
Registration,
|
||||
never,
|
||||
Scope.Scope
|
||||
>
|
||||
(name: "http", middleware: SessionHttpMiddleware): Effect.Effect<Registration, never, Scope.Scope>
|
||||
}
|
||||
|
||||
export type SessionDomain = Pick<
|
||||
SessionApi<unknown>,
|
||||
"create" | "get" | "prompt" | "generate" | "command" | "synthetic" | "interrupt" | "rename" | "wait"
|
||||
> & {
|
||||
readonly hook: Hooks<SessionHooks>
|
||||
readonly http: (middleware: SessionHttpMiddleware) => Effect.Effect<Registration, never, Scope.Scope>
|
||||
readonly hook: SessionHook
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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, Registration } from "./registration.js"
|
||||
import type { Registration } from "./registration.js"
|
||||
|
||||
export interface SessionContext {
|
||||
readonly sessionID: Session.ID
|
||||
|
|
@ -31,10 +31,18 @@ export interface SessionHooks {
|
|||
readonly context: SessionContext
|
||||
}
|
||||
|
||||
export type SessionHookRegistration =
|
||||
| [name: "context", callback: (event: SessionContext) => Promise<void> | void]
|
||||
| [name: "http", middleware: SessionHttpMiddleware]
|
||||
|
||||
export interface SessionHook {
|
||||
(name: "context", callback: (event: SessionContext) => Promise<void> | void): Promise<Registration>
|
||||
(name: "http", middleware: SessionHttpMiddleware): Promise<Registration>
|
||||
}
|
||||
|
||||
export type SessionDomain = Pick<
|
||||
SessionApi,
|
||||
"create" | "get" | "prompt" | "generate" | "command" | "synthetic" | "interrupt"
|
||||
> & {
|
||||
readonly hook: Hooks<SessionHooks>
|
||||
readonly http: (middleware: SessionHttpMiddleware) => Promise<Registration>
|
||||
readonly hook: SessionHook
|
||||
}
|
||||
|
|
|
|||
4
packages/www/content/docs/build/plugins.mdx
vendored
4
packages/www/content/docs/build/plugins.mdx
vendored
|
|
@ -246,7 +246,7 @@ Runtime hooks intercept live operations:
|
|||
| `ctx.aisdk.hook("sdk", callback)` | `sdk`, after inspecting `model`, `package`, and `options` |
|
||||
| `ctx.aisdk.hook("language", callback)` | `language`, after inspecting `model`, `sdk`, and `options` |
|
||||
| `ctx.session.hook("context", callback)` | `system`, `messages`, and the `tools` record immediately before model dispatch |
|
||||
| `ctx.session.http(middleware)` | The model's HTTP request and response |
|
||||
| `ctx.session.hook("http", middleware)` | The model's HTTP request and response |
|
||||
| `ctx.tool.hook("execute.before", callback)` | `input`, before the selected tool executes |
|
||||
| `ctx.tool.hook("execute.after", callback)` | Terminal `result` on success or `error` on failure |
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ HTTP middleware can modify requests, inspect responses, retry, or return a
|
|||
response without calling the provider:
|
||||
|
||||
```ts
|
||||
await ctx.session.http(async (event, request, next) => {
|
||||
await ctx.session.hook("http", async (event, request, next) => {
|
||||
request.headers.set("x-session-id", event.sessionID)
|
||||
const response = await next(request)
|
||||
return response
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue