Skip to content

Conversation

@jakobhoeg
Copy link

@jakobhoeg jakobhoeg commented Jan 17, 2026

🎯 Changes

When calling append() with a ModelMessage containing multimodal content (images, audio, files), the content was stripped during the ModelMessage → UIMessage conversion because modelMessageToUIMessage() only extracted text via getTextContent(). Along this, the parts of a message doesn't include multimodal parts, making it impossible to build chat UIs that preserve and display multimodal content.

Added new message part types and updated the conversion functions to preserve multimodal content during round-trips:
New Types (@tanstack/ai and @tanstack/ai-client):

  • ImageMessagePart - preserves image data with source and optional metadata
  • AudioMessagePart - preserves audio data
  • VideoMessagePart - preserves video data - (NOT TESTED)
  • DocumentMessagePart - preserves document data (e.g., PDFs) - (NOT TESTED)

Updated Conversion Functions:

  • modelMessageToUIMessage() - now converts ContentPart[] to corresponding MessagePart[] instead of discarding non-text parts
  • uiMessageToModelMessages() - now builds ContentPart[] when multimodal parts are present, preserving part ordering

Example:

// Input ModelMessage with multimodal content
const message: ModelMessage = {
  role: 'user',
  content: [
    { type: 'text', text: 'What is in this image?' },
    { type: 'image', source: { type: 'url', value: '' } }
  ]
}

// UIMessage now preserves all content
const uiMessage = modelMessageToUIMessage(message)
// uiMessage.parts = [
//   { type: 'text', content: 'What is in this image?' },
//   { type: 'image', source: { type: 'url', value: '' } }
// ]

// UI
if (part.type === 'image') { // 'audio' etc.
  ...<Render UI />
}

Demo

Images:
https://github.com/user-attachments/assets/5f62ab32-9f11-44f7-bfc0-87d00678e265

Audio:
https://github.com/user-attachments/assets/bbbdc2f9-f8d7-4d74-99c2-23d15a3278a3

Closes #200

Note

I have not tested this with other adapters than my own community adapter that I'm currently working on.

This contribution touches core message handling. Let me know if the approach doesn't align with the project's vision, I am happy to iterate on it :)

This PR is not ready to be merged because:

  • Video and document parts are implemented but not yet tested
  • Only tested with my community adapter - needs verification with official adapters (OpenAI, Anthropic, etc.)

✅ Checklist

  • I have followed the steps in the Contributing guide.
    • I followed CLAUDE.md, since the link is broken.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multimodal support for UI messages, enabling handling of images, audio, video, and documents alongside text content.
  • Tests

    • Added comprehensive test coverage for multimodal message conversion logic and content preservation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

📝 Walkthrough

Walkthrough

This pull request extends UIMessage to support multimodal content types (images, audio, video, documents) by adding new ContentPart interfaces and updating message conversion logic to preserve multimodal content structure through ModelMessage and UIMessage transformations.

Changes

Cohort / File(s) Summary
Changeset
.changeset/brave-nights-shout.md
Patch release for multimodal UIMessage support feature flag
Type Definitions
packages/typescript/ai/src/types.ts, packages/typescript/ai-client/src/types.ts
Added ImagePart, AudioPart, VideoPart, and DocumentPart interfaces with type discriminators, content sources, and metadata; extended MessagePart union to include new multimodal types
Message Conversion Logic
packages/typescript/ai/src/activities/chat/messages.ts
Updated uiMessageToModelMessages to build ContentPart[] arrays for multimodal content and modelMessageToUIMessage to preserve multimodal structures instead of converting to text; enhanced to handle all four new part types and their sources
Test Coverage
packages/typescript/ai/tests/messages.test.ts
Added 190 lines of comprehensive tests validating text-only and multimodal content preservation, metadata handling, part ordering, and round-trip conversions across all part types

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • AlemTuzlak
  • harry-whorlow

Poem

🐰 Hops with glee, through images bright,
Audio and video in perfect flight,
Documents bundled, metadata's care,
Multimodal messages float through the air!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add multimodal UIMessage support' is concise, clear, and directly summarizes the main change in the changeset.
Description check ✅ Passed The PR description provides comprehensive context about the changes, includes examples, demo links, testing status, and completed the required checklist items.
Linked Issues check ✅ Passed The PR addresses all core objectives from issue #200: preserves multimodal ContentPart entries during append(), extends UIMessage.parts to support multimodal types, maintains part ordering, and enables chat UIs to render multimodal content [#200].
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #200 objectives. Changes to types.ts, messages.ts, and tests.ts focus on adding multimodal support; the changeset entry documents the feature addition.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@jakobhoeg
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jakobhoeg jakobhoeg marked this pull request as ready for review January 17, 2026 10:07
* Convert ContentPart array to MessagePart array
* Preserves all multimodal content (text, image, audio, video, document)
*/
function contentPartsToMessageParts(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit confused, don't these two types match identically? from what I see what you're doing is just coping the old data into the new one?

Copy link
Author

@jakobhoeg jakobhoeg Jan 23, 2026

Choose a reason for hiding this comment

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

I might've gotten carried away here and overcomplicated things.
I initially thought ContentPart (used in ModelMessage.content) and MessagePart (used in UIMessage.parts) were separate type systems for model and ui that needed their own definitions.
Pushed changes to simplify and resolve this.

@ilbertt
Copy link

ilbertt commented Jan 23, 2026

I would also like to send media messages from the client, I need this feature

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.

useChat's UiMessage.parts doesn't support multimodal parts

3 participants