1use serde::{Deserialize, Serialize};
6pub mod compat;
7
8use std::collections::HashMap;
9
10fn nullable_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
13where
14 D: serde::Deserializer<'de>,
15 T: Deserialize<'de>,
16{
17 Option::<Vec<T>>::deserialize(deserializer).map(|v| v.unwrap_or_default())
18}
19
20fn nullable_map<'de, D, K, V>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
22where
23 D: serde::Deserializer<'de>,
24 K: Deserialize<'de> + std::cmp::Eq + std::hash::Hash,
25 V: Deserialize<'de>,
26{
27 Option::<HashMap<K, V>>::deserialize(deserializer).map(|v| v.unwrap_or_default())
28}
29use uuid::Uuid;
30
31pub const PROTOCOL_VERSION: &str = "1.0";
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
37#[serde(rename_all = "camelCase")]
38pub struct AgentCard {
39 pub name: String,
41 pub description: String,
43 #[serde(default, deserialize_with = "nullable_vec")]
45 pub supported_interfaces: Vec<AgentInterface>,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub provider: Option<AgentProvider>,
49 pub version: String,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub documentation_url: Option<String>,
54 #[serde(default)]
56 pub capabilities: AgentCapabilities,
57 #[serde(default, deserialize_with = "nullable_map")]
59 pub security_schemes: HashMap<String, SecurityScheme>,
60 #[serde(default, deserialize_with = "nullable_vec")]
62 pub security_requirements: Vec<SecurityRequirement>,
63 #[serde(default, deserialize_with = "nullable_vec")]
65 pub default_input_modes: Vec<String>,
66 #[serde(default, deserialize_with = "nullable_vec")]
68 pub default_output_modes: Vec<String>,
69 #[serde(default, deserialize_with = "nullable_vec")]
71 pub skills: Vec<AgentSkill>,
72 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "nullable_vec")]
74 pub signatures: Vec<AgentCardSignature>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub icon_url: Option<String>,
78}
79
80impl AgentCard {
81 pub fn endpoint(&self) -> Option<&str> {
83 self.supported_interfaces
84 .iter()
85 .find(|i| i.protocol_binding.eq_ignore_ascii_case("jsonrpc"))
86 .map(|i| i.url.as_str())
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
92#[serde(rename_all = "camelCase")]
93pub struct AgentInterface {
94 pub url: String,
95 pub protocol_binding: String,
96 pub protocol_version: String,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub tenant: Option<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103#[serde(rename_all = "camelCase")]
104pub struct AgentProvider {
105 pub organization: String,
107 pub url: String,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
113#[serde(rename_all = "camelCase")]
114#[non_exhaustive]
115pub enum SecurityScheme {
116 ApiKeySecurityScheme(ApiKeySecurityScheme),
117 HttpAuthSecurityScheme(HttpAuthSecurityScheme),
118 Oauth2SecurityScheme(OAuth2SecurityScheme),
119 OpenIdConnectSecurityScheme(OpenIdConnectSecurityScheme),
120 MtlsSecurityScheme(MutualTlsSecurityScheme),
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124#[serde(rename_all = "camelCase")]
125pub struct ApiKeySecurityScheme {
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub description: Option<String>,
128 pub location: String,
130 pub name: String,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
135#[serde(rename_all = "camelCase")]
136pub struct HttpAuthSecurityScheme {
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub description: Option<String>,
139 pub scheme: String,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub bearer_format: Option<String>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
146#[serde(rename_all = "camelCase")]
147pub struct OAuth2SecurityScheme {
148 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub description: Option<String>,
150 pub flows: OAuthFlows,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub oauth2_metadata_url: Option<String>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
156#[serde(rename_all = "camelCase")]
157pub struct OpenIdConnectSecurityScheme {
158 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub description: Option<String>,
160 pub open_id_connect_url: String,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
164#[serde(rename_all = "camelCase")]
165pub struct MutualTlsSecurityScheme {
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub description: Option<String>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172#[serde(rename_all = "camelCase")]
173#[non_exhaustive]
174pub enum OAuthFlows {
175 AuthorizationCode(AuthorizationCodeOAuthFlow),
176 ClientCredentials(ClientCredentialsOAuthFlow),
177 Implicit(ImplicitOAuthFlow),
179 Password(PasswordOAuthFlow),
181 DeviceCode(DeviceCodeOAuthFlow),
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185#[serde(rename_all = "camelCase")]
186pub struct AuthorizationCodeOAuthFlow {
187 pub authorization_url: String,
188 pub token_url: String,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub refresh_url: Option<String>,
191 #[serde(default)]
192 pub scopes: HashMap<String, String>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub pkce_required: Option<bool>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
198#[serde(rename_all = "camelCase")]
199pub struct ClientCredentialsOAuthFlow {
200 pub token_url: String,
201 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub refresh_url: Option<String>,
203 #[serde(default)]
204 pub scopes: HashMap<String, String>,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
208#[serde(rename_all = "camelCase")]
209pub struct ImplicitOAuthFlow {
210 pub authorization_url: String,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub refresh_url: Option<String>,
213 #[serde(default)]
214 pub scopes: HashMap<String, String>,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218#[serde(rename_all = "camelCase")]
219pub struct PasswordOAuthFlow {
220 pub token_url: String,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub refresh_url: Option<String>,
223 #[serde(default)]
224 pub scopes: HashMap<String, String>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
228#[serde(rename_all = "camelCase")]
229pub struct DeviceCodeOAuthFlow {
230 pub device_authorization_url: String,
231 pub token_url: String,
232 #[serde(default, skip_serializing_if = "Option::is_none")]
233 pub refresh_url: Option<String>,
234 #[serde(default)]
235 pub scopes: HashMap<String, String>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
240#[serde(rename_all = "camelCase")]
241pub struct StringList {
242 #[serde(default)]
243 pub list: Vec<String>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
248#[serde(rename_all = "camelCase")]
249pub struct SecurityRequirement {
250 #[serde(default)]
252 pub schemes: HashMap<String, StringList>,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
257#[serde(rename_all = "camelCase")]
258pub struct AgentCardSignature {
259 pub protected: String,
261 pub signature: String,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
265 pub header: Option<serde_json::Value>,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
269#[serde(rename_all = "camelCase")]
270pub struct AgentCapabilities {
271 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub streaming: Option<bool>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub push_notifications: Option<bool>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub extended_agent_card: Option<bool>,
277 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "nullable_vec")]
278 pub extensions: Vec<AgentExtension>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
282#[serde(rename_all = "camelCase")]
283pub struct AgentExtension {
284 pub uri: String,
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub description: Option<String>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
288 pub required: Option<bool>,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub params: Option<serde_json::Value>,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
295#[serde(rename_all = "camelCase")]
296pub struct AgentSkill {
297 pub id: String,
299 pub name: String,
301 pub description: String,
303 #[serde(default, deserialize_with = "nullable_vec")]
305 pub tags: Vec<String>,
306 #[serde(default, deserialize_with = "nullable_vec")]
308 pub examples: Vec<String>,
309 #[serde(default, deserialize_with = "nullable_vec")]
311 pub input_modes: Vec<String>,
312 #[serde(default, deserialize_with = "nullable_vec")]
314 pub output_modes: Vec<String>,
315 #[serde(default, deserialize_with = "nullable_vec")]
317 pub security_requirements: Vec<SecurityRequirement>,
318}
319
320#[derive(Debug, Clone, PartialEq)]
331pub enum Part {
332 Text {
334 text: String,
336 metadata: Option<serde_json::Value>,
338 },
339 File {
341 file: FileContent,
343 metadata: Option<serde_json::Value>,
345 },
346 Data {
348 data: serde_json::Value,
350 metadata: Option<serde_json::Value>,
352 },
353}
354
355impl Serialize for Part {
356 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
357 use serde::ser::SerializeMap;
358 match self {
359 Part::Text { text, metadata } => {
360 let mut map = serializer.serialize_map(None)?;
361 map.serialize_entry("kind", "text")?;
362 map.serialize_entry("text", text)?;
363 if let Some(m) = metadata {
364 map.serialize_entry("metadata", m)?;
365 }
366 map.end()
367 }
368 Part::File { file, metadata } => {
369 let mut map = serializer.serialize_map(None)?;
370 map.serialize_entry("kind", "file")?;
371 map.serialize_entry("file", file)?;
372 if let Some(m) = metadata {
373 map.serialize_entry("metadata", m)?;
374 }
375 map.end()
376 }
377 Part::Data { data, metadata } => {
378 let mut map = serializer.serialize_map(None)?;
379 map.serialize_entry("kind", "data")?;
380 map.serialize_entry("data", data)?;
381 if let Some(m) = metadata {
382 map.serialize_entry("metadata", m)?;
383 }
384 map.end()
385 }
386 }
387 }
388}
389
390impl<'de> Deserialize<'de> for Part {
391 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
392 let value = serde_json::Value::deserialize(deserializer)?;
393 let obj = value
394 .as_object()
395 .ok_or_else(|| serde::de::Error::custom("expected object for Part"))?;
396 let metadata = obj.get("metadata").cloned();
397
398 if let Some(text) = obj.get("text") {
399 Ok(Part::Text {
400 text: text
401 .as_str()
402 .ok_or_else(|| serde::de::Error::custom("text must be a string"))?
403 .to_string(),
404 metadata,
405 })
406 } else if let Some(file) = obj.get("file") {
407 let file: FileContent = serde_json::from_value(file.clone())
408 .map_err(serde::de::Error::custom)?;
409 Ok(Part::File { file, metadata })
410 } else if let Some(data) = obj.get("data") {
411 Ok(Part::Data {
412 data: data.clone(),
413 metadata,
414 })
415 } else if obj.contains_key("raw") || obj.contains_key("url") {
416 let file = FileContent {
418 bytes: obj.get("raw").and_then(|v| v.as_str()).map(|s| s.to_string()),
419 uri: obj.get("url").and_then(|v| v.as_str()).map(|s| s.to_string()),
420 name: obj.get("filename").and_then(|v| v.as_str()).map(|s| s.to_string()),
421 mime_type: obj
422 .get("mediaType")
423 .or_else(|| obj.get("mimeType"))
424 .and_then(|v| v.as_str())
425 .map(|s| s.to_string()),
426 };
427 Ok(Part::File { file, metadata })
428 } else if obj.contains_key("kind") {
429 let kind = obj["kind"].as_str().unwrap_or("");
431 match kind {
432 "text" => Ok(Part::Text {
433 text: obj
434 .get("text")
435 .and_then(|v| v.as_str())
436 .unwrap_or("")
437 .to_string(),
438 metadata,
439 }),
440 "file" => {
441 let file: FileContent = serde_json::from_value(
442 obj.get("file").cloned().unwrap_or_default(),
443 )
444 .map_err(serde::de::Error::custom)?;
445 Ok(Part::File { file, metadata })
446 }
447 "data" => Ok(Part::Data {
448 data: obj.get("data").cloned().unwrap_or_default(),
449 metadata,
450 }),
451 _ => Err(serde::de::Error::custom(format!(
452 "unknown part kind: {kind}"
453 ))),
454 }
455 } else {
456 Err(serde::de::Error::custom(
457 "Part must have text, file, or data field",
458 ))
459 }
460 }
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
465#[serde(rename_all = "camelCase")]
466pub struct FileContent {
467 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub bytes: Option<String>,
470 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub uri: Option<String>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub name: Option<String>,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub mime_type: Option<String>,
479}
480
481impl Part {
482 pub fn text(text: impl Into<String>) -> Self {
484 Part::Text {
485 text: text.into(),
486 metadata: None,
487 }
488 }
489
490 pub fn file_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
492 Part::File {
493 file: FileContent {
494 bytes: None,
495 uri: Some(uri.into()),
496 name: None,
497 mime_type: Some(mime_type.into()),
498 },
499 metadata: None,
500 }
501 }
502
503 pub fn file_bytes(bytes: impl Into<String>, mime_type: impl Into<String>) -> Self {
505 Part::File {
506 file: FileContent {
507 bytes: Some(bytes.into()),
508 uri: None,
509 name: None,
510 mime_type: Some(mime_type.into()),
511 },
512 metadata: None,
513 }
514 }
515
516 pub fn data(data: serde_json::Value) -> Self {
518 Part::Data {
519 data,
520 metadata: None,
521 }
522 }
523
524 pub fn as_text(&self) -> Option<&str> {
526 match self {
527 Part::Text { text, .. } => Some(text),
528 _ => None,
529 }
530 }
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
537#[non_exhaustive]
538pub enum Role {
539 #[serde(rename = "ROLE_UNSPECIFIED")]
540 Unspecified,
541 #[serde(rename = "ROLE_USER")]
542 User,
543 #[serde(rename = "ROLE_AGENT")]
544 Agent,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
549#[serde(rename_all = "camelCase")]
550pub struct Message {
551 #[serde(default = "default_message_kind")]
553 pub kind: String,
554 pub message_id: String,
556 #[serde(default, skip_serializing_if = "Option::is_none")]
558 pub context_id: Option<String>,
559 #[serde(default, skip_serializing_if = "Option::is_none")]
561 pub task_id: Option<String>,
562 pub role: Role,
564 pub parts: Vec<Part>,
566 #[serde(default, skip_serializing_if = "Option::is_none")]
568 pub metadata: Option<serde_json::Value>,
569 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "nullable_vec")]
571 pub extensions: Vec<String>,
572 #[serde(default, skip_serializing_if = "Option::is_none")]
574 pub reference_task_ids: Option<Vec<String>>,
575}
576
577fn default_message_kind() -> String {
578 "message".to_string()
579}
580
581fn default_task_kind() -> String {
582 "task".to_string()
583}
584
585fn default_status_update_kind() -> String {
586 "status-update".to_string()
587}
588
589fn default_artifact_update_kind() -> String {
590 "artifact-update".to_string()
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
595#[serde(rename_all = "camelCase")]
596pub struct Artifact {
597 pub artifact_id: String,
599 #[serde(default, skip_serializing_if = "Option::is_none")]
601 pub name: Option<String>,
602 #[serde(default, skip_serializing_if = "Option::is_none")]
604 pub description: Option<String>,
605 pub parts: Vec<Part>,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub metadata: Option<serde_json::Value>,
610 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "nullable_vec")]
612 pub extensions: Vec<String>,
613}
614
615#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
617#[non_exhaustive]
618pub enum TaskState {
619 #[serde(rename = "TASK_STATE_UNSPECIFIED")]
620 Unspecified,
621 #[serde(rename = "TASK_STATE_SUBMITTED")]
622 Submitted,
623 #[serde(rename = "TASK_STATE_WORKING")]
624 Working,
625 #[serde(rename = "TASK_STATE_COMPLETED")]
626 Completed,
627 #[serde(rename = "TASK_STATE_FAILED")]
628 Failed,
629 #[serde(rename = "TASK_STATE_CANCELED")]
630 Canceled,
631 #[serde(rename = "TASK_STATE_INPUT_REQUIRED")]
632 InputRequired,
633 #[serde(rename = "TASK_STATE_REJECTED")]
634 Rejected,
635 #[serde(rename = "TASK_STATE_AUTH_REQUIRED")]
636 AuthRequired,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
641#[serde(rename_all = "camelCase")]
642pub struct TaskStatus {
643 pub state: TaskState,
645 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub message: Option<Message>,
648 #[serde(default, skip_serializing_if = "Option::is_none")]
650 pub timestamp: Option<String>,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
655#[serde(rename_all = "camelCase")]
656pub struct Task {
657 #[serde(default = "default_task_kind")]
659 pub kind: String,
660 pub id: String,
662 pub context_id: String,
664 pub status: TaskStatus,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
668 pub artifacts: Option<Vec<Artifact>>,
669 #[serde(default, skip_serializing_if = "Option::is_none")]
671 pub history: Option<Vec<Message>>,
672 #[serde(default, skip_serializing_if = "Option::is_none")]
674 pub metadata: Option<serde_json::Value>,
675}
676
677impl TaskState {
678 pub fn is_terminal(&self) -> bool {
680 matches!(
681 self,
682 TaskState::Completed | TaskState::Failed | TaskState::Canceled | TaskState::Rejected
683 )
684 }
685}
686
687#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
694#[serde(rename_all = "camelCase")]
695pub enum SendMessageResponse {
696 Task(Task),
697 Message(Message),
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
705#[serde(rename_all = "camelCase")]
706pub enum SendMessageResult {
707 Task(Task),
708 Message(Message),
709}
710
711#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
714pub struct JsonRpcRequest {
715 pub jsonrpc: String,
716 pub method: String,
717 #[serde(default)]
718 pub params: Option<serde_json::Value>,
719 pub id: serde_json::Value,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
723pub struct JsonRpcResponse {
724 pub jsonrpc: String,
725 pub id: serde_json::Value,
726 #[serde(skip_serializing_if = "Option::is_none")]
727 pub result: Option<serde_json::Value>,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 pub error: Option<JsonRpcError>,
730}
731
732#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
733pub struct JsonRpcError {
734 pub code: i32,
735 pub message: String,
736 #[serde(skip_serializing_if = "Option::is_none")]
737 pub data: Option<serde_json::Value>,
738}
739
740pub mod errors {
742 pub const PARSE_ERROR: i32 = -32700;
744 pub const INVALID_REQUEST: i32 = -32600;
745 pub const METHOD_NOT_FOUND: i32 = -32601;
746 pub const INVALID_PARAMS: i32 = -32602;
747 pub const INTERNAL_ERROR: i32 = -32603;
748
749 pub const TASK_NOT_FOUND: i32 = -32001;
751 pub const TASK_NOT_CANCELABLE: i32 = -32002;
752 pub const PUSH_NOTIFICATION_NOT_SUPPORTED: i32 = -32003;
753 pub const UNSUPPORTED_OPERATION: i32 = -32004;
754 pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
755 pub const EXTENDED_AGENT_CARD_NOT_CONFIGURED: i32 = -32006;
756 pub const VERSION_NOT_SUPPORTED: i32 = -32007;
757 pub const INVALID_AGENT_RESPONSE: i32 = -32008;
758 pub const EXTENSION_SUPPORT_REQUIRED: i32 = -32009;
759
760 pub fn message_for_code(code: i32) -> &'static str {
761 match code {
762 PARSE_ERROR => "Parse error",
763 INVALID_REQUEST => "Invalid request",
764 METHOD_NOT_FOUND => "Method not found",
765 INVALID_PARAMS => "Invalid params",
766 INTERNAL_ERROR => "Internal error",
767 TASK_NOT_FOUND => "Task not found",
768 TASK_NOT_CANCELABLE => "Task not cancelable",
769 PUSH_NOTIFICATION_NOT_SUPPORTED => "Push notifications not supported",
770 UNSUPPORTED_OPERATION => "Unsupported operation",
771 CONTENT_TYPE_NOT_SUPPORTED => "Content type not supported",
772 EXTENDED_AGENT_CARD_NOT_CONFIGURED => "Extended agent card not configured",
773 VERSION_NOT_SUPPORTED => "Protocol version not supported",
774 INVALID_AGENT_RESPONSE => "Invalid agent response",
775 EXTENSION_SUPPORT_REQUIRED => "Extension support required",
776 _ => "Unknown error",
777 }
778 }
779}
780
781pub fn success(id: serde_json::Value, result: serde_json::Value) -> JsonRpcResponse {
782 JsonRpcResponse {
783 jsonrpc: "2.0".to_string(),
784 id,
785 result: Some(result),
786 error: None,
787 }
788}
789
790pub fn error(
791 id: serde_json::Value,
792 code: i32,
793 message: &str,
794 data: Option<serde_json::Value>,
795) -> JsonRpcResponse {
796 JsonRpcResponse {
797 jsonrpc: "2.0".to_string(),
798 id,
799 result: None,
800 error: Some(JsonRpcError {
801 code,
802 message: message.to_string(),
803 data,
804 }),
805 }
806}
807
808#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
812#[serde(rename_all = "camelCase")]
813pub struct SendMessageRequest {
814 #[serde(default, skip_serializing_if = "Option::is_none")]
816 pub tenant: Option<String>,
817 pub message: Message,
819 #[serde(default, skip_serializing_if = "Option::is_none")]
821 pub configuration: Option<SendMessageConfiguration>,
822 #[serde(default, skip_serializing_if = "Option::is_none")]
824 pub metadata: Option<serde_json::Value>,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
829#[serde(rename_all = "camelCase")]
830pub struct SendMessageConfiguration {
831 #[serde(default, skip_serializing_if = "Option::is_none")]
833 pub accepted_output_modes: Option<Vec<String>>,
834 #[serde(default, skip_serializing_if = "Option::is_none")]
836 pub push_notification_config: Option<PushNotificationConfig>,
837 #[serde(default, skip_serializing_if = "Option::is_none")]
839 pub history_length: Option<u32>,
840 #[serde(default, skip_serializing_if = "Option::is_none")]
842 pub blocking: Option<bool>,
843 #[serde(default, skip_serializing_if = "Option::is_none")]
846 pub return_immediately: Option<bool>,
847}
848
849#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
851#[serde(rename_all = "camelCase")]
852pub struct GetTaskRequest {
853 pub id: String,
855 #[serde(default, skip_serializing_if = "Option::is_none")]
857 pub history_length: Option<u32>,
858 #[serde(default, skip_serializing_if = "Option::is_none")]
860 pub tenant: Option<String>,
861}
862
863#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
865#[serde(rename_all = "camelCase")]
866pub struct CancelTaskRequest {
867 pub id: String,
869 #[serde(default, skip_serializing_if = "Option::is_none")]
871 pub tenant: Option<String>,
872}
873
874#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
876#[serde(rename_all = "camelCase")]
877pub struct ListTasksRequest {
878 #[serde(default, skip_serializing_if = "Option::is_none")]
880 pub context_id: Option<String>,
881 #[serde(default, skip_serializing_if = "Option::is_none")]
883 pub status: Option<TaskState>,
884 #[serde(default, skip_serializing_if = "Option::is_none")]
886 pub page_size: Option<u32>,
887 #[serde(default, skip_serializing_if = "Option::is_none")]
889 pub page_token: Option<String>,
890 #[serde(default, skip_serializing_if = "Option::is_none")]
892 pub history_length: Option<u32>,
893 #[serde(default, skip_serializing_if = "Option::is_none")]
895 pub status_timestamp_after: Option<i64>,
896 #[serde(default, skip_serializing_if = "Option::is_none")]
898 pub include_artifacts: Option<bool>,
899 #[serde(default, skip_serializing_if = "Option::is_none")]
901 pub tenant: Option<String>,
902}
903
904#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
906#[serde(rename_all = "camelCase")]
907pub struct TaskListResponse {
908 pub tasks: Vec<Task>,
910 pub next_page_token: String,
912 pub page_size: u32,
914 pub total_size: u32,
916}
917
918#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
920#[serde(rename_all = "camelCase")]
921pub struct SubscribeToTaskRequest {
922 pub id: String,
924 #[serde(default, skip_serializing_if = "Option::is_none")]
926 pub tenant: Option<String>,
927}
928
929#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
931#[serde(rename_all = "camelCase")]
932pub struct PushNotificationConfig {
933 #[serde(default, skip_serializing_if = "Option::is_none")]
935 pub id: Option<String>,
936 pub url: String,
938 #[serde(default, skip_serializing_if = "Option::is_none")]
940 pub token: Option<String>,
941 #[serde(default, skip_serializing_if = "Option::is_none")]
943 pub authentication: Option<AuthenticationInfo>,
944}
945
946#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
948#[serde(rename_all = "camelCase")]
949pub struct AuthenticationInfo {
950 pub scheme: String,
952 #[serde(default, skip_serializing_if = "Option::is_none")]
954 pub credentials: Option<String>,
955}
956
957#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
959#[serde(rename_all = "camelCase")]
960pub struct TaskPushNotificationConfig {
961 pub id: String,
963 pub task_id: String,
965 pub push_notification_config: PushNotificationConfig,
967 #[serde(default, skip_serializing_if = "Option::is_none")]
969 pub tenant: Option<String>,
970}
971
972#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
974#[serde(rename_all = "camelCase")]
975pub struct CreateTaskPushNotificationConfigRequest {
976 pub task_id: String,
978 pub config_id: String,
980 pub push_notification_config: PushNotificationConfig,
982 #[serde(default, skip_serializing_if = "Option::is_none")]
984 pub tenant: Option<String>,
985}
986
987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
989#[serde(rename_all = "camelCase")]
990pub struct GetTaskPushNotificationConfigRequest {
991 pub id: String,
993 pub task_id: String,
995 #[serde(default, skip_serializing_if = "Option::is_none")]
997 pub tenant: Option<String>,
998}
999
1000#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1002#[serde(rename_all = "camelCase")]
1003pub struct ListTaskPushNotificationConfigRequest {
1004 pub task_id: String,
1006 #[serde(default, skip_serializing_if = "Option::is_none")]
1008 pub page_size: Option<u32>,
1009 #[serde(default, skip_serializing_if = "Option::is_none")]
1011 pub page_token: Option<String>,
1012 #[serde(default, skip_serializing_if = "Option::is_none")]
1014 pub tenant: Option<String>,
1015}
1016
1017#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1019#[serde(rename_all = "camelCase")]
1020pub struct ListTaskPushNotificationConfigResponse {
1021 pub configs: Vec<TaskPushNotificationConfig>,
1023 #[serde(default)]
1025 pub next_page_token: String,
1026}
1027
1028#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1030#[serde(rename_all = "camelCase")]
1031pub struct DeleteTaskPushNotificationConfigRequest {
1032 pub id: String,
1034 pub task_id: String,
1036 #[serde(default, skip_serializing_if = "Option::is_none")]
1038 pub tenant: Option<String>,
1039}
1040
1041#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1043#[serde(rename_all = "camelCase")]
1044pub struct GetExtendedAgentCardRequest {
1045 #[serde(default, skip_serializing_if = "Option::is_none")]
1047 pub tenant: Option<String>,
1048}
1049
1050#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1054#[serde(rename_all = "camelCase")]
1055pub enum StreamResponse {
1056 Task(Task),
1058 Message(Message),
1060 StatusUpdate(TaskStatusUpdateEvent),
1062 ArtifactUpdate(TaskArtifactUpdateEvent),
1064}
1065
1066#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1068#[serde(rename_all = "camelCase")]
1069pub struct TaskStatusUpdateEvent {
1070 #[serde(default = "default_status_update_kind")]
1072 pub kind: String,
1073 pub task_id: String,
1075 pub context_id: String,
1077 pub status: TaskStatus,
1079 #[serde(rename = "final", default)]
1081 pub is_final: bool,
1082 #[serde(default, skip_serializing_if = "Option::is_none")]
1084 pub metadata: Option<serde_json::Value>,
1085}
1086
1087#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1089#[serde(rename_all = "camelCase")]
1090pub struct TaskArtifactUpdateEvent {
1091 #[serde(default = "default_artifact_update_kind")]
1093 pub kind: String,
1094 pub task_id: String,
1096 pub context_id: String,
1098 pub artifact: Artifact,
1100 #[serde(default, skip_serializing_if = "Option::is_none")]
1102 pub append: Option<bool>,
1103 #[serde(default, skip_serializing_if = "Option::is_none")]
1105 pub last_chunk: Option<bool>,
1106 #[serde(default, skip_serializing_if = "Option::is_none")]
1108 pub metadata: Option<serde_json::Value>,
1109}
1110
1111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1116#[serde(rename_all = "camelCase")]
1117pub enum StreamingMessageResult {
1118 StatusUpdate(TaskStatusUpdateEvent),
1119 ArtifactUpdate(TaskArtifactUpdateEvent),
1120 Task(Task),
1121 Message(Message),
1122}
1123
1124pub fn new_message(role: Role, text: &str, context_id: Option<String>) -> Message {
1128 Message {
1129 kind: "message".to_string(),
1130 message_id: Uuid::new_v4().to_string(),
1131 context_id,
1132 task_id: None,
1133 role,
1134 parts: vec![Part::text(text)],
1135 metadata: None,
1136 extensions: vec![],
1137 reference_task_ids: None,
1138 }
1139}
1140
1141pub fn completed_task_with_text(user_message: Message, reply_text: &str) -> Task {
1143 let context_id = user_message
1144 .context_id
1145 .clone()
1146 .unwrap_or_else(|| Uuid::new_v4().to_string());
1147 let task_id = Uuid::new_v4().to_string();
1148 let agent_msg = new_message(Role::Agent, reply_text, Some(context_id.clone()));
1149
1150 Task {
1151 kind: "task".to_string(),
1152 id: task_id,
1153 context_id,
1154 status: TaskStatus {
1155 state: TaskState::Completed,
1156 message: Some(agent_msg.clone()),
1157 timestamp: Some(chrono::Utc::now().to_rfc3339()),
1158 },
1159 history: Some(vec![user_message, agent_msg]),
1160 artifacts: None,
1161 metadata: None,
1162 }
1163}
1164
1165pub fn now_iso8601() -> String {
1167 chrono::Utc::now().to_rfc3339()
1168}
1169
1170pub fn validate_task_id(id: &str) -> bool {
1172 Uuid::parse_str(id).is_ok()
1173}
1174
1175#[cfg(test)]
1176mod tests {
1177 use super::*;
1178
1179 #[test]
1180 fn jsonrpc_helpers_round_trip() {
1181 let resp = success(serde_json::json!(1), serde_json::json!({"ok": true}));
1182 assert_eq!(resp.jsonrpc, "2.0");
1183 assert!(resp.error.is_none());
1184 assert!(resp.result.is_some());
1185 }
1186
1187 #[test]
1188 fn task_state_is_terminal() {
1189 assert!(TaskState::Completed.is_terminal());
1190 assert!(TaskState::Failed.is_terminal());
1191 assert!(TaskState::Canceled.is_terminal());
1192 assert!(TaskState::Rejected.is_terminal());
1193 assert!(!TaskState::Working.is_terminal());
1194 assert!(!TaskState::Submitted.is_terminal());
1195 assert!(!TaskState::InputRequired.is_terminal());
1196 }
1197
1198 #[test]
1199 fn task_state_serialization() {
1200 let state = TaskState::Working;
1201 let json = serde_json::to_string(&state).unwrap();
1202 assert_eq!(json, r#""TASK_STATE_WORKING""#);
1203
1204 let parsed: TaskState = serde_json::from_str(&json).unwrap();
1205 assert_eq!(parsed, TaskState::Working);
1206 }
1207
1208 #[test]
1209 fn role_serialization() {
1210 let role = Role::User;
1211 let json = serde_json::to_string(&role).unwrap();
1212 assert_eq!(json, r#""ROLE_USER""#);
1213 }
1214
1215 #[test]
1216 fn message_serialization() {
1217 let msg = new_message(Role::User, "hello", Some("ctx-123".to_string()));
1218 let json = serde_json::to_string(&msg).unwrap();
1219 let parsed: Message = serde_json::from_str(&json).unwrap();
1220 assert_eq!(parsed.role, Role::User);
1221 assert_eq!(parsed.parts.len(), 1);
1222 assert_eq!(parsed.parts[0].as_text(), Some("hello"));
1223
1224 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1226 assert!(value.get("messageId").is_some());
1227 assert!(value.get("contextId").is_some());
1228 }
1229
1230 #[test]
1231 fn task_serialization() {
1232 let user_msg = new_message(Role::User, "test", None);
1233 let task = completed_task_with_text(user_msg, "response");
1234 let json = serde_json::to_string(&task).unwrap();
1235 let parsed: Task = serde_json::from_str(&json).unwrap();
1236 assert_eq!(parsed.status.state, TaskState::Completed);
1237 assert!(parsed.history.is_some());
1238 assert_eq!(parsed.history.unwrap().len(), 2);
1239
1240 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1242 assert!(value.get("contextId").is_some());
1243 }
1244
1245 #[test]
1246 fn part_text_serialization() {
1247 let part = Part::text("hello");
1248 let json = serde_json::to_string(&part).unwrap();
1249 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1250 assert_eq!(value.get("kind").unwrap().as_str().unwrap(), "text");
1251 assert_eq!(value.get("text").unwrap().as_str().unwrap(), "hello");
1252 }
1253
1254 #[test]
1255 fn part_text_round_trip() {
1256 let part = Part::text("hello");
1257 let json = serde_json::to_string(&part).unwrap();
1258 let parsed: Part = serde_json::from_str(&json).unwrap();
1259 assert_eq!(parsed, part);
1260 assert_eq!(parsed.as_text(), Some("hello"));
1261 }
1262
1263 #[test]
1264 fn part_file_uri_serialization() {
1265 let part = Part::file_uri("https://example.com/file.pdf", "application/pdf");
1266 let json = serde_json::to_string(&part).unwrap();
1267 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1268 assert_eq!(value.get("kind").unwrap().as_str().unwrap(), "file");
1269 let file = value.get("file").unwrap();
1270 assert_eq!(
1271 file.get("uri").unwrap().as_str().unwrap(),
1272 "https://example.com/file.pdf"
1273 );
1274 assert_eq!(
1275 file.get("mimeType").unwrap().as_str().unwrap(),
1276 "application/pdf"
1277 );
1278 }
1279
1280 #[test]
1281 fn part_data_serialization() {
1282 let part = Part::data(serde_json::json!({"key": "value"}));
1283 let json = serde_json::to_string(&part).unwrap();
1284 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1285 assert_eq!(value.get("kind").unwrap().as_str().unwrap(), "data");
1286 assert_eq!(
1287 value.get("data").unwrap(),
1288 &serde_json::json!({"key": "value"})
1289 );
1290 }
1291
1292 #[test]
1293 fn part_deserialization_from_wire_format() {
1294 let text: Part = serde_json::from_str(r#"{"text":"hello"}"#).unwrap();
1296 assert_eq!(text.as_text(), Some("hello"));
1297
1298 let file: Part = serde_json::from_str(
1299 r#"{"file":{"uri":"https://example.com/f.pdf","mimeType":"application/pdf"}}"#,
1300 )
1301 .unwrap();
1302 match &file {
1303 Part::File { file, .. } => {
1304 assert_eq!(file.uri.as_deref(), Some("https://example.com/f.pdf"));
1305 assert_eq!(file.mime_type.as_deref(), Some("application/pdf"));
1306 }
1307 _ => panic!("expected File part"),
1308 }
1309
1310 let data: Part = serde_json::from_str(r#"{"data":{"k":"v"}}"#).unwrap();
1311 match &data {
1312 Part::Data { data, .. } => assert_eq!(data, &serde_json::json!({"k": "v"})),
1313 _ => panic!("expected Data part"),
1314 }
1315
1316 let text_v03: Part =
1318 serde_json::from_str(r#"{"kind":"text","text":"hello v03"}"#).unwrap();
1319 assert_eq!(text_v03.as_text(), Some("hello v03"));
1320 }
1321
1322 #[test]
1323 fn agent_card_with_security() {
1324 let card = AgentCard {
1325 name: "Test Agent".to_string(),
1326 description: "Test description".to_string(),
1327 supported_interfaces: vec![AgentInterface {
1328 url: "https://example.com/v1/rpc".to_string(),
1329 protocol_binding: "JSONRPC".to_string(),
1330 protocol_version: PROTOCOL_VERSION.to_string(),
1331 tenant: None,
1332 }],
1333 provider: Some(AgentProvider {
1334 organization: "Test Org".to_string(),
1335 url: "https://example.com".to_string(),
1336 }),
1337 version: PROTOCOL_VERSION.to_string(),
1338 documentation_url: None,
1339 capabilities: AgentCapabilities::default(),
1340 security_schemes: {
1341 let mut m = HashMap::new();
1342 m.insert(
1343 "apiKey".to_string(),
1344 SecurityScheme::ApiKeySecurityScheme(ApiKeySecurityScheme {
1345 name: "X-API-Key".to_string(),
1346 location: "header".to_string(),
1347 description: None,
1348 }),
1349 );
1350 m
1351 },
1352 security_requirements: vec![],
1353 default_input_modes: vec![],
1354 default_output_modes: vec![],
1355 skills: vec![],
1356 signatures: vec![],
1357 icon_url: None,
1358 };
1359
1360 let json = serde_json::to_string(&card).unwrap();
1361 let parsed: AgentCard = serde_json::from_str(&json).unwrap();
1362 assert_eq!(parsed.name, "Test Agent");
1363 assert_eq!(parsed.security_schemes.len(), 1);
1364 assert_eq!(parsed.endpoint(), Some("https://example.com/v1/rpc"));
1365
1366 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1368 assert!(value.get("supportedInterfaces").is_some());
1369 assert!(value.get("securitySchemes").is_some());
1370 assert!(value.get("securityRequirements").is_some());
1371 }
1372
1373 #[test]
1374 fn validate_task_id_helper() {
1375 let valid_uuid = Uuid::new_v4().to_string();
1376 assert!(validate_task_id(&valid_uuid));
1377 assert!(!validate_task_id("not-a-uuid"));
1378 }
1379
1380 #[test]
1381 fn error_codes() {
1382 use errors::*;
1383 assert_eq!(message_for_code(TASK_NOT_FOUND), "Task not found");
1384 assert_eq!(
1385 message_for_code(VERSION_NOT_SUPPORTED),
1386 "Protocol version not supported"
1387 );
1388 assert_eq!(
1389 message_for_code(INVALID_AGENT_RESPONSE),
1390 "Invalid agent response"
1391 );
1392 assert_eq!(
1393 message_for_code(EXTENSION_SUPPORT_REQUIRED),
1394 "Extension support required"
1395 );
1396 assert_eq!(message_for_code(999), "Unknown error");
1397 }
1398
1399 #[test]
1400 fn send_message_result_serialization() {
1401 let task = Task {
1402 kind: "task".to_string(),
1403 id: "t-1".to_string(),
1404 context_id: "ctx-1".to_string(),
1405 status: TaskStatus {
1406 state: TaskState::Completed,
1407 message: None,
1408 timestamp: None,
1409 },
1410 artifacts: None,
1411 history: None,
1412 metadata: None,
1413 };
1414
1415 let result = SendMessageResult::Task(task.clone());
1417 let json = serde_json::to_string(&result).unwrap();
1418 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1419 assert!(value.get("task").is_some(), "should have task wrapper key");
1420 let inner = value.get("task").unwrap();
1421 assert_eq!(inner.get("id").unwrap().as_str().unwrap(), "t-1");
1422
1423 let parsed: SendMessageResult = serde_json::from_str(&json).unwrap();
1425 assert_eq!(parsed, SendMessageResult::Task(task));
1426 }
1427
1428 #[test]
1429 fn stream_response_serialization() {
1430 let event = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
1431 kind: "status-update".to_string(),
1432 task_id: "t-1".to_string(),
1433 context_id: "ctx-1".to_string(),
1434 status: TaskStatus {
1435 state: TaskState::Working,
1436 message: None,
1437 timestamp: None,
1438 },
1439 is_final: false,
1440 metadata: None,
1441 });
1442 let json = serde_json::to_string(&event).unwrap();
1443 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1444 assert!(value.get("statusUpdate").is_some());
1445 }
1446
1447 #[test]
1448 fn push_notification_config_serialization() {
1449 let config = PushNotificationConfig {
1450 id: Some("cfg-1".to_string()),
1451 url: "https://example.com/webhook".to_string(),
1452 token: Some("secret".to_string()),
1453 authentication: Some(AuthenticationInfo {
1454 scheme: "bearer".to_string(),
1455 credentials: Some("token123".to_string()),
1456 }),
1457 };
1458 let json = serde_json::to_string(&config).unwrap();
1459 let parsed: PushNotificationConfig = serde_json::from_str(&json).unwrap();
1460 assert_eq!(parsed.url, "https://example.com/webhook");
1461 assert_eq!(parsed.authentication.unwrap().scheme, "bearer");
1462 }
1463}