Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.8 kB · 174 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175/** * 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 limitconst 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}