refactor(core): consolidate tool architecture

This commit is contained in:
Dax Raad 2026-07-26 20:08:55 -04:00
commit 8db7487c89
466 changed files with 9405 additions and 11071 deletions

View file

@ -9,48 +9,47 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
import { filesystem } from "@opencode-ai/util/effect/app-node-platform"
import { Database } from "@opencode-ai/core/database/database"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { FSUtil } from "@opencode-ai/util/fs-util"
import { Global } from "@opencode-ai/util/global"
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Model } from "@opencode-ai/core/model"
import { Provider } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { AgentV2 } from "@opencode-ai/core/agent"
import { Agent } from "@opencode-ai/core/agent"
import { Job } from "@opencode-ai/core/job"
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { SessionEvent } from "@opencode-ai/core/session/event"
import { SessionExecution } from "@opencode-ai/core/session/execution"
import { SessionMessage } from "@opencode-ai/core/session/message"
import { SessionStore } from "@opencode-ai/core/session/store"
import { PermissionV2 } from "@opencode-ai/core/permission"
import { Permission } from "@opencode-ai/core/permission"
import { PluginRuntime } from "@opencode-ai/core/plugin/runtime"
import { Shell } from "@opencode-ai/core/shell"
import { Shell as ShellSchema } from "@opencode-ai/schema/shell"
import { ShellTool } from "@opencode-ai/core/tool/shell"
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
import { ShellTool } from "@opencode-ai/core/tool/plugin/shell"
import { Tool } from "@opencode-ai/core/tool"
import { tmpdir } from "./fixture/tmpdir"
import { testEffect } from "./lib/effect"
import { toolIdentity, executeTool, toolDefinitions, waitForTool } from "./lib/tool"
const sessionID = SessionV2.ID.make("ses_shell_tool_test")
const sessionModel = ModelV2.Ref.make({ id: ModelV2.ID.make("test"), providerID: ProviderV2.ID.make("test") })
const assertions: PermissionV2.AssertInput[] = []
const sessionID = Session.ID.make("ses_shell_tool_test")
const sessionModel = Model.Ref.make({ id: Model.ID.make("test"), providerID: Provider.ID.make("test") })
const assertions: Permission.AssertInput[] = []
let denyAction: string | undefined
let afterPermission = (_input: PermissionV2.AssertInput): Effect.Effect<void> => Effect.void
let afterPermission = (_input: Permission.AssertInput): Effect.Effect<void> => Effect.void
const permission = Layer.succeed(
PermissionV2.Service,
PermissionV2.Service.of({
Permission.Service,
Permission.Service.of({
assert: (input) =>
Effect.sync(() => assertions.push(input)).pipe(
Effect.andThen(Effect.suspend(() => afterPermission(input))),
Effect.andThen(
input.action === denyAction
? Effect.fail(
new PermissionV2.BlockedError({
new Permission.BlockedError({
rules: [],
permission: input.action,
resources: input.resources,
@ -78,30 +77,30 @@ const executionNode = makeGlobalNode({
layer: Layer.effect(
SessionExecution.Service,
Effect.gen(function* () {
const events = yield* EventV2.Service
const bus = yield* Bus.Service
const store = yield* SessionStore.Service
const complete = Effect.fn("ShellTest.complete")(function* (id: SessionV2.ID) {
const complete = Effect.fn("ShellTest.complete")(function* (id: Session.ID) {
const session = yield* store.get(id)
if (!session) return
const assistantMessageID = SessionMessage.ID.create()
yield* events.publish(SessionEvent.Step.Started, {
yield* bus.publish(SessionEvent.Step.Started, {
sessionID: id,
assistantMessageID,
agent: session.agent ?? AgentV2.ID.make("code"),
agent: session.agent ?? Agent.ID.make("code"),
model: sessionModel,
})
yield* events.publish(SessionEvent.Text.Started, {
yield* bus.publish(SessionEvent.Text.Started, {
sessionID: id,
assistantMessageID,
ordinal: 0,
})
yield* events.publish(SessionEvent.Text.Ended, {
yield* bus.publish(SessionEvent.Text.Ended, {
sessionID: id,
assistantMessageID,
ordinal: 0,
text: "ok",
})
yield* events.publish(SessionEvent.Step.Ended, {
yield* bus.publish(SessionEvent.Step.Ended, {
sessionID: id,
assistantMessageID,
finish: "stop",
@ -118,16 +117,15 @@ const executionNode = makeGlobalNode({
})
}),
),
deps: [EventV2.node, SessionStore.node],
deps: [Bus.node, SessionStore.node],
})
const layer = AppNodeBuilder.build(
LayerNode.group([
Database.node,
EventV2.node,
Bus.node,
Job.node,
ToolOutputStore.cleanupNode,
SessionV2.node,
Session.node,
SessionExecution.node,
PluginRuntime.providerNode,
LocationServiceMap.node,
@ -137,7 +135,7 @@ const layer = AppNodeBuilder.build(
]),
[
[SessionExecution.node, executionNode],
[PermissionV2.node, permission],
[Permission.node, permission],
],
)
@ -174,9 +172,9 @@ const progressOverflowCommand = (bytes: number, release: string) =>
? `[Console]::Out.Write(('x' * ${bytes})); while (!(Test-Path -LiteralPath '${release}')) { Start-Sleep -Milliseconds 50 }`
: `head -c ${bytes} /dev/zero | tr '\\0' 'x'; while [ ! -e '${release}' ]; do sleep 0.05; done`
const withSession = <A, E, R>(directory: string, body: (registry: ToolRegistry.Interface) => Effect.Effect<A, E, R>) =>
const withSession = <A, E, R>(directory: string, body: (registry: Tool.Interface) => Effect.Effect<A, E, R>) =>
Effect.gen(function* () {
const sessions = yield* SessionV2.Service
const sessions = yield* Session.Service
const location = Location.Ref.make({ directory: AbsolutePath.make(directory) })
yield* sessions.create({
id: sessionID,
@ -187,7 +185,7 @@ const withSession = <A, E, R>(directory: string, body: (registry: ToolRegistry.I
const locations = yield* LocationServiceMap.Service
const locationLayer = locations.get(location)
return yield* Effect.gen(function* () {
const registry = yield* ToolRegistry.Service
const registry = yield* Tool.Service
yield* waitForTool(registry, ShellTool.name)
return yield* body(registry)
}).pipe(Effect.provide(locationLayer), Effect.ensuring(locations.invalidate(location)))
@ -466,7 +464,7 @@ describe("ShellTool", () => {
reset()
return withSession(tmp.path, (registry) =>
Effect.gen(function* () {
const updates: ToolRegistry.Progress[] = []
const updates: Tool.Metadata[] = []
yield* executeTool(registry, {
...call({ command: steadyProgressCommand }, "call-steady-progress"),
progress: (update) => Effect.sync(() => updates.push(update)),
@ -511,8 +509,8 @@ describe("ShellTool", () => {
reset()
return withSession(tmp.path, (registry) =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const admitted = yield* events.subscribe(SessionEvent.InputAdmitted).pipe(
const bus = yield* Bus.Service
const admitted = yield* bus.subscribe(SessionEvent.InputAdmitted).pipe(
Stream.filter((event) => event.data.sessionID === sessionID && event.data.input.type === "synthetic"),
Stream.runHead,
Effect.forkScoped({ startImmediately: true }),