// 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: emit( state.copyWith( isSending: false, isWaitingFirstToken: true, isCancelling: false, error: null, currentStage: null, hasSeenStep: false, ), ); case AgUiEventType.runFinished: emit( _resetRunState().copyWith(items: _removeToolCallItems(state.items)), ); case AgUiEventType.runError: final errorEvent = event as RunErrorEvent; final isCanceledByUser = errorEvent.code == 'RUN_CANCELED'; emit( _resetRunState( error: isCanceledByUser ? null : errorEvent.message, ).copyWith( items: _markActiveToolCallsFailed( state.items, reason: isCanceledByUser ? L10n.current.chatRunCanceled : L10n.current.chatRunFailed, ), ), ); case AgUiEventType.stepStarted: _handleStepStarted(event as StepStartedEvent); case AgUiEventType.stepFinished: _handleStepFinished(event as StepFinishedEvent); case AgUiEventType.textMessageEnd: _handleTextMessageEnd(event as TextMessageEndEvent); case AgUiEventType.toolCallStart: _handleToolCallStart(event as ToolCallStartEvent); case AgUiEventType.toolCallArgs: _handleToolCallArgs(event as ToolCallArgsEvent); case AgUiEventType.toolCallEnd: _handleToolCallEnd(event as ToolCallEndEvent); case AgUiEventType.toolCallResult: _handleToolCallResult(event as ToolCallResultEvent); case AgUiEventType.toolCallError: _handleToolCallError(event as ToolCallErrorEvent); 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) { final timestamp = DateTime.now(); final items = _updateOrAddMessage( state.items, event.messageId, event.answer, timestamp, ); final uiSchema = event.uiSchema; if (uiSchema != null) { _upsertUiSchema(items, event.messageId, uiSchema, timestamp); } emit( state.copyWith( items: _removeToolCallItems(items), currentMessageId: null, isWaitingFirstToken: false, isStreaming: false, ), ); } List _updateOrAddMessage( List items, String messageId, String content, 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); return result; } result.add( TextMessageItem( id: messageId, content: content, timestamp: timestamp, sender: MessageSender.ai, isStreaming: false, ), ); return result; } void _upsertUiSchema( List items, String messageId, Map uiSchema, DateTime timestamp, ) { final uiItemId = '$messageId-ui'; final uiItem = ToolResultItem( id: uiItemId, callId: messageId, 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); } void _handleToolCallStart(ToolCallStartEvent event) { final exists = state.items.any( (item) => item is ToolCallItem && item.id == event.toolCallId, ); if (exists) { return; } emit( state.copyWith( items: [ ...state.items, ToolCallItem( id: event.toolCallId, callId: event.toolCallId, toolName: event.toolCallName, args: const {}, status: ToolCallStatus.pending, timestamp: DateTime.now(), sender: MessageSender.ai, ), ], ), ); } void _handleToolCallArgs(ToolCallArgsEvent event) { emit( state.copyWith( items: state.items.map((item) { if (item is ToolCallItem && item.id == event.toolCallId) { return item.copyWith(args: event.args); } return item; }).toList(), ), ); } void _handleToolCallEnd(ToolCallEndEvent event) { emit( state.copyWith( items: state.items.map((item) { if (item is ToolCallItem && item.id == event.toolCallId) { return item.copyWith(status: ToolCallStatus.executing); } return item; }).toList(), ), ); } void _handleToolCallResult(ToolCallResultEvent event) { if (_shouldRefreshCalendarForTool(event)) { unawaited(_refreshCalendarAfterToolMutation()); } emit( state.copyWith( items: state.items.map((item) { if (item is ToolCallItem && item.id == event.toolCallId) { return item.copyWith(status: ToolCallStatus.completed); } return item; }).toList(), ), ); } void _handleToolCallError(ToolCallErrorEvent event) { emit( state.copyWith( items: state.items.map((item) { if (item is ToolCallItem && item.id == event.toolCallId) { return item.copyWith( status: ToolCallStatus.error, errorMessage: event.error, ); } return item; }).toList(), ), ); } List _removeToolCallItems(List items) { return items.where((item) => item is! ToolCallItem).toList(); } List _markActiveToolCallsFailed( List items, { required String reason, }) { return items.map((item) { if (item is! ToolCallItem || item.status == ToolCallStatus.error || item.status == ToolCallStatus.completed) { return item; } return item.copyWith(status: ToolCallStatus.error, errorMessage: reason); }).toList(); } List _convertHistoryMessages(List messages) { final converted = []; for (final msg in messages) { final normalizedRole = msg.role.toLowerCase(); final isUser = normalizedRole == 'user'; final isTool = normalizedRole == 'tool' || normalizedRole == 'tools'; 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, ), ); } 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); } }