// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member part of 'chat_bloc.dart'; extension _ChatBlocEvents on ChatBloc { void _handleEvent(AgUiEvent event) { switch (event.type) { case AgUiEventType.runStarted: final runStartedEvent = event as RunStartedEvent; _recordRunStarted( runId: runStartedEvent.runId, threadId: runStartedEvent.threadId, ); emit( state.copyWith( isSending: false, isWaitingFirstToken: true, isCancelling: false, error: null, currentStage: null, hasSeenStep: false, ), ); case AgUiEventType.runFinished: _trackChatCompleted(); _clearRunMetrics(); emit(_resetRunState()); case AgUiEventType.runError: final errorEvent = event as RunErrorEvent; _clearRunMetrics(); final isCanceledByUser = errorEvent.code == 'RUN_CANCELED'; emit( _resetRunState(error: isCanceledByUser ? null : errorEvent.message), ); case AgUiEventType.stepStarted: _handleStepStarted(event as StepStartedEvent); case AgUiEventType.stepFinished: _handleStepFinished(event as StepFinishedEvent); case AgUiEventType.textMessageEnd: _handleTextMessageEnd(event as TextMessageEndEvent); case AgUiEventType.toolCallResult: _handleToolCallResult(event as ToolCallResultEvent); case AgUiEventType.unknown: break; } } void _handleStepStarted(StepStartedEvent event) { emit( state.copyWith( currentStage: stageFromStepName(event.stepName), hasSeenStep: true, ), ); } void _handleStepFinished(StepFinishedEvent event) { if (state.currentStage == stageFromStepName(event.stepName)) { emit(state.copyWith(currentStage: null)); } } void _handleTextMessageEnd(TextMessageEndEvent event) { _recordRunFirstResponse(); final timestamp = DateTime.now(); final items = _updateOrAddMessage( state.items, event.messageId, event.answer, event.suggestedActions, timestamp, ); emit( state.copyWith( items: items, currentMessageId: null, isWaitingFirstToken: false, isStreaming: false, ), ); } List _updateOrAddMessage( List items, String messageId, String content, List suggestedActions, DateTime timestamp, ) { final result = List.from(items); final index = result.indexWhere( (item) => item.id == messageId && item is TextMessageItem, ); if (index >= 0) { final existing = result[index] as TextMessageItem; result[index] = existing.copyWith( content: content, isStreaming: false, suggestedActions: suggestedActions, ); return result; } result.add( TextMessageItem( id: messageId, content: content, timestamp: timestamp, sender: MessageSender.ai, isStreaming: false, suggestedActions: suggestedActions, ), ); return result; } void _handleToolCallResult(ToolCallResultEvent event) { if (_shouldRefreshCalendarForTool(event)) { unawaited(_refreshCalendarAfterToolMutation()); } final timestamp = DateTime.now(); final items = List.from(state.items); final uiSchema = event.uiSchema; if (uiSchema != null) { _upsertToolResultUi(items, event.toolCallId, uiSchema, timestamp); } emit(state.copyWith(items: items)); } void _upsertToolResultUi( List items, String toolCallId, Map uiSchema, DateTime timestamp, ) { final uiItemId = '$toolCallId-ui'; final uiItem = ToolResultItem( id: uiItemId, callId: toolCallId, uiSchema: uiSchema, timestamp: timestamp, sender: MessageSender.ai, ); final existingIndex = items.indexWhere((item) => item.id == uiItemId); if (existingIndex >= 0) { items[existingIndex] = uiItem; return; } items.add(uiItem); } List _convertHistoryMessages(List messages) { final converted = []; for (final msg in messages) { final normalizedRole = msg.role.toLowerCase(); final isUser = normalizedRole == 'user'; final isTool = normalizedRole == 'tool'; final sender = isUser ? MessageSender.user : MessageSender.ai; final attachments = msg.attachments .map( (attachment) => { 'url': attachment.url, 'mimeType': attachment.mimeType, }, ) .toList(); if (!isTool && (msg.content.isNotEmpty || isUser)) { converted.add( TextMessageItem( id: msg.id, content: msg.content, timestamp: msg.timestamp, sender: sender, isLocalEcho: false, attachments: attachments, suggestedActions: msg.suggestedActions, ), ); } if (isTool && msg.uiSchema != null) { converted.add( ToolResultItem( id: '${msg.id}-ui', callId: msg.id, uiSchema: msg.uiSchema!, timestamp: msg.timestamp, sender: MessageSender.ai, ), ); } } return converted; } DateTime? _extractDateFromItems(List items) { if (items.isEmpty) { return null; } return items .map( (item) => DateTime( item.timestamp.year, item.timestamp.month, item.timestamp.day, ), ) .reduce((a, b) => a.isBefore(b) ? a : b); } }