Ver Fonte

fix(plugin): restore package publishing

Dax Raad há 1 mês atrás
pai
commit
761f37370f

+ 0 - 2
packages/core/src/plugin/hooks.ts

@@ -1,7 +1,6 @@
 export * as PluginHooks from "./hooks"
 
 import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk"
-import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
 import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool"
 import { Context, Effect, Layer, Scope } from "effect"
 import { makeLocationNode } from "../effect/app-node"
@@ -9,7 +8,6 @@ import { State } from "../state"
 
 export interface Domains {
   readonly aisdk: AISDKHooks
-  readonly session: SessionHooks
   readonly tool: ToolHooks
 }
 

+ 0 - 1
packages/core/src/plugin/host.ts

@@ -367,7 +367,6 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
       },
     },
     session: {
-      hook: (name, callback) => hooks.register("session", name, callback),
       create: (input) =>
         runtime.session.create({
           id: input?.id,

+ 0 - 2
packages/core/src/plugin/promise.ts

@@ -118,8 +118,6 @@ export function fromPromise(plugin: PromisePlugin) {
             prompt: (input) => run(host.session.prompt(input)),
             command: (input) => run(host.session.command(input)),
             interrupt: (input) => run(host.session.interrupt(input)),
-            hook: (name, callback) =>
-              register(host.session.hook(name, (event) => Effect.promise(() => Promise.resolve(callback(event))))),
           },
         }
 

+ 1 - 43
packages/core/src/session/runner/llm.ts

@@ -51,8 +51,6 @@ import { llmClient } from "../../effect/app-node-platform"
 import { AgentNotFoundError, StepFailedError } from "../error"
 import { toSessionError } from "../to-session-error"
 import { SessionRunnerRetry } from "./retry"
-import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
-import { PluginHooks } from "../../plugin/hooks"
 import { PluginSupervisor } from "../../plugin/supervisor"
 
 type StepTokens = {
@@ -91,7 +89,6 @@ const layer = Layer.effect(
     const llm = yield* LLMClient.Service
     const agents = yield* AgentV2.Service
     const tools = yield* ToolRegistry.Service
-    const hooks = yield* PluginHooks.Service
     const models = yield* SessionRunnerModel.Service
     const store = yield* SessionStore.Service
     const location = yield* Location.Service
@@ -212,30 +209,7 @@ const layer = Layer.effect(
       const toolFibers = yield* FiberSet.make<void, ToolOutputStore.Error>()
       const ownedToolFibers: Array<Fiber.Fiber<void, ToolOutputStore.Error>> = []
       let needsContinuation = false
-      const availableTools = new Map(request.tools.map((tool) => [tool.name, tool]))
-      const requestEvent: SessionHooks["request"] = {
-        sessionID: session.id,
-        agent: agent.id,
-        model: resolved.ref,
-        system: [...request.system],
-        messages: [...request.messages],
-        tools: Object.fromEntries(
-          request.tools.map((tool) => [tool.name, { description: tool.description, input: { ...tool.inputSchema } }]),
-        ),
-      }
-      // Plugins may reshape the draft, but cannot advertise tools excluded earlier
-      // by permissions or registration state.
-      yield* hooks.trigger("session", "request", requestEvent)
-      const hookedRequest = LLM.updateRequest(request, {
-        system: requestEvent.system,
-        messages: requestEvent.messages,
-        tools: Object.entries(requestEvent.tools).flatMap(([name, tool]) => {
-          const registered = availableTools.get(name)
-          if (!registered) return []
-          return [{ ...registered, description: tool.description, inputSchema: tool.input }]
-        }),
-      })
-      const advertisedTools = new Set(hookedRequest.tools.map((tool) => tool.name))
+      const hookedRequest = request
       // Automatic compaction completed; rebuild the request from compacted history.
       if (
         !(yield* SessionPending.compaction(db, session.id)) &&
@@ -280,21 +254,6 @@ const layer = Layer.effect(
               )
               return
             }
-            // A request hook hid this registered tool from the current request. Fail only
-            // this call durably and continue so the model can react, instead of executing
-            // a tool that was not advertised. Unregistered tools flow through settle, which
-            // durably fails them as unknown.
-            if (!advertisedTools.has(event.name) && availableTools.has(event.name)) {
-              needsContinuation = true
-              yield* publish(
-                LLMEvent.toolError({
-                  id: event.id,
-                  name: event.name,
-                  message: `Tool is not available for this request: ${event.name}`,
-                }),
-              )
-              return
-            }
             needsContinuation = true
             const assistantMessageID = yield* publisher.assistantMessageID(event.id)
             ownedToolFibers.push(
@@ -624,7 +583,6 @@ export const node = makeLocationNode({
     llmClient,
     AgentV2.node,
     ToolRegistry.node,
-    PluginHooks.node,
     SessionRunnerModel.node,
     SessionStore.node,
     Location.node,

+ 2 - 1
packages/core/src/session/to-session-error.ts

@@ -1,4 +1,5 @@
 import { LLMError, ToolFailure } from "@opencode-ai/llm"
+import { Tool } from "@opencode-ai/plugin/v2/effect/tool"
 import { SessionError } from "@opencode-ai/schema/session-error"
 import { PermissionV2 } from "../permission"
 import { QuestionV2 } from "../question"
@@ -38,7 +39,7 @@ export function toSessionError(cause: unknown): SessionError.Error {
   }
   if (cause instanceof PermissionV2.BlockedError) return { type: "permission.rejected", message: cause.message }
   if (cause instanceof QuestionV2.RejectedError) return { type: "aborted", message: cause.message }
-  if (cause instanceof ToolFailure)
+  if (cause instanceof ToolFailure || cause instanceof Tool.Failure)
     return cause.error === undefined ? { type: "tool.execution", message: cause.message } : toSessionError(cause.error)
   if (cause instanceof StepFailedError) return cause.error
   if (cause instanceof AgentNotFoundError) return { type: "unknown", message: cause.message }

+ 0 - 13
packages/core/src/tool/patch.ts

@@ -194,19 +194,6 @@ export const Plugin = {
         ),
       )
       .pipe(Effect.orDie)
-
-    yield* ctx.session.hook("request", (event) =>
-      Effect.sync(() => {
-        const usePatch =
-          event.model.providerID.toLowerCase() === "openai" || event.model.id.toLowerCase().includes("gpt")
-        if (usePatch) {
-          delete event.tools.edit
-          delete event.tools.write
-          return
-        }
-        delete event.tools.patch
-      }),
-    )
   }),
 }
 

+ 0 - 27
packages/core/src/tool/subagent.ts

@@ -192,32 +192,5 @@ export const Plugin = {
         ),
       )
       .pipe(Effect.orDie)
-
-    yield* ctx.session.hook("request", (event) =>
-      Effect.gen(function* () {
-        const tool = event.tools[name]
-        if (!tool) return
-        const selected = yield* agents.resolve(event.agent)
-        if (!selected) return
-        const available = (yield* agents.list())
-          .filter(
-            (agent) =>
-              agent.mode !== "primary" &&
-              !agent.hidden &&
-              PermissionV2.evaluate(name, agent.id, selected.permissions).effect !== "deny",
-          )
-          .toSorted((a, b) => a.id.localeCompare(b.id))
-        if (available.length === 0) return
-        tool.description = [
-          tool.description,
-          "",
-          "Available subagents:",
-          ...available.map(
-            (agent) =>
-              `- ${agent.id}: ${agent.description ?? "This subagent should only be called when explicitly requested."}`,
-          ),
-        ].join("\n")
-      }),
-    )
   }),
 }

