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(rename = "pushNotificationConfig")]
441    pub push_notification_config: Option<PushNotificationConfig>,
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/// Push notification configuration.
591#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct PushNotificationConfig {
594    /// Callback URL for push notifications.
595    pub url: String,
596
597    /// Optional unique ID for this configuration.
598    #[serde(default, skip_serializing_if = "Option::is_none")]
599    pub id: Option<String>,
600
601    /// Optional session/task token for validation.
602    #[serde(default, skip_serializing_if = "Option::is_none")]
603    pub token: Option<String>,
604
605    /// Optional authentication details for the webhook.
606    #[serde(default, skip_serializing_if = "Option::is_none")]
607    pub authentication: Option<AuthenticationInfo>,
608}
609
610/// Authentication details for a push notification endpoint.
611#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
612#[serde(rename_all = "camelCase")]
613pub struct AuthenticationInfo {
614    /// HTTP authentication scheme (e.g., "Bearer", "Basic").
615    pub scheme: String,
616
617    /// Scheme-specific credentials.
618    #[serde(default, skip_serializing_if = "Option::is_none")]
619    pub credentials: Option<String>,
620}
621
622/// Container associating push config with a task.
623#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
624#[serde(rename_all = "camelCase")]
625pub struct TaskPushNotificationConfig {
626    pub task_id: TaskId,
627    pub config: PushNotificationConfig,
628
629    #[serde(default, skip_serializing_if = "Option::is_none")]
630    pub tenant: Option<String>,
631}
632
633/// Request to get a specific push config.
634#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
635#[serde(rename_all = "camelCase")]
636pub struct GetTaskPushNotificationConfigRequest {
637    pub task_id: TaskId,
638    pub id: String,
639
640    #[serde(default, skip_serializing_if = "Option::is_none")]
641    pub tenant: Option<String>,
642}
643
644/// Request to list push configs for a task.
645#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
646#[serde(rename_all = "camelCase")]
647pub struct ListTaskPushNotificationConfigsRequest {
648    pub task_id: TaskId,
649
650    #[serde(default, skip_serializing_if = "Option::is_none")]
651    pub page_size: Option<i32>,
652
653    #[serde(default, skip_serializing_if = "Option::is_none")]
654    pub page_token: Option<String>,
655
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub tenant: Option<String>,
658}
659
660/// Response for listing push configs.
661#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
662#[serde(rename_all = "camelCase")]
663pub struct ListTaskPushNotificationConfigsResponse {
664    pub configs: Vec<TaskPushNotificationConfig>,
665
666    #[serde(default, skip_serializing_if = "Option::is_none")]
667    pub next_page_token: Option<String>,
668}
669
670/// Request to create a push config for a task.
671#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
672#[serde(rename_all = "camelCase")]
673pub struct CreateTaskPushNotificationConfigRequest {
674    pub task_id: TaskId,
675    pub config: PushNotificationConfig,
676
677    #[serde(default, skip_serializing_if = "Option::is_none")]
678    pub tenant: Option<String>,
679}
680
681/// Request to delete a push config.
682#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
683#[serde(rename_all = "camelCase")]
684pub struct DeleteTaskPushNotificationConfigRequest {
685    pub task_id: TaskId,
686    pub id: String,
687
688    #[serde(default, skip_serializing_if = "Option::is_none")]
689    pub tenant: Option<String>,
690}
691
692// ---------------------------------------------------------------------------
693// TransportProtocol
694// ---------------------------------------------------------------------------
695
696/// Transport protocol identifier. Open-form string to allow custom protocols.
697pub type TransportProtocol = String;
698
699/// JSON-RPC transport protocol constant.
700pub const TRANSPORT_PROTOCOL_JSONRPC: &str = "JSONRPC";
701/// gRPC transport protocol constant.
702pub const TRANSPORT_PROTOCOL_GRPC: &str = "GRPC";
703/// HTTP+JSON (REST) transport protocol constant.
704pub const TRANSPORT_PROTOCOL_HTTP_JSON: &str = "HTTP+JSON";
705/// SLIMRPC transport protocol constant.
706pub const TRANSPORT_PROTOCOL_SLIMRPC: &str = "SLIMRPC";
707
708/// Protocol version string (e.g., "1.0").
709pub type ProtocolVersion = String;
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714
715    #[test]
716    fn test_task_state_serde() {
717        let state = TaskState::Completed;
718        let json = serde_json::to_string(&state).unwrap();
719        assert_eq!(json, r#""TASK_STATE_COMPLETED""#);
720        let back: TaskState = serde_json::from_str(&json).unwrap();
721        assert_eq!(back, TaskState::Completed);
722    }
723
724    #[test]
725    fn test_role_serde() {
726        let role = Role::Agent;
727        let json = serde_json::to_string(&role).unwrap();
728        assert_eq!(json, r#""ROLE_AGENT""#);
729        let back: Role = serde_json::from_str(&json).unwrap();
730        assert_eq!(back, Role::Agent);
731    }
732
733    #[test]
734    fn test_part_text_serde() {
735        let part = Part::text("hello");
736        let json = serde_json::to_string(&part).unwrap();
737        let v: Value = serde_json::from_str(&json).unwrap();
738        assert_eq!(v["text"], "hello");
739        assert!(v.get("raw").is_none());
740
741        let back: Part = serde_json::from_str(&json).unwrap();
742        assert_eq!(back.content, PartContent::Text("hello".into()));
743    }
744
745    #[test]
746    fn test_part_raw_serde() {
747        let part = Part::raw(vec![1, 2, 3]);
748        let json = serde_json::to_string(&part).unwrap();
749        let back: Part = serde_json::from_str(&json).unwrap();
750        assert_eq!(back.content, PartContent::Raw(vec![1, 2, 3]));
751    }
752
753    #[test]
754    fn test_send_message_response_serde() {
755        let resp = SendMessageResponse::Task(Task {
756            id: "t1".into(),
757            context_id: "c1".into(),
758            status: TaskStatus {
759                state: TaskState::Submitted,
760                message: None,
761                timestamp: None,
762            },
763            artifacts: None,
764            history: None,
765            metadata: None,
766        });
767        let json = serde_json::to_string(&resp).unwrap();
768        let v: Value = serde_json::from_str(&json).unwrap();
769        assert!(v.get("task").is_some());
770        assert!(v.get("message").is_none());
771        let back: SendMessageResponse = serde_json::from_str(&json).unwrap();
772        assert!(matches!(back, SendMessageResponse::Task(_)));
773    }
774
775    #[test]
776    fn test_send_message_response_message_variant() {
777        let resp = SendMessageResponse::Message(Message::new(Role::Agent, vec![Part::text("hi")]));
778        let json = serde_json::to_string(&resp).unwrap();
779        let v: Value = serde_json::from_str(&json).unwrap();
780        assert!(v.get("message").is_some());
781        assert!(v.get("task").is_none());
782        let back: SendMessageResponse = serde_json::from_str(&json).unwrap();
783        assert!(matches!(back, SendMessageResponse::Message(_)));
784    }
785
786    #[test]
787    fn test_part_url_serde() {
788        let part = Part::url("https://example.com/file.pdf");
789        let json = serde_json::to_string(&part).unwrap();
790        let v: Value = serde_json::from_str(&json).unwrap();
791        assert_eq!(v["url"], "https://example.com/file.pdf");
792        let back: Part = serde_json::from_str(&json).unwrap();
793        assert!(matches!(back.content, PartContent::Url(_)));
794    }
795
796    #[test]
797    fn test_part_data_serde() {
798        let data = serde_json::json!({"key": "value", "count": 42});
799        let part = Part::data(data.clone());
800        let json = serde_json::to_string(&part).unwrap();
801        let back: Part = serde_json::from_str(&json).unwrap();
802        assert!(matches!(back.content, PartContent::Data(_)));
803        if let PartContent::Data(d) = back.content {
804            assert_eq!(d["key"], "value");
805        }
806    }
807
808    #[test]
809    fn test_part_with_metadata_and_media_type() {
810        let part = Part::text("hello")
811            .with_media_type("text/plain")
812            .with_filename("test.txt");
813        assert_eq!(part.media_type.as_deref(), Some("text/plain"));
814        assert_eq!(part.filename.as_deref(), Some("test.txt"));
815        let json = serde_json::to_string(&part).unwrap();
816        let v: Value = serde_json::from_str(&json).unwrap();
817        assert_eq!(v["mediaType"], "text/plain");
818        assert_eq!(v["filename"], "test.txt");
819    }
820
821    #[test]
822    fn test_part_as_text() {
823        let part = Part::text("hello");
824        assert_eq!(part.as_text(), Some("hello"));
825        let part = Part::raw(vec![]);
826        assert_eq!(part.as_text(), None);
827    }
828
829    #[test]
830    fn test_message_new() {
831        let msg = Message::new(Role::User, vec![Part::text("hi")]);
832        assert!(!msg.message_id.is_empty());
833        assert_eq!(msg.role, Role::User);
834        assert_eq!(msg.parts.len(), 1);
835        assert_eq!(msg.text(), Some("hi"));
836    }
837
838    #[test]
839    fn test_message_serde() {
840        let msg = Message {
841            message_id: "m1".to_string(),
842            context_id: Some("c1".to_string()),
843            task_id: None,
844            role: Role::Agent,
845            parts: vec![Part::text("response")],
846            metadata: None,
847            extensions: Some(vec!["ext1".to_string()]),
848            reference_task_ids: Some(vec!["t1".to_string()]),
849        };
850        let json = serde_json::to_string(&msg).unwrap();
851        let back: Message = serde_json::from_str(&json).unwrap();
852        assert_eq!(msg, back);
853    }
854
855    #[test]
856    fn test_task_state_is_terminal() {
857        assert!(TaskState::Completed.is_terminal());
858        assert!(TaskState::Failed.is_terminal());
859        assert!(TaskState::Canceled.is_terminal());
860        assert!(TaskState::Rejected.is_terminal());
861        assert!(!TaskState::Submitted.is_terminal());
862        assert!(!TaskState::Working.is_terminal());
863        assert!(!TaskState::InputRequired.is_terminal());
864        assert!(!TaskState::AuthRequired.is_terminal());
865        assert!(!TaskState::Unspecified.is_terminal());
866    }
867
868    #[test]
869    fn test_task_full_serde() {
870        let task = Task {
871            id: "t1".to_string(),
872            context_id: "c1".to_string(),
873            status: TaskStatus {
874                state: TaskState::Working,
875                message: Some(Message::new(Role::Agent, vec![Part::text("working")])),
876                timestamp: None,
877            },
878            artifacts: Some(vec![Artifact {
879                artifact_id: "a1".to_string(),
880                name: Some("output".to_string()),
881                description: None,
882                parts: vec![Part::text("result data")],
883                metadata: None,
884                extensions: None,
885            }]),
886            history: Some(vec![Message::new(Role::User, vec![Part::text("do it")])]),
887            metadata: None,
888        };
889        let json = serde_json::to_string(&task).unwrap();
890        let back: Task = serde_json::from_str(&json).unwrap();
891        assert_eq!(task.id, back.id);
892        assert_eq!(task.status.state, back.status.state);
893        assert!(back.artifacts.is_some());
894        assert!(back.history.is_some());
895    }
896
897    #[test]
898    fn test_push_notification_config_serde() {
899        let config = PushNotificationConfig {
900            url: "https://example.com/webhook".to_string(),
901            id: Some("cfg-1".to_string()),
902            token: Some("tok-1".to_string()),
903            authentication: Some(AuthenticationInfo {
904                scheme: "Bearer".to_string(),
905                credentials: Some("secret".to_string()),
906            }),
907        };
908        let json = serde_json::to_string(&config).unwrap();
909        let back: PushNotificationConfig = serde_json::from_str(&json).unwrap();
910        assert_eq!(config, back);
911    }
912
913    #[test]
914    fn test_send_message_request_serde() {
915        let req = SendMessageRequest {
916            message: Message::new(Role::User, vec![Part::text("hello")]),
917            configuration: Some(SendMessageConfiguration {
918                accepted_output_modes: Some(vec!["text/plain".to_string()]),
919                push_notification_config: None,
920                history_length: Some(10),
921                return_immediately: Some(true),
922            }),
923            metadata: None,
924            tenant: Some("tenant-1".to_string()),
925        };
926        let json = serde_json::to_string(&req).unwrap();
927        let back: SendMessageRequest = serde_json::from_str(&json).unwrap();
928        assert_eq!(req.tenant, back.tenant);
929        assert_eq!(
930            req.configuration.as_ref().unwrap().history_length,
931            back.configuration.as_ref().unwrap().history_length
932        );
933    }
934
935    #[test]
936    fn test_role_all_variants_serde() {
937        let cases = [
938            (Role::Unspecified, "\"ROLE_UNSPECIFIED\""),
939            (Role::User, "\"ROLE_USER\""),
940            (Role::Agent, "\"ROLE_AGENT\""),
941        ];
942
943        for (role, expected_json) in cases {
944            let json = serde_json::to_string(&role).unwrap();
945            assert_eq!(json, expected_json);
946            let back: Role = serde_json::from_str(&json).unwrap();
947            assert_eq!(back, role);
948        }
949
950        let back: Role = serde_json::from_str("\"\"").unwrap();
951        assert_eq!(back, Role::Unspecified);
952    }
953
954    #[test]
955    fn test_task_state_all_variants_serde() {
956        let cases = [
957            (TaskState::Unspecified, "TASK_STATE_UNSPECIFIED"),
958            (TaskState::Submitted, "TASK_STATE_SUBMITTED"),
959            (TaskState::Working, "TASK_STATE_WORKING"),
960            (TaskState::Completed, "TASK_STATE_COMPLETED"),
961            (TaskState::Failed, "TASK_STATE_FAILED"),
962            (TaskState::Canceled, "TASK_STATE_CANCELED"),
963            (TaskState::InputRequired, "TASK_STATE_INPUT_REQUIRED"),
964            (TaskState::Rejected, "TASK_STATE_REJECTED"),
965            (TaskState::AuthRequired, "TASK_STATE_AUTH_REQUIRED"),
966        ];
967
968        for (state, expected_str) in cases {
969            let json = serde_json::to_string(&state).unwrap();
970            assert_eq!(json, format!("\"{}\"", expected_str));
971            let back: TaskState = serde_json::from_str(&json).unwrap();
972            assert_eq!(back, state);
973        }
974
975        let back: TaskState = serde_json::from_str("\"\"").unwrap();
976        assert_eq!(back, TaskState::Unspecified);
977    }
978
979    #[test]
980    fn test_part_all_content_variants_serde() {
981        let parts = [
982            Part::text("hello"),
983            Part::raw(vec![1, 2, 3]),
984            Part::url("https://example.com"),
985            Part::data(serde_json::json!({"value": 1})),
986        ];
987
988        for part in parts {
989            let json = serde_json::to_string(&part).unwrap();
990            let back: Part = serde_json::from_str(&json).unwrap();
991            match (&part.content, &back.content) {
992                (PartContent::Text(a), PartContent::Text(b)) => assert_eq!(a, b),
993                (PartContent::Raw(a), PartContent::Raw(b)) => assert_eq!(a, b),
994                (PartContent::Url(a), PartContent::Url(b)) => assert_eq!(a, b),
995                (PartContent::Data(_), PartContent::Data(_)) => {}
996                _ => panic!("mismatched part content variants"),
997            }
998        }
999    }
1000
1001    #[test]
1002    fn test_send_message_response_message_deserialize() {
1003        let json = serde_json::json!({
1004            "message": {
1005                "messageId": "m1",
1006                "role": "ROLE_AGENT",
1007                "parts": [{"text": "hello"}]
1008            }
1009        });
1010        let back: SendMessageResponse = serde_json::from_value(json).unwrap();
1011        assert!(matches!(back, SendMessageResponse::Message(_)));
1012    }
1013
1014    #[test]
1015    fn test_new_id_functions() {
1016        let task_id = new_task_id();
1017        assert!(!task_id.is_empty());
1018        let ctx_id = new_context_id();
1019        assert!(!ctx_id.is_empty());
1020        let msg_id = new_message_id();
1021        assert!(!msg_id.is_empty());
1022        let art_id = new_artifact_id();
1023        assert!(!art_id.is_empty());
1024        // All should be unique
1025        assert_ne!(task_id, ctx_id);
1026    }
1027
1028    #[test]
1029    fn test_role_default() {
1030        assert_eq!(Role::default(), Role::Unspecified);
1031    }
1032
1033    #[test]
1034    fn test_task_state_default() {
1035        assert_eq!(TaskState::default(), TaskState::Unspecified);
1036    }
1037
1038    #[test]
1039    fn test_list_tasks_request_serde() {
1040        let req = ListTasksRequest {
1041            context_id: Some("c1".to_string()),
1042            status: Some(TaskState::Working),
1043            page_size: Some(10),
1044            page_token: None,
1045            history_length: Some(5),
1046            status_timestamp_after: None,
1047            include_artifacts: Some(true),
1048            tenant: None,
1049        };
1050        let json = serde_json::to_string(&req).unwrap();
1051        let back: ListTasksRequest = serde_json::from_str(&json).unwrap();
1052        assert_eq!(req, back);
1053    }
1054
1055    #[test]
1056    fn test_cancel_task_request_serde() {
1057        let req = CancelTaskRequest {
1058            id: "t1".to_string(),
1059            metadata: None,
1060            tenant: Some("ten".to_string()),
1061        };
1062        let json = serde_json::to_string(&req).unwrap();
1063        let back: CancelTaskRequest = serde_json::from_str(&json).unwrap();
1064        assert_eq!(req, back);
1065    }
1066
1067    #[test]
1068    fn test_subscribe_to_task_request_serde() {
1069        let req = SubscribeToTaskRequest {
1070            id: "t1".to_string(),
1071            tenant: None,
1072        };
1073        let json = serde_json::to_string(&req).unwrap();
1074        let back: SubscribeToTaskRequest = serde_json::from_str(&json).unwrap();
1075        assert_eq!(req, back);
1076    }
1077}