diff --git a/plugins/antigravity-provider/server/internal/prompt.ts b/plugins/antigravity-provider/server/internal/prompt.ts index 07f4298b35..a9d57c9149 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, rm, writeFile } from "node:fs/promises"; +import { chmod, mkdtemp, readFile, 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,6 +10,8 @@ interface EncodedPrompt { nativeText: string; } +const MAX_INLINE_FILE_BYTES = 2 * 1024 * 1024; // 2 MB + export class PromptFiles { private directory: string | null = null; @@ -20,19 +22,16 @@ export class PromptFiles { prompt.outputSchema !== undefined ) throw new AntigravityError("Antigravity supports messages only"); - const parts = prompt.input.content.map((part) => { - if (part.type === "text") return part; - if (part.type === "image") return { ...part, extension: imageExtension(part.mimeType) }; - throw new AntigravityError("Antigravity supports text and image content only"); - }); + const text: string[] = []; const native: string[] = []; - for (const part of parts) { + + for (const part of prompt.input.content) { if (part.type === "text") { text.push(part.text); native.push(part.text); } else if (part.type === "image") { - const extension = part.extension; + const extension = imageExtension(part.mimeType); if (this.directory === null) { this.directory = await mkdtemp(path.join(os.tmpdir(), "paseo-antigravity-images-")); await chmod(this.directory, 0o700); @@ -41,8 +40,32 @@ export class PromptFiles { await writeFile(file, Buffer.from(part.data, "base64"), { flag: "wx", mode: 0o600 }); await chmod(file, 0o600); native.push(`Attached image: ${file}`); + } else if (part.type === "uploaded_file") { + if (part.mimeType && 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); + } + } + } 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 631f10a6fe..4cbb7c9433 100644 --- a/plugins/antigravity-provider/server/provider.test.ts +++ b/plugins/antigravity-provider/server/provider.test.ts @@ -774,6 +774,52 @@ describe("image prompts", () => { }); }); +describe("uploaded file prompts", () => { + it("inlines readable text files and references uploaded images and large files", async () => { + const h = await harness(); + await h.open(); + const txtFile = path.join(h.cwd, "notes.txt"); + await writeFile(txtFile, "export const meaning = 42;"); + const imgFile = path.join(h.cwd, "photo.png"); + await writeFile(imgFile, "png bytes"); + + await h.completed( + await h.message([ + { type: "text", text: "Please review these attachments:" }, + { + type: "uploaded_file", + id: "upload-txt-1", + fileName: "notes.txt", + mimeType: "text/plain", + size: 26, + path: txtFile, + }, + { + type: "uploaded_file", + id: "upload-img-1", + fileName: "photo.png", + mimeType: "image/png", + size: 9, + path: imgFile, + }, + { + type: "uploaded_file", + id: "upload-big-1", + fileName: "dataset.bin", + mimeType: "application/octet-stream", + size: 3 * 1024 * 1024, + path: "/tmp/dataset.bin", + }, + ]), + ); + + 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)]`, + ); + }); +}); + it.runIf(process.platform === "win32")( "launches an Antigravity command shim for probes and conversation startup", async () => {