+ 0 - 47
packages/core/test/plugin-hooks.test.ts

@@ -1,47 +0,0 @@
-import { describe, expect, it } from "bun:test"
-import { Message, SystemPart } from "@opencode-ai/llm"
-import { Agent } from "@opencode-ai/schema/agent"
-import { Model } from "@opencode-ai/schema/model"
-import { Provider } from "@opencode-ai/schema/provider"
-import { Session } from "@opencode-ai/schema/session"
-import { Effect, Layer } from "effect"
-import { PluginHooks } from "../src/plugin/hooks"
-
-describe("PluginHooks", () => {
-  it("registers scoped domain hooks and triggers them sequentially", async () => {
-    const seen: string[] = []
-    const program = Effect.gen(function* () {
-      const hooks = yield* PluginHooks.Service
-      yield* hooks.register("session", "request", (event) =>
-        Effect.sync(() => {
-          seen.push("first")
-          event.system.push(SystemPart.make("second"))
-        }),
-      )
-      yield* hooks.register("session", "request", (event) =>
-        Effect.sync(() => {
-          seen.push(event.system[1]?.text ?? "missing")
-          event.messages = [Message.user("changed")]
-        }),
-      )
-      const event = {
-        sessionID: Session.ID.make("ses_hooks"),
-        agent: Agent.ID.make("build"),
-        model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make("model") }),
-        system: [SystemPart.make("first")],
-        messages: [Message.user("original")],
-        tools: {},
-      }
-
-      expect(yield* hooks.trigger("session", "request", event)).toBe(event)
-      expect(seen).toEqual(["first", "second"])
-      expect(event.messages).toEqual([Message.user("changed")])
-    })
-
-    await Effect.runPromise(
-      Effect.scoped(program).pipe(
-        Effect.provide(PluginHooks.node.implementation as Layer.Layer<PluginHooks.Service>),
-      ),
-    )
-  })
-})

