Skip to content

feat(sdk): add multipart image send support#46

Merged
Ryan Zhu (underthestars-zhy) merged 3 commits into
mainfrom
feat/multipart-remote-sdk-docs
May 5, 2026
Merged

feat(sdk): add multipart image send support#46
Ryan Zhu (underthestars-zhy) merged 3 commits into
mainfrom
feat/multipart-remote-sdk-docs

Conversation

@LingJueYa
Copy link
Copy Markdown
Contributor

@LingJueYa LingJueYa~ (LingJueYa) commented May 4, 2026

Summary

Add SDK support for sending multiple images in a single multipart iMessage request.

Changes

  • add messages.sendMultipartMessage() to the SDK
  • add multipart message request types
  • add an example for sending multiple images in one request
  • document multipart image sends in the README
  • clarify remote SDK file upload behavior in the README

Details

sendMultipartMessage() reads local files on the SDK host, uploads them to the server, and then sends them together in a single /api/v1/message/multipart request.

This flow requires:

  • a server with Private API support
  • an existing chatGuid
  • file paths that exist on the SDK machine

Example

const message = await sdk.messages.sendMultipartMessage({
  chatGuid: "iMessage;+;chat123456789",
  parts: [
    { partIndex: 0, filePath: "/path/to/image-1.jpg" },
    { partIndex: 1, filePath: "/path/to/image-2.jpg" },
    { partIndex: 2, filePath: "/path/to/image-3.jpg" },
  ],
});

Validation

  • verified the SDK compiles with tsc --noEmit
  • verified the multipart flow end to end against a running server
  • confirmed that a single multipart message can contain multiple uploaded images

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 message sending: send multiple images/content in a single request.
  • Documentation

    • Clarified remote server setup behavior for file paths and uploads when the SDK and server run on different machines.
    • Added guidance and requirements for sending multiple images in one request.
  • Examples

    • New runnable example demonstrating multipart image sending with file validation and error handling.

Copilot AI review requested due to automatic review settings May 4, 2026 16:56
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d87748d1-ac13-43ac-b0dd-c639e3bc1f51

📥 Commits

Reviewing files that changed from the base of the PR and between 2393af9 and 972cbc0.

📒 Files selected for processing (1)
  • examples/message-multipart-images.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/message-multipart-images.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent

📝 Walkthrough

Walkthrough

Adds multipart message support: new types, a MessageModule sendMultipartMessage implementation with a local file-upload helper, README docs for remote server/filePath behavior, and an example script demonstrating sending multiple images in one request.

Changes

Multipart Message Sending

