Skip to content

feat(messages): support buffer-backed attachments in sendMultipart - #28

Open
ZhYGuoL (ZhYGuoL) wants to merge 1 commit into
photon-hq:mainfrom
ZhYGuoL:feat/buffer-backed-send-multipart
Open

feat(messages): support buffer-backed attachments in sendMultipart#28
ZhYGuoL (ZhYGuoL) wants to merge 1 commit into
photon-hq:mainfrom
ZhYGuoL:feat/buffer-backed-send-multipart

Conversation

@ZhYGuoL

@ZhYGuoL ZhYGuoL (ZhYGuoL) commented May 14, 2026

Copy link
Copy Markdown

Summary

Adds buffer-backed attachment parts to messages.sendMultipart(...). Callers can pass attachment: { data, fileName, … } on a multipart bubble; the SDK uploads bytes via the existing attachments.upload flow, then sends the multipart message using attachmentGuid / attachmentName on the wire—same shape as today’s uploaded-GUID parts.

Closes / addresses Issue #24 — Support buffer-backed attachments in sendMultipart.

Motivation

Previously, multipart sends only supported attachment bubbles via attachmentGuid (and optional attachmentName). Apps that already held file bytes in memory had to write temp files visible to the helper or pre-upload every part themselves. This change keeps the SDK thin: client-side normalize → upload → send, without proto or server changes.

What changed

Public API (src/types/messages.ts)

  • MultipartAttachmentInput: extends AttachmentInput with an optional mimeType field documented as a call-site hint only (upload RPC still infers type from bytes/filename; mimeType is not sent as a separate upload field).
  • MessagePart: optional attachment?: MultipartAttachmentInput for byte-backed bubbles.

Behavior (src/resources/messages.ts)

  • sendMultipart scans parts for attachment without attachmentGuid.
  • For those parts, invokes an injected uploadAttachment with the AttachmentInput payload.
  • Replaces each such part with attachmentGuid + attachmentName (defaulting name from attachment.fileName when attachmentName is omitted).
  • Preserves part order, bubbleIndex, and text/mention/formatting fields unchanged.
  • Parts that already specify attachmentGuid are unchanged.

Wiring (src/client.ts)

  • createClient constructs AttachmentsResource first, then passes uploadAttachment: (input) => attachments.upload(input) into MessagesResource so production uses the real upload RPC.

Docs (README.md)

  • Multipart section documents byte-backed parts and that they are uploaded first internally; existing attachmentGuid example retained.

Tests

New coverage lives under describe("byte-backed multipart attachments", …) in tests/unit/messages-resource.test.ts.

Test What it proves
uploads byte-backed parts and sends the uploaded attachment guid With a stub uploadAttachment, sendMultipart calls upload once with the expected fileName and data. The captured sendMultipartMessage request has three parts in order (text → attachment → text). The middle part’s wire attachment is { attachmentGuid, attachmentName } only—no raw data—so normalization matches the intended upload-then-send contract.
preserves bubbleIndex on uploaded multipart attachment parts After normalization, bubbleIndex on a byte-backed part is still present on the outgoing multipart payload alongside attachmentGuid / attachmentName.
does not send multipart request when byte-backed attachment upload fails If uploadAttachment throws, sendMultipart rejects with that error and the stub sendMultipartMessage is never called (sendCalls === 0), so a failed upload does not trigger a send.

Existing multipart tests (text-only, attachmentGuid, mentions, formatting, bubbleIndex forwarding, etc.) continue to pass unchanged.

How to verify locally

bun install
bun run check
bun run lint
bun test
bun run build

Checklist

  • Public types extended without exposing generated protobuf types
  • Backward compatible for existing attachmentGuid multipart parts
  • README updated with an example
  • Unit tests added for upload path, wire shape, ordering, bubbleIndex, and upload failure short-circuit

View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by CodeRabbit

  • New Features

    • Multipart messages now support inline attachment bytes alongside text and mentions, with automatic upload handling. Previously uploaded attachment GUIDs remain supported.
  • Documentation

    • Updated documentation clarifying that multipart sends are atomic and can combine text, mentions, and attachments with improved code examples.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR extends the sendMultipart API to support byte-backed attachments. Byte-backed attachment data is automatically uploaded via the attachments service and replaced with GUIDs before sending, enabling mixed text, mentions, and inline attachment bytes in atomic multipart messages.

Changes

