1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5#[serde(rename_all = "lowercase")]
6pub enum Role {
7 User,
8 Agent,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct FileContent {
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pub name: Option<String>,
15 #[serde(skip_serializing_if = "Option::is_none", rename = "mimeType")]
16 pub mime_type: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub bytes: Option<String>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub uri: Option<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(untagged)]
25pub enum Part {
26 Text {
27 text: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 metadata: Option<Map<String, Value>>,
30 },
31 File {
32 file: FileContent,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 metadata: Option<Map<String, Value>>,
35 },
36 Data {
37 data: Map<String, Value>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 metadata: Option<Map<String, Value>>,
40 },
41}
42
43impl Part {
44 pub fn text(text: String) -> Self {
45 Part::Text { text, metadata: None }
46 }
47
48 pub fn file(file: FileContent) -> Self {
49 Part::File { file, metadata: None }
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Message {
55 pub role: Role,
56 pub parts: Vec<Part>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub metadata: Option<Map<String, Value>>,
59 #[serde(rename = "messageId")]
60 pub message_id: String,
61 #[serde(skip_serializing_if = "Option::is_none", rename = "taskId")]
62 pub task_id: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none", rename = "contextId")]
64 pub context_id: Option<String>,
65}
66
67impl Message {
68 pub fn builder() -> MessageBuilder {
69 MessageBuilder::default()
70 }
71}
72
73#[derive(Default)]
74pub struct MessageBuilder {
75 role: Option<Role>,
76 parts: Vec<Part>,
77 metadata: Option<Map<String, Value>>,
78 message_id: Option<String>,
79 task_id: Option<String>,
80 context_id: Option<String>,
81}
82
83impl MessageBuilder {
84 pub fn role(mut self, role: Role) -> Self {
85 self.role = Some(role);
86 self
87 }
88
89 pub fn parts(mut self, parts: Vec<Part>) -> Self {
90 self.parts = parts;
91 self
92 }
93
94 pub fn metadata(mut self, metadata: Option<Map<String, Value>>) -> Self {
95 self.metadata = metadata;
96 self
97 }
98
99 pub fn message_id(mut self, id: String) -> Self {
100 self.message_id = Some(id);
101 self
102 }
103
104 pub fn build(self) -> Message {
105 Message {
106 role: self.role.unwrap_or(Role::User),
107 parts: self.parts,
108 metadata: self.metadata,
109 message_id: self.message_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()),
110 task_id: self.task_id,
111 context_id: self.context_id,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct Artifact {
118 #[serde(rename = "artifactId")]
119 pub artifact_id: String,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub name: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub description: Option<String>,
124 pub parts: Vec<Part>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub metadata: Option<Map<String, Value>>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub extensions: Option<Vec<String>>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
132#[serde(rename_all = "kebab-case")]
133pub enum TaskState {
134 Submitted,
135 Working,
136 InputRequired,
137 Completed,
138 Failed,
139 Canceled,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct TaskStatus {
144 pub state: TaskState,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub message: Option<String>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct TaskStatusUpdateEvent {
151 #[serde(rename = "taskId")]
152 pub task_id: String,
153 #[serde(skip_serializing_if = "Option::is_none", rename = "contextId")]
154 pub context_id: Option<String>,
155 pub status: TaskStatus,
156 #[serde(rename = "finalUpdate")]
157 pub final_update: bool,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct TaskArtifactUpdateEvent {
162 #[serde(rename = "taskId")]
163 pub task_id: String,
164 #[serde(skip_serializing_if = "Option::is_none", rename = "contextId")]
165 pub context_id: Option<String>,
166 pub artifact: Artifact,
167 pub append: bool,
168 #[serde(rename = "lastChunk")]
169 pub last_chunk: bool,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(untagged)]
174pub enum UpdateEvent {
175 TaskStatusUpdate(TaskStatusUpdateEvent),
176 TaskArtifactUpdate(TaskArtifactUpdateEvent),
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct AgentSkill {
181 pub id: String,
182 pub name: String,
183 pub description: String,
184 pub tags: Vec<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub examples: Option<Vec<String>>,
187}
188
189impl AgentSkill {
190 pub fn new(id: String, name: String, description: String, tags: Vec<String>) -> Self {
191 Self { id, name, description, tags, examples: None }
192 }
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct AgentCapabilities {
197 pub streaming: bool,
198 #[serde(rename = "pushNotifications")]
199 pub push_notifications: bool,
200 #[serde(rename = "stateTransitionHistory")]
201 pub state_transition_history: bool,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub extensions: Option<Vec<String>>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct AgentCard {
208 pub name: String,
209 pub description: String,
210 pub url: String,
211 pub version: String,
212 #[serde(rename = "protocolVersion")]
213 pub protocol_version: String,
214 pub capabilities: AgentCapabilities,
215 pub skills: Vec<AgentSkill>,
216}
217
218impl AgentCard {
219 pub fn builder() -> AgentCardBuilder {
220 AgentCardBuilder::default()
221 }
222}
223
224#[derive(Default)]
225pub struct AgentCardBuilder {
226 name: Option<String>,
227 description: Option<String>,
228 url: Option<String>,
229 version: Option<String>,
230 capabilities: Option<AgentCapabilities>,
231 skills: Vec<AgentSkill>,
232}
233
234impl AgentCardBuilder {
235 pub fn name(mut self, name: String) -> Self {
236 self.name = Some(name);
237 self
238 }
239
240 pub fn description(mut self, description: String) -> Self {
241 self.description = Some(description);
242 self
243 }
244
245 pub fn url(mut self, url: String) -> Self {
246 self.url = Some(url);
247 self
248 }
249
250 pub fn version(mut self, version: String) -> Self {
251 self.version = Some(version);
252 self
253 }
254
255 pub fn capabilities(mut self, capabilities: AgentCapabilities) -> Self {
256 self.capabilities = Some(capabilities);
257 self
258 }
259
260 pub fn skills(mut self, skills: Vec<AgentSkill>) -> Self {
261 self.skills = skills;
262 self
263 }
264
265 pub fn build(self) -> std::result::Result<AgentCard, String> {
272 let name =
273 self.name.filter(|s| !s.is_empty()).ok_or("AgentCard requires a non-empty 'name'")?;
274 let description = self
275 .description
276 .filter(|s| !s.is_empty())
277 .ok_or("AgentCard requires a non-empty 'description'")?;
278 let url =
279 self.url.filter(|s| !s.is_empty()).ok_or("AgentCard requires a non-empty 'url'")?;
280
281 Ok(AgentCard {
282 name,
283 description,
284 url,
285 version: self.version.unwrap_or_else(|| "1.0.0".to_string()),
286 protocol_version: "0.3.0".to_string(),
287 capabilities: self.capabilities.unwrap_or(AgentCapabilities {
288 streaming: false,
289 push_notifications: false,
290 state_transition_history: false,
291 extensions: None,
292 }),
293 skills: self.skills,
294 })
295 }
296}