Layer / File(s) Summary
Type Definitions
types/message.ts
Adds SendMultipartMessagePart (text
Core Implementation
modules/message.ts
Adds private uploadMultipartAttachment(part, fileName?) that reads part.filePath, uploads via POST /api/v1/attachment/upload (FormData), and returns the attachment path. Adds public sendMultipartMessage(options) which resolves partIndex, uploads file parts, assembles parts payload, posts to /api/v1/message/multipart, and maps “chat does not exist” failures to a clearer Error.
Example Script
examples/message-multipart-images.ts
New example: reads CHAT_GUID and IMAGE_PATHS from env, validates files exist, calls sdk.messages.sendMultipartMessage() with { partIndex, filePath, fileName } parts, fetches the sent message with attachments, logs GUID/count/timestamp, and exits with appropriate status codes.
Documentation
README.md
Adds “Remote Server Setup” explaining serverUrl/filePath semantics (local-first then upload) and a “Send Multiple Images In One Request” section documenting sdk.messages.sendMultipartMessage() with example parts and usage notes (uploads before multipart send, private API, chat must exist, per-machine filePath availability).

Sequence Diagram

sequenceDiagram
    participant User as User/Script
    participant SDK as SDK (MessageModule)
    participant FS as File System
    participant AttachAPI as Attachment API
    participant MessageAPI as Message API

    User->>SDK: sendMultipartMessage(chatGuid, parts[])
    loop each part
        alt file part
            SDK->>FS: readFile(filePath)
            FS-->>SDK: file bytes
            SDK->>AttachAPI: POST /api/v1/attachment/upload (FormData)
            AttachAPI-->>SDK: { attachment: path }
        else text part
            SDK-->SDK: prepare text part
        end
    end
    SDK->>MessageAPI: POST /api/v1/message/multipart (assembled parts)
    MessageAPI-->>SDK: MessageResponse
    SDK->>MessageAPI: fetchMessage(with attachments)
    MessageAPI-->>SDK: Message with attachments
    SDK-->>User: return MessageResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I found the files, I gave a hop,

I packed the parts and didn’t stop,
I uploaded bytes and stitched the thread,
Multipart blossoms neatly spread,
Hooray — the message danced and popped!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(sdk): add multipart image send support' directly and clearly summarizes the main change: adding SDK support for sending multiple images in a single multipart iMessage request, which aligns with the primary changes across all modified files.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multipart-remote-sdk-docs

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


Review rate limit: 3/5 reviews remaining, refill in 20 minutes and 17 seconds.

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds SDK support for sending multiple images (and other parts) in a single multipart iMessage request, including new SDK API surface, request types, documentation, and an example script.

Changes:

  • Adds messages.sendMultipartMessage() with local file reading + upload + /api/v1/message/multipart send flow.
  • Introduces multipart message option/part types in the SDK type layer.
  • Updates README with remote server file-path clarification and multipart usage example.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
types/message.ts Adds new multipart send option/part types for SDK consumers.
modules/message.ts Implements multipart upload + send logic in MessageModule.
README.md Documents multipart sending and clarifies local-vs-remote file upload behavior.
examples/message-multipart-images.ts Adds a runnable example for sending multiple images in one multipart request.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread modules/message.ts Outdated
Comment thread modules/message.ts Outdated
Comment thread types/message.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
modules/message.ts (1)

69-91: 💤 Low value

Uploads may be wasted if any single upload fails.

Promise.all will reject immediately when any upload fails, but other uploads continue running in the background. For multipart messages with many large files, consider:

  1. Using Promise.allSettled with error aggregation, or
  2. Sequential uploads with early exit

For typical use cases (a few images), the current approach is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/message.ts` around lines 69 - 91, The current uploadParts function
uses Promise.all which causes other uploads to keep running in the background if
one fails; replace Promise.all with Promise.allSettled and aggregate results
from options.parts to detect any rejected uploads (using the existing
uploadMultipartAttachment and resolvedPartIndex logic), then: 1) collect all
settled results, build the array of successful part objects (text or
attachment/name) from fulfilled entries, and 2) if any promise was rejected,
throw an aggregated Error containing all failure reasons (or return an error
structure) so callers can abort/cleanup; alternatively implement a sequential
loop over options.parts that calls uploadMultipartAttachment and exits early on
the first failure using the same partIndex/name resolution.
examples/message-multipart-images.ts (1)

1-1: 💤 Low value

Consider using Bun APIs for file existence checks.

Per coding guidelines, Bun APIs are preferred. For async existence checking:

const exists = await Bun.file(filePath).exists();

However, since this is example code demonstrating the SDK (not the SDK itself), and existsSync is simple and clear, this is acceptable.

As per coding guidelines: "Prefer Bun.file over node:fs's readFile/writeFile for file operations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/message-multipart-images.ts` at line 1, The example currently
imports node:fs; replace synchronous Node APIs with Bun file APIs per
guidelines: remove or stop using import fs from "node:fs" and instead use
Bun.file(filePath).exists() for existence checks (await
Bun.file(filePath).exists()) and use Bun.file(filePath).text() or .arrayBuffer()
for reading where the example used fs.readFileSync/existsSync; update any
occurrences of fs.existsSync, fs.readFileSync, or fs.readFile to the
corresponding Bun.file(...) methods in examples/message-multipart-images.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/message-multipart-images.ts`:
- Around line 11-14: The DEFAULT_IMAGE_PATHS array formatting in
examples/message-multipart-images.ts doesn't match Biome style; run the Biome
formatter (bun biome format --write examples/message-multipart-images.ts) or
apply equivalent formatting so the DEFAULT_IMAGE_PATHS constant
(DEFAULT_IMAGE_PATHS) conforms to the project's formatter rules, then commit the
formatted changes.

In `@modules/message.ts`:
- Around line 82-88: The returned attachment name currently uses
path.basename(uploadedPath) which takes the server-side upload path rather than
the user's original filename; change the return to prefer the original filename
from the incoming multipart part (use part.fileName if present, otherwise derive
basename from the part's original path property such as part.path /
part.originalPath / part.originalname) and only fall back to
path.basename(uploadedPath) as a last resort; update the code around the
uploadMultipartAttachment call and the object returned (the block that
constructs { partIndex, attachment, name }) so the name is derived from the
original part metadata instead of the server upload path.

---

Nitpick comments:
In `@examples/message-multipart-images.ts`:
- Line 1: The example currently imports node:fs; replace synchronous Node APIs
with Bun file APIs per guidelines: remove or stop using import fs from "node:fs"
and instead use Bun.file(filePath).exists() for existence checks (await
Bun.file(filePath).exists()) and use Bun.file(filePath).text() or .arrayBuffer()
for reading where the example used fs.readFileSync/existsSync; update any
occurrences of fs.existsSync, fs.readFileSync, or fs.readFile to the
corresponding Bun.file(...) methods in examples/message-multipart-images.ts.

In `@modules/message.ts`:
- Around line 69-91: The current uploadParts function uses Promise.all which
causes other uploads to keep running in the background if one fails; replace
Promise.all with Promise.allSettled and aggregate results from options.parts to
detect any rejected uploads (using the existing uploadMultipartAttachment and
resolvedPartIndex logic), then: 1) collect all settled results, build the array
of successful part objects (text or attachment/name) from fulfilled entries, and
2) if any promise was rejected, throw an aggregated Error containing all failure
reasons (or return an error structure) so callers can abort/cleanup;
alternatively implement a sequential loop over options.parts that calls
uploadMultipartAttachment and exits early on the first failure using the same
partIndex/name resolution.
🪄 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: 0cfd3fbe-355e-4909-bec9-83d9d97ce709

📥 Commits

Reviewing files that changed from the base of the PR and between b246c46 and 234e665.

📒 Files selected for processing (4)
  • README.md
  • examples/message-multipart-images.ts
  • modules/message.ts
  • types/message.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

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

**/*.{ts,tsx,js,jsx}: Use bun <file> instead of node <file> or ts-node <file> for running TypeScript and JavaScript files
Bun automatically loads .env, so don't use dotenv library
Use Bun.serve() with WebSockets, HTTPS, and routes instead of express
Use bun:sqlite for SQLite database operations instead of better-sqlite3
Use Bun.redis for Redis operations instead of ioredis
Use Bun.sql for Postgres database operations instead of pg or postgres.js
Use built-in WebSocket instead of ws library
Prefer Bun.file over node:fs's readFile/writeFile for file operations
Use Bun.$ template tag for shell commands instead of execa library
CSS files can be imported directly in TypeScript/JavaScript/JSX files and will be bundled automatically by Bun

Files:

  • types/message.ts
  • examples/message-multipart-images.ts
  • modules/message.ts
**/*.{html,ts,tsx,css}

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

Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild for bundling

Files:

  • types/message.ts
  • examples/message-multipart-images.ts
  • modules/message.ts
**/*.{html,ts,tsx,js,jsx}

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

Use HTML imports with Bun.serve() for frontend instead of vite

Files:

  • types/message.ts
  • examples/message-multipart-images.ts
  • modules/message.ts
**/*.ts

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

Run TypeScript server files with bun --hot <file.ts> for hot module reloading during development

Files:

  • types/message.ts
  • examples/message-multipart-images.ts
  • modules/message.ts
🪛 GitHub Actions: CI
examples/message-multipart-images.ts

[error] 11-15: Biome formatter check failed: file content is not formatted as expected. Reported expected change for DEFAULT_IMAGE_PATHS array formatting.

🔇 Additional comments (7)
types/message.ts (1)

44-66: LGTM!

The type definitions are well-structured. The discriminated union for SendMultipartMessagePart correctly distinguishes text parts (via text property) from file parts (via filePath property), which allows TypeScript to properly narrow types in the implementation.

modules/message.ts (3)

20-31: LGTM with minor note.

The implementation correctly handles file upload. The type assertion as string on line 30 assumes the API response structure - this is consistent with patterns elsewhere in the codebase. Consider adding error context if file read fails, but this matches the existing pattern in sendAttachment.


111-121: Inconsistent error handling compared to sendAttachment.

Per the relevant code snippet from modules/attachment.ts, sendAttachment handles isChatNotExistError by calling ensureChatExists() and retrying. This method throws a custom error instead.

This appears intentional based on the PR requirements ("Requires... an existing chatGuid"), but the inconsistency may surprise users. Consider documenting this difference prominently or aligning the behavior in a future iteration.


2-5: 💤 Low value

No changes needed. This library explicitly targets Node.js >= 18.0.0 (per package.json "engines"), so node:fs/promises is the correct choice. The Bun.file guideline applies to Bun-native projects, not npm-published libraries requiring cross-runtime compatibility.

			> Likely an incorrect or invalid review comment.
examples/message-multipart-images.ts (1)

16-66: LGTM!

The example clearly demonstrates the multipart message API:

  • Proper environment variable handling with fallback defaults
  • File existence validation before sending
  • Clean error handling with handleError
  • Fetches and logs the sent message with attachments
README.md (2)

122-130: LGTM!

Clear and concise explanation of remote server setup semantics. This helps users understand the file path behavior when SDK and server are on different machines.


624-654: LGTM!

Comprehensive documentation for the new multipart feature:

  • Clear code example showing the API usage
  • Important notes about Private API requirement, chat existence, and cross-machine file handling
  • Correctly links to the example file

Comment thread examples/message-multipart-images.ts Outdated
Comment thread modules/message.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/message-multipart-images.ts`:
- Around line 52-58: The script currently always calls process.exit(0) even
after failures; update the error paths so failures return a non-zero exit code:
when catching errors where handleError(error, "Failed to send multipart images")
is called, follow that with process.exit(1) (or propagate a non-zero code)
instead of falling through to process.exit(0); also ensure the top-level failure
handler that currently logs errors uses process.exit(1) after logging, and
consider moving sdk.close() into a finally block so sdk.close() runs before
exiting.
🪄 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: 5351e620-9af6-49bb-b33b-66d390e6f80b

📥 Commits

Reviewing files that changed from the base of the PR and between 234e665 and 2393af9.

📒 Files selected for processing (2)
  • examples/message-multipart-images.ts
  • modules/message.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • modules/message.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

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

**/*.{ts,tsx,js,jsx}: Use bun <file> instead of node <file> or ts-node <file> for running TypeScript and JavaScript files
Bun automatically loads .env, so don't use dotenv library
Use Bun.serve() with WebSockets, HTTPS, and routes instead of express
Use bun:sqlite for SQLite database operations instead of better-sqlite3
Use Bun.redis for Redis operations instead of ioredis
Use Bun.sql for Postgres database operations instead of pg or postgres.js
Use built-in WebSocket instead of ws library
Prefer Bun.file over node:fs's readFile/writeFile for file operations
Use Bun.$ template tag for shell commands instead of execa library
CSS files can be imported directly in TypeScript/JavaScript/JSX files and will be bundled automatically by Bun

Files:

  • examples/message-multipart-images.ts
**/*.{html,ts,tsx,css}

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

Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild for bundling

Files:

  • examples/message-multipart-images.ts
**/*.{html,ts,tsx,js,jsx}

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

Use HTML imports with Bun.serve() for frontend instead of vite

Files:

  • examples/message-multipart-images.ts
**/*.ts

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

Run TypeScript server files with bun --hot <file.ts> for hot module reloading during development

Files:

  • examples/message-multipart-images.ts
🔇 Additional comments (1)
examples/message-multipart-images.ts (1)

35-43: Multipart part construction looks correct.

Using partIndex, filePath, and fileName per part cleanly matches the multipart SDK contract and preserves deterministic ordering.

Comment thread examples/message-multipart-images.ts
Copilot AI review requested due to automatic review settings May 4, 2026 17:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread modules/message.ts
Comment thread modules/message.ts
Comment thread types/message.ts
@underthestars-zhy Ryan Zhu (underthestars-zhy) merged commit 80c8d97 into main May 5, 2026
10 checks passed
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.

3 participants