Byte-backed multipart attachment uploads

Layer / File(s) Summary
Type contracts for multipart attachments
src/types/messages.ts
MultipartAttachmentInput extends AttachmentInput with optional mimeType hint. MessagePart.attachment field added to accept byte-backed inputs. MessagesResource constructor signature updated to accept optional uploadAttachment dependency.
Normalization and upload logic
src/resources/messages.ts
Type guard for byte-backed attachments. MessagesResource stores injected uploadAttachment dependency. New private normalizeMultipartParts method uploads byte-backed parts without attachmentGuid, replaces raw data with returned GUID, and throws if upload support is absent. sendMultipart calls normalization before gRPC transmission.
Client instantiation and API documentation
src/client.ts
AttachmentsImpl instantiated before MessagesImpl. MessagesImpl receives uploadAttachment callback delegating to attachments.upload(...). JSDoc for sendMultipart in MessagesResource and AdvancedIMessage updated to clarify support for uploaded GUIDs or byte-backed inputs.
Byte-backed multipart test suite
tests/unit/messages-resource.test.ts
makeUploadResult helper for constructing test upload results. Test suite verifies byte-backed attachments trigger upload, returned GUIDs replace raw data in multipart parts (preserving bubbleIndex), text parts remain unchanged, and upload errors propagate without calling sendMultipartMessage.
User documentation for multipart attachments
README.md
"Multipart sends" section clarified with new documentation and code example showing byte-backed attachment upload via attachments.upload(...) followed by sendMultipart, while preserving support for direct GUID-based attachment usage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • photon-hq/advanced-imessage-ts#24: This PR directly implements the requested feature for byte-backed multipart attachments with the new MultipartAttachmentInput type, automatic upload normalization, and comprehensive test coverage.

Possibly related PRs

  • photon-hq/advanced-imessage-ts#25: Both PRs modify the multipart attachment pipeline—the referenced PR switches proto/types to use attachmentGuid, while this PR normalizes byte-backed inputs into attachmentGuid before sending.

Poem

🐇 A bunny hops with bytes and care,
Uploads attachments through the air!
From raw data, GUIDs take flight,
Mixed with text—atomic and right.
Multiparts now blend, prepared to share! 📨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding support for buffer-backed attachments in the sendMultipart function, which is the core feature across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
README.md (1)

203-204: ⚡ Quick win

Consider adding the import statement for consistency.

The example uses readFile without showing the import. Other attachment examples in the README (line 92) include import { readFile } from "node:fs/promises"; for clarity.

📝 Suggested addition
+import { readFile } from "node:fs/promises";
+
 const bytes = await readFile("photo.png");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 203 - 204, Add the missing import for readFile used
in the example: include an import like import { readFile } from
"node:fs/promises"; at the top of the example so the readFile call is shown with
its required import (match the style used for other attachment examples).
src/resources/messages.ts (1)

113-143: 💤 Low value

Consider validating mutually exclusive attachment fields.

When a part has both attachment (bytes) and attachmentGuid (pre-uploaded), the current logic prioritizes attachmentGuid and silently ignores the byte data (line 128). While this aligns with the design ("parts with existing attachmentGuid remain unchanged"), carrying unused byte data through the call chain wastes memory and may confuse callers.

Consider adding validation to detect and reject this ambiguous state:

