From f8c224d4e081f6818e1a7d755b50f1bf3ad5cd0e Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 06:07:22 +0000 Subject: [PATCH] fix(antigravity): preserve uploaded file references Keep original file paths and metadata instead of decoding every small upload as UTF-8. This accepts attachments without losing binary contents or requiring file reads during prompt encoding. Co-authored-by: ax --- .../server/internal/prompt.ts | 44 +++++++------------ .../server/provider.test.ts | 32 +++++++++++++- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/plugins/antigravity-provider/server/internal/prompt.ts b/plugins/antigravity-provider/server/internal/prompt.ts index a9d57c9149..1fa546140f 100644 --- a/plugins/antigravity-provider/server/internal/prompt.ts +++ b/plugins/antigravity-provider/server/internal/prompt.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import type { ProviderPrompt } from "@getpaseo/plugin/server/provider"; @@ -10,8 +10,6 @@ interface EncodedPrompt { nativeText: string; } -const MAX_INLINE_FILE_BYTES = 2 * 1024 * 1024; // 2 MB - export class PromptFiles { private directory: string | null = null; @@ -22,16 +20,19 @@ export class PromptFiles { prompt.outputSchema !== undefined ) throw new AntigravityError("Antigravity supports messages only"); - + const parts = prompt.input.content.map((part) => { + if (part.type === "text" || part.type === "uploaded_file") return part; + if (part.type === "image") return { ...part, extension: imageExtension(part.mimeType) }; + throw new AntigravityError("Antigravity supports text, image and uploaded file content only"); + }); const text: string[] = []; const native: string[] = []; - - for (const part of prompt.input.content) { + for (const part of parts) { if (part.type === "text") { text.push(part.text); native.push(part.text); } else if (part.type === "image") { - const extension = imageExtension(part.mimeType); + const extension = part.extension; if (this.directory === null) { this.directory = await mkdtemp(path.join(os.tmpdir(), "paseo-antigravity-images-")); await chmod(this.directory, 0o700); @@ -41,31 +42,20 @@ export class PromptFiles { await chmod(file, 0o600); native.push(`Attached image: ${file}`); } else if (part.type === "uploaded_file") { - if (part.mimeType && part.mimeType.startsWith("image/")) { + if (part.mimeType.startsWith("image/")) { native.push(`Attached image: ${part.path}`); - } else if (part.size > MAX_INLINE_FILE_BYTES) { - const notice = `[Attached large file: ${part.fileName} at ${part.path} (${part.size} bytes)]`; - text.push(notice); - native.push(notice); } else { - try { - const content = await readFile(part.path, "utf8"); - const fileBlock = `[File: ${part.fileName}]\n${content}`; - text.push(fileBlock); - native.push(fileBlock); - } catch { - const notice = `[Attached file: ${part.fileName} at ${part.path}]`; - text.push(notice); - native.push(notice); - } + const reference = [ + `Uploaded file: ${part.fileName}`, + `Path: ${part.path}`, + `MIME: ${part.mimeType}`, + `Size: ${part.size} bytes`, + ].join("\n"); + text.push(reference); + native.push(reference); } - } else { - throw new AntigravityError( - `Antigravity does not support content type: ${(part as { type?: string }).type ?? "unknown"}`, - ); } } - return { text: text.join("\n"), nativeText: native.join("\n") }; } diff --git a/plugins/antigravity-provider/server/provider.test.ts b/plugins/antigravity-provider/server/provider.test.ts index 4cbb7c9433..94e98b5fa7 100644 --- a/plugins/antigravity-provider/server/provider.test.ts +++ b/plugins/antigravity-provider/server/provider.test.ts @@ -775,7 +775,7 @@ describe("image prompts", () => { }); describe("uploaded file prompts", () => { - it("inlines readable text files and references uploaded images and large files", async () => { + it("references uploaded text, images and large files without changing their bytes", async () => { const h = await harness(); await h.open(); const txtFile = path.join(h.cwd, "notes.txt"); @@ -815,8 +815,10 @@ describe("uploaded file prompts", () => { const sent = (await h.records()).find((entry) => entry.input).input.message.content; expect(sent).toBe( - `SYSTEM PREFIX\n\nPlease review these attachments:\n[File: notes.txt]\nexport const meaning = 42;\nAttached image: ${imgFile}\n[Attached large file: dataset.bin at /tmp/dataset.bin (3145728 bytes)]`, + `SYSTEM PREFIX\n\nPlease review these attachments:\nUploaded file: notes.txt\nPath: ${txtFile}\nMIME: text/plain\nSize: 26 bytes\nAttached image: ${imgFile}\nUploaded file: dataset.bin\nPath: /tmp/dataset.bin\nMIME: application/octet-stream\nSize: 3145728 bytes`, ); + expect(await readFile(txtFile, "utf8")).toBe("export const meaning = 42;"); + expect(await readFile(imgFile, "utf8")).toBe("png bytes"); }); }); @@ -835,3 +837,29 @@ it.runIf(process.platform === "win32")( }); }, ); + +it("preserves a usable reference for an uploaded binary file", async () => { + const h = await harness(); + await h.open(); + const file = path.join(h.cwd, "document.pdf"); + const bytes = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x00, 0xff, 0xfe]); + await writeFile(file, bytes); + await h.completed( + await h.message([ + { type: "text", text: "Read the attachment" }, + { + type: "uploaded_file", + id: "pdf-upload", + fileName: "document.pdf", + mimeType: "application/pdf", + size: 7, + path: file, + }, + ]), + ); + const sent = (await h.records()).find((entry) => entry.input).input.message.content; + expect(sent).toContain(file); + expect(sent).not.toContain("\u0000"); + expect(sent).not.toContain("\ufffd"); + expect(await readFile(file)).toEqual(bytes); +});