refactor(core): unify filesystem search service (#31566)
This commit is contained in:
parent
ce4e658e3f
commit
a0409e64d8
57 changed files with 962 additions and 2852 deletions
|
|
@ -33,6 +33,7 @@ import { Project } from "@/project/project"
|
|||
import { Vcs } from "@/project/vcs"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
|
||||
const originalEnv = {
|
||||
OPENCODE_AUTH_CONTENT: process.env.OPENCODE_AUTH_CONTENT,
|
||||
|
|
@ -54,6 +55,7 @@ const workspaceLayer = (experimentalWorkspaces: boolean) =>
|
|||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces })),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { FetchHttpClient } from "effect/unstable/http"
|
|||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
|
|
@ -55,7 +56,9 @@ const workspaceLayer = Workspace.layer.pipe(
|
|||
Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: true })),
|
||||
)
|
||||
const it = testEffect(Layer.mergeAll(pluginLayer, workspaceLayer, CrossSpawnSpawner.defaultLayer))
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(pluginLayer, workspaceLayer, CrossSpawnSpawner.defaultLayer).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
|
||||
afterEach(async () => {
|
||||
await disposeAllInstances()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { mkdir } from "node:fs/promises"
|
|||
import path from "node:path"
|
||||
import { registerAdapter } from "../../src/control-plane/adapters"
|
||||
import { WorkspaceV2 } from "@opencode-ai/core/workspace"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import type { WorkspaceAdapter } from "../../src/control-plane/types"
|
||||
import { Workspace } from "../../src/control-plane/workspace"
|
||||
import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref"
|
||||
|
|
@ -53,7 +54,7 @@ const it = testEffect(
|
|||
InstanceLayer.layer,
|
||||
Project.defaultLayer,
|
||||
workspaceLayer,
|
||||
),
|
||||
).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
|
||||
const instanceContextTestLayer = Layer.mergeAll(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import * as Socket from "effect/unstable/socket/Socket"
|
|||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { mkdir } from "node:fs/promises"
|
||||
import { registerAdapter } from "../../src/control-plane/adapters"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import type { WorkspaceAdapter } from "../../src/control-plane/types"
|
||||
import { Workspace } from "../../src/control-plane/workspace"
|
||||
import { InstanceRef, WorkspaceRef } from "../../src/effect/instance-ref"
|
||||
|
|
@ -58,7 +59,7 @@ const it = testEffect(
|
|||
InstanceLayer.layer,
|
||||
Project.defaultLayer,
|
||||
workspaceLayer,
|
||||
),
|
||||
).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
|
||||
const instanceContextTestLayer = Layer.mergeAll(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse, HttpRouter, HttpServ
|
|||
import { layerWebSocketConstructorGlobal } from "effect/unstable/socket/Socket"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { registerAdapter } from "../../src/control-plane/adapters"
|
||||
import type { WorkspaceAdapter } from "../../src/control-plane/types"
|
||||
import { Workspace } from "../../src/control-plane/workspace"
|
||||
|
|
@ -66,7 +67,7 @@ const it = testEffect(
|
|||
workspaceLayer,
|
||||
Database.defaultLayer,
|
||||
httpApiLayer,
|
||||
),
|
||||
).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
|
||||
function pathFor(path: string, params: Record<string, string>) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import type { WorkspaceAdapter } from "../../src/control-plane/types"
|
|||
import { Workspace } from "../../src/control-plane/workspace"
|
||||
import { WorkspaceTable } from "@opencode-ai/core/control-plane/workspace.sql"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Project } from "../../src/project/project"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/groups/workspace"
|
||||
|
|
@ -58,7 +59,7 @@ const it = testEffect(
|
|||
Project.defaultLayer,
|
||||
workspaceLayer,
|
||||
Socket.layerWebSocketConstructorGlobal,
|
||||
),
|
||||
).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
|
||||
type ProxiedRequest = {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/groups/
|
|||
import { EventPaths } from "../../src/server/routes/instance/httpapi/groups/event"
|
||||
import { Session } from "@/session/session"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Server } from "../../src/server/server"
|
||||
import { resetDatabase } from "../fixture/db"
|
||||
import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/fixture"
|
||||
|
|
@ -34,7 +35,7 @@ const it = testEffect(
|
|||
InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer)),
|
||||
Database.defaultLayer,
|
||||
httpApiLayer,
|
||||
),
|
||||
).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
|
||||
function request(path: string, directory: string, init: RequestInit = {}) {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ import { Snapshot } from "../../src/snapshot"
|
|||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Format } from "../../src/format"
|
||||
import { TestInstance } from "../fixture/fixture"
|
||||
import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
|
||||
|
|
@ -190,7 +190,7 @@ function makePrompt(input?: { processor?: "blocking" }) {
|
|||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Search.defaultLayer),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
Layer.provideMerge(todo),
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ import { ToolRegistry } from "@/tool/registry"
|
|||
import { Truncate } from "@/tool/truncate"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Format } from "../../src/format"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ function makeHttp() {
|
|||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Search.defaultLayer),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
Layer.provideMerge(todo),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionPrompt } from "../../src/session/prompt"
|
||||
|
|
@ -8,7 +9,9 @@ import { testEffect } from "../lib/effect"
|
|||
|
||||
// Skip tests if no API key is available
|
||||
const hasApiKey = !!process.env.ANTHROPIC_API_KEY
|
||||
const it = testEffect(Layer.mergeAll(SessionPrompt.defaultLayer, Session.defaultLayer))
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(SessionPrompt.defaultLayer, Session.defaultLayer).pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
)
|
||||
const live = hasApiKey ? it.instance : it.instance.skip
|
||||
|
||||
describe("StructuredOutput Integration", () => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ 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 { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Truncate } from "@/tool/truncate"
|
||||
|
|
@ -23,7 +23,7 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
Layer.mergeAll(
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
FSUtil.defaultLayer,
|
||||
Search.defaultLayer,
|
||||
Ripgrep.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ 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 { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { Permission } from "../../src/permission"
|
||||
|
|
@ -25,7 +25,7 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
Layer.mergeAll(
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
FSUtil.defaultLayer,
|
||||
Search.defaultLayer,
|
||||
Ripgrep.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
|
|
@ -135,6 +135,25 @@ describe("tool.grep", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.instance("does not report an unknown total when results are truncated", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
yield* Effect.promise(() =>
|
||||
Promise.all(
|
||||
Array.from({ length: 101 }, (_, index) =>
|
||||
Bun.write(path.join(test.directory, `match-${index}.txt`), "needle"),
|
||||
),
|
||||
),
|
||||
)
|
||||
const info = yield* GrepTool
|
||||
const grep = yield* info.init()
|
||||
const result = yield* grep.execute({ pattern: "needle", path: test.directory, include: "*.txt" }, ctx)
|
||||
|
||||
expect(result.output).toContain("(Results truncated. Consider using a more specific path or pattern.)")
|
||||
expect(result.output).not.toMatch(/showing \d+ of \d+ matches/)
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("supports exact file paths", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ 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"
|
||||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import { Permission } from "../../src/permission"
|
||||
import { SessionID, MessageID } from "../../src/session/schema"
|
||||
|
|
@ -50,7 +50,7 @@ const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
CrossSpawnSpawner.defaultLayer,
|
||||
Instruction.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Search.defaultLayer,
|
||||
Ripgrep.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import * as Truncate from "@/tool/truncate"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ const registryLayer = (opts: RegistryLayerOptions = {}) =>
|
|||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(Layer.mergeAll(node, Database.defaultLayer)),
|
||||
Layer.provide(Search.defaultLayer),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer),
|
||||
)
|
||||
.pipe(Layer.provide(RuntimeFlags.layer(opts.flags ?? {})))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Cause, Effect, Exit, Layer } from "effect"
|
||||
import { afterEach, describe, expect } from "bun:test"
|
||||
import path from "path"
|
||||
|
|
@ -28,7 +29,7 @@ afterEach(async () => {
|
|||
|
||||
const node = CrossSpawnSpawner.defaultLayer
|
||||
|
||||
const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node))
|
||||
const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node).pipe(Layer.provide(Ripgrep.defaultLayer)))
|
||||
|
||||
describe("tool.skill", () => {
|
||||
it.instance("execute returns skill content block with files", () =>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { BackgroundJob } from "@/background/job"
|
|||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Config } from "@/config/config"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { Session } from "@/session/session"
|
||||
import type { SessionPrompt } from "../../src/session/prompt"
|
||||
import { MessageID, PartID, SessionID } from "../../src/session/schema"
|
||||
|
|
@ -45,7 +46,7 @@ const layer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
ToolRegistry.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
RuntimeFlags.layer(flags),
|
||||
)
|
||||
).pipe(Layer.provide(Ripgrep.defaultLayer))
|
||||
|
||||
const it = testEffect(layer())
|
||||
const background = testEffect(layer({ experimentalBackgroundSubagents: true }))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue