refactor(core): resolve database and websearch config through Effect Config
This commit is contained in:
parent
4045041554
commit
0552458fba
10 changed files with 222 additions and 46 deletions
|
|
@ -4,17 +4,36 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk"
|
||||
import { createEmbeddedRoutes } from "@opencode-ai/server/routes"
|
||||
import { Context, Effect, Layer, Scope } from "effect"
|
||||
import { ConfigProvider, Context, Effect, Layer, Scope } from "effect"
|
||||
import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
|
||||
export const create = Effect.fn("OpenCode.create")(function* () {
|
||||
export interface Options {
|
||||
/**
|
||||
* Replaces the ConfigProvider read while this host's process-global layers
|
||||
* are built, for example the OPENCODE_DB database placement. The default
|
||||
* provider snapshots the process environment on first use, so per-host
|
||||
* configuration must come through this seam rather than env mutation.
|
||||
* Location-scoped layers build lazily outside host construction and still
|
||||
* read the process default. Compose with
|
||||
* ConfigProvider.orElse(ConfigProvider.fromEnv()) to keep environment
|
||||
* fallback.
|
||||
*/
|
||||
readonly configProvider?: ConfigProvider.ConfigProvider
|
||||
}
|
||||
|
||||
export const create = Effect.fn("OpenCode.create")(function* (options?: Options) {
|
||||
const scope = yield* Scope.Scope
|
||||
const memoMap = yield* Layer.makeMemoMap
|
||||
const configLayer = options?.configProvider === undefined ? undefined : ConfigProvider.layer(options.configProvider)
|
||||
const withConfig = <A, E, R>(layer: Layer.Layer<A, E, R>) =>
|
||||
configLayer === undefined ? layer : layer.pipe(Layer.provide(configLayer))
|
||||
const sdkPlugins = SdkPlugins.makeStore()
|
||||
const context = yield* Layer.buildWithMemoMap(
|
||||
AppNodeBuilder.build(LayerNode.group([PermissionSaved.node, SdkPlugins.node]), [
|
||||
[SdkPlugins.node, SdkPlugins.layerWithStore(sdkPlugins)],
|
||||
]),
|
||||
withConfig(
|
||||
AppNodeBuilder.build(LayerNode.group([PermissionSaved.node, SdkPlugins.node]), [
|
||||
[SdkPlugins.node, SdkPlugins.layerWithStore(sdkPlugins)],
|
||||
]),
|
||||
),
|
||||
memoMap,
|
||||
scope,
|
||||
)
|
||||
|
|
@ -23,9 +42,11 @@ export const create = Effect.fn("OpenCode.create")(function* () {
|
|||
const web = yield* Effect.acquireRelease(
|
||||
Effect.sync(() =>
|
||||
HttpRouter.toWebHandler(
|
||||
createEmbeddedRoutes(sdkPlugins).pipe(
|
||||
HttpRouter.provideRequest(Layer.succeed(PermissionSaved.Service, permissions)),
|
||||
Layer.provide(HttpServer.layerServices),
|
||||
withConfig(
|
||||
createEmbeddedRoutes(sdkPlugins).pipe(
|
||||
HttpRouter.provideRequest(Layer.succeed(PermissionSaved.Service, permissions)),
|
||||
Layer.provide(HttpServer.layerServices),
|
||||
),
|
||||
),
|
||||
{ disableLogger: true, memoMap },
|
||||
),
|
||||
|
|
@ -56,4 +77,6 @@ export type Interface = Effect.Success<ReturnType<typeof create>>
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode-ai/sdk-next/OpenCode") {}
|
||||
|
||||
export const layer = Layer.effect(Service, create())
|
||||
export const layerWith = (options: Options) => Layer.effect(Service, create(options))
|
||||
|
||||
export const layer = layerWith({})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import { expect } from "bun:test"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Deferred, Effect, Latch, Layer, Option, Schema, Stream } from "effect"
|
||||
import { ConfigProvider, Deferred, Effect, Latch, Layer, Option, Schema, Stream } from "effect"
|
||||
import { join } from "node:path"
|
||||
import { testEffect } from "../../core/test/lib/effect"
|
||||
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
||||
import type { OpenCodeEvent } from "../src"
|
||||
|
||||
Flag.OPENCODE_DB = ":memory:"
|
||||
// Database placement resolves through Effect Config when a host's layers are
|
||||
// built. The default ConfigProvider snapshots process.env on first use, so
|
||||
// this must run before the first Config read in the process.
|
||||
process.env.OPENCODE_DB = ":memory:"
|
||||
|
||||
const it = testEffect(Layer.empty)
|
||||
type Sdk = typeof import("../src")
|
||||
|
|
@ -204,3 +207,19 @@ it.live("embedded client is available as a Layer service", () =>
|
|||
}).pipe(Effect.provide(fixture.sdk.OpenCode.layer))
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("a host ConfigProvider chooses this host's database placement", () =>
|
||||
withEmbedded("opencode-embedded-config-", (fixture) =>
|
||||
Effect.gen(function* () {
|
||||
// The process env pins OPENCODE_DB to ":memory:" above, so a database
|
||||
// materializing at this path proves the host read the provider instead.
|
||||
const file = join(fixture.directory, "seam.sqlite")
|
||||
const opencode = yield* fixture.sdk.OpenCode.create({
|
||||
configProvider: ConfigProvider.fromUnknown({ OPENCODE_DB: file }),
|
||||
})
|
||||
const created = yield* opencode.sessions.create({ location: location(fixture) })
|
||||
expect(created.id).toBeTruthy()
|
||||
expect(yield* Effect.promise(() => Bun.file(file).exists())).toBe(true)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue