From b65124359c932fdf45ba802c603b1ad42e0ac46c Mon Sep 17 00:00:00 2001 From: Bil0000 <62337003+Bil0000@users.noreply.github.com> Date: Thu, 24 Sep 2026 03:44:10 +0200 Subject: [PATCH 1/3] fix: allow 100 MB file uploads without raising native media limits --- .../sharing/incoming-share-model.test.ts | 2 +- apps/mobile/src/lib/composerFiles.test.ts | 16 ++++--- .../src/assets/AttachmentUpload.test.ts | 27 ++++++++++++ .../src/environment/ServerEnvironment.test.ts | 2 +- .../provider/Layers/ProviderService.test.ts | 42 +++++++++++++++++++ .../acp/AntigravityAcpSupport.test.ts | 11 +++-- .../src/provider/acp/AntigravityAcpSupport.ts | 5 +-- .../opencodeRuntime.cliParsers.test.ts | 3 +- docs/user/composer.md | 10 ++--- .../src/state/attachments.test.ts | 3 ++ .../client-runtime/src/state/attachments.ts | 12 +++--- packages/contracts/src/assets.test.ts | 14 +++++++ packages/contracts/src/orchestration.test.ts | 3 ++ packages/contracts/src/orchestration.ts | 2 +- 14 files changed, 120 insertions(+), 32 deletions(-) diff --git a/apps/mobile/src/features/sharing/incoming-share-model.test.ts b/apps/mobile/src/features/sharing/incoming-share-model.test.ts index ce2011650e71..01beb7824a8b 100644 --- a/apps/mobile/src/features/sharing/incoming-share-model.test.ts +++ b/apps/mobile/src/features/sharing/incoming-share-model.test.ts @@ -176,7 +176,7 @@ describe("incoming native shares", () => { }); expect(result.attachments).toEqual([]); - expect(result.warnings).toEqual(["'huge.zip' exceeds the 50 MB attachment limit."]); + expect(result.warnings).toEqual(["'huge.zip' exceeds the 100 MB attachment limit."]); expect(persistFile).not.toHaveBeenCalled(); expect(removeOwnedFile).toHaveBeenCalledWith(file.value); }); diff --git a/apps/mobile/src/lib/composerFiles.test.ts b/apps/mobile/src/lib/composerFiles.test.ts index a99c2c6064e7..c29543bb54c9 100644 --- a/apps/mobile/src/lib/composerFiles.test.ts +++ b/apps/mobile/src/lib/composerFiles.test.ts @@ -446,10 +446,10 @@ describe("composer file attachments", () => { }, { reason: "server advertises more than the contract limit", - reported: 51 * 1024 * 1024, + reported: 100_000_001, stored: 42, - limit: 80 * 1024 * 1024, - error: "'clip.mov' exceeds the 50 MB attachment limit.", + limit: 200_000_000, + error: "'clip.mov' exceeds the 100 MB attachment limit.", }, ])( "rejects a video when $reason while retaining the selected photo", @@ -676,7 +676,7 @@ describe("composer file attachments", () => { expect(mocks.copy).not.toHaveBeenCalled(); }); - it("never accepts files above the 50 MB contract limit", async () => { + it("never accepts files above the 100 MB contract limit", async () => { mocks.pickFile.mockResolvedValue({ canceled: false, assets: [ @@ -684,16 +684,14 @@ describe("composer file attachments", () => { uri: "file:///downloads/archive.zip", name: "archive.zip", mimeType: "application/zip", - size: 51 * 1024 * 1024, + size: 100_000_001, }, ], }); - await expect( - pickComposerFiles({ existingCount: 0, maxBytes: 80 * 1024 * 1024 }), - ).resolves.toEqual({ + await expect(pickComposerFiles({ existingCount: 0, maxBytes: 200_000_000 })).resolves.toEqual({ files: [], - error: "'archive.zip' exceeds the 50 MB attachment limit.", + error: "'archive.zip' exceeds the 100 MB attachment limit.", }); }); diff --git a/apps/server/src/assets/AttachmentUpload.test.ts b/apps/server/src/assets/AttachmentUpload.test.ts index 6fffa1d1f9f2..7046e1937526 100644 --- a/apps/server/src/assets/AttachmentUpload.test.ts +++ b/apps/server/src/assets/AttachmentUpload.test.ts @@ -182,6 +182,33 @@ describe("AttachmentUpload", () => { }).pipe(Effect.provide(testLayer)), ); + it.effect("streams a 100 MB ZIP upload to disk", () => + Effect.gen(function* () { + const config = yield* ServerConfig.ServerConfig; + const issued = yield* issueAttachmentUploadUrl({ + type: "file", + name: "archive.zip", + mimeType: "application/zip", + sizeBytes: 100_000_000, + }); + const token = issued.relativeUrl.slice(ATTACHMENT_UPLOAD_ROUTE_PREFIX.length + 1); + const claims = yield* validateAttachmentUploadToken(token); + if (!claims) throw new Error("Expected valid upload claims."); + const chunk = new Uint8Array(1_000_000); + expect( + yield* storeAttachmentUpload( + claims, + Stream.fromIterable(Array.from({ length: 100 }, () => chunk)), + ), + ).toEqual({ ok: true }); + expect( + NodeFS.statSync(NodePath.join(config.attachmentsDir, `${issued.attachmentId}.zip`)).size, + ).toBe(100_000_000); + yield* deletePendingAttachment(issued.attachmentId); + expect(NodeFS.readdirSync(config.attachmentsDir)).toEqual([]); + }).pipe(Effect.provide(testLayer)), + ); + it.effect("removes partial streamed uploads that exceed their signed size", () => Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; diff --git a/apps/server/src/environment/ServerEnvironment.test.ts b/apps/server/src/environment/ServerEnvironment.test.ts index b4758e980065..83549e153d58 100644 --- a/apps/server/src/environment/ServerEnvironment.test.ts +++ b/apps/server/src/environment/ServerEnvironment.test.ts @@ -171,7 +171,7 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { expect(second.capabilities.repositoryIdentity).toBe(true); expect(second.capabilities.connectionProbe).toBe(true); expect(second.capabilities.attachmentUploads).toBe(true); - expect(second.capabilities.fileAttachments).toEqual({ maxUploadBytes: 50 * 1024 * 1024 }); + expect(second.capabilities.fileAttachments).toEqual({ maxUploadBytes: 100_000_000 }); expect(second.capabilities.pullRequests).toBe(true); expect(second.capabilities.requiredWorktreeBootstrap).toBe(true); expect(second.capabilities.usagePriceOverrides).toBe(true); diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index b9997e1df312..b21497b84d6e 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -986,6 +986,48 @@ it.effect("ProviderServiceLive rejects new sessions for disabled custom instance }).pipe(Effect.provide(NodeServices.layer)), ); +for (const name of ["codex", "claudeAgent", "cursor", "grok", "opencode", "antigravity"]) { + const driver = ProviderDriverKind.make(name); + const instanceId = ProviderInstanceId.make(name); + const adapter = makeFakeCodexAdapter(driver); + makeProviderServiceLayer({ + registry: makeStaticInstanceRegistry([[instanceId, adapter.adapter]]), + }).layer(`${name} file attachment limits`, (it) => { + it.effect("passes a 100 MB ZIP by path and rejects one byte more", () => + Effect.gen(function* () { + const provider = yield* ProviderService.ProviderService; + const threadId = asThreadId(`zip-${name}`); + yield* provider.startSession(threadId, { + provider: driver, + providerInstanceId: instanceId, + threadId, + runtimeMode: "full-access", + }); + const attachment = { + type: "file" as const, + id: "thread-attach-12345678-1234-1234-1234-123456789abc-zip", + name: "archive.zip", + mimeType: "application/zip", + sizeBytes: 100_000_000, + }; + yield* provider.sendTurn({ threadId, attachments: [attachment] }); + const sent = adapter.sendTurn.mock.calls[0]?.[0]; + assert.include(sent?.input ?? "", '[Attached file "archive.zip" is saved at: '); + assert.include(sent?.input ?? "", `${attachment.id}.zip]`); + assert.deepEqual(sent?.attachments, [attachment]); + const error = yield* provider + .sendTurn({ + threadId, + attachments: [{ ...attachment, sizeBytes: 100_000_001 }], + }) + .pipe(Effect.flip); + assert.instanceOf(error, ProviderValidationError); + assert.equal(adapter.sendTurn.mock.calls.length, 1); + }), + ); + }); +} + const routing = makeProviderServiceLayer(); const customCompactionDriver = ProviderDriverKind.make("custom-compaction-provider"); diff --git a/apps/server/src/provider/acp/AntigravityAcpSupport.test.ts b/apps/server/src/provider/acp/AntigravityAcpSupport.test.ts index aee10782a613..830cbe1095f7 100644 --- a/apps/server/src/provider/acp/AntigravityAcpSupport.test.ts +++ b/apps/server/src/provider/acp/AntigravityAcpSupport.test.ts @@ -2,7 +2,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; import { ANTIGRAVITY_DEFAULT_MODEL, - PROVIDER_SEND_TURN_MAX_FILE_BYTES, PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, type ChatAttachment, type RuntimeMode, @@ -403,7 +402,7 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => { it.effect.each([ { attachment: textAttachment, bytes: ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES + 1 }, { attachment: imageAttachment, bytes: PROVIDER_SEND_TURN_MAX_IMAGE_BYTES + 1 }, - { attachment: pdfAttachment, bytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES + 1 }, + { attachment: pdfAttachment, bytes: 50 * 1024 * 1024 + 1 }, ])( "rejects oversized $attachment.name using file size instead of upload metadata", ({ attachment, bytes }) => @@ -435,8 +434,8 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => { }; const first = yield* fixture.write(pdfAttachment, ""); const second = yield* fixture.write(secondAttachment, ""); - yield* fixture.fs.truncate(first.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES / 2); - yield* fixture.fs.truncate(second.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES / 2); + yield* fixture.fs.truncate(first.filePath, (50 * 1024 * 1024) / 2); + yield* fixture.fs.truncate(second.filePath, (50 * 1024 * 1024) / 2); const input = { input: undefined, attachments: [pdfAttachment, secondAttachment], @@ -448,7 +447,7 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => { { type: "resource_link", uri: second.uri, name: "second.pdf", mimeType: "application/pdf" }, ]); - yield* fixture.fs.truncate(second.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES / 2 + 1); + yield* fixture.fs.truncate(second.filePath, (50 * 1024 * 1024) / 2 + 1); const error = yield* buildAntigravityPrompt(input).pipe(Effect.flip); expect(error).toMatchObject({ _tag: "AcpRequestError", @@ -463,7 +462,7 @@ it.layer(NodeServices.layer)("buildAntigravityPrompt", (it) => { const fixture = yield* makeAttachmentFixture(); const pdf = yield* fixture.write(pdfAttachment, ""); const text = yield* fixture.write(textAttachment, "a"); - yield* fixture.fs.truncate(pdf.filePath, PROVIDER_SEND_TURN_MAX_FILE_BYTES - 1); + yield* fixture.fs.truncate(pdf.filePath, 50 * 1024 * 1024 - 1); const error = yield* buildAntigravityPrompt({ input: undefined, attachments: [pdfAttachment, textAttachment], diff --git a/apps/server/src/provider/acp/AntigravityAcpSupport.ts b/apps/server/src/provider/acp/AntigravityAcpSupport.ts index f2f370068181..98a06c4a28eb 100644 --- a/apps/server/src/provider/acp/AntigravityAcpSupport.ts +++ b/apps/server/src/provider/acp/AntigravityAcpSupport.ts @@ -1,7 +1,6 @@ import { ANTIGRAVITY_DEFAULT_MODEL, type AntigravityAuthMethod, - PROVIDER_SEND_TURN_MAX_FILE_BYTES, PROVIDER_SEND_TURN_MAX_IMAGE_BYTES, type ProviderSendTurnInput, type RuntimeMode, @@ -244,7 +243,7 @@ const TEXT_FILE_EXTENSIONS = new Set([ ".conf", ]); export const ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES = 1024 * 1024; -const MAX_TOTAL_ATTACHMENT_BYTES = PROVIDER_SEND_TURN_MAX_FILE_BYTES; +const MAX_TOTAL_ATTACHMENT_BYTES = 50 * 1024 * 1024; /** Sends uploads as native ACP content instead of workspace path hints. */ export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(function* (input: { @@ -317,7 +316,7 @@ export const buildAntigravityPrompt = Effect.fn("buildAntigravityPrompt")(functi : audio ? ANTIGRAVITY_MAX_AUDIO_ATTACHMENT_BYTES : pdf - ? PROVIDER_SEND_TURN_MAX_FILE_BYTES + ? MAX_TOTAL_ATTACHMENT_BYTES : ANTIGRAVITY_MAX_TEXT_ATTACHMENT_BYTES; totalBytes += size; if (info.type !== "File" || size > limit || totalBytes > MAX_TOTAL_ATTACHMENT_BYTES) { diff --git a/apps/server/src/provider/opencodeRuntime.cliParsers.test.ts b/apps/server/src/provider/opencodeRuntime.cliParsers.test.ts index c66a2f36a207..39df72f67c81 100644 --- a/apps/server/src/provider/opencodeRuntime.cliParsers.test.ts +++ b/apps/server/src/provider/opencodeRuntime.cliParsers.test.ts @@ -302,13 +302,14 @@ describe("toOpenCodeFileParts", () => { attachment("image/png"), // A ZIP file part makes OpenCode's Anthropic path throw before the // turn starts; it must ride only as the prompt's file path line. - attachment("application/zip"), + attachment("application/zip", 100_000_000), attachment("application/octet-stream"), // Image formats the model APIs reject stay on the fallback path too. attachment("image/bmp"), attachment("image/svg+xml"), // Over the direct-attachment limit: path fallback even for a PDF. attachment("application/pdf", 21 * 1024 * 1024), + attachment("application/pdf", 100_000_000), ], resolveAttachmentPath: () => "/tmp/attachment", }); diff --git a/docs/user/composer.md b/docs/user/composer.md index 6e699d0669c1..9e307020adc0 100644 --- a/docs/user/composer.md +++ b/docs/user/composer.md @@ -16,11 +16,11 @@ to keep a large paste editable in the composer instead. Attach up to 100 files per message. Each image can be up to 10 MiB, with at most 80 MiB of images in one message. Other files, including videos, can be up to -50 MiB each, subject to the environment's upload support and limit. The agent -receives them on the environment's machine. Provider and model limits still -apply, including images already in the conversation. A video attachment gives -the agent a file path; it does not enable native video input. Antigravity does -not accept video attachments. +100 MB (100,000,000 bytes) each, subject to the environment's upload support and +limit. The agent receives them on the environment's machine. Provider and model +limits still apply, including images already in the conversation. A video +attachment gives the agent a file path; it does not enable native video input. +Antigravity does not accept video attachments. Uploads begin when you add an attachment. All uploads must finish before the message can send. Retry or remove a failed upload. On web and desktop, reloading diff --git a/packages/client-runtime/src/state/attachments.test.ts b/packages/client-runtime/src/state/attachments.test.ts index f09c47362241..8a99082904f5 100644 --- a/packages/client-runtime/src/state/attachments.test.ts +++ b/packages/client-runtime/src/state/attachments.test.ts @@ -195,6 +195,9 @@ describe("file attachment limits", () => { expect(fileAttachmentTooLargeMessage("one.bin", 1024 * 1024)).toBe( "'one.bin' exceeds the 1 MB attachment limit.", ); + expect(fileAttachmentTooLargeMessage("archive.zip", 100_000_000)).toBe( + "'archive.zip' exceeds the 100 MB attachment limit.", + ); expect(fileAttachmentTooLargeMessage("big.zip", 50 * 1024 * 1024)).toBe( "'big.zip' exceeds the 50 MB attachment limit.", ); diff --git a/packages/client-runtime/src/state/attachments.ts b/packages/client-runtime/src/state/attachments.ts index 0dc457e2cade..d25ca26aa10a 100644 --- a/packages/client-runtime/src/state/attachments.ts +++ b/packages/client-runtime/src/state/attachments.ts @@ -227,10 +227,12 @@ export function formatAttachmentSize(sizeBytes: number): string { /** User-facing rejection for a file over the effective upload limit. */ export function fileAttachmentTooLargeMessage(name: string, maxUploadBytes: number): string { const maxUploadSize = - maxUploadBytes >= 1024 * 1024 && maxUploadBytes % (1024 * 1024) === 0 - ? `${maxUploadBytes / (1024 * 1024)} MB` - : maxUploadBytes >= 1024 && maxUploadBytes % 1024 === 0 - ? `${maxUploadBytes / 1024} KB` - : `${maxUploadBytes} ${maxUploadBytes === 1 ? "byte" : "bytes"}`; + maxUploadBytes >= 1_000_000 && maxUploadBytes % 1_000_000 === 0 + ? `${maxUploadBytes / 1_000_000} MB` + : maxUploadBytes >= 1024 * 1024 && maxUploadBytes % (1024 * 1024) === 0 + ? `${maxUploadBytes / (1024 * 1024)} MB` + : maxUploadBytes >= 1024 && maxUploadBytes % 1024 === 0 + ? `${maxUploadBytes / 1024} KB` + : `${maxUploadBytes} ${maxUploadBytes === 1 ? "byte" : "bytes"}`; return `'${name}' exceeds the ${maxUploadSize} attachment limit.`; } diff --git a/packages/contracts/src/assets.test.ts b/packages/contracts/src/assets.test.ts index c47c53b2a84e..623fb0b4e7a7 100644 --- a/packages/contracts/src/assets.test.ts +++ b/packages/contracts/src/assets.test.ts @@ -43,6 +43,20 @@ describe("AttachmentCreateUploadUrlInput", () => { ).toBe(true); }); + it.each([100_000_000, 100_000_001])( + "enforces the 100 MB file boundary at %s bytes", + (sizeBytes) => { + expect( + isUploadInput({ + type: "file", + name: "archive.zip", + mimeType: "application/zip", + sizeBytes, + }), + ).toBe(sizeBytes <= 100_000_000); + }, + ); + it("rejects empty and oversized uploads", () => { expect(isUploadInput({ ...uploadInput, sizeBytes: 0 })).toBe(false); expect( diff --git a/packages/contracts/src/orchestration.test.ts b/packages/contracts/src/orchestration.test.ts index 7c7bade4502e..9abcf61cea37 100644 --- a/packages/contracts/src/orchestration.test.ts +++ b/packages/contracts/src/orchestration.test.ts @@ -380,6 +380,9 @@ it.effect("rejects malformed known attachment types instead of tolerating them", updatedAt: "2026-01-01T00:00:00.000Z", }); + const maxFile = yield* decode({ ...base, type: "file", sizeBytes: 100_000_000 }); + assert.strictEqual(maxFile.attachments?.[0]?.sizeBytes, 100_000_000); + const oversizedFile = yield* Effect.exit( decode({ ...base, type: "file", sizeBytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES + 1 }), ); diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index 3e323e4964d5..a8481d95b1c9 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -166,7 +166,7 @@ export const PROVIDER_SEND_TURN_MAX_INPUT_CHARS = 120_000; export const PROVIDER_SEND_TURN_MAX_ATTACHMENTS = 100; export const PROVIDER_SEND_TURN_MAX_IMAGE_BYTES = 10 * 1024 * 1024; const PROVIDER_SEND_TURN_MAX_TOTAL_IMAGE_BYTES = 80 * 1024 * 1024; -export const PROVIDER_SEND_TURN_MAX_FILE_BYTES = 50 * 1024 * 1024; +export const PROVIDER_SEND_TURN_MAX_FILE_BYTES = 100_000_000; export const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES = [ "image/gif", "image/jpeg", From 2fa6fd8c00e028d95f1284b93fa3f9408de62cdf Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 24 Sep 2026 00:39:23 -0700 Subject: [PATCH 2/3] fix: report the 100 MB browser recording limit Browser recordings share the ordinary-file upload cap, so the tool description and too-large error still claimed 50 MiB after the cap rose. Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/server/src/mcp/toolkits/preview/tools.ts | 2 +- packages/contracts/src/previewAutomation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/mcp/toolkits/preview/tools.ts b/apps/server/src/mcp/toolkits/preview/tools.ts index 3f80e84e9a59..8f36f048648e 100644 --- a/apps/server/src/mcp/toolkits/preview/tools.ts +++ b/apps/server/src/mcp/toolkits/preview/tools.ts @@ -233,7 +233,7 @@ const PreviewRecordingStartTool = safeBrowserTool( const PreviewRecordingStopTool = safeBrowserTool( Tool.make("preview_recording_stop", { description: - "Stop recording the collaborative browser tab selected by tabId, or this agent session's current tab when omitted, and transfer the compressed recording once (up to 50 MiB) to an evidence file readable in this agent's environment. Returns its environment-local path after transfer succeeds.", + "Stop recording the collaborative browser tab selected by tabId, or this agent session's current tab when omitted, and transfer the compressed recording once (up to 100 MB) to an evidence file readable in this agent's environment. Returns its environment-local path after transfer succeeds.", parameters: PreviewAutomationTabTargetInput, success: Schema.Struct({ ...PreviewAutomationRecordingArtifact.fields, ...presentationFields }), failure: PreviewAutomationError, diff --git a/packages/contracts/src/previewAutomation.ts b/packages/contracts/src/previewAutomation.ts index 431e83189d22..5f9c3d2a1696 100644 --- a/packages/contracts/src/previewAutomation.ts +++ b/packages/contracts/src/previewAutomation.ts @@ -910,7 +910,7 @@ export class PreviewAutomationRecordingTooLargeError extends Schema.TaggedError< { threadId: ThreadId, cause: Schema.optional(Schema.Defect()) }, ) { override get message(): string { - return "The recording exceeds 50 MiB. The saved copy remains on the desktop."; + return "The recording exceeds 100 MB. The saved copy remains on the desktop."; } } From def7c5ff503d5e31b09350c59540c023dae4e11a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Fri, 25 Sep 2026 13:08:16 -0700 Subject: [PATCH 3/3] test: keep file cap tests valid after the reader relaxes Once #13429 lets ChatFileAttachment decode any positive size, message decoding and sendTurn no longer reject a file one byte over the upload cap. That boundary belongs to AttachmentCreateUploadUrlInput, which assets.test.ts already covers, so drop the read-side rejection assertions that would fail once both PRs land. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../server/src/provider/Layers/ProviderService.test.ts | 10 +--------- packages/contracts/src/orchestration.test.ts | 3 --- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index b21497b84d6e..6843ec2f97aa 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -993,7 +993,7 @@ for (const name of ["codex", "claudeAgent", "cursor", "grok", "opencode", "antig makeProviderServiceLayer({ registry: makeStaticInstanceRegistry([[instanceId, adapter.adapter]]), }).layer(`${name} file attachment limits`, (it) => { - it.effect("passes a 100 MB ZIP by path and rejects one byte more", () => + it.effect("passes a 100 MB ZIP by path", () => Effect.gen(function* () { const provider = yield* ProviderService.ProviderService; const threadId = asThreadId(`zip-${name}`); @@ -1015,14 +1015,6 @@ for (const name of ["codex", "claudeAgent", "cursor", "grok", "opencode", "antig assert.include(sent?.input ?? "", '[Attached file "archive.zip" is saved at: '); assert.include(sent?.input ?? "", `${attachment.id}.zip]`); assert.deepEqual(sent?.attachments, [attachment]); - const error = yield* provider - .sendTurn({ - threadId, - attachments: [{ ...attachment, sizeBytes: 100_000_001 }], - }) - .pipe(Effect.flip); - assert.instanceOf(error, ProviderValidationError); - assert.equal(adapter.sendTurn.mock.calls.length, 1); }), ); }); diff --git a/packages/contracts/src/orchestration.test.ts b/packages/contracts/src/orchestration.test.ts index 9abcf61cea37..7c7bade4502e 100644 --- a/packages/contracts/src/orchestration.test.ts +++ b/packages/contracts/src/orchestration.test.ts @@ -380,9 +380,6 @@ it.effect("rejects malformed known attachment types instead of tolerating them", updatedAt: "2026-01-01T00:00:00.000Z", }); - const maxFile = yield* decode({ ...base, type: "file", sizeBytes: 100_000_000 }); - assert.strictEqual(maxFile.attachments?.[0]?.sizeBytes, 100_000_000); - const oversizedFile = yield* Effect.exit( decode({ ...base, type: "file", sizeBytes: PROVIDER_SEND_TURN_MAX_FILE_BYTES + 1 }), );