feat(cursor): honor pi_grep's context and limit
`pi_grep` carries a context width and a total match cap. Neither is expressible in the model-facing `grep` schema — context comes from `grep.contextBefore`/`grep.contextAfter`, fixed when the shared tool is constructed — so both were dropped. `GrepTool` now takes them as constructor options. The model-facing schema is unchanged: this is a seam for wire bridges whose protocol supplies the values, mirroring `GlobTool`'s existing options bag. The bridge builds a per-call `grep` only for frames that supply them; everything else keeps the shared instance and session defaults. `pi_ls`'s `limit` stays unmapped, now deliberately and documented. It caps directory entries, while the local `read` renders a depth-2 tree and slices rendered lines — nested rows, headers and elision summaries all count — so `:1+K` would cap a different unit while looking honored. Verified against real files in a temp dir, not captured arguments: match counts and context lines are asserted from actual search output. Mutation-checked — ignoring either option, or dropping the scoped tool in the bridge, fails a test. (cherry picked from commit 299ded5a274427c2c2d5de27c00a2056a709581c)
This commit is contained in:
committed by
can1357
parent
7b62fef366
commit
9436fb5640
@@ -20,7 +20,7 @@ import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { CursorExecHandlers } from "@oh-my-pi/pi-coding-agent/cursor";
|
||||
import type { ExtensionRunner } from "@oh-my-pi/pi-coding-agent/extensibility/extensions";
|
||||
import { ExtensionToolWrapper } from "@oh-my-pi/pi-coding-agent/extensibility/extensions";
|
||||
import { GrepTool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
||||
import { GrepTool, type Tool, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
|
||||
import { removeWithRetries } from "@oh-my-pi/pi-utils";
|
||||
import { type } from "arktype";
|
||||
import { AdviseTool } from "../src/advisor/advise-tool";
|
||||
@@ -82,6 +82,58 @@ describe("CursorExecHandlers.grep bridge", () => {
|
||||
} as any);
|
||||
expect((sensitiveResult.details as { matchCount?: number } | undefined)?.matchCount).toBe(1);
|
||||
});
|
||||
|
||||
it("honors pi_grep's requested match limit against real files", async () => {
|
||||
// The frame's `limit` caps total surfaced matches. The model-facing schema
|
||||
// has no such parameter, so without a per-call tool the cap is dropped and
|
||||
// the search returns everything it found.
|
||||
await Bun.write(path.join(cwd, "many.txt"), Array.from({ length: 10 }, (_, i) => `needle ${i}`).join("\n"));
|
||||
const scopedHandlers = new CursorExecHandlers({
|
||||
cwd,
|
||||
tools: new Map<string, Tool>([["grep", searchTool]]),
|
||||
createGrepTool: options => new GrepTool(createTestSession(cwd), options),
|
||||
});
|
||||
|
||||
const capped = await scopedHandlers.piGrep({
|
||||
toolCallId: "c1",
|
||||
args: { pattern: "needle", path: cwd, limit: 3 },
|
||||
} as never);
|
||||
expect((capped.details as { matchCount?: number } | undefined)?.matchCount).toBe(3);
|
||||
|
||||
const uncapped = await scopedHandlers.piGrep({
|
||||
toolCallId: "c2",
|
||||
args: { pattern: "needle", path: cwd },
|
||||
} as never);
|
||||
expect((uncapped.details as { matchCount?: number } | undefined)?.matchCount).toBe(10);
|
||||
});
|
||||
|
||||
it("honors pi_grep's requested context width against real files", async () => {
|
||||
// `context` has no schema parameter either: the width is read from
|
||||
// settings fixed at tool construction, so the frame's value only lands
|
||||
// through a per-call instance.
|
||||
await Bun.write(path.join(cwd, "ctx.txt"), "before line\nneedle here\nafter line\n");
|
||||
const scopedHandlers = new CursorExecHandlers({
|
||||
cwd,
|
||||
tools: new Map<string, Tool>([["grep", searchTool]]),
|
||||
createGrepTool: options => new GrepTool(createTestSession(cwd), options),
|
||||
});
|
||||
|
||||
const noContext = await scopedHandlers.piGrep({
|
||||
toolCallId: "c1",
|
||||
args: { pattern: "needle here", path: path.join(cwd, "ctx.txt"), context: 0 },
|
||||
} as never);
|
||||
const noContextText = noContext.content.map(c => (c.type === "text" ? c.text : "")).join("");
|
||||
expect(noContextText).not.toContain("before line");
|
||||
expect(noContextText).not.toContain("after line");
|
||||
|
||||
const withContext = await scopedHandlers.piGrep({
|
||||
toolCallId: "c2",
|
||||
args: { pattern: "needle here", path: path.join(cwd, "ctx.txt"), context: 1 },
|
||||
} as never);
|
||||
const withContextText = withContext.content.map(c => (c.type === "text" ? c.text : "")).join("");
|
||||
expect(withContextText).toContain("before line");
|
||||
expect(withContextText).toContain("after line");
|
||||
});
|
||||
});
|
||||
|
||||
describe("CursorExecHandlers error results", () => {
|
||||
|
||||
Reference in New Issue
Block a user