fix(cursor): honor legacy read range and grep offset
Modern Cursor builds paginate the legacy `read` and `grep` frames with fields this branch modeled in the proto but never wired. `read` composed no range, so every page returned the whole file (or its own truncation) and a model walking a large file never advanced past the first window. It now goes through `piReadPath`, the same helper the Pi frame uses, so both translate a range identically - including the `limit: 0` case, which asks for zero lines and has no selector. The answer reports `range_applied`, left false for an unranged read since that is precisely the server's "this is the whole file". `grep` dropped its `offset`. The local tool paginates by file through `skip` and advertises exactly that unit in its own "use skip=N" advice, so an unforwarded offset re-ran the identical search and answered page one forever. A present `0` stays unset: it means "start at the beginning", which is the un-skipped search. (cherry picked from commit 4f647d86b6e4a507d57fb4d247f19db8240e51cf)
This commit is contained in:
committed by
can1357
parent
d9bc1e80b6
commit
4414ee4b0d
@@ -1533,3 +1533,41 @@ describe("Cursor modern exec frames: server-resolved tool calls leave a paired b
|
||||
expect(output.content.filter(block => block.type === "toolCall")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cursor legacy read frame: range reporting", () => {
|
||||
it("reports rangeApplied only when the frame asked for a window", async () => {
|
||||
// `range_applied: false` tells the server "this is the whole file", which
|
||||
// is true for an unranged read and a lie for a paginated one — a model
|
||||
// walking a large file would stop after the first window believing it had
|
||||
// seen everything.
|
||||
const handlers: CursorExecHandlers = {
|
||||
async read() {
|
||||
return toolResult("line1\nline2");
|
||||
},
|
||||
};
|
||||
|
||||
const ranged = await dispatchExec(
|
||||
buildExecMessage({
|
||||
case: "readArgs",
|
||||
value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c1", offset: 5, limit: 20 }),
|
||||
}),
|
||||
{ execHandlers: handlers },
|
||||
);
|
||||
const rangedAnswer = soleResult(ranged.frames);
|
||||
if (rangedAnswer.case !== "readResult") throw new Error(`got ${rangedAnswer.case}`);
|
||||
if (rangedAnswer.value.result.case !== "success") throw new Error(`got ${rangedAnswer.value.result.case}`);
|
||||
expect(rangedAnswer.value.result.value.rangeApplied).toBe(true);
|
||||
|
||||
const whole = await dispatchExec(
|
||||
buildExecMessage({
|
||||
case: "readArgs",
|
||||
value: create(ReadArgsSchema, { path: "/repo/big.ts", toolCallId: "c2" }),
|
||||
}),
|
||||
{ execHandlers: handlers },
|
||||
);
|
||||
const wholeAnswer = soleResult(whole.frames);
|
||||
if (wholeAnswer.case !== "readResult") throw new Error(`got ${wholeAnswer.case}`);
|
||||
if (wholeAnswer.value.result.case !== "success") throw new Error(`got ${wholeAnswer.value.result.case}`);
|
||||
expect(wholeAnswer.value.result.value.rangeApplied).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user