From 271f5ef9bcceba185eee4f7225a883f857b04715 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 1 Nov 2026 20:30:04 -0600 Subject: [PATCH] refactor(core): share the ToolFailure error mapping in tools (#61140) --- packages/core/src/tool/plugin/edit.ts | 7 ++----- packages/core/src/tool/plugin/glob.ts | 7 ++----- packages/core/src/tool/plugin/image-generate.ts | 9 +++------ packages/core/src/tool/plugin/opencode.ts | 7 ++----- packages/core/src/tool/plugin/patch.ts | 11 +++-------- packages/core/src/tool/runtime.ts | 6 +++++- 6 files changed, 17 insertions(+), 30 deletions(-) diff --git a/packages/core/src/tool/plugin/edit.ts b/packages/core/src/tool/plugin/edit.ts index b380b8312f3..789853955bb 100644 --- a/packages/core/src/tool/plugin/edit.ts +++ b/packages/core/src/tool/plugin/edit.ts @@ -11,6 +11,7 @@ import { ToolFailure } from "@opencode/ai/effect" import { FileDiff } from "@opencode/schema/file-diff" import { Bom } from "@opencode/util/bom" import { Effect, Schema } from "effect" +import { toolFailure } from "../runtime.js" import { Environment } from "../../environment/index.js" import { FileMutation } from "../../file-mutation.js" import { Formatter } from "../../formatter.js" @@ -381,11 +382,7 @@ export const Plugin = { content: `Edited ${output.files[0]?.file} (${output.replacements} replacement${output.replacements === 1 ? "" : "s"})`, metadata: { files: output.files }, })), - Effect.mapError((error) => - error instanceof ToolFailure - ? error - : new ToolFailure({ message: `Unable to edit ${input.path}`, error }), - ), + Effect.mapError(toolFailure(`Unable to edit ${input.path}`)), ) }, }), diff --git a/packages/core/src/tool/plugin/glob.ts b/packages/core/src/tool/plugin/glob.ts index 40bcc7f91c5..b7d5ff11a48 100644 --- a/packages/core/src/tool/plugin/glob.ts +++ b/packages/core/src/tool/plugin/glob.ts @@ -4,6 +4,7 @@ import { ToolFailure } from "@opencode/ai/effect" import type { Context } from "@opencode/plugin/effect/plugin" import { Effect, Schema } from "effect" import path from "path" +import { toolFailure } from "../runtime.js" import { Environment } from "../../environment/index.js" import { FileSystem } from "../../filesystem.js" import { Location } from "../../location.js" @@ -126,11 +127,7 @@ export const Plugin = { ), metadata: { count: result.entries.length, truncated: result.truncated }, })), - Effect.mapError((error) => - error instanceof ToolFailure - ? error - : new ToolFailure({ message: `Unable to find files matching ${input.pattern}`, error }), - ), + Effect.mapError(toolFailure(`Unable to find files matching ${input.pattern}`)), ), }), ) diff --git a/packages/core/src/tool/plugin/image-generate.ts b/packages/core/src/tool/plugin/image-generate.ts index 88ddcbc96fd..bff7bd1a58c 100644 --- a/packages/core/src/tool/plugin/image-generate.ts +++ b/packages/core/src/tool/plugin/image-generate.ts @@ -8,6 +8,7 @@ import { Identifier } from "@opencode/schema/identifier" import { Global } from "@opencode/util/global" import { Duration, Effect, Schema, Scope, Stream } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http" +import { toolFailure } from "../runtime.js" import { Config } from "../../config.js" import { ConfigEntryObserver } from "../../config/plugin/entry-observer.js" import { Environment } from "../../environment/index.js" @@ -186,9 +187,7 @@ export const Plugin = { duration: DOWNLOAD_TIMEOUT, orElse: () => Effect.fail(new ToolFailure({ message: `Timed out downloading ${source}.` })), }), - Effect.mapError((error) => - error instanceof ToolFailure ? error : new ToolFailure({ message: `Unable to download ${source}`, error }), - ), + Effect.mapError(toolFailure(`Unable to download ${source}`)), ) } const target = yield* access.authorizeRead(source, context) @@ -374,9 +373,7 @@ export const Plugin = { metadata: { path: stem, model: ref, jobID: id }, } }).pipe( - Effect.mapError((error) => - error instanceof ToolFailure ? error : new ToolFailure({ message: "Unable to generate image", error }), - ), + Effect.mapError(toolFailure("Unable to generate image")), ), }) }) diff --git a/packages/core/src/tool/plugin/opencode.ts b/packages/core/src/tool/plugin/opencode.ts index d18bb45e41d..615940677a7 100644 --- a/packages/core/src/tool/plugin/opencode.ts +++ b/packages/core/src/tool/plugin/opencode.ts @@ -7,6 +7,7 @@ import { Model } from "@opencode/schema/model" import { AbsolutePath } from "@opencode/schema/schema" import { Session } from "@opencode/schema/session" import { Effect, Schema } from "effect" +import { toolFailure } from "../runtime.js" import { FileAccess } from "../../file-access.js" import { Git } from "../../git.js" import { Permission } from "../../permission.js" @@ -216,11 +217,7 @@ export const Plugin = { content: `Removed worktree ${target.absolute}.`, } }).pipe( - Effect.mapError((error) => - error instanceof ToolFailure - ? error - : new ToolFailure({ message: `Unable to remove worktree ${input.directory}`, error }), - ), + Effect.mapError(toolFailure(`Unable to remove worktree ${input.directory}`)), ), }) draft.add({ diff --git a/packages/core/src/tool/plugin/patch.ts b/packages/core/src/tool/plugin/patch.ts index 554e7a9d3eb..dedcb831778 100644 --- a/packages/core/src/tool/plugin/patch.ts +++ b/packages/core/src/tool/plugin/patch.ts @@ -6,6 +6,7 @@ import { ToolFailure } from "@opencode/ai/effect" import { FileDiff } from "@opencode/schema/file-diff" import { Effect, Result, Schema } from "effect" import { Bom } from "@opencode/util/bom" +import { toolFailure } from "../runtime.js" import { Environment } from "../../environment/index.js" import { Formatter } from "../../formatter.js" import { FileMutation } from "../../file-mutation.js" @@ -203,11 +204,7 @@ export const Plugin = { updates.set(target.absolute, next) } }).pipe( - Effect.mapError((error) => - error instanceof ToolFailure - ? error - : new ToolFailure({ message: `Unable to prepare patch at ${hunk.path}`, error }), - ), + Effect.mapError(toolFailure(`Unable to prepare patch at ${hunk.path}`)), ) } @@ -307,9 +304,7 @@ export const Plugin = { content: toModelContent(output), metadata: { files: output.files }, })), - Effect.mapError((error) => - error instanceof ToolFailure ? error : new ToolFailure({ message: "Unable to apply patch", error }), - ), + Effect.mapError(toolFailure("Unable to apply patch")), ) }, }), diff --git a/packages/core/src/tool/runtime.ts b/packages/core/src/tool/runtime.ts index 152b4cd3102..751757c7194 100644 --- a/packages/core/src/tool/runtime.ts +++ b/packages/core/src/tool/runtime.ts @@ -1,4 +1,4 @@ -import { emptyInputJsonSchema, type ToolDefinition } from "@opencode/ai/effect" +import { emptyInputJsonSchema, ToolFailure, type ToolDefinition } from "@opencode/ai/effect" import { isRecord } from "@opencode/util/record" import { Tool } from "@opencode/schema/tool" import type { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec" @@ -291,3 +291,7 @@ export const effectiveName = (tool: Tool.Info) => tool.options?.namespace === undefined ? normalizedName(tool) : `${tool.options.namespace.replaceAll(".", "_")}_${normalizedName(tool)}` + +/** For `Effect.mapError`: keep a `ToolFailure` as is, wrap anything else with `message`. */ +export const toolFailure = (message: string) => (error: unknown) => + error instanceof ToolFailure ? error : new ToolFailure({ message, error })