refactor(server): migrate host options
This commit is contained in:
parent
9445b4d940
commit
407ac2d8aa
20 changed files with 149 additions and 94 deletions
|
|
@ -90,6 +90,9 @@ const processEffect = Effect.fnUntraced(function* (options: Options) {
|
|||
process.env.OPENCODE_CONFIG_PROJECT_DISABLE ?? process.env.OPENCODE_DISABLE_PROJECT_CONFIG,
|
||||
),
|
||||
},
|
||||
windows: {
|
||||
gitbash: process.env.OPENCODE_GIT_BASH_PATH,
|
||||
},
|
||||
fs: {
|
||||
filewatcher: !truthy(
|
||||
process.env.OPENCODE_FILEWATCHER_DISABLE ?? process.env.OPENCODE_DISABLE_FILEWATCHER,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export interface Interface extends State.Transformable<Draft> {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Command") {}
|
||||
|
||||
const layer = Layer.effect(
|
||||
export const layer = (options?: ShellSelect.Options) => Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const mcp = yield* MCP.Service
|
||||
|
|
@ -111,6 +111,7 @@ const layer = Layer.effect(
|
|||
config,
|
||||
location,
|
||||
processes,
|
||||
shell: options,
|
||||
})
|
||||
|
||||
const prompt = (yield* mcp.prompts()).find((prompt) => mcpCommandName(prompt.server, prompt.name) === input.name)
|
||||
|
|
@ -157,6 +158,7 @@ function evaluateTemplate(
|
|||
readonly config: Config.Interface
|
||||
readonly location: Location.Info
|
||||
readonly processes: AppProcess.Interface
|
||||
readonly shell?: ShellSelect.Options
|
||||
},
|
||||
) {
|
||||
return Effect.gen(function* () {
|
||||
|
|
@ -188,11 +190,12 @@ const evaluateShell = Effect.fnUntraced(function* (
|
|||
readonly config: Config.Interface
|
||||
readonly location: Location.Info
|
||||
readonly processes: AppProcess.Interface
|
||||
readonly shell?: ShellSelect.Options
|
||||
},
|
||||
) {
|
||||
const matches = Array.from(text.matchAll(shellRegex))
|
||||
if (matches.length === 0) return text
|
||||
const shell = ShellSelect.preferred(Config.latest(yield* services.config.entries(), "shell"))
|
||||
const shell = ShellSelect.preferred(Config.latest(yield* services.config.entries(), "shell"), services.shell)
|
||||
const outputs = yield* Effect.forEach(
|
||||
matches,
|
||||
(match) => {
|
||||
|
|
@ -240,8 +243,12 @@ const placeholderRegex = /\$(\d+)/g
|
|||
const quoteTrimRegex = /^["']|["']$/g
|
||||
const shellRegex = /!`([^`]+)`/g
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
deps: [MCP.node, EventV2.node, AppProcess.node, Config.node, Location.node],
|
||||
})
|
||||
export function configured(options?: ShellSelect.Options) {
|
||||
return makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(options),
|
||||
deps: [MCP.node, EventV2.node, AppProcess.node, Config.node, Location.node],
|
||||
})
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
|
|
|||
|
|
@ -399,8 +399,12 @@ export const layer = (options?: Options) => Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(),
|
||||
deps: [Watcher.node, EventV2.node, FSUtil.node, Global.node, Location.node, Credential.node, WellKnown.node],
|
||||
})
|
||||
export function configured(options?: Options) {
|
||||
return makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(options),
|
||||
deps: [Watcher.node, EventV2.node, FSUtil.node, Global.node, Location.node, Credential.node, WellKnown.node],
|
||||
})
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ export function layer(options?: Options) {
|
|||
})
|
||||
}
|
||||
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer({ path: ":memory:" }),
|
||||
deps: [],
|
||||
})
|
||||
export function configured(options?: Options) {
|
||||
return makeGlobalNode({ service: Service, layer: layer(options), deps: [] })
|
||||
}
|
||||
|
||||
export const node = configured({ path: ":memory:" })
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ type CheckReplacementErrors<SourceError, ReplacementError> = [Exclude<Replacemen
|
|||
type CheckReplacement<Item> = Item extends readonly [Node<infer A, infer E, infer T>, infer Replacement]
|
||||
? Replacement extends Node<NoInfer<A>, infer E2, T>
|
||||
? CheckReplacementErrors<E, NoInfer<E2>>
|
||||
: Replacement extends Layer.Layer<NoInfer<A>, infer E2, infer _R>
|
||||
: Replacement extends Layer.Layer<NoInfer<A>, infer E2, never>
|
||||
? CheckReplacementErrors<E, NoInfer<E2>>
|
||||
: { readonly "Invalid replacement": Replacement }
|
||||
: { readonly "Invalid replacement": Item }
|
||||
|
|
@ -133,16 +133,14 @@ type CheckReplacements<Items extends Replacements> = {
|
|||
type ValidReplacements<Items extends Replacements> = Items & CheckReplacements<Items>
|
||||
|
||||
function replacementNode(source: AnyNode, replacement: AnyNode | Layer.Any) {
|
||||
const replacementNode: AnyNode = isNode(replacement)
|
||||
const replacementNode = isNode(replacement)
|
||||
? replacement
|
||||
: {
|
||||
kind: "layer",
|
||||
name: source.name,
|
||||
service: source.service,
|
||||
implementation: replacement,
|
||||
dependencies: source.dependencies,
|
||||
: make({
|
||||
...nodeMakeIdentity(source),
|
||||
layer: replacement as Layer.Layer<unknown, unknown>,
|
||||
deps: [],
|
||||
tag: source.tag,
|
||||
}
|
||||
})
|
||||
if (source.name !== replacementNode.name) {
|
||||
throw new Error(`Cannot replace ${source.name} with ${replacementNode.name}`)
|
||||
}
|
||||
|
|
@ -152,6 +150,11 @@ function replacementNode(source: AnyNode, replacement: AnyNode | Layer.Any) {
|
|||
return replacementNode
|
||||
}
|
||||
|
||||
function nodeMakeIdentity(node: AnyNode): NodeIdentity {
|
||||
if (node.service !== undefined) return { service: node.service }
|
||||
return { name: node.name }
|
||||
}
|
||||
|
||||
function isNode(input: Layer.Any | AnyNode): input is AnyNode {
|
||||
return "kind" in input && "dependencies" in input
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,8 +246,8 @@ export const layer = (options?: Options) => Layer.unwrap(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(),
|
||||
deps: [FSUtil.node, Location.node, Ripgrep.node],
|
||||
})
|
||||
export function configured(options?: Options) {
|
||||
return makeLocationNode({ service: Service, layer: layer(options), deps: [FSUtil.node, Location.node, Ripgrep.node] })
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
|
|
|||
|
|
@ -144,7 +144,11 @@ export const layer = (options?: Options) => Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer(), deps: [] })
|
||||
export function configured(options?: Options) {
|
||||
return makeGlobalNode({ service: Service, layer: layer(options), deps: [] })
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
||||
function subscribeDirectory(
|
||||
native: typeof import("@parcel/watcher") | undefined,
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@ function enabledByExperimental(key: string) {
|
|||
}
|
||||
|
||||
export const Flag = {
|
||||
// V2: ServerOptions.observability.endpoint
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"],
|
||||
// V2: ServerOptions.observability.headers
|
||||
OTEL_EXPORTER_OTLP_HEADERS: process.env["OTEL_EXPORTER_OTLP_HEADERS"],
|
||||
|
||||
OPENCODE_AUTO_HEAP_SNAPSHOT: truthy("OPENCODE_AUTO_HEAP_SNAPSHOT"),
|
||||
// V2: ServerOptions.windows.gitbash
|
||||
OPENCODE_GIT_BASH_PATH: process.env["OPENCODE_GIT_BASH_PATH"],
|
||||
OPENCODE_CONFIG: process.env["OPENCODE_CONFIG"],
|
||||
OPENCODE_CONFIG_CONTENT: process.env["OPENCODE_CONFIG_CONTENT"],
|
||||
|
|
@ -26,19 +29,25 @@ export const Flag = {
|
|||
OPENCODE_DISABLE_TERMINAL_TITLE: truthy("OPENCODE_DISABLE_TERMINAL_TITLE"),
|
||||
OPENCODE_SHOW_TTFD: truthy("OPENCODE_SHOW_TTFD"),
|
||||
OPENCODE_DISABLE_AUTOCOMPACT: truthy("OPENCODE_DISABLE_AUTOCOMPACT"),
|
||||
// V2: ServerOptions.models.fetch
|
||||
OPENCODE_DISABLE_MODELS_FETCH: truthy("OPENCODE_DISABLE_MODELS_FETCH"),
|
||||
OPENCODE_DISABLE_MOUSE: truthy("OPENCODE_DISABLE_MOUSE"),
|
||||
OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"],
|
||||
OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"],
|
||||
OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"],
|
||||
// V2: ServerOptions.fs.fff
|
||||
OPENCODE_DISABLE_FFF: fff === undefined ? process.platform === "win32" : truthy("OPENCODE_DISABLE_FFF"),
|
||||
// V2: ServerOptions.fs.filewatcher
|
||||
OPENCODE_DISABLE_FILEWATCHER: truthy("OPENCODE_DISABLE_FILEWATCHER"),
|
||||
|
||||
// Experimental
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT:
|
||||
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
|
||||
// V2: ServerOptions.models.url
|
||||
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
|
||||
// V2: ServerOptions.models.file
|
||||
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
|
||||
// V2: ServerOptions.database.path
|
||||
OPENCODE_DB: process.env["OPENCODE_DB"],
|
||||
|
||||
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
|
||||
|
|
@ -46,6 +55,7 @@ export const Flag = {
|
|||
|
||||
// Evaluated at access time (not module load) because tests, the CLI, and
|
||||
// external tooling set these env vars at runtime.
|
||||
// V2: ServerOptions.config.project
|
||||
get OPENCODE_DISABLE_PROJECT_CONFIG() {
|
||||
return truthy("OPENCODE_DISABLE_PROJECT_CONFIG")
|
||||
},
|
||||
|
|
@ -55,6 +65,7 @@ export const Flag = {
|
|||
get OPENCODE_TUI_CONFIG() {
|
||||
return process.env["OPENCODE_TUI_CONFIG"]
|
||||
},
|
||||
// V2: ServerOptions.config.directory
|
||||
get OPENCODE_CONFIG_DIR() {
|
||||
return process.env["OPENCODE_CONFIG_DIR"]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -97,11 +97,15 @@ export const layer = (options?: Options) => Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(),
|
||||
deps: [FSUtil.node, Global.node, Location.node],
|
||||
})
|
||||
export function configured(options?: Options) {
|
||||
return makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(options),
|
||||
deps: [FSUtil.node, Global.node, Location.node],
|
||||
})
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
||||
function render(files: ReadonlyArray<File>) {
|
||||
return files.map((file) => `Instructions from: ${file.path}\n${file.content}`).join("\n\n")
|
||||
|
|
|
|||
|
|
@ -659,6 +659,10 @@ export const layer = (options?: Options) => Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer: layer(), deps: [FSUtil.node, EventV2.node, httpClient] })
|
||||
export function configured(options?: Options) {
|
||||
return makeGlobalNode({ service: Service, layer: layer(options), deps: [FSUtil.node, EventV2.node, httpClient] })
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
||||
export * as ModelsDev from "./models-dev"
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Pty") {}
|
||||
|
||||
const layer = Layer.effect(
|
||||
export const layer = (options?: ShellSelect.Options) => Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
|
@ -164,7 +164,7 @@ const layer = Layer.effect(
|
|||
|
||||
const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
|
||||
const id = PtyID.ascending()
|
||||
const command = input.command || ShellSelect.preferred(Config.latest(yield* config.entries(), "shell"))
|
||||
const command = input.command || ShellSelect.preferred(Config.latest(yield* config.entries(), "shell"), options)
|
||||
const args = ShellSelect.login(command) ? [...(input.args ?? []), "-l"] : [...(input.args ?? [])]
|
||||
const cwd = input.cwd || location.directory
|
||||
const env = {
|
||||
|
|
@ -313,4 +313,8 @@ const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({ service: Service, layer, deps: [EventV2.node, Location.node, Config.node] })
|
||||
export function configured(options?: ShellSelect.Options) {
|
||||
return makeLocationNode({ service: Service, layer: layer(options), deps: [EventV2.node, Location.node, Config.node] })
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Shell") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
export const layer = (options?: ShellSelect.Options) => Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
|
@ -168,7 +168,7 @@ export const layer = Layer.effect(
|
|||
const id = Shell.ID.ascending()
|
||||
const cwd = input.cwd ?? location.directory
|
||||
const configShell = Config.latest(yield* config.entries(), "shell")
|
||||
const shell = ShellSelect.preferred(configShell)
|
||||
const shell = ShellSelect.preferred(configShell, options)
|
||||
const args = ShellSelect.args(shell, input.command)
|
||||
const file = path.join(outputDir, `${id}.out`)
|
||||
const env = {
|
||||
|
|
@ -316,8 +316,12 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer,
|
||||
deps: [EventV2.node, Location.node, Config.node, Global.node, AppProcess.node],
|
||||
})
|
||||
export function configured(options?: ShellSelect.Options) {
|
||||
return makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer(options),
|
||||
deps: [EventV2.node, Location.node, Config.node, Global.node, AppProcess.node],
|
||||
})
|
||||
}
|
||||
|
||||
export const node = configured()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { spawn, type ChildProcess } from "child_process"
|
|||
import { readFile } from "fs/promises"
|
||||
import { statSync } from "fs"
|
||||
import { setTimeout } from "node:timers/promises"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { Schema } from "effect"
|
||||
import { FSUtil } from "../fs-util"
|
||||
import { which } from "../util/which"
|
||||
|
||||
|
|
@ -28,6 +28,11 @@ export type Item = {
|
|||
acceptable: boolean
|
||||
}
|
||||
|
||||
export const Options = Schema.Struct({
|
||||
gitbash: Schema.optional(Schema.String),
|
||||
})
|
||||
export type Options = typeof Options.Type
|
||||
|
||||
export async function killTree(proc: ChildProcess, opts?: { exited?: () => boolean }): Promise<void> {
|
||||
const pid = proc.pid
|
||||
if (!pid || opts?.exited?.()) return
|
||||
|
|
@ -63,14 +68,14 @@ function stat(file: string) {
|
|||
return statSync(file, { throwIfNoEntry: false }) ?? undefined
|
||||
}
|
||||
|
||||
function full(file: string) {
|
||||
function full(file: string, options?: Options) {
|
||||
if (process.platform !== "win32") return file
|
||||
const shell = FSUtil.windowsPath(file)
|
||||
if (path.win32.dirname(shell) !== ".") {
|
||||
if (shell.startsWith("/") && name(shell) === "bash") return gitbash() || shell
|
||||
if (shell.startsWith("/") && name(shell) === "bash") return gitbash(options) || shell
|
||||
return shell
|
||||
}
|
||||
if (name(shell) === "bash") return gitbash() || which(shell) || shell
|
||||
if (name(shell) === "bash") return gitbash(options) || which(shell) || shell
|
||||
return which(shell) || shell
|
||||
}
|
||||
|
||||
|
|
@ -86,8 +91,8 @@ function rooted(file: string) {
|
|||
return path.isAbsolute(FSUtil.windowsPath(file))
|
||||
}
|
||||
|
||||
function resolve(file: string) {
|
||||
const shell = full(file)
|
||||
function resolve(file: string, options?: Options) {
|
||||
const shell = full(file, options)
|
||||
if (rooted(shell)) {
|
||||
if (stat(shell)?.isFile()) return shell
|
||||
return
|
||||
|
|
@ -95,12 +100,12 @@ function resolve(file: string) {
|
|||
return which(shell) ?? undefined
|
||||
}
|
||||
|
||||
function win() {
|
||||
function win(options?: Options) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
[which("pwsh"), which("powershell"), gitbash(), process.env.COMSPEC || "cmd.exe"]
|
||||
[which("pwsh"), which("powershell"), gitbash(options), process.env.COMSPEC || "cmd.exe"]
|
||||
.filter((item): item is string => Boolean(item))
|
||||
.map(full),
|
||||
.map((file) => full(file, options)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -111,18 +116,18 @@ async function unix() {
|
|||
return ["/bin/bash", "/bin/zsh", "/bin/sh"]
|
||||
}
|
||||
|
||||
function select(file: string | undefined, opts?: { acceptable?: boolean }) {
|
||||
function select(file: string | undefined, options?: Options, opts?: { acceptable?: boolean }) {
|
||||
if (file && (!opts?.acceptable || ok(file))) {
|
||||
const shell = resolve(file)
|
||||
const shell = resolve(file, options)
|
||||
if (shell) return shell
|
||||
}
|
||||
if (process.platform === "win32") return win()[0]
|
||||
if (process.platform === "win32") return win(options)[0]
|
||||
return fallback()
|
||||
}
|
||||
|
||||
export function gitbash() {
|
||||
export function gitbash(options?: Options) {
|
||||
if (process.platform !== "win32") return
|
||||
if (Flag.OPENCODE_GIT_BASH_PATH) return Flag.OPENCODE_GIT_BASH_PATH
|
||||
if (options?.gitbash) return options.gitbash
|
||||
const git = which("git")
|
||||
if (!git) return
|
||||
const file = path.join(git, "..", "..", "bin", "bash.exe")
|
||||
|
|
@ -153,12 +158,12 @@ export function ps(file: string) {
|
|||
return meta(file)?.ps === true
|
||||
}
|
||||
|
||||
function info(file: string): Item {
|
||||
const item = full(file)
|
||||
function info(file: string, options?: Options): Item {
|
||||
const item = full(file, options)
|
||||
const n = name(item)
|
||||
return {
|
||||
path: item,
|
||||
name: resolve(n) ? n : item,
|
||||
name: resolve(n, options) ? n : item,
|
||||
acceptable: ok(item),
|
||||
}
|
||||
}
|
||||
|
|
@ -175,8 +180,9 @@ export function args(file: string, command: string) {
|
|||
let defaultPreferred: string | undefined
|
||||
let defaultAcceptable: string | undefined
|
||||
|
||||
export function preferred(configShell?: string) {
|
||||
if (configShell) return select(configShell)
|
||||
export function preferred(configShell?: string, options?: Options) {
|
||||
if (configShell) return select(configShell, options)
|
||||
if (options?.gitbash) return select(process.env.SHELL, options)
|
||||
defaultPreferred ??= select(process.env.SHELL)
|
||||
return defaultPreferred
|
||||
}
|
||||
|
|
@ -184,16 +190,17 @@ preferred.reset = () => {
|
|||
defaultPreferred = undefined
|
||||
}
|
||||
|
||||
export function acceptable(configShell?: string) {
|
||||
if (configShell) return select(configShell, { acceptable: true })
|
||||
defaultAcceptable ??= select(process.env.SHELL, { acceptable: true })
|
||||
export function acceptable(configShell?: string, options?: Options) {
|
||||
if (configShell) return select(configShell, options, { acceptable: true })
|
||||
if (options?.gitbash) return select(process.env.SHELL, options, { acceptable: true })
|
||||
defaultAcceptable ??= select(process.env.SHELL, undefined, { acceptable: true })
|
||||
return defaultAcceptable
|
||||
}
|
||||
acceptable.reset = () => {
|
||||
defaultAcceptable = undefined
|
||||
}
|
||||
|
||||
export async function list(): Promise<Item[]> {
|
||||
const shells = process.platform === "win32" ? win() : await unix()
|
||||
return shells.filter((s) => resolve(s)).map(info)
|
||||
export async function list(options?: Options): Promise<Item[]> {
|
||||
const shells = process.platform === "win32" ? win(options) : await unix()
|
||||
return shells.filter((shell) => resolve(shell, options)).map((shell) => info(shell, options))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function testLayer(
|
|||
),
|
||||
)
|
||||
return AppNodeBuilder.build(LayerNode.group([Config.node, EventV2.node]), [
|
||||
[Config.node, Config.layer(options)],
|
||||
[Config.node, Config.configured(options)],
|
||||
[Location.node, locationLayer],
|
||||
[Global.node, Global.layerWith({ config: globalDirectory, home: path.join(globalDirectory, "home") })],
|
||||
[Credential.node, credentialNode],
|
||||
|
|
|
|||
|
|
@ -81,17 +81,6 @@ describe("layer node", () => {
|
|||
expect(await Effect.runPromise(program)).toBe("hello simulation")
|
||||
})
|
||||
|
||||
test("preserves source dependencies when replacing a node with a layer", async () => {
|
||||
const replacement = Layer.effect(
|
||||
Greeting,
|
||||
Effect.map(Value, (item) => Greeting.of({ value: `replaced ${item.value}` })),
|
||||
)
|
||||
const program = Effect.map(Greeting, (item) => item.value).pipe(
|
||||
Effect.provide(build(LayerNode.group([greeting]), [[greeting, replacement]])),
|
||||
)
|
||||
expect(await Effect.runPromise(program)).toBe("replaced production")
|
||||
})
|
||||
|
||||
test("replaces every use of the same layer", async () => {
|
||||
const leftLayer = Layer.effect(
|
||||
Left,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const instructionLayer = (input: {
|
|||
project?: boolean
|
||||
}) =>
|
||||
AppNodeBuilder.build(InstructionDiscovery.node, [
|
||||
[InstructionDiscovery.node, InstructionDiscovery.layer({ project: input.project })],
|
||||
[InstructionDiscovery.node, InstructionDiscovery.configured({ project: input.project })],
|
||||
[Global.node, Global.layerWith({ config: input.config })],
|
||||
[Location.node, input.locationServiceLayer],
|
||||
...(input.filesystemLayer ? [[FSUtil.node, input.filesystemLayer] as const] : []),
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ const buildLayer = (state: Ref.Ref<MockState>, options: ModelsDev.Options = { fe
|
|||
// every test would reuse the cachedInvalidateWithTTL state from the first run.
|
||||
Layer.fresh(
|
||||
AppNodeBuilder.build(ModelsDev.node, [
|
||||
[ModelsDev.node, ModelsDev.layer(options)],
|
||||
[ModelsDev.node, ModelsDev.configured(options)],
|
||||
[LayerNodePlatform.httpClient, Layer.succeed(HttpClient.HttpClient, makeMockClient(state))],
|
||||
]),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.no
|
|||
])
|
||||
const it = testEffect(layer)
|
||||
const models = (file: string) =>
|
||||
AppNodeBuilder.build(ModelsDev.node, [[ModelsDev.node, ModelsDev.layer({ file, fetch: false })]])
|
||||
AppNodeBuilder.build(ModelsDev.node, [[ModelsDev.node, ModelsDev.configured({ file, fetch: false })]])
|
||||
|
||||
describe("ModelsDevPlugin", () => {
|
||||
it.effect("projects normalized models.dev snapshots into the catalog", () =>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ export const ServerOptions = Schema.Struct({
|
|||
project: Schema.optional(Schema.Boolean),
|
||||
}),
|
||||
),
|
||||
windows: Schema.optional(
|
||||
Schema.Struct({
|
||||
gitbash: Schema.optional(Schema.String),
|
||||
}),
|
||||
),
|
||||
fs: Schema.optional(
|
||||
Schema.Struct({
|
||||
filewatcher: Schema.optional(Schema.Boolean),
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ import { FileSystemSearch } from "@opencode-ai/core/filesystem/search"
|
|||
import { Observability } from "@opencode-ai/core/observability"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { CommandV2 } from "@opencode-ai/core/command"
|
||||
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||
import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
||||
import { Pty } from "@opencode-ai/core/pty"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { Job } from "@opencode-ai/core/job"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { InstructionDiscovery } from "@opencode-ai/core/instruction-discovery"
|
||||
|
|
@ -79,13 +82,16 @@ function makeRoutes<AuthError, AuthServices>(
|
|||
) {
|
||||
const pluginRuntimeCell = PluginRuntime.makeCell()
|
||||
const replacements: LayerNode.Replacements = [
|
||||
[Database.node, Database.layer(options.database)],
|
||||
[ModelsDev.node, ModelsDev.layer(options.models)],
|
||||
[Watcher.node, Watcher.layer({ enabled: options.fs?.filewatcher })],
|
||||
[FileSystemSearch.node, FileSystemSearch.layer({ fff: options.fs?.fff })],
|
||||
[Database.node, Database.configured(options.database)],
|
||||
[ModelsDev.node, ModelsDev.configured(options.models)],
|
||||
[Watcher.node, Watcher.configured({ enabled: options.fs?.filewatcher })],
|
||||
[FileSystemSearch.node, FileSystemSearch.configured({ fff: options.fs?.fff })],
|
||||
[Global.node, Global.layerWith(options.config?.directory ? { config: options.config.directory } : {})],
|
||||
[Config.node, Config.layer({ project: options.config?.project })],
|
||||
[InstructionDiscovery.node, InstructionDiscovery.layer({ project: options.config?.project })],
|
||||
[Config.node, Config.configured({ project: options.config?.project })],
|
||||
[InstructionDiscovery.node, InstructionDiscovery.configured({ project: options.config?.project })],
|
||||
[CommandV2.node, CommandV2.configured({ gitbash: options.windows?.gitbash })],
|
||||
[Pty.node, Pty.configured({ gitbash: options.windows?.gitbash })],
|
||||
[Shell.node, Shell.configured({ gitbash: options.windows?.gitbash })],
|
||||
[PluginRuntime.node, PluginRuntime.layerWithCell(pluginRuntimeCell)],
|
||||
[PluginRuntime.providerNode, PluginRuntime.providerNodeWithCell(pluginRuntimeCell)],
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue