Skip to content

Commit 27c3115

Browse files
committed
feat: update the read tool to remove the pages param, and no longer output PDF content in base64
1 parent ab28669 commit 27c3115

5 files changed

Lines changed: 26 additions & 87 deletions

File tree

packages/core/src/prompt.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ export function getTools(_options: PromptToolOptions = {}, externalTools: ToolDe
589589
type: "function",
590590
function: {
591591
name: "read",
592-
description: "Read files from the filesystem (text, images, PDFs, notebooks).",
592+
description: "Read files from the filesystem (text, images, notebooks).",
593593
parameters: {
594594
type: "object",
595595
properties: {
@@ -605,10 +605,6 @@ export function getTools(_options: PromptToolOptions = {}, externalTools: ToolDe
605605
type: "number",
606606
description: "Number of lines to read",
607607
},
608-
pages: {
609-
type: "string",
610-
description: 'Page range for PDF files (e.g., "1-5", "3", "10-20"). Only applicable to PDF files.',
611-
},
612608
},
613609
required: ["file_path"],
614610
additionalProperties: false,

packages/core/src/tests/prompt.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ test("getTools requires bash sideEffects permission scopes", () => {
5555
assert.equal(runInBackground.type, "boolean");
5656
});
5757

58+
test("getTools does not expose the unused PDF pages parameter", () => {
59+
const tool = getTools().find((candidate) => candidate.function.name === "read");
60+
assert.ok(tool);
61+
assert.equal("pages" in tool.function.parameters.properties, false);
62+
});
63+
5864
test("getSystemPrompt always includes WebSearch docs", () => {
5965
const prompt = getSystemPrompt("/tmp/project");
6066
assert.equal(prompt.includes("## WebSearch"), true);

packages/core/src/tests/tool-handlers.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,24 @@ test("Read returns an acknowledgement for images and attaches the image as a fol
998998
);
999999
});
10001000

1001+
test("Read reports PDFs as binary without attaching their contents", async () => {
1002+
const workspace = createTempWorkspace();
1003+
const filePath = path.join(workspace, "document.pdf");
1004+
fs.writeFileSync(filePath, "%PDF-1.7\n" + "x".repeat(1024 * 1024));
1005+
1006+
const readResult = await handleReadTool({ file_path: filePath }, createContext("pdf-read", workspace));
1007+
1008+
assert.equal(readResult.ok, true);
1009+
assert.equal(readResult.output, "WARNING: File is binary.");
1010+
assert.deepEqual(readResult.metadata, {
1011+
mime: "application/pdf",
1012+
encoding: "base64",
1013+
bytes: 1024 * 1024 + "%PDF-1.7\n".length,
1014+
pageCount: 0,
1015+
});
1016+
assert.equal(readResult.followUpMessages, undefined);
1017+
});
1018+
10011019
function createContext(
10021020
sessionId: string,
10031021
projectRoot: string,

packages/core/src/tools/read-handler.ts

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import {
1313

1414
const DEFAULT_LINE_LIMIT = 2000;
1515
const MAX_LINE_LENGTH = 2000;
16-
const PDF_LARGE_PAGE_THRESHOLD = 10;
17-
const PDF_MAX_PAGE_RANGE = 20;
1816
const LINE_NUMBER_WIDTH = 6;
1917
const DEFAULT_GITIGNORE = [
2018
"node_modules/",
@@ -41,12 +39,6 @@ const DEFAULT_GITIGNORE = [
4139
"target/",
4240
];
4341

44-
type PageRange = {
45-
start: number;
46-
end: number;
47-
count: number;
48-
};
49-
5042
type TextReadResult = {
5143
content: string;
5244
output: string;
@@ -159,36 +151,8 @@ export async function handleReadTool(
159151
}
160152

161153
if (ext === ".pdf") {
162-
const pagesParam = typeof args.pages === "string" ? args.pages.trim() : "";
163154
const buffer = fs.readFileSync(filePath);
164155
const pageCount = countPdfPages(buffer);
165-
const pageRange = pagesParam ? parsePageRange(pagesParam) : null;
166-
167-
if (!pageRange && pageCount !== null && pageCount > PDF_LARGE_PAGE_THRESHOLD) {
168-
return {
169-
ok: false,
170-
name: "read",
171-
error: `PDF has ${pageCount} pages; provide "pages" to read a range.`,
172-
};
173-
}
174-
175-
if (pageRange && pageRange.count > PDF_MAX_PAGE_RANGE) {
176-
return {
177-
ok: false,
178-
name: "read",
179-
error: `PDF page range exceeds ${PDF_MAX_PAGE_RANGE} pages.`,
180-
};
181-
}
182-
183-
if (pageRange && pageCount !== null && pageRange.end > pageCount) {
184-
return {
185-
ok: false,
186-
name: "read",
187-
error: `PDF page range exceeds total page count (${pageCount}).`,
188-
};
189-
}
190-
191-
const base64 = buffer.toString("base64");
192156
markFileRead(context.sessionId, filePath, {
193157
content: "",
194158
timestamp: Math.floor(stat.mtimeMs),
@@ -197,13 +161,12 @@ export async function handleReadTool(
197161
return {
198162
ok: true,
199163
name: "read",
200-
output: `data:application/pdf;base64,${base64}`,
164+
output: "WARNING: File is binary.",
201165
metadata: {
202166
mime: "application/pdf",
203167
encoding: "base64",
204168
bytes: buffer.length,
205169
pageCount,
206-
pages: pageRange ? `${pageRange.start}-${pageRange.end}` : null,
207170
},
208171
};
209172
}
@@ -520,45 +483,6 @@ function countPdfPages(buffer: Buffer): number | null {
520483
}
521484
}
522485

523-
function parsePageRange(input: string): PageRange {
524-
const trimmed = input.trim();
525-
if (!trimmed) {
526-
throw new Error("pages must be a non-empty string.");
527-
}
528-
if (trimmed.includes(",")) {
529-
throw new Error('pages must be a single range like "1-5" or "3".');
530-
}
531-
532-
const parts = trimmed.split("-").map((part) => part.trim());
533-
if (parts.length === 1) {
534-
const value = parsePositiveInt(parts[0], "pages");
535-
return { start: value, end: value, count: 1 };
536-
}
537-
538-
if (parts.length === 2) {
539-
const start = parsePositiveInt(parts[0], "pages");
540-
const end = parsePositiveInt(parts[1], "pages");
541-
if (end < start) {
542-
throw new Error("pages range end must be >= start.");
543-
}
544-
return { start, end, count: end - start + 1 };
545-
}
546-
547-
throw new Error('pages must be a single range like "1-5" or "3".');
548-
}
549-
550-
function parsePositiveInt(value: string, label: string): number {
551-
const numeric = Number(value);
552-
if (!Number.isFinite(numeric)) {
553-
throw new Error(`${label} must be a number.`);
554-
}
555-
const integer = Math.trunc(numeric);
556-
if (integer < 1) {
557-
throw new Error(`${label} must be >= 1.`);
558-
}
559-
return integer;
560-
}
561-
562486
function readNotebook(filePath: string): string {
563487
const raw = fs.readFileSync(filePath, "utf8");
564488
if (!raw) {

packages/core/templates/tools/read.md.ejs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ Usage:
1515
<%_ } else { _%>
1616
- This tool can inspect image files, but the current model is not multimodal, so image reads are not presented visually to the model.
1717
<%_ } _%>
18-
- This tool can read PDF files (.pdf). For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges (e.g., pages: "1-5"). Reading a large PDF without the pages parameter will fail. Maximum 20 pages per request.
1918
- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.
2019
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
2120
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
@@ -38,10 +37,6 @@ Usage:
3837
"limit": {
3938
"description": "The number of lines to read. Only provide if the file is too large to read at once.",
4039
"type": "number"
41-
},
42-
"pages": {
43-
"description": "Page range for PDF files (e.g., \"1-5\", \"3\", \"10-20\"). Only applicable to PDF files. Maximum 20 pages per request.",
44-
"type": "string"
4540
}
4641
},
4742
"required": [

0 commit comments

Comments
 (0)