1#![allow(non_snake_case)] use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Role {
15 User,
17 Agent,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24#[non_exhaustive]
25pub enum TaskStatus {
26 Submitted,
28 Working,
30 InputRequired,
32 Completed,
34 Failed,
36 Canceled,
38 Rejected,
40 Authenticated,
42 #[serde(other)]
46 Unknown,
47}
48
49impl TaskStatus {
50 pub fn is_terminal(&self) -> bool {
53 matches!(
54 self,
55 TaskStatus::Completed
56 | TaskStatus::Failed
57 | TaskStatus::Canceled
58 | TaskStatus::Rejected
59 )
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(tag = "type", rename_all = "lowercase")]
69#[non_exhaustive]
70pub enum Part {
71 Text {
73 content: String,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 metadata: Option<serde_json::Value>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
80 mediaType: Option<String>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
83 filename: Option<String>,
84 },
85 Raw {
87 bytes: String,
89 mediaType: String,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 metadata: Option<serde_json::Value>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
96 filename: Option<String>,
97 },
98 Url {
100 url: String,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
104 mediaType: Option<String>,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 metadata: Option<serde_json::Value>,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 filename: Option<String>,
111 },
112 Data {
114 data: serde_json::Value,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
118 metadata: Option<serde_json::Value>,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
121 mediaType: Option<String>,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
124 filename: Option<String>,
125 },
126 #[serde(other)]
129 Unknown,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct Message {
135 pub messageId: String,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub contextId: Option<String>,
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub taskId: Option<String>,
143 pub role: Role,
145 pub parts: Vec<Part>,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub metadata: Option<serde_json::Value>,
150 #[serde(default)]
152 pub extensions: Vec<String>,
153 #[serde(default)]
155 pub referenceTaskIds: Vec<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Artifact {
161 pub artifactId: String,
163 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub name: Option<String>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub description: Option<String>,
169 pub parts: Vec<Part>,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub metadata: Option<serde_json::Value>,
174 #[serde(default)]
176 pub extensions: Vec<String>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct Task {
182 pub id: String,
184 pub contextId: String,
186 pub status: TaskStatus,
188 #[serde(default)]
190 pub artifacts: Vec<Artifact>,
191 #[serde(default)]
193 pub history: Vec<Message>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub metadata: Option<serde_json::Value>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct AgentProvider {
202 pub organization: String,
204 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub url: Option<String>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct AgentCapabilities {
212 #[serde(default)]
214 pub streaming: bool,
215 #[serde(default)]
217 pub pushNotifications: bool,
218 #[serde(default)]
220 pub stateTransitionHistory: bool,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct AgentSkill {
226 pub id: String,
228 pub name: String,
230 #[serde(default, skip_serializing_if = "Option::is_none")]
232 pub description: Option<String>,
233 #[serde(default)]
235 pub tags: Vec<String>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct AgentInterface {
241 pub transport: String,
244 pub url: String,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct AgentCardSignature {
251 pub alg: String,
253 pub kid: String,
255 pub signature: String,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct AgentCard {
262 pub id: String,
264 pub name: String,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub description: Option<String>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub provider: Option<AgentProvider>,
272 pub capabilities: AgentCapabilities,
274 #[serde(default)]
276 pub skills: Vec<AgentSkill>,
277 #[serde(default)]
279 pub interfaces: Vec<AgentInterface>,
280 #[serde(default)]
282 pub securitySchemes: serde_json::Map<String, serde_json::Value>,
283 #[serde(default)]
285 pub security: Vec<serde_json::Value>,
286 #[serde(default)]
288 pub extensions: Vec<String>,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
291 pub signature: Option<AgentCardSignature>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct SendMessageParams {
297 pub message: Message,
299 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub configuration: Option<serde_json::Value>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
306#[serde(untagged)]
307pub enum SendMessageResult {
308 Message(Message),
310 Task(Task),
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct GetTaskParams {
317 pub id: String,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub historyLength: Option<usize>,
322}
323
324#[derive(Debug, Clone, Default, Serialize, Deserialize)]
326pub struct ListTasksParams {
327 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub contextId: Option<String>,
330 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub cursor: Option<String>,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
335 pub limit: Option<usize>,
336}
337
338#[derive(Debug, Clone, Default, Serialize, Deserialize)]
340pub struct ListTasksResult {
341 pub tasks: Vec<Task>,
343 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub nextCursor: Option<String>,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct CancelTaskParams {
351 pub id: String,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct SubscribeToTaskParams {
358 pub id: String,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct PushNotificationConfigParams {
365 pub taskId: String,
367 pub url: String,
369 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub token: Option<String>,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct PushNotificationConfig {
377 pub id: String,
379 pub taskId: String,
381 pub url: String,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct GetPushNotificationConfigParams {
388 pub taskId: String,
390 pub id: String,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct ListPushNotificationConfigsParams {
397 pub taskId: String,
399}
400
401#[derive(Debug, Clone, Default, Serialize, Deserialize)]
403pub struct ListPushNotificationConfigsResult {
404 pub configs: Vec<PushNotificationConfig>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct DeletePushNotificationConfigParams {
411 pub taskId: String,
413 pub id: String,
415}
416
417#[derive(Debug, Clone, Default, Serialize, Deserialize)]
419pub struct GetExtendedAgentCardParams {}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use serde_json::json;
425
426 #[test]
427 fn unknown_task_status_deserialises_to_unknown_not_error() {
428 let s: TaskStatus = serde_json::from_value(json!("some_future_state")).unwrap();
429 assert_eq!(s, TaskStatus::Unknown);
430 assert!(!s.is_terminal(), "Unknown status is never terminal");
431 }
432
433 #[test]
434 fn unknown_part_type_deserialises_to_unknown_not_error() {
435 let p: Part = serde_json::from_value(json!({"type": "future_part", "x": 1})).unwrap();
436 assert!(matches!(p, Part::Unknown));
437 }
438
439 #[test]
440 fn part_text_serialises_with_type_text() {
441 let p = Part::Text {
442 content: "hello".into(),
443 metadata: None,
444 mediaType: None,
445 filename: None,
446 };
447 let v = serde_json::to_value(&p).unwrap();
448 assert_eq!(v["type"], "text");
449 assert_eq!(v["content"], "hello");
450 }
451
452 #[test]
453 fn part_raw_uses_base64_string_field() {
454 let p = Part::Raw {
455 bytes: "aGVsbG8=".into(),
456 mediaType: "text/plain".into(),
457 metadata: None,
458 filename: None,
459 };
460 let v = serde_json::to_value(&p).unwrap();
461 assert_eq!(v["type"], "raw");
462 assert_eq!(v["bytes"], "aGVsbG8=");
463 assert_eq!(v["mediaType"], "text/plain");
464 }
465
466 #[test]
467 fn message_round_trips() {
468 let m = Message {
469 messageId: "m-1".into(),
470 contextId: Some("c-1".into()),
471 taskId: Some("t-1".into()),
472 role: Role::User,
473 parts: vec![Part::Text {
474 content: "hi".into(),
475 metadata: None,
476 mediaType: None,
477 filename: None,
478 }],
479 metadata: None,
480 extensions: vec![],
481 referenceTaskIds: vec![],
482 };
483 let v = serde_json::to_value(&m).unwrap();
484 let back: Message = serde_json::from_value(v).unwrap();
485 assert_eq!(back.messageId, "m-1");
486 assert_eq!(back.role, Role::User);
487 assert_eq!(back.parts.len(), 1);
488 }
489
490 #[test]
491 fn task_with_empty_history_serialises_minimally() {
492 let t = Task {
493 id: "t-1".into(),
494 contextId: "c-1".into(),
495 status: TaskStatus::Submitted,
496 artifacts: vec![],
497 history: vec![],
498 metadata: None,
499 };
500 let v = serde_json::to_value(&t).unwrap();
501 assert_eq!(v["id"], "t-1");
502 assert_eq!(v["status"], "submitted");
503 }
504
505 #[test]
506 fn agent_card_round_trips() {
507 let card = AgentCard {
508 id: "agent-1".into(),
509 name: "Echo".into(),
510 description: Some("Echoes input".into()),
511 provider: None,
512 capabilities: AgentCapabilities {
513 streaming: false,
514 pushNotifications: false,
515 stateTransitionHistory: false,
516 },
517 skills: vec![],
518 interfaces: vec![],
519 securitySchemes: serde_json::Map::new(),
520 security: vec![],
521 extensions: vec![],
522 signature: None,
523 };
524 let v = serde_json::to_value(&card).unwrap();
525 let back: AgentCard = serde_json::from_value(v).unwrap();
526 assert_eq!(back.id, "agent-1");
527 }
528
529 #[test]
530 fn role_agent_serialises_as_lowercase() {
531 let v = serde_json::to_value(Role::Agent).unwrap();
532 assert_eq!(v, json!("agent"));
533 }
534
535 #[test]
536 fn task_status_is_terminal_covers_completed_failed_canceled_rejected() {
537 assert!(TaskStatus::Completed.is_terminal());
538 assert!(TaskStatus::Failed.is_terminal());
539 assert!(TaskStatus::Canceled.is_terminal());
540 assert!(TaskStatus::Rejected.is_terminal());
541 }
542
543 #[test]
544 fn task_status_is_terminal_excludes_in_progress() {
545 assert!(!TaskStatus::Submitted.is_terminal());
546 assert!(!TaskStatus::Working.is_terminal());
547 assert!(!TaskStatus::Authenticated.is_terminal());
548 assert!(!TaskStatus::InputRequired.is_terminal());
549 }
550}