diff --git a/packages/opencode/src/reference/reference.ts b/packages/opencode/src/reference/reference.ts index c305632b6b..ac91d6f735 100644 --- a/packages/opencode/src/reference/reference.ts +++ b/packages/opencode/src/reference/reference.ts @@ -6,7 +6,6 @@ import { Config } from "@/config/config" import { ConfigReference } from "@/config/reference" import { InstanceState } from "@/effect/instance-state" import { RuntimeFlags } from "@/effect/runtime-flags" -import { Git } from "@/git" import { parseRepositoryReference, repositoryCachePath, type RemoteReference } from "@/util/repository" import { RepositoryCache } from "./repository-cache" @@ -130,8 +129,7 @@ export const layer = Layer.effect( Service, Effect.gen(function* () { const config = yield* Config.Service - const fs = yield* AppFileSystem.Service - const git = yield* Git.Service + const cache = yield* RepositoryCache.Service const scope = yield* Scope.Scope const flags = yield* RuntimeFlags.Service @@ -154,10 +152,7 @@ export const layer = Layer.effect( gitReferences, Effect.fnUntraced(function* (reference) { const run = yield* Effect.cached( - RepositoryCache.ensure( - { reference: reference.reference, branch: reference.branch, refresh: true }, - { fs, git }, - ).pipe( + cache.ensure({ reference: reference.reference, branch: reference.branch, refresh: true }).pipe( Effect.asVoid, Effect.catchCause((cause) => Effect.logWarning("failed to materialize reference repository").pipe( @@ -223,8 +218,7 @@ export const layer = Layer.effect( export const defaultLayer = layer.pipe( Layer.provide(Config.defaultLayer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer), ) diff --git a/packages/opencode/src/reference/repository-cache.ts b/packages/opencode/src/reference/repository-cache.ts index a873990a30..1f3ec7bf68 100644 --- a/packages/opencode/src/reference/repository-cache.ts +++ b/packages/opencode/src/reference/repository-cache.ts @@ -1,5 +1,5 @@ import path from "path" -import { Effect } from "effect" +import { Context, Effect, Layer } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Flock } from "@opencode-ai/core/util/flock" import { Git } from "@/git" @@ -21,6 +21,18 @@ export type Result = { branch?: string } +export type EnsureInput = { + reference: RemoteReference + refresh?: boolean + branch?: string +} + +export interface Interface { + ensure: (input: EnsureInput) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/RepositoryCache") {} + function statusForRepository(input: { reuse: boolean; refresh?: boolean; branchMatches?: boolean }) { if (!input.reuse) return "cloned" as const if (input.branchMatches === false) return "refreshed" as const @@ -43,12 +55,8 @@ function resetTarget(input: { return "HEAD" } -export const ensure = Effect.fn("RepositoryCache.ensure")(function* ( - input: { - reference: RemoteReference - refresh?: boolean - branch?: string - }, +const ensureWithServices = Effect.fn("RepositoryCache.ensureWithServices")(function* ( + input: EnsureInput, services: { fs: AppFileSystem.Interface git: Git.Interface @@ -144,4 +152,28 @@ export const ensure = Effect.fn("RepositoryCache.ensure")(function* ( ) }) +export const layer: Layer.Layer = Layer.effect( + Service, + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const git = yield* Git.Service + + return Service.of({ + ensure: Effect.fn("RepositoryCache.ensure")(function* (input) { + return yield* ensureWithServices(input, { fs, git }) + }), + }) + }), +) + +export const defaultLayer: Layer.Layer = layer.pipe( + Layer.provide(AppFileSystem.defaultLayer), + Layer.provide(Git.defaultLayer), +) + +export const ensure = Effect.fn("RepositoryCache.ensure")(function* (input: EnsureInput) { + const cache = yield* Service + return yield* cache.ensure(input) +}) + export * as RepositoryCache from "./repository-cache" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 879855c366..fc24aa35e7 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -25,6 +25,7 @@ import { ProviderID, type ModelID } from "../provider/schema" import { WebSearchTool } from "./websearch" import { RepoCloneTool } from "./repo_clone" import { RepoOverviewTool } from "./repo_overview" +import { RepositoryCache } from "@/reference/repository-cache" import * as Log from "@opencode-ai/core/util/log" import { LspTool } from "./lsp" import * as Truncate from "./truncate" @@ -93,6 +94,7 @@ export const layer: Layer.Layer< | BackgroundJob.Service | Provider.Service | Git.Service + | RepositoryCache.Service | Reference.Service | LSP.Service | Instruction.Service @@ -378,7 +380,7 @@ export const defaultLayer = Layer.suspend(() => Layer.provide(Session.defaultLayer), Layer.provide(Layer.mergeAll(SessionStatus.defaultLayer, BackgroundJob.defaultLayer)), Layer.provide(Provider.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(Layer.mergeAll(Git.defaultLayer, RepositoryCache.defaultLayer)), Layer.provide(Reference.defaultLayer), Layer.provide(LSP.defaultLayer), Layer.provide(Instruction.defaultLayer), diff --git a/packages/opencode/src/tool/repo_clone.ts b/packages/opencode/src/tool/repo_clone.ts index 2b5e41844e..8515f27661 100644 --- a/packages/opencode/src/tool/repo_clone.ts +++ b/packages/opencode/src/tool/repo_clone.ts @@ -1,6 +1,4 @@ import { Effect, Schema } from "effect" -import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { Git } from "@/git" import DESCRIPTION from "./repo_clone.txt" import * as Tool from "./tool" import { parseRemoteRepositoryReference, repositoryCachePath, validateRepositoryBranch } from "@/util/repository" @@ -28,11 +26,10 @@ type Metadata = { branch?: string } -export const RepoCloneTool = Tool.define( +export const RepoCloneTool = Tool.define( "repo_clone", Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const git = yield* Git.Service + const cache = yield* RepositoryCache.Service return { description: DESCRIPTION, @@ -59,10 +56,7 @@ export const RepoCloneTool = Tool.define { const referenceLayer = (flags: Partial = {}) => Reference.layer.pipe( Layer.provide(Config.defaultLayer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(RuntimeFlags.layer(flags)), ) diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 891efc1872..b6a4f8a344 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -48,6 +48,7 @@ import * as Database from "../../src/storage/db" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" import { Reference } from "../../src/reference/reference" +import { RepositoryCache } from "../../src/reference/repository-cache" import { TestInstance } from "../fixture/fixture" import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect" import { reply, TestLLMServer } from "../lib/llm-server" @@ -189,6 +190,7 @@ function makeHttp(input?: { processor?: "blocking" }) { Layer.provide(Skill.defaultLayer), Layer.provide(FetchHttpClient.layer), Layer.provide(CrossSpawnSpawner.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(Git.defaultLayer), Layer.provide(Reference.defaultLayer), Layer.provide(Ripgrep.defaultLayer), diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 664b02a6cc..89ed11613e 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -59,6 +59,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" import { Reference } from "../../src/reference/reference" +import { RepositoryCache } from "../../src/reference/repository-cache" import { SyncEvent } from "@/sync" import { RuntimeFlags } from "@/effect/runtime-flags" import { EventV2Bridge } from "@/event-v2-bridge" @@ -138,6 +139,7 @@ function makeHttp() { Layer.provide(Skill.defaultLayer), Layer.provide(FetchHttpClient.layer), Layer.provide(CrossSpawnSpawner.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(Git.defaultLayer), Layer.provide(Reference.defaultLayer), Layer.provide(Ripgrep.defaultLayer), diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 8542d61f31..bfe9b75d48 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -12,6 +12,7 @@ import { Agent } from "../../src/agent/agent" import { TestInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { Reference } from "@/reference/reference" +import { RepositoryCache } from "@/reference/repository-cache" import { Config } from "@/config/config" import { RuntimeFlags } from "@/effect/runtime-flags" import { Git } from "@/git" @@ -21,8 +22,7 @@ import type * as Tool from "../../src/tool/tool" const referenceLayer = (flags: Partial = {}) => Reference.layer.pipe( Layer.provide(Config.defaultLayer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(RuntimeFlags.layer(flags)), ) diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 469f34c64a..027d5201cb 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -14,6 +14,7 @@ import { Ripgrep } from "../../src/file/ripgrep" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { testEffect } from "../lib/effect" import { Reference } from "@/reference/reference" +import { RepositoryCache } from "@/reference/repository-cache" import { Permission } from "../../src/permission" import type * as Tool from "../../src/tool/tool" import { Config } from "@/config/config" @@ -24,8 +25,7 @@ import { Filesystem } from "@/util/filesystem" const referenceLayer = (flags: Partial = {}) => Reference.layer.pipe( Layer.provide(Config.defaultLayer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(RuntimeFlags.layer(flags)), ) diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 1eba44c6a1..159a25697f 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -19,6 +19,7 @@ import { Filesystem } from "@/util/filesystem" import { disposeAllInstances, provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { Reference } from "@/reference/reference" +import { RepositoryCache } from "@/reference/repository-cache" const FIXTURES_DIR = path.join(import.meta.dir, "fixtures") @@ -40,8 +41,7 @@ const ctx = { const referenceLayer = (flags: Partial = {}) => Reference.layer.pipe( Layer.provide(Config.defaultLayer), - Layer.provide(AppFileSystem.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(RepositoryCache.defaultLayer), Layer.provide(RuntimeFlags.layer(flags)), ) diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index c426704295..261c469372 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -29,6 +29,7 @@ import { Ripgrep } from "@/file/ripgrep" import * as Truncate from "@/tool/truncate" import { InstanceState } from "@/effect/instance-state" import { Reference } from "@/reference/reference" +import { RepositoryCache } from "@/reference/repository-cache" import { ProviderID, ModelID } from "@/provider/schema" import { ToolJsonSchema } from "@/tool/json-schema" import { MessageID, SessionID } from "@/session/schema" @@ -51,7 +52,7 @@ const registryLayer = (flags: Partial = {}) => Layer.provide(Session.defaultLayer), Layer.provide(Layer.mergeAll(SessionStatus.defaultLayer, BackgroundJob.defaultLayer)), Layer.provide(Provider.defaultLayer), - Layer.provide(Git.defaultLayer), + Layer.provide(Layer.mergeAll(Git.defaultLayer, RepositoryCache.defaultLayer)), Layer.provide(Reference.defaultLayer), Layer.provide(LSP.defaultLayer), Layer.provide(Instruction.defaultLayer), diff --git a/packages/opencode/test/tool/repo_clone.test.ts b/packages/opencode/test/tool/repo_clone.test.ts index 1ac913328d..cba0885477 100644 --- a/packages/opencode/test/tool/repo_clone.test.ts +++ b/packages/opencode/test/tool/repo_clone.test.ts @@ -10,6 +10,7 @@ import { Global } from "@opencode-ai/core/global" import { MessageID, SessionID } from "../../src/session/schema" import { Truncate } from "../../src/tool/truncate" import { RepoCloneTool } from "../../src/tool/repo_clone" +import { RepositoryCache } from "../../src/reference/repository-cache" import { disposeAllInstances, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" @@ -34,6 +35,7 @@ const it = testEffect( AppFileSystem.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, + RepositoryCache.defaultLayer, Truncate.defaultLayer, ), )