refactor(core): resolve database and websearch config through Effect Config
This commit is contained in:
parent
a39db9c6f6
commit
cb32c42e6f
7 changed files with 148 additions and 43 deletions
|
|
@ -2,9 +2,9 @@ export * as Database from "./database"
|
|||
|
||||
import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
|
||||
import { layer as sqliteLayer } from "#sqlite"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { Config, Context, Effect, Layer, Option } from "effect"
|
||||
import { Global } from "../global"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { truthy } from "../flag/flag"
|
||||
import { isAbsolute, join } from "path"
|
||||
import { DatabaseMigration } from "./migration"
|
||||
import { InstallationChannel } from "../installation/version"
|
||||
|
|
@ -40,18 +40,33 @@ export function layerFromPath(filename: string) {
|
|||
return layer.pipe(Layer.provide(sqliteLayer({ filename })))
|
||||
}
|
||||
|
||||
export function path() {
|
||||
if (Flag.OPENCODE_DB) {
|
||||
if (Flag.OPENCODE_DB === ":memory:" || isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB
|
||||
return join(Global.Path.data, Flag.OPENCODE_DB)
|
||||
/** One placement rule shared by the config-backed layer and the V1 `path()` helper. */
|
||||
export function resolvePath(input: { readonly file: string | undefined; readonly disableChannelDb: boolean }) {
|
||||
if (input.file) {
|
||||
if (input.file === ":memory:" || isAbsolute(input.file)) return input.file
|
||||
return join(Global.Path.data, input.file)
|
||||
}
|
||||
if (
|
||||
["latest", "beta", "prod"].includes(InstallationChannel) ||
|
||||
process.env.OPENCODE_DISABLE_CHANNEL_DB === "1" ||
|
||||
process.env.OPENCODE_DISABLE_CHANNEL_DB === "true"
|
||||
)
|
||||
if (["latest", "beta", "prod"].includes(InstallationChannel) || input.disableChannelDb)
|
||||
return join(Global.Path.data, "opencode.db")
|
||||
return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
|
||||
}
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer: layerFromPath(path()), deps: [] })
|
||||
/** V1 compatibility helper; reads the process environment at call time. */
|
||||
export function path() {
|
||||
return resolvePath({
|
||||
file: process.env.OPENCODE_DB,
|
||||
disableChannelDb: truthy("OPENCODE_DISABLE_CHANNEL_DB"),
|
||||
})
|
||||
}
|
||||
|
||||
// Placement is resolved through Effect Config when the layer is built, not at
|
||||
// module import, so tests and tooling can override it with a ConfigProvider.
|
||||
const configuredLayer = Layer.unwrap(
|
||||
Effect.gen(function* () {
|
||||
const file = yield* Config.option(Config.string("OPENCODE_DB"))
|
||||
const disableChannelDb = yield* Config.boolean("OPENCODE_DISABLE_CHANNEL_DB").pipe(Config.withDefault(false))
|
||||
return layerFromPath(resolvePath({ file: Option.getOrUndefined(file), disableChannelDb }))
|
||||
}).pipe(Effect.orDie),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer: configuredLayer, deps: [] })
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ export const Flag = {
|
|||
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
|
||||
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
|
||||
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
|
||||
OPENCODE_DB: process.env["OPENCODE_DB"],
|
||||
|
||||
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
|
||||
OPENCODE_EXPERIMENTAL_WORKSPACES: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
export * as WebSearchTool from "./websearch"
|
||||
|
||||
import { ToolFailure } from "@opencode-ai/llm"
|
||||
import { Context, Duration, Effect, Layer, Schema } from "effect"
|
||||
import { Config, Context, Duration, Effect, Layer, Option, Redacted, Schema } from "effect"
|
||||
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
||||
import { makeLocationNode } from "../effect/app-node"
|
||||
import { LayerNodePlatform } from "../effect/app-node-platform"
|
||||
import { truthy } from "../flag/flag"
|
||||
import { InstallationVersion } from "../installation/version"
|
||||
import { PositiveInt } from "../schema"
|
||||
import { PermissionV2 } from "../permission"
|
||||
|
|
@ -69,19 +68,33 @@ export interface Config {
|
|||
|
||||
export class ConfigService extends Context.Service<ConfigService, Config>()("@opencode/v2/WebSearchConfig") {}
|
||||
|
||||
/** Isolates the retained product environment contract from the generic tool implementation. */
|
||||
export const defaultConfigLayer = Layer.sync(ConfigService, () =>
|
||||
ConfigService.of({
|
||||
provider:
|
||||
process.env.OPENCODE_WEBSEARCH_PROVIDER === "exa" || process.env.OPENCODE_WEBSEARCH_PROVIDER === "parallel"
|
||||
? process.env.OPENCODE_WEBSEARCH_PROVIDER
|
||||
: undefined,
|
||||
enableExa: truthy("OPENCODE_EXPERIMENTAL") || truthy("OPENCODE_ENABLE_EXA") || truthy("OPENCODE_EXPERIMENTAL_EXA"),
|
||||
enableParallel: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"),
|
||||
exaApiKey: process.env.EXA_API_KEY,
|
||||
parallelApiKey: process.env.PARALLEL_API_KEY,
|
||||
const flag = (name: string) => Config.boolean(name).pipe(Config.withDefault(false))
|
||||
|
||||
/**
|
||||
* Isolates the retained product environment contract from the generic tool
|
||||
* implementation. Reads through Effect `Config` when the layer is built, so
|
||||
* tests can override values with a `ConfigProvider` instead of mutating
|
||||
* `process.env`. Malformed values fail the layer instead of silently
|
||||
* disabling a provider the user asked for.
|
||||
*/
|
||||
export const defaultConfigLayer = Layer.effect(
|
||||
ConfigService,
|
||||
Effect.gen(function* () {
|
||||
const provider = yield* Config.option(Config.literals(["exa", "parallel"], "OPENCODE_WEBSEARCH_PROVIDER"))
|
||||
const exaApiKey = yield* Config.option(Config.redacted("EXA_API_KEY"))
|
||||
const parallelApiKey = yield* Config.option(Config.redacted("PARALLEL_API_KEY"))
|
||||
return ConfigService.of({
|
||||
provider: Option.getOrUndefined(provider),
|
||||
enableExa:
|
||||
(yield* flag("OPENCODE_EXPERIMENTAL")) ||
|
||||
(yield* flag("OPENCODE_ENABLE_EXA")) ||
|
||||
(yield* flag("OPENCODE_EXPERIMENTAL_EXA")),
|
||||
enableParallel: (yield* flag("OPENCODE_ENABLE_PARALLEL")) || (yield* flag("OPENCODE_EXPERIMENTAL_PARALLEL")),
|
||||
exaApiKey: Option.getOrUndefined(Option.map(exaApiKey, Redacted.value)),
|
||||
parallelApiKey: Option.getOrUndefined(Option.map(parallelApiKey, Redacted.value)),
|
||||
})
|
||||
}),
|
||||
)
|
||||
).pipe(Layer.orDie)
|
||||
|
||||
export const configNode = makeLocationNode({ service: ConfigService, layer: defaultConfigLayer, deps: [] })
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue