From 407ac2d8aa181ee5f18995a556122bc1a1f279d2 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 20 Jul 2026 21:21:36 -0400 Subject: [PATCH] refactor(server): migrate host options --- packages/cli/src/server-process.ts | 3 + packages/core/src/command.ts | 21 ++++--- packages/core/src/config.ts | 14 +++-- packages/core/src/database/database.ts | 10 ++-- packages/core/src/effect/layer-node.ts | 21 ++++--- packages/core/src/filesystem/search.ts | 10 ++-- packages/core/src/filesystem/watcher.ts | 6 +- packages/core/src/flag/flag.ts | 11 ++++ packages/core/src/instruction-discovery.ts | 14 +++-- packages/core/src/models-dev.ts | 6 +- packages/core/src/pty.ts | 10 +++- packages/core/src/shell.ts | 18 +++--- packages/core/src/shell/select.ts | 57 +++++++++++-------- packages/core/test/config/config.test.ts | 2 +- .../test/effect/layer-node/layer-node.test.ts | 11 ---- .../core/test/instruction-discovery.test.ts | 2 +- packages/core/test/models.test.ts | 2 +- packages/core/test/plugin/models-dev.test.ts | 2 +- packages/server/src/options.ts | 5 ++ packages/server/src/routes.ts | 18 ++++-- 20 files changed, 149 insertions(+), 94 deletions(-) diff --git a/packages/cli/src/server-process.ts b/packages/cli/src/server-process.ts index e9d1f27c1b..f0629f12b0 100644 --- a/packages/cli/src/server-process.ts +++ b/packages/cli/src/server-process.ts @@ -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, diff --git a/packages/core/src/command.ts b/packages/core/src/command.ts index ff3ffe353e..4b23b0b3fb 100644 --- a/packages/core/src/command.ts +++ b/packages/core/src/command.ts @@ -52,7 +52,7 @@ export interface Interface extends State.Transformable { export class Service extends Context.Service()("@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() diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 2d70a31973..5d7c2c5210 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -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() diff --git a/packages/core/src/database/database.ts b/packages/core/src/database/database.ts index d6cb3069d4..acfd7155eb 100644 --- a/packages/core/src/database/database.ts +++ b/packages/core/src/database/database.ts @@ -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:" }) diff --git a/packages/core/src/effect/layer-node.ts b/packages/core/src/effect/layer-node.ts index 6ab6c22a7b..dc7aa972f0 100644 --- a/packages/core/src/effect/layer-node.ts +++ b/packages/core/src/effect/layer-node.ts @@ -121,7 +121,7 @@ type CheckReplacementErrors = [Exclude = Item extends readonly [Node, infer Replacement] ? Replacement extends Node, infer E2, T> ? CheckReplacementErrors> - : Replacement extends Layer.Layer, infer E2, infer _R> + : Replacement extends Layer.Layer, infer E2, never> ? CheckReplacementErrors> : { readonly "Invalid replacement": Replacement } : { readonly "Invalid replacement": Item } @@ -133,16 +133,14 @@ type CheckReplacements = { type ValidReplacements = Items & CheckReplacements 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, + 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 } diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index 6a1e36ea14..59e2bf3216 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -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() diff --git a/packages/core/src/filesystem/watcher.ts b/packages/core/src/filesystem/watcher.ts index cea9021403..7da5fbeb0d 100644 --- a/packages/core/src/filesystem/watcher.ts +++ b/packages/core/src/filesystem/watcher.ts @@ -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, diff --git a/packages/core/src/flag/flag.ts b/packages/core/src/flag/flag.ts index 1c975040dc..b60aa8a6cb 100644 --- a/packages/core/src/flag/flag.ts +++ b/packages/core/src/flag/flag.ts @@ -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"] }, diff --git a/packages/core/src/instruction-discovery.ts b/packages/core/src/instruction-discovery.ts index 3a4e9ed394..6845eca07b 100644 --- a/packages/core/src/instruction-discovery.ts +++ b/packages/core/src/instruction-discovery.ts @@ -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) { return files.map((file) => `Instructions from: ${file.path}\n${file.content}`).join("\n\n") diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index 670face8b0..857f28d945 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -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" diff --git a/packages/core/src/pty.ts b/packages/core/src/pty.ts index 4111aaa31a..4ccfdf1820 100644 --- a/packages/core/src/pty.ts +++ b/packages/core/src/pty.ts @@ -89,7 +89,7 @@ export interface Interface { export class Service extends Context.Service()("@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() diff --git a/packages/core/src/shell.ts b/packages/core/src/shell.ts index a62fef8107..9835d27a51 100644 --- a/packages/core/src/shell.ts +++ b/packages/core/src/shell.ts @@ -59,7 +59,7 @@ export interface Interface { export class Service extends Context.Service()("@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() diff --git a/packages/core/src/shell/select.ts b/packages/core/src/shell/select.ts index 311fb7c9a1..f9c81c5325 100644 --- a/packages/core/src/shell/select.ts +++ b/packages/core/src/shell/select.ts @@ -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 { 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 { - const shells = process.platform === "win32" ? win() : await unix() - return shells.filter((s) => resolve(s)).map(info) +export async function list(options?: Options): Promise { + const shells = process.platform === "win32" ? win(options) : await unix() + return shells.filter((shell) => resolve(shell, options)).map((shell) => info(shell, options)) } diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 86b88e1426..77ed9bbb73 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -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], diff --git a/packages/core/test/effect/layer-node/layer-node.test.ts b/packages/core/test/effect/layer-node/layer-node.test.ts index b6d94cfd56..b671792c59 100644 --- a/packages/core/test/effect/layer-node/layer-node.test.ts +++ b/packages/core/test/effect/layer-node/layer-node.test.ts @@ -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, diff --git a/packages/core/test/instruction-discovery.test.ts b/packages/core/test/instruction-discovery.test.ts index 41f7d5a3ac..71d753681d 100644 --- a/packages/core/test/instruction-discovery.test.ts +++ b/packages/core/test/instruction-discovery.test.ts @@ -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] : []), diff --git a/packages/core/test/models.test.ts b/packages/core/test/models.test.ts index 48ae970fe3..0060281f37 100644 --- a/packages/core/test/models.test.ts +++ b/packages/core/test/models.test.ts @@ -161,7 +161,7 @@ const buildLayer = (state: Ref.Ref, 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))], ]), ) diff --git a/packages/core/test/plugin/models-dev.test.ts b/packages/core/test/plugin/models-dev.test.ts index 4ae46a7236..2aa5d457e2 100644 --- a/packages/core/test/plugin/models-dev.test.ts +++ b/packages/core/test/plugin/models-dev.test.ts @@ -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", () => diff --git a/packages/server/src/options.ts b/packages/server/src/options.ts index 7ab33da648..834a297749 100644 --- a/packages/server/src/options.ts +++ b/packages/server/src/options.ts @@ -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), diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index f57d869a65..cfcb14c95a 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -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( ) { 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)], ]