* Download attachment content from AgentMail, convert to file content objects * for the LLM, and upload to copilot storage for chat display.
( attachments: AgentMailAttachment[], inboxProviderId: string | null, messageId: string | null, taskId: string, userId: string )
| 431 | * for the LLM, and upload to copilot storage for chat display. |
| 432 | */ |
| 433 | async function downloadAttachmentContents( |
| 434 | attachments: AgentMailAttachment[], |
| 435 | inboxProviderId: string | null, |
| 436 | messageId: string | null, |
| 437 | taskId: string, |
| 438 | userId: string |
| 439 | ): Promise<DownloadedAttachments> { |
| 440 | if (!inboxProviderId || !messageId || attachments.length === 0) { |
| 441 | return { fileAttachments: [], storedAttachments: [] } |
| 442 | } |
| 443 | |
| 444 | const eligible = attachments.filter((a) => { |
| 445 | if (a.size > MAX_ATTACHMENT_SIZE) { |
| 446 | logger.info('Skipping large attachment', { taskId, filename: a.filename, size: a.size }) |
| 447 | return false |
| 448 | } |
| 449 | return true |
| 450 | }) |
| 451 | |
| 452 | const settled = await Promise.allSettled( |
| 453 | eligible.map(async (attachment) => { |
| 454 | const arrayBuffer = await agentmail.getAttachment( |
| 455 | inboxProviderId, |
| 456 | messageId, |
| 457 | attachment.attachment_id |
| 458 | ) |
| 459 | const buffer = Buffer.from(arrayBuffer) |
| 460 | const fileContent = createFileContent(buffer, attachment.content_type) |
| 461 | if (!fileContent) return null |
| 462 | |
| 463 | const storageKey = `copilot/${Date.now()}-${attachment.attachment_id}-${attachment.filename}` |
| 464 | const uploaded = await uploadFile({ |
| 465 | file: buffer, |
| 466 | fileName: attachment.filename, |
| 467 | contentType: attachment.content_type, |
| 468 | context: 'copilot', |
| 469 | customKey: storageKey, |
| 470 | preserveKey: true, |
| 471 | metadata: { userId, originalName: attachment.filename }, |
| 472 | }) |
| 473 | |
| 474 | const stored: StoredAttachment = { |
| 475 | id: attachment.attachment_id, |
| 476 | key: uploaded.key, |
| 477 | filename: attachment.filename, |
| 478 | media_type: attachment.content_type, |
| 479 | size: buffer.length, |
| 480 | } |
| 481 | |
| 482 | return { fileContent: { ...fileContent, filename: attachment.filename }, stored } |
| 483 | }) |
| 484 | ) |
| 485 | |
| 486 | const fileAttachments: Array<MessageContent & { filename: string }> = [] |
| 487 | const storedAttachments: StoredAttachment[] = [] |
| 488 | for (let i = 0; i < settled.length; i++) { |
| 489 | const outcome = settled[i] |
| 490 | if (outcome.status === 'fulfilled' && outcome.value) { |
no test coverage detected