Skip to content

feat: add ConversationTurn enum and ConversationReady event (#847)#855

Open
omarfarouk228 wants to merge 2 commits intoflutter:mainfrom
omarfarouk228:feat/observable-turn-api
Open

feat: add ConversationTurn enum and ConversationReady event (#847)#855
omarfarouk228 wants to merge 2 commits intoflutter:mainfrom
omarfarouk228:feat/observable-turn-api

Conversation

@omarfarouk228
Copy link
Copy Markdown

Description

Fixes #847
Adds an explicit, observable API to detect whose turn it is in a conversation.

Changes

  • Added ConversationTurn enum with user and agent values.
  • Added turn getter on ConversationState, derived from isWaiting.
  • Added ConversationReady event, emitted when the agent finishes responding — the complement of the existing ConversationWaiting event.
  • Updated chat_session.dart and simple_chat_test.dart to handle the new ConversationReady case in their exhaustive switches.

Event sequence for a single turn

  1. ConversationWaiting — agent's turn begins
  2. ConversationSurfaceAdded / ConversationComponentsUpdated — AI streams UI
  3. ConversationReady — agent's turn ends, user's turn begins

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-devrel channel on Discord.

Copy link
Copy Markdown
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 a ConversationTurn enum and a turn getter in ConversationState to track whether it is the user's or the agent's turn. It also adds a ConversationReady event, emitted when the agent finishes responding. Feedback focuses on potential state inconsistencies during concurrent requests, the need for safety checks when adding events to a potentially closed controller during disposal, and an opportunity to improve test assertions by utilizing the new ConversationReady event.

Comment on lines +114 to +115
ConversationTurn get turn =>
isWaiting ? ConversationTurn.agent : ConversationTurn.user;
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.

high

The turn property is derived from isWaiting, which currently does not account for concurrent calls to sendRequest. If multiple requests are initiated, the first one to complete will set isWaiting to false, incorrectly signaling that it is the user's turn even if other agent responses are still pending. Consider implementing a request counter or a concurrency guard in sendRequest to ensure this state remains reliable.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines +132 to +134
case ConversationReady():
// No-op for now
break;
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.

medium

With the introduction of the ConversationReady event, we can now verify the turn's state immediately after the agent finishes responding. This provides a more precise point for assertions than waiting for the next ConversationWaiting event.

Suggested change
case ConversationReady():
// No-op for now
break;
case ConversationReady():
verifyTurn();
break;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done

@@ -190,6 +213,7 @@ interface class Conversation {
_eventController.add(ConversationError(exception, stackTrace));
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.

medium

Since sendRequest is an asynchronous method, there is a risk that dispose() is called while a request is in flight. If the _eventController is closed during the await, calling add() will throw an exception. It is safer to check _eventController.isClosed before adding the event.

Suggested change
_eventController.add(ConversationError(exception, stackTrace));
if (!_eventController.isClosed) _eventController.add(ConversationError(exception, stackTrace));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

_eventController.add(ConversationError(exception, stackTrace));
} finally {
_updateState((s) => s.copyWith(isWaiting: false));
_eventController.add(ConversationReady());
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.

medium

Similar to the error event above, adding the ConversationReady event after an await should be guarded against a closed controller to prevent exceptions during disposal.

Suggested change
_eventController.add(ConversationReady());
if (!_eventController.isClosed) _eventController.add(ConversationReady());

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

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.

Create clear API to observe who's turn it is.

1 participant