diff --git a/packages/core/src/observability.ts b/packages/core/src/observability.ts index faffb27333..22285974d8 100644 --- a/packages/core/src/observability.ts +++ b/packages/core/src/observability.ts @@ -1,6 +1,7 @@ export * as Observability from "./observability" import { NodeFileSystem } from "@effect/platform-node" +import { LayerNode } from "./effect/layer-node" import { Effect, Layer, Logger, References } from "effect" import { FetchHttpClient } from "effect/unstable/http" import { OtlpSerialization } from "effect/unstable/observability" @@ -19,3 +20,5 @@ export const layer = Layer.unwrap( return Layer.merge(logs, yield* Effect.promise(Otlp.tracingLayer)) }), ) + +export const node = LayerNode.make({ name: "observability", layer, deps: [] }) diff --git a/packages/opencode/src/acp/session.ts b/packages/opencode/src/acp/session.ts index e0b3af99f8..ea76cd91f8 100644 --- a/packages/opencode/src/acp/session.ts +++ b/packages/opencode/src/acp/session.ts @@ -1,5 +1,6 @@ import type { McpServer } from "@agentclientprotocol/sdk" import type { Message, Part } from "@opencode-ai/sdk/v2" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { Context, Effect, Layer, Ref } from "effect" @@ -202,6 +203,8 @@ export const layer = Layer.effect( export const defaultLayer = layer +export const node = LayerNode.make({ service: Service, layer, deps: [] }) + function makeSession(input: StoreInput): Info { return { id: input.id, diff --git a/packages/opencode/test/account/repo.test.ts b/packages/opencode/test/account/repo.test.ts index 42851fc19d..756d873f9c 100644 --- a/packages/opencode/test/account/repo.test.ts +++ b/packages/opencode/test/account/repo.test.ts @@ -1,4 +1,5 @@ import { expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect, Layer, Option } from "effect" import { sql } from "drizzle-orm" @@ -13,9 +14,10 @@ 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]))) it.live("list returns empty when no accounts exist", () => Effect.gen(function* () { diff --git a/packages/opencode/test/acp/event.test.ts b/packages/opencode/test/acp/event.test.ts index 8a72754f0e..8271b28265 100644 --- a/packages/opencode/test/acp/event.test.ts +++ b/packages/opencode/test/acp/event.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "bun:test" import type { AgentSideConnection } from "@agentclientprotocol/sdk" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import type { Event, Message, OpencodeClient, Part, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2" import { Effect, ManagedRuntime } from "effect" import { ACPEvent } from "@/acp/event" @@ -30,7 +31,7 @@ const pollUntil = async ( } function makeSessionService() { - return ManagedRuntime.make(ACPSession.defaultLayer).runSync( + return ManagedRuntime.make(LayerNode.compile(ACPSession.node)).runSync( ACPSession.Service.use((service) => Effect.succeed(service)), ) } diff --git a/packages/opencode/test/acp/permission.test.ts b/packages/opencode/test/acp/permission.test.ts index eaf3218e9c..99da5e6ac1 100644 --- a/packages/opencode/test/acp/permission.test.ts +++ b/packages/opencode/test/acp/permission.test.ts @@ -6,6 +6,7 @@ import type { SessionUpdate, } from "@agentclientprotocol/sdk" import type { Event, OpencodeClient } from "@opencode-ai/sdk/v2" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { createTwoFilesPatch } from "diff" import { Effect, ManagedRuntime } from "effect" import { mkdtemp, rm } from "node:fs/promises" @@ -37,7 +38,7 @@ const pollUntil = async ( } function makeSessionService() { - return ManagedRuntime.make(ACPSession.defaultLayer).runSync( + return ManagedRuntime.make(LayerNode.compile(ACPSession.node)).runSync( ACPSession.Service.use((service) => Effect.succeed(service)), ) } diff --git a/packages/opencode/test/acp/session.test.ts b/packages/opencode/test/acp/session.test.ts index c3d41ef08e..89968563c9 100644 --- a/packages/opencode/test/acp/session.test.ts +++ b/packages/opencode/test/acp/session.test.ts @@ -1,5 +1,6 @@ import { describe, expect } from "bun:test" import type { McpServer } from "@agentclientprotocol/sdk" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect } from "effect" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" @@ -7,7 +8,7 @@ import * as ACPError from "@/acp/error" import * as ACPSession from "@/acp/session" import { testEffect } from "../lib/effect" -const sessionTest = testEffect(ACPSession.defaultLayer) +const sessionTest = testEffect(LayerNode.compile(ACPSession.node)) const model = (providerID: string, modelID: string): ACPSession.SelectedModel => ({ providerID: ProviderV2.ID.make(providerID), diff --git a/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts b/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts index a58a5ddf2f..6fc9197d9a 100644 --- a/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts +++ b/packages/opencode/test/agent/plan-mode-subagent-bypass.test.ts @@ -1,4 +1,5 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { expect } from "bun:test" import { Effect } from "effect" import { Agent } from "../../src/agent/agent" @@ -6,7 +7,7 @@ import { deriveSubagentSessionPermission } from "../../src/agent/subagent-permis import { Permission } from "../../src/permission" import { testEffect } from "../lib/effect" -const it = testEffect(Agent.defaultLayer) +const it = testEffect(LayerNode.compile(Agent.node)) function testAgent(input: { name: string diff --git a/packages/opencode/test/auth/auth.test.ts b/packages/opencode/test/auth/auth.test.ts index 58ce6ea718..bb72be66e5 100644 --- a/packages/opencode/test/auth/auth.test.ts +++ b/packages/opencode/test/auth/auth.test.ts @@ -1,12 +1,10 @@ import { describe, expect } from "bun:test" -import { Effect, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect } from "effect" import { Auth } from "../../src/auth" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" -const node = CrossSpawnSpawner.defaultLayer - -const it = testEffect(Layer.mergeAll(Auth.defaultLayer, node)) +const it = testEffect(LayerNode.compile(Auth.node)) describe("Auth", () => { it.instance("set normalizes trailing slashes in keys", () => diff --git a/packages/opencode/test/background/job.test.ts b/packages/opencode/test/background/job.test.ts index dbcb484dc6..649f935c08 100644 --- a/packages/opencode/test/background/job.test.ts +++ b/packages/opencode/test/background/job.test.ts @@ -1,9 +1,10 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Deferred, Effect } from "effect" import { BackgroundJob } from "@/background/job" import { testEffect } from "../lib/effect" -const it = testEffect(BackgroundJob.defaultLayer) +const it = testEffect(LayerNode.compile(BackgroundJob.node)) describe("background.job", () => { it.instance("tracks started jobs through completion", () => diff --git a/packages/opencode/test/cli/effect-cmd-instance-als.test.ts b/packages/opencode/test/cli/effect-cmd-instance-als.test.ts index 7c93ab3190..1592b00146 100644 --- a/packages/opencode/test/cli/effect-cmd-instance-als.test.ts +++ b/packages/opencode/test/cli/effect-cmd-instance-als.test.ts @@ -1,4 +1,5 @@ import { afterEach, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" import { Effect } from "effect" import { fileURLToPath } from "url" @@ -6,7 +7,7 @@ import { InstanceRef } from "../../src/effect/instance-ref" import { disposeAllInstances, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(FSUtil.defaultLayer) +const it = testEffect(LayerNode.compile(FSUtil.node)) afterEach(async () => { await disposeAllInstances() diff --git a/packages/opencode/test/config/agent-color.test.ts b/packages/opencode/test/config/agent-color.test.ts index 664170696e..6b27cafe85 100644 --- a/packages/opencode/test/config/agent-color.test.ts +++ b/packages/opencode/test/config/agent-color.test.ts @@ -1,11 +1,11 @@ import { expect } from "bun:test" -import { Effect, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect } from "effect" import { Config } from "@/config/config" import { Agent as AgentSvc } from "../../src/agent/agent" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Config.defaultLayer, AgentSvc.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Config.node, AgentSvc.node]))) it.instance( "agent color parsed from project config", diff --git a/packages/opencode/test/effect/app-runtime-logger.test.ts b/packages/opencode/test/effect/app-runtime-logger.test.ts index ecebe50446..a0c34244f0 100644 --- a/packages/opencode/test/effect/app-runtime-logger.test.ts +++ b/packages/opencode/test/effect/app-runtime-logger.test.ts @@ -1,4 +1,5 @@ import { expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Context, Deferred, Effect, Fiber, Layer, Logger } from "effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppLayer } from "../../src/effect/app-runtime" @@ -9,7 +10,8 @@ import { attach } from "../../src/effect/run-service" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(CrossSpawnSpawner.defaultLayer) +const observabilityLayer = LayerNode.compile(Observability.node) +const it = testEffect(LayerNode.compile(CrossSpawnSpawner.node)) function check(loggers: ReadonlySet>) { return { @@ -34,7 +36,7 @@ it.live("makeRuntime installs the observability logger", () => ) const current = yield* Dummy.use((svc) => svc.current()).pipe( - Effect.provide(Layer.provideMerge(layer, Observability.layer)), + Effect.provide(Layer.provideMerge(layer, observabilityLayer)), ) expect(current.size).toBeGreaterThan(0) @@ -94,6 +96,6 @@ it.instance( expect(result.directory).toBe(test.directory) expect(result.size).toBeGreaterThan(0) - }).pipe(Effect.provide(Observability.layer)), + }).pipe(Effect.provide(observabilityLayer)), { git: true }, ) diff --git a/packages/opencode/test/filesystem/filesystem.test.ts b/packages/opencode/test/filesystem/filesystem.test.ts index 686a21d527..d1ccc1f3ee 100644 --- a/packages/opencode/test/filesystem/filesystem.test.ts +++ b/packages/opencode/test/filesystem/filesystem.test.ts @@ -1,12 +1,11 @@ import { describe, test, expect } from "bun:test" -import { Effect, Layer } from "effect" -import { NodeFileSystem } from "@effect/platform-node" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect } from "effect" import { FSUtil } from "@opencode-ai/core/fs-util" import { testEffect } from "../lib/effect" import path from "path" -const live = FSUtil.layer.pipe(Layer.provide(NodeFileSystem.layer)) -const { effect: it } = testEffect(live) +const { effect: it } = testEffect(LayerNode.compile(FSUtil.node)) describe("FSUtil", () => { describe("isDir", () => { diff --git a/packages/opencode/test/image/image.test.ts b/packages/opencode/test/image/image.test.ts index c5d832cd5e..29667f6e2f 100644 --- a/packages/opencode/test/image/image.test.ts +++ b/packages/opencode/test/image/image.test.ts @@ -1,20 +1,18 @@ import { describe, expect } from "bun:test" -import { Cause, Effect, Exit, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Cause, Effect, Exit } from "effect" import { Image } from "@/image/image" +import { Config } from "@/config/config" import { MessageID, PartID, SessionID } from "@/session/schema" import path from "node:path" import { TestConfig } from "../fixture/config" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Image.layer.pipe(Layer.provide(TestConfig.layer())))) +const it = testEffect(LayerNode.compile(Image.node, [[Config.node, TestConfig.layer()]])) const tiny = testEffect( - Layer.mergeAll( - Image.layer.pipe( - Layer.provide( - TestConfig.layer({ get: () => Effect.succeed({ attachment: { image: { max_base64_bytes: 1 } } }) }), - ), - ), - ), + LayerNode.compile(Image.node, [ + [Config.node, TestConfig.layer({ get: () => Effect.succeed({ attachment: { image: { max_base64_bytes: 1 } } }) })], + ]), ) function part(mime: string, data: string) { diff --git a/packages/opencode/test/lsp/lifecycle.test.ts b/packages/opencode/test/lsp/lifecycle.test.ts index 5d0313e6d2..17387a6bf8 100644 --- a/packages/opencode/test/lsp/lifecycle.test.ts +++ b/packages/opencode/test/lsp/lifecycle.test.ts @@ -1,13 +1,13 @@ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test" import path from "path" -import { Effect, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect } from "effect" import { LSP } from "@/lsp/lsp" import * as LSPServer from "@/lsp/server" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(LayerNode.compile(LSP.node)) describe("LSP service lifecycle", () => { let spawnSpy: ReturnType diff --git a/packages/opencode/test/mcp/headers.test.ts b/packages/opencode/test/mcp/headers.test.ts index c51ed00d32..e6b83d678e 100644 --- a/packages/opencode/test/mcp/headers.test.ts +++ b/packages/opencode/test/mcp/headers.test.ts @@ -1,4 +1,5 @@ import { describe, expect, mock, beforeEach } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect } from "effect" import { testEffect } from "../lib/effect" @@ -46,7 +47,7 @@ beforeEach(() => { // Import MCP after mocking const { MCP } = await import("../../src/mcp/index") -const it = testEffect(MCP.defaultLayer) +const it = testEffect(LayerNode.compile(MCP.node)) describe("mcp.headers", () => { it.instance("headers are passed to transports when oauth is enabled (default)", () => diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index ec82d4f47f..aabb171f93 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -2,6 +2,7 @@ import path from "node:path" import { pathToFileURL } from "node:url" import { expect, mock, beforeEach } from "bun:test" import { ListRootsRequestSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Cause, Effect, Exit } from "effect" import type { MCP as MCPNS } from "../../src/mcp/index" import { testEffect } from "../lib/effect" @@ -275,7 +276,7 @@ beforeEach(() => { const { MCP } = await import("../../src/mcp/index") const { McpOAuthCallback } = await import("../../src/mcp/oauth-callback") -const it = testEffect(MCP.defaultLayer) +const it = testEffect(LayerNode.compile(MCP.node)) function statusName(status: Record | MCPNS.Status, server: string) { if ("status" in status) return status.status diff --git a/packages/opencode/test/patch/patch.test.ts b/packages/opencode/test/patch/patch.test.ts index c1e47a4f1d..0a8dd5d8ad 100644 --- a/packages/opencode/test/patch/patch.test.ts +++ b/packages/opencode/test/patch/patch.test.ts @@ -1,4 +1,5 @@ import { describe, test, expect, beforeEach, afterEach } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Effect } from "effect" import * as fs from "fs/promises" import * as path from "path" @@ -7,7 +8,7 @@ import { Patch } from "../../src/patch" import { FSUtil } from "@opencode-ai/core/fs-util" import { testEffect } from "../lib/effect" -const it = testEffect(FSUtil.defaultLayer) +const it = testEffect(LayerNode.compile(FSUtil.node)) describe("Patch namespace", () => { let tempDir: string diff --git a/packages/opencode/test/permission-task.test.ts b/packages/opencode/test/permission-task.test.ts index e5d92c5815..09a71465d5 100644 --- a/packages/opencode/test/permission-task.test.ts +++ b/packages/opencode/test/permission-task.test.ts @@ -1,11 +1,12 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { describe, test, expect } from "bun:test" import { Effect } from "effect" import { Permission } from "../src/permission" import { Config } from "@/config/config" import { testEffect } from "./lib/effect" -const it = testEffect(Config.defaultLayer) +const it = testEffect(LayerNode.compile(Config.node)) const load = Config.use.get() diff --git a/packages/opencode/test/project/instance-bootstrap.test.ts b/packages/opencode/test/project/instance-bootstrap.test.ts index 5009d6b500..5a07756a78 100644 --- a/packages/opencode/test/project/instance-bootstrap.test.ts +++ b/packages/opencode/test/project/instance-bootstrap.test.ts @@ -2,16 +2,16 @@ import { afterEach, expect } from "bun:test" import { existsSync } from "node:fs" import path from "node:path" import { pathToFileURL } from "node:url" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { bootstrap as cliBootstrap } from "../../src/cli/bootstrap" -import { InstanceLayer } from "../../src/project/instance-layer" import { InstanceStore } from "../../src/project/instance-store" import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { waitGlobalBusEvent } from "../server/global-bus" -const it = testEffect(Layer.mergeAll(InstanceLayer.layer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([InstanceStore.node, CrossSpawnSpawner.node]))) // InstanceBootstrap must run before any code touches the instance — // originally tracked by PRs #25389 and #25449, now a permanent diff --git a/packages/opencode/test/project/instance.test.ts b/packages/opencode/test/project/instance.test.ts index 491cfe93d7..dc52a3b9f8 100644 --- a/packages/opencode/test/project/instance.test.ts +++ b/packages/opencode/test/project/instance.test.ts @@ -1,9 +1,10 @@ import { describe, expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Deferred, Effect, Fiber, Layer } from "effect" import { InstanceRef } from "../../src/effect/instance-ref" import { registerDisposer } from "../../src/effect/instance-registry" -import { InstanceBootstrap } from "../../src/project/bootstrap-service" +import { InstanceBootstrap } from "../../src/project/bootstrap" import { InstanceStore } from "../../src/project/instance-store" import { tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -15,7 +16,9 @@ const noopBootstrap = Layer.succeed( ) const it = testEffect( - Layer.mergeAll(InstanceStore.defaultLayer, CrossSpawnSpawner.defaultLayer).pipe(Layer.provide(noopBootstrap)), + LayerNode.compile(LayerNode.group([InstanceStore.node, CrossSpawnSpawner.node]), [ + [InstanceBootstrap.node, noopBootstrap], + ]), ) const setBootstrap = (run: Effect.Effect) => diff --git a/packages/opencode/test/project/migrate-global.test.ts b/packages/opencode/test/project/migrate-global.test.ts index 77de870161..24d8aaaaaf 100644 --- a/packages/opencode/test/project/migrate-global.test.ts +++ b/packages/opencode/test/project/migrate-global.test.ts @@ -9,11 +9,12 @@ import { ProjectV2 } from "@opencode-ai/core/project" import { SessionID } from "../../src/session/schema" import { $ } from "bun" import { tmpdirScoped } from "../fixture/fixture" -import { Effect, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Effect } from "effect" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Project.defaultLayer, CrossSpawnSpawner.defaultLayer, Database.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Project.node, Database.node, CrossSpawnSpawner.node]))) function legacySessionID() { // Global-session migration covers persisted IDs from before prefixed session IDs. diff --git a/packages/opencode/test/project/project-directory.test.ts b/packages/opencode/test/project/project-directory.test.ts index cb458c30f3..112271c2c4 100644 --- a/packages/opencode/test/project/project-directory.test.ts +++ b/packages/opencode/test/project/project-directory.test.ts @@ -2,8 +2,9 @@ import { describe, expect } from "bun:test" import { $ } from "bun" import path from "path" import { eq } from "drizzle-orm" -import { Effect, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Effect } from "effect" import { Hash } from "@opencode-ai/core/util/hash" import { AbsolutePath } from "@opencode-ai/core/schema" import { Database } from "@opencode-ai/core/database/database" @@ -13,7 +14,7 @@ import { Project } from "@/project/project" import { tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Project.defaultLayer, Database.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Project.node, Database.node, CrossSpawnSpawner.node]))) function directories(projectID: ProjectV2.ID) { return Database.Service.use(({ db }) => diff --git a/packages/opencode/test/project/vcs.test.ts b/packages/opencode/test/project/vcs.test.ts index 21620adaf0..085a3349b4 100644 --- a/packages/opencode/test/project/vcs.test.ts +++ b/packages/opencode/test/project/vcs.test.ts @@ -1,8 +1,9 @@ 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 { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { parsePatch } from "diff" import { Deferred, Effect, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import fs from "fs/promises" import path from "path" import { @@ -24,11 +25,7 @@ import { testEffect } from "../lib/effect" const weird = process.platform === "win32" ? "space file.txt" : "tab\tfile.txt" -const layer = Layer.mergeAll( - Vcs.layer.pipe(Layer.provideMerge(Git.defaultLayer), Layer.provideMerge(EventV2Bridge.defaultLayer)), - CrossSpawnSpawner.defaultLayer, - FSUtil.defaultLayer, -) +const layer = LayerNode.compile(LayerNode.group([Vcs.node, Git.node, EventV2Bridge.node, FSUtil.node, CrossSpawnSpawner.node])) const it = testEffect(layer) const worktreeIt = testEffect(Layer.mergeAll(layer, testInstanceStoreLayer)) diff --git a/packages/opencode/test/project/worktree-remove.test.ts b/packages/opencode/test/project/worktree-remove.test.ts index c717578024..795fed4ac4 100644 --- a/packages/opencode/test/project/worktree-remove.test.ts +++ b/packages/opencode/test/project/worktree-remove.test.ts @@ -2,13 +2,13 @@ import { $ } from "bun" import { describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" -import { Effect, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect } from "effect" import { Worktree } from "../../src/worktree" import { TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Worktree.defaultLayer, CrossSpawnSpawner.defaultLayer)) +const it = testEffect(LayerNode.compile(Worktree.node)) const wintest = process.platform === "win32" ? it.instance : it.instance.skip describe("Worktree.remove", () => { diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index eebd0f55bd..f0a72c4b48 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -1,17 +1,15 @@ import { afterEach, describe, expect } from "bun:test" import path from "path" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { FSUtil } from "@opencode-ai/core/fs-util" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" -import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" +import { Cause, Deferred, Effect, Exit, Fiber } from "effect" import { GlobalBus, type GlobalEvent } from "../../src/bus/global" import { Git } from "../../src/git" import { Worktree } from "../../src/worktree" import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -const it = testEffect( - Layer.mergeAll(Worktree.defaultLayer, FSUtil.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer), -) +const it = testEffect(LayerNode.compile(LayerNode.group([Worktree.node, FSUtil.node, Git.node]))) const wintest = process.platform !== "win32" ? it.instance : it.instance.skip function normalize(input: string) { diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index d1cc510cd1..6e677631c4 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -1,5 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test" -import { Effect, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect } from "effect" import path from "path" import { unlink } from "fs/promises" import { Global } from "@opencode-ai/core/global" @@ -12,7 +13,7 @@ import { testEffect } from "../lib/effect" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" -const it = testEffect(Layer.mergeAll(Provider.defaultLayer, Env.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Provider.node, Env.node]))) const originalEnv = new Map() diff --git a/packages/opencode/test/provider/digitalocean.test.ts b/packages/opencode/test/provider/digitalocean.test.ts index 59c3f8da7c..144335c73b 100644 --- a/packages/opencode/test/provider/digitalocean.test.ts +++ b/packages/opencode/test/provider/digitalocean.test.ts @@ -1,4 +1,5 @@ import { expect } from "bun:test" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { Provider } from "../../src/provider/provider" import { Effect } from "effect" @@ -6,7 +7,7 @@ import { testEffect } from "../lib/effect" import { ProviderV2 } from "@opencode-ai/core/provider" const DIGITALOCEAN = ProviderV2.ID.make("digitalocean") -const it = testEffect(Provider.defaultLayer) +const it = testEffect(LayerNode.compile(Provider.node)) const withEnv = (values: Record, effect: Effect.Effect) => Effect.acquireUseRelease( diff --git a/packages/opencode/test/provider/header-timeout.test.ts b/packages/opencode/test/provider/header-timeout.test.ts index b9d8d45306..6b88574fd7 100644 --- a/packages/opencode/test/provider/header-timeout.test.ts +++ b/packages/opencode/test/provider/header-timeout.test.ts @@ -1,8 +1,9 @@ import { afterEach, expect } from "bun:test" import { createServer, type Server } from "node:http" import { streamText } from "ai" -import { Effect, Layer } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Effect } from "effect" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" import { disposeAllInstances, provideTmpdirInstance } from "../fixture/fixture" @@ -17,9 +18,7 @@ afterEach(async () => { await disposeAllInstances() }) -const it = testEffect( - Layer.mergeAll(Provider.defaultLayer, Env.defaultLayer, Plugin.defaultLayer, CrossSpawnSpawner.defaultLayer), -) +const it = testEffect(LayerNode.compile(LayerNode.group([Provider.node, Env.node, Plugin.node, CrossSpawnSpawner.node]))) it.live("headerTimeout does not abort delayed SSE body after headers arrive", () => Effect.gen(function* () { diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index 5ae076b439..72850bd5f0 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.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, Fiber, Layer, Queue } from "effect" import { Question } from "../../src/question" import { InstanceRef } from "../../src/effect/instance-ref" @@ -10,13 +11,11 @@ import { testEffect } from "../lib/effect" import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { EventV2Bridge } from "../../src/event-v2-bridge" -const it = testEffect( - Layer.mergeAll(Question.layer.pipe(Layer.provideMerge(EventV2Bridge.defaultLayer)), CrossSpawnSpawner.defaultLayer), -) +const questionLayer = LayerNode.compile(LayerNode.group([Question.node, EventV2Bridge.node, CrossSpawnSpawner.node])) +const it = testEffect(questionLayer) const lifecycle = testEffect( Layer.mergeAll( - Question.layer.pipe(Layer.provideMerge(EventV2Bridge.defaultLayer)), - CrossSpawnSpawner.defaultLayer, + questionLayer, testInstanceStoreLayer, ), ) diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index 4e71a6a36c..58f215be7a 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -1,29 +1,26 @@ import { describe, expect } from "bun:test" import { SessionV1 } from "@opencode-ai/core/v1/session" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { SessionProjector } from "@opencode-ai/core/session/projector" import fs from "fs/promises" import path from "path" -import { Effect, Layer } from "effect" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" +import { Effect } from "effect" import { Session } from "@/session/session" import { SessionRevert } from "../../src/session/revert" import { MessageV2 } from "../../src/session/message-v2" import { Snapshot } from "../../src/snapshot" import { MessageID, PartID, SessionID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { ProviderV2 } from "@opencode-ai/core/provider" import { ModelV2 } from "@opencode-ai/core/model" -const env = Layer.mergeAll( - Session.defaultLayer, - SessionRevert.defaultLayer, - Snapshot.defaultLayer, - CrossSpawnSpawner.defaultLayer, +const it = testEffect( + LayerNode.compile(LayerNode.group([Session.node, SessionRevert.node, Snapshot.node, SessionProjector.node, CrossSpawnSpawner.node])), ) -const it = testEffect(env) - const user = Effect.fn("test.user")(function* (sessionID: SessionID, agent = "default") { const session = yield* Session.Service return yield* session.updateMessage({ diff --git a/packages/opencode/test/skill/discovery.test.ts b/packages/opencode/test/skill/discovery.test.ts index a5833e7baf..bdb60dfddc 100644 --- a/packages/opencode/test/skill/discovery.test.ts +++ b/packages/opencode/test/skill/discovery.test.ts @@ -1,6 +1,7 @@ import { describe, expect, beforeAll, afterAll } 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 { Effect } from "effect" import { Discovery } from "../../src/skill/discovery" import { Global } from "@opencode-ai/core/global" import { Filesystem } from "@/util/filesystem" @@ -18,7 +19,7 @@ let mutableFiles = ["SKILL.md"] const fixturePath = path.join(import.meta.dir, "../fixture/skills") const cacheDir = path.join(Global.Path.cache, "skills") -const it = testEffect(Layer.mergeAll(Discovery.defaultLayer, FSUtil.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Discovery.node, FSUtil.node]))) beforeAll(async () => { await rm(cacheDir, { recursive: true, force: true }) diff --git a/packages/opencode/test/tool/external-directory.test.ts b/packages/opencode/test/tool/external-directory.test.ts index 69a48bad7a..d43accfb70 100644 --- a/packages/opencode/test/tool/external-directory.test.ts +++ b/packages/opencode/test/tool/external-directory.test.ts @@ -1,4 +1,5 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { describe, expect } from "bun:test" import path from "path" import { Effect } from "effect" @@ -11,7 +12,7 @@ import type { Permission } from "../../src/permission" import { SessionID, MessageID } from "../../src/session/schema" import { testEffect } from "../lib/effect" -const it = testEffect(CrossSpawnSpawner.defaultLayer) +const it = testEffect(LayerNode.compile(CrossSpawnSpawner.node)) const baseCtx: Omit = { sessionID: SessionID.make("ses_test"), diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 0bbc58d442..26fb1fdbaf 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -1,10 +1,10 @@ import { describe, expect } from "bun:test" -import { Effect, Fiber, Layer, Queue } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Effect, Fiber, Queue } from "effect" import { QuestionTool } from "../../src/tool/question" import { Question } from "../../src/question" import { SessionID, MessageID } from "../../src/session/schema" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Truncate } from "@/tool/truncate" import { testEffect } from "../lib/effect" import { EventV2Bridge } from "../../src/event-v2-bridge" @@ -20,14 +20,7 @@ const ctx = { ask: () => Effect.void, } -const it = testEffect( - Layer.mergeAll( - Question.layer.pipe(Layer.provideMerge(EventV2Bridge.defaultLayer)), - CrossSpawnSpawner.defaultLayer, - Truncate.defaultLayer, - Agent.defaultLayer, - ), -) +const it = testEffect(LayerNode.compile(LayerNode.group([Question.node, EventV2Bridge.node, Truncate.node, Agent.node]))) const pending = Effect.fn("QuestionToolTest.pending")(function* (question: Question.Interface) { const events = yield* EventV2Bridge.Service diff --git a/packages/opencode/test/tool/tool-define.test.ts b/packages/opencode/test/tool/tool-define.test.ts index 08e0604362..e254e5050e 100644 --- a/packages/opencode/test/tool/tool-define.test.ts +++ b/packages/opencode/test/tool/tool-define.test.ts @@ -1,12 +1,13 @@ import { describe, expect } from "bun:test" -import { Cause, Effect, Exit, Layer, Schema } from "effect" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { Cause, Effect, Exit, Schema } from "effect" import { Agent } from "../../src/agent/agent" import { MessageID, SessionID } from "../../src/session/schema" import { Tool } from "@/tool/tool" import { Truncate } from "@/tool/truncate" import { testEffect } from "../lib/effect" -const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Truncate.node, Agent.node]))) const params = Schema.Struct({ input: Schema.String }) diff --git a/packages/opencode/test/tool/truncation.test.ts b/packages/opencode/test/tool/truncation.test.ts index 6e65b5f54c..d575a58ffa 100644 --- a/packages/opencode/test/tool/truncation.test.ts +++ b/packages/opencode/test/tool/truncation.test.ts @@ -1,8 +1,9 @@ import { describe, test, expect } from "bun:test" import { ConfigV1 } from "@opencode-ai/core/v1/config/config" -import { NodeFileSystem } from "@effect/platform-node" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { filesystem } from "@opencode-ai/core/effect/app-node-platform" import { FSUtil } from "@opencode-ai/core/fs-util" -import { Effect, FileSystem, Layer } from "effect" +import { Effect, FileSystem } from "effect" import { Truncate } from "@/tool/truncate" import { Config } from "@/config/config" import { Identifier } from "../../src/id/id" @@ -15,15 +16,12 @@ import { TestConfig } from "../fixture/config" const FIXTURES_DIR = path.join(import.meta.dir, "fixtures") const ROOT = path.resolve(import.meta.dir, "..", "..") -const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer, FSUtil.defaultLayer)) +const it = testEffect(LayerNode.compile(LayerNode.group([Truncate.node, FSUtil.node, filesystem]))) const configuredLayer = (cfg: ConfigV1.Info) => - Layer.mergeAll( - Truncate.defaultLayer, - NodeFileSystem.layer, - FSUtil.defaultLayer, - TestConfig.layer({ get: () => Effect.succeed(cfg) }), - ) + LayerNode.compile(LayerNode.group([Truncate.node, FSUtil.node, filesystem, Config.node]), [ + [Config.node, TestConfig.layer({ get: () => Effect.succeed(cfg) })], + ]) const configuredIt = (cfg: ConfigV1.Info) => testEffect(configuredLayer(cfg)) describe("Truncate", () => {