@@ -49,7 +49,7 @@ type StructuredAttachmentSource = {
4949 attachment : Record < string , unknown > ;
5050 key : string ;
5151 value : string ;
52- kind : "media" | "file" ;
52+ kind : "media" | "file" | "buffer" ;
5353 contentType ?: string ;
5454 filename ?: string ;
5555} ;
@@ -107,6 +107,7 @@ function collectStructuredAttachmentSources(
107107 if ( ! isRecord ( attachment ) ) {
108108 continue ;
109109 }
110+ let matched = false ;
110111 for ( const key of STRUCTURED_ATTACHMENT_MEDIA_SOURCE_PARAM_KEYS ) {
111112 const entry = resolveMediaParamEntry ( attachment , key ) ;
112113 if ( ! entry || ! normalizeOptionalString ( entry . value ) ) {
@@ -121,8 +122,31 @@ function collectStructuredAttachmentSources(
121122 readStringParam ( attachment , "contentType" ) ?? readStringParam ( attachment , "mimeType" ) ,
122123 filename : readStringParam ( attachment , "filename" ) ?? readStringParam ( attachment , "name" ) ,
123124 } ) ;
125+ matched = true ;
124126 break ;
125127 }
128+ if ( matched ) {
129+ continue ;
130+ }
131+ // Buffer-only attachments carry no media path/URL; surface them so the
132+ // runner can hydrate the buffer onto top-level args before channel dispatch.
133+ const bufferKey = resolveSnakeCaseParamKey ( attachment , "buffer" ) ;
134+ if ( ! bufferKey ) {
135+ continue ;
136+ }
137+ const bufferValue = readStringParam ( attachment , "buffer" , { trim : false } ) ;
138+ if ( ! bufferValue ) {
139+ continue ;
140+ }
141+ sources . push ( {
142+ attachment,
143+ key : bufferKey ,
144+ value : bufferValue ,
145+ kind : "buffer" ,
146+ contentType :
147+ readStringParam ( attachment , "contentType" ) ?? readStringParam ( attachment , "mimeType" ) ,
148+ filename : readStringParam ( attachment , "filename" ) ?? readStringParam ( attachment , "name" ) ,
149+ } ) ;
126150 }
127151 return sources ;
128152}
@@ -502,6 +526,15 @@ async function hydrateAttachmentActionPayload(params: {
502526 if ( attachmentSource ?. contentType && ! readStringParam ( params . args , "contentType" ) ) {
503527 params . args . contentType = attachmentSource . contentType ;
504528 }
529+ // Buffer-only structured attachments carry no media path/URL. Promote the
530+ // raw base64 to top-level args so downstream hydration treats them like a
531+ // direct buffer send instead of dropping the attachment.
532+ if (
533+ attachmentSource ?. kind === "buffer" &&
534+ ! readStringParam ( params . args , "buffer" , { trim : false } )
535+ ) {
536+ params . args . buffer = attachmentSource . value ;
537+ }
505538
506539 if ( params . allowMessageCaptionFallback ) {
507540 const caption = readStringParam ( params . args , "caption" , { allowEmpty : true } ) ?. trim ( ) ;
0 commit comments