Bläddra i källkod

fix(core): validate tool definitions on registration (#42117)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
opencode-agent[bot] 4 dagar sedan
förälder
incheckning
8d5dd206a6
2 ändrade filer med 37 tillägg och 2 borttagningar
  1. 15 2
      packages/core/src/tool.ts
  2. 22 0
      packages/core/test/session-runner-tool-registry.test.ts

+ 15 - 2
packages/core/src/tool.ts

@@ -2,7 +2,7 @@ export * as Tool from "./tool.js"
 export { CallID, Content, Error, FileContent, TextContent } from "@opencode-ai/schema/tool"
 export type { Context, Metadata, Options, Result } from "@opencode-ai/schema/tool"
 
-import type { ToolCall, ToolDefinition } from "@opencode-ai/ai"
+import { ToolDefinition, type ToolCall } from "@opencode-ai/ai"
 import { Tool } from "@opencode-ai/schema/tool"
 import { Context, Effect, Layer, Schema, Scope, Semaphore } from "effect"
 import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
@@ -100,7 +100,7 @@ const layer = Layer.effect(
       const execution = yield* execute(tool, beforeEvent.input, context).pipe(
         Effect.map((value) => ({ value })),
         Effect.catchTag("Tool.Error", (failure) => Effect.succeed({ failure })),
-      )
+        )
       const base = {
         tool: name,
         sessionID: context.sessionID,
@@ -165,6 +165,19 @@ const layer = Layer.effect(
           }),
         )
       if (entries.length === 0) return
+      yield* Effect.forEach(
+        entries,
+        (entry) =>
+          Effect.try({
+            try: () => ToolDefinition.make(definition(entry.tool)),
+            catch: (error) =>
+              new RegistrationError({
+                name: entry.key,
+                message: `Invalid tool definition ${entry.key}: ${error instanceof Error ? error.message : String(error)}`,
+              }),
+          }),
+        { discard: true },
+      )
       yield* Effect.uninterruptible(
         lock.withPermit(
           Effect.gen(function* () {

+ 22 - 0
packages/core/test/session-runner-tool-registry.test.ts

@@ -110,6 +110,28 @@ describe("Tool", () => {
     }),
   )
 
+  it.effect("rejects invalid tool definitions before installing any tools", () =>
+    Effect.gen(function* () {
+      const service = yield* Tool.Service
+      const error = yield* service
+        .transform((draft) => {
+          draft.add({ ...make(), name: "healthy", options: { codemode: false } })
+          draft.add({
+            name: "phone_type",
+            input: Schema.Struct({}),
+            execute: () => Effect.succeed({ content: "ok" }),
+            options: { codemode: false },
+          } as unknown as Info)
+        })
+        .pipe(Effect.flip)
+
+      expect(error).toBeInstanceOf(Tool.RegistrationError)
+      expect(error.name).toBe("phone_type")
+      expect(error.message).toContain('Expected string, got undefined\n  at ["description"]')
+      expect((yield* service.snapshot()).definitions.map((tool) => tool.name)).toEqual(["execute"])
+    }),
+  )
+
   it.effect("canonicalizes effective definitions and keeps Code Mode last", () =>
     Effect.gen(function* () {
       const service = yield* Tool.Service