blob: f911c39b2ec6b40c1b86fbd13fdb8e8479b97047 [file]
import '../../shared/models/prompt.dart';
import 'agent_timeline_item.dart';
/// A single user-initiated turn in the agent conversation.
///
/// Groups a user message together with the subsequent run items
/// (tool calls, thinking messages, errors) and the final assistant response.
class AgentTurn {
AgentTurn({
required this.userPrompt,
required this.runItems,
this.assistantMessage,
this.wasStopped = false,
this.error,
this.runDuration,
});
/// The user prompt that initiated this turn.
final Prompt userPrompt;
/// Tool calls, thinking messages, and errors that occurred during the run.
final List<AgentTimelineItem> runItems;
/// The final assistant response, if any.
final String? assistantMessage;
/// Whether the run was cancelled by the user.
final bool wasStopped;
/// The error text from the run, if any.
final String? error;
/// The wall-clock duration of the agent run for this turn.
final Duration? runDuration;
/// Groups a flat [timeline] into user-initiated [AgentTurn]s.
///
/// Each turn starts with a user message and includes all subsequent
/// timeline items until the next user message (or the end of the list).
static List<AgentTurn> groupFromTimeline(List<AgentTimelineItem> timeline) {
final turns = <AgentTurn>[];
int i = 0;
while (i < timeline.length) {
final item = timeline[i];
if (item is AgentPromptTimelineItem) {
final userPrompt = item.prompt;
final runItems = <AgentTimelineItem>[];
String? assistantMessage;
bool wasStopped = false;
String? error;
Duration? runDuration;
i++;
while (i < timeline.length) {
final nextItem = timeline[i];
if (nextItem is AgentPromptTimelineItem) {
break;
}
switch (nextItem) {
case AgentMessageTimelineItem(:final message, :final duration):
assistantMessage = message;
runDuration = duration;
case AgentPlanTimelineItem():
runItems.add(nextItem);
case AgentStoppedTimelineItem(:final duration):
wasStopped = true;
runDuration = duration;
case AgentErrorTimelineItem(error: final itemError, :final duration):
error = itemError;
runDuration = duration;
runItems.add(nextItem);
case AgentThinkingTimelineItem() || AgentToolTimelineItem():
runItems.add(nextItem);
case AgentPromptTimelineItem():
break;
}
i++;
}
// When a turn ends with an error or stop, the last thinking message
// was never finalized (duration == null) and would start a phantom
// live timer in the UI. Remove it.
if (error != null || wasStopped) {
for (int j = runItems.length - 1; j >= 0; j--) {
final ri = runItems[j];
if (ri is AgentThinkingTimelineItem && ri.duration == null) {
runItems.removeAt(j);
break;
}
}
}
turns.add(
AgentTurn(
userPrompt: userPrompt,
runItems: runItems,
assistantMessage: assistantMessage,
wasStopped: wasStopped,
error: error,
runDuration: runDuration,
),
);
} else {
// Skip items before first user message if any.
i++;
}
}
return turns;
}
}