refactor(core): simplify tool infrastructure (#43967)

This commit is contained in:
Kit Langton
2026-08-21 15:24:06 -04:00
committed by GitHub
parent 79d5436d2a
commit 08d52be8c2
8 changed files with 41 additions and 66 deletions
+14 -25
View File
@@ -90,31 +90,20 @@ export function applyOnly(db: Database, input: Migration[]) {
)
}),
)
if (migration.foreignKeys !== false) {
yield* apply.pipe(
Effect.tapError((error) =>
Effect.logError("database migration failed", {
migration: migration.id,
durationMs: Date.now() - started,
error,
}),
),
)
yield* Effect.logInfo("database migration completed", {
migration: migration.id,
durationMs: Date.now() - started,
})
continue
}
// Durable Object SQLite rejects the foreign_keys toggle; the closest
// allowlisted relaxation is deferring enforcement to transaction commit.
const relaxForeignKeys = supportsForeignKeyToggle
? db.run(sql`PRAGMA foreign_keys = OFF`)
: db.run(sql`PRAGMA defer_foreign_keys = ON`)
const restoreForeignKeys = supportsForeignKeyToggle ? db.run(sql`PRAGMA foreign_keys = ON`) : Effect.void
yield* relaxForeignKeys
yield* apply.pipe(
Effect.ensuring(restoreForeignKeys.pipe(Effect.orDie)),
const run =
migration.foreignKeys !== false
? apply
: Effect.gen(function* () {
// Durable Object SQLite rejects the foreign_keys toggle; the closest
// allowlisted relaxation is deferring enforcement to transaction commit.
const relaxForeignKeys = supportsForeignKeyToggle
? db.run(sql`PRAGMA foreign_keys = OFF`)
: db.run(sql`PRAGMA defer_foreign_keys = ON`)
const restoreForeignKeys = supportsForeignKeyToggle ? db.run(sql`PRAGMA foreign_keys = ON`) : Effect.void
yield* relaxForeignKeys
yield* apply.pipe(Effect.ensuring(restoreForeignKeys.pipe(Effect.orDie)))
})
yield* run.pipe(
Effect.tapError((error) =>
Effect.logError("database migration failed", {
migration: migration.id,
+5 -8
View File
@@ -4,16 +4,13 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
export function build<A, E>(root: LayerNode.Node<A, E, any>, replacements: LayerNode.Replacements = []) {
let allReplacements = replacements
// Only build the location service map if it's actually needed
if (LayerNode.hasUnbound(root, LocationServiceMap.node) && !hasReplacement(replacements, LocationServiceMap.node)) {
const locationMap = buildLocationServiceMap(replacements)
const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] })
allReplacements = replacements.concat([[LocationServiceMap.node, locationMapNode]])
}
if (!LayerNode.hasUnbound(root, LocationServiceMap.node) || hasReplacement(replacements, LocationServiceMap.node))
return LayerNode.compile(root, replacements)
return LayerNode.compile(root, allReplacements)
const locationMap = buildLocationServiceMap(replacements)
const locationMapNode = makeGlobalNode({ service: LocationServiceMap.Service, layer: locationMap, deps: [] })
return LayerNode.compile(root, replacements.concat([[LocationServiceMap.node, locationMapNode]]))
}
function hasReplacement(replacements: LayerNode.Replacements, node: LayerNode.Node<unknown, unknown, any>) {
+1 -1
View File
@@ -70,7 +70,7 @@ const baseLayer = Layer.effect(
return yield* Effect.die(new Error("Path escapes the location"))
const real = yield* fs.realPath(absolute).pipe(Effect.orDie)
if (!FSUtil.contains(root, real)) return yield* Effect.die(new Error("Path escapes the location"))
return { absolute, real, directory: location.directory, root }
return { absolute, real, directory: location.directory }
})
return Service.of({
find: search.find,
+3 -3
View File
@@ -286,10 +286,10 @@ export const Plugin = {
}),
{ discard: true },
)
const files = yield* Effect.forEach(prepared, (change) => {
if (change.type === "delete") return Effect.succeed(patchFile(change))
const files = prepared.map((change) => {
if (change.type === "delete") return patchFile(change)
const target = change.type === "update" && change.moveTarget ? change.moveTarget : change.target
return Effect.succeed(patchFile(change, formatted.get(target.absolute)))
return patchFile(change, formatted.get(target.absolute))
})
return { applied, files }
}).pipe(
+9 -12
View File
@@ -94,6 +94,13 @@ const toolResult = (output: Output) => {
}
}
const backgroundResult = (shellID: string) => ({
output: BACKGROUND_STARTED,
shellID,
truncated: false,
status: "running" as const,
})
export const Plugin = {
id: "opencode.tool.shell",
effect: Effect.fn("ShellTool.Plugin")(function* (ctx: PluginContext) {
@@ -303,12 +310,7 @@ export const Plugin = {
if (input.background === true) {
yield* runtime.job.background(job.id)
yield* notifyWhenDone(context.sessionID, context.id, info.id, info.command, settled)
return {
output: BACKGROUND_STARTED,
shellID: info.id,
truncated: false,
status: "running" as const,
}
return backgroundResult(info.id)
}
const result = yield* runtime.job
@@ -317,12 +319,7 @@ export const Plugin = {
if (result?.type === "backgrounded") {
yield* shell.timeout(info.id, 0)
yield* notifyWhenDone(context.sessionID, context.id, info.id, info.command, settled)
return {
output: BACKGROUND_STARTED,
shellID: info.id,
truncated: false,
status: "running" as const,
}
return backgroundResult(info.id)
}
if (result?.info.status === "error")
return yield* Effect.fail(new Error(result.info.error ?? "Command failed"))
+8 -13
View File
@@ -12,12 +12,15 @@ import { SessionSchema } from "../../session/schema.js"
export const name = "subagent"
const NO_TEXT = "Subagent completed without a text response."
const backgroundStarted = (sessionID: SessionSchema.ID) =>
[
const backgroundResult = (sessionID: SessionSchema.ID) => ({
sessionID,
status: "running" as const,
output: [
`The subagent is working in the background (sessionID: ${sessionID}). You will be notified automatically when it finishes.`,
"DO NOT sleep, poll for progress, ask the subagent for status, or duplicate this subagent's work; avoid working with the same files or topics it is using.",
"Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.",
].join("\n")
].join("\n"),
})
export const Input = Schema.Struct({
agent: Schema.String.annotate({ description: "The type of specialized agent to use for this task" }),
@@ -259,11 +262,7 @@ export const Plugin = {
if (background) {
yield* runtime.job.background(info.id)
yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description, info.started_at)
return {
sessionID: child.id,
status: "running" as const,
output: backgroundStarted(child.id),
}
return backgroundResult(child.id)
}
const result = yield* runtime.job.block({ id: child.id, sessionID: context.sessionID }).pipe(
@@ -281,11 +280,7 @@ export const Plugin = {
input.description,
result.info.started_at,
)
return {
sessionID: child.id,
status: "running" as const,
output: backgroundStarted(child.id),
}
return backgroundResult(child.id)
}
// Failure surfaces keep the sessionID visible so the model can continue the child.
if (result?.info.status === "error")
@@ -47,7 +47,6 @@ const acceptHeader = (format: Format) => {
case "html":
return "text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1"
}
return "*/*"
}
const headers = (format: Format, userAgent: string) => ({
+1 -3
View File
@@ -220,8 +220,6 @@ const layer = Layer.effect(
const gitStrategy = yield* WorktreeGit.make
yield* register(gitStrategy).pipe(Effect.orDie)
const strategies = () => Array.from(registry.values())
const source = Effect.fnUntraced(function* (input: AbsolutePath | undefined, projectID: ProjectSchema.ID) {
const sourceDirectory = input ?? (yield* ops.primary(projectID))?.directory
if (!sourceDirectory) return yield* new SourceDirectoryNotFoundError({ projectID })
@@ -290,7 +288,7 @@ const layer = Layer.effect(
const discovered = yield* Effect.forEach(
sourceDirectories,
(sourceDirectory) =>
Effect.forEach(strategies(), (strategy) =>
Effect.forEach(Array.from(registry.values()), (strategy) =>
strategy.list(sourceDirectory).pipe(
Effect.catchTag("Worktree.DirectoryUnavailableError", () => Effect.succeed([])),
Effect.map((items) =>