+ 0 - 3
packages/core/test/plugin/host.ts

@@ -83,9 +83,6 @@ export function host(overrides: Overrides = {}): PluginContext {
       prompt: () => Effect.die("unused session.prompt"),
       command: () => Effect.die("unused session.command"),
       interrupt: () => Effect.die("unused session.interrupt"),
-      // Plugins register session hooks during setup, so a bare host accepts the
-      // registration; the callback only runs when a test triggers the request pipeline.
-      hook: () => Effect.succeed({ dispose: Effect.void }),
     },
   }
 }

+ 5 - 0
packages/core/test/session-error.test.ts

@@ -16,6 +16,7 @@ import {
   ToolFailure,
 } from "@opencode-ai/llm"
 import { PermissionV2 } from "@opencode-ai/core/permission"
+import { Tool } from "@opencode-ai/plugin/v2/effect/tool"
 import { toSessionError } from "@opencode-ai/core/session/to-session-error"
 import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry"
 
@@ -64,6 +65,10 @@ describe("toSessionError", () => {
       type: "permission.rejected",
       message: "Permission denied: external_directory",
     })
+    expect(toSessionError(new Tool.Failure({ message: "failed" }))).toEqual({
+      type: "tool.execution",
+      message: "failed",
+    })
   })
 
   test("retries only rate limits, provider-internal failures, and transport failures", () => {

+ 1 - 2
packages/plugin/package.json

@@ -7,7 +7,7 @@
   "scripts": {
     "test": "bun test --timeout 5000",
     "typecheck": "tsgo --noEmit",
-    "build": "tsc"
+    "build": "tsc -p tsconfig.build.json"
   },
   "exports": {
     ".": "./src/index.ts",
@@ -24,7 +24,6 @@
   "dependencies": {
     "@ai-sdk/provider": "3.0.8",
     "@opencode-ai/client": "workspace:*",
-    "@opencode-ai/llm": "workspace:*",
     "@opencode-ai/protocol": "workspace:*",
     "@opencode-ai/schema": "workspace:*",
     "@opencode-ai/sdk": "workspace:*",

+ 1 - 1
packages/plugin/script/publish.ts

@@ -10,7 +10,7 @@ async function published(name: string, version: string) {
   return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
 }
 
-await $`bun tsc`
+await $`bun run build`
 const originalText = await Bun.file("package.json").text()
 const pkg = JSON.parse(originalText) as {
   name: string

+ 1 - 23
packages/plugin/src/v2/effect/session.ts

@@ -1,25 +1,3 @@
 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>
-}
+export type SessionDomain = Pick<SessionApi<unknown>, "create" | "get" | "prompt" | "command" | "interrupt">

+ 38 - 12
packages/plugin/src/v2/effect/tool.ts

@@ -1,7 +1,7 @@
 export * as Tool from "./tool.js"
 
-import { ToolDefinition, ToolFailure, ToolOutput, type ToolCall, type ToolResultValue } from "@opencode-ai/llm"
 import { Agent } from "@opencode-ai/schema/agent"
+import type { LLM } from "@opencode-ai/schema/llm"
 import { Session } from "@opencode-ai/schema/session"
 import { SessionMessage } from "@opencode-ai/schema/session-message"
 import { Effect, JsonSchema, Schema, type Scope } from "effect"
@@ -16,6 +16,29 @@ export interface Context {
 
 export type SchemaType<A> = Schema.Codec<A, any>
 
+type ToolDefinition = {
+  readonly name: string
+  readonly description: string
+  readonly inputSchema: JsonSchema.JsonSchema
+  readonly outputSchema?: JsonSchema.JsonSchema
+}
+
+type ToolCall = {
+  readonly input: unknown
+  readonly [key: string]: unknown
+}
+
+type ToolResultValue =
+  | { readonly type: "json"; readonly value: unknown }
+  | { readonly type: "text"; readonly value: unknown }
+  | { readonly type: "error"; readonly value: unknown }
+  | { readonly type: "content"; readonly value: ReadonlyArray<LLM.ToolContent> }
+
+type ToolOutput = {
+  readonly structured: unknown
+  readonly content: ReadonlyArray<LLM.ToolContent>
+}
+
 declare const TypeId: unique symbol
 
 export interface Definition<Input extends SchemaType<any>, Output extends SchemaType<any>> {
@@ -26,8 +49,11 @@ export interface Definition<Input extends SchemaType<any>, Output extends Schema
 }
 
 export type AnyTool = Definition<any, any>
-export const Failure = ToolFailure
-export type Failure = ToolFailure
+export class Failure extends Schema.TaggedErrorClass<Failure>()("LLM.ToolFailure", {
+  message: Schema.String,
+  error: Schema.optional(Schema.Defect()),
+  metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
+}) {}
 
 export class RegistrationError extends Schema.TaggedErrorClass<RegistrationError>()("Tool.RegistrationError", {
   name: Schema.String,
@@ -54,7 +80,7 @@ type Config<
   readonly execute: (
     input: Schema.Schema.Type<Input>,
     context: Context,
-  ) => Effect.Effect<Schema.Schema.Type<Output>, ToolFailure>
+  ) => Effect.Effect<Schema.Schema.Type<Output>, Failure>
   readonly toModelOutput?: (input: {
     readonly input: Schema.Schema.Type<Input>
     readonly output: Output["Encoded"]
@@ -75,13 +101,13 @@ type DynamicConfig = {
   readonly description: string
   readonly jsonSchema: JsonSchema.JsonSchema
   readonly outputSchema?: JsonSchema.JsonSchema
-  readonly execute: (input: unknown, context: Context) => Effect.Effect<DynamicOutput, ToolFailure>
+  readonly execute: (input: unknown, context: Context) => Effect.Effect<DynamicOutput, Failure>
 }
 
 type Runtime = {
   readonly permission?: string
   readonly definition: (name: string) => ToolDefinition
-  readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, ToolFailure>
+  readonly settle: (call: ToolCall, context: Context) => Effect.Effect<ToolOutput, Failure>
 }
 
 const runtimes = new WeakMap<AnyTool, Runtime>()
@@ -108,18 +134,18 @@ function makeTyped<
     definition: (name) => {
       const cached = definitions.get(name)
       if (cached) return cached
-      const definition = new ToolDefinition({
+      const definition: ToolDefinition = {
         name,
         description: config.description,
         inputSchema: toJsonSchema(config.input),
         outputSchema: toJsonSchema(config.structured ?? config.output),
-      })
+      }
       definitions.set(name, definition)
       return definition
     },
     settle: (call, context) =>
       Schema.decodeUnknownEffect(config.input)(call.input).pipe(
-        Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })),
+        Effect.mapError((error) => new Failure({ message: `Invalid tool input: ${error.message}` })),
         Effect.flatMap((input) =>
           config.execute(input, context).pipe(
             Effect.flatMap((output) =>
@@ -133,7 +159,7 @@ function makeTyped<
                 }),
                 Effect.mapError(
                   (error) =>
-                    new ToolFailure({
+                    new Failure({
                       message: `Tool returned an invalid value for its output schema: ${error.message}`,
                     }),
                 ),
@@ -159,12 +185,12 @@ function makeDynamic(config: DynamicConfig): AnyTool {
     definition: (name) => {
       const cached = definitions.get(name)
       if (cached) return cached
-      const definition = new ToolDefinition({
+      const definition: ToolDefinition = {
         name,
         description: config.description,
         inputSchema: config.jsonSchema,
         outputSchema: config.outputSchema,
-      })
+      }
       definitions.set(name, definition)
       return definition
     },

+ 2 - 0
packages/plugin/src/v2/promise/index.ts

@@ -1,6 +1,8 @@
 export type { PluginOptions } from "../options.js"
 export * as Plugin from "./plugin.js"
 
+export { Tool } from "./tool.js"
+
 export { Agent } from "@opencode-ai/schema/agent"
 export { Command } from "@opencode-ai/schema/command"
 export { Connection } from "@opencode-ai/schema/connection"

+ 1 - 22
packages/plugin/src/v2/promise/session.ts

@@ -1,24 +1,3 @@
 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>
-}
+export type SessionDomain = Pick<SessionApi, "create" | "get" | "prompt" | "command" | "interrupt">

+ 2 - 3
packages/plugin/src/v2/promise/tool.ts

@@ -1,7 +1,6 @@
 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"
@@ -85,8 +84,8 @@ export interface ToolExecuteAfterEvent {
   readonly assistantMessageID: SessionMessage.ID
   readonly toolCallID: string
   readonly input: unknown
-  result: ToolResultValue
-  output?: ToolOutput
+  result: Tool.ToolExecuteAfterEvent["result"]
+  output?: Tool.ToolExecuteAfterEvent["output"]
   outputPaths?: ReadonlyArray<string>
 }
 

+ 2 - 2
packages/plugin/test/contract-identity.test.ts

@@ -32,10 +32,10 @@ test.each([
     "Credential",
     "Integration",
     "Model",
+    "Plugin",
     "Provider",
     "Reference",
     "Skill",
-    ...(name === "effect" ? ["Tool"] : []),
-    "define",
+    ...(name === "promise" ? ["Tool"] : []),
   ])
 })

+ 8 - 0
packages/plugin/tsconfig.build.json

@@ -0,0 +1,8 @@
+{
+  "$schema": "https://json.schemastore.org/tsconfig",
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "allowImportingTsExtensions": false,
+    "noEmit": false
+  }
+}

+ 3 - 0
script/publish.ts

@@ -50,6 +50,9 @@ await $`bun ./packages/cli/script/publish.ts`
 console.log("\n=== sdk ===\n")
 await $`bun ./packages/sdk/js/script/publish.ts`
 
+console.log("\n=== plugin ===\n")
+await $`bun ./packages/plugin/script/publish.ts`
+
 console.log("\n=== ui ===\n")
 await $`bun ./packages/ui/script/publish.ts`