Skip to content

fix(ai): preserve UI message IDs in model conversion - #1066

Open
mikemikimike wants to merge 2 commits into
TanStack:mainfrom
mikemikimike:fix/1063-preserve-message-ids
Open

fix(ai): preserve UI message IDs in model conversion#1066
mikemikimike wants to merge 2 commits into
TanStack:mainfrom
mikemikimike:fix/1063-preserve-message-ids

Conversation

@mikemikimike

@mikemikimike mikemikimike commented Aug 8, 2026

Copy link
Copy Markdown

Summary

  • Preserve UIMessage.id on every ModelMessage emitted by UI-to-model conversion, including assistant segments and derived tool results.
  • Add unit coverage and a provider-free chat-boundary E2E regression.
  • Add a patch changeset for @tanstack/ai.

Fixes #1063

Test plan

  • pnpm --filter @tanstack/ai test:lib --run — 80 files, 1451 tests passed.
  • pnpm --filter @tanstack/ai test:types — passed.
  • pnpm --filter @tanstack/ai test:oxlint — passed with existing warnings only.
  • pnpm --filter @tanstack/ai test:build — passed.
  • pnpm --filter @tanstack/ai-e2e test:e2e -- --grep "preserves UI message IDs" — passed.
  • pnpm test:dts — passed.
  • pnpm test:pr — all 73 Nx target groups reported success; the wrapper exits with the existing Windows Nx EISDIR: illegal operation on a directory, lstat 'D:' cleanup error.
  • The full E2E suite was attempted; browser-launch tests were blocked by the missing Playwright chromium_headless_shell-1200 binary, while request-only tests ran.

Summary by CodeRabbit

  • Bug Fixes

    • Preserved stable message IDs when converting UI messages to model messages.
    • Maintained IDs across text, tool calls, tool results, client outputs, approvals, and multi-part assistant messages.
    • Improved round-trip consistency for segmented and empty messages.
    • Added clearer handling for malformed or invalid message data.
  • Tests

    • Expanded coverage for message ID preservation and text- and tool-based conversations.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: abfd4305-a0f8-41b8-af25-37da9811e8c9

📥 Commits

Reviewing files that changed from the base of the PR and between adcbf5d and d9db7e1.

📒 Files selected for processing (3)
  • packages/ai/tests/message-converters.test.ts
  • testing/e2e/src/routes/api.message-ids.ts
  • testing/e2e/tests/chat.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ai/tests/message-converters.test.ts

📝 Walkthrough

Walkthrough

UI-to-model conversion now preserves stable UI message IDs across generated user, assistant, tool, approval, and fallback messages. Unit tests and a provider-free end-to-end route verify the behavior and validation responses.

Changes

Message ID preservation

Layer / File(s) Summary
Preserve IDs during message conversion
packages/ai/src/activities/chat/messages.ts, .changeset/fix-ui-message-id-roundtrip.md
Generated model messages retain the originating UI message ID across conversion paths. The changeset records the patch release.
Validate conversion and round trips
packages/ai/tests/message-converters.test.ts, packages/ai/tests/messages.test.ts
Tests cover segmented messages, tool flows, approvals, mixed inputs, and round-trip fixtures.
Exercise conversion through the API route
testing/e2e/src/routes/api.message-ids.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/tests/chat.spec.ts
A provider-free route validates input, converts valid messages, and returns JSON. End-to-end tests verify IDs, tool metadata, malformed JSON handling, and invalid message-part responses.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: alemtuzlak

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes preserving UI message IDs during AI model conversion.
Description check ✅ Passed The description explains the change, lists validation results, and documents the changeset and known E2E limitation.
Linked Issues check ✅ Passed The implementation preserves UIMessage.id for user messages, assistant segments, and derived tool-result ModelMessage objects required by issue #1063.
Out of Scope Changes check ✅ Passed The unit tests, provider-free E2E route, generated route tree, and changeset directly support the ID-preservation fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
testing/e2e/tests/chat.spec.ts (1)

56-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the provider-free aimock exception in the spec header.

This test does not call a provider HTTP endpoint. Add a header comment that states this condition and explains why aimock is intentionally not configured. Based on learnings: provider-free E2E specs must document the aimock policy exception in the spec header.

🤖 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 `@testing/e2e/tests/chat.spec.ts` around lines 56 - 58, Document the
provider-free aimock policy exception in the header of the chat E2E spec, near
the test identified by preserves UI message IDs at the server conversion
boundary. State that the test does not call a provider HTTP endpoint and
therefore intentionally does not configure aimock.

Source: Learnings

packages/ai/tests/message-converters.test.ts (1)

350-404: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the empty assistant fallback ID path.

This test does not exercise the messageList.length === 0 path. Add a test with an assistant UIMessage that has no emit-worthy parts. Assert that the fallback assistant ModelMessage retains uiMessage.id.

🤖 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 `@packages/ai/tests/message-converters.test.ts` around lines 350 - 404, The
existing test only verifies IDs on generated messages when parts produce output;
add a separate test for an assistant UIMessage with no emit-worthy parts. Call
uiMessageToModelMessages and assert it returns the fallback assistant
ModelMessage with uiMessage.id preserved.
🤖 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 `@testing/e2e/src/routes/api.message-ids.ts`:
- Around line 13-18: Update the POST handler to validate the parsed request body
with the existing or appropriate Zod schema before calling
convertMessagesToModelMessages. Catch JSON parsing and schema-validation
failures, returning a 400 Response for either invalid JSON or invalid
message/message-part data; only perform conversion after successful validation.

---

Nitpick comments:
In `@packages/ai/tests/message-converters.test.ts`:
- Around line 350-404: The existing test only verifies IDs on generated messages
when parts produce output; add a separate test for an assistant UIMessage with
no emit-worthy parts. Call uiMessageToModelMessages and assert it returns the
fallback assistant ModelMessage with uiMessage.id preserved.

In `@testing/e2e/tests/chat.spec.ts`:
- Around line 56-58: Document the provider-free aimock policy exception in the
header of the chat E2E spec, near the test identified by preserves UI message
IDs at the server conversion boundary. State that the test does not call a
provider HTTP endpoint and therefore intentionally does not configure aimock.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 94a97e80-caa5-4bf2-ab2d-fb7af1490b83

📥 Commits

Reviewing files that changed from the base of the PR and between 7d92296 and adcbf5d.

📒 Files selected for processing (7)
  • .changeset/fix-ui-message-id-roundtrip.md
  • packages/ai/src/activities/chat/messages.ts
  • packages/ai/tests/message-converters.test.ts
  • packages/ai/tests/messages.test.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.message-ids.ts
  • testing/e2e/tests/chat.spec.ts

Comment thread testing/e2e/src/routes/api.message-ids.ts Outdated
@mikemikimike

Copy link
Copy Markdown
Author

Addressed the review feedback in d9db7e1:\n\n- validate the provider-free message conversion request with Zod and return 400 for malformed JSON or invalid message parts\n- cover the empty assistant fallback ID path\n- document why this provider-free E2E intentionally does not configure aimock\n\nValidation: focused converter tests (64 passed), focused Playwright tests (3 passed), affected type checks, lint, build/publint, Sherif, Knip, docs, Kiira, and dangling declaration scan.

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.

uiMessageToModelMessages() drops stable message IDs

1 participant