Skip to main content

a2a/
types.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3use 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
11// ---------------------------------------------------------------------------
12// TaskID / ArtifactID / ContextID
13// ---------------------------------------------------------------------------
14
15/// Unique identifier for a task, generated by the server.
16pub type TaskId = String;
17
18/// Unique identifier for an artifact within a task.
19pub type ArtifactId = String;
20
21/// Generate a new random task ID (UUIDv7).
22pub fn new_task_id() -> TaskId {
23    Uuid::now_v7().to_string()
24}
25
26/// Generate a new random context ID (UUIDv7).
27pub fn new_context_id() -> String {
28    Uuid::now_v7().to_string()
29}
30
31/// Generate a new random message ID (UUIDv7).
32pub fn new_message_id() -> String {
33    Uuid::now_v7().to_string()
34}
35
36/// Generate a new random artifact ID (UUIDv7).
37pub fn new_artifact_id() -> ArtifactId {
38    Uuid::now_v7().to_string()
39}
40
41// ---------------------------------------------------------------------------
42// MessageRole
43// ---------------------------------------------------------------------------
44
45/// Identifies the sender of a message.
46#[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// ---------------------------------------------------------------------------
80// TaskState
81// ---------------------------------------------------------------------------
82
83/// The lifecycle state of a task.
84#[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    /// Returns true for terminal states where no further changes are permitted.
100    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// ---------------------------------------------------------------------------
157// Part (field-presence union)
158// ---------------------------------------------------------------------------
159
160/// A content part of a message or artifact. Uses field-presence serialization
161/// (like C# SDK) so only the relevant content field is present in JSON.
162#[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/// The content of a part — a discriminated union.
174#[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// ---------------------------------------------------------------------------
306// Message
307// ---------------------------------------------------------------------------
308
309/// A single message in a conversation between user and agent.
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct Message {
313    /// Unique identifier for the message, generated by the sender.
314    pub message_id: String,
315
316    /// Context identifier for grouping related interactions.
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    pub context_id: Option<String>,
319
320    /// Task identifier this message belongs to.
321    #[serde(default, skip_serializing_if = "Option::is_none")]
322    pub task_id: Option<TaskId>,
323
324    /// Sender role.
325    pub role: Role,
326
327    /// Content parts forming the message body.
328    pub parts: Vec<Part>,
329
330    /// Optional metadata for extensions.
331    #[serde(default, skip_serializing_if = "Option::is_none")]
332    pub metadata: Option<HashMap<String, Value>>,
333
334    /// Extension URIs relevant to this message.
335    #[serde(default, skip_serializing_if = "Option::is_none")]
336    pub extensions: Option<Vec<String>>,
337
338    /// Related task IDs for additional context.
339    #[serde(default, skip_serializing_if = "Option::is_none")]
340    pub reference_task_ids: Option<Vec<TaskId>>,
341}
342
343impl Message {
344    /// Create a new message with a random ID.
345    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    /// Return the text of the first text part, if any.
359    pub fn text(&self) -> Option<&str> {
360        self.parts.iter().find_map(|p| p.as_text())
361    }
362}
363
364// ---------------------------------------------------------------------------
365// TaskStatus
366// ---------------------------------------------------------------------------
367
368/// The status of a task at a specific point in time.
369#[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// ---------------------------------------------------------------------------
382// Task
383// ---------------------------------------------------------------------------
384
385/// A single stateful operation or conversation between client and agent.
386#[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// ---------------------------------------------------------------------------
404// Artifact
405// ---------------------------------------------------------------------------
406
407/// A resource generated by an agent during a task.
408#[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// ---------------------------------------------------------------------------
429// Request / Response types
430// ---------------------------------------------------------------------------
431
432/// Configuration for `SendMessage` / `SendStreamingMessage`.
433#[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/// Request to send a message to an agent.
451#[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/// Response from `SendMessage` — either a Task or a Message.
467#[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/// Request to get a task by ID.
504#[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/// Request to list tasks.
517#[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/// Response for listing tasks.
546#[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/// Request to cancel a task.
556#[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/// Request to subscribe to task updates.
569#[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/// Request to get an extended (authenticated) agent card.
579#[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// ---------------------------------------------------------------------------
587// Push Notification types
588// ---------------------------------------------------------------------------
589
590/// Authentication details for a push notification endpoint.
591#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct AuthenticationInfo {
594    /// HTTP authentication scheme (e.g., "Bearer", "Basic").
595    pub scheme: String,
596
597    /// Scheme-specific credentials.
598    #[serde(default, skip_serializing_if = "Option::is_none")]
599    pub credentials: Option<String>,
600}
601
602/// Push notification configuration for a task.
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604#[serde(rename_all = "camelCase")]
605pub struct TaskPushNotificationConfig {
606    /// Callback URL for push notifications.
607    pub url: String,
608
609    /// Optional unique ID for this configuration.
610    #[serde(default, skip_serializing_if = "Option::is_none")]
611    pub id: Option<String>,
612
613    /// The task this configuration is associated with.
614    #[serde(default, skip_serializing_if = "String::is_empty")]
615    pub task_id: TaskId,
616
617    /// Optional session/task token for validation.
618    #[serde(default, skip_serializing_if = "Option::is_none")]
619    pub token: Option<String>,
620
621    /// Optional authentication details for the webhook.
622    #[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/// Request to get a specific push config.
630#[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/// Request to list push configs for a task.
641#[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/// Response for listing push configs.
657#[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/// Request to delete a push config.
668#[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
678// ---------------------------------------------------------------------------
679// TransportProtocol
680// ---------------------------------------------------------------------------
681
682/// Transport protocol identifier. Open-form string to allow custom protocols.
683pub type TransportProtocol = String;
684
685/// JSON-RPC transport protocol constant.
686pub const TRANSPORT_PROTOCOL_JSONRPC: &str = "JSONRPC";
687/// gRPC transport protocol constant.
688pub const TRANSPORT_PROTOCOL_GRPC: &str = "GRPC";
689/// HTTP+JSON (REST) transport protocol constant.
690pub const TRANSPORT_PROTOCOL_HTTP_JSON: &str = "HTTP+JSON";
691/// SLIMRPC transport protocol constant.
692pub const TRANSPORT_PROTOCOL_SLIMRPC: &str = "SLIMRPC";
693
694/// Protocol version string (e.g., "1.0").
695pub 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        // All should be unique
1013        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}