refactor(core): consolidate filesystem services (#30447)

This commit is contained in:
Dax
2026-06-02 16:09:26 -04:00
committed by GitHub
parent b93963e462
commit 604a5f781f
153 changed files with 2553 additions and 4312 deletions

View File

@@ -4,7 +4,7 @@ import * as fs from "fs/promises"
import { Cause, Effect, Exit, Layer } from "effect"
import { ApplyPatchTool } from "../../src/tool/apply_patch"
import { LSP } from "@/lsp/lsp"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Format } from "../../src/format"
import { Agent } from "../../src/agent/agent"
import { EventV2Bridge } from "../../src/event-v2-bridge"
@@ -16,7 +16,7 @@ import { testEffect } from "../lib/effect"
const it = testEffect(
Layer.mergeAll(
LSP.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
Format.defaultLayer,
EventV2Bridge.defaultLayer,
Truncate.defaultLayer,

View File

@@ -5,7 +5,7 @@ import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
import { EditTool } from "../../src/tool/edit"
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
import { LSP } from "@/lsp/lsp"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Format } from "../../src/format"
import { Agent } from "../../src/agent/agent"
import { EventV2Bridge } from "../../src/event-v2-bridge"
@@ -13,7 +13,7 @@ import { Truncate } from "@/tool/truncate"
import { SessionID, MessageID } from "../../src/session/schema"
import * as Tool from "../../src/tool/tool"
import { testEffect } from "../lib/effect"
import { FileWatcher } from "../../src/file/watcher"
import { Watcher } from "@opencode-ai/core/filesystem/watcher"
const ctx = {
sessionID: SessionID.make("ses_test-edit-session"),
@@ -32,7 +32,7 @@ afterEach(async () => {
const layer = Layer.mergeAll(
LSP.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
Format.defaultLayer,
EventV2Bridge.defaultLayer,
Truncate.defaultLayer,
@@ -64,12 +64,12 @@ const fail = Effect.fn("EditToolTest.fail")(function* (args: Tool.InferParameter
})
const put = Effect.fn("EditToolTest.put")(function* (p: string, content: string) {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
yield* fs.writeWithDirs(p, content)
})
const load = Effect.fn("EditToolTest.load")(function* (p: string) {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
return yield* fs.readFileString(p)
})
@@ -78,11 +78,11 @@ const loadRaw = Effect.fn("EditToolTest.loadRaw")(function* (p: string) {
})
const makeDirectory = Effect.fn("EditToolTest.makeDirectory")(function* (p: string) {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
yield* fs.makeDirectory(p)
})
const onceBus = Effect.fn("EditToolTest.onceBus")(function* (def: typeof FileWatcher.Event.Updated) {
const onceBus = Effect.fn("EditToolTest.onceBus")(function* (def: typeof Watcher.Event.Updated) {
const events = yield* EventV2Bridge.Service
const deferred = yield* Deferred.make<void>()
const unsub = yield* events.listen((event) => {
@@ -138,7 +138,7 @@ describe("tool.edit", () => {
it.instance("emits add event for new files", () =>
Effect.gen(function* () {
const test = yield* TestInstance
const updated = yield* onceBus(FileWatcher.Event.Updated)
const updated = yield* onceBus(Watcher.Event.Updated)
yield* run({ filePath: path.join(test.directory, "new.txt"), oldString: "", newString: "content" })
yield* Deferred.await(updated)
@@ -230,7 +230,7 @@ describe("tool.edit", () => {
const test = yield* TestInstance
const filepath = path.join(test.directory, "file.txt")
yield* put(filepath, "original")
const updated = yield* onceBus(FileWatcher.Event.Updated)
const updated = yield* onceBus(Watcher.Event.Updated)
yield* run({ filePath: filepath, oldString: "original", newString: "modified" })
yield* Deferred.await(updated)

View File

@@ -5,8 +5,8 @@ import { Cause, Effect, Exit, Layer } from "effect"
import { GlobTool } from "../../src/tool/glob"
import { SessionID, MessageID } from "../../src/session/schema"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Ripgrep } from "../../src/file/ripgrep"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Global } from "@opencode-ai/core/global"
import { Truncate } from "@/tool/truncate"
import { Agent } from "../../src/agent/agent"
@@ -30,7 +30,7 @@ const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
Layer.mergeAll(
CrossSpawnSpawner.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
Ripgrep.defaultLayer,
Truncate.defaultLayer,
Agent.defaultLayer,
@@ -148,7 +148,7 @@ describe("tool.glob", () => {
() =>
Effect.gen(function* () {
yield* TestInstance
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const cache = path.join(Global.Path.repos, "github.com", "opencode-glob-reference", "repo")
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))

View File

@@ -11,8 +11,8 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Global } from "@opencode-ai/core/global"
import { Truncate } from "@/tool/truncate"
import { Agent } from "../../src/agent/agent"
import { Ripgrep } from "../../src/file/ripgrep"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { testEffect } from "../lib/effect"
import { Reference } from "@/reference/reference"
import { RepositoryCache } from "@/reference/repository-cache"
@@ -33,7 +33,7 @@ const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
Layer.mergeAll(
CrossSpawnSpawner.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
Ripgrep.defaultLayer,
Truncate.defaultLayer,
Agent.defaultLayer,
@@ -220,7 +220,7 @@ describe("tool.grep", () => {
() =>
Effect.gen(function* () {
yield* TestInstance
const appfs = yield* AppFileSystem.Service
const appfs = yield* FSUtil.Service
const cache = path.join(Global.Path.repos, "github.com", "opencode-grep-reference", "repo")
yield* appfs.remove(cache, { recursive: true }).pipe(Effect.ignore)
yield* Effect.addFinalizer(() => appfs.remove(cache, { recursive: true }).pipe(Effect.ignore))

View File

@@ -4,7 +4,7 @@ import { Effect, Layer } from "effect"
import path from "path"
import { Agent } from "../../src/agent/agent"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { LSP } from "@/lsp/lsp"
import { Permission } from "../../src/permission"
import { MessageID, SessionID } from "../../src/session/schema"
@@ -56,13 +56,7 @@ const lsp = Layer.succeed(
)
const it = testEffect(
Layer.mergeAll(
Agent.defaultLayer,
AppFileSystem.defaultLayer,
CrossSpawnSpawner.defaultLayer,
Truncate.defaultLayer,
lsp,
),
Layer.mergeAll(Agent.defaultLayer, FSUtil.defaultLayer, CrossSpawnSpawner.defaultLayer, Truncate.defaultLayer, lsp),
)
const init = Effect.fn("LspToolTest.init")(function* () {
@@ -79,7 +73,7 @@ const run = Effect.fn("LspToolTest.run")(function* (
})
const put = Effect.fn("LspToolTest.put")(function* (file: string) {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
yield* fs.writeWithDirs(file, "export const x = 1\n")
})

View File

@@ -4,7 +4,7 @@ import { Cause, Effect, Exit, Layer, Stream } from "effect"
import path from "path"
import { Agent } from "../../src/agent/agent"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Global } from "@opencode-ai/core/global"
import { Config } from "@/config/config"
import { RuntimeFlags } from "@/effect/runtime-flags"
@@ -54,7 +54,7 @@ const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
Layer.mergeAll(
Agent.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
CrossSpawnSpawner.defaultLayer,
Instruction.defaultLayer,
LSP.defaultLayer,
@@ -133,11 +133,11 @@ const git = Effect.fn("ReadToolTest.git")(function* (cwd: string, args: string[]
})
})
const put = Effect.fn("ReadToolTest.put")(function* (p: string, content: string | Buffer | Uint8Array) {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
yield* fs.writeWithDirs(p, content)
})
const load = Effect.fn("ReadToolTest.load")(function* (p: string) {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
return yield* fs.readFileString(p)
})
const asks = () => {
@@ -266,7 +266,7 @@ describe("tool.read external_directory permission", () => {
references.live("does not ask for external_directory permission when reading configured references", () =>
Effect.gen(function* () {
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
@@ -379,12 +379,12 @@ describe("tool.read truncation", () => {
const content = `${"x".repeat(80)}\n`.repeat(50_000)
yield* put(filepath, content)
const fs = yield* AppFileSystem.Service
const fs = yield* FSUtil.Service
const counter = { bytes: 0 }
const result = yield* run({ filePath: filepath }).pipe(
Effect.provideService(
AppFileSystem.Service,
AppFileSystem.Service.of({
FSUtil.Service,
FSUtil.Service.of({
...fs,
stream: (file, options) =>
fs.stream(file, options).pipe(

View File

@@ -10,7 +10,7 @@ import { Tool } from "@/tool/tool"
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { TestConfig } from "../fixture/config"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Plugin } from "@/plugin"
import { Question } from "@/question"
import { Todo } from "@/session/todo"
@@ -26,7 +26,7 @@ import { Instruction } from "@/session/instruction"
import { EventV2Bridge } from "@/event-v2-bridge"
import { FetchHttpClient } from "effect/unstable/http"
import { Format } from "@/format"
import { Ripgrep } from "@/file/ripgrep"
import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
import * as Truncate from "@/tool/truncate"
import { InstanceState } from "@/effect/instance-state"
import { Reference } from "@/reference/reference"
@@ -63,7 +63,7 @@ const registryLayer = (opts: RegistryLayerOptions = {}) =>
Layer.provide(Reference.defaultLayer),
Layer.provide(LSP.defaultLayer),
Layer.provide(Instruction.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(FSUtil.defaultLayer),
Layer.provide(EventV2Bridge.defaultLayer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(Format.defaultLayer),

View File

@@ -14,7 +14,7 @@ import { Agent } from "../../src/agent/agent"
import { Truncate } from "@/tool/truncate"
import { SessionID, MessageID } from "../../src/session/schema"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Plugin } from "../../src/plugin"
import { testEffect } from "../lib/effect"
import { Tool } from "@/tool/tool"
@@ -23,7 +23,7 @@ import { InstanceStore } from "@/project/instance-store"
const shellLayer = Layer.mergeAll(
CrossSpawnSpawner.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
Plugin.defaultLayer,
Truncate.defaultLayer,
Config.defaultLayer,
@@ -1223,7 +1223,7 @@ describe("tool.shell truncation", () => {
const filepath = (result.metadata as { outputPath?: string }).outputPath
expect(filepath).toBeTruthy()
const saved = yield* (yield* AppFileSystem.Service).readFileString(filepath!)
const saved = yield* (yield* FSUtil.Service).readFileString(filepath!)
const lines = saved.trim().split(/\r?\n/)
expect(lines.length).toBe(lineCount)
expect(lines[0]).toBe("1")

View File

@@ -1,6 +1,6 @@
import { describe, test, expect } from "bun:test"
import { NodeFileSystem } from "@effect/platform-node"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Effect, FileSystem, Layer } from "effect"
import { Truncate } from "@/tool/truncate"
import { Config } from "@/config/config"
@@ -14,13 +14,13 @@ import { TestConfig } from "../fixture/config"
const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
const ROOT = path.resolve(import.meta.dir, "..", "..")
const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer, AppFileSystem.defaultLayer))
const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer, FSUtil.defaultLayer))
const configuredLayer = (cfg: Config.Info) =>
Layer.mergeAll(
Truncate.defaultLayer,
NodeFileSystem.layer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
TestConfig.layer({ get: () => Effect.succeed(cfg) }),
)
const configuredIt = (cfg: Config.Info) => testEffect(configuredLayer(cfg))
@@ -30,7 +30,7 @@ describe("Truncate", () => {
it.live("truncates large json file by bytes", () =>
Effect.gen(function* () {
const svc = yield* Truncate.Service
const fsys = yield* AppFileSystem.Service
const fsys = yield* FSUtil.Service
const content = yield* fsys.readFileString(path.join(FIXTURES_DIR, "models-api.json"))
const result = yield* svc.output(content)
@@ -164,7 +164,7 @@ describe("Truncate", () => {
it.live("large single-line file truncates with byte message", () =>
Effect.gen(function* () {
const svc = yield* Truncate.Service
const fsys = yield* AppFileSystem.Service
const fsys = yield* FSUtil.Service
const content = yield* fsys.readFileString(path.join(FIXTURES_DIR, "models-api.json"))
const result = yield* svc.output(content)
@@ -187,7 +187,7 @@ describe("Truncate", () => {
expect(result.outputPath).toBeDefined()
expect(result.outputPath).toContain("tool_")
const fsys = yield* AppFileSystem.Service
const fsys = yield* FSUtil.Service
const written = yield* fsys.readFileString(result.outputPath!)
expect(written).toBe(lines)
}),

View File

@@ -4,7 +4,7 @@ import path from "path"
import fs from "fs/promises"
import { WriteTool } from "../../src/tool/write"
import { LSP } from "@/lsp/lsp"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { EventV2Bridge } from "../../src/event-v2-bridge"
import { Format } from "../../src/format"
import { Truncate } from "@/tool/truncate"
@@ -33,7 +33,7 @@ afterEach(async () => {
const it = testEffect(
Layer.mergeAll(
LSP.defaultLayer,
AppFileSystem.defaultLayer,
FSUtil.defaultLayer,
EventV2Bridge.defaultLayer,
Format.defaultLayer,
CrossSpawnSpawner.defaultLayer,