Skip to content

Conversation

@AVGVSTVS96
Copy link
Member

@AVGVSTVS96 AVGVSTVS96 commented Dec 14, 2025

Important

Preserve metadata in message conversion for user, system, and assistant roles in react-ai-sdk.

  • Behavior:
    • Preserve metadata in AISDKMessageConverter for user, system, and assistant roles in convertMessage.ts.
    • Handle unstable_annotations, unstable_data, and custom fields in assistant role.
  • Functions:
    • Add metadata handling in toCreateMessage function in toCreateMessage.ts.

This description was created by Ellipsis for 29f6500. You can customize this summary. It will automatically update as commits are pushed.

@changeset-bot
Copy link

changeset-bot bot commented Dec 14, 2025

🦋 Changeset detected

Latest commit: 49f61de

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@assistant-ui/react-ai-sdk Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@claude
Copy link

claude bot commented Dec 14, 2025

Claude finished @AVGVSTVS96's task in 2m 21s —— View job


Code Review Complete

Summary

This PR successfully implements metadata preservation in message conversion for the react-ai-sdk package. The changes ensure that metadata is properly passed through for user, system, and assistant roles, with backward compatibility for legacy annotations and data fields in assistant messages.

Changes Analyzed

Modified Files:

  • packages/react-ai-sdk/src/ui/utils/convertMessage.ts - Core metadata preservation logic
  • packages/react-ai-sdk/src/ui/utils/toCreateMessage.ts - Metadata handling in message creation
  • .changeset/bright-snakes-beg.md - Changeset file

Key Changes:

  1. Added MessageMetadata type alias from ThreadMessageLike["metadata"]
  2. Preserved metadata for user and system roles (line 213, 222)
  3. Enhanced assistant role metadata handling with legacy field support (lines 225-253)
  4. Added metadata to toCreateMessage function output (line 52)

Critical Issues

1. Missing Null Safety Check for incomingMetadata?.custom

Location: convertMessage.ts:251

The code accesses incomingMetadata?.custom with optional chaining, but then falls back to an empty object {} if it's undefined. However, if incomingMetadata exists but custom is explicitly null or has other unexpected values, this could cause issues.

Current Code:

custom: incomingMetadata?.custom ?? {},

Consideration: This pattern is consistent with the other metadata fields and follows the same approach. The optional chaining should handle the case where incomingMetadata is undefined, and the nullish coalescing provides a safe default. This is likely acceptable, but worth verifying that custom field type allows this.

2. Metadata Type Casting Without Validation

Location: convertMessage.ts:213, 222, 234

The code casts message.metadata to MessageMetadata without validation:

metadata: message.metadata as MessageMetadata,

Risk: If the incoming UIMessage.metadata structure doesn't match ThreadMessageLike["metadata"], this could lead to type mismatches at runtime.

Recommendation: Consider adding runtime validation or at least a type guard to ensure the metadata structure is valid before casting. Alternatively, document the expected contract clearly.

3. Complex Type Inference May Be Fragile

Location: convertMessage.ts:227-231

annotations?: MessageMetadata extends {
  unstable_annotations?: infer A;
}
  ? A
  : never;

This conditional type inference is clever but may be fragile if MessageMetadata structure changes. If the metadata type evolves, this inference could break silently.

Recommendation: Consider extracting this type to a named type or adding a comment explaining the inference logic.


Suggested Improvements

1. Add JSDoc Documentation

The MessageMetadata type alias and the metadata handling logic would benefit from documentation explaining:

  • What metadata is expected to contain
  • The backward compatibility strategy for annotations and data
  • When to use each metadata field

Example:

/**
 * Metadata type for messages, extracted from ThreadMessageLike.
 * Contains unstable_annotations, unstable_data, and custom fields.
 */
type MessageMetadata = ThreadMessageLike["metadata"];
2. Extract Legacy Field Handling Logic

The assistant case has complex fallback logic that could be extracted into a helper function:

const extractAssistantMetadata = (
  message: UIMessage,
  incomingMetadata: MessageMetadata | undefined
): MessageMetadata => {
  const legacyMessage = message as UIMessage & {
    annotations?: MessageMetadata extends { unstable_annotations?: infer A }
      ? A
      : never;
    data?: unknown | readonly unknown[];
  };
  
  return {
    unstable_annotations:
      incomingMetadata?.unstable_annotations ?? legacyMessage.annotations,
    unstable_data:
      incomingMetadata?.unstable_data ??
      (Array.isArray(legacyMessage.data)
        ? legacyMessage.data
        : legacyMessage.data
          ? [legacyMessage.data]
          : undefined),
    custom: incomingMetadata?.custom ?? {},
  };
};

This would improve readability and testability.

3. Consider Adding Tests

The react-ai-sdk package appears to lack test files. Given the complexity of metadata handling and backward compatibility requirements, consider adding tests for:

  • User/system message metadata preservation
  • Assistant message metadata with new format
  • Assistant message metadata with legacy annotations field
  • Assistant message metadata with legacy data field (both array and single value)
  • Assistant message metadata with both new and legacy fields (verify precedence)
  • Undefined/null metadata handling

Note: This is a broader package concern, not specific to this PR, but the metadata logic would particularly benefit from test coverage.

4. Type Safety for Legacy Message Cast

The cast to legacyMessage could be more type-safe:

type LegacyAssistantMessage = UIMessage & {
  annotations?: MessageMetadata extends {
    unstable_annotations?: infer A;
  }
    ? A
    : never;
  data?: unknown | readonly unknown[];
};

// Then use:
const legacyMessage = message as LegacyAssistantMessage;

This makes the intent clearer and the type reusable.

5. Consistency in Metadata Access Pattern

For user and system roles, metadata is cast directly:

metadata: message.metadata as MessageMetadata,

But for assistant, it's extracted first:

const incomingMetadata = message.metadata as MessageMetadata;

Consider using consistent patterns across all three cases for maintainability.


Good Practices Observed

  • ✅ Proper use of optional chaining and nullish coalescing for safe fallbacks
  • ✅ Backward compatibility maintained for legacy annotations and data fields
  • ✅ Appropriate changeset file included for the patch version bump
  • ✅ Code formatting applied by autofix.ci

Security & Performance

  • Security: No security concerns identified. Metadata is passed through without evaluation or execution.
  • Performance: Minimal performance impact. The metadata extraction logic runs once per message conversion.

Changeset Review

Appropriate: The changeset correctly identifies this as a patch change for @assistant-ui/react-ai-sdk with a clear description.


Recommendation

Approve with Suggestions - The implementation is functional and maintains backward compatibility. The critical issues identified are primarily about type safety and defensive programming. Consider addressing the suggested improvements in a follow-up if not in this PR.

The metadata preservation logic is sound, but would benefit from:

  1. Documentation of the metadata contract
  2. Test coverage for the various metadata scenarios
  3. Slightly improved type safety in the casting operations

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

AVGVSTVS96 and others added 3 commits December 21, 2025 16:57
Signed-off-by: Bassim Shahidy <122117267+AVGVSTVS96@users.noreply.github.com>
@dflynn15
Copy link
Contributor

dflynn15 commented Jan 9, 2026

@Yonom @AVGVSTVS96 is there something preventing this from being merged?

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.

[react-ai-sdk] Preserve metadata when converting messages between assistant-ui and AI SDK formats

3 participants