@@ -441,27 +441,38 @@ function registerGatewayHandlers(
441441 } ) => {
442442 try {
443443 let message = params . message ;
444- const imageAttachments : Array < { type : string ; mimeType : string ; fileName : string ; content : string } > = [ ] ;
444+ // The Gateway processes image attachments through TWO parallel paths:
445+ // Path A: `attachments` param → parsed via `parseMessageWithAttachments` →
446+ // injected as inline vision content when the model supports images.
447+ // Format: { content: base64, mimeType: string, fileName?: string }
448+ // Path B: `[media attached: ...]` in message text → Gateway's native image
449+ // detection (`detectAndLoadPromptImages`) reads the file from disk and
450+ // injects it as inline vision content. Also works for history messages.
451+ // We use BOTH paths for maximum reliability.
452+ const imageAttachments : Array < Record < string , unknown > > = [ ] ;
445453 const fileReferences : string [ ] = [ ] ;
446454
447455 if ( params . media && params . media . length > 0 ) {
448456 for ( const m of params . media ) {
449457 logger . info ( `[chat:sendWithMedia] Processing file: ${ m . fileName } (${ m . mimeType } ), path: ${ m . filePath } , exists: ${ existsSync ( m . filePath ) } , isVision: ${ VISION_MIME_TYPES . has ( m . mimeType ) } ` ) ;
458+
459+ // Always add file path reference so the model can access it via tools
460+ fileReferences . push (
461+ `[media attached: ${ m . filePath } (${ m . mimeType } ) | ${ m . filePath } ]` ,
462+ ) ;
463+
450464 if ( VISION_MIME_TYPES . has ( m . mimeType ) ) {
451- // Raster image — inline as base64 vision attachment
465+ // Send as base64 attachment in the format the Gateway expects:
466+ // { content: base64String, mimeType: string, fileName?: string }
467+ // The Gateway normalizer looks for `a.content` (NOT `a.source.data`).
452468 const fileBuffer = readFileSync ( m . filePath ) ;
453- logger . info ( `[chat:sendWithMedia] Read ${ fileBuffer . length } bytes, base64 length: ${ fileBuffer . toString ( 'base64' ) . length } ` ) ;
469+ const base64Data = fileBuffer . toString ( 'base64' ) ;
470+ logger . info ( `[chat:sendWithMedia] Read ${ fileBuffer . length } bytes, base64 length: ${ base64Data . length } ` ) ;
454471 imageAttachments . push ( {
455- type : 'image' ,
472+ content : base64Data ,
456473 mimeType : m . mimeType ,
457474 fileName : m . fileName ,
458- content : fileBuffer . toString ( 'base64' ) ,
459475 } ) ;
460- } else {
461- // Non-vision file — reference by path (same format as channel inbound media)
462- fileReferences . push (
463- `[media attached: ${ m . filePath } (${ m . mimeType } ) | ${ m . filePath } ]` ,
464- ) ;
465476 }
466477 }
467478 }
@@ -483,9 +494,9 @@ function registerGatewayHandlers(
483494 rpcParams . attachments = imageAttachments ;
484495 }
485496
486- logger . info ( `[chat:sendWithMedia] Sending: message="${ message . substring ( 0 , 100 ) } ", imageAttachments =${ imageAttachments . length } , fileRefs=${ fileReferences . length } ` ) ;
497+ logger . info ( `[chat:sendWithMedia] Sending: message="${ message . substring ( 0 , 100 ) } ", attachments =${ imageAttachments . length } , fileRefs=${ fileReferences . length } ` ) ;
487498
488- // Use a longer timeout when attachments are present (120s vs default 30s)
499+ // Use a longer timeout when images are present (120s vs default 30s)
489500 const timeoutMs = imageAttachments . length > 0 ? 120000 : 30000 ;
490501 const result = await gatewayManager . rpc ( 'chat.send' , rpcParams , timeoutMs ) ;
491502 logger . info ( `[chat:sendWithMedia] RPC result: ${ JSON . stringify ( result ) } ` ) ;
@@ -1557,4 +1568,26 @@ function registerFileHandlers(): void {
15571568
15581569 return { id, fileName : payload . fileName , mimeType, fileSize, stagedPath, preview } ;
15591570 } ) ;
1571+
1572+ // Load thumbnails for file paths on disk (used to restore previews in history)
1573+ ipcMain . handle ( 'media:getThumbnails' , async ( _ , paths : Array < { filePath : string ; mimeType : string } > ) => {
1574+ const results : Record < string , { preview : string | null ; fileSize : number } > = { } ;
1575+ for ( const { filePath, mimeType } of paths ) {
1576+ try {
1577+ if ( ! existsSync ( filePath ) ) {
1578+ results [ filePath ] = { preview : null , fileSize : 0 } ;
1579+ continue ;
1580+ }
1581+ const stat = statSync ( filePath ) ;
1582+ let preview : string | null = null ;
1583+ if ( mimeType . startsWith ( 'image/' ) ) {
1584+ preview = generateImagePreview ( filePath , mimeType ) ;
1585+ }
1586+ results [ filePath ] = { preview, fileSize : stat . size } ;
1587+ } catch {
1588+ results [ filePath ] = { preview : null , fileSize : 0 } ;
1589+ }
1590+ }
1591+ return results ;
1592+ } ) ;
15601593}
0 commit comments