openAgent
gitlabThe open source UNIVERSAL agent.
clone
https://gitlab.com/bitzzdev/openAgent.gitAIagentautomationopen-sourceuniversal-agent
export * as FileSystemSearch from "./search"import { makeLocationNode } from "../effect/app-node"import path from "path"import { Context, Effect, Layer, Scope } from "effect"import { Fff } from "#fff"import fuzzysort from "fuzzysort"import { FileSystem } from "../filesystem"import { FSUtil } from "../fs-util"import { Location } from "../location"import { Ripgrep } from "../ripgrep"import { RelativePath } from "../schema"import { Flag } from "../flag/flag"export interface Interface {readonly find: (input: FileSystem.FindInput) => Effect.Effect<FileSystem.Entry[]>readonly glob: (input: FileSystem.GlobInput) => Effect.Effect<readonly FileSystem.Entry[]>readonly grep: (input: FileSystem.GrepInput) => Effect.Effect<readonly FileSystem.Match[]>}export class Service extends Context.Service<Service, Interface>()("@opencode/v2/FileSystem/Search") {}export const ripgrepLayer = Layer.effect(Service,Effect.gen(function* () {const fs = yield* FSUtil.Serviceconst location = yield* Location.Serviceconst ripgrep = yield* Ripgrep.Serviceconst scope = yield* Scope.Scopeconst state = {files: [] as string[],directories: [] as string[],}const directories = new Set<string>()yield* ripgrep.find({cwd: location.directory,pattern: "*",limit: location.vcs ? Number.MAX_SAFE_INTEGER : 100_000,onEntry: (entry) =>Effect.sync(() => {state.files.push(entry.path)const parts = entry.path.split("/")parts.slice(0, -1).forEach((_, index) => directories.add(parts.slice(0, index + 1).join("/") + path.sep))state.directories = Array.from(directories)}),}).pipe(Effect.orDie, Effect.asVoid, Effect.forkIn(scope))return Service.of({glob: (input) =>Effect.gen(function* () {const target = path.resolve(location.directory, input.path ?? ".")const info = yield* fs.stat(target).pipe(Effect.orDie)const cwd = info.type === "File" ? path.dirname(target) : targetreturn yield* ripgrep.glob({cwd,pattern: input.pattern,limit: input.limit ?? Number.MAX_SAFE_INTEGER,}).pipe(Effect.map((result) =>result.map((entry) =>FileSystem.Entry.make({...entry,path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),}),),),Effect.orDie,)}),grep: (input) =>Effect.gen(function* () {const target = path.resolve(location.directory, input.path ?? ".")const info = yield* fs.stat(target).pipe(Effect.orDie)const cwd = info.type === "File" ? path.dirname(target) : targetreturn yield* ripgrep.grep({cwd,pattern: input.pattern,file: info.type === "File" ? path.basename(target) : undefined,include: input.include,limit: input.limit ?? Number.MAX_SAFE_INTEGER,}).pipe(Effect.map((result) =>result.map((match) =>FileSystem.Match.make({...match,entry: FileSystem.Entry.make({...match.entry,path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),}),}),),),Effect.orDie,)}),find: (input) =>Effect.gen(function* () {const items =input.type === "file"? state.files: input.type === "directory"? state.directories: [...state.files, ...state.directories]return fuzzysort.go(input.query, items, { limit: input.limit ?? 50 }).map((item) => {const relative = item.targetconst type = relative.endsWith(path.sep) ? ("directory" as const) : ("file" as const)return FileSystem.Entry.make({path: RelativePath.make(relative),type,})})}),})}),)export const fffLayer = Layer.effect(Service,Effect.gen(function* () {const location = yield* Location.Serviceconst result = yield* Effect.try({try: () =>Fff.create({basePath: location.directory,aiMode: true,disableMmapCache: true,disableContentIndexing: true,}),catch: (cause) => cause,}).pipe(Effect.catch((error) => Effect.logWarning("failed to initialize fff", { error }).pipe(Effect.as(undefined))),)if (!result?.ok) {if (result) yield* Effect.logWarning("failed to initialize fff", { error: result.error })return Service.of({find: () => Effect.succeed([]),glob: () => Effect.succeed([]),grep: () => Effect.succeed([]),})}yield* Effect.addFinalizer(() => Effect.sync(() => result.value.destroy()).pipe(Effect.ignore))return Service.of({glob: (input) =>Effect.sync(() => {const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")const found = result.value.glob(prefix ? `${prefix}/${input.pattern}` : input.pattern, {pageIndex: 0,pageSize: input.limit,})if (!found.ok) throw found.errorreturn found.value.items.map((item) =>FileSystem.Entry.make({path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),type: "file",}),)}),grep: (input) =>Effect.sync(() => {const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")const found = result.value.grep([prefix ? `${prefix}/**` : undefined, input.include, input.pattern].filter((value) => value !== undefined).join(" "),{ mode: "regex", pageSize: input.limit, timeBudgetMs: 1_500 },)if (!found.ok) throw found.errorreturn found.value.items.map((match) => {const bytes = Buffer.from(match.lineContent)return FileSystem.Match.make({entry: FileSystem.Entry.make({path: RelativePath.make(match.relativePath.replaceAll("\\", "/")),type: "file",}),line: match.lineNumber,offset: match.byteOffset,text: match.lineContent.length > 2_000 ? match.lineContent.slice(0, 2_000) + "..." : match.lineContent,submatches: match.matchRanges.map(([start, end]) => ({text: bytes.subarray(start, end).toString("utf8"),start,end,})),})})}),find: (input) =>Effect.sync(() => {const options = { pageIndex: 0, pageSize: input.limit ?? 50 }const items = (() => {if (input.type === "file") {const found = result.value.fileSearch(input.query.trim(), options)if (!found.ok) throw found.errorreturn found.value.items.map((item, index) => ({path: item.relativePath,type: "file" as const,score: found.value.scores[index]?.total ?? 0,}))}if (input.type === "directory") {const found = result.value.directorySearch(input.query.trim(), options)if (!found.ok) throw found.errorreturn found.value.items.map((item, index) => ({path: item.relativePath,type: "directory" as const,score: found.value.scores[index]?.total ?? 0,}))}const found = result.value.mixedSearch(input.query.trim(), options)if (!found.ok) throw found.errorreturn found.value.items.map((item, index) => ({path: item.item.relativePath,type: item.type,score: found.value.scores[index]?.total ?? 0,}))})()return items.sort((a, b) => b.score - a.score || a.path.length - b.path.length).map((item) => {const relative = item.path.replaceAll("\\", "/").replace(/\/$/, "")return FileSystem.Entry.make({path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")),type: item.type,})})}),})}),)const layer = Layer.unwrap(Effect.sync(() => (Flag.OPENCODE_DISABLE_FFF || !Fff.available() ? ripgrepLayer : fffLayer)))export const locationLayer = layerexport const node = makeLocationNode({ service: Service, layer, deps: [FSUtil.node, Location.node, Ripgrep.node] })