From d8431388549d749ec30cd30765f4dfc1295fd84a Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 9 May 2026 22:20:27 +0530 Subject: [PATCH 1/4] feat(chat): migrate sandbox upload token handshake to api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flips `handleUploadUrl` in lib/sandboxes/uploadSandboxFiles.ts from the local /api/sandbox/upload route to the new ${getClientApiBaseUrl()}/api/sandboxes/staged-files on api, then deletes the now-unused local route. Closes Group 6 of the chat→api migration. Pairs with api PR for the staged-files handler and docs PR for the OpenAPI entry. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/sandbox/upload/route.ts | 41 ----------------------------- lib/sandboxes/uploadSandboxFiles.ts | 2 +- 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 app/api/sandbox/upload/route.ts diff --git a/app/api/sandbox/upload/route.ts b/app/api/sandbox/upload/route.ts deleted file mode 100644 index 2fb52414b..000000000 --- a/app/api/sandbox/upload/route.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { handleUpload, type HandleUploadBody } from "@vercel/blob/client"; -import { NextResponse } from "next/server"; - -/** - * POST /api/sandbox/upload - * - * Handles client-side Vercel Blob uploads by generating presigned tokens. - * Auth is validated via clientPayload (the blob client controls its own - * request headers, so we validate the token passed in the payload). - * Files are temporarily stored in Vercel Blob, then the client passes - * the blob URLs to the API which commits them to GitHub and cleans up. - */ -export async function POST(request: Request): Promise { - try { - const body = (await request.json()) as HandleUploadBody; - - const jsonResponse = await handleUpload({ - body, - request, - onBeforeGenerateToken: async (_pathname, clientPayload) => { - const payload = clientPayload ? JSON.parse(clientPayload) : null; - if (!payload?.token) { - throw new Error("Authentication required"); - } - - return { - maximumSizeInBytes: 100 * 1024 * 1024, // 100MB - addRandomSuffix: true, - }; - }, - onUploadCompleted: async () => {}, - }); - - return NextResponse.json(jsonResponse); - } catch (error) { - return NextResponse.json( - { error: error instanceof Error ? error.message : "Upload failed" }, - { status: 400 }, - ); - } -} diff --git a/lib/sandboxes/uploadSandboxFiles.ts b/lib/sandboxes/uploadSandboxFiles.ts index dcfc685c6..da221f207 100644 --- a/lib/sandboxes/uploadSandboxFiles.ts +++ b/lib/sandboxes/uploadSandboxFiles.ts @@ -40,7 +40,7 @@ export async function uploadSandboxFiles({ files.map(async (file) => { const blob = await upload(file.name, file, { access: "public", - handleUploadUrl: "/api/sandbox/upload", + handleUploadUrl: `${getClientApiBaseUrl()}/api/sandboxes/staged-files`, clientPayload: JSON.stringify({ token: accessToken }), }); return { url: blob.url, name: file.name }; From bc281b8b0d17133b26100f49bfdcd76867f3d16f Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 9 May 2026 22:47:07 +0530 Subject: [PATCH 2/4] refactor(chat): point sandbox upload at /api/sandboxes/stage-files Pairs with the api-side rename of /api/sandboxes/staged-files to /api/sandboxes/stage-files. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/sandboxes/uploadSandboxFiles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sandboxes/uploadSandboxFiles.ts b/lib/sandboxes/uploadSandboxFiles.ts index da221f207..2ee07cf57 100644 --- a/lib/sandboxes/uploadSandboxFiles.ts +++ b/lib/sandboxes/uploadSandboxFiles.ts @@ -40,7 +40,7 @@ export async function uploadSandboxFiles({ files.map(async (file) => { const blob = await upload(file.name, file, { access: "public", - handleUploadUrl: `${getClientApiBaseUrl()}/api/sandboxes/staged-files`, + handleUploadUrl: `${getClientApiBaseUrl()}/api/sandboxes/stage-files`, clientPayload: JSON.stringify({ token: accessToken }), }); return { url: blob.url, name: file.name }; From 3fe77d6df06538ecb5e190ba64ec4802739ba9b9 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 9 May 2026 22:50:47 +0530 Subject: [PATCH 3/4] refactor(chat): point sandbox upload at /api/sandboxes/staged-file Pairs with the api-side rename to the noun-shaped resource path. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/sandboxes/uploadSandboxFiles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sandboxes/uploadSandboxFiles.ts b/lib/sandboxes/uploadSandboxFiles.ts index 2ee07cf57..dd6267160 100644 --- a/lib/sandboxes/uploadSandboxFiles.ts +++ b/lib/sandboxes/uploadSandboxFiles.ts @@ -40,7 +40,7 @@ export async function uploadSandboxFiles({ files.map(async (file) => { const blob = await upload(file.name, file, { access: "public", - handleUploadUrl: `${getClientApiBaseUrl()}/api/sandboxes/stage-files`, + handleUploadUrl: `${getClientApiBaseUrl()}/api/sandboxes/staged-file`, clientPayload: JSON.stringify({ token: accessToken }), }); return { url: blob.url, name: file.name }; From 6982d75458c2a2251e9a1eeeddcb366d32a6a13a Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 9 May 2026 23:04:55 +0530 Subject: [PATCH 4/4] refactor(chat): pass sandbox staging auth via Authorization header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel Blob client's upload() supports a `headers` option that forwards arbitrary headers onto the handshake POST — replacing the clientPayload.token dance with a normal Bearer header. Pairs with the api-side switch to validateAuthContext(). Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/sandboxes/uploadSandboxFiles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sandboxes/uploadSandboxFiles.ts b/lib/sandboxes/uploadSandboxFiles.ts index dd6267160..df199d5af 100644 --- a/lib/sandboxes/uploadSandboxFiles.ts +++ b/lib/sandboxes/uploadSandboxFiles.ts @@ -41,7 +41,7 @@ export async function uploadSandboxFiles({ const blob = await upload(file.name, file, { access: "public", handleUploadUrl: `${getClientApiBaseUrl()}/api/sandboxes/staged-file`, - clientPayload: JSON.stringify({ token: accessToken }), + headers: { Authorization: `Bearer ${accessToken}` }, }); return { url: blob.url, name: file.name }; }),