@@ -13,6 +13,7 @@ import * as agentmail from '@/lib/mothership/inbox/agentmail-client'
1313import { formatEmailAsMessage } from '@/lib/mothership/inbox/format'
1414import { sendInboxResponse } from '@/lib/mothership/inbox/response'
1515import type { AgentMailAttachment } from '@/lib/mothership/inbox/types'
16+ import { uploadFile } from '@/lib/uploads/core/storage-service'
1617import { createFileContent , type MessageContent } from '@/lib/uploads/utils/file-utils'
1718import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1819
@@ -146,13 +147,14 @@ export async function executeInboxTask(taskId: string): Promise<void> {
146147 logger . warn ( 'Failed to fetch attachment metadata' , { taskId, attachErr } )
147148 }
148149 }
149- const fileAttachments = await downloadAttachmentContents (
150+ const downloaded = await downloadAttachmentContents (
150151 attachments ,
151152 ws . inboxProviderId ,
152153 inboxTask . agentmailMessageId ,
153- taskId
154+ taskId ,
155+ userId
154156 )
155- return { attachments, fileAttachments }
157+ return { attachments, ... downloaded }
156158 }
157159
158160 const [ attachmentResult , workspaceContext , integrationTools , userPermission ] =
@@ -162,7 +164,7 @@ export async function executeInboxTask(taskId: string): Promise<void> {
162164 buildIntegrationToolSchemas ( userId ) ,
163165 getUserEntityPermissions ( userId , 'workspace' , ws . id ) . catch ( ( ) => null ) ,
164166 ] )
165- const { attachments, fileAttachments } = attachmentResult
167+ const { attachments, fileAttachments, storedAttachments } = attachmentResult
166168
167169 const truncatedTask = {
168170 ...inboxTask ,
@@ -197,10 +199,17 @@ export async function executeInboxTask(taskId: string): Promise<void> {
197199 const cleanContent = stripThinkingTags ( result . content || '' )
198200
199201 if ( chatId ) {
200- await persistChatMessages ( chatId , userId , userMessageId , messageContent , {
201- ...result ,
202- content : cleanContent ,
203- } )
202+ await persistChatMessages (
203+ chatId ,
204+ userId ,
205+ userMessageId ,
206+ messageContent ,
207+ {
208+ ...result ,
209+ content : cleanContent ,
210+ } ,
211+ storedAttachments
212+ )
204213 }
205214
206215 const finalStatus = result . success ? 'completed' : 'failed'
@@ -295,7 +304,8 @@ async function persistChatMessages(
295304 userId : string ,
296305 userMessageId : string ,
297306 userContent : string ,
298- result : OrchestratorResult
307+ result : OrchestratorResult ,
308+ storedAttachments : StoredAttachment [ ] = [ ]
299309) : Promise < void > {
300310 try {
301311 const now = new Date ( ) . toISOString ( )
@@ -305,6 +315,7 @@ async function persistChatMessages(
305315 role : 'user' as const ,
306316 content : userContent ,
307317 timestamp : now ,
318+ ...( storedAttachments . length > 0 ? { fileAttachments : storedAttachments } : { } ) ,
308319 }
309320
310321 const assistantMessage = {
@@ -351,17 +362,33 @@ async function markTaskFailed(taskId: string, errorMessage: string): Promise<voi
351362
352363const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
353364
365+ interface StoredAttachment {
366+ id : string
367+ key : string
368+ filename : string
369+ media_type : string
370+ size : number
371+ }
372+
373+ interface DownloadedAttachments {
374+ fileAttachments : Array < MessageContent & { filename : string } >
375+ storedAttachments : StoredAttachment [ ]
376+ }
377+
354378/**
355- * Download attachment content from AgentMail and convert to file content objects
356- * that the orchestrator can pass to the LLM as multimodal content .
379+ * Download attachment content from AgentMail, convert to file content objects
380+ * for the LLM, and upload to copilot storage for chat display .
357381 */
358382async function downloadAttachmentContents (
359383 attachments : AgentMailAttachment [ ] ,
360384 inboxProviderId : string | null ,
361385 messageId : string | null ,
362- taskId : string
363- ) : Promise < Array < MessageContent & { filename : string } > > {
364- if ( ! inboxProviderId || ! messageId || attachments . length === 0 ) return [ ]
386+ taskId : string ,
387+ userId : string
388+ ) : Promise < DownloadedAttachments > {
389+ if ( ! inboxProviderId || ! messageId || attachments . length === 0 ) {
390+ return { fileAttachments : [ ] , storedAttachments : [ ] }
391+ }
365392
366393 const eligible = attachments . filter ( ( a ) => {
367394 if ( a . size > MAX_ATTACHMENT_SIZE ) {
@@ -381,15 +408,37 @@ async function downloadAttachmentContents(
381408 const buffer = Buffer . from ( arrayBuffer )
382409 const fileContent = createFileContent ( buffer , attachment . content_type )
383410 if ( ! fileContent ) return null
384- return { ...fileContent , filename : attachment . filename }
411+
412+ const storageKey = `copilot/${ Date . now ( ) } -${ attachment . attachment_id } -${ attachment . filename } `
413+ const uploaded = await uploadFile ( {
414+ file : buffer ,
415+ fileName : attachment . filename ,
416+ contentType : attachment . content_type ,
417+ context : 'copilot' ,
418+ customKey : storageKey ,
419+ preserveKey : true ,
420+ metadata : { userId, originalName : attachment . filename } ,
421+ } )
422+
423+ const stored : StoredAttachment = {
424+ id : attachment . attachment_id ,
425+ key : uploaded . key ,
426+ filename : attachment . filename ,
427+ media_type : attachment . content_type ,
428+ size : buffer . length ,
429+ }
430+
431+ return { fileContent : { ...fileContent , filename : attachment . filename } , stored }
385432 } )
386433 )
387434
388- const results : Array < MessageContent & { filename : string } > = [ ]
435+ const fileAttachments : Array < MessageContent & { filename : string } > = [ ]
436+ const storedAttachments : StoredAttachment [ ] = [ ]
389437 for ( let i = 0 ; i < settled . length ; i ++ ) {
390438 const outcome = settled [ i ]
391439 if ( outcome . status === 'fulfilled' && outcome . value ) {
392- results . push ( outcome . value )
440+ fileAttachments . push ( outcome . value . fileContent )
441+ storedAttachments . push ( outcome . value . stored )
393442 } else if ( outcome . status === 'rejected' ) {
394443 const attachment = eligible [ i ]
395444 logger . warn ( 'Failed to download attachment' , {
@@ -404,8 +453,8 @@ async function downloadAttachmentContents(
404453 logger . info ( 'Downloaded attachment contents' , {
405454 taskId,
406455 total : attachments . length ,
407- downloaded : results . length ,
456+ downloaded : fileAttachments . length ,
408457 } )
409458
410- return results
459+ return { fileAttachments , storedAttachments }
411460}
0 commit comments