feat: include email attachments in agent.generate + tool calls#250
Conversation
Fetch attachment download URLs from Resend, append them to email text so sandbox/tools can download files, and inject image parts into agent messages so the LLM can visually process images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds email attachment support: new Resend-based attachment fetcher, a formatter that renders attachments as text, integration into inbound email validation to append formatted attachment info, and extra logging plus a SANDBOX prompt note expansion. Changes
Sequence DiagramsequenceDiagram
participant Inbound as Inbound Service
participant Resend as Resend API
participant Formatter as Format Module
participant Chat as Chat Service
Inbound->>Resend: getEmailAttachments(emailId)
Resend-->>Inbound: [EmailAttachment...]
Inbound->>Formatter: formatAttachmentsText(attachments)
Formatter-->>Inbound: "Attached files:\n- name (type): url"
Inbound->>Chat: append formatted text to email body and send chatRequestBody
Chat-->>Inbound: response/result
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ❌ 1❌ Failed checks (1 warning)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
lib/emails/inbound/validateNewEmailMemory.ts (1)
34-36: Consider graceful handling if attachment fetch fails.If
getEmailAttachmentsthrows (network error, API failure), the entire email processing will fail. Depending on business requirements, you may want to continue processing the email without attachments rather than failing completely:🛡️ Proposed graceful fallback
- const attachments = await getEmailAttachments(emailId); + let attachments: Awaited<ReturnType<typeof getEmailAttachments>> = []; + try { + attachments = await getEmailAttachments(emailId); + } catch (error) { + console.error(`Failed to fetch attachments for email ${emailId}, continuing without:`, error); + } const emailText = trimRepliedContext(emailContent.html || "") + formatAttachmentsText(attachments);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/emails/inbound/validateNewEmailMemory.ts` around lines 34 - 36, Wrap the call to getEmailAttachments(emailId) in a try/catch inside validateNewEmailMemory (or the function containing this diff) so that if getEmailAttachments throws (network/API error) you log the error and proceed with attachments = [] instead of letting the entire flow fail; then build emailText using trimRepliedContext(...) + formatAttachmentsText(attachments) as before. Optionally emit a telemetry/metric or processLogger.warn mentioning the emailId and the attachment fetch failure for visibility.lib/emails/inbound/generateEmailResponse.ts (1)
32-54: Consider extracting attachment injection logic for better testability.The image attachment injection logic (lines 32-54) could be extracted into a dedicated helper function like
injectImageAttachments(messages, attachments). This would improve testability and align with SRP, allowing this function to focus on orchestration while the helper handles the message transformation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/emails/inbound/generateEmailResponse.ts` around lines 32 - 54, Extract the image-attachment injection block into a helper function named injectImageAttachments(messages, attachments) that takes the existing messages array and attachments list, filters image attachments (imageAttachments), finds the last user message index (lastUserIdx), builds the parts array (preserving the original textContent and pushing image entries with new URL(att.downloadUrl) and mimeType att.contentType), and replaces messages[lastUserIdx].content with the parts cast to ModelMessage["content"]; then replace the inline block in generateEmailResponse.ts with a call to injectImageAttachments(messages, attachments) and export the helper (or keep it top-level) so it can be unit tested while maintaining identical behavior and types.lib/emails/inbound/getEmailAttachments.ts (1)
24-29: Missing validation for required response fields.The mapping assumes
att.content_typeandatt.download_urlare always present. If the API returns unexpected data, this could result inundefinedvalues in the returned objects.🛡️ Proposed defensive filtering
- return data.data.map(att => ({ - id: att.id, - filename: att.filename || "attachment", - contentType: att.content_type, - downloadUrl: att.download_url, - })); + return data.data + .filter(att => att.id && att.content_type && att.download_url) + .map(att => ({ + id: att.id, + filename: att.filename || "attachment", + contentType: att.content_type, + downloadUrl: att.download_url, + }));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/emails/inbound/getEmailAttachments.ts` around lines 24 - 29, The mapping in getEmailAttachments currently assumes att.content_type and att.download_url exist, which can yield undefined; update the logic that processes data.data (the att items) to defensively filter out or skip any att entries where att.content_type or att.download_url are falsy before mapping, and/or supply a safe default only for filename (keep filename fallback "attachment"), ensure the returned objects (contentType and downloadUrl) are never undefined by either excluding those attachments or throwing/logging a clear warning inside the same function (referencing the att.id, att.content_type, att.download_url and the produced keys contentType/downloadUrl to locate the change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/emails/inbound/generateEmailResponse.ts`:
- Around line 39-51: The code in generateEmailResponse.ts overwrites existing
multipart message content by coercing msg.content to an empty string; instead,
detect whether msg.content is already an array and preserve/merge it: if
msg.content is an array use that as the initial parts (or concat new image
parts) else create a text part from the string as currently done; update the
logic around messages[lastUserIdx] and the parts construction (referencing msg,
messages, lastUserIdx, parts, imageAttachments, and ModelMessage["content"]) so
existing multipart content is retained and new image parts are appended.
- Line 47: The code constructs an image URL with image: new URL(att.downloadUrl)
which can throw on malformed URLs; update the logic in generateEmailResponse (or
the surrounding function where att is processed) to defensively validate/parse
att.downloadUrl: attempt to create the URL inside a try/catch (or pre-validate
with a safe URL-check helper), and on failure set the image field to
undefined/null or skip attaching the image and log the bad URL (include
att.downloadUrl and context) instead of allowing the exception to propagate.
- Around line 41-51: The current manual parts array and cast around
messages[lastUserIdx] use a loose shape and then force-cast to
ModelMessage["content"]; instead import the ai library discriminated types (e.g.
ModelMessage plus TextPart, ImagePart, FilePart) and type the parts as
Array<TextPart | ImagePart | FilePart>, then build each part using the exact
property names required by those types (use the ImagePart shape for image
attachments and TextPart for textContent) and assign directly to
messages[lastUserIdx].content without any cast so the compiler validates the
shapes (reference symbols: ModelMessage, TextPart, ImagePart, the parts
variable, and messages[lastUserIdx] / msg).
In `@lib/emails/inbound/getEmailAttachments.ts`:
- Around line 18-22: The call to resend.emails.receiving.attachments.list inside
getEmailAttachments is missing error handling; wrap the API call in a try/catch
around getResendClient() and the list call (in function getEmailAttachments) and
handle failures by either returning an empty array on recoverable errors or
re-throwing a new Error with contextual information (including emailId and the
original error) so callers can distinguish Resend failures; ensure the catch
uses the resend client reference (getResendClient) and the method name
(resend.emails.receiving.attachments.list) in the logged/thrown message for
easier debugging.
---
Nitpick comments:
In `@lib/emails/inbound/generateEmailResponse.ts`:
- Around line 32-54: Extract the image-attachment injection block into a helper
function named injectImageAttachments(messages, attachments) that takes the
existing messages array and attachments list, filters image attachments
(imageAttachments), finds the last user message index (lastUserIdx), builds the
parts array (preserving the original textContent and pushing image entries with
new URL(att.downloadUrl) and mimeType att.contentType), and replaces
messages[lastUserIdx].content with the parts cast to ModelMessage["content"];
then replace the inline block in generateEmailResponse.ts with a call to
injectImageAttachments(messages, attachments) and export the helper (or keep it
top-level) so it can be unit tested while maintaining identical behavior and
types.
In `@lib/emails/inbound/getEmailAttachments.ts`:
- Around line 24-29: The mapping in getEmailAttachments currently assumes
att.content_type and att.download_url exist, which can yield undefined; update
the logic that processes data.data (the att items) to defensively filter out or
skip any att entries where att.content_type or att.download_url are falsy before
mapping, and/or supply a safe default only for filename (keep filename fallback
"attachment"), ensure the returned objects (contentType and downloadUrl) are
never undefined by either excluding those attachments or throwing/logging a
clear warning inside the same function (referencing the att.id,
att.content_type, att.download_url and the produced keys contentType/downloadUrl
to locate the change).
In `@lib/emails/inbound/validateNewEmailMemory.ts`:
- Around line 34-36: Wrap the call to getEmailAttachments(emailId) in a
try/catch inside validateNewEmailMemory (or the function containing this diff)
so that if getEmailAttachments throws (network/API error) you log the error and
proceed with attachments = [] instead of letting the entire flow fail; then
build emailText using trimRepliedContext(...) +
formatAttachmentsText(attachments) as before. Optionally emit a telemetry/metric
or processLogger.warn mentioning the emailId and the attachment fetch failure
for visibility.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
lib/emails/inbound/__tests__/formatAttachmentsText.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/emails/inbound/__tests__/generateEmailResponse.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/emails/inbound/__tests__/getEmailAttachments.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/emails/inbound/__tests__/validateNewEmailMemory.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (5)
lib/chat/validateChatRequest.tslib/emails/inbound/formatAttachmentsText.tslib/emails/inbound/generateEmailResponse.tslib/emails/inbound/getEmailAttachments.tslib/emails/inbound/validateNewEmailMemory.ts
| const msg = messages[lastUserIdx]; | ||
| const textContent = typeof msg.content === "string" ? msg.content : ""; | ||
| const parts: Array<{ type: string; text?: string; image?: URL; mimeType?: string }> = [ | ||
| { type: "text", text: textContent }, | ||
| ]; | ||
| for (const att of imageAttachments) { | ||
| parts.push({ | ||
| type: "image", | ||
| image: new URL(att.downloadUrl), | ||
| mimeType: att.contentType, | ||
| }); | ||
| } | ||
| messages[lastUserIdx] = { ...msg, content: parts as ModelMessage["content"] }; |
There was a problem hiding this comment.
Potential data loss when message content is already multipart.
If msg.content is already an array of content parts (not a string), line 40 converts it to an empty string, losing the original content. Consider handling array content:
const msg = messages[lastUserIdx];
-const textContent = typeof msg.content === "string" ? msg.content : "";
+const existingParts = typeof msg.content === "string"
+ ? [{ type: "text" as const, text: msg.content }]
+ : Array.isArray(msg.content) ? msg.content : [];
const parts: Array<{ type: string; text?: string; image?: URL; mimeType?: string }> = [
- { type: "text", text: textContent },
+ ...existingParts,
];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/emails/inbound/generateEmailResponse.ts` around lines 39 - 51, The code
in generateEmailResponse.ts overwrites existing multipart message content by
coercing msg.content to an empty string; instead, detect whether msg.content is
already an array and preserve/merge it: if msg.content is an array use that as
the initial parts (or concat new image parts) else create a text part from the
string as currently done; update the logic around messages[lastUserIdx] and the
parts construction (referencing msg, messages, lastUserIdx, parts,
imageAttachments, and ModelMessage["content"]) so existing multipart content is
retained and new image parts are appended.
Construct a proper UserModelMessage instead of spreading a generic ModelMessage, fixing the discriminated union type incompatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
♻️ Duplicate comments (2)
lib/emails/inbound/generateEmailResponse.ts (2)
39-40:⚠️ Potential issue | 🟡 MinorPotential data loss when
msg.contentis already multipart.If
msg.contentis already an array of content parts (not a string), line 40 converts it to an empty string, silently discarding the original content. WhilegetEmailRoomMessagescurrently returns string content, theModelMessagetype allows arrays—this creates fragile coupling.🛡️ Proposed defensive handling
const msg = messages[lastUserIdx]; -const textContent = typeof msg.content === "string" ? msg.content : ""; -const parts: UserModelMessage["content"] = [ - { type: "text", text: textContent }, +const existingParts: UserModelMessage["content"] = + typeof msg.content === "string" + ? [{ type: "text", text: msg.content }] + : Array.isArray(msg.content) + ? msg.content + : []; +const parts: UserModelMessage["content"] = [ + ...existingParts,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/emails/inbound/generateEmailResponse.ts` around lines 39 - 40, The current generateEmailResponse logic grabs the last user message (msg from messages[lastUserIdx]) but forces non-string msg.content to an empty string, risking data loss if content is an array per the ModelMessage type; update the assignment of textContent in generateEmailResponse to defensively handle arrays and other non-string types returned by getEmailRoomMessages — e.g., if msg.content is an array, join parts (or JSON.stringify when parts are objects), if it's an object serialize it appropriately, and otherwise fallback to an empty string — so that msg, messages, lastUserIdx, and textContent are preserved without silently discarding content.
43-47:⚠️ Potential issue | 🟡 Minor
new URL()can throw on malformed URLs.If
att.downloadUrlis malformed, this will throw an unhandled exception, potentially breaking the entire email response flow. Consider defensive handling to skip invalid attachments rather than crashing.🛡️ Proposed defensive handling
- ...imageAttachments.map(att => ({ - type: "image" as const, - image: new URL(att.downloadUrl), - mimeType: att.contentType, - })), + ...imageAttachments.flatMap(att => { + try { + return [{ + type: "image" as const, + image: new URL(att.downloadUrl), + mimeType: att.contentType, + }]; + } catch { + console.warn(`Skipping attachment with invalid URL: ${att.downloadUrl}`); + return []; + } + }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/emails/inbound/generateEmailResponse.ts` around lines 43 - 47, The mapping that constructs image attachments uses new URL(att.downloadUrl) which can throw on malformed URLs; in the imageAttachments -> map block (where objects with type "image" and image: new URL(...) are created) wrap the URL construction in defensive logic: attempt to parse/construct the URL inside a try/catch (or validate it first) and if it fails simply skip that attachment (omit it from the returned array) and optionally log a warning; ensure the rest of generateEmailResponse continues without throwing for invalid att.downloadUrl values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@lib/emails/inbound/generateEmailResponse.ts`:
- Around line 39-40: The current generateEmailResponse logic grabs the last user
message (msg from messages[lastUserIdx]) but forces non-string msg.content to an
empty string, risking data loss if content is an array per the ModelMessage
type; update the assignment of textContent in generateEmailResponse to
defensively handle arrays and other non-string types returned by
getEmailRoomMessages — e.g., if msg.content is an array, join parts (or
JSON.stringify when parts are objects), if it's an object serialize it
appropriately, and otherwise fallback to an empty string — so that msg,
messages, lastUserIdx, and textContent are preserved without silently discarding
content.
- Around line 43-47: The mapping that constructs image attachments uses new
URL(att.downloadUrl) which can throw on malformed URLs; in the imageAttachments
-> map block (where objects with type "image" and image: new URL(...) are
created) wrap the URL construction in defensive logic: attempt to
parse/construct the URL inside a try/catch (or validate it first) and if it
fails simply skip that attachment (omit it from the returned array) and
optionally log a warning; ensure the rest of generateEmailResponse continues
without throwing for invalid att.downloadUrl values.
Remove image part injection into agent messages. Attachment download URLs in the email text are sufficient — OpenClaw in the sandbox can fetch any file type directly. This avoids signed URL access issues and unsupported format errors (e.g. SVG) with LLM vision APIs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs at each step to identify where the email response pipeline stops: memory validation, CC check, response generation, and send. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Attachment download URLs from email are temporary (expire in 1 hour). Add sandbox prompt note telling OpenClaw to always download files locally via curl instead of storing the raw URL in repo files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
lib/emails/inbound/respondToInboundEmail.ts (1)
35-40: Extract a logging helper to reduce duplication and shrink function size.The repeated stage logs are clear, but duplicated string templates across these lines make maintenance harder and push this domain function further past the size guideline. Consider a tiny helper for stage-based logging.
Suggested refactor
+function logInboundStage(emailId: string, stage: string, details?: string): void { + console.log( + `[respondToInboundEmail] Email ${emailId} - ${stage}${details ? ` (${details})` : ""}`, + ); +} - console.log(`[respondToInboundEmail] Email ${emailId} - early return from validateNewEmailMemory`); + logInboundStage(emailId, "early return from validateNewEmailMemory"); - console.log(`[respondToInboundEmail] Email ${emailId} - memory validated, roomId=${chatRequestBody.roomId}, emailText length=${emailText.length}`); + logInboundStage( + emailId, + "memory validated", + `roomId=${chatRequestBody.roomId}, emailTextLength=${emailText.length}`, + ); - console.log(`[respondToInboundEmail] Email ${emailId} - generating response...`); + logInboundStage(emailId, "generating response");As per coding guidelines,
**/*.{ts,tsx}: “Extract shared logic into reusable utilities following Don't Repeat Yourself (DRY) principle” andlib/**/*.ts: “For domain functions, ensure ... Keep functions under 50 lines”.Also applies to: 45-53, 73-77
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/emails/inbound/respondToInboundEmail.ts` around lines 35 - 40, The function respondToInboundEmail has duplicated stage log templates (e.g., the console.log lines using emailId, validationResult, chatRequestBody.roomId, and emailText.length at multiple spots including the shown block and lines 45-53, 73-77); extract a small helper (e.g., logEmailStage or formatEmailStageLog) that accepts (stage: string, emailId: string, opts?: {roomId?: string, emailText?: string, extra?: any}) and builds/prints the standardized message, then replace the repeated console.log calls in respondToInboundEmail (and the other noted blocks) to call that helper so the domain function shrinks and duplicated string templates are centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/emails/inbound/respondToInboundEmail.ts`:
- Around line 30-31: The logs in respondToInboundEmail are outputting raw PII
(email addresses via variables to and from); change the logging to not emit raw
addresses by introducing and using a redaction helper (e.g., redactEmail or
hashEmail) and replace direct uses of to and from in the console.log (and the
other log at the second occurrence) with the redacted/hashed value; update the
two places where emailId and to/from are logged (identify calls inside
respondToInboundEmail) to log emailId plus redactEmail(to) or redactEmail(from)
and ensure the helper is deterministic and non-reversible (hash or mask) and
reused for any other email logging in this file.
- Around line 67-74: In respondToInboundEmail, saveChatCompletion is being
called before confirming send success; move the call so it runs only after
sendEmailWithResend returns a non-error result (i.e., after the check that
result instanceof NextResponse fails). Specifically, ensure
sendEmailWithResend(payload) is awaited, then check if result is an error
(result instanceof NextResponse) and return on error, and only after that call
saveChatCompletion({ text, roomId }) so assistant replies are persisted only
when outbound delivery succeeded.
---
Nitpick comments:
In `@lib/emails/inbound/respondToInboundEmail.ts`:
- Around line 35-40: The function respondToInboundEmail has duplicated stage log
templates (e.g., the console.log lines using emailId, validationResult,
chatRequestBody.roomId, and emailText.length at multiple spots including the
shown block and lines 45-53, 73-77); extract a small helper (e.g., logEmailStage
or formatEmailStageLog) that accepts (stage: string, emailId: string, opts?:
{roomId?: string, emailText?: string, extra?: any}) and builds/prints the
standardized message, then replace the repeated console.log calls in
respondToInboundEmail (and the other noted blocks) to call that helper so the
domain function shrinks and duplicated string templates are centralized.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
lib/chat/tools/createPromptSandboxStreamingTool.tslib/emails/inbound/respondToInboundEmail.ts
✅ Files skipped from review due to trivial changes (1)
- lib/chat/tools/createPromptSandboxStreamingTool.ts
| const result = await sendEmailWithResend(payload); | ||
|
|
||
| // Save the assistant response message | ||
| await saveChatCompletion({ text, roomId }); | ||
|
|
||
| if (result instanceof NextResponse) { | ||
| console.log(`[respondToInboundEmail] Email ${emailId} - sendEmailWithResend returned error response`); | ||
| return result; |
There was a problem hiding this comment.
Persist completion only after send success is confirmed.
saveChatCompletion currently runs before the error check (result instanceof NextResponse). If send fails, the assistant reply is still persisted, which can desync memory from actual outbound delivery.
Suggested fix
const result = await sendEmailWithResend(payload);
-// Save the assistant response message
-await saveChatCompletion({ text, roomId });
-
if (result instanceof NextResponse) {
console.log(`[respondToInboundEmail] Email ${emailId} - sendEmailWithResend returned error response`);
return result;
}
+// Save only after successful send
+await saveChatCompletion({ text, roomId });
+
console.log(`[respondToInboundEmail] Email ${emailId} - reply sent successfully`);
return NextResponse.json(result);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const result = await sendEmailWithResend(payload); | |
| // Save the assistant response message | |
| await saveChatCompletion({ text, roomId }); | |
| if (result instanceof NextResponse) { | |
| console.log(`[respondToInboundEmail] Email ${emailId} - sendEmailWithResend returned error response`); | |
| return result; | |
| const result = await sendEmailWithResend(payload); | |
| if (result instanceof NextResponse) { | |
| console.log(`[respondToInboundEmail] Email ${emailId} - sendEmailWithResend returned error response`); | |
| return result; | |
| } | |
| // Save only after successful send | |
| await saveChatCompletion({ text, roomId }); | |
| console.log(`[respondToInboundEmail] Email ${emailId} - reply sent successfully`); | |
| return NextResponse.json(result); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/emails/inbound/respondToInboundEmail.ts` around lines 67 - 74, In
respondToInboundEmail, saveChatCompletion is being called before confirming send
success; move the call so it runs only after sendEmailWithResend returns a
non-error result (i.e., after the check that result instanceof NextResponse
fails). Specifically, ensure sendEmailWithResend(payload) is awaited, then check
if result is an error (result instanceof NextResponse) and return on error, and
only after that call saveChatCompletion({ text, roomId }) so assistant replies
are persisted only when outbound delivery succeeded.
…nterface Replace the custom EmailAttachment interface with ListAttachmentsResponseSuccess["data"][number] from the Resend SDK. Remove the camelCase remapping layer — return SDK objects directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
agent.generate()attachmentsfield toChatRequestBodyto pass attachment metadata through the pipelineTest plan
getEmailAttachments(4 tests) — Resend API mapping, empty/null responses, filename defaultsformatAttachmentsText(3 tests) — empty array, single, multiple attachmentsgenerateEmailResponse(5 tests) — no attachments, image parts injection, non-image skipping, multi-message conversationsvalidateNewEmailMemorytests (3 new tests) — attachment fetching, URL appending, empty attachments🤖 Generated with Claude Code
Summary by CodeRabbit