mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 05:08:32 +00:00
refactor(opencode): build runtimes from layer nodes (#34515)
This commit is contained in:
parent
60b6229c4e
commit
5b62211167
10 changed files with 97 additions and 82 deletions
|
|
@ -30,6 +30,7 @@ import {
|
|||
type SetSessionModeResponse,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import type { AssistantMessage, Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
||||
import { Context, Effect, Layer, ManagedRuntime } from "effect"
|
||||
import * as ACPError from "./error"
|
||||
|
|
@ -569,22 +570,26 @@ export function make(input: {
|
|||
}
|
||||
|
||||
function makeSessionService() {
|
||||
return ManagedRuntime.make(ACPSession.defaultLayer).runSync(
|
||||
return ManagedRuntime.make(AppNodeBuilder.build(ACPSession.node)).runSync(
|
||||
ACPSession.Service.use((service) => Effect.succeed(service)),
|
||||
)
|
||||
}
|
||||
|
||||
function makeDirectoryService(sdk: OpencodeClient) {
|
||||
return ManagedRuntime.make(
|
||||
Directory.layer.pipe(
|
||||
Layer.provide(
|
||||
Layer.succeed(
|
||||
Directory.Loader,
|
||||
Directory.Loader.of({
|
||||
load: (directory) => request(() => loadDirectorySnapshot(sdk, directory), "directory"),
|
||||
}),
|
||||
),
|
||||
),
|
||||
AppNodeBuilder.build(
|
||||
Directory.node,
|
||||
[
|
||||
[
|
||||
Directory.loaderNode,
|
||||
Layer.succeed(
|
||||
Directory.Loader,
|
||||
Directory.Loader.of({
|
||||
load: (directory) => request(() => loadDirectorySnapshot(sdk, directory), "directory"),
|
||||
}),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
).runSync(Directory.Service.use((service) => Effect.succeed(service)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ export const ScrapCommand = cmd({
|
|||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
const { Project } = await import("@/project/project")
|
||||
const { AppNodeBuilder } = await import("@opencode-ai/core/effect/app-node-builder")
|
||||
const { makeRuntime } = await import("@opencode-ai/core/effect/runtime")
|
||||
const runtime = makeRuntime(Project.Service, Project.defaultLayer)
|
||||
const runtime = makeRuntime(Project.Service, AppNodeBuilder.build(Project.node))
|
||||
const list = await runtime.runPromise((project) => project.list())
|
||||
process.stdout.write(JSON.stringify(list, null, 2) + EOL)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ export * as TuiConfig from "./tui"
|
|||
|
||||
import path from "path"
|
||||
import { mergeDeep, unique } from "remeda"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Cause, Context, Effect, Fiber, Layer } from "effect"
|
||||
import { ConfigParse } from "@/config/parse"
|
||||
import * as ConfigPaths from "@/config/paths"
|
||||
|
|
@ -223,7 +225,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
|||
}
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const directory = yield* CurrentWorkingDirectory
|
||||
|
|
@ -257,9 +259,9 @@ export const layer = Layer.effect(
|
|||
}).pipe(Effect.withSpan("TuiConfig.layer")),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Npm.defaultLayer), Layer.provide(FSUtil.defaultLayer))
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [Npm.node, FSUtil.node] })
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
const { runPromise } = makeRuntime(Service, AppNodeBuilder.build(node))
|
||||
|
||||
export async function waitForDependencies() {
|
||||
await runPromise((svc) => svc.waitForDependencies())
|
||||
|
|
|
|||
|
|
@ -53,57 +53,63 @@ import { BackgroundJob } from "@/background/job"
|
|||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
|
||||
export const AppLayer = Layer.mergeAll(
|
||||
Npm.defaultLayer,
|
||||
FSUtil.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
Auth.defaultLayer,
|
||||
Account.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
Storage.defaultLayer,
|
||||
Snapshot.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
ModelsDev.defaultLayer,
|
||||
Provider.defaultLayer,
|
||||
ProviderAuth.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Skill.defaultLayer,
|
||||
Discovery.defaultLayer,
|
||||
Question.defaultLayer,
|
||||
Permission.defaultLayer,
|
||||
Todo.defaultLayer,
|
||||
Session.defaultLayer,
|
||||
SessionStatus.defaultLayer,
|
||||
BackgroundJob.defaultLayer,
|
||||
RuntimeFlags.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
SessionRunState.defaultLayer,
|
||||
SessionProcessor.defaultLayer,
|
||||
SessionCompaction.defaultLayer,
|
||||
SessionRevert.defaultLayer,
|
||||
SessionSummary.defaultLayer,
|
||||
SessionPrompt.defaultLayer,
|
||||
Instruction.defaultLayer,
|
||||
LLM.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
MCP.defaultLayer,
|
||||
McpAuth.defaultLayer,
|
||||
Command.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
ToolRegistry.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
Project.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
Workspace.defaultLayer,
|
||||
Worktree.appLayer,
|
||||
Installation.defaultLayer,
|
||||
ShareNext.defaultLayer,
|
||||
SessionShare.defaultLayer,
|
||||
export const AppLayer = AppNodeBuilder.build(
|
||||
LayerNode.group([
|
||||
Npm.node,
|
||||
FSUtil.node,
|
||||
Database.node,
|
||||
Auth.node,
|
||||
Account.node,
|
||||
Config.node,
|
||||
Git.node,
|
||||
Storage.node,
|
||||
Snapshot.node,
|
||||
Plugin.node,
|
||||
ModelsDev.node,
|
||||
Provider.node,
|
||||
ProviderAuth.node,
|
||||
Agent.node,
|
||||
Skill.node,
|
||||
Discovery.node,
|
||||
Question.node,
|
||||
Permission.node,
|
||||
Todo.node,
|
||||
Session.node,
|
||||
SessionProjector.node,
|
||||
SessionStatus.node,
|
||||
BackgroundJob.node,
|
||||
RuntimeFlags.node,
|
||||
EventV2Bridge.node,
|
||||
SessionRunState.node,
|
||||
SessionProcessor.node,
|
||||
SessionCompaction.node,
|
||||
SessionRevert.node,
|
||||
SessionSummary.node,
|
||||
SessionPrompt.node,
|
||||
Instruction.node,
|
||||
LLM.node,
|
||||
LSP.node,
|
||||
MCP.node,
|
||||
McpAuth.node,
|
||||
Command.node,
|
||||
Truncate.node,
|
||||
ToolRegistry.node,
|
||||
Format.node,
|
||||
InstanceStore.node,
|
||||
Project.node,
|
||||
Vcs.node,
|
||||
Workspace.node,
|
||||
Worktree.node,
|
||||
Installation.node,
|
||||
ShareNext.node,
|
||||
SessionShare.node,
|
||||
]),
|
||||
[[InstanceStore.bootstrapNode, InstanceBootstrap.node]],
|
||||
).pipe(
|
||||
Layer.provideMerge(Ripgrep.defaultLayer),
|
||||
Layer.provideMerge(LayerNode.compile(InstanceStore.node, [[InstanceStore.bootstrapNode, InstanceBootstrap.node]])),
|
||||
Layer.provideMerge(AppNodeBuilder.build(Ripgrep.node)),
|
||||
Layer.provideMerge(Observability.layer),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { Layer, ManagedRuntime } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
import { Plugin } from "@/plugin"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
|
|
@ -10,14 +12,8 @@ import { Config } from "@/config/config"
|
|||
import * as Observability from "@opencode-ai/core/observability"
|
||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||
|
||||
export const BootstrapLayer = Layer.mergeAll(
|
||||
Config.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
ShareNext.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
Snapshot.defaultLayer,
|
||||
export const BootstrapLayer = AppNodeBuilder.build(
|
||||
LayerNode.group([Config.node, Plugin.node, ShareNext.node, Format.node, LSP.node, Vcs.node, Snapshot.node]),
|
||||
).pipe(Layer.provide(Observability.layer))
|
||||
|
||||
export const BootstrapRuntime = ManagedRuntime.make(BootstrapLayer, { memoMap })
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { Effect, Layer, Schema, Context, Stream } from "effect"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
|
|
@ -82,7 +83,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/In
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProcess.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProcess.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const http = yield* HttpClient.HttpClient
|
||||
|
|
@ -324,14 +325,12 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FetchHttpClient.layer), Layer.provide(AppProcess.defaultLayer))
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [httpClient, AppProcess.node] })
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
const { runPromise } = makeRuntime(Service, AppNodeBuilder.build(node))
|
||||
|
||||
export const latest = (...args: Parameters<Interface["latest"]>) => runPromise((s) => s.latest(...args))
|
||||
export const method = () => runPromise((s) => s.method())
|
||||
export const upgrade = (...args: Parameters<Interface["upgrade"]>) => runPromise((s) => s.upgrade(...args))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [httpClient, AppProcess.node] })
|
||||
|
||||
export * as Installation from "."
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { TuiConfig } from "@/config/tui"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { errorData, errorMessage } from "@opencode-ai/tui/util/error"
|
||||
import { isRecord } from "@opencode-ai/tui/util/record"
|
||||
import { resolveHostAttentionSoundPaths } from "@/config/tui-host-attention"
|
||||
|
|
@ -1082,7 +1083,7 @@ async function load(input: {
|
|||
const flags = await Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
return yield* RuntimeFlags.Service
|
||||
}).pipe(Effect.provide(RuntimeFlags.defaultLayer)),
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(RuntimeFlags.node))),
|
||||
)
|
||||
const pluginOrigins = config.plugin_origins ?? (await TuiConfig.pluginOrigins())
|
||||
const records = Flag.OPENCODE_PURE ? [] : pluginOrigins
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Context, Effect, Layer, Option } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
|
||||
export const SERVER_CLOSING_EVENT = () => new Socket.CloseEvent(1001, "server closing")
|
||||
|
|
@ -13,7 +14,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/HttpApiWebSocketTracker") {}
|
||||
|
||||
export const layer = Layer.sync(Service)(() => {
|
||||
const layer = Layer.sync(Service)(() => {
|
||||
const sockets = new Set<Close>()
|
||||
let closing = false
|
||||
return Service.of({
|
||||
|
|
@ -44,6 +45,8 @@ export const layer = Layer.sync(Service)(() => {
|
|||
})
|
||||
})
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [] })
|
||||
|
||||
export const register = (close: Close) =>
|
||||
Effect.gen(function* () {
|
||||
const tracker = yield* Effect.serviceOption(Service)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import "./init-projectors"
|
||||
|
||||
import { NodeHttpServer } from "@effect/platform-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
|
||||
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
import { OpenApi } from "effect/unstable/httpapi"
|
||||
|
|
@ -102,7 +103,7 @@ function listenerLayer(opts: ListenOptions, port: number) {
|
|||
disableLogger: true,
|
||||
disableListenLog: true,
|
||||
}).pipe(
|
||||
Layer.provideMerge(WebSocketTracker.layer),
|
||||
Layer.provideMerge(AppNodeBuilder.build(WebSocketTracker.node)),
|
||||
Layer.provideMerge(serverLayer({ port, hostname: opts.hostname })),
|
||||
// Install a fresh `ConfigProvider` per listener so `Config.string(...)`
|
||||
// reads reflect the current `process.env`. Effect's default
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { expect } from "bun:test"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
|
|
@ -70,12 +71,12 @@ const withPlatform = <A, E, R>(platform: typeof process.platform, self: Effect.E
|
|||
|
||||
const getTuiConfig = (directory: string) =>
|
||||
TuiConfig.Service.use((svc) => svc.get()).pipe(
|
||||
Effect.provide(TuiConfig.defaultLayer.pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory)))),
|
||||
Effect.provide(AppNodeBuilder.build(TuiConfig.node).pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory)))),
|
||||
)
|
||||
|
||||
const getTuiPluginOrigins = (directory: string) =>
|
||||
TuiConfig.Service.use((svc) => svc.pluginOrigins()).pipe(
|
||||
Effect.provide(TuiConfig.defaultLayer.pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory)))),
|
||||
Effect.provide(AppNodeBuilder.build(TuiConfig.node).pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory)))),
|
||||
)
|
||||
|
||||
it.instance("keeps server and tui plugin merge semantics aligned", () =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue