Download src/tools/BriefTool/upload.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 5.82 kB
-
https://huggingface.co/chenbhao/codev/resolve/e91c8197c9918ff9e5df3140e8c36282bb30e9a2/src/tools/BriefTool/upload.ts
- Command line
-
hf download hf://chenbhao/codev@e91c8197c9918ff9e5df3140e8c36282bb30e9a2/src/tools/BriefTool/upload.ts
-
curl -L -o upload.ts https://huggingface.co/chenbhao/codev/resolve/e91c8197c9918ff9e5df3140e8c36282bb30e9a2/src/tools/BriefTool/upload.ts
5.82 kB
| /** | |
| * Upload BriefTool attachments to private_api so web viewers can preview them. | |
| * | |
| * When the repl bridge is active, attachment paths are meaningless to a web | |
| * viewer (they're on Claude's machine). We upload to /api/oauth/file_upload β | |
| * the same store MessageComposer/SpaceMessage render from β and stash the | |
| * returned file_uuid alongside the path. Web resolves file_uuid β preview; | |
| * desktop/local try path first. | |
| * | |
| * Best-effort: any failure (no token, bridge off, network error, 4xx) logs | |
| * debug and returns undefined. The attachment still carries {path, size, | |
| * isImage}, so local-terminal and same-machine-desktop render unaffected. | |
| */ | |
| import { feature } from 'bun:bundle' | |
| import axios from 'axios' | |
| import { randomUUID } from 'crypto' | |
| import { readFile } from 'fs/promises' | |
| import { basename, extname } from 'path' | |
| import { z } from 'zod/v4' | |
| import { | |
| getBridgeAccessToken, | |
| getBridgeBaseUrlOverride, | |
| } from '../../bridge/bridgeConfig.js' | |
| import { getOauthConfig } from '../../constants/oauth.js' | |
| import { logForDebugging } from '../../utils/debug.js' | |
| import { lazySchema } from '../../utils/lazySchema.js' | |
| import { jsonStringify } from '../../utils/slowOperations.js' | |
| // Matches the private_api backend limit | |
| const MAX_UPLOAD_BYTES = 30 * 1024 * 1024 | |
| const UPLOAD_TIMEOUT_MS = 30_000 | |
| // Backend dispatches on mime: image/* β upload_image_wrapped (writes | |
| // PREVIEW/THUMBNAIL, no ORIGINAL), everything else β upload_generic_file | |
| // (ORIGINAL only, no preview). Only whitelist raster formats the | |
| // transcoder reliably handles β svg/bmp/ico risk a 400, and pdf routes | |
| // to upload_pdf_file_wrapped which also skips ORIGINAL. Dispatch | |
| // viewers use /preview for images and /contents for everything else, | |
| // so images go image/* and the rest go octet-stream. | |
| const MIME_BY_EXT: Record<string, string> = { | |
| '.png': 'image/png', | |
| '.jpg': 'image/jpeg', | |
| '.jpeg': 'image/jpeg', | |
| '.gif': 'image/gif', | |
| '.webp': 'image/webp', | |
| } | |
| function guessMimeType(filename: string): string { | |
| const ext = extname(filename).toLowerCase() | |
| return MIME_BY_EXT[ext] ?? 'application/octet-stream' | |
| } | |
| function debug(msg: string): void { | |
| logForDebugging(`[brief:upload] ${msg}`) | |
| } | |
| /** | |
| * Base URL for uploads. Must match the host the token is valid for. | |
| * | |
| * Subprocess hosts (cowork) pass ANTHROPIC_BASE_URL alongside | |
| * CLAUDE_CODE_OAUTH_TOKEN β prefer that since getOauthConfig() only | |
| * returns staging when USE_STAGING_OAUTH is set, which such hosts don't | |
| * set. Without this a staging token hits api.anthropic.com β 401 β silent | |
| * skip β web viewer sees inert cards with no file_uuid. | |
| */ | |
| function getBridgeBaseUrl(): string { | |
| return ( | |
| getBridgeBaseUrlOverride() ?? | |
| process.env.ANTHROPIC_BASE_URL ?? | |
| getOauthConfig().BASE_API_URL | |
| ) | |
| } | |
| // /api/oauth/file_upload returns one of ChatMessage{Image,Blob,Document}FileSchema. | |
| // All share file_uuid; that's the only field we need. | |
| const uploadResponseSchema = lazySchema(() => | |
| z.object({ file_uuid: z.string() }), | |
| ) | |
| export type BriefUploadContext = { | |
| replBridgeEnabled: boolean | |
| signal?: AbortSignal | |
| } | |
| /** | |
| * Upload a single attachment. Returns file_uuid on success, undefined otherwise. | |
| * Every early-return is intentional graceful degradation. | |
| */ | |
| export async function uploadBriefAttachment( | |
| fullPath: string, | |
| size: number, | |
| ctx: BriefUploadContext, | |
| ): Promise<string | undefined> { | |
| // Positive pattern so bun:bundle eliminates the entire body from | |
| // non-BRIDGE_MODE builds (negative `if (!feature(...)) return` does not). | |
| if (feature('BRIDGE_MODE')) { | |
| if (!ctx.replBridgeEnabled) return undefined | |
| if (size > MAX_UPLOAD_BYTES) { | |
| debug(`skip ${fullPath}: ${size} bytes exceeds ${MAX_UPLOAD_BYTES} limit`) | |
| return undefined | |
| } | |
| const token = getBridgeAccessToken() | |
| if (!token) { | |
| debug('skip: no oauth token') | |
| return undefined | |
| } | |
| let content: Buffer | |
| try { | |
| content = await readFile(fullPath) | |
| } catch (e) { | |
| debug(`read failed for ${fullPath}: ${e}`) | |
| return undefined | |
| } | |
| const baseUrl = getBridgeBaseUrl() | |
| const url = `${baseUrl}/api/oauth/file_upload` | |
| const filename = basename(fullPath) | |
| const mimeType = guessMimeType(filename) | |
| const boundary = `----FormBoundary${randomUUID()}` | |
| // Manual multipart β same pattern as filesApi.ts. The oauth endpoint takes | |
| // a single "file" part (no "purpose" field like the public Files API). | |
| const body = Buffer.concat([ | |
| Buffer.from( | |
| `--${boundary}\r\n` + | |
| `Content-Disposition: form-data; name="file"; filename="${filename}"\r\n` + | |
| `Content-Type: ${mimeType}\r\n\r\n`, | |
| ), | |
| content, | |
| Buffer.from(`\r\n--${boundary}--\r\n`), | |
| ]) | |
| try { | |
| const response = await axios.post(url, body, { | |
| headers: { | |
| Authorization: `Bearer ${token}`, | |
| 'Content-Type': `multipart/form-data; boundary=${boundary}`, | |
| 'Content-Length': body.length.toString(), | |
| }, | |
| timeout: UPLOAD_TIMEOUT_MS, | |
| signal: ctx.signal, | |
| validateStatus: () => true, | |
| }) | |
| if (response.status !== 201) { | |
| debug( | |
| `upload failed for ${fullPath}: status=${response.status} body=${jsonStringify(response.data).slice(0, 200)}`, | |
| ) | |
| return undefined | |
| } | |
| const parsed = uploadResponseSchema().safeParse(response.data) | |
| if (!parsed.success) { | |
| debug( | |
| `unexpected response shape for ${fullPath}: ${parsed.error.message}`, | |
| ) | |
| return undefined | |
| } | |
| debug(`uploaded ${fullPath} β ${parsed.data.file_uuid} (${size} bytes)`) | |
| return parsed.data.file_uuid | |
| } catch (e) { | |
| debug(`upload threw for ${fullPath}: ${e}`) | |
| return undefined | |
| } | |
| } | |
| return undefined | |
| } | |