refactor(core): share the ToolFailure error mapping in tools (#61140)

This commit is contained in:
Aiden Cline
2026-11-01 20:30:04 -06:00
committed by GitHub
Unverified
parent 66d113c4e4
commit 271f5ef9bc
6 changed files with 17 additions and 30 deletions
+2 -5
View File
@@ -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}`)),
)
},
}),
+2 -5
View File
@@ -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}`)),
),
}),
)
@@ -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")),
),
})
})
+2 -5
View File
@@ -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({
+3 -8
View File
@@ -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")),
)
},
}),
+5 -1
View File
@@ -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 })