Skip to content

Conversation

@polina-c
Copy link
Collaborator

@polina-c polina-c commented Dec 24, 2025

Contributes to #607

  1. Remove ChatMessageRole: this library should not enforce the set of roles, as it can vary from case to case. The role should be part of envelope in client's code.
  2. Rename ChatMessage to Message, because this library can be used in chats and in other systems.
  3. Use constants for repeating literals
  4. Remove postfix 'Part' from part type literals, to reduce size of messages.
  5. Extend test coverage for primitives.
  6. Add test for example.
  7. fix case of null in nameFromMimeType
  8. Follow Gemini's advises below

gemini-code-assist[bot]

This comment was marked as outdated.

@polina-c polina-c changed the title - Some cleanups to make the library useable for wide range of use cases. Dec 24, 2025
@polina-c polina-c changed the title Some cleanups to make the library useable for wide range of use cases. Updates to make the library applicable for wide range of use cases. Dec 24, 2025
@polina-c polina-c changed the title Updates to make the library applicable for wide range of use cases. Updates to make the library applicable for wider range of use cases. Dec 24, 2025
gemini-code-assist[bot]

This comment was marked as outdated.

@polina-c
Copy link
Collaborator Author

/gemini review

@polina-c polina-c marked this pull request as ready for review December 24, 2025 06:08
@polina-c
Copy link
Collaborator Author

polina-c commented Dec 24, 2025

@csells , how does it look?

@polina-c polina-c changed the title Updates to make the library applicable for wider range of use cases. Updates to make the library applicable for wider range of use cases, and more stable. Dec 24, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several significant and beneficial changes to make the library more flexible and robust. Key changes include removing the enforced ChatMessageRole, renaming ChatMessage to Message, and using constants for JSON keys to reduce message size. The test coverage has been significantly improved, including a new test for the example, which is excellent. My review focuses on some opportunities to improve code clarity and leverage more modern Dart features, such as using const constructors for immutable classes and refactoring switch statements/expressions for better readability. Overall, this is a solid pull request that improves the library's design and usability.

@polina-c
Copy link
Collaborator Author

Options for name for the class ChatMessage:

  1. Message (I used it for now, but I am open)
  2. MessageBody
  3. MessageContent
  4. Content

@csells
Copy link
Contributor

csells commented Dec 30, 2025

I think you left one option:

  1. ChatMessage

Can I ask what problem is solved by renaming it?

@polina-c polina-c removed the request for review from gspencergoog December 30, 2025 14:57
@polina-c
Copy link
Collaborator Author

I think you left one option:

  1. ChatMessage

Can I ask what problem is solved by renaming it?

Sure!

This library is intended to serve very wide range of client applications, interacting with AI.

'Chat' is implying type of interaction, where history is preserved, and UI looks as timeline.

But, there can be many other types. And, I am sure we do not know these types as they do not exist yet.

Does it help?

@csells
Copy link
Contributor

csells commented Dec 30, 2025

That sounds abstract. My understanding of the goal of this package was to concretely provide base types for chat history, which would eliminate the need to provide mapping layers between GenUI, dartantic and other packages like the Flutter AI Toolkit.

What concrete scenarios do you have in mind?

@polina-c
Copy link
Collaborator Author

polina-c commented Dec 30, 2025

That sounds abstract. My understanding of the goal of this package was to concretely provide base types for chat history, which would eliminate the need to provide mapping layers between GenUI, dartantic and other packages like the Flutter AI Toolkit.

What concrete scenarios do you have in mind?

For example, interaction with some history-less agent without chat-like UI.
Like agent that is doing google search, without history, using google-search-tool and then selecting responses that are more educational than offering concrete products and services.

@csells
Copy link
Contributor

csells commented Dec 30, 2025

To which agent are you referring that provides such a history-based API without it being conversational? I'm unfamiliar with any that work like that.


/// A message in a conversation between a user and a model.
@immutable
class Message {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this class intended to be implemented? If not, let's mark it using the base modifier.

static const metadata = 'metadata';
}

/// A message in a conversation between a user and a model.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not super-clear what "between" means. Does it mean that a Message can be sent by the user to the model, and by the model to the user? If so, consider spelling it out, if not in the intro paragraph, then in a second paragraph.

/// A message in a conversation between a user and a model.
@immutable
class Message {
/// Creates a new message.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Document the arguments. Special cases, such as when parts and metadata are both empty, could be particularly important.


/// Creates a new message, taking a text string as separate parameter.
Message(
String text, {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like this is a convenience constructor. Typically, the default constructor is the main constructor, and named constructors are conveniences. That is, I'd make fromParts the default, and this one named Message.fromText.

/// Creates a new message, taking a text string as separate parameter.
Message(
String text, {
List<Part> parts = const [],
Copy link
Collaborator

Choose a reason for hiding this comment

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

What happens if one of the parts is a TextPart? Is it OK to have two text parts?

}

int mapHashCode(Map<dynamic, dynamic>? map) {
if (map == null) return 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's avoid redefining the hash code for null. null already has hashCode, and it is equal to 2011 on the Dart VM, and 0 on the web. Let's just return null.hashCode and let the language/compiler do its thing.

@immutable
abstract class Part {
/// Creates a new part.
const Part();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need a constructor for creating an empty part?

final Map<String, dynamic> metadata;

/// Gets the text content of the message by concatenating all text parts.
String get text => parts.whereType<TextPart>().map((p) => p.text).join();
Copy link
Collaborator

Choose a reason for hiding this comment

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

If I have parts "happy", "new", "year", "world", will the concatenated text be "happynewyearworld"?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, but luckily the text parts include whitespace, so we're good.

/// Gets the text content of the message by concatenating all text parts.
String get text => parts.whereType<TextPart>().map((p) => p.text).join();

/// Checks if this message contains any tool calls.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Checks" is an unusual way to describe a getter. It's as if it fails if there are no tool calls. Typically we say, "Returns whether this message contains...", or "Returns true if this message contains...".

This comment applies to other getters in the library.

.where((p) => p.kind == ToolPartKind.result)
.toList();

/// Converts the message to a JSON-compatible map.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: consider "Serializes" instead of "Converts". The word "convert" implies that this same object transforms/mutates to something else, but what this method does is create a new object that represents this object in JSON format.

@mrtakao550-gif mrtakao550-gif mentioned this pull request Dec 30, 2025
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