refactor(core): convert more opencode tests to nodes (#34464)

This commit is contained in:
James Long 2026-06-29 13:56:28 -04:00 committed by GitHub
commit d4fd528152
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 209 additions and 203 deletions

View file

@ -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"

View file

@ -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"

View file

@ -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<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) =>
HttpClientResponse.fromWeb(

View file

@ -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", () => {

View file

@ -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 UsageService.SessionMessage[], unknown>
readonly providers?: (directory: string) => Effect.Effect<Record<ProviderV2.ID, Provider.Info>, 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) {

View file

@ -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<RuntimeFlags.Info> = {}) =>
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())

View file

@ -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", () => {

View file

@ -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) =>

View file

@ -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 = <A, E>(state: InstanceState.InstanceState<A, E>, dir: string) =>
InstanceState.get(state).pipe(provideInstanceEffect(dir))

View file

@ -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(), {

View file

@ -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", () => {

View file

@ -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<typeof RuntimeFlags.layer>[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", () => {

View file

@ -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<FSUtil.Service>) {
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]])),
)
}

View file

@ -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) => ({

View file

@ -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<MCPNS.Interface, never, never>

View file

@ -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(

View file

@ -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<T, A, E, R>(
init: (dir: string) => Promise<T>,

View file

@ -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<RuntimeFlags.Info> = {}) =>
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<string, { models: Record<string, { cost: { input
const languageBaseURL = (language: unknown) => (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 = {

View file

@ -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<SessionNs.Interface["create"]>[0]) =>
Effect.acquireRelease(SessionNs.use.create(input), (created) =>

View file

@ -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 = {}) {

View file

@ -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"

View file

@ -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

View file

@ -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<unknown>
const it = testEffect(Layer.mergeAll(Session.defaultLayer, httpApiLayer))
const it = testEffect(Layer.mergeAll(LayerNode.compile(Session.node), httpApiLayer))
afterEach(async () => {
mock.restore()

View file

@ -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* () {

View file

@ -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 = {}) {

View file

@ -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)

View file

@ -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()

View file

@ -11,6 +11,7 @@
* asserts that GET /session/<id>/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()

View file

@ -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"),

View file

@ -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(

View file

@ -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", () => {

View file

@ -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,
),

View file

@ -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 = <A, E, R>(
})
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 () => {

View file

@ -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", () =>

View file

@ -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 = {

View file

@ -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)

View file

@ -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<RuntimeFlags.Info> = {}) =>
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)

View file

@ -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<RuntimeFlags.Info> = {}) =>
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))

View file

@ -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* () {

View file

@ -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<RuntimeFlags.Info> = {}) =>
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))

View file

@ -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)

View file

@ -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", () =>

View file

@ -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<RuntimeFlags.Info> = {}) =>
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 }))

View file

@ -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<HttpClient.HttpClient>],
]),
)
const ctx = {
sessionID: SessionID.make("ses_test"),

View file

@ -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]),
),
)