Skip to content

Commit ae4a1e2

Browse files
fix(pdf): reject fractional page selections (#99399)
* fix(pdf): reject fractional page selections * chore: refresh pdf fractional page checks * chore: refresh failed checkout checks * test(pdf): prove fractional pages fail at tool boundary * test(pdf): cover mixed fractional page ranges * fix(pdf): validate page selections before loading --------- Co-authored-by: Peter Steinberger <[email protected]>
1 parent 00f5df2 commit ae4a1e2

4 files changed

Lines changed: 42 additions & 2 deletions

File tree

src/agents/tools/pdf-tool.helpers.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ describe("parsePageRange", () => {
7474
expect(() => parsePageRange("abc", 20)).toThrow("Invalid page number");
7575
});
7676

77+
it("throws on fractional page numbers", () => {
78+
expect(() => parsePageRange("1.5", 20)).toThrow('Invalid page number: "1.5"');
79+
expect(() => parsePageRange("1,2.5", 20)).toThrow('Invalid page number: "2.5"');
80+
});
81+
7782
it("throws on invalid range (start > end)", () => {
7883
expect(() => parsePageRange("5-3", 20)).toThrow("Invalid page range");
7984
});

src/agents/tools/pdf-tool.helpers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ export function parsePageRange(range: string, maxPages: number): number[] {
6565
pages.add(i);
6666
}
6767
} else {
68+
if (!/^\d+$/.test(part)) {
69+
throw new Error(`Invalid page number: "${part}"`);
70+
}
6871
const num = Number(part);
6972
if (!Number.isFinite(num) || num < 1) {
7073
throw new Error(`Invalid page number: "${part}"`);

src/agents/tools/pdf-tool.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,39 @@ describe("createPdfTool", () => {
568568
});
569569
});
570570

571+
it.each([
572+
["1.5", "1.5"],
573+
["1,2.5", "2.5"],
574+
])(
575+
"rejects fractional page selection %s before loading or fallback extraction",
576+
async (pages, invalidPage) => {
577+
await withTempPdfAgentDir(async (agentDir) => {
578+
const { loadSpy } = await stubPdfToolInfra(agentDir, {
579+
provider: "openai",
580+
api: "openai-responses",
581+
input: ["text"],
582+
});
583+
const extractSpy = vi.spyOn(pdfExtractModule, "extractPdfContent").mockResolvedValue({
584+
text: "Extracted content",
585+
images: [],
586+
});
587+
const cfg = withPdfModel(OPENAI_PDF_MODEL);
588+
const tool = requirePdfTool((await loadCreatePdfTool())({ config: cfg, agentDir }));
589+
590+
await expect(
591+
tool.execute("t1", {
592+
prompt: "summarize",
593+
pdf: "/tmp/doc.pdf",
594+
pages,
595+
}),
596+
).rejects.toThrow(`Invalid page number: "${invalidPage}"`);
597+
expect(loadSpy).not.toHaveBeenCalled();
598+
expect(extractSpy).not.toHaveBeenCalled();
599+
expect(completeMock).not.toHaveBeenCalled();
600+
});
601+
},
602+
);
603+
571604
it("rejects password parameter for native PDF providers", async () => {
572605
await withTempPdfAgentDir(async (agentDir) => {
573606
await stubPdfToolInfra(agentDir, { provider: "anthropic", input: ["text", "document"] });

src/agents/tools/pdf-tool.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ export function createPdfTool(options?: {
374374

375375
// Parse page range
376376
const pagesRaw = normalizeOptionalString(record.pages);
377+
const pageNumbers = pagesRaw ? parsePageRange(pagesRaw, configuredMaxPages) : undefined;
377378
const password = typeof record.password === "string" ? record.password : undefined;
378379

379380
const pdfModelConfig =
@@ -495,8 +496,6 @@ export function createPdfTool(options?: {
495496
});
496497
}
497498

498-
const pageNumbers = pagesRaw ? parsePageRange(pagesRaw, configuredMaxPages) : undefined;
499-
500499
const getExtractions = async (): Promise<PdfExtractedContent[]> => {
501500
const extractedAll: PdfExtractedContent[] = [];
502501
for (const pdf of loadedPdfs) {

0 commit comments

Comments
 (0)