From 472d0f376e654fbbf573c84aec70aaf821d78a58 Mon Sep 17 00:00:00 2001 From: James Long Date: Tue, 30 Jun 2026 12:17:12 -0400 Subject: [PATCH] refactor(core): remove infrastructure layer exports (#34624) --- packages/cli/src/commands/handlers/serve.ts | 5 +++-- packages/cli/src/tui.ts | 3 ++- packages/core/src/background-job.ts | 4 +--- packages/core/src/credential.ts | 4 +--- packages/core/src/cross-spawn-spawner.ts | 3 +-- packages/core/src/database/database.ts | 8 +------- packages/core/src/event.ts | 3 +-- packages/core/src/filesystem.ts | 4 ---- packages/core/src/filesystem/watcher.ts | 4 +--- packages/core/src/fs-util.ts | 3 +-- packages/core/src/git.ts | 3 +-- packages/core/src/global.ts | 3 +-- packages/core/src/models-dev.ts | 7 +------ packages/core/src/npm.ts | 8 +------- packages/core/src/permission/saved.ts | 4 +--- packages/core/src/plugin.ts | 1 - packages/core/src/plugin/internal.ts | 2 -- packages/core/src/process.ts | 3 +-- packages/core/src/project.ts | 7 +------ packages/core/src/project/directories.ts | 3 +-- packages/core/src/repository-cache.ts | 9 +-------- packages/core/src/ripgrep.ts | 3 +-- packages/core/src/ripgrep/binary.ts | 8 +------- packages/core/src/skill.ts | 2 -- packages/core/src/skill/discovery.ts | 8 +------- packages/core/src/util/effect-flock.ts | 3 +-- packages/opencode/src/cli/cmd/run/variant.shared.ts | 5 +++-- packages/opencode/src/cli/tui/layer.ts | 3 ++- packages/opencode/src/session/session.ts | 3 ++- packages/opencode/test/fixture/fixture.ts | 3 ++- packages/opencode/test/installation/installation.test.ts | 5 ++--- packages/opencode/test/lib/cli-process.ts | 4 +++- packages/opencode/test/provider/provider.test.ts | 7 ++++--- packages/tui/test/app-lifecycle.test.tsx | 5 +++-- 34 files changed, 46 insertions(+), 104 deletions(-) diff --git a/packages/cli/src/commands/handlers/serve.ts b/packages/cli/src/commands/handlers/serve.ts index d4ecfed974..19b097453d 100644 --- a/packages/cli/src/commands/handlers/serve.ts +++ b/packages/cli/src/commands/handlers/serve.ts @@ -1,5 +1,7 @@ import { NodeHttpServer } from "@effect/platform-node" import { Credential } from "@opencode-ai/core/credential" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { PermissionSaved } from "@opencode-ai/core/permission/saved" import { Context, Layer, Option } from "effect" import * as Effect from "effect/Effect" @@ -38,8 +40,7 @@ function bind(hostname: string, port: number, password: string) { return Layer.build( HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe( Layer.provideMerge(NodeHttpServer.layer(() => createServer(), { port, host: hostname })), - Layer.provide(Credential.defaultLayer), - Layer.provide(PermissionSaved.defaultLayer), + Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node]))), ), ).pipe(Effect.map((context) => Context.get(context, HttpServer.HttpServer).address)) } diff --git a/packages/cli/src/tui.ts b/packages/cli/src/tui.ts index 4722441b2c..5100e1c99a 100644 --- a/packages/cli/src/tui.ts +++ b/packages/cli/src/tui.ts @@ -1,6 +1,7 @@ import { run } from "@opencode-ai/tui" import { TuiConfig } from "@opencode-ai/tui/config" import { Effect } from "effect" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { Global } from "@opencode-ai/core/global" export function runTui(transport: { url: string; headers: RequestInit["headers"] }) { @@ -14,7 +15,7 @@ export function runTui(transport: { url: string; headers: RequestInit["headers"] async start() {}, async dispose() {}, }, - }).pipe(Effect.provide(Global.defaultLayer)) + }).pipe(Effect.provide(AppNodeBuilder.build(Global.node))) } const legacyDefaults: Record = { diff --git a/packages/core/src/background-job.ts b/packages/core/src/background-job.ts index 4287830bf1..cdffd212bc 100644 --- a/packages/core/src/background-job.ts +++ b/packages/core/src/background-job.ts @@ -360,8 +360,6 @@ export const make = Effect.gen(function* () { return Service.of({ list, get, start, extend, wait, waitForPromotion, promote, cancel }) }) -export const layer = Layer.effect(Service, make) - -export const defaultLayer = layer +const layer = Layer.effect(Service, make) export const node = makeGlobalNode({ service: Service, layer, deps: [] }) diff --git a/packages/core/src/credential.ts b/packages/core/src/credential.ts index bd5272ac57..5540739709 100644 --- a/packages/core/src/credential.ts +++ b/packages/core/src/credential.ts @@ -48,7 +48,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/v2/Credential") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const { db } = yield* Database.Service @@ -135,6 +135,4 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer)) - export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] }) diff --git a/packages/core/src/cross-spawn-spawner.ts b/packages/core/src/cross-spawn-spawner.ts index 2bae929948..6ea9022acf 100644 --- a/packages/core/src/cross-spawn-spawner.ts +++ b/packages/core/src/cross-spawn-spawner.ts @@ -497,12 +497,11 @@ export const make = Effect.gen(function* () { return makeSpawner(spawnCommand) }) -export const layer: Layer.Layer = Layer.effect( +const layer: Layer.Layer = Layer.effect( ChildProcessSpawner, make, ) -export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer), Layer.provide(NodePath.layer)) export const node = makeGlobalNode({ service: ChildProcessSpawner, layer, deps: [filesystem, path] }) export * as CrossSpawnSpawner from "./cross-spawn-spawner" diff --git a/packages/core/src/database/database.ts b/packages/core/src/database/database.ts index 2946450e29..d61adf047e 100644 --- a/packages/core/src/database/database.ts +++ b/packages/core/src/database/database.ts @@ -19,7 +19,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/v2/storage/Database") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const db = yield* makeDatabase @@ -54,10 +54,4 @@ export function path() { return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`) } -export const defaultLayer = Layer.unwrap( - Effect.gen(function* () { - return layerFromPath(path()) - }), -).pipe(Layer.provide(Global.defaultLayer)) - export const node = makeGlobalNode({ service: Service, layer: layerFromPath(path()), deps: [] }) diff --git a/packages/core/src/event.ts b/packages/core/src/event.ts index c9195be79f..6e42ec0f29 100644 --- a/packages/core/src/event.ts +++ b/packages/core/src/event.ts @@ -634,7 +634,6 @@ export const layerWith = (options?: LayerOptions) => }), ) -export const layer = layerWith() +const layer = layerWith() export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] }) -export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer)) diff --git a/packages/core/src/filesystem.ts b/packages/core/src/filesystem.ts index f12b52d682..8470134ff7 100644 --- a/packages/core/src/filesystem.ts +++ b/packages/core/src/filesystem.ts @@ -111,10 +111,6 @@ const baseLayer = Layer.effect( }), ) -export const layer = baseLayer.pipe(Layer.provide(FileSystemSearch.locationLayer), Layer.provide(FSUtil.defaultLayer)) - -export const locationLayer = layer - export const node = makeLocationNode({ service: Service, layer: baseLayer, diff --git a/packages/core/src/filesystem/watcher.ts b/packages/core/src/filesystem/watcher.ts index 6d4bb1c326..b0f568a566 100644 --- a/packages/core/src/filesystem/watcher.ts +++ b/packages/core/src/filesystem/watcher.ts @@ -54,7 +54,7 @@ export interface Interface {} export class Service extends Context.Service()("@opencode/v2/FileWatcher") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { if (yield* Flag.OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER) return Service.of({}) @@ -133,8 +133,6 @@ export const layer = Layer.effect( ), ) -export const locationLayer = layer.pipe(Layer.provide(Config.locationLayer), Layer.provide(Git.defaultLayer)) - export const node = makeLocationNode({ service: Service, layer, diff --git a/packages/core/src/fs-util.ts b/packages/core/src/fs-util.ts index 7f7d703dda..ff71477d70 100644 --- a/packages/core/src/fs-util.ts +++ b/packages/core/src/fs-util.ts @@ -49,7 +49,7 @@ export namespace FSUtil { export const use = serviceUse(Service) - export const layer = Layer.effect( + const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* FileSystem.FileSystem @@ -200,7 +200,6 @@ export namespace FSUtil { }), ) - export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer)) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [filesystem] }) // Pure helpers that don't need Effect (path manipulation, sync operations) diff --git a/packages/core/src/git.ts b/packages/core/src/git.ts index ddd6d952b6..19dcde8961 100644 --- a/packages/core/src/git.ts +++ b/packages/core/src/git.ts @@ -172,7 +172,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/GitV2") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -943,7 +943,6 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(AppProcess.defaultLayer)) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [FSUtil.node, AppProcess.node] }) interface Result { diff --git a/packages/core/src/global.ts b/packages/core/src/global.ts index 6c11392f64..a192a4b468 100644 --- a/packages/core/src/global.ts +++ b/packages/core/src/global.ts @@ -71,12 +71,11 @@ export function make(input: Partial = {}): Interface { } } -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.sync(() => Service.of(make())), ) -export const defaultLayer = layer export const node = makeGlobalNode({ service: Service, layer: layer, deps: [] }) export const layerWith = (input: Partial) => diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index c41b82b5f4..c2325cb57f 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -120,7 +120,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/ModelsDev") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -239,11 +239,6 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe( - Layer.provide(FetchHttpClient.layer), - Layer.provide(FSUtil.defaultLayer), - Layer.provide(EventV2.defaultLayer), -) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [FSUtil.node, EventV2.node, httpClient] }) export * as ModelsDev from "./models-dev" diff --git a/packages/core/src/npm.ts b/packages/core/src/npm.ts index abda278cae..30e12cff12 100644 --- a/packages/core/src/npm.ts +++ b/packages/core/src/npm.ts @@ -69,7 +69,7 @@ interface ArboristTree { edgesOut: Map } -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const afs = yield* FSUtil.Service @@ -248,12 +248,6 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe( - Layer.provide(EffectFlock.layer), - Layer.provide(FSUtil.layer), - Layer.provide(Global.layer), - Layer.provide(NodeFileSystem.layer), -) export const node = makeGlobalNode({ service: Service, layer: layer, diff --git a/packages/core/src/permission/saved.ts b/packages/core/src/permission/saved.ts index 5a40e11844..ffc4559afe 100644 --- a/packages/core/src/permission/saved.ts +++ b/packages/core/src/permission/saved.ts @@ -34,7 +34,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/v2/PermissionSaved") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const { db } = yield* Database.Service @@ -76,6 +76,4 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer)) - export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] }) diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts index 2776ee20f7..4c0bb8d831 100644 --- a/packages/core/src/plugin.ts +++ b/packages/core/src/plugin.ts @@ -149,7 +149,6 @@ export const locationLayer = layer.pipe( Layer.provideMerge(CommandV2.locationLayer), Layer.provideMerge(Integration.locationLayer), Layer.provideMerge(Reference.locationLayer), - Layer.provideMerge(SkillV2.locationLayer), ) export const node = makeLocationNode({ diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index 72b1c1ff0f..d4ab71cb6b 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -125,9 +125,7 @@ const layer = Layer.effectDiscard( ) export const locationLayer = layer.pipe( - Layer.provideMerge(PluginV2.locationLayer), Layer.provideMerge(Config.locationLayer), - Layer.provideMerge(FileSystem.locationLayer), Layer.provideMerge(FetchHttpClient.layer), ) diff --git a/packages/core/src/process.ts b/packages/core/src/process.ts index 179029f0e5..16e9118d1f 100644 --- a/packages/core/src/process.ts +++ b/packages/core/src/process.ts @@ -136,7 +136,7 @@ export const collectStream = (stream: Stream.Stream, }, ).pipe(Effect.map((x) => ({ buffer: Buffer.concat(x.chunks), truncated: x.truncated }))) -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const spawner = yield* ChildProcessSpawner @@ -256,7 +256,6 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(CrossSpawnSpawner.defaultLayer)) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [CrossSpawnSpawner.node] }) export * as AppProcess from "./process" diff --git a/packages/core/src/project.ts b/packages/core/src/project.ts index 5804ff93ff..49e054d7cc 100644 --- a/packages/core/src/project.ts +++ b/packages/core/src/project.ts @@ -51,7 +51,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/ProjectV2") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -129,11 +129,6 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe( - Layer.provide(FSUtil.defaultLayer), - Layer.provide(Git.defaultLayer), - Layer.provideMerge(ProjectDirectories.defaultLayer), -) export const node = makeGlobalNode({ service: Service, layer: layer, diff --git a/packages/core/src/project/directories.ts b/packages/core/src/project/directories.ts index 6f7e36c35e..6c9ad2e515 100644 --- a/packages/core/src/project/directories.ts +++ b/packages/core/src/project/directories.ts @@ -57,7 +57,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/ProjectDirectories") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const db = (yield* Database.Service).db @@ -155,5 +155,4 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer)) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Database.node] }) diff --git a/packages/core/src/repository-cache.ts b/packages/core/src/repository-cache.ts index 7fb429a156..988236743b 100644 --- a/packages/core/src/repository-cache.ts +++ b/packages/core/src/repository-cache.ts @@ -121,7 +121,7 @@ export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(functi }) }) -export const layer: Layer.Layer = +const layer: Layer.Layer = Layer.effect( Service, Effect.gen(function* () { @@ -223,13 +223,6 @@ export const layer: Layer.Layer = layer.pipe( - Layer.provide(EffectFlock.defaultLayer), - Layer.provide(FSUtil.defaultLayer), - Layer.provide(Git.defaultLayer), - Layer.provide(Global.defaultLayer), -) - export const node = makeGlobalNode({ service: Service, layer, diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index e6cf889572..ac8ea52d93 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -89,7 +89,7 @@ const failure = (message: string, cause?: unknown) => new Error({ message, cause const isInvalidPattern = (stderr: string) => stderr.includes("regex parse error") || stderr.includes("error parsing regex") -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const process = yield* AppProcess.Service @@ -278,5 +278,4 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe(Layer.provide(Layer.merge(RipgrepBinary.defaultLayer, AppProcess.defaultLayer))) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [RipgrepBinary.node, AppProcess.node] }) diff --git a/packages/core/src/ripgrep/binary.ts b/packages/core/src/ripgrep/binary.ts index 3a6c81dbce..762a8e613e 100644 --- a/packages/core/src/ripgrep/binary.ts +++ b/packages/core/src/ripgrep/binary.ts @@ -28,7 +28,7 @@ export namespace RipgrepBinary { export class Service extends Context.Service()("@opencode/RipgrepBinary") {} - export const layer = Layer.effect( + const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -124,12 +124,6 @@ export namespace RipgrepBinary { }), ) - export const defaultLayer = layer.pipe( - Layer.provide(FetchHttpClient.layer), - Layer.provide(FSUtil.defaultLayer), - Layer.provide(CrossSpawnSpawner.defaultLayer), - ) - export const node = makeGlobalNode({ service: Service, layer: layer, diff --git a/packages/core/src/skill.ts b/packages/core/src/skill.ts index cb8ce271c5..86253208cc 100644 --- a/packages/core/src/skill.ts +++ b/packages/core/src/skill.ts @@ -129,6 +129,4 @@ export const layer = Layer.effect( }), ) -export const locationLayer = layer.pipe(Layer.provide(SkillDiscovery.defaultLayer)) - export const node = makeLocationNode({ service: Service, layer, deps: [SkillDiscovery.node, FSUtil.node] }) diff --git a/packages/core/src/skill/discovery.ts b/packages/core/src/skill/discovery.ts index c970636e60..a192ba1023 100644 --- a/packages/core/src/skill/discovery.ts +++ b/packages/core/src/skill/discovery.ts @@ -68,7 +68,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/v2/SkillDiscovery") {} -export const layer = Layer.effect( +const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* FSUtil.Service @@ -210,10 +210,4 @@ export const layer = Layer.effect( }), ) -export const defaultLayer = layer.pipe( - Layer.provide(FetchHttpClient.layer), - Layer.provide(FSUtil.defaultLayer), - Layer.provide(Global.defaultLayer), -) - export const node = makeGlobalNode({ service: Service, layer, deps: [httpClient, FSUtil.node, Global.node] }) diff --git a/packages/core/src/util/effect-flock.ts b/packages/core/src/util/effect-flock.ts index 62bc606f74..b85900118a 100644 --- a/packages/core/src/util/effect-flock.ts +++ b/packages/core/src/util/effect-flock.ts @@ -94,7 +94,7 @@ export namespace EffectFlock { const isPathGone = (e: PlatformError) => e.reason._tag === "NotFound" || e.reason._tag === "Unknown" - export const layer: Layer.Layer = Layer.effect( + const layer: Layer.Layer = Layer.effect( Service, Effect.gen(function* () { const global = yield* Global.Service @@ -280,6 +280,5 @@ export namespace EffectFlock { }), ) - export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(Global.layer)) export const node = makeGlobalNode({ service: Service, layer: layer, deps: [Global.node, FSUtil.node] }) } diff --git a/packages/opencode/src/cli/cmd/run/variant.shared.ts b/packages/opencode/src/cli/cmd/run/variant.shared.ts index 0c4b82fa5c..e685ceb028 100644 --- a/packages/opencode/src/cli/cmd/run/variant.shared.ts +++ b/packages/opencode/src/cli/cmd/run/variant.shared.ts @@ -8,6 +8,7 @@ // variant and the persisted file. import path from "path" import { FSUtil } from "@opencode-ai/core/fs-util" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { Context, Effect, Layer } from "effect" import { makeRuntime } from "@/effect/run-service" import { Global } from "@opencode-ai/core/global" @@ -135,7 +136,7 @@ function state(value: unknown): ModelState { } } -function createLayer(fs = FSUtil.defaultLayer) { +function createLayer(fs = AppNodeBuilder.build(FSUtil.node)) { return Layer.fresh( Layer.effect( Service, @@ -196,7 +197,7 @@ function createLayer(fs = FSUtil.defaultLayer) { } /** @internal Exported for testing. */ -export function createVariantRuntime(fs = FSUtil.defaultLayer): VariantRuntime { +export function createVariantRuntime(fs = AppNodeBuilder.build(FSUtil.node)): VariantRuntime { const runtime = makeRuntime(Service, createLayer(fs)) return { resolveSavedVariant: (model) => runtime.runPromise((svc) => svc.resolveSavedVariant(model)).catch(() => undefined), diff --git a/packages/opencode/src/cli/tui/layer.ts b/packages/opencode/src/cli/tui/layer.ts index cc88498cb9..a592b86dae 100644 --- a/packages/opencode/src/cli/tui/layer.ts +++ b/packages/opencode/src/cli/tui/layer.ts @@ -1,7 +1,8 @@ import { run as runTui, type TuiInput } from "@opencode-ai/tui" import { Global } from "@opencode-ai/core/global" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { Effect } from "effect" export function run(input: TuiInput) { - return runTui(input).pipe(Effect.provide(Global.defaultLayer)) + return runTui(input).pipe(Effect.provide(AppNodeBuilder.build(Global.node))) } diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 0882f17941..ede9b77f0b 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -1,4 +1,5 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { Slug } from "@opencode-ai/core/util/slug" import { SessionV1 } from "@opencode-ai/core/v1/session" @@ -46,7 +47,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { SessionMessage } from "@opencode-ai/schema/session-message" -const runtime = makeRuntime(Database.Service, Database.defaultLayer) +const runtime = makeRuntime(Database.Service, AppNodeBuilder.build(Database.node)) const parentTitlePrefix = "New session - " const childTitlePrefix = "Child session - " diff --git a/packages/opencode/test/fixture/fixture.ts b/packages/opencode/test/fixture/fixture.ts index fc5a253b15..53e3142cdf 100644 --- a/packages/opencode/test/fixture/fixture.ts +++ b/packages/opencode/test/fixture/fixture.ts @@ -7,6 +7,7 @@ import { Effect, Context, Layer } from "effect" import type * as PlatformError from "effect/PlatformError" import type * as Scope from "effect/Scope" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import type { Config } from "@/config/config" import { LayerNode } from "@opencode-ai/core/effect/layer-node" @@ -207,7 +208,7 @@ export const withTmpdirInstance = Effect.gen(function* () { const directory = yield* tmpdirScoped(options) return yield* self.pipe(Effect.provideService(TestInstance, { directory }), provideInstanceEffect(directory)) - }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)) + }).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node))) export function provideTmpdirServer( self: (input: { dir: string; llm: TestLLMServer["Service"] }) => Effect.Effect, diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts index 9db6b0d125..3d4b4cba31 100644 --- a/packages/opencode/test/installation/installation.test.ts +++ b/packages/opencode/test/installation/installation.test.ts @@ -7,7 +7,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstab import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { Installation } from "../../src/installation" import { InstallationChannel } from "@opencode-ai/core/installation/version" -import { AppProcess } from "@opencode-ai/core/process" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" const encoder = new TextEncoder() @@ -60,10 +60,9 @@ function testLayer( layer: mockSpawner(spawnHandler), deps: [], }) - const appProcessNode = makeGlobalNode({ service: AppProcess.Service, layer: AppProcess.layer, deps: [spawnerNode] }) return LayerNode.compile(Installation.node, [ [httpClient, mockHttpClient(httpHandler)], - [AppProcess.node, appProcessNode], + [CrossSpawnSpawner.node, spawnerNode], ]) } diff --git a/packages/opencode/test/lib/cli-process.ts b/packages/opencode/test/lib/cli-process.ts index 6de9033ffe..ca2d4e6433 100644 --- a/packages/opencode/test/lib/cli-process.ts +++ b/packages/opencode/test/lib/cli-process.ts @@ -19,6 +19,8 @@ // different return shape — see the TODO at the bottom of OpencodeCli. import { test, type TestOptions } from "bun:test" import { FSUtil } from "@opencode-ai/core/fs-util" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { AppProcess } from "@opencode-ai/core/process" import { Deferred, Duration, Effect, Layer, Queue, Schedule, Scope, Stream } from "effect" import { FetchHttpClient, HttpClient } from "effect/unstable/http" @@ -469,7 +471,7 @@ export function withCliFixture( // and hit endpoints on `opencode.serve()` without rolling their own fetch. }).pipe( Effect.provide( - Layer.mergeAll(TestLLMServer.layer, FetchHttpClient.layer, FSUtil.defaultLayer, AppProcess.defaultLayer), + Layer.mergeAll(TestLLMServer.layer, FetchHttpClient.layer, AppNodeBuilder.build(LayerNode.group([FSUtil.node, AppProcess.node]))), ), ) } diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 82c9f10c5e..18ec8f9fbe 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -2,6 +2,7 @@ import { afterEach, expect, test } from "bun:test" import { mkdir, unlink } from "fs/promises" import path from "path" import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { Effect, Layer } from "effect" import { ModelsDev } from "@opencode-ai/core/models-dev" import { FSUtil } from "@opencode-ai/core/fs-util" @@ -1765,7 +1766,7 @@ const instanceStoreLayer = LayerNode.compile(InstanceStore.node, [ [InstanceStore.bootstrapNode, InstanceBootstrap.node], ]) const provideMultiInstance = (eff: Effect.Effect) => - eff.pipe(Effect.provide(instanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)) + eff.pipe(Effect.provide(instanceStoreLayer), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node))) it.effect("plugin config providers persist after instance dispose", () => Effect.gen(function* () { @@ -1868,7 +1869,7 @@ it.effect("opencode loader keeps paid models when config apiKey is present", () Provider.use .list() .pipe(provideInstanceEffect(directory)) - .pipe(Effect.provide(instanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)) + .pipe(Effect.provide(instanceStoreLayer), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node))) const none = paid(yield* listIn(noneDir)) const keyedCount = paid(yield* listIn(keyedDir)) @@ -1887,7 +1888,7 @@ it.effect("opencode loader keeps paid models when auth exists", () => Provider.use .list() .pipe(provideInstanceEffect(directory)) - .pipe(Effect.provide(instanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)) + .pipe(Effect.provide(instanceStoreLayer), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node))) const none = paid(yield* listIn(noneDir)) diff --git a/packages/tui/test/app-lifecycle.test.tsx b/packages/tui/test/app-lifecycle.test.tsx index d3983ae8e0..5706634247 100644 --- a/packages/tui/test/app-lifecycle.test.tsx +++ b/packages/tui/test/app-lifecycle.test.tsx @@ -2,6 +2,7 @@ import { expect, mock, test } from "bun:test" import type { TuiPluginApi } from "@opencode-ai/plugin/tui" import { createTestRenderer } from "@opentui/core/testing" import { Effect } from "effect" +import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { Global } from "@opencode-ai/core/global" import { createTuiResolvedConfig } from "./fixture/tui-runtime" import { createEventSource, createFetch, directory, json } from "./fixture/tui-sdk" @@ -43,7 +44,7 @@ test("SIGHUP clears title and disposes scoped resources once", async () => { disposes++ }, }, - }).pipe(Effect.provide(Global.defaultLayer)), + }).pipe(Effect.provide(AppNodeBuilder.build(Global.node))), ) await ready process.emit("SIGHUP") @@ -108,7 +109,7 @@ test("app.exit prints the session epilogue after scoped cleanup", async () => { }, async dispose() {}, }, - }).pipe(Effect.provide(Global.defaultLayer)), + }).pipe(Effect.provide(AppNodeBuilder.build(Global.node))), ) await ready