authorgravatar for 62337003+Bil0000@users.noreply.github.comBilal Bakr <62337003+Bil0000@users.noreply.github.com> 2026-09-26 11:09:35+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-26 03:09:35-05:00
logdef34c28b8841002bb97a3238b82ac758cbdaa0f
tree09877d4bb294b67d3d5d915005cb2b00d0fea8a7
parent2679d279ce4b4879290bc756cf219b10f77e1873
signature Signed by PGP key B5690EEEBB952194

fix(server): let Antigravity inspect unsupported files by path (#13339)

Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

7 files changed, 182 insertions(+), 61 deletions(-)

apps/server/src/provider/Layers/AntigravityAdapter.ts+3-3
...@@ -786,9 +786,9 @@ export const makeAntigravityAdapter = Effect.fn("makeAntigravityAdapter")(functi...@@ -786,9 +786,9 @@ export const makeAntigravityAdapter = Effect.fn("makeAntigravityAdapter")(functi
786 stopOwned,786 stopOwned,
787 Effect.gen(function* () {787 Effect.gen(function* () {
788 const mcp = McpProviderSession.readMcpProviderSession(input.threadId);788 const mcp = McpProviderSession.readMcpProviderSession(input.threadId);
789 // The attachments dir grant lets the agent read pasted files at789 // The attachments dir grant lets the agent read path-only uploads
790 // the paths ProviderService injects into the turn text. It is a790 // at the paths ProviderService injects into the turn text. It is
791 // leaf directory holding only uploads.791 // a leaf directory holding only uploads.
792 const runtime = yield* options.makeRuntime({792 const runtime = yield* options.makeRuntime({
793 cwd,793 cwd,
794 clientInfo: { name: "t3-code", version: "0.0.0" },794 clientInfo: { name: "t3-code", version: "0.0.0" },
apps/server/src/provider/Layers/ProviderService.test.ts+51
...@@ -4801,6 +4801,57 @@ validation.layer("ProviderServiceLive validation", (it) => {...@@ -4801,6 +4801,57 @@ validation.layer("ProviderServiceLive validation", (it) => {
4801 }),4801 }),
4802 );4802 );
48034803
4804 it.effect("rejects a file when its path cannot fit in the prompt", () =>
4805 Effect.gen(function* () {
4806 const provider = yield* ProviderService.ProviderService;
4807 validation.codex.sendTurn.mockClear();
4808 const failure = yield* provider
4809 .sendTurn({
4810 threadId: asThreadId("thread-file-path-context-limit"),
4811 input: "x".repeat(PROVIDER_SEND_TURN_MAX_INPUT_CHARS),
4812 attachments: [
4813 {
4814 type: "file",
4815 id: "thread-attach-12345678-1234-1234-1234-123456789abc-zip",
4816 name: "archive.zip",
4817 mimeType: "application/zip",
4818 sizeBytes: 1024,
4819 },
4820 ],
4821 })
4822 .pipe(Effect.flip);
4823
4824 assert.instanceOf(failure, ProviderValidationError);
4825 assert.include(failure.issue, String(PROVIDER_SEND_TURN_MAX_INPUT_CHARS));
4826 assert.equal(validation.codex.sendTurn.mock.calls.length, 0);
4827 }),
4828 );
4829
4830 it.effect("sends a native image when its path cannot fit in the prompt", () =>
4831 Effect.gen(function* () {
4832 const provider = yield* ProviderService.ProviderService;
4833 const threadId = asThreadId("thread-image-path-context-limit");
4834 yield* provider.startSession(threadId, {
4835 provider: ProviderDriverKind.make("codex"),
4836 providerInstanceId: codexInstanceId,
4837 threadId,
4838 runtimeMode: "full-access",
4839 });
4840 validation.codex.sendTurn.mockClear();
4841 const attachment = {
4842 type: "image" as const,
4843 id: "thread-attach-12345678-1234-1234-1234-123456789abc-png",
4844 name: "screen.png",
4845 mimeType: "image/png",
4846 sizeBytes: 1024,
4847 };
4848 const input = "x".repeat(PROVIDER_SEND_TURN_MAX_INPUT_CHARS);
4849 yield* provider.sendTurn({ threadId, input, attachments: [attachment] });
4850 assert.equal(validation.codex.sendTurn.mock.calls[0]?.[0].input, input);
4851 assert.deepEqual(validation.codex.sendTurn.mock.calls[0]?.[0].attachments, [attachment]);
4852 }),
4853 );
4854
4804 it.effect("rejects citation-expanded input over the provider character limit", () =>4855 it.effect("rejects citation-expanded input over the provider character limit", () =>
4805 Effect.gen(function* () {4856 Effect.gen(function* () {
4806 const provider = yield* ProviderService.ProviderService;4857 const provider = yield* ProviderService.ProviderService;
apps/server/src/provider/Layers/ProviderService.ts+4-2
...@@ -1634,10 +1634,12 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (...@@ -1634,10 +1634,12 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
1634 ? `[Pasted text "${attachment.name}" is saved at: ${attachmentPath}. Inspect it as needed.]`1634 ? `[Pasted text "${attachment.name}" is saved at: ${attachmentPath}. Inspect it as needed.]`
1635 : `[Attached ${attachment.type} "${attachment.name}" is saved at: ${attachmentPath}]`,1635 : `[Attached ${attachment.type} "${attachment.name}" is saved at: ${attachmentPath}]`,
1636 );1636 );
1637 if (isPastedText && !appended) {1637 // Most adapters see generic files only through this path line, so a file
1638 // without one would be silently dropped. Images still go natively.
1639 if (!appended && attachment.type === "file") {
1638 return yield* toValidationError(1640 return yield* toValidationError(
1639 "ProviderService.sendTurn",1641 "ProviderService.sendTurn",
1640 `Input plus pasted-text attachment context exceeds the ${PROVIDER_SEND_TURN_MAX_INPUT_CHARS} character limit`,1642 `Input plus attachment context exceeds the ${PROVIDER_SEND_TURN_MAX_INPUT_CHARS} character limit`,
1641 );1643 );
1642 }1644 }
1643 }1645 }
apps/server/src/provider/acp/AntigravityAcpSupport.test.ts+94-38
...@@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";...@@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices";
2import { describe, expect, it } from "@effect/vitest";2import { describe, expect, it } from "@effect/vitest";
3import {3import {
4 ANTIGRAVITY_DEFAULT_MODEL,4 ANTIGRAVITY_DEFAULT_MODEL,
5 PROVIDER_SEND_TURN_MAX_FILE_BYTES,
6 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,5 PROVIDER_SEND_TURN_MAX_IMAGE_BYTES,
7 type ChatAttachment,6 type ChatAttachment,
8 type RuntimeMode,7 type RuntimeMode,
...@@ -15,7 +14,6 @@ import type * as EffectAcpSchema from "effect-acp/schema";...@@ -15,7 +14,6 @@ import type * as EffectAcpSchema from "effect-acp/schema";
1514
16import { resolveAttachmentPath } from "../../attachmentStore.ts";15import { resolveAttachmentPath } from "../../attachmentStore.ts";
17import {16import {
18 ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES,
19 antigravityPermissionMode,17 antigravityPermissionMode,
20 applyAntigravityAcpModelSelection,18 applyAntigravityAcpModelSelection,
21 buildAntigravityPrompt,19 buildAntigravityPrompt,
...@@ -324,6 +322,41 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {...@@ -324,6 +322,41 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {
324 }),322 }),
325 );323 );
326324
325 it.effect.each([
326 { name: "archive.zip", mimeType: "application/zip" },
327 { name: "clip.mp4", mimeType: "video/mp4" },
328 { name: "recording.aiff", mimeType: "audio/aiff" },
329 { name: "large.pdf", mimeType: "application/pdf", sizeBytes: 75_000_000 },
330 { name: "large.txt", mimeType: "text/plain", sizeBytes: 1024 * 1024 + 1 },
331 { name: "large.wav", mimeType: "audio/wav", sizeBytes: 20 * 1024 * 1024 + 1 },
332 ])("keeps $name as a file path without reading or spending the native media budget", (file) =>
333 Effect.gen(function* () {
334 const fixture = yield* makeAttachmentFixture();
335 const attachment = { ...textAttachment, sizeBytes: 50 * 1024 * 1024, ...file };
336 const upload = yield* fixture.write(attachment, "");
337 yield* fixture.fs.truncate(upload.filePath, attachment.sizeBytes);
338 const pdf = yield* fixture.write(pdfAttachment, "");
339 yield* fixture.fs.truncate(pdf.filePath, 50 * 1024 * 1024);
340 const input = `Inspect the file at ${upload.filePath}`;
341 const prompt = yield* buildAntigravityPrompt({
342 input,
343 attachments: [attachment, pdfAttachment],
344 attachmentsDir: fixture.attachmentsDir,
345 }).pipe(
346 Effect.provideService(FileSystem.FileSystem, {
347 ...fixture.fs,
348 stream: () => {
349 throw new Error("Path attachments must not be read into the prompt");
350 },
351 }),
352 );
353 expect(prompt).toEqual([
354 { type: "text", text: input },
355 { type: "resource_link", uri: pdf.uri, name: "report.pdf", mimeType: "application/pdf" },
356 ]);
357 }),
358 );
359
327 it.effect("sends supported audio files as native audio content", () =>360 it.effect("sends supported audio files as native audio content", () =>
328 Effect.gen(function* () {361 Effect.gen(function* () {
329 const fixture = yield* makeAttachmentFixture();362 const fixture = yield* makeAttachmentFixture();
...@@ -378,8 +411,6 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {...@@ -378,8 +411,6 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {
378411
379 it.effect.each([412 it.effect.each([
380 { ...imageAttachment, name: "animation.gif", mimeType: "image/gif" },413 { ...imageAttachment, name: "animation.gif", mimeType: "image/gif" },
381 { ...textAttachment, name: "archive.zip", mimeType: "application/zip" },
382 { ...textAttachment, name: "recording.aiff", mimeType: "audio/aiff" },
383 ] satisfies ReadonlyArray<ChatAttachment>)(414 ] satisfies ReadonlyArray<ChatAttachment>)(
384 "rejects $name instead of silently dropping it from a valid prompt",415 "rejects $name instead of silently dropping it from a valid prompt",
385 (attachment) =>416 (attachment) =>
...@@ -400,32 +431,25 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {...@@ -400,32 +431,25 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {
400 }),431 }),
401 );432 );
402433
403 it.effect.each([434 it.effect("rejects oversized images using file size instead of upload metadata", () =>
404 { attachment: textAttachment, bytes: ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES + 1 },435 Effect.gen(function* () {
405 { attachment: imageAttachment, bytes: PROVIDER_SEND_TURN_MAX_IMAGE_BYTES + 1 },436 const fixture = yield* makeAttachmentFixture();
406 { attachment: pdfAttachment, bytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES + 1 },437 const upload = yield* fixture.write(imageAttachment, "");
407 ])(438 yield* fixture.fs.truncate(upload.filePath, PROVIDER_SEND_TURN_MAX_IMAGE_BYTES + 1);
408 "rejects oversized $attachment.name using file size instead of upload metadata",439 const error = yield* buildAntigravityPrompt({
409 ({ attachment, bytes }) =>440 input: "Read this attachment.",
410 Effect.gen(function* () {441 attachments: [imageAttachment],
411 const fixture = yield* makeAttachmentFixture();442 attachmentsDir: fixture.attachmentsDir,
412 const upload = yield* fixture.write(attachment, "");443 }).pipe(Effect.flip);
413 yield* fixture.fs.truncate(upload.filePath, bytes);444 expect(error).toMatchObject({
414 const error = yield* buildAntigravityPrompt({445 _tag: "AcpRequestError",
415 input: "Read this attachment.",446 code: -32602,
416 attachments: [attachment],447 errorMessage: expect.stringContaining("'screen.png' is too large"),
417 attachmentsDir: fixture.attachmentsDir,448 });
418 }).pipe(Effect.flip);449 }),
419
420 expect(error).toMatchObject({
421 _tag: "AcpRequestError",
422 code: -32602,
423 errorMessage: expect.stringContaining(`'${attachment.name}' is too large`),
424 });
425 }),
426 );450 );
427451
428 it.effect("accepts 50 MiB in total but rejects one byte more across files", () =>452 it.effect("keeps PDF overflow on the file-path route", () =>
429 Effect.gen(function* () {453 Effect.gen(function* () {
430 const fixture = yield* makeAttachmentFixture();454 const fixture = yield* makeAttachmentFixture();
431 const secondAttachment = {455 const secondAttachment = {
...@@ -435,25 +459,57 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {...@@ -435,25 +459,57 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {
435 };459 };
436 const first = yield* fixture.write(pdfAttachment, "");460 const first = yield* fixture.write(pdfAttachment, "");
437 const second = yield* fixture.write(secondAttachment, "");461 const second = yield* fixture.write(secondAttachment, "");
438 yield* fixture.fs.truncate(first.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES / 2);462 yield* fixture.fs.truncate(first.filePath, (50 * 1024 * 1024) / 2);
439 yield* fixture.fs.truncate(second.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES / 2);463 yield* fixture.fs.truncate(second.filePath, (50 * 1024 * 1024) / 2);
440 const input = {464 const input = {
441 input: undefined,465 input: `Read ${first.filePath} and ${second.filePath}`,
442 attachments: [pdfAttachment, secondAttachment],466 attachments: [pdfAttachment, secondAttachment],
443 attachmentsDir: fixture.attachmentsDir,467 attachmentsDir: fixture.attachmentsDir,
444 };468 };
445 const prompt = yield* buildAntigravityPrompt(input);469 const prompt = yield* buildAntigravityPrompt(input);
446 expect(prompt).toEqual([470 expect(prompt).toEqual([
447 { type: "resource_link", uri: first.uri, name: "report.pdf", mimeType: "application/pdf" },471 { type: "text", text: input.input },
448 { type: "resource_link", uri: second.uri, name: "second.pdf", mimeType: "application/pdf" },472 {
473 type: "resource_link",
474 uri: first.uri,
475 name: "report.pdf",
476 mimeType: "application/pdf",
477 },
478 {
479 type: "resource_link",
480 uri: second.uri,
481 name: "second.pdf",
482 mimeType: "application/pdf",
483 },
449 ]);484 ]);
450485
451 yield* fixture.fs.truncate(second.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES / 2 + 1);486 yield* fixture.fs.truncate(second.filePath, (50 * 1024 * 1024) / 2 + 1);
452 const error = yield* buildAntigravityPrompt(input).pipe(Effect.flip);487 expect(yield* buildAntigravityPrompt(input)).toEqual([
488 { type: "text", text: input.input },
489 {
490 type: "resource_link",
491 uri: first.uri,
492 name: "report.pdf",
493 mimeType: "application/pdf",
494 },
495 ]);
496 }),
497 );
498
499 it.effect("still rejects images when the native budget is full", () =>
500 Effect.gen(function* () {
501 const fixture = yield* makeAttachmentFixture();
502 const pdf = yield* fixture.write(pdfAttachment, "");
503 yield* fixture.fs.truncate(pdf.filePath, 50 * 1024 * 1024);
504 yield* fixture.write(imageAttachment, new Uint8Array([1]));
505 const error = yield* buildAntigravityPrompt({
506 input: "Inspect both attachments.",
507 attachments: [pdfAttachment, imageAttachment],
508 attachmentsDir: fixture.attachmentsDir,
509 }).pipe(Effect.flip);
453 expect(error).toMatchObject({510 expect(error).toMatchObject({
454 _tag: "AcpRequestError",511 _tag: "AcpRequestError",
455 code: -32602,512 errorMessage: expect.stringContaining("'screen.png' is too large"),
456 errorMessage: expect.stringContaining("'second.pdf' is too large"),
457 });513 });
458 }),514 }),
459 );515 );
...@@ -463,7 +519,7 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {...@@ -463,7 +519,7 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => {
463 const fixture = yield* makeAttachmentFixture();519 const fixture = yield* makeAttachmentFixture();
464 const pdf = yield* fixture.write(pdfAttachment, "");520 const pdf = yield* fixture.write(pdfAttachment, "");
465 const text = yield* fixture.write(textAttachment, "a");521 const text = yield* fixture.write(textAttachment, "a");
466 yield* fixture.fs.truncate(pdf.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES - 1);522 yield* fixture.fs.truncate(pdf.filePath, 50 * 1024 * 1024 - 1);
467 const error = yield* buildAntigravityPrompt({523 const error = yield* buildAntigravityPrompt({
468 input: undefined,524 input: undefined,
469 attachments: [pdfAttachment, textAttachment],525 attachments: [pdfAttachment, textAttachment],
apps/server/src/provider/acp/AntigravityAcpSupport.ts+23-13
...@@ -243,10 +243,14 @@ const TEXT_FILE_EXTENSIONS = new Set([...@@ -243,10 +243,14 @@ const TEXT_FILE_EXTENSIONS = new Set([
243 ".ini",243 ".ini",
244 ".conf",244 ".conf",
245]);245]);
246export const ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES = 1024 * 1024;246const ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES = 1024 * 1024;
247const MAX_TOTAL_ATTACHMENT_BYTES = PROVIDER_SEND_TURN_MAX_FILE_BYTES;247const MAX_TOTAL_ATTACHMENT_BYTES = PROVIDER_SEND_TURN_MAX_FILE_BYTES;
248248
249/** Sends uploads as native ACP content instead of workspace path hints. */249/**
250 * Sends supported uploads as native ACP content. Other files, and native
251 * candidates over their limits, reach the agent through the saved path
252 * ProviderService puts in the text block.
253 */
250export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(function* (input: {254export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(function* (input: {
251 readonly input: ProviderSendTurnInput["input"];255 readonly input: ProviderSendTurnInput["input"];
252 readonly attachments: ProviderSendTurnInput["attachments"];256 readonly attachments: ProviderSendTurnInput["attachments"];
...@@ -280,7 +284,9 @@ export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(functi...@@ -280,7 +284,9 @@ export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(functi
280 (mimeType.startsWith("text/") ||284 (mimeType.startsWith("text/") ||
281 TEXT_MIME_TYPES.has(mimeType) ||285 TEXT_MIME_TYPES.has(mimeType) ||
282 TEXT_FILE_EXTENSIONS.has(path.extname(attachment.name).toLowerCase()));286 TEXT_FILE_EXTENSIONS.has(path.extname(attachment.name).toLowerCase()));
283 if (!image && !audio && !pdf && !textFile) {287 const isPathOnly =
288 attachment.type === "file" && (isPastedText || (!audio && !pdf && !textFile));
289 if (attachment.type === "image" && !image) {
284 return yield* EffectAcpErrors.AcpRequestError.invalidParams(290 return yield* EffectAcpErrors.AcpRequestError.invalidParams(
285 `Antigravity does not support '${attachment.name}' (${attachment.mimeType}). Attach a BMP, JPEG, PNG, WebP, PDF, audio, or text file.`,291 `Antigravity does not support '${attachment.name}' (${attachment.mimeType}). Attach a BMP, JPEG, PNG, WebP, PDF, audio, or text file.`,
286 );292 );
...@@ -303,26 +309,30 @@ export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(functi...@@ -303,26 +309,30 @@ export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(functi
303 ),309 ),
304 ),310 ),
305 );311 );
306 if (isPastedText) {312 if (info.type !== "File") {
307 if (info.type !== "File") {313 return yield* EffectAcpErrors.AcpRequestError.invalidParams(
308 return yield* EffectAcpErrors.AcpRequestError.invalidParams(314 `Could not read attachment '${attachment.name}'.`,
309 `Could not read attachment '${attachment.name}'.`,315 );
310 );
311 }
312 continue;
313 }316 }
317 if (isPathOnly) continue;
314 const size = Number(info.size);318 const size = Number(info.size);
315 const limit = image319 const limit = image
316 ? PROVIDER_SEND_TURN_MAX_IMAGE_BYTES320 ? PROVIDER_SEND_TURN_MAX_IMAGE_BYTES
317 : audio321 : audio
318 ? ANTIGRAVITY_MAX_AUDIO_ATTACHMENT_BYTES322 ? ANTIGRAVITY_MAX_AUDIO_ATTACHMENT_BYTES
319 : pdf323 : pdf
320 ? PROVIDER_SEND_TURN_MAX_FILE_BYTES324 ? MAX_TOTAL_ATTACHMENT_BYTES
321 : ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES;325 : ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES;
326 if (
327 attachment.type === "file" &&
328 (size > limit || totalBytes + size > MAX_TOTAL_ATTACHMENT_BYTES)
329 ) {
330 continue;
331 }
322 totalBytes += size;332 totalBytes += size;
323 if (info.type !== "File" || size > limit || totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) {333 if (size > limit || totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) {
324 return yield* EffectAcpErrors.AcpRequestError.invalidParams(334 return yield* EffectAcpErrors.AcpRequestError.invalidParams(
325 `Attachment '${attachment.name}' is too large. Antigravity accepts text files up to 1 MiB, images up to 10 MiB, audio up to 20 MiB, and 50 MiB total attachments.`,335 `Image '${attachment.name}' is too large. Antigravity accepts images up to 10 MiB and 50 MiB of native attachments per message.`,
326 );336 );
327 }337 }
328 const uri = yield* path.toFileUrl(attachmentPath).pipe(338 const uri = yield* path.toFileUrl(attachmentPath).pipe(
docs/user/composer.md+1-2
...@@ -19,8 +19,7 @@ Attach up to 100 files per message. Each image can be up to 10 MiB, with at most...@@ -19,8 +19,7 @@ Attach up to 100 files per message. Each image can be up to 10 MiB, with at most
1950 MiB each, subject to the environment's upload support and limit. The agent1950 MiB each, subject to the environment's upload support and limit. The agent
20receives them on the environment's machine. Provider and model limits still20receives them on the environment's machine. Provider and model limits still
21apply, including images already in the conversation. A video attachment gives21apply, including images already in the conversation. A video attachment gives
22the agent a file path; it does not enable native video input. Antigravity does22the agent a file path; it does not enable native video input.
23not accept video attachments.
2423
25Uploads begin when you add an attachment. All uploads must finish before the24Uploads begin when you add an attachment. All uploads must finish before the
26message can send. Retry or remove a failed upload. On web and desktop, reloading25message can send. Retry or remove a failed upload. On web and desktop, reloading
docs/user/providers-antigravity.md+6-3
...@@ -94,9 +94,12 @@ so a skill there only appears when the project itself is your home directory....@@ -94,9 +94,12 @@ so a skill there only appears when the project itself is your home directory.
9494
95Antigravity accepts images, PDFs, text files, and supported audio formats directly.95Antigravity accepts images, PDFs, text files, and supported audio formats directly.
96Its limits are 1 MiB per text file, 10 MiB per image, 20 MiB per audio clip, and96Its limits are 1 MiB per text file, 10 MiB per image, 20 MiB per audio clip, and
9750 MiB total attachments per message. Unsupported formats are rejected. These9750 MiB total native attachments per message. PDFs, text, and audio that exceed
98limits can be lower than the general upload limit; uploading a file does not98their native limits or the remaining native budget are passed as file paths
99mean this provider can use it.99for the agent to inspect with tools. ZIP archives and videos also use file paths.
100These files do not count toward the native attachment budget or enable native video input.
101Unsupported image formats are rejected. The native limits can be lower than
102the general upload limit.
100103
101### Subagents104### Subagents
102105