Skip to main content

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}