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