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 <chinaszlcx@gmail.com>
This commit is contained in:
paseo-bot[bot]
2026-11-01 06:07:46 +00:00
co-authored by ax
parent 7b53464b0d
commit f8c224d4e0
2 changed files with 47 additions and 29 deletions
@@ -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") };
}
@@ -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);
});