| import 'agent_tool_event.dart'; |
| import 'agent_tool_id.dart'; |
| import 'agent_tool_metadata.dart'; |
| |
| /// UI-friendly representation of an executed tool event. |
| class AgentToolEventPresentation { |
| const AgentToolEventPresentation({ |
| required this.toolLabel, |
| required this.actionText, |
| required this.ariaLabel, |
| required this.uiKind, |
| this.errorText, |
| }); |
| |
| /// The user-facing label/name of the tool. |
| final String toolLabel; |
| |
| /// Human-readable summary of the action performed by the tool. |
| final String actionText; |
| |
| /// Screen-reader accessible label describing the tool's execution and outcome. |
| final String ariaLabel; |
| |
| /// The UI category of the tool (e.g. read, edit, search) for icons and colors. |
| final AgentToolUiKind uiKind; |
| |
| /// Error message details, if the tool execution failed. |
| final String? errorText; |
| } |
| |
| /// Helper that transforms low-level [AgentToolEvent] objects into [AgentToolEventPresentation] |
| /// suitable for rendering in the UI. |
| class AgentToolEventPresenter { |
| const AgentToolEventPresenter._(); |
| |
| /// Converts a raw [AgentToolEvent] into a structured [AgentToolEventPresentation]. |
| static AgentToolEventPresentation present(AgentToolEvent event) { |
| final toolLabel = event.toolName; |
| final actionText = _buildActionText(event); |
| final errorText = event.status == ToolStatus.error ? event.errorMessage : null; |
| final statusText = event.status == ToolStatus.running |
| ? 'running' |
| : (event.status == ToolStatus.success ? 'succeeded' : 'failed'); |
| final ariaLabel = errorText == null |
| ? 'Tool $toolLabel $statusText. $actionText.' |
| : 'Tool $toolLabel $statusText. $actionText. Error: $errorText.'; |
| |
| return AgentToolEventPresentation( |
| toolLabel: toolLabel, |
| actionText: actionText, |
| ariaLabel: ariaLabel, |
| uiKind: event.uiKind, |
| errorText: errorText, |
| ); |
| } |
| |
| static String _buildActionText(AgentToolEvent event) { |
| final path = event.targetPath ?? 'unknown file'; |
| |
| return switch ((event.toolId, event.status)) { |
| (AgentToolId.writeFile, ToolStatus.running) => 'Editing $path', |
| (AgentToolId.writeFile, _) => 'Edited $path', |
| |
| (AgentToolId.applyPatch, ToolStatus.running) => 'Editing $path', |
| (AgentToolId.applyPatch, _) => 'Edited $path', |
| |
| (AgentToolId.readFile, ToolStatus.running) => 'Reading $path', |
| (AgentToolId.readFile, _) => 'Read $path', |
| |
| (AgentToolId.readLines, ToolStatus.running) => 'Reading $path', |
| (AgentToolId.readLines, _) => |
| event.startLine != null && event.endLine != null |
| ? 'Read lines ${event.startLine}-${event.endLine} from $path' |
| : 'Read $path', |
| |
| (AgentToolId.deleteFile, ToolStatus.running) => 'Deleting $path', |
| (AgentToolId.deleteFile, _) => 'Deleted $path', |
| |
| (AgentToolId.grep, ToolStatus.running) => 'Searching workspace', |
| (AgentToolId.grep, _) => 'Searched workspace', |
| |
| (AgentToolId.glob, ToolStatus.running) => 'Searching files', |
| (AgentToolId.glob, _) => 'Searched files', |
| |
| (AgentToolId.fuzzyFileSearch, ToolStatus.running) => 'Searching files', |
| (AgentToolId.fuzzyFileSearch, _) => 'Searched files', |
| |
| (AgentToolId.analyzeStart, ToolStatus.running) => 'Analyzing files', |
| (AgentToolId.analyzeStart, _) => 'Analyzed files', |
| |
| (AgentToolId.askQuestion, ToolStatus.running) => 'Asking a question', |
| (AgentToolId.askQuestion, _) => 'Asked a question', |
| |
| // planExit is never rendered as a tool row (uiKind == interaction). |
| (AgentToolId.planExit, _) => throw StateError('Unreachable: planExit is not displayed'), |
| |
| (AgentToolId.controlApp, ToolStatus.running) => switch (event.subAction) { |
| 'restart' => 'Restarting preview', |
| 'reload' => 'Reloading preview', |
| _ => 'Starting preview', |
| }, |
| (AgentToolId.controlApp, _) => switch (event.subAction) { |
| 'restart' => 'Restarted preview', |
| 'reload' => 'Reloaded preview', |
| _ => 'Started preview', |
| }, |
| |
| (AgentToolId.workspaceMaintenance, ToolStatus.running) => switch (event.subAction) { |
| 'clean' => 'Cleaning workspace', |
| 'pub_get' => 'Resolving packages', |
| 'full_rebuild' => 'Cleaning and rebuilding workspace', |
| _ => 'Running maintenance', |
| }, |
| (AgentToolId.workspaceMaintenance, _) => switch (event.subAction) { |
| 'clean' => 'Cleaned workspace', |
| 'pub_get' => 'Resolved packages', |
| 'full_rebuild' => 'Cleaned and rebuilt workspace', |
| _ => 'Maintenance finished', |
| }, |
| |
| (AgentToolId.generateImage, ToolStatus.running) => 'Generating image', |
| (AgentToolId.generateImage, _) => 'Generated image', |
| |
| (AgentToolId.getWidgetTree, _) => event.summary, |
| (AgentToolId.getWidgetDetails, _) => event.summary, |
| (AgentToolId.findWidget, _) => event.summary, |
| |
| (AgentToolId.screenshotWidget, ToolStatus.running) => 'Capturing screenshot', |
| (AgentToolId.screenshotWidget, _) => 'Captured screenshot', |
| |
| (AgentToolId.tapWidget, _) => event.summary, |
| (AgentToolId.enterText, _) => event.summary, |
| (AgentToolId.scrollWidget, _) => event.summary, |
| |
| (AgentToolId.changeDeviceMode, ToolStatus.running) => 'Changing device mode', |
| (AgentToolId.changeDeviceMode, _) => event.summary, |
| }; |
| } |
| } |