source dump of claude code
at main 80 lines 2.7 kB view raw
1import type { 2 Base64ImageSource, 3 ContentBlockParam, 4 ImageBlockParam, 5} from '@anthropic-ai/sdk/resources/messages.mjs' 6import type { UUID } from 'crypto' 7import type { SDKMessage } from '../entrypoints/agentSdkTypes.js' 8import { detectImageFormatFromBase64 } from '../utils/imageResizer.js' 9 10/** 11 * Process an inbound user message from the bridge, extracting content 12 * and UUID for enqueueing. Supports both string content and 13 * ContentBlockParam[] (e.g. messages containing images). 14 * 15 * Normalizes image blocks from bridge clients that may use camelCase 16 * `mediaType` instead of snake_case `media_type` (mobile-apps#5825). 17 * 18 * Returns the extracted fields, or undefined if the message should be 19 * skipped (non-user type, missing/empty content). 20 */ 21export function extractInboundMessageFields( 22 msg: SDKMessage, 23): 24 | { content: string | Array<ContentBlockParam>; uuid: UUID | undefined } 25 | undefined { 26 if (msg.type !== 'user') return undefined 27 const content = msg.message?.content 28 if (!content) return undefined 29 if (Array.isArray(content) && content.length === 0) return undefined 30 31 const uuid = 32 'uuid' in msg && typeof msg.uuid === 'string' 33 ? (msg.uuid as UUID) 34 : undefined 35 36 return { 37 content: Array.isArray(content) ? normalizeImageBlocks(content) : content, 38 uuid, 39 } 40} 41 42/** 43 * Normalize image content blocks from bridge clients. iOS/web clients may 44 * send `mediaType` (camelCase) instead of `media_type` (snake_case), or 45 * omit the field entirely. Without normalization, the bad block poisons 46 * the session — every subsequent API call fails with 47 * "media_type: Field required". 48 * 49 * Fast-path scan returns the original array reference when no 50 * normalization is needed (zero allocation on the happy path). 51 */ 52export function normalizeImageBlocks( 53 blocks: Array<ContentBlockParam>, 54): Array<ContentBlockParam> { 55 if (!blocks.some(isMalformedBase64Image)) return blocks 56 57 return blocks.map(block => { 58 if (!isMalformedBase64Image(block)) return block 59 const src = block.source as unknown as Record<string, unknown> 60 const mediaType = 61 typeof src.mediaType === 'string' && src.mediaType 62 ? src.mediaType 63 : detectImageFormatFromBase64(block.source.data) 64 return { 65 ...block, 66 source: { 67 type: 'base64' as const, 68 media_type: mediaType as Base64ImageSource['media_type'], 69 data: block.source.data, 70 }, 71 } 72 }) 73} 74 75function isMalformedBase64Image( 76 block: ContentBlockParam, 77): block is ImageBlockParam & { source: Base64ImageSource } { 78 if (block.type !== 'image' || block.source?.type !== 'base64') return false 79 return !(block.source as unknown as Record<string, unknown>).media_type 80}