test(web): cover group uploads in the main conversation

The image E2E now runs in the main conversation, which is a group thread,
so the browser test covers group uploads and reads from the artifact route
as well as bot threads. Bot file attachments keep their run's operation id
for tracing, and the non-streaming attach cap is the inline cap directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
This commit is contained in:
Claude 2026-10-03 14:18:06 +00:00
parent 147611a8a1
commit d67a6275a4
No known key found for this signature in database
3 changed files with 11 additions and 5 deletions

View File

@ -54,10 +54,13 @@ test("an uploaded video streams from the artifact route and plays inline", async
await captureScreenshot(page, testInfo, "media-video-block");
});
test("the image lightbox downloads the original from the artifact route", async ({ page }) => {
test("an image in the main conversation opens and downloads from the artifact route", async ({
page,
}) => {
const stamp = Date.now();
await signup(page, `media-image-${stamp}@rakazo.test`, "password12", "Media Image");
await completeOnboarding(page);
// The main conversation is a group thread, so this covers group uploads and reads.
await completeOnboarding(page, undefined, { mainConversation: true });
await page
.locator('input[type="file"]')
@ -65,7 +68,7 @@ test("the image lightbox downloads the original from the artifact route", async
await page.getByRole("combobox", { name: /Message/ }).fill("a pixel");
await page.keyboard.press("Enter");
const thumbnail = page.getByRole("img", { name: "pixel.png" });
const thumbnail = page.getByTestId("transcript").getByRole("img", { name: "pixel.png" });
await expect(thumbnail).toBeVisible({ timeout: 30_000 });
await expect(thumbnail).toHaveAttribute("src", /^\/api\/artifacts\/[^/?]+$/);
await thumbnail.click();

View File

@ -47,6 +47,7 @@ export async function storeArtifactStream(
stream: ArtifactStreamPut["stream"];
maxBytes?: number;
signal?: AbortSignal;
operationId?: string;
},
) {
validateAttachmentMimeType(input.mimeType);
@ -54,7 +55,8 @@ export async function storeArtifactStream(
input.maxBytes ?? Number.POSITIVE_INFINITY,
artifactMaxBytes(input.mimeType),
);
const operationId = `artifact-store:${input.runId ?? input.botId ?? input.userId}`;
const operationId =
input.operationId ?? `artifact-store:${input.runId ?? input.botId ?? input.userId}`;
const context: AdapterContext = {
operationId,
traceId: operationId,

View File

@ -37,7 +37,7 @@ const ATTACHMENT_BLOCK_KINDS: ReadonlySet<string> = new Set(["image", "video", "
export function attachableFileMaxBytes(artifacts: ArtifactStore, mimeType: string): number {
return artifacts.describe().capabilities.stream
? artifactMaxBytes(mimeType)
: Math.min(ATTACHMENT_MAX_BYTES, artifactMaxBytes(mimeType));
: ATTACHMENT_MAX_BYTES;
}
export function formatMiB(bytes: number): string {
@ -82,6 +82,7 @@ export async function attachWorkspaceFileToThread(
yield bytes;
})(),
maxBytes,
operationId: input.operationId,
});
return { artifactId: row.id, block: messageBlockForArtifact(row) };
}