robit_agent/event.rs
1//! Agent event and message types for Frontend <-> Agent communication.
2
3use crate::tool::ToolResult;
4
5/// Unique session identifier (UUID v4).
6pub type SessionId = String;
7
8/// Create a new random session ID.
9pub fn new_session_id() -> SessionId {
10 uuid::Uuid::new_v4().to_string()
11}
12
13/// A media attachment (image, file, etc.) included in a user message.
14#[derive(Debug, Clone)]
15pub struct MediaAttachment {
16 /// MIME type (e.g. "image/jpeg", "application/pdf").
17 pub content_type: String,
18 /// URL to access the media.
19 pub url: String,
20 /// Original filename if available.
21 pub filename: Option<String>,
22 /// File size in bytes if available.
23 pub size: Option<u64>,
24 /// Image width in pixels if available.
25 pub width: Option<u32>,
26 /// Image height in pixels if available.
27 pub height: Option<u32>,
28}
29
30impl MediaAttachment {
31 /// Whether this attachment is an image.
32 pub fn is_image(&self) -> bool {
33 self.content_type.starts_with("image/")
34 }
35
36 /// A human-readable description for the LLM.
37 pub fn describe(&self) -> String {
38 let filename = self.filename.as_deref().unwrap_or("unknown");
39 let type_desc = if self.is_image() { "图片" } else { "文件" };
40 let size_str = self
41 .size
42 .map(|s| format!(" ({:.1}KB)", s as f64 / 1024.0))
43 .unwrap_or_default();
44 format!("[用户发送了{}: {}{}]", type_desc, filename, size_str)
45 }
46}
47
48/// Events pushed from Agent to Frontend.
49#[derive(Debug)]
50pub enum AgentEvent {
51 /// Streaming text delta from LLM response.
52 TextDelta(String),
53
54 /// LLM requested a tool call. Frontend should display and optionally wait for confirmation.
55 ToolCallRequested {
56 tool_call_id: String,
57 name: String,
58 arguments: String,
59 },
60
61 /// Tool execution completed with result.
62 ToolCallResult {
63 tool_call_id: String,
64 result: ToolResult,
65 },
66
67 /// Current turn is complete (LLM finished responding, no more tool calls).
68 TurnComplete,
69
70 /// An error occurred during agent execution.
71 Error(crate::error::AgentError),
72
73 /// A skill was triggered. Frontend can display this as a system notice.
74 SkillTriggered { name: String, description: String },
75
76 /// An async background task finished (success, failure, or cancellation).
77 /// `result` is the final outcome, already reinjected into the conversation
78 /// as a system-notification message, and the LLM is being woken to act on
79 /// it. Frontends use this to update task-progress UI; no further action is
80 /// required for the conversation itself.
81 ///
82 /// Task *start* is signalled by the regular `ToolCallResult` event whose
83 /// `result.is_pending` is `true` (with `result.pending_task_id` set).
84 AsyncToolCompleted {
85 task_id: String,
86 tool_call_id: String,
87 result: ToolResult,
88 },
89}
90
91/// Messages sent from Frontend to Agent.
92#[derive(Debug)]
93pub enum FrontendMessage {
94 /// User typed a new message with optional media attachments.
95 UserInput {
96 text: String,
97 attachments: Vec<MediaAttachment>,
98 },
99
100 /// User wants to cancel the current operation.
101 Cancel,
102
103 /// User responded to a tool confirmation request.
104 ConfirmationResponse {
105 tool_call_id: String,
106 approved: bool,
107 },
108
109 /// User requested cancellation of a specific async background task.
110 CancelTask { task_id: String },
111}
112
113// Keep backward compatibility: from String to UserInput with no attachments.
114impl From<String> for FrontendMessage {
115 fn from(text: String) -> Self {
116 Self::UserInput {
117 text,
118 attachments: vec![],
119 }
120 }
121}
122
123// Also support &str for convenience.
124impl From<&str> for FrontendMessage {
125 fn from(text: &str) -> Self {
126 Self::UserInput {
127 text: text.to_string(),
128 attachments: vec![],
129 }
130 }
131}