fix(core): bound default search results (#37154)

This commit is contained in:
Kit Langton
2026-07-15 13:49:16 -04:00
committed by GitHub
parent ec8ee60e0b
commit b6ccb66610
5 changed files with 40 additions and 9 deletions
+2
View File
@@ -31,6 +31,8 @@ export type ListInput = typeof ListInput.Type
export { FindInput }
export const DEFAULT_SEARCH_LIMIT = 100
export class GlobInput extends Schema.Class<GlobInput>("FileSystem.GlobInput")({
pattern: Schema.String,
path: RelativePath.pipe(Schema.optional),
+4 -4
View File
@@ -56,7 +56,7 @@ export const ripgrepLayer = Layer.effect(
.glob({
cwd,
pattern: input.pattern,
limit: input.limit ?? Number.MAX_SAFE_INTEGER,
limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
})
.pipe(
Effect.map((result) =>
@@ -81,7 +81,7 @@ export const ripgrepLayer = Layer.effect(
pattern: input.pattern,
file: info.type === "File" ? path.basename(target) : undefined,
include: input.include,
limit: input.limit ?? Number.MAX_SAFE_INTEGER,
limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
})
.pipe(
Effect.map((result) =>
@@ -150,7 +150,7 @@ export const fffLayer = Layer.effect(
const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")
const found = result.value.glob(prefix ? `${prefix}/${input.pattern}` : input.pattern, {
pageIndex: 0,
pageSize: input.limit,
pageSize: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
})
if (!found.ok) throw found.error
return found.value.items.map((item) =>
@@ -167,7 +167,7 @@ export const fffLayer = Layer.effect(
[prefix ? `${prefix}/**` : undefined, input.include, input.pattern]
.filter((value) => value !== undefined)
.join(" "),
{ mode: "regex", pageSize: input.limit, timeBudgetMs: 1_500 },
{ mode: "regex", pageSize: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT, timeBudgetMs: 1_500 },
)
if (!found.ok) throw found.error
return found.value.items.map((match) => {
+2 -2
View File
@@ -20,7 +20,7 @@ export const Input = Schema.Struct({
description: "Relative directory to search. Defaults to the active Location.",
}),
limit: FileSystem.GlobInput.fields.limit.annotate({
description: "Maximum results to return",
description: `Maximum results to return (default: ${FileSystem.DEFAULT_SEARCH_LIMIT})`,
}),
})
@@ -86,7 +86,7 @@ export const Plugin = {
.glob({
cwd,
pattern: input.pattern,
limit: input.limit ?? Number.MAX_SAFE_INTEGER,
limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
})
.pipe(
Effect.map((result) =>
+2 -2
View File
@@ -25,7 +25,7 @@ export const Input = Schema.Struct({
description: 'File glob to include in the search (for example, "*.js" or "*.{ts,tsx}")',
}),
limit: FileSystem.GrepInput.fields.limit.annotate({
description: "Maximum matches to return",
description: `Maximum matches to return (default: ${FileSystem.DEFAULT_SEARCH_LIMIT})`,
}),
})
@@ -106,7 +106,7 @@ export const Plugin = {
pattern: input.pattern,
file: info?.type === "File" ? path.basename(target) : undefined,
include: input.include,
limit: input.limit ?? Number.MAX_SAFE_INTEGER,
limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
})
.pipe(
Effect.map((result) =>
+30 -1
View File
@@ -1,8 +1,11 @@
import { describe, expect } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { Effect, Layer } from "effect"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { FileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Location } from "@opencode-ai/core/location"
import { PermissionV2 } from "@opencode-ai/core/permission"
@@ -16,7 +19,7 @@ import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
import { location } from "./fixture/location"
import { tmpdir } from "./fixture/tmpdir"
import { testEffect } from "./lib/effect"
import { executeTool, registerToolPlugin, toolIdentity } from "./lib/tool"
import { executeTool, registerToolPlugin, settleTool, toolIdentity } from "./lib/tool"
const globToolNode = makeLocationNode({
name: "test/glob-tool-plugin",
@@ -66,6 +69,32 @@ const call = (name: "glob" | "grep", input: unknown) => ({
const it = testEffect(Layer.empty)
describe("search tools", () => {
it.live("bounds omitted glob and grep limits", () =>
Effect.acquireUseRelease(
Effect.promise(() => tmpdir()),
(tmp) =>
Effect.gen(function* () {
yield* Effect.promise(() =>
Promise.all(
Array.from({ length: FileSystem.DEFAULT_SEARCH_LIMIT + 1 }, (_, index) =>
fs.writeFile(path.join(tmp.path, `${index}.txt`), "needle\n"),
),
),
)
yield* withTools(tmp.path, (registry) =>
Effect.gen(function* () {
const glob = yield* settleTool(registry, call("glob", { pattern: "*" }))
const grep = yield* settleTool(registry, call("grep", { pattern: "needle" }))
expect(glob.output?.structured).toHaveLength(FileSystem.DEFAULT_SEARCH_LIMIT)
expect(grep.output?.structured).toHaveLength(FileSystem.DEFAULT_SEARCH_LIMIT)
}),
)
}),
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
),
)
for (const name of ["glob", "grep"] as const) {
it.live(`${name} reports a missing search path`, () =>
Effect.acquireUseRelease(