diff --git a/packages/opencode/src/acp/directory.ts b/packages/opencode/src/acp/directory.ts index f61023c103..3a627fe30f 100644 --- a/packages/opencode/src/acp/directory.ts +++ b/packages/opencode/src/acp/directory.ts @@ -2,6 +2,7 @@ import { Agent } from "@/agent/agent" import { Command } from "@/command" import { InstanceRef } from "@/effect/instance-ref" import { InstanceStore } from "@/project/instance-store" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { Provider } from "@/provider/provider" @@ -207,4 +208,8 @@ export const defaultLayer = layer.pipe( Layer.provide(InstanceStore.defaultLayer), ) +export const loaderNode = LayerNode.make({ service: Loader, layer: loaderLayer, deps: [Provider.node, Agent.node, Command.node, InstanceStore.node] }) + +export const node = LayerNode.make({ service: Service, layer, deps: [loaderNode] }) + export * as Directory from "./directory" diff --git a/packages/opencode/src/acp/usage.ts b/packages/opencode/src/acp/usage.ts index be64114bb8..f717d142cf 100644 --- a/packages/opencode/src/acp/usage.ts +++ b/packages/opencode/src/acp/usage.ts @@ -2,6 +2,8 @@ import type { AgentSideConnection, Usage } from "@agentclientprotocol/sdk" import type { AssistantMessage as OpenCodeAssistantMessage, Message } from "@opencode-ai/sdk/v2" import { InstanceRef } from "@/effect/instance-ref" import { InstanceStore } from "@/project/instance-store" +import { makeGlobalNode, Node } from "@opencode-ai/core/effect/app-node" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { Provider } from "@/provider/provider" @@ -229,4 +231,14 @@ export const defaultLayer = layer.pipe( Layer.provide(InstanceStore.defaultLayer), ) +export const messageLoaderNode = LayerNode.unbound(MessageLoader, Node.tags.values.global) + +export const contextLimitLoaderNode = makeGlobalNode({ + service: ContextLimitLoader, + layer: contextLimitLoaderLayer, + deps: [Provider.node, InstanceStore.node], +}) + +export const node = makeGlobalNode({ service: Service, layer, deps: [messageLoaderNode, contextLimitLoaderNode] }) + export * as UsageService from "./usage" diff --git a/packages/opencode/test/account/service.test.ts b/packages/opencode/test/account/service.test.ts index 04d425e2c4..0ebe69c239 100644 --- a/packages/opencode/test/account/service.test.ts +++ b/packages/opencode/test/account/service.test.ts @@ -1,4 +1,6 @@ import { expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { httpClient } from "@opencode-ai/core/effect/app-node-platform" import { Duration, Effect, Layer, Option, Schema } from "effect" import { sql } from "drizzle-orm" import { HttpClient, HttpClientError, HttpClientResponse } from "effect/unstable/http" @@ -25,15 +27,16 @@ const truncate = Layer.effectDiscard( yield* db.run(sql`DELETE FROM account_state`) yield* db.run(sql`DELETE FROM account`) }), -).pipe(Layer.provide(Database.defaultLayer)) +) +const truncateNode = LayerNode.make({ name: "truncate-account", layer: truncate, deps: [Database.node] }) -const it = testEffect(Layer.merge(AccountRepo.defaultLayer, truncate)) +const it = testEffect(LayerNode.compile(LayerNode.group([AccountRepo.node, truncateNode]))) const insideEagerRefreshWindow = Duration.toMillis(Duration.minutes(1)) const outsideEagerRefreshWindow = Duration.toMillis(Duration.minutes(10)) const live = (client: HttpClient.HttpClient) => - Account.layer.pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client))) + LayerNode.compile(Account.node, [[httpClient, Layer.succeed(HttpClient.HttpClient, client)]]) const json = (req: Parameters[0], body: unknown, status = 200) => HttpClientResponse.fromWeb( diff --git a/packages/opencode/test/acp/directory.test.ts b/packages/opencode/test/acp/directory.test.ts index e274db85cb..75f2d50854 100644 --- a/packages/opencode/test/acp/directory.test.ts +++ b/packages/opencode/test/acp/directory.test.ts @@ -1,6 +1,7 @@ import { describe, expect } from "bun:test" import { Directory } from "@/acp/directory" import { Command } from "@/command" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { Provider } from "@/provider/provider" @@ -83,8 +84,9 @@ const snapshot = (directory: string) => { } const fakeLayer = (calls: string[]) => - Directory.layer.pipe( - Layer.provide( + LayerNode.compile(Directory.node, [ + [ + Directory.loaderNode, Layer.succeed( Directory.Loader, Directory.Loader.of({ @@ -95,8 +97,8 @@ const fakeLayer = (calls: string[]) => }), }), ), - ), - ) + ], + ]) describe("ACP directory snapshot", () => { it.effect("two concurrent callers share one load", () => { diff --git a/packages/opencode/test/acp/usage.test.ts b/packages/opencode/test/acp/usage.test.ts index d2ff139c56..06ccfb1f50 100644 --- a/packages/opencode/test/acp/usage.test.ts +++ b/packages/opencode/test/acp/usage.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "bun:test" import type { SessionNotification } from "@agentclientprotocol/sdk" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { UsageService } from "@/acp/usage" @@ -97,24 +98,26 @@ const fakeLayer = (input: { readonly messages?: Effect.Effect readonly providers?: (directory: string) => Effect.Effect, unknown> }) => - UsageService.layer.pipe( - Layer.provide( - Layer.mergeAll( - Layer.succeed( - UsageService.MessageLoader, - UsageService.MessageLoader.of({ - messages: () => input.messages ?? Effect.succeed([]), - }), - ), - Layer.succeed( - UsageService.ContextLimitLoader, - UsageService.ContextLimitLoader.of({ - providers: input.providers ?? (() => Effect.succeed(providers())), - }), - ), + LayerNode.compile(UsageService.node, [ + [ + UsageService.messageLoaderNode, + Layer.succeed( + UsageService.MessageLoader, + UsageService.MessageLoader.of({ + messages: () => input.messages ?? Effect.succeed([]), + }), ), - ), - ) + ], + [ + UsageService.contextLimitLoaderNode, + Layer.succeed( + UsageService.ContextLimitLoader, + UsageService.ContextLimitLoader.of({ + providers: input.providers ?? (() => Effect.succeed(providers())), + }), + ), + ], + ]) const connection = (updates: SessionNotification[]) => ({ sessionUpdate(params: SessionNotification) { diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index b1162bb7e4..eb0b1aab4a 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -1,4 +1,5 @@ import { afterEach, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { disposeAllInstances, TestInstance } from "../fixture/fixture" @@ -14,18 +15,11 @@ import { Plugin } from "../../src/plugin" import { Provider } from "../../src/provider/provider" import { Skill } from "../../src/skill" import { Truncate } from "../../src/tool/truncate" -import { LocationServiceMap, locationServiceMapLayer } from "@opencode-ai/core/location-services" const agentLayer = (flags: Partial = {}) => - Agent.layer.pipe( - Layer.provide(Plugin.defaultLayer), - Layer.provide(Provider.defaultLayer), - Layer.provide(Auth.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(Skill.defaultLayer), - Layer.provide(locationServiceMapLayer), - Layer.provide(RuntimeFlags.layer(flags)), - ) + LayerNode.compile(LayerNode.group([Agent.node, Plugin.node, Provider.node, Auth.node, Config.node, Skill.node, RuntimeFlags.node]), [ + [RuntimeFlags.node, RuntimeFlags.layer(flags)], + ]) const it = testEffect(agentLayer()) diff --git a/packages/opencode/test/cli/run/variant.shared.test.ts b/packages/opencode/test/cli/run/variant.shared.test.ts index ee9bb07325..3de324b5e4 100644 --- a/packages/opencode/test/cli/run/variant.shared.test.ts +++ b/packages/opencode/test/cli/run/variant.shared.test.ts @@ -1,5 +1,6 @@ import path from "path" import { NodeFileSystem } from "@effect/platform-node" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" import { describe, expect, test } from "bun:test" import { Effect, FileSystem, Layer } from "effect" @@ -98,7 +99,7 @@ function userMessage( } } -const it = testEffect(Layer.mergeAll(FSUtil.defaultLayer, NodeFileSystem.layer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(FSUtil.node), NodeFileSystem.layer)) function remap(root: string, file: string) { if (file === Global.Path.state) { @@ -123,7 +124,7 @@ function remappedFs(root: string) { writeJson: (file, data, mode) => fs.writeJson(remap(root, file), data, mode), }) }), - ).pipe(Layer.provide(FSUtil.defaultLayer)) + ).pipe(Layer.provide(LayerNode.compile(FSUtil.node))) } describe("run variant shared", () => { diff --git a/packages/opencode/test/config/tui.test.ts b/packages/opencode/test/config/tui.test.ts index 0c96267a2d..38413533c4 100644 --- a/packages/opencode/test/config/tui.test.ts +++ b/packages/opencode/test/config/tui.test.ts @@ -1,6 +1,7 @@ import { expect } from "bun:test" import path from "path" import { pathToFileURL } from "url" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { FSUtil } from "@opencode-ai/core/fs-util" import { Global } from "@opencode-ai/core/global" @@ -11,7 +12,7 @@ import { TuiConfig } from "../../src/config/tui" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Config.defaultLayer, FSUtil.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Config.node, FSUtil.node]))) const winIt = process.platform === "win32" ? it.instance : it.instance.skip const globalConfigFiles = ["opencode.json", "opencode.jsonc", "tui.json", "tui.jsonc"].map((file) => diff --git a/packages/opencode/test/effect/instance-state.test.ts b/packages/opencode/test/effect/instance-state.test.ts index d983de89a8..148eca8d5c 100644 --- a/packages/opencode/test/effect/instance-state.test.ts +++ b/packages/opencode/test/effect/instance-state.test.ts @@ -1,5 +1,6 @@ import { expect } from "bun:test" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { $ } from "bun" import { Context, Deferred, Duration, Effect, Exit, Fiber, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" @@ -12,7 +13,7 @@ import { } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, testInstanceStoreLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(CrossSpawnSpawner.node), testInstanceStoreLayer)) const access = (state: InstanceState.InstanceState, dir: string) => InstanceState.get(state).pipe(provideInstanceEffect(dir)) diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index e0388a7fd3..53ea17cdfa 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -1,5 +1,6 @@ import { NodeFileSystem } from "@effect/platform-node" import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { provideTmpdirInstance, testInstanceStoreLayer, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -7,7 +8,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Format } from "../../src/format" import * as Formatter from "../../src/format/formatter" -const it = testEffect(Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([Format.node, CrossSpawnSpawner.node])), NodeFileSystem.layer)) describe("Format", () => { it.instance("status() returns empty list when no formatters are configured", () => @@ -106,7 +107,7 @@ describe("Format", () => { ) testEffect( - Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, testInstanceStoreLayer), + Layer.mergeAll(LayerNode.compile(LayerNode.group([Format.node, CrossSpawnSpawner.node])), NodeFileSystem.layer, testInstanceStoreLayer), ).live("status() initializes formatter state per directory", () => Effect.gen(function* () { const a = yield* provideTmpdirInstance(() => Format.use.status(), { diff --git a/packages/opencode/test/installation/installation.test.ts b/packages/opencode/test/installation/installation.test.ts index aaf2a9ea02..ac1bcf92e8 100644 --- a/packages/opencode/test/installation/installation.test.ts +++ b/packages/opencode/test/installation/installation.test.ts @@ -1,4 +1,7 @@ import { describe, expect } from "bun:test" +import { makeGlobalNode } from "@opencode-ai/core/effect/app-node" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { httpClient } from "@opencode-ai/core/effect/app-node-platform" import { Effect, Layer, Stream } from "effect" import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" @@ -52,8 +55,12 @@ function testLayer( httpHandler: (request: HttpClientRequest.HttpClientRequest) => Response, spawnHandler?: (cmd: string, args: readonly string[]) => string | { code: number; stdout?: string; stderr?: string }, ) { - const appProcess = AppProcess.layer.pipe(Layer.provide(mockSpawner(spawnHandler))) - return Installation.layer.pipe(Layer.provide(mockHttpClient(httpHandler)), Layer.provide(appProcess)) + const spawnerNode = makeGlobalNode({ service: ChildProcessSpawner.ChildProcessSpawner, 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], + ]) } describe("installation", () => { diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index 86f3a5dadf..57a7b59d1c 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -1,5 +1,6 @@ import { describe, expect, spyOn } from "bun:test" import path from "path" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Deferred, Effect, Layer } from "effect" import { EventV2Bridge } from "@/event-v2-bridge" import { Config } from "@/config/config" @@ -11,19 +12,17 @@ import { TestInstance } from "../fixture/fixture" import { awaitWithTimeout, testEffect } from "../lib/effect" const lspLayer = (flags: Parameters[0] = {}) => - LSP.layer.pipe( - Layer.provide(Config.defaultLayer), - Layer.provide(RuntimeFlags.layer(flags)), - Layer.provideMerge(EventV2Bridge.defaultLayer), - ) + LayerNode.compile(LayerNode.group([LSP.node, Config.node, RuntimeFlags.node, EventV2Bridge.node]), [ + [RuntimeFlags.node, RuntimeFlags.layer(flags)], + ]) -const it = testEffect(Layer.mergeAll(lspLayer(), CrossSpawnSpawner.defaultLayer)) +const it = testEffect(Layer.mergeAll(lspLayer(), LayerNode.compile(CrossSpawnSpawner.node))) const experimentalTyIt = testEffect( - Layer.mergeAll(lspLayer({ experimentalLspTy: true }), CrossSpawnSpawner.defaultLayer), + Layer.mergeAll(lspLayer({ experimentalLspTy: true }), LayerNode.compile(CrossSpawnSpawner.node)), ) const fakeServerPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js") const disabledDownloadIt = testEffect( - Layer.mergeAll(lspLayer({ disableLspDownload: true }), CrossSpawnSpawner.defaultLayer), + Layer.mergeAll(lspLayer({ disableLspDownload: true }), LayerNode.compile(CrossSpawnSpawner.node)), ) describe("lsp.spawn", () => { diff --git a/packages/opencode/test/mcp/auth.test.ts b/packages/opencode/test/mcp/auth.test.ts index 0fdfe78b2a..7ad8c4826c 100644 --- a/packages/opencode/test/mcp/auth.test.ts +++ b/packages/opencode/test/mcp/auth.test.ts @@ -1,5 +1,6 @@ import { expect, test } from "bun:test" import { setTimeout as sleep } from "node:timers/promises" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { FSUtil } from "@opencode-ai/core/fs-util" import { EffectFlock } from "@opencode-ai/core/util/effect-flock" @@ -41,14 +42,14 @@ function authFile() { : fs.writeJson(file, value, mode), }) }), - ).pipe(Layer.provide(FSUtil.defaultLayer)) + ).pipe(Layer.provide(LayerNode.compile(FSUtil.node))) return { layer, raw: () => raw } } function authService(layer: Layer.Layer) { return McpAuth.Service.use((auth) => Effect.succeed(auth)).pipe( - Effect.provide(McpAuth.layer.pipe(Layer.provide(EffectFlock.defaultLayer), Layer.provide(layer))), + Effect.provide(LayerNode.compile(McpAuth.node, [[FSUtil.node, layer]])), ) } diff --git a/packages/opencode/test/mcp/oauth-auto-connect.test.ts b/packages/opencode/test/mcp/oauth-auto-connect.test.ts index 866bf82901..557e0a1ee7 100644 --- a/packages/opencode/test/mcp/oauth-auto-connect.test.ts +++ b/packages/opencode/test/mcp/oauth-auto-connect.test.ts @@ -1,4 +1,5 @@ import { expect, mock, beforeEach } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { testEffect } from "../lib/effect" @@ -155,16 +156,7 @@ const { FSUtil } = await import("@opencode-ai/core/fs-util") const { CrossSpawnSpawner } = await import("@opencode-ai/core/cross-spawn-spawner") const mcpTest = testEffect( - Layer.mergeAll( - MCP.layer.pipe( - Layer.provide(McpAuth.defaultLayer), - Layer.provideMerge(EventV2Bridge.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(CrossSpawnSpawner.defaultLayer), - Layer.provide(FSUtil.defaultLayer), - ), - McpAuth.defaultLayer, - ), + LayerNode.compile(LayerNode.group([MCP.node, McpAuth.node, EventV2Bridge.node, Config.node, CrossSpawnSpawner.node, FSUtil.node])), ) const config = (name: string) => ({ diff --git a/packages/opencode/test/mcp/oauth-browser.test.ts b/packages/opencode/test/mcp/oauth-browser.test.ts index 5797aef8d9..7156167460 100644 --- a/packages/opencode/test/mcp/oauth-browser.test.ts +++ b/packages/opencode/test/mcp/oauth-browser.test.ts @@ -1,5 +1,6 @@ import { expect, mock, beforeEach } from "bun:test" import { EventEmitter } from "events" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Deferred, Effect, Layer, Option } from "effect" import { awaitWithTimeout, testEffect } from "../lib/effect" import type { MCP as MCPNS } from "../../src/mcp/index" @@ -122,13 +123,7 @@ const { McpOAuthCallback } = await import("../../src/mcp/oauth-callback") const { FSUtil } = await import("@opencode-ai/core/fs-util") const { CrossSpawnSpawner } = await import("@opencode-ai/core/cross-spawn-spawner") const mcpTest = testEffect( - MCP.layer.pipe( - Layer.provide(McpAuth.defaultLayer), - Layer.provideMerge(EventV2Bridge.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(CrossSpawnSpawner.defaultLayer), - Layer.provide(FSUtil.defaultLayer), - ), + LayerNode.compile(LayerNode.group([MCP.node, McpAuth.node, EventV2Bridge.node, Config.node, CrossSpawnSpawner.node, FSUtil.node])), ) const service = MCP.Service as unknown as Effect.Effect diff --git a/packages/opencode/test/plugin/auth-override.test.ts b/packages/opencode/test/plugin/auth-override.test.ts index e68c1dd7aa..dd659728eb 100644 --- a/packages/opencode/test/plugin/auth-override.test.ts +++ b/packages/opencode/test/plugin/auth-override.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" import path from "path" import { pathToFileURL } from "url" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { FSUtil } from "@opencode-ai/core/fs-util" import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" @@ -15,7 +16,7 @@ import { testEffect } from "../lib/effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ProviderV2 } from "@opencode-ai/core/provider" -const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, FSUtil.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([CrossSpawnSpawner.node, FSUtil.node]))) function layer(directory: string, plugins: string[]) { return ProviderAuth.layer.pipe( diff --git a/packages/opencode/test/plugin/loader-shared.test.ts b/packages/opencode/test/plugin/loader-shared.test.ts index 017bc84d85..285febbda5 100644 --- a/packages/opencode/test/plugin/loader-shared.test.ts +++ b/packages/opencode/test/plugin/loader-shared.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, spyOn } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import fs from "fs/promises" import path from "path" @@ -20,7 +21,7 @@ afterEach(async () => { await disposeAllInstances() }) -const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, FSUtil.defaultLayer, testInstanceStoreLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([CrossSpawnSpawner.node, FSUtil.node])), testInstanceStoreLayer)) function withTmp( init: (dir: string) => Promise, diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index 2d2e8cb8a0..da84935433 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -1,6 +1,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 { Effect, Layer } from "effect" import { ModelsDev } from "@opencode-ai/core/models-dev" import { FSUtil } from "@opencode-ai/core/fs-util" @@ -57,14 +58,9 @@ afterEach(async () => { }) const providerLayer = (flags: Partial = {}) => - Provider.layer.pipe( - Layer.provide(FSUtil.defaultLayer), - Layer.provide(Env.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(Auth.defaultLayer), - Layer.provide(Plugin.defaultLayer), - Layer.provide(ModelsDev.defaultLayer), - Layer.provide(RuntimeFlags.layer(flags)), + LayerNode.compile( + LayerNode.group([Provider.node, FSUtil.node, Env.node, Config.node, Auth.node, Plugin.node, ModelsDev.node, RuntimeFlags.node]), + [[RuntimeFlags.node, RuntimeFlags.layer(flags)]], ) const list = Provider.use.list() @@ -77,7 +73,7 @@ const paid = (providers: Record (language as { config: { baseURL: string } }).config.baseURL -const it = testEffect(Layer.mergeAll(Provider.defaultLayer, Env.defaultLayer, Plugin.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Provider.node, Env.node, Plugin.node]))) const experimentalModels = testEffect(providerLayer({ enableExperimentalModels: true })) const alphaProviderConfig = { diff --git a/packages/opencode/test/server/global-session-list.test.ts b/packages/opencode/test/server/global-session-list.test.ts index 21faa82fe3..7a13553af6 100644 --- a/packages/opencode/test/server/global-session-list.test.ts +++ b/packages/opencode/test/server/global-session-list.test.ts @@ -1,4 +1,6 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { SessionProjector } from "@opencode-ai/core/session/projector" import { Deferred, Effect, Layer } from "effect" import { Project } from "@/project/project" import { Session as SessionNs } from "@/session/session" @@ -6,7 +8,9 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, Project.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect( + LayerNode.compile(LayerNode.group([SessionNs.node, SessionProjector.node, Project.node, CrossSpawnSpawner.node])), +) const withSession = (input?: Parameters[0]) => Effect.acquireRelease(SessionNs.use.create(input), (created) => diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index 6b87039f04..1717164356 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Deferred, Effect, Fiber, Layer } from "effect" import { HttpClient, HttpClientResponse } from "effect/unstable/http" import { eq } from "drizzle-orm" @@ -15,7 +16,7 @@ import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(Session.defaultLayer, Database.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([Session.node, Database.node])), httpApiLayer)) const testWorktreeMutations = process.platform === "win32" ? it.instance.skip : it.instance function request(path: string, directory: string, init: RequestInit = {}) { diff --git a/packages/opencode/test/server/httpapi-provider.test.ts b/packages/opencode/test/server/httpapi-provider.test.ts index f96abc2395..625ca1896b 100644 --- a/packages/opencode/test/server/httpapi-provider.test.ts +++ b/packages/opencode/test/server/httpapi-provider.test.ts @@ -1,4 +1,5 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" import { Effect, Layer } from "effect" import path from "path" @@ -15,7 +16,7 @@ const testStateLayer = Layer.effectDiscard( ), ) -const it = testEffect(Layer.mergeAll(testStateLayer, FSUtil.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(testStateLayer, LayerNode.compile(FSUtil.node), httpApiLayer)) const projectOptions = { config: { formatter: false, lsp: false } } const providerID = "test-oauth-parity" const oauthURL = "https://example.com/oauth" diff --git a/packages/opencode/test/server/httpapi-schema-error-body.test.ts b/packages/opencode/test/server/httpapi-schema-error-body.test.ts index c650b3772a..4ea6fd38e8 100644 --- a/packages/opencode/test/server/httpapi-schema-error-body.test.ts +++ b/packages/opencode/test/server/httpapi-schema-error-body.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { HttpClientResponse } from "effect/unstable/http" import { eq } from "drizzle-orm" @@ -16,7 +17,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(Session.defaultLayer, Database.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([Session.node, Database.node])), httpApiLayer)) const text = (response: HttpClientResponse.HttpClientResponse) => response.text diff --git a/packages/opencode/test/server/httpapi-sync.test.ts b/packages/opencode/test/server/httpapi-sync.test.ts index 9a6861c1f2..0577e6bf02 100644 --- a/packages/opencode/test/server/httpapi-sync.test.ts +++ b/packages/opencode/test/server/httpapi-sync.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, mock } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Context, Effect, Layer } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" import { SyncPaths } from "../../src/server/routes/instance/httpapi/groups/sync" @@ -11,7 +12,7 @@ import { httpApiLayer, requestInDirectory } from "./httpapi-layer" const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES const context = Context.empty() as Context.Context -const it = testEffect(Layer.mergeAll(Session.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(Session.node), httpApiLayer)) afterEach(async () => { mock.restore() diff --git a/packages/opencode/test/server/negative-tokens-regression.test.ts b/packages/opencode/test/server/negative-tokens-regression.test.ts index b23726965f..453aa189d5 100644 --- a/packages/opencode/test/server/negative-tokens-regression.test.ts +++ b/packages/opencode/test/server/negative-tokens-regression.test.ts @@ -6,6 +6,7 @@ // strict `NonNegativeInt` schema then made every load of the message list // fail to encode, killing Desktop boot for every user with such a row. import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { eq } from "drizzle-orm" @@ -21,7 +22,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(Session.defaultLayer, Database.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([Session.node, Database.node])), httpApiLayer)) function seedNegativeTokenSession() { return Effect.gen(function* () { diff --git a/packages/opencode/test/server/project-copy.test.ts b/packages/opencode/test/server/project-copy.test.ts index 5b2deb87a9..7a69150953 100644 --- a/packages/opencode/test/server/project-copy.test.ts +++ b/packages/opencode/test/server/project-copy.test.ts @@ -2,12 +2,13 @@ import { afterEach, describe, expect } from "bun:test" import { $ } from "bun" import fs from "fs/promises" import path from "path" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { HttpClientResponse } from "effect/unstable/http" import { FSUtil } from "@opencode-ai/core/fs-util" import { Database } from "@opencode-ai/core/database/database" import { Snapshot } from "@/snapshot" -import { InstanceBootstrap } from "@/project/bootstrap-service" +import { InstanceBootstrap } from "@/project/bootstrap" import { InstanceStore } from "@/project/instance-store" import { resetDatabase } from "../fixture/db" import { disposeAllInstances, TestInstance } from "../fixture/fixture" @@ -20,9 +21,9 @@ afterEach(async () => { }) const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) -const testInstanceStore = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)) +const testInstanceStore = LayerNode.compile(InstanceStore.node, [[InstanceBootstrap.node, noopBootstrap]]) const it = testEffect( - Layer.mergeAll(FSUtil.defaultLayer, Database.defaultLayer, Snapshot.defaultLayer, testInstanceStore, httpApiLayer), + Layer.mergeAll(LayerNode.compile(LayerNode.group([FSUtil.node, Database.node, Snapshot.node])), testInstanceStore, httpApiLayer), ) function request(directory: string, url: string, init: RequestInit = {}) { diff --git a/packages/opencode/test/server/project-init-git.test.ts b/packages/opencode/test/server/project-init-git.test.ts index 9deac21af9..e74f93aaaf 100644 --- a/packages/opencode/test/server/project-init-git.test.ts +++ b/packages/opencode/test/server/project-init-git.test.ts @@ -1,10 +1,11 @@ import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" import { Effect, Layer } from "effect" import { HttpClientResponse } from "effect/unstable/http" import path from "path" import { InstanceRef } from "../../src/effect/instance-ref" -import { InstanceBootstrap } from "../../src/project/bootstrap-service" +import { InstanceBootstrap } from "../../src/project/bootstrap" import { InstanceStore } from "../../src/project/instance-store" import { GlobalBus, type GlobalEvent } from "../../src/bus/global" import { Snapshot } from "../../src/snapshot" @@ -19,9 +20,9 @@ afterEach(async () => { }) const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })) -const testInstanceStore = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap)) +const testInstanceStore = LayerNode.compile(InstanceStore.node, [[InstanceBootstrap.node, noopBootstrap]]) -const it = testEffect(Layer.mergeAll(FSUtil.defaultLayer, Snapshot.defaultLayer, testInstanceStore, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([FSUtil.node, Snapshot.node])), testInstanceStore, httpApiLayer)) function request(directory: string, url: string, init: RequestInit = {}) { return requestInDirectory(url, directory, init) diff --git a/packages/opencode/test/server/session-actions.test.ts b/packages/opencode/test/server/session-actions.test.ts index a4ad3793bc..cf27c74cbe 100644 --- a/packages/opencode/test/server/session-actions.test.ts +++ b/packages/opencode/test/server/session-actions.test.ts @@ -1,11 +1,12 @@ import { afterEach, describe, expect, mock } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { Session as SessionNs } from "@/session/session" import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(SessionNs.node), httpApiLayer)) afterEach(async () => { mock.restore() diff --git a/packages/opencode/test/server/session-diff-missing-patch.test.ts b/packages/opencode/test/server/session-diff-missing-patch.test.ts index 5982627b04..d2a4211ff1 100644 --- a/packages/opencode/test/server/session-diff-missing-patch.test.ts +++ b/packages/opencode/test/server/session-diff-missing-patch.test.ts @@ -11,6 +11,7 @@ * asserts that GET /session//diff returns 200 with empty data. */ import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { SessionPaths } from "@/server/routes/instance/httpapi/groups/session" import { Session } from "@/session/session" @@ -24,7 +25,7 @@ import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(Session.defaultLayer, Storage.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([Session.node, Storage.node])), httpApiLayer)) afterEach(async () => { await disposeAllInstances() diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts index 3e66c59d5e..c47913bde5 100644 --- a/packages/opencode/test/server/session-messages.test.ts +++ b/packages/opencode/test/server/session-messages.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect } from "bun:test" import { SessionV1 } from "@opencode-ai/core/v1/session" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { HttpClientResponse } from "effect/unstable/http" import { Session as SessionNs } from "@/session/session" @@ -12,7 +13,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(SessionNs.node), httpApiLayer)) const model = { providerID: ProviderV2.ID.make("test"), diff --git a/packages/opencode/test/server/session-select.test.ts b/packages/opencode/test/server/session-select.test.ts index a9bc84202b..82a8ad9395 100644 --- a/packages/opencode/test/server/session-select.test.ts +++ b/packages/opencode/test/server/session-select.test.ts @@ -1,11 +1,12 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { Session } from "@/session/session" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { httpApiLayer, requestInDirectory } from "./httpapi-layer" -const it = testEffect(Layer.mergeAll(Session.defaultLayer, httpApiLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(Session.node), httpApiLayer)) describe("tui.selectSession endpoint", () => { it.instance( diff --git a/packages/opencode/test/session/system.test.ts b/packages/opencode/test/session/system.test.ts index c3ff3788d6..1484bfd631 100644 --- a/packages/opencode/test/session/system.test.ts +++ b/packages/opencode/test/session/system.test.ts @@ -1,4 +1,5 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import type { Agent } from "../../src/agent/agent" import { NamedError } from "@opencode-ai/core/util/error" @@ -6,7 +7,6 @@ import { Skill } from "../../src/skill" import { Permission } from "../../src/permission" import { SystemPrompt } from "../../src/session/system" import { MCP } from "../../src/mcp" -import { LocationServiceMap, locationServiceMapLayer } from "@opencode-ai/core/location-services" import { testEffect } from "../lib/effect" const skills: Skill.Info[] = [ @@ -43,9 +43,9 @@ const build: Agent.Info = { } const it = testEffect( - SystemPrompt.layer.pipe( - Layer.provide(locationServiceMapLayer), - Layer.provide( + LayerNode.compile(SystemPrompt.node, [ + [ + MCP.node, Layer.mock(MCP.Service, { instructions: () => Effect.succeed([ @@ -61,8 +61,9 @@ const it = testEffect( }, ]), }), - ), - Layer.provide( + ], + [ + Skill.node, Layer.succeed( Skill.Service, Skill.Service.of({ @@ -77,8 +78,8 @@ const it = testEffect( available: () => Effect.succeed(skills), }), ), - ), - ), + ], + ]), ) describe("session.system", () => { diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index a2e5b6b77c..f365b63f0d 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -1,4 +1,5 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { Skill } from "../../src/skill" import { Discovery } from "../../src/skill/discovery" @@ -13,33 +14,19 @@ import { testEffect } from "../lib/effect" import path from "path" import fs from "fs/promises" -const node = CrossSpawnSpawner.defaultLayer +const node = LayerNode.compile(CrossSpawnSpawner.node) -const it = testEffect(Layer.mergeAll(Skill.defaultLayer, node, testInstanceStoreLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(Skill.node), node, testInstanceStoreLayer)) const itWithoutClaudeCodeSkills = testEffect( Layer.mergeAll( - Skill.layer.pipe( - Layer.provide(Discovery.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(EventV2Bridge.defaultLayer), - Layer.provide(FSUtil.defaultLayer), - Layer.provide(Global.layer), - Layer.provide(RuntimeFlags.layer({ disableClaudeCodeSkills: true })), - ), + LayerNode.compile(Skill.node, [[RuntimeFlags.node, RuntimeFlags.layer({ disableClaudeCodeSkills: true })]]), node, testInstanceStoreLayer, ), ) const itWithoutExternalSkills = testEffect( Layer.mergeAll( - Skill.layer.pipe( - Layer.provide(Discovery.defaultLayer), - Layer.provide(Config.defaultLayer), - Layer.provide(EventV2Bridge.defaultLayer), - Layer.provide(FSUtil.defaultLayer), - Layer.provide(Global.layer), - Layer.provide(RuntimeFlags.layer({ disableExternalSkills: true })), - ), + LayerNode.compile(Skill.node, [[RuntimeFlags.node, RuntimeFlags.layer({ disableExternalSkills: true })]]), node, testInstanceStoreLayer, ), diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts index 21ec0872b8..6822b6ee9e 100644 --- a/packages/opencode/test/snapshot/snapshot.test.ts +++ b/packages/opencode/test/snapshot/snapshot.test.ts @@ -1,6 +1,7 @@ import { afterEach, expect } from "bun:test" import { $ } from "bun" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" import fs from "fs/promises" import path from "path" @@ -15,7 +16,7 @@ import { } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Snapshot.defaultLayer, FSUtil.defaultLayer, testInstanceStoreLayer)) +const it = testEffect(Layer.mergeAll(LayerNode.compile(LayerNode.group([Snapshot.node, FSUtil.node])), testInstanceStoreLayer)) // Windows forbids both * and : in directory names. const nonWindowsIt = process.platform === "win32" ? it.live.skip : it.live @@ -74,11 +75,11 @@ const withTrackedSnapshot = ( }) const bootstrapScoped = Effect.fn("SnapshotTest.bootstrapScoped")(function* () { - const dir = yield* tmpdirScoped({ git: true }).pipe(Effect.provide(CrossSpawnSpawner.defaultLayer)) + const dir = yield* tmpdirScoped({ git: true }).pipe(Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))) return { path: dir, extra: yield* initialize(dir) } }) -const scopedGitTmpdir = () => tmpdirScoped({ git: true }).pipe(Effect.provide(CrossSpawnSpawner.defaultLayer)) +const scopedGitTmpdir = () => tmpdirScoped({ git: true }).pipe(Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))) const cleanupWorktree = (repo: string, worktree: string, files: string[] = []) => Effect.promise(async () => { diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index afb2e93755..a3f3aef446 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -1,5 +1,6 @@ import { describe, expect } from "bun:test" import path from "path" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Exit, Layer } from "effect" import { FSUtil } from "@opencode-ai/core/fs-util" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" @@ -11,7 +12,7 @@ import { testEffect } from "../lib/effect" const dir = path.join(Global.Path.data, "storage") -const it = testEffect(Layer.mergeAll(Storage.defaultLayer, FSUtil.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Storage.node, FSUtil.node, CrossSpawnSpawner.node]))) const scope = Effect.fnUntraced(function* () { const root = ["storage_test", crypto.randomUUID()] @@ -50,14 +51,14 @@ function remappedFs(root: string) { fs.glob(pattern, options?.cwd ? { ...options, cwd: remap(root, options.cwd) } : options), }) }), - ).pipe(Layer.provide(FSUtil.defaultLayer)) + ).pipe(Layer.provide(LayerNode.compile(FSUtil.node))) } // Layer.fresh forces a new Storage instance — without it, Effect's in-test layer cache // returns the outer testEffect's Storage (which uses the real FSUtil), not a new // one built on top of remappedFs. const remappedStorage = (root: string) => - Layer.fresh(Storage.layer.pipe(Layer.provide(remappedFs(root)), Layer.provide(Git.defaultLayer))) + Layer.fresh(LayerNode.compile(Storage.node, [[FSUtil.node, remappedFs(root)]])) describe("Storage", () => { it.live("round-trips JSON content", () => diff --git a/packages/opencode/test/tool/apply_patch.test.ts b/packages/opencode/test/tool/apply_patch.test.ts index 01a09add87..d890053ed9 100644 --- a/packages/opencode/test/tool/apply_patch.test.ts +++ b/packages/opencode/test/tool/apply_patch.test.ts @@ -1,6 +1,7 @@ import { describe, expect } from "bun:test" import path from "path" import * as fs from "fs/promises" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Effect, Exit, Layer } from "effect" import { ApplyPatchTool } from "../../src/tool/apply_patch" import { LSP } from "@/lsp/lsp" @@ -14,14 +15,7 @@ import { SessionID, MessageID } from "../../src/session/schema" import { testEffect } from "../lib/effect" const it = testEffect( - Layer.mergeAll( - LSP.defaultLayer, - FSUtil.defaultLayer, - Format.defaultLayer, - EventV2Bridge.defaultLayer, - Truncate.defaultLayer, - Agent.defaultLayer, - ), + LayerNode.compile(LayerNode.group([LSP.node, FSUtil.node, Format.node, EventV2Bridge.node, Truncate.node, Agent.node])), ) const baseCtx = { diff --git a/packages/opencode/test/tool/edit.test.ts b/packages/opencode/test/tool/edit.test.ts index 12db535518..0193d39cd5 100644 --- a/packages/opencode/test/tool/edit.test.ts +++ b/packages/opencode/test/tool/edit.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect } from "bun:test" import path from "path" import fs from "fs/promises" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" import { EditTool } from "../../src/tool/edit" import { disposeAllInstances, TestInstance } from "../fixture/fixture" @@ -30,14 +31,7 @@ afterEach(async () => { await disposeAllInstances() }) -const layer = Layer.mergeAll( - LSP.defaultLayer, - FSUtil.defaultLayer, - Format.defaultLayer, - EventV2Bridge.defaultLayer, - Truncate.defaultLayer, - Agent.defaultLayer, -) +const layer = LayerNode.compile(LayerNode.group([LSP.node, FSUtil.node, Format.node, EventV2Bridge.node, Truncate.node, Agent.node])) const it = testEffect(layer) diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 4414624990..5c1785e69c 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -1,6 +1,7 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { describe, expect } from "bun:test" import path from "path" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Effect, Exit, Layer } from "effect" import { GlobTool } from "../../src/tool/glob" import { SessionID, MessageID } from "../../src/session/schema" @@ -20,14 +21,7 @@ import { Permission } from "../../src/permission" import type * as Tool from "../../src/tool/tool" const toolLayer = (flags: Partial = {}) => - Layer.mergeAll( - CrossSpawnSpawner.defaultLayer, - FSUtil.defaultLayer, - Ripgrep.defaultLayer, - Truncate.defaultLayer, - Agent.defaultLayer, - Git.defaultLayer, - ) + LayerNode.compile(LayerNode.group([CrossSpawnSpawner.node, FSUtil.node, Ripgrep.node, Truncate.node, Agent.node, Git.node])) const it = testEffect(toolLayer()) const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p) diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index da314d7bf3..74b876a7de 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -3,6 +3,7 @@ import { describe, expect } from "bun:test" import fs from "fs/promises" import os from "os" import path from "path" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import { GrepTool } from "../../src/tool/grep" import { provideInstance, testInstanceStoreLayer, TestInstance, tmpdirScoped } from "../fixture/fixture" @@ -22,14 +23,7 @@ import { Git } from "@/git" import { Filesystem } from "@/util/filesystem" const toolLayer = (flags: Partial = {}) => - Layer.mergeAll( - CrossSpawnSpawner.defaultLayer, - FSUtil.defaultLayer, - Ripgrep.defaultLayer, - Truncate.defaultLayer, - Agent.defaultLayer, - Git.defaultLayer, - ) + LayerNode.compile(LayerNode.group([CrossSpawnSpawner.node, FSUtil.node, Ripgrep.node, Truncate.node, Agent.node, Git.node])) const it = testEffect(toolLayer()) const rooted = testEffect(Layer.mergeAll(toolLayer(), testInstanceStoreLayer)) diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index ddcf14e9ae..8735fdb7b2 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -1,5 +1,6 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" @@ -56,7 +57,9 @@ const lsp = Layer.succeed( ) const it = testEffect( - Layer.mergeAll(Agent.defaultLayer, FSUtil.defaultLayer, CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, lsp), + LayerNode.compile(LayerNode.group([Agent.node, FSUtil.node, CrossSpawnSpawner.node, Truncate.node, LSP.node]), [ + [LSP.node, lsp], + ]), ) const init = Effect.fn("LspToolTest.init")(function* () { diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 67205f56e3..7eaac9b45d 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -1,5 +1,6 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Effect, Exit, Layer, Stream } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" @@ -44,14 +45,8 @@ const ctx = { } const readLayer = (flags: Partial = {}) => - Layer.mergeAll( - Agent.defaultLayer, - FSUtil.defaultLayer, - CrossSpawnSpawner.defaultLayer, - Instruction.defaultLayer, - LSP.defaultLayer, - Ripgrep.defaultLayer, - Truncate.defaultLayer, + LayerNode.compile( + LayerNode.group([Agent.node, FSUtil.node, CrossSpawnSpawner.node, Instruction.node, LSP.node, Ripgrep.node, Truncate.node]), ) const it = testEffect(Layer.mergeAll(readLayer(), testInstanceStoreLayer)) diff --git a/packages/opencode/test/tool/shell.test.ts b/packages/opencode/test/tool/shell.test.ts index 7b98e721d3..a970f85d46 100644 --- a/packages/opencode/test/tool/shell.test.ts +++ b/packages/opencode/test/tool/shell.test.ts @@ -1,5 +1,6 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Effect, Exit, Layer } from "effect" import type * as Scope from "effect/Scope" import os from "os" @@ -22,13 +23,17 @@ import { RuntimeFlags } from "@/effect/runtime-flags" import { InstanceStore } from "@/project/instance-store" const shellLayer = Layer.mergeAll( - CrossSpawnSpawner.defaultLayer, - FSUtil.defaultLayer, - Plugin.defaultLayer, - Truncate.defaultLayer, - Config.defaultLayer, - Agent.defaultLayer, - RuntimeFlags.defaultLayer, + LayerNode.compile( + LayerNode.group([ + CrossSpawnSpawner.node, + FSUtil.node, + Plugin.node, + Truncate.node, + Config.node, + Agent.node, + RuntimeFlags.node, + ]), + ), testInstanceStoreLayer, ) const it = testEffect(shellLayer) diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index 177dbddcbb..1c25b253cd 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -1,5 +1,6 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Ripgrep } from "@opencode-ai/core/ripgrep" import { Cause, Effect, Exit, Layer } from "effect" import { afterEach, describe, expect } from "bun:test" @@ -26,9 +27,7 @@ afterEach(async () => { await disposeAllInstances() }) -const node = CrossSpawnSpawner.defaultLayer - -const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node).pipe(Layer.provide(Ripgrep.defaultLayer))) +const it = testEffect(LayerNode.compile(LayerNode.group([ToolRegistry.node, CrossSpawnSpawner.node, Ripgrep.node]))) describe("tool.skill", () => { it.instance("execute returns skill content block with files", () => diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 97bb7db065..6238a6a077 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -1,6 +1,8 @@ import { afterEach, describe, expect } from "bun:test" import { SessionV1 } from "@opencode-ai/core/v1/session" import { Database } from "@opencode-ai/core/database/database" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { SessionProjector } from "@opencode-ai/core/session/projector" import { Deferred, Effect, Exit, Fiber, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { BackgroundJob } from "@/background/job" @@ -33,20 +35,25 @@ const ref = { } const layer = (flags: Partial = {}) => - Layer.mergeAll( - Agent.defaultLayer, - BackgroundJob.defaultLayer, - EventV2Bridge.defaultLayer, - Config.defaultLayer, - CrossSpawnSpawner.defaultLayer, - Session.defaultLayer, - SessionRunState.defaultLayer, - SessionStatus.defaultLayer, - Truncate.defaultLayer, - ToolRegistry.defaultLayer, - Database.defaultLayer, - RuntimeFlags.layer(flags), - ).pipe(Layer.provide(Ripgrep.defaultLayer)) + LayerNode.compile( + LayerNode.group([ + Agent.node, + BackgroundJob.node, + EventV2Bridge.node, + Config.node, + CrossSpawnSpawner.node, + Session.node, + SessionProjector.node, + SessionRunState.node, + SessionStatus.node, + Truncate.node, + ToolRegistry.node, + Database.node, + RuntimeFlags.node, + Ripgrep.node, + ]), + [[RuntimeFlags.node, RuntimeFlags.layer(flags)]], + ) const it = testEffect(layer()) const background = testEffect(layer({ experimentalBackgroundSubagents: true })) diff --git a/packages/opencode/test/tool/webfetch.test.ts b/packages/opencode/test/tool/webfetch.test.ts index fdf5210b9c..05599a6784 100644 --- a/packages/opencode/test/tool/webfetch.test.ts +++ b/packages/opencode/test/tool/webfetch.test.ts @@ -1,6 +1,8 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { httpClient } from "@opencode-ai/core/effect/app-node-platform" import { Effect, Layer } from "effect" -import { FetchHttpClient } from "effect/unstable/http" +import { FetchHttpClient, HttpClient } from "effect/unstable/http" import { Agent } from "../../src/agent/agent" import { Truncate } from "@/tool/truncate" import { WebFetchTool } from "../../src/tool/webfetch" @@ -8,7 +10,11 @@ import { SessionID, MessageID } from "../../src/session/schema" import { Tool } from "@/tool/tool" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(FetchHttpClient.layer, Truncate.defaultLayer, Agent.defaultLayer)) +const it = testEffect( + LayerNode.compile(LayerNode.group([httpClient, Truncate.node, Agent.node]), [ + [httpClient, FetchHttpClient.layer as Layer.Layer], + ]), +) const ctx = { sessionID: SessionID.make("ses_test"), diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 63a2a52aa9..664e725cd1 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer } from "effect" import path from "path" import fs from "fs/promises" @@ -31,14 +32,8 @@ afterEach(async () => { }) const it = testEffect( - Layer.mergeAll( - LSP.defaultLayer, - FSUtil.defaultLayer, - EventV2Bridge.defaultLayer, - Format.defaultLayer, - CrossSpawnSpawner.defaultLayer, - Truncate.defaultLayer, - Agent.defaultLayer, + LayerNode.compile( + LayerNode.group([LSP.node, FSUtil.node, EventV2Bridge.node, Format.node, CrossSpawnSpawner.node, Truncate.node, Agent.node]), ), )