tower_a2a/protocol/
task.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::{error::TaskError, message::Message, Artifact};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct Task {
14 pub id: String,
16
17 pub status: TaskStatus,
19
20 pub input: Message,
22
23 #[serde(default, skip_serializing_if = "Vec::is_empty")]
25 pub artifacts: Vec<Artifact>,
26
27 #[serde(default, skip_serializing_if = "Vec::is_empty")]
29 pub history: Vec<Message>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub error: Option<TaskError>,
34
35 #[serde(rename = "createdAt")]
37 pub created_at: DateTime<Utc>,
38
39 #[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none")]
41 pub updated_at: Option<DateTime<Utc>>,
42
43 #[serde(rename = "contextId", skip_serializing_if = "Option::is_none")]
45 pub context_id: Option<String>,
46}
47
48impl Task {
49 pub fn new(id: impl Into<String>, input: Message) -> Self {
51 Self {
52 id: id.into(),
53 status: TaskStatus::Submitted,
54 input,
55 artifacts: Vec::new(),
56 history: Vec::new(),
57 error: None,
58 created_at: Utc::now(),
59 updated_at: None,
60 context_id: None,
61 }
62 }
63
64 pub fn is_terminal(&self) -> bool {
66 matches!(
67 self.status,
68 TaskStatus::Completed
69 | TaskStatus::Failed
70 | TaskStatus::Cancelled
71 | TaskStatus::Rejected
72 )
73 }
74
75 pub fn is_processing(&self) -> bool {
77 matches!(self.status, TaskStatus::Submitted | TaskStatus::Working)
78 }
79
80 pub fn requires_input(&self) -> bool {
82 matches!(
83 self.status,
84 TaskStatus::InputRequired | TaskStatus::AuthRequired
85 )
86 }
87
88 pub fn with_status(mut self, status: TaskStatus) -> Self {
90 self.status = status;
91 self.updated_at = Some(Utc::now());
92 self
93 }
94
95 pub fn with_artifact(mut self, artifact: Artifact) -> Self {
97 self.artifacts.push(artifact);
98 self.updated_at = Some(Utc::now());
99 self
100 }
101
102 pub fn with_history_message(mut self, message: Message) -> Self {
104 self.history.push(message);
105 self.updated_at = Some(Utc::now());
106 self
107 }
108
109 pub fn with_error(mut self, error: TaskError) -> Self {
111 self.error = Some(error);
112 self.updated_at = Some(Utc::now());
113 self
114 }
115
116 pub fn with_context_id(mut self, context_id: impl Into<String>) -> Self {
118 self.context_id = Some(context_id.into());
119 self
120 }
121}
122
123#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
128#[serde(rename_all = "kebab-case")]
129pub enum TaskStatus {
130 Submitted,
132
133 Working,
135
136 InputRequired,
138
139 AuthRequired,
141
142 Completed,
144
145 Failed,
147
148 Cancelled,
150
151 Rejected,
153}
154
155impl TaskStatus {
156 pub fn is_terminal(&self) -> bool {
158 matches!(
159 self,
160 TaskStatus::Completed
161 | TaskStatus::Failed
162 | TaskStatus::Cancelled
163 | TaskStatus::Rejected
164 )
165 }
166
167 pub fn requires_action(&self) -> bool {
169 matches!(self, TaskStatus::InputRequired | TaskStatus::AuthRequired)
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct SendMessageRequest {
176 pub message: Message,
178
179 #[serde(default)]
181 pub stream: bool,
182
183 #[serde(rename = "contextId", skip_serializing_if = "Option::is_none")]
185 pub context_id: Option<String>,
186
187 #[serde(rename = "taskId", skip_serializing_if = "Option::is_none")]
189 pub task_id: Option<String>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct TaskListResponse {
195 pub tasks: Vec<Task>,
197
198 pub total: usize,
200
201 #[serde(rename = "nextToken", skip_serializing_if = "Option::is_none")]
203 pub next_token: Option<String>,
204}
205
206#[cfg(test)]
207mod tests {
208 use crate::protocol::message::Message;
209
210 use super::*;
211
212 #[test]
213 fn test_task_creation() {
214 let msg = Message::user("Test");
215 let task = Task::new("task-123", msg);
216
217 assert_eq!(task.id, "task-123");
218 assert_eq!(task.status, TaskStatus::Submitted);
219 assert!(!task.is_terminal());
220 assert!(task.is_processing());
221 }
222
223 #[test]
224 fn test_task_lifecycle() {
225 let msg = Message::user("Test");
226 let task = Task::new("task-123", msg);
227
228 let task = task.with_status(TaskStatus::Working);
229 assert_eq!(task.status, TaskStatus::Working);
230 assert!(task.is_processing());
231
232 let task = task.with_status(TaskStatus::Completed);
233 assert!(task.is_terminal());
234 assert!(!task.is_processing());
235 }
236
237 #[test]
238 fn test_task_status() {
239 assert!(TaskStatus::Completed.is_terminal());
240 assert!(TaskStatus::Failed.is_terminal());
241 assert!(!TaskStatus::Working.is_terminal());
242
243 assert!(TaskStatus::InputRequired.requires_action());
244 assert!(TaskStatus::AuthRequired.requires_action());
245 assert!(!TaskStatus::Working.requires_action());
246 }
247
248 #[test]
249 fn test_task_serialization() {
250 let msg = Message::user("Test");
251 let task = Task::new("task-123", msg);
252
253 let json = serde_json::to_string(&task).unwrap();
254 assert!(json.contains("\"id\":\"task-123\""));
255 assert!(json.contains("\"status\":\"submitted\""));
256
257 let deserialized: Task = serde_json::from_str(&json).unwrap();
258 assert_eq!(task.id, deserialized.id);
259 assert_eq!(task.status, deserialized.status);
260 }
261}