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

@ -1,6 +1,7 @@
export * as EventFeed from "./event-feed"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { Event } from "@opencode-ai/schema/event"
import { isOpenCodeEvent, OpenCodeEvent } from "@opencode-ai/protocol/groups/event"
import { Cause, Context, Effect, Layer, Queue, Schema, Scope, Stream } from "effect"
@ -12,7 +13,7 @@ export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberO
) {}
export class EncodingError extends Schema.TaggedErrorClass<EncodingError>()("EventFeed.EncodingError", {
eventID: EventV2.ID,
eventID: Event.ID,
eventType: Schema.String,
cause: Schema.Defect(),
}) {}
@ -32,7 +33,7 @@ export function frame(event: OpenCodeEvent) {
}
export const make = Effect.fn("EventFeed.make")(function* (
observe: (subscriber: EventV2.Subscriber) => Effect.Effect<EventV2.Unsubscribe>,
observe: (subscriber: Bus.Subscriber) => Effect.Effect<Bus.Unsubscribe>,
options?: { readonly capacity?: number; readonly encode?: (event: OpenCodeEvent) => string },
) {
const capacity = options?.capacity ?? SubscriberCapacity
@ -46,7 +47,7 @@ export const make = Effect.fn("EventFeed.make")(function* (
for (const subscriber of current) Queue.failCauseUnsafe(subscriber, Cause.fail(error))
})
const publish = Effect.fnUntraced(function* (event: EventV2.Payload) {
const publish = Effect.fnUntraced(function* (event: Event.Payload) {
if (!isOpenCodeEvent(event)) return
if (subscribers.size === 0) return
const encoded = yield* Effect.try({
@ -84,7 +85,7 @@ export const make = Effect.fn("EventFeed.make")(function* (
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const events = yield* EventV2.Service
return yield* make(events.listen)
const bus = yield* Bus.Service
return yield* make(bus.listen)
}),
)

View file

@ -1,13 +1,27 @@
import { AgentV2 } from "@opencode-ai/core/agent"
import { Agent } from "@opencode-ai/core/agent"
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api"
import { response } from "../location"
import { AgentNotFoundError } from "@opencode-ai/protocol/errors"
export const AgentHandler = HttpApiBuilder.group(Api, "server.agent", (handlers) =>
handlers.handle("agent.list", () =>
Effect.gen(function* () {
return yield* response(AgentV2.Service.use((agent) => agent.list()))
}),
),
handlers
.handle("agent.list", () =>
Effect.gen(function* () {
return yield* response(Agent.Service.use((agent) => agent.list()))
}),
)
.handle(
"agent.get",
Effect.fn(function* (ctx) {
const agent = yield* Agent.Service.use((service) => service.get(ctx.params.agentID))
if (!agent)
return yield* new AgentNotFoundError({
agentID: ctx.params.agentID,
message: `Agent not found: ${ctx.params.agentID}`,
})
return yield* response(Effect.succeed(agent))
}),
),
)

View file

@ -1,8 +1,8 @@
import { CommandV2 } from "@opencode-ai/core/command"
import { Command } from "@opencode-ai/core/command"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api"
import { response } from "../location"
export const CommandHandler = HttpApiBuilder.group(Api, "server.command", (handlers) =>
handlers.handle("command.list", () => response(CommandV2.Service.use((command) => command.list()))),
handlers.handle("command.list", () => response(Command.Service.use((command) => command.list()))),
)

View file

@ -1,4 +1,5 @@
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { Event } from "@opencode-ai/schema/event"
import { Effect, Stream } from "effect"
import { HttpServerResponse } from "effect/unstable/http"
import { HttpApiBuilder } from "effect/unstable/httpapi"
@ -11,7 +12,7 @@ export const EventHandler = HttpApiBuilder.group(Api, "server.event", (handlers)
return handlers.handleRaw("event.subscribe", () =>
Effect.gen(function* () {
const connected = {
id: EventV2.ID.create(),
id: Event.ID.create(),
type: "server.connected",
data: {},
} as const

View file

@ -1,5 +1,5 @@
import { SessionMessage } from "@opencode-ai/core/session/message"
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { Effect, Schema } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api"
@ -26,7 +26,7 @@ const cursor = {
export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handlers) =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const session = yield* Session.Service
return handlers.handle(
"session.messages",

View file

@ -1,5 +1,5 @@
import { Location } from "@opencode-ai/core/location"
import { PermissionV2 } from "@opencode-ai/core/permission"
import { Permission } from "@opencode-ai/core/permission"
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
import { Effect } from "effect"
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
@ -7,7 +7,7 @@ import { Api } from "../api"
import { PermissionNotFoundError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
import { response } from "../location"
function missingRequest(id: PermissionV2.ID) {
function missingRequest(id: Permission.ID) {
return new PermissionNotFoundError({ requestID: id, message: `Permission request not found: ${id}` })
}
@ -17,14 +17,14 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
.handle(
"permission.request.list",
Effect.fn(function* () {
const permission = yield* PermissionV2.Service
const permission = yield* Permission.Service
return yield* response(permission.list())
}),
)
.handle(
"session.permission.create",
Effect.fn(function* (ctx) {
const permission = yield* PermissionV2.Service
const permission = yield* Permission.Service
return {
data: yield* permission
.ask({
@ -53,14 +53,14 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
.handle(
"session.permission.list",
Effect.fn(function* (ctx) {
const permission = yield* PermissionV2.Service
const permission = yield* Permission.Service
return { data: yield* permission.forSession(ctx.params.sessionID) }
}),
)
.handle(
"session.permission.get",
Effect.fn(function* (ctx) {
const permission = yield* PermissionV2.Service
const permission = yield* Permission.Service
const request = yield* permission.get(ctx.params.requestID)
if (!request || request.sessionID !== ctx.params.sessionID) return yield* missingRequest(ctx.params.requestID)
return { data: request }
@ -69,12 +69,12 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
.handle(
"session.permission.reply",
Effect.fn(function* (ctx) {
const permission = yield* PermissionV2.Service
const permission = yield* Permission.Service
const request = yield* permission.get(ctx.params.requestID)
if (!request || request.sessionID !== ctx.params.sessionID) return yield* missingRequest(ctx.params.requestID)
yield* permission
.reply({ requestID: ctx.params.requestID, reply: ctx.payload.reply, message: ctx.payload.message })
.pipe(Effect.catchTag("PermissionV2.NotFoundError", () => missingRequest(ctx.params.requestID)))
.pipe(Effect.catchTag("Permission.NotFoundError", () => missingRequest(ctx.params.requestID)))
return HttpApiSchema.NoContent.make()
}),
)

View file

@ -1,4 +1,4 @@
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { Effect } from "effect"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api"
@ -7,7 +7,7 @@ import { response } from "../location"
export const PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handlers) =>
handlers.handle("plugin.list", () =>
Effect.gen(function* () {
return yield* response(PluginV2.Service.use((plugin) => plugin.list()))
return yield* response(Plugin.Service.use((plugin) => plugin.list()))
}),
),
)

View file

@ -1,22 +1,22 @@
import { QuestionV2 } from "@opencode-ai/core/question"
import { Question } from "@opencode-ai/core/question"
import { Effect } from "effect"
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
import { Api } from "../api"
import { QuestionNotFoundError } from "@opencode-ai/protocol/errors"
import { response } from "../location"
function missingRequest(id: QuestionV2.ID) {
function missingRequest(id: Question.ID) {
return new QuestionNotFoundError({ requestID: id, message: `Question request not found: ${id}` })
}
export const QuestionHandler = HttpApiBuilder.group(Api, "server.question", (handlers) =>
Effect.gen(function* () {
const withOwnedQuestion = Effect.fnUntraced(function* <A, E>(
sessionID: QuestionV2.Request["sessionID"],
requestID: QuestionV2.ID,
use: (question: QuestionV2.Interface) => Effect.Effect<A, E>,
sessionID: Question.Request["sessionID"],
requestID: Question.ID,
use: (question: Question.Interface) => Effect.Effect<A, E>,
) {
const question = yield* QuestionV2.Service
const question = yield* Question.Service
const request = (yield* question.list()).find((request) => request.id === requestID)
if (!request || request.sessionID !== sessionID) return yield* missingRequest(requestID)
return yield* use(question)
@ -26,14 +26,14 @@ export const QuestionHandler = HttpApiBuilder.group(Api, "server.question", (han
.handle(
"question.request.list",
Effect.fn(function* () {
const question = yield* QuestionV2.Service
const question = yield* Question.Service
return yield* response(question.list())
}),
)
.handle(
"session.question.list",
Effect.fn(function* (ctx) {
const question = yield* QuestionV2.Service
const question = yield* Question.Service
const requests = yield* question.list()
return { data: requests.filter((request) => request.sessionID === ctx.params.sessionID) }
}),
@ -44,7 +44,7 @@ export const QuestionHandler = HttpApiBuilder.group(Api, "server.question", (han
yield* withOwnedQuestion(ctx.params.sessionID, ctx.params.requestID, (question) =>
question
.reply({ requestID: ctx.params.requestID, answers: ctx.payload.answers })
.pipe(Effect.catchTag("QuestionV2.NotFoundError", () => missingRequest(ctx.params.requestID))),
.pipe(Effect.catchTag("Question.NotFoundError", () => missingRequest(ctx.params.requestID))),
)
return HttpApiSchema.NoContent.make()
}),
@ -55,7 +55,7 @@ export const QuestionHandler = HttpApiBuilder.group(Api, "server.question", (han
yield* withOwnedQuestion(ctx.params.sessionID, ctx.params.requestID, (question) =>
question
.reject(ctx.params.requestID)
.pipe(Effect.catchTag("QuestionV2.NotFoundError", () => missingRequest(ctx.params.requestID))),
.pipe(Effect.catchTag("Question.NotFoundError", () => missingRequest(ctx.params.requestID))),
)
return HttpApiSchema.NoContent.make()
}),

View file

@ -1,4 +1,4 @@
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { InstructionEntry } from "@opencode-ai/core/session/instruction-entry"
import { DateTime, Effect, Stream } from "effect"
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
@ -23,7 +23,7 @@ const DefaultSessionsLimit = 50
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const session = yield* Session.Service
return handlers
.handle(

View file

@ -1,8 +1,8 @@
import { SkillV2 } from "@opencode-ai/core/skill"
import { Skill } from "@opencode-ai/core/skill"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { Api } from "../api"
import { response } from "../location"
export const SkillHandler = HttpApiBuilder.group(Api, "server.skill", (handlers) =>
handlers.handle("skill.list", () => response(SkillV2.Service.use((skill) => skill.list()))),
handlers.handle("skill.list", () => response(Skill.Service.use((skill) => skill.list()))),
)

View file

@ -1,7 +1,7 @@
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-services"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { Workspace } from "@opencode-ai/core/workspace"
import { Effect, Layer } from "effect"
import { HttpServerRequest } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
@ -34,7 +34,7 @@ export function requestRef(request: HttpServerRequest.HttpServerRequest): Locati
(request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd())
return Location.Ref.make({
directory: AbsolutePath.make(directory),
workspaceID: workspaceID ? WorkspaceV2.ID.make(workspaceID) : undefined,
workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : undefined,
})
}

View file

@ -2,9 +2,9 @@ import { Database } from "@opencode-ai/core/database/database"
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-services"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { SessionTable } from "@opencode-ai/core/session/sql"
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { Workspace } from "@opencode-ai/core/workspace"
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
import { eq } from "drizzle-orm"
import { Effect, Layer, Schema } from "effect"
@ -19,7 +19,7 @@ export class FormLocationMiddleware extends HttpApiMiddleware.Service<
error: [InvalidRequestError, SessionNotFoundError],
}) {}
const decodeSessionID = Schema.decodeUnknownEffect(SessionV2.ID)
const decodeSessionID = Schema.decodeUnknownEffect(Session.ID)
export const formLocationLayer = Layer.effect(
FormLocationMiddleware,
@ -65,7 +65,7 @@ export const formLocationLayer = Layer.effect(
locations.get(
Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : undefined,
}),
),
),

View file

@ -2,9 +2,9 @@ import { Database } from "@opencode-ai/core/database/database"
import { LocationServiceMap } from "@opencode-ai/core/location-services"
import { Location } from "@opencode-ai/core/location"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { SessionTable } from "@opencode-ai/core/session/sql"
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { Workspace } from "@opencode-ai/core/workspace"
import { eq } from "drizzle-orm"
import { Effect, Layer, Schema } from "effect"
import { HttpRouter } from "effect/unstable/http"
@ -19,7 +19,7 @@ export class SessionLocationMiddleware extends HttpApiMiddleware.Service<
error: [InvalidRequestError, SessionNotFoundError],
}) {}
const decodeSessionID = Schema.decodeUnknownEffect(SessionV2.ID)
const decodeSessionID = Schema.decodeUnknownEffect(Session.ID)
export const sessionLocationLayer = Layer.effect(
SessionLocationMiddleware,
@ -56,7 +56,7 @@ export const sessionLocationLayer = Layer.effect(
locations.get(
Location.Ref.make({
directory: AbsolutePath.make(row.directory),
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : undefined,
}),
),
),

View file

@ -3,18 +3,18 @@ import { App } from "@opencode-ai/core/app"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { httpClient } from "@opencode-ai/util/effect/app-node-platform"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { EventLogger } from "@opencode-ai/core/event-logger"
import { FileSystemSearch } from "@opencode-ai/core/filesystem/search"
import { Observability } from "@opencode-ai/util/observability"
import { Credential } from "@opencode-ai/core/credential"
import { Config } from "@opencode-ai/core/config"
import { CommandV2 } from "@opencode-ai/core/command"
import { Command } from "@opencode-ai/core/command"
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
import { Pty } from "@opencode-ai/core/pty"
import { Project } from "@opencode-ai/core/project"
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { Shell } from "@opencode-ai/core/shell"
import { Job } from "@opencode-ai/core/job"
import { MCP } from "@opencode-ai/core/mcp/index"
@ -25,7 +25,6 @@ import { ModelsDev } from "@opencode-ai/core/models-dev"
import { SessionRestart } from "@opencode-ai/core/session/execution/restart"
import { PluginRuntime } from "@opencode-ai/core/plugin/runtime"
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk"
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
import { WellKnown } from "@opencode-ai/core/wellknown"
import { Watcher } from "@opencode-ai/core/filesystem/watcher"
import { HttpRouter } from "effect/unstable/http"
@ -45,13 +44,12 @@ import type { ServerOptions } from "./options"
const applicationServices = LayerNode.group([
Database.node,
EventV2.node,
Bus.node,
EventLogger.node,
httpClient,
ToolOutputStore.cleanupNode,
Job.node,
Project.node,
SessionV2.node,
Session.node,
PluginRuntime.providerNode,
SdkPlugins.node,
PermissionSaved.node,
@ -99,7 +97,7 @@ function makeRoutes<AuthError, AuthServices>(
}),
],
[InstructionDiscovery.node, InstructionDiscovery.configured({ project: options.config?.project })],
[CommandV2.node, CommandV2.configured({ gitbash: options.windows?.gitbash })],
[Command.node, Command.configured({ gitbash: options.windows?.gitbash })],
[Pty.node, Pty.configured({ gitbash: options.windows?.gitbash })],
[Shell.node, Shell.configured({ gitbash: options.windows?.gitbash })],
[