refactor(core): resolve database and websearch config through Effect Config

This commit is contained in:
Kit Langton 2026-07-02 00:25:31 -04:00
commit cb32c42e6f
7 changed files with 148 additions and 43 deletions

View file

@ -0,0 +1,38 @@
import { describe, expect, test } from "bun:test"
import path from "path"
import { ConfigProvider, Effect, Layer } from "effect"
import { Database } from "@opencode-ai/core/database/database"
import { Global } from "@opencode-ai/core/global"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { tmpdir } from "./fixture/tmpdir"
describe("Database placement", () => {
test("resolves explicit files, relative names, and the channel default", () => {
expect(Database.resolvePath({ file: ":memory:", disableChannelDb: false })).toBe(":memory:")
expect(Database.resolvePath({ file: "/tmp/explicit.db", disableChannelDb: false })).toBe("/tmp/explicit.db")
expect(Database.resolvePath({ file: "relative.db", disableChannelDb: false })).toBe(
path.join(Global.Path.data, "relative.db"),
)
expect(Database.resolvePath({ file: undefined, disableChannelDb: true })).toBe(
path.join(Global.Path.data, "opencode.db"),
)
})
test("reads placement from the active ConfigProvider when the layer is built", async () => {
await using tmp = await tmpdir()
const file = path.join(tmp.path, "config-seam.sqlite")
// The preload sets OPENCODE_DB=":memory:" in the process environment, so a
// database appearing at this path proves the layer reads through the
// replaced ConfigProvider rather than the environment snapshot.
await Effect.runPromise(
Layer.build(
LayerNode.compile(LayerNode.group([Database.node])).pipe(
Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({ OPENCODE_DB: file }))),
),
).pipe(Effect.scoped, Effect.asVoid),
)
expect(await Bun.file(file).exists()).toBe(true)
})
})

View file

@ -1,5 +1,5 @@
import { beforeEach, describe, expect, test } from "bun:test"
import { Effect, Layer, Schema } from "effect"
import { ConfigProvider, Effect, Exit, Layer, Schema } from "effect"
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
@ -47,6 +47,52 @@ describe("WebSearchTool provider selection", () => {
})
})
const readDefaultConfig = (env: Record<string, string>) =>
Effect.gen(function* () {
return yield* WebSearchTool.ConfigService
}).pipe(
Effect.provide(
WebSearchTool.defaultConfigLayer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(env)))),
),
)
describe("WebSearchTool default config", () => {
test("decodes an empty environment to defaults", async () => {
expect(await Effect.runPromise(readDefaultConfig({}))).toEqual({
provider: undefined,
enableExa: false,
enableParallel: false,
exaApiKey: undefined,
parallelApiKey: undefined,
})
})
test("decodes provider, truthy flags, and credentials from the active ConfigProvider", async () => {
expect(
await Effect.runPromise(
readDefaultConfig({
OPENCODE_WEBSEARCH_PROVIDER: "parallel",
OPENCODE_ENABLE_EXA: "1",
OPENCODE_EXPERIMENTAL_PARALLEL: "true",
EXA_API_KEY: "exa-key",
PARALLEL_API_KEY: "parallel-key",
}),
),
).toEqual({
provider: "parallel",
enableExa: true,
enableParallel: true,
exaApiKey: "exa-key",
parallelApiKey: "parallel-key",
})
})
test("fails on an invalid provider instead of silently ignoring it", async () => {
const exit = await Effect.runPromiseExit(readDefaultConfig({ OPENCODE_WEBSEARCH_PROVIDER: "bing" }))
expect(Exit.isFailure(exit)).toBe(true)
})
})
describe("WebSearchTool MCP response parser", () => {
test("parses plain JSON-RPC responses", async () => {
expect(await Effect.runPromise(WebSearchTool.parseResponse(payload("search results")))).toBe("search results")