🛡️ Optional validation to prevent ambiguous part configuration
 private async normalizeMultipartParts(
   parts: readonly MessagePart[]
 ): Promise<readonly MessagePart[]> {
   if (!parts.some(hasByteBackedAttachment)) {
     return parts;
   }

   if (!this._uploadAttachment) {
     throw new Error(
       "messages.sendMultipart received a byte-backed attachment part without upload support"
     );
   }

   const normalizedParts: MessagePart[] = [];
   for (const part of parts) {
+    if (hasByteBackedAttachment(part) && part.attachmentGuid) {
+      throw new Error(
+        "MessagePart cannot have both attachment (bytes) and attachmentGuid; provide one or the other"
+      );
+    }
     if (!hasByteBackedAttachment(part) || part.attachmentGuid) {
       normalizedParts.push(part);
       continue;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/resources/messages.ts` around lines 113 - 143, In
normalizeMultipartParts, detect parts where both attachment (byte-backed) and
attachmentGuid are present and reject them instead of silently ignoring bytes:
inside the loop in normalizeMultipartParts (or before processing) add a
validation that throws a clear Error when a MessagePart contains both attachment
and attachmentGuid, referencing the function name normalizeMultipartParts and
the MessagePart fields attachment and attachmentGuid; alternatively, if you
prefer to accept and free memory, explicitly delete or null out attachment when
attachmentGuid is present and log a warning—ensure the validation/cleanup occurs
before calling _uploadAttachment or pushing parts to normalizedParts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 198: Add a proper section header before the multipart documentation line
("Multipart sends are atomic and can mix text, mentions, and attachments.") to
match the style of other major sections (e.g., "## Send Messages", "## Send
Attachments"); insert a header like "## Multipart Messages" immediately above
that sentence so the multipart content is discoverable and consistent with the
rest of the README.

---

Nitpick comments:
In `@README.md`:
- Around line 203-204: Add the missing import for readFile used in the example:
include an import like import { readFile } from "node:fs/promises"; at the top
of the example so the readFile call is shown with its required import (match the
style used for other attachment examples).

In `@src/resources/messages.ts`:
- Around line 113-143: In normalizeMultipartParts, detect parts where both
attachment (byte-backed) and attachmentGuid are present and reject them instead
of silently ignoring bytes: inside the loop in normalizeMultipartParts (or
before processing) add a validation that throws a clear Error when a MessagePart
contains both attachment and attachmentGuid, referencing the function name
normalizeMultipartParts and the MessagePart fields attachment and
attachmentGuid; alternatively, if you prefer to accept and free memory,
explicitly delete or null out attachment when attachmentGuid is present and log
a warning—ensure the validation/cleanup occurs before calling _uploadAttachment
or pushing parts to normalizedParts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2258784-4009-4cb9-ad21-a6d75e09a2e0

📥 Commits

Reviewing files that changed from the base of the PR and between f5b47ec and 213b81f.

📒 Files selected for processing (5)
  • README.md
  • src/client.ts
  • src/resources/messages.ts
  • src/types/messages.ts
  • tests/unit/messages-resource.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Don't use eval() or assign directly to document.cookie
Validate and sanitize user input
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Avoid barrel files (index files that re-export everything)
Write assertions inside it() or test() blocks

`**/*.{ts...

Files:

  • src/client.ts
  • tests/unit/messages-resource.test.ts
  • src/resources/messages.ts
  • src/types/messages.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Use TypeScript for all new projects and modules

Files:

  • src/client.ts
  • tests/unit/messages-resource.test.ts
  • src/resources/messages.ts
  • src/types/messages.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Avoid done callbacks in async tests - use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat - avoid excessive describe nesting

Files:

  • tests/unit/messages-resource.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Write comprehensive unit tests for all functions and components

Files:

  • tests/unit/messages-resource.test.ts
🔇 Additional comments (15)
README.md (1)

200-201: LGTM!

Also applies to: 217-228

src/types/messages.ts (2)

188-196: LGTM!


199-200: LGTM!

src/resources/messages.ts (5)

57-61: LGTM!


63-67: LGTM!


105-111: LGTM!


231-234: LGTM!


79-80: LGTM!

src/client.ts (3)

203-204: LGTM!


403-404: LGTM!


460-463: LGTM!

tests/unit/messages-resource.test.ts (4)

47-64: LGTM!


603-644: LGTM!


646-678: LGTM!


680-707: LGTM!

Comment thread README.md

Multipart sends are atomic and can mix text, mentions, and uploaded
attachments:
Multipart sends are atomic and can mix text, mentions, and attachments.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a section header for multipart messages.

The multipart documentation starts without a section header, inconsistent with other major sections like "## Send Messages", "## Send Attachments", and "## Chat Backgrounds". This impacts navigation and makes the document harder to scan.

📝 Suggested fix
+## Multipart Messages
+
 Multipart sends are atomic and can mix text, mentions, and attachments.
📝 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.

Suggested change
Multipart sends are atomic and can mix text, mentions, and attachments.
## Multipart Messages
Multipart sends are atomic and can mix text, mentions, and attachments.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 198, Add a proper section header before the multipart
documentation line ("Multipart sends are atomic and can mix text, mentions, and
attachments.") to match the style of other major sections (e.g., "## Send
Messages", "## Send Attachments"); insert a header like "## Multipart Messages"
immediately above that sentence so the multipart content is discoverable and
consistent with the rest of the README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant