1use base64::Engine;
4use base64::engine::general_purpose::STANDARD as BASE64;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use serde_json::Value;
8use std::collections::HashMap;
9use uuid::Uuid;
10
11pub type TaskId = String;
17
18pub type ArtifactId = String;
20
21pub fn new_task_id() -> TaskId {
23 Uuid::now_v7().to_string()
24}
25
26pub fn new_context_id() -> String {
28 Uuid::now_v7().to_string()
29}
30
31pub fn new_message_id() -> String {
33 Uuid::now_v7().to_string()
34}
35
36pub fn new_artifact_id() -> ArtifactId {
38 Uuid::now_v7().to_string()
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
47pub enum Role {
48 #[default]
49 Unspecified,
50 User,
51 Agent,
52}
53
54impl Serialize for Role {
55 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
56 match self {
57 Role::Unspecified => serializer.serialize_str("ROLE_UNSPECIFIED"),
58 Role::User => serializer.serialize_str("ROLE_USER"),
59 Role::Agent => serializer.serialize_str("ROLE_AGENT"),
60 }
61 }
62}
63
64impl<'de> Deserialize<'de> for Role {
65 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
66 let s = String::deserialize(deserializer)?;
67 match s.as_str() {
68 "ROLE_USER" => Ok(Role::User),
69 "ROLE_AGENT" => Ok(Role::Agent),
70 "ROLE_UNSPECIFIED" | "" => Ok(Role::Unspecified),
71 other => Err(serde::de::Error::unknown_variant(
72 other,
73 &["ROLE_USER", "ROLE_AGENT", "ROLE_UNSPECIFIED"],
74 )),
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
85pub enum TaskState {
86 #[default]
87 Unspecified,
88 Submitted,
89 Working,
90 Completed,
91 Failed,
92 Canceled,
93 InputRequired,
94 Rejected,
95 AuthRequired,
96}
97
98impl TaskState {
99 pub fn is_terminal(&self) -> bool {
101 matches!(
102 self,
103 TaskState::Completed | TaskState::Failed | TaskState::Canceled | TaskState::Rejected
104 )
105 }
106}
107
108impl Serialize for TaskState {
109 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
110 let s = match self {
111 TaskState::Unspecified => "TASK_STATE_UNSPECIFIED",
112 TaskState::Submitted => "TASK_STATE_SUBMITTED",
113 TaskState::Working => "TASK_STATE_WORKING",
114 TaskState::Completed => "TASK_STATE_COMPLETED",
115 TaskState::Failed => "TASK_STATE_FAILED",
116 TaskState::Canceled => "TASK_STATE_CANCELED",
117 TaskState::InputRequired => "TASK_STATE_INPUT_REQUIRED",
118 TaskState::Rejected => "TASK_STATE_REJECTED",
119 TaskState::AuthRequired => "TASK_STATE_AUTH_REQUIRED",
120 };
121 serializer.serialize_str(s)
122 }
123}
124
125impl<'de> Deserialize<'de> for TaskState {
126 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
127 let s = String::deserialize(deserializer)?;
128 match s.as_str() {
129 "TASK_STATE_SUBMITTED" => Ok(TaskState::Submitted),
130 "TASK_STATE_WORKING" => Ok(TaskState::Working),
131 "TASK_STATE_COMPLETED" => Ok(TaskState::Completed),
132 "TASK_STATE_FAILED" => Ok(TaskState::Failed),
133 "TASK_STATE_CANCELED" => Ok(TaskState::Canceled),
134 "TASK_STATE_INPUT_REQUIRED" => Ok(TaskState::InputRequired),
135 "TASK_STATE_REJECTED" => Ok(TaskState::Rejected),
136 "TASK_STATE_AUTH_REQUIRED" => Ok(TaskState::AuthRequired),
137 "TASK_STATE_UNSPECIFIED" | "" => Ok(TaskState::Unspecified),
138 other => Err(serde::de::Error::unknown_variant(
139 other,
140 &[
141 "TASK_STATE_SUBMITTED",
142 "TASK_STATE_WORKING",
143 "TASK_STATE_COMPLETED",
144 "TASK_STATE_FAILED",
145 "TASK_STATE_CANCELED",
146 "TASK_STATE_INPUT_REQUIRED",
147 "TASK_STATE_REJECTED",
148 "TASK_STATE_AUTH_REQUIRED",
149 "TASK_STATE_UNSPECIFIED",
150 ],
151 )),
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq)]
163pub struct Part {
164 pub content: PartContent,
165 #[doc = "Optional filename for file parts."]
166 pub filename: Option<String>,
167 #[doc = "MIME type of the part content."]
168 pub media_type: Option<String>,
169 #[doc = "Optional metadata for extensions."]
170 pub metadata: Option<HashMap<String, Value>>,
171}
172
173#[derive(Debug, Clone, PartialEq)]
175pub enum PartContent {
176 Text(String),
177 Raw(Vec<u8>),
178 Url(String),
179 Data(Value),
180}
181
182impl Part {
183 pub fn text(text: impl Into<String>) -> Self {
184 Part {
185 content: PartContent::Text(text.into()),
186 filename: None,
187 media_type: None,
188 metadata: None,
189 }
190 }
191
192 pub fn raw(data: Vec<u8>) -> Self {
193 Part {
194 content: PartContent::Raw(data),
195 filename: None,
196 media_type: None,
197 metadata: None,
198 }
199 }
200
201 pub fn url(url: impl Into<String>) -> Self {
202 Part {
203 content: PartContent::Url(url.into()),
204 filename: None,
205 media_type: None,
206 metadata: None,
207 }
208 }
209
210 pub fn data(value: Value) -> Self {
211 Part {
212 content: PartContent::Data(value),
213 filename: None,
214 media_type: None,
215 metadata: None,
216 }
217 }
218
219 pub fn with_media_type(mut self, media_type: impl Into<String>) -> Self {
220 self.media_type = Some(media_type.into());
221 self
222 }
223
224 pub fn with_filename(mut self, filename: impl Into<String>) -> Self {
225 self.filename = Some(filename.into());
226 self
227 }
228
229 pub fn as_text(&self) -> Option<&str> {
230 if let PartContent::Text(ref s) = self.content {
231 Some(s)
232 } else {
233 None
234 }
235 }
236}
237
238impl Serialize for Part {
239 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
240 use serde::ser::SerializeMap;
241 let mut map = serializer.serialize_map(None)?;
242
243 match &self.content {
244 PartContent::Text(t) => map.serialize_entry("text", t)?,
245 PartContent::Raw(r) => map.serialize_entry("raw", &BASE64.encode(r))?,
246 PartContent::Url(u) => map.serialize_entry("url", u)?,
247 PartContent::Data(d) => map.serialize_entry("data", d)?,
248 }
249
250 if let Some(ref f) = self.filename {
251 map.serialize_entry("filename", f)?;
252 }
253 if let Some(ref m) = self.media_type {
254 map.serialize_entry("mediaType", m)?;
255 }
256 if let Some(ref meta) = self.metadata {
257 if !meta.is_empty() {
258 map.serialize_entry("metadata", meta)?;
259 }
260 }
261 map.end()
262 }
263}
264
265impl<'de> Deserialize<'de> for Part {
266 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
267 let raw: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
268
269 let content = if let Some(Value::String(t)) = raw.get("text") {
270 PartContent::Text(t.clone())
271 } else if let Some(Value::String(r)) = raw.get("raw") {
272 let bytes = BASE64.decode(r).map_err(serde::de::Error::custom)?;
273 PartContent::Raw(bytes)
274 } else if let Some(Value::String(u)) = raw.get("url") {
275 PartContent::Url(u.clone())
276 } else if let Some(d) = raw.get("data") {
277 PartContent::Data(d.clone())
278 } else {
279 return Err(serde::de::Error::custom(
280 "Part must have one of: text, raw, url, data",
281 ));
282 };
283
284 let filename = raw
285 .get("filename")
286 .and_then(|v| v.as_str())
287 .map(String::from);
288 let media_type = raw
289 .get("mediaType")
290 .and_then(|v| v.as_str())
291 .map(String::from);
292 let metadata: Option<HashMap<String, Value>> = raw
293 .get("metadata")
294 .and_then(|v| serde_json::from_value(v.clone()).ok());
295
296 Ok(Part {
297 content,
298 filename,
299 media_type,
300 metadata,
301 })
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct Message {
313 pub message_id: String,
315
316 #[serde(default, skip_serializing_if = "Option::is_none")]
318 pub context_id: Option<String>,
319
320 #[serde(default, skip_serializing_if = "Option::is_none")]
322 pub task_id: Option<TaskId>,
323
324 pub role: Role,
326
327 pub parts: Vec<Part>,
329
330 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub metadata: Option<HashMap<String, Value>>,
333
334 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub extensions: Option<Vec<String>>,
337
338 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub reference_task_ids: Option<Vec<TaskId>>,
341}
342
343impl Message {
344 pub fn new(role: Role, parts: Vec<Part>) -> Self {
346 Message {
347 message_id: new_message_id(),
348 context_id: None,
349 task_id: None,
350 role,
351 parts,
352 metadata: None,
353 extensions: None,
354 reference_task_ids: None,
355 }
356 }
357
358 pub fn text(&self) -> Option<&str> {
360 self.parts.iter().find_map(|p| p.as_text())
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
370#[serde(rename_all = "camelCase")]
371pub struct TaskStatus {
372 pub state: TaskState,
373
374 #[serde(default, skip_serializing_if = "Option::is_none")]
375 pub message: Option<Message>,
376
377 #[serde(default, skip_serializing_if = "Option::is_none")]
378 pub timestamp: Option<DateTime<Utc>>,
379}
380
381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct Task {
389 pub id: TaskId,
390 pub context_id: String,
391 pub status: TaskStatus,
392
393 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub artifacts: Option<Vec<Artifact>>,
395
396 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub history: Option<Vec<Message>>,
398
399 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub metadata: Option<HashMap<String, Value>>,
401}
402
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct Artifact {
411 pub artifact_id: ArtifactId,
412
413 #[serde(default, skip_serializing_if = "Option::is_none")]
414 pub name: Option<String>,
415
416 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub description: Option<String>,
418
419 pub parts: Vec<Part>,
420
421 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub metadata: Option<HashMap<String, Value>>,
423
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub extensions: Option<Vec<String>>,
426}
427
428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
434#[serde(rename_all = "camelCase")]
435pub struct SendMessageConfiguration {
436 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub accepted_output_modes: Option<Vec<String>>,
438
439 #[serde(default, skip_serializing_if = "Option::is_none")]
440 #[serde(alias = "pushNotificationConfig")]
441 pub task_push_notification_config: Option<TaskPushNotificationConfig>,
442
443 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub history_length: Option<i32>,
445
446 #[serde(default, skip_serializing_if = "Option::is_none")]
447 pub return_immediately: Option<bool>,
448}
449
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct SendMessageRequest {
454 pub message: Message,
455
456 #[serde(default, skip_serializing_if = "Option::is_none")]
457 pub configuration: Option<SendMessageConfiguration>,
458
459 #[serde(default, skip_serializing_if = "Option::is_none")]
460 pub metadata: Option<HashMap<String, Value>>,
461
462 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub tenant: Option<String>,
464}
465
466#[derive(Debug, Clone, PartialEq)]
468pub enum SendMessageResponse {
469 Task(Task),
470 Message(Message),
471}
472
473impl Serialize for SendMessageResponse {
474 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
475 use serde::ser::SerializeMap;
476 let mut map = serializer.serialize_map(Some(1))?;
477 match self {
478 SendMessageResponse::Task(t) => map.serialize_entry("task", t)?,
479 SendMessageResponse::Message(m) => map.serialize_entry("message", m)?,
480 }
481 map.end()
482 }
483}
484
485impl<'de> Deserialize<'de> for SendMessageResponse {
486 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
487 let raw: HashMap<String, Value> = HashMap::deserialize(deserializer)?;
488 if let Some(v) = raw.get("task") {
489 let task: Task = serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?;
490 Ok(SendMessageResponse::Task(task))
491 } else if let Some(v) = raw.get("message") {
492 let msg: Message =
493 serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?;
494 Ok(SendMessageResponse::Message(msg))
495 } else {
496 Err(serde::de::Error::custom(
497 "SendMessageResponse must have 'task' or 'message'",
498 ))
499 }
500 }
501}
502
503#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
505#[serde(rename_all = "camelCase")]
506pub struct GetTaskRequest {
507 pub id: TaskId,
508
509 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub history_length: Option<i32>,
511
512 #[serde(default, skip_serializing_if = "Option::is_none")]
513 pub tenant: Option<String>,
514}
515
516#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct ListTasksRequest {
520 #[serde(default, skip_serializing_if = "Option::is_none")]
521 pub context_id: Option<String>,
522
523 #[serde(default, skip_serializing_if = "Option::is_none")]
524 pub status: Option<TaskState>,
525
526 #[serde(default, skip_serializing_if = "Option::is_none")]
527 pub page_size: Option<i32>,
528
529 #[serde(default, skip_serializing_if = "Option::is_none")]
530 pub page_token: Option<String>,
531
532 #[serde(default, skip_serializing_if = "Option::is_none")]
533 pub history_length: Option<i32>,
534
535 #[serde(default, skip_serializing_if = "Option::is_none")]
536 pub status_timestamp_after: Option<DateTime<Utc>>,
537
538 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub include_artifacts: Option<bool>,
540
541 #[serde(default, skip_serializing_if = "Option::is_none")]
542 pub tenant: Option<String>,
543}
544
545#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
547#[serde(rename_all = "camelCase")]
548pub struct ListTasksResponse {
549 pub tasks: Vec<Task>,
550 pub next_page_token: String,
551 pub page_size: i32,
552 pub total_size: i32,
553}
554
555#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
557#[serde(rename_all = "camelCase")]
558pub struct CancelTaskRequest {
559 pub id: TaskId,
560
561 #[serde(default, skip_serializing_if = "Option::is_none")]
562 pub metadata: Option<HashMap<String, Value>>,
563
564 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub tenant: Option<String>,
566}
567
568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct SubscribeToTaskRequest {
572 pub id: TaskId,
573
574 #[serde(default, skip_serializing_if = "Option::is_none")]
575 pub tenant: Option<String>,
576}
577
578#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
580#[serde(rename_all = "camelCase")]
581pub struct GetExtendedAgentCardRequest {
582 #[serde(default, skip_serializing_if = "Option::is_none")]
583 pub tenant: Option<String>,
584}
585
586#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct AuthenticationInfo {
594 pub scheme: String,
596
597 #[serde(default, skip_serializing_if = "Option::is_none")]
599 pub credentials: Option<String>,
600}
601
602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604#[serde(rename_all = "camelCase")]
605pub struct TaskPushNotificationConfig {
606 pub url: String,
608
609 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub id: Option<String>,
612
613 #[serde(default, skip_serializing_if = "String::is_empty")]
615 pub task_id: TaskId,
616
617 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub token: Option<String>,
620
621 #[serde(default, skip_serializing_if = "Option::is_none")]
623 pub authentication: Option<AuthenticationInfo>,
624
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub tenant: Option<String>,
627}
628
629#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
631#[serde(rename_all = "camelCase")]
632pub struct GetTaskPushNotificationConfigRequest {
633 pub task_id: TaskId,
634 pub id: String,
635
636 #[serde(default, skip_serializing_if = "Option::is_none")]
637 pub tenant: Option<String>,
638}
639
640#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
642#[serde(rename_all = "camelCase")]
643pub struct ListTaskPushNotificationConfigsRequest {
644 pub task_id: TaskId,
645
646 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub page_size: Option<i32>,
648
649 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub page_token: Option<String>,
651
652 #[serde(default, skip_serializing_if = "Option::is_none")]
653 pub tenant: Option<String>,
654}
655
656#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
658#[serde(rename_all = "camelCase")]
659pub struct ListTaskPushNotificationConfigsResponse {
660 #[serde(default, skip_serializing_if = "Vec::is_empty")]
661 pub configs: Vec<TaskPushNotificationConfig>,
662
663 #[serde(default, skip_serializing_if = "Option::is_none")]
664 pub next_page_token: Option<String>,
665}
666
667#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
669#[serde(rename_all = "camelCase")]
670pub struct DeleteTaskPushNotificationConfigRequest {
671 pub task_id: TaskId,
672 pub id: String,
673
674 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub tenant: Option<String>,
676}
677
678pub type TransportProtocol = String;
684
685pub const TRANSPORT_PROTOCOL_JSONRPC: &str = "JSONRPC";
687pub const TRANSPORT_PROTOCOL_GRPC: &str = "GRPC";
689pub const TRANSPORT_PROTOCOL_HTTP_JSON: &str = "HTTP+JSON";
691pub const TRANSPORT_PROTOCOL_SLIMRPC: &str = "SLIMRPC";
693
694pub type ProtocolVersion = String;
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700
701 #[test]
702 fn test_task_state_serde() {
703 let state = TaskState::Completed;
704 let json = serde_json::to_string(&state).unwrap();
705 assert_eq!(json, r#""TASK_STATE_COMPLETED""#);
706 let back: TaskState = serde_json::from_str(&json).unwrap();
707 assert_eq!(back, TaskState::Completed);
708 }
709
710 #[test]
711 fn test_role_serde() {
712 let role = Role::Agent;
713 let json = serde_json::to_string(&role).unwrap();
714 assert_eq!(json, r#""ROLE_AGENT""#);
715 let back: Role = serde_json::from_str(&json).unwrap();
716 assert_eq!(back, Role::Agent);
717 }
718
719 #[test]
720 fn test_part_text_serde() {
721 let part = Part::text("hello");
722 let json = serde_json::to_string(&part).unwrap();
723 let v: Value = serde_json::from_str(&json).unwrap();
724 assert_eq!(v["text"], "hello");
725 assert!(v.get("raw").is_none());
726
727 let back: Part = serde_json::from_str(&json).unwrap();
728 assert_eq!(back.content, PartContent::Text("hello".into()));
729 }
730
731 #[test]
732 fn test_part_raw_serde() {
733 let part = Part::raw(vec![1, 2, 3]);
734 let json = serde_json::to_string(&part).unwrap();
735 let back: Part = serde_json::from_str(&json).unwrap();
736 assert_eq!(back.content, PartContent::Raw(vec![1, 2, 3]));
737 }
738
739 #[test]
740 fn test_send_message_response_serde() {
741 let resp = SendMessageResponse::Task(Task {
742 id: "t1".into(),
743 context_id: "c1".into(),
744 status: TaskStatus {
745 state: TaskState::Submitted,
746 message: None,
747 timestamp: None,
748 },
749 artifacts: None,
750 history: None,
751 metadata: None,
752 });
753 let json = serde_json::to_string(&resp).unwrap();
754 let v: Value = serde_json::from_str(&json).unwrap();
755 assert!(v.get("task").is_some());
756 assert!(v.get("message").is_none());
757 let back: SendMessageResponse = serde_json::from_str(&json).unwrap();
758 assert!(matches!(back, SendMessageResponse::Task(_)));
759 }
760
761 #[test]
762 fn test_send_message_response_message_variant() {
763 let resp = SendMessageResponse::Message(Message::new(Role::Agent, vec![Part::text("hi")]));
764 let json = serde_json::to_string(&resp).unwrap();
765 let v: Value = serde_json::from_str(&json).unwrap();
766 assert!(v.get("message").is_some());
767 assert!(v.get("task").is_none());
768 let back: SendMessageResponse = serde_json::from_str(&json).unwrap();
769 assert!(matches!(back, SendMessageResponse::Message(_)));
770 }
771
772 #[test]
773 fn test_part_url_serde() {
774 let part = Part::url("https://example.com/file.pdf");
775 let json = serde_json::to_string(&part).unwrap();
776 let v: Value = serde_json::from_str(&json).unwrap();
777 assert_eq!(v["url"], "https://example.com/file.pdf");
778 let back: Part = serde_json::from_str(&json).unwrap();
779 assert!(matches!(back.content, PartContent::Url(_)));
780 }
781
782 #[test]
783 fn test_part_data_serde() {
784 let data = serde_json::json!({"key": "value", "count": 42});
785 let part = Part::data(data.clone());
786 let json = serde_json::to_string(&part).unwrap();
787 let back: Part = serde_json::from_str(&json).unwrap();
788 assert!(matches!(back.content, PartContent::Data(_)));
789 if let PartContent::Data(d) = back.content {
790 assert_eq!(d["key"], "value");
791 }
792 }
793
794 #[test]
795 fn test_part_with_metadata_and_media_type() {
796 let part = Part::text("hello")
797 .with_media_type("text/plain")
798 .with_filename("test.txt");
799 assert_eq!(part.media_type.as_deref(), Some("text/plain"));
800 assert_eq!(part.filename.as_deref(), Some("test.txt"));
801 let json = serde_json::to_string(&part).unwrap();
802 let v: Value = serde_json::from_str(&json).unwrap();
803 assert_eq!(v["mediaType"], "text/plain");
804 assert_eq!(v["filename"], "test.txt");
805 }
806
807 #[test]
808 fn test_part_as_text() {
809 let part = Part::text("hello");
810 assert_eq!(part.as_text(), Some("hello"));
811 let part = Part::raw(vec![]);
812 assert_eq!(part.as_text(), None);
813 }
814
815 #[test]
816 fn test_message_new() {
817 let msg = Message::new(Role::User, vec![Part::text("hi")]);
818 assert!(!msg.message_id.is_empty());
819 assert_eq!(msg.role, Role::User);
820 assert_eq!(msg.parts.len(), 1);
821 assert_eq!(msg.text(), Some("hi"));
822 }
823
824 #[test]
825 fn test_message_serde() {
826 let msg = Message {
827 message_id: "m1".to_string(),
828 context_id: Some("c1".to_string()),
829 task_id: None,
830 role: Role::Agent,
831 parts: vec![Part::text("response")],
832 metadata: None,
833 extensions: Some(vec!["ext1".to_string()]),
834 reference_task_ids: Some(vec!["t1".to_string()]),
835 };
836 let json = serde_json::to_string(&msg).unwrap();
837 let back: Message = serde_json::from_str(&json).unwrap();
838 assert_eq!(msg, back);
839 }
840
841 #[test]
842 fn test_task_state_is_terminal() {
843 assert!(TaskState::Completed.is_terminal());
844 assert!(TaskState::Failed.is_terminal());
845 assert!(TaskState::Canceled.is_terminal());
846 assert!(TaskState::Rejected.is_terminal());
847 assert!(!TaskState::Submitted.is_terminal());
848 assert!(!TaskState::Working.is_terminal());
849 assert!(!TaskState::InputRequired.is_terminal());
850 assert!(!TaskState::AuthRequired.is_terminal());
851 assert!(!TaskState::Unspecified.is_terminal());
852 }
853
854 #[test]
855 fn test_task_full_serde() {
856 let task = Task {
857 id: "t1".to_string(),
858 context_id: "c1".to_string(),
859 status: TaskStatus {
860 state: TaskState::Working,
861 message: Some(Message::new(Role::Agent, vec![Part::text("working")])),
862 timestamp: None,
863 },
864 artifacts: Some(vec![Artifact {
865 artifact_id: "a1".to_string(),
866 name: Some("output".to_string()),
867 description: None,
868 parts: vec![Part::text("result data")],
869 metadata: None,
870 extensions: None,
871 }]),
872 history: Some(vec![Message::new(Role::User, vec![Part::text("do it")])]),
873 metadata: None,
874 };
875 let json = serde_json::to_string(&task).unwrap();
876 let back: Task = serde_json::from_str(&json).unwrap();
877 assert_eq!(task.id, back.id);
878 assert_eq!(task.status.state, back.status.state);
879 assert!(back.artifacts.is_some());
880 assert!(back.history.is_some());
881 }
882
883 #[test]
884 fn test_push_notification_config_serde() {
885 let config = TaskPushNotificationConfig {
886 url: "https://example.com/webhook".to_string(),
887 id: Some("cfg-1".to_string()),
888 task_id: "task-1".to_string(),
889 token: Some("tok-1".to_string()),
890 authentication: Some(AuthenticationInfo {
891 scheme: "Bearer".to_string(),
892 credentials: Some("secret".to_string()),
893 }),
894 tenant: None,
895 };
896 let json = serde_json::to_string(&config).unwrap();
897 let back: TaskPushNotificationConfig = serde_json::from_str(&json).unwrap();
898 assert_eq!(config, back);
899 }
900
901 #[test]
902 fn test_send_message_request_serde() {
903 let req = SendMessageRequest {
904 message: Message::new(Role::User, vec![Part::text("hello")]),
905 configuration: Some(SendMessageConfiguration {
906 accepted_output_modes: Some(vec!["text/plain".to_string()]),
907 task_push_notification_config: None,
908 history_length: Some(10),
909 return_immediately: Some(true),
910 }),
911 metadata: None,
912 tenant: Some("tenant-1".to_string()),
913 };
914 let json = serde_json::to_string(&req).unwrap();
915 let back: SendMessageRequest = serde_json::from_str(&json).unwrap();
916 assert_eq!(req.tenant, back.tenant);
917 assert_eq!(
918 req.configuration.as_ref().unwrap().history_length,
919 back.configuration.as_ref().unwrap().history_length
920 );
921 }
922
923 #[test]
924 fn test_role_all_variants_serde() {
925 let cases = [
926 (Role::Unspecified, "\"ROLE_UNSPECIFIED\""),
927 (Role::User, "\"ROLE_USER\""),
928 (Role::Agent, "\"ROLE_AGENT\""),
929 ];
930
931 for (role, expected_json) in cases {
932 let json = serde_json::to_string(&role).unwrap();
933 assert_eq!(json, expected_json);
934 let back: Role = serde_json::from_str(&json).unwrap();
935 assert_eq!(back, role);
936 }
937
938 let back: Role = serde_json::from_str("\"\"").unwrap();
939 assert_eq!(back, Role::Unspecified);
940 }
941
942 #[test]
943 fn test_task_state_all_variants_serde() {
944 let cases = [
945 (TaskState::Unspecified, "TASK_STATE_UNSPECIFIED"),
946 (TaskState::Submitted, "TASK_STATE_SUBMITTED"),
947 (TaskState::Working, "TASK_STATE_WORKING"),
948 (TaskState::Completed, "TASK_STATE_COMPLETED"),
949 (TaskState::Failed, "TASK_STATE_FAILED"),
950 (TaskState::Canceled, "TASK_STATE_CANCELED"),
951 (TaskState::InputRequired, "TASK_STATE_INPUT_REQUIRED"),
952 (TaskState::Rejected, "TASK_STATE_REJECTED"),
953 (TaskState::AuthRequired, "TASK_STATE_AUTH_REQUIRED"),
954 ];
955
956 for (state, expected_str) in cases {
957 let json = serde_json::to_string(&state).unwrap();
958 assert_eq!(json, format!("\"{}\"", expected_str));
959 let back: TaskState = serde_json::from_str(&json).unwrap();
960 assert_eq!(back, state);
961 }
962
963 let back: TaskState = serde_json::from_str("\"\"").unwrap();
964 assert_eq!(back, TaskState::Unspecified);
965 }
966
967 #[test]
968 fn test_part_all_content_variants_serde() {
969 let parts = [
970 Part::text("hello"),
971 Part::raw(vec![1, 2, 3]),
972 Part::url("https://example.com"),
973 Part::data(serde_json::json!({"value": 1})),
974 ];
975
976 for part in parts {
977 let json = serde_json::to_string(&part).unwrap();
978 let back: Part = serde_json::from_str(&json).unwrap();
979 match (&part.content, &back.content) {
980 (PartContent::Text(a), PartContent::Text(b)) => assert_eq!(a, b),
981 (PartContent::Raw(a), PartContent::Raw(b)) => assert_eq!(a, b),
982 (PartContent::Url(a), PartContent::Url(b)) => assert_eq!(a, b),
983 (PartContent::Data(_), PartContent::Data(_)) => {}
984 _ => panic!("mismatched part content variants"),
985 }
986 }
987 }
988
989 #[test]
990 fn test_send_message_response_message_deserialize() {
991 let json = serde_json::json!({
992 "message": {
993 "messageId": "m1",
994 "role": "ROLE_AGENT",
995 "parts": [{"text": "hello"}]
996 }
997 });
998 let back: SendMessageResponse = serde_json::from_value(json).unwrap();
999 assert!(matches!(back, SendMessageResponse::Message(_)));
1000 }
1001
1002 #[test]
1003 fn test_new_id_functions() {
1004 let task_id = new_task_id();
1005 assert!(!task_id.is_empty());
1006 let ctx_id = new_context_id();
1007 assert!(!ctx_id.is_empty());
1008 let msg_id = new_message_id();
1009 assert!(!msg_id.is_empty());
1010 let art_id = new_artifact_id();
1011 assert!(!art_id.is_empty());
1012 assert_ne!(task_id, ctx_id);
1014 }
1015
1016 #[test]
1017 fn test_role_default() {
1018 assert_eq!(Role::default(), Role::Unspecified);
1019 }
1020
1021 #[test]
1022 fn test_task_state_default() {
1023 assert_eq!(TaskState::default(), TaskState::Unspecified);
1024 }
1025
1026 #[test]
1027 fn test_list_tasks_request_serde() {
1028 let req = ListTasksRequest {
1029 context_id: Some("c1".to_string()),
1030 status: Some(TaskState::Working),
1031 page_size: Some(10),
1032 page_token: None,
1033 history_length: Some(5),
1034 status_timestamp_after: None,
1035 include_artifacts: Some(true),
1036 tenant: None,
1037 };
1038 let json = serde_json::to_string(&req).unwrap();
1039 let back: ListTasksRequest = serde_json::from_str(&json).unwrap();
1040 assert_eq!(req, back);
1041 }
1042
1043 #[test]
1044 fn test_cancel_task_request_serde() {
1045 let req = CancelTaskRequest {
1046 id: "t1".to_string(),
1047 metadata: None,
1048 tenant: Some("ten".to_string()),
1049 };
1050 let json = serde_json::to_string(&req).unwrap();
1051 let back: CancelTaskRequest = serde_json::from_str(&json).unwrap();
1052 assert_eq!(req, back);
1053 }
1054
1055 #[test]
1056 fn test_subscribe_to_task_request_serde() {
1057 let req = SubscribeToTaskRequest {
1058 id: "t1".to_string(),
1059 tenant: None,
1060 };
1061 let json = serde_json::to_string(&req).unwrap();
1062 let back: SubscribeToTaskRequest = serde_json::from_str(&json).unwrap();
1063 assert_eq!(req, back);
1064 }
1065}