fix(core): harden unified tool runtime (#31171)

This commit is contained in:
Kit Langton
2026-06-06 21:55:34 -04:00
committed by GitHub
parent 48c26fa039
commit eb9a683b40
35 changed files with 435 additions and 237 deletions
+23 -21
View File
@@ -1,6 +1,6 @@
export * as ToolRegistry from "./registry"
import { Tool as LlmTool, ToolOutput, type ToolCall, type ToolSettlement } from "@opencode-ai/llm"
import { ToolOutput, type ToolCall, type ToolDefinition, type ToolSettlement } from "@opencode-ai/llm"
import { Context, Effect, Layer, Scope } from "effect"
import { AgentV2 } from "../agent"
import { PermissionV2 } from "../permission"
@@ -9,7 +9,7 @@ import { SessionSchema } from "../session/schema"
import { ToolOutputStore } from "../tool-output-store"
import { Wildcard } from "../util/wildcard"
import { ApplicationTools } from "./application-tools"
import { Tool } from "./tool"
import { definition, permission, settle, validateName, type AnyTool, type RegistrationError } from "./tool"
import { Tools } from "./tools"
export type ExecuteInput = {
@@ -22,13 +22,11 @@ export type ExecuteInput = {
export interface Interface {
readonly materialize: (permissions?: PermissionV2.Ruleset) => Effect.Effect<Materialization>
/** Internal registration capability exposed publicly only through Tools.Service. */
readonly register: (
tools: Readonly<Record<string, Tool.AnyTool>>,
) => Effect.Effect<void, Tool.RegistrationError, Scope.Scope>
readonly register: (tools: Readonly<Record<string, AnyTool>>) => Effect.Effect<void, RegistrationError, Scope.Scope>
}
export interface Materialization {
readonly definitions: ReadonlyArray<ReturnType<typeof LlmTool.toDefinitions>[number]>
readonly definitions: ReadonlyArray<ToolDefinition>
readonly settle: (input: ExecuteInput) => Effect.Effect<Settlement, ToolOutputStore.Error>
}
@@ -43,7 +41,7 @@ const registryLayer = Layer.effect(
Effect.gen(function* () {
const applications = yield* ApplicationTools.Service
const resources = yield* ToolOutputStore.Service
type Registration = { readonly identity: object; readonly tool: Tool.AnyTool }
type Registration = { readonly identity: object; readonly tool: AnyTool }
const local = new Map<string, Array<{ readonly token: object; readonly registration: Registration }>>()
const settleWith = Effect.fn("ToolRegistry.settle")(function* (input: ExecuteInput, advertised?: object) {
@@ -58,7 +56,7 @@ const registryLayer = Layer.effect(
}
if (advertised && registration.identity !== advertised)
return { result: { type: "error" as const, value: `Stale tool call: ${input.call.name}` } }
const pending = yield* Tool.settle(registration.tool, input.call, {
const pending = yield* settle(registration.tool, input.call, {
sessionID: input.sessionID,
agent: input.agent,
assistantMessageID: input.assistantMessageID,
@@ -84,17 +82,21 @@ const registryLayer = Layer.effect(
register: Effect.fn("ToolRegistry.register")(function* (tools) {
const entries = Object.entries(tools)
if (entries.length === 0) return
yield* Effect.forEach(entries, ([name]) => Tool.validateName(name), { discard: true })
const token = {}
for (const [name, tool] of entries)
local.set(name, [...(local.get(name) ?? []), { token, registration: { identity: {}, tool } }])
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
for (const [name] of entries) {
const registrations = local.get(name)?.filter((registration) => registration.token !== token) ?? []
if (registrations.length > 0) local.set(name, registrations)
else local.delete(name)
}
yield* Effect.forEach(entries, ([name]) => validateName(name), { discard: true })
yield* Effect.uninterruptible(
Effect.gen(function* () {
const token = {}
for (const [name, tool] of entries)
local.set(name, [...(local.get(name) ?? []), { token, registration: { identity: {}, tool } }])
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
for (const [name] of entries) {
const registrations = local.get(name)?.filter((registration) => registration.token !== token) ?? []
if (registrations.length > 0) local.set(name, registrations)
else local.delete(name)
}
}),
)
}),
)
}),
@@ -105,9 +107,9 @@ const registryLayer = Layer.effect(
if (registration) registrations.set(name, registration)
}
for (const [name, registration] of registrations)
if (whollyDisabled(Tool.permission(registration.tool, name), permissions)) registrations.delete(name)
if (whollyDisabled(permission(registration.tool, name), permissions)) registrations.delete(name)
return {
definitions: Array.from(registrations, ([name, registration]) => Tool.definition(name, registration.tool)),
definitions: Array.from(registrations, ([name, registration]) => definition(name, registration.tool)),
settle: (input) => {
const registration = registrations.get(input.call.name)
if (registration) return settleWith(input, registration.identity)