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)]
538#[non_exhaustive]
539pub enum Role {
540 #[serde(rename = "unspecified", alias = "ROLE_UNSPECIFIED")]
541 Unspecified,
542 #[serde(rename = "user", alias = "ROLE_USER")]
543 User,
544 #[serde(rename = "agent", alias = "ROLE_AGENT")]
545 Agent,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
550#[serde(rename_all = "camelCase")]
551pub struct Message {
552 #[serde(default = "default_message_kind")]
554 pub kind: String,
555 pub message_id: String,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
559 pub context_id: Option<String>,
560 #[serde(default, skip_serializing_if = "Option::is_none")]
562 pub task_id: Option<String>,
563 pub role: Role,
565 pub parts: Vec<Part>,
567 #[serde(default, skip_serializing_if = "Option::is_none")]
569 pub metadata: Option<serde_json::Value>,
570 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "nullable_vec")]
572 pub extensions: Vec<String>,
573 #[serde(default, skip_serializing_if = "Option::is_none")]
575 pub reference_task_ids: Option<Vec<String>>,
576}
577
578fn default_message_kind() -> String {
579 "message".to_string()
580}
581
582fn default_task_kind() -> String {
583 "task".to_string()
584}
585
586fn default_status_update_kind() -> String {
587 "status-update".to_string()
588}
589
590fn default_artifact_update_kind() -> String {
591 "artifact-update".to_string()
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
596#[serde(rename_all = "camelCase")]
597pub struct Artifact {
598 pub artifact_id: String,
600 #[serde(default, skip_serializing_if = "Option::is_none")]
602 pub name: Option<String>,
603 #[serde(default, skip_serializing_if = "Option::is_none")]
605 pub description: Option<String>,
606 pub parts: Vec<Part>,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
610 pub metadata: Option<serde_json::Value>,
611 #[serde(default, skip_serializing_if = "Vec::is_empty", deserialize_with = "nullable_vec")]
613 pub extensions: Vec<String>,
614}
615
616#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
619#[non_exhaustive]
620pub enum TaskState {
621 #[serde(rename = "unspecified", alias = "TASK_STATE_UNSPECIFIED")]
622 Unspecified,
623 #[serde(rename = "submitted", alias = "TASK_STATE_SUBMITTED")]
624 Submitted,
625 #[serde(rename = "working", alias = "TASK_STATE_WORKING")]
626 Working,
627 #[serde(rename = "completed", alias = "TASK_STATE_COMPLETED")]
628 Completed,
629 #[serde(rename = "failed", alias = "TASK_STATE_FAILED")]
630 Failed,
631 #[serde(rename = "canceled", alias = "TASK_STATE_CANCELED")]
632 Canceled,
633 #[serde(rename = "input-required", alias = "TASK_STATE_INPUT_REQUIRED")]
634 InputRequired,
635 #[serde(rename = "rejected", alias = "TASK_STATE_REJECTED")]
636 Rejected,
637 #[serde(rename = "auth-required", alias = "TASK_STATE_AUTH_REQUIRED")]
638 AuthRequired,
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
643#[serde(rename_all = "camelCase")]
644pub struct TaskStatus {
645 pub state: TaskState,
647 #[serde(default, skip_serializing_if = "Option::is_none")]
649 pub message: Option<Message>,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
652 pub timestamp: Option<String>,
653}
654
655#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
657#[serde(rename_all = "camelCase")]
658pub struct Task {
659 #[serde(default = "default_task_kind")]
661 pub kind: String,
662 pub id: String,
664 pub context_id: String,
666 pub status: TaskStatus,
668 #[serde(default, skip_serializing_if = "Option::is_none")]
670 pub artifacts: Option<Vec<Artifact>>,
671 #[serde(default, skip_serializing_if = "Option::is_none")]
673 pub history: Option<Vec<Message>>,
674 #[serde(default, skip_serializing_if = "Option::is_none")]
676 pub metadata: Option<serde_json::Value>,
677}
678
679impl TaskState {
680 pub fn is_terminal(&self) -> bool {
682 matches!(
683 self,
684 TaskState::Completed | TaskState::Failed | TaskState::Canceled | TaskState::Rejected
685 )
686 }
687}
688
689#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
696#[serde(rename_all = "camelCase")]
697pub enum SendMessageResponse {
698 Task(Task),
699 Message(Message),
700}
701
702#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
707#[serde(rename_all = "camelCase")]
708pub enum SendMessageResult {
709 Task(Task),
710 Message(Message),
711}
712
713#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
716pub struct JsonRpcRequest {
717 pub jsonrpc: String,
718 pub method: String,
719 #[serde(default)]
720 pub params: Option<serde_json::Value>,
721 pub id: serde_json::Value,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
725pub struct JsonRpcResponse {
726 pub jsonrpc: String,
727 pub id: serde_json::Value,
728 #[serde(skip_serializing_if = "Option::is_none")]
729 pub result: Option<serde_json::Value>,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 pub error: Option<JsonRpcError>,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
735pub struct JsonRpcError {
736 pub code: i32,
737 pub message: String,
738 #[serde(skip_serializing_if = "Option::is_none")]
739 pub data: Option<serde_json::Value>,
740}
741
742pub mod errors {
744 pub const PARSE_ERROR: i32 = -32700;
746 pub const INVALID_REQUEST: i32 = -32600;
747 pub const METHOD_NOT_FOUND: i32 = -32601;
748 pub const INVALID_PARAMS: i32 = -32602;
749 pub const INTERNAL_ERROR: i32 = -32603;
750
751 pub const TASK_NOT_FOUND: i32 = -32001;
753 pub const TASK_NOT_CANCELABLE: i32 = -32002;
754 pub const PUSH_NOTIFICATION_NOT_SUPPORTED: i32 = -32003;
755 pub const UNSUPPORTED_OPERATION: i32 = -32004;
756 pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
757 pub const EXTENDED_AGENT_CARD_NOT_CONFIGURED: i32 = -32006;
758 pub const VERSION_NOT_SUPPORTED: i32 = -32007;
759 pub const INVALID_AGENT_RESPONSE: i32 = -32008;
760 pub const EXTENSION_SUPPORT_REQUIRED: i32 = -32009;
761
762 pub fn message_for_code(code: i32) -> &'static str {
763 match code {
764 PARSE_ERROR => "Parse error",
765 INVALID_REQUEST => "Invalid request",
766 METHOD_NOT_FOUND => "Method not found",
767 INVALID_PARAMS => "Invalid params",
768 INTERNAL_ERROR => "Internal error",
769 TASK_NOT_FOUND => "Task not found",
770 TASK_NOT_CANCELABLE => "Task not cancelable",
771 PUSH_NOTIFICATION_NOT_SUPPORTED => "Push notifications not supported",
772 UNSUPPORTED_OPERATION => "Unsupported operation",
773 CONTENT_TYPE_NOT_SUPPORTED => "Content type not supported",
774 EXTENDED_AGENT_CARD_NOT_CONFIGURED => "Extended agent card not configured",
775 VERSION_NOT_SUPPORTED => "Protocol version not supported",
776 INVALID_AGENT_RESPONSE => "Invalid agent response",
777 EXTENSION_SUPPORT_REQUIRED => "Extension support required",
778 _ => "Unknown error",
779 }
780 }
781}
782
783pub fn success(id: serde_json::Value, result: serde_json::Value) -> JsonRpcResponse {
784 JsonRpcResponse {
785 jsonrpc: "2.0".to_string(),
786 id,
787 result: Some(result),
788 error: None,
789 }
790}
791
792pub fn error(
793 id: serde_json::Value,
794 code: i32,
795 message: &str,
796 data: Option<serde_json::Value>,
797) -> JsonRpcResponse {
798 JsonRpcResponse {
799 jsonrpc: "2.0".to_string(),
800 id,
801 result: None,
802 error: Some(JsonRpcError {
803 code,
804 message: message.to_string(),
805 data,
806 }),
807 }
808}
809
810#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
814#[serde(rename_all = "camelCase")]
815pub struct SendMessageRequest {
816 #[serde(default, skip_serializing_if = "Option::is_none")]
818 pub tenant: Option<String>,
819 pub message: Message,
821 #[serde(default, skip_serializing_if = "Option::is_none")]
823 pub configuration: Option<SendMessageConfiguration>,
824 #[serde(default, skip_serializing_if = "Option::is_none")]
826 pub metadata: Option<serde_json::Value>,
827}
828
829#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
831#[serde(rename_all = "camelCase")]
832pub struct SendMessageConfiguration {
833 #[serde(default, skip_serializing_if = "Option::is_none")]
835 pub accepted_output_modes: Option<Vec<String>>,
836 #[serde(default, skip_serializing_if = "Option::is_none")]
838 pub push_notification_config: Option<PushNotificationConfig>,
839 #[serde(default, skip_serializing_if = "Option::is_none")]
841 pub history_length: Option<u32>,
842 #[serde(default, skip_serializing_if = "Option::is_none")]
844 pub blocking: Option<bool>,
845 #[serde(default, skip_serializing_if = "Option::is_none")]
848 pub return_immediately: Option<bool>,
849}
850
851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
853#[serde(rename_all = "camelCase")]
854pub struct GetTaskRequest {
855 pub id: String,
857 #[serde(default, skip_serializing_if = "Option::is_none")]
859 pub history_length: Option<u32>,
860 #[serde(default, skip_serializing_if = "Option::is_none")]
862 pub tenant: Option<String>,
863}
864
865#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
867#[serde(rename_all = "camelCase")]
868pub struct CancelTaskRequest {
869 pub id: String,
871 #[serde(default, skip_serializing_if = "Option::is_none")]
873 pub tenant: Option<String>,
874}
875
876#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
878#[serde(rename_all = "camelCase")]
879pub struct ListTasksRequest {
880 #[serde(default, skip_serializing_if = "Option::is_none")]
882 pub context_id: Option<String>,
883 #[serde(default, skip_serializing_if = "Option::is_none")]
885 pub status: Option<TaskState>,
886 #[serde(default, skip_serializing_if = "Option::is_none")]
888 pub page_size: Option<u32>,
889 #[serde(default, skip_serializing_if = "Option::is_none")]
891 pub page_token: Option<String>,
892 #[serde(default, skip_serializing_if = "Option::is_none")]
894 pub history_length: Option<u32>,
895 #[serde(default, skip_serializing_if = "Option::is_none")]
897 pub status_timestamp_after: Option<i64>,
898 #[serde(default, skip_serializing_if = "Option::is_none")]
900 pub include_artifacts: Option<bool>,
901 #[serde(default, skip_serializing_if = "Option::is_none")]
903 pub tenant: Option<String>,
904}
905
906#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
908#[serde(rename_all = "camelCase")]
909pub struct TaskListResponse {
910 pub tasks: Vec<Task>,
912 pub next_page_token: String,
914 pub page_size: u32,
916 pub total_size: u32,
918}
919
920#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
922#[serde(rename_all = "camelCase")]
923pub struct SubscribeToTaskRequest {
924 pub id: String,
926 #[serde(default, skip_serializing_if = "Option::is_none")]
928 pub tenant: Option<String>,
929}
930
931#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
933#[serde(rename_all = "camelCase")]
934pub struct PushNotificationConfig {
935 #[serde(default, skip_serializing_if = "Option::is_none")]
937 pub id: Option<String>,
938 pub url: String,
940 #[serde(default, skip_serializing_if = "Option::is_none")]
942 pub token: Option<String>,
943 #[serde(default, skip_serializing_if = "Option::is_none")]
945 pub authentication: Option<AuthenticationInfo>,
946}
947
948#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
950#[serde(rename_all = "camelCase")]
951pub struct AuthenticationInfo {
952 pub scheme: String,
954 #[serde(default, skip_serializing_if = "Option::is_none")]
956 pub credentials: Option<String>,
957}
958
959#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
961#[serde(rename_all = "camelCase")]
962pub struct TaskPushNotificationConfig {
963 pub id: String,
965 pub task_id: String,
967 pub push_notification_config: PushNotificationConfig,
969 #[serde(default, skip_serializing_if = "Option::is_none")]
971 pub tenant: Option<String>,
972}
973
974#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
976#[serde(rename_all = "camelCase")]
977pub struct CreateTaskPushNotificationConfigRequest {
978 pub task_id: String,
980 pub config_id: String,
982 pub push_notification_config: PushNotificationConfig,
984 #[serde(default, skip_serializing_if = "Option::is_none")]
986 pub tenant: Option<String>,
987}
988
989#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
991#[serde(rename_all = "camelCase")]
992pub struct GetTaskPushNotificationConfigRequest {
993 pub id: String,
995 pub task_id: String,
997 #[serde(default, skip_serializing_if = "Option::is_none")]
999 pub tenant: Option<String>,
1000}
1001
1002#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1004#[serde(rename_all = "camelCase")]
1005pub struct ListTaskPushNotificationConfigRequest {
1006 pub task_id: String,
1008 #[serde(default, skip_serializing_if = "Option::is_none")]
1010 pub page_size: Option<u32>,
1011 #[serde(default, skip_serializing_if = "Option::is_none")]
1013 pub page_token: Option<String>,
1014 #[serde(default, skip_serializing_if = "Option::is_none")]
1016 pub tenant: Option<String>,
1017}
1018
1019#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1021#[serde(rename_all = "camelCase")]
1022pub struct ListTaskPushNotificationConfigResponse {
1023 pub configs: Vec<TaskPushNotificationConfig>,
1025 #[serde(default)]
1027 pub next_page_token: String,
1028}
1029
1030#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1032#[serde(rename_all = "camelCase")]
1033pub struct DeleteTaskPushNotificationConfigRequest {
1034 pub id: String,
1036 pub task_id: String,
1038 #[serde(default, skip_serializing_if = "Option::is_none")]
1040 pub tenant: Option<String>,
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1045#[serde(rename_all = "camelCase")]
1046pub struct GetExtendedAgentCardRequest {
1047 #[serde(default, skip_serializing_if = "Option::is_none")]
1049 pub tenant: Option<String>,
1050}
1051
1052#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1056#[serde(rename_all = "camelCase")]
1057pub enum StreamResponse {
1058 Task(Task),
1060 Message(Message),
1062 StatusUpdate(TaskStatusUpdateEvent),
1064 ArtifactUpdate(TaskArtifactUpdateEvent),
1066}
1067
1068#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1070#[serde(rename_all = "camelCase")]
1071pub struct TaskStatusUpdateEvent {
1072 #[serde(default = "default_status_update_kind")]
1074 pub kind: String,
1075 pub task_id: String,
1077 pub context_id: String,
1079 pub status: TaskStatus,
1081 #[serde(rename = "final", default)]
1083 pub is_final: bool,
1084 #[serde(default, skip_serializing_if = "Option::is_none")]
1086 pub metadata: Option<serde_json::Value>,
1087}
1088
1089#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1091#[serde(rename_all = "camelCase")]
1092pub struct TaskArtifactUpdateEvent {
1093 #[serde(default = "default_artifact_update_kind")]
1095 pub kind: String,
1096 pub task_id: String,
1098 pub context_id: String,
1100 pub artifact: Artifact,
1102 #[serde(default, skip_serializing_if = "Option::is_none")]
1104 pub append: Option<bool>,
1105 #[serde(default, skip_serializing_if = "Option::is_none")]
1107 pub last_chunk: Option<bool>,
1108 #[serde(default, skip_serializing_if = "Option::is_none")]
1110 pub metadata: Option<serde_json::Value>,
1111}
1112
1113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1118#[serde(rename_all = "camelCase")]
1119pub enum StreamingMessageResult {
1120 StatusUpdate(TaskStatusUpdateEvent),
1121 ArtifactUpdate(TaskArtifactUpdateEvent),
1122 Task(Task),
1123 Message(Message),
1124}
1125
1126pub fn new_message(role: Role, text: &str, context_id: Option<String>) -> Message {
1130 Message {
1131 kind: "message".to_string(),
1132 message_id: Uuid::new_v4().to_string(),
1133 context_id,
1134 task_id: None,
1135 role,
1136 parts: vec![Part::text(text)],
1137 metadata: None,
1138 extensions: vec![],
1139 reference_task_ids: None,
1140 }
1141}
1142
1143pub fn completed_task_with_text(user_message: Message, reply_text: &str) -> Task {
1145 let context_id = user_message
1146 .context_id
1147 .clone()
1148 .unwrap_or_else(|| Uuid::new_v4().to_string());
1149 let task_id = Uuid::new_v4().to_string();
1150 let agent_msg = new_message(Role::Agent, reply_text, Some(context_id.clone()));
1151
1152 Task {
1153 kind: "task".to_string(),
1154 id: task_id,
1155 context_id,
1156 status: TaskStatus {
1157 state: TaskState::Completed,
1158 message: Some(agent_msg.clone()),
1159 timestamp: Some(chrono::Utc::now().to_rfc3339()),
1160 },
1161 history: Some(vec![user_message, agent_msg]),
1162 artifacts: None,
1163 metadata: None,
1164 }
1165}
1166
1167pub fn now_iso8601() -> String {
1169 chrono::Utc::now().to_rfc3339()
1170}
1171
1172pub fn validate_task_id(id: &str) -> bool {
1174 Uuid::parse_str(id).is_ok()
1175}
1176
1177#[cfg(test)]
1178mod tests {
1179 use super::*;
1180
1181 #[test]
1182 fn jsonrpc_helpers_round_trip() {
1183 let resp = success(serde_json::json!(1), serde_json::json!({"ok": true}));
1184 assert_eq!(resp.jsonrpc, "2.0");
1185 assert!(resp.error.is_none());
1186 assert!(resp.result.is_some());
1187 }
1188
1189 #[test]
1190 fn task_state_is_terminal() {
1191 assert!(TaskState::Completed.is_terminal());
1192 assert!(TaskState::Failed.is_terminal());
1193 assert!(TaskState::Canceled.is_terminal());
1194 assert!(TaskState::Rejected.is_terminal());
1195 assert!(!TaskState::Working.is_terminal());
1196 assert!(!TaskState::Submitted.is_terminal());
1197 assert!(!TaskState::InputRequired.is_terminal());
1198 }
1199
1200 #[test]
1201 fn task_state_serialization() {
1202 let state = TaskState::Working;
1203 let json = serde_json::to_string(&state).unwrap();
1204 assert_eq!(json, r#""working""#);
1205
1206 let parsed: TaskState = serde_json::from_str(&json).unwrap();
1208 assert_eq!(parsed, TaskState::Working);
1209
1210 let parsed: TaskState = serde_json::from_str(r#""TASK_STATE_WORKING""#).unwrap();
1212 assert_eq!(parsed, TaskState::Working);
1213 }
1214
1215 #[test]
1216 fn role_serialization() {
1217 let role = Role::User;
1218 let json = serde_json::to_string(&role).unwrap();
1219 assert_eq!(json, r#""user""#);
1220
1221 let parsed: Role = serde_json::from_str(r#""ROLE_USER""#).unwrap();
1223 assert_eq!(parsed, Role::User);
1224 }
1225
1226 #[test]
1227 fn message_serialization() {
1228 let msg = new_message(Role::User, "hello", Some("ctx-123".to_string()));
1229 let json = serde_json::to_string(&msg).unwrap();
1230 let parsed: Message = serde_json::from_str(&json).unwrap();
1231 assert_eq!(parsed.role, Role::User);
1232 assert_eq!(parsed.parts.len(), 1);
1233 assert_eq!(parsed.parts[0].as_text(), Some("hello"));
1234
1235 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1237 assert!(value.get("messageId").is_some());
1238 assert!(value.get("contextId").is_some());
1239 }
1240
1241 #[test]
1242 fn task_serialization() {
1243 let user_msg = new_message(Role::User, "test", None);
1244 let task = completed_task_with_text(user_msg, "response");
1245 let json = serde_json::to_string(&task).unwrap();
1246 let parsed: Task = serde_json::from_str(&json).unwrap();
1247 assert_eq!(parsed.status.state, TaskState::Completed);
1248 assert!(parsed.history.is_some());
1249 assert_eq!(parsed.history.unwrap().len(), 2);
1250
1251 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1253 assert!(value.get("contextId").is_some());
1254 }
1255
1256 #[test]
1257 fn part_text_serialization() {
1258 let part = Part::text("hello");
1259 let json = serde_json::to_string(&part).unwrap();
1260 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1261 assert_eq!(value.get("kind").unwrap().as_str().unwrap(), "text");
1262 assert_eq!(value.get("text").unwrap().as_str().unwrap(), "hello");
1263 }
1264
1265 #[test]
1266 fn part_text_round_trip() {
1267 let part = Part::text("hello");
1268 let json = serde_json::to_string(&part).unwrap();
1269 let parsed: Part = serde_json::from_str(&json).unwrap();
1270 assert_eq!(parsed, part);
1271 assert_eq!(parsed.as_text(), Some("hello"));
1272 }
1273
1274 #[test]
1275 fn part_file_uri_serialization() {
1276 let part = Part::file_uri("https://example.com/file.pdf", "application/pdf");
1277 let json = serde_json::to_string(&part).unwrap();
1278 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1279 assert_eq!(value.get("kind").unwrap().as_str().unwrap(), "file");
1280 let file = value.get("file").unwrap();
1281 assert_eq!(
1282 file.get("uri").unwrap().as_str().unwrap(),
1283 "https://example.com/file.pdf"
1284 );
1285 assert_eq!(
1286 file.get("mimeType").unwrap().as_str().unwrap(),
1287 "application/pdf"
1288 );
1289 }
1290
1291 #[test]
1292 fn part_data_serialization() {
1293 let part = Part::data(serde_json::json!({"key": "value"}));
1294 let json = serde_json::to_string(&part).unwrap();
1295 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1296 assert_eq!(value.get("kind").unwrap().as_str().unwrap(), "data");
1297 assert_eq!(
1298 value.get("data").unwrap(),
1299 &serde_json::json!({"key": "value"})
1300 );
1301 }
1302
1303 #[test]
1304 fn part_deserialization_from_wire_format() {
1305 let text: Part = serde_json::from_str(r#"{"text":"hello"}"#).unwrap();
1307 assert_eq!(text.as_text(), Some("hello"));
1308
1309 let file: Part = serde_json::from_str(
1310 r#"{"file":{"uri":"https://example.com/f.pdf","mimeType":"application/pdf"}}"#,
1311 )
1312 .unwrap();
1313 match &file {
1314 Part::File { file, .. } => {
1315 assert_eq!(file.uri.as_deref(), Some("https://example.com/f.pdf"));
1316 assert_eq!(file.mime_type.as_deref(), Some("application/pdf"));
1317 }
1318 _ => panic!("expected File part"),
1319 }
1320
1321 let data: Part = serde_json::from_str(r#"{"data":{"k":"v"}}"#).unwrap();
1322 match &data {
1323 Part::Data { data, .. } => assert_eq!(data, &serde_json::json!({"k": "v"})),
1324 _ => panic!("expected Data part"),
1325 }
1326
1327 let text_v03: Part =
1329 serde_json::from_str(r#"{"kind":"text","text":"hello v03"}"#).unwrap();
1330 assert_eq!(text_v03.as_text(), Some("hello v03"));
1331 }
1332
1333 #[test]
1334 fn agent_card_with_security() {
1335 let card = AgentCard {
1336 name: "Test Agent".to_string(),
1337 description: "Test description".to_string(),
1338 supported_interfaces: vec![AgentInterface {
1339 url: "https://example.com/v1/rpc".to_string(),
1340 protocol_binding: "JSONRPC".to_string(),
1341 protocol_version: PROTOCOL_VERSION.to_string(),
1342 tenant: None,
1343 }],
1344 provider: Some(AgentProvider {
1345 organization: "Test Org".to_string(),
1346 url: "https://example.com".to_string(),
1347 }),
1348 version: PROTOCOL_VERSION.to_string(),
1349 documentation_url: None,
1350 capabilities: AgentCapabilities::default(),
1351 security_schemes: {
1352 let mut m = HashMap::new();
1353 m.insert(
1354 "apiKey".to_string(),
1355 SecurityScheme::ApiKeySecurityScheme(ApiKeySecurityScheme {
1356 name: "X-API-Key".to_string(),
1357 location: "header".to_string(),
1358 description: None,
1359 }),
1360 );
1361 m
1362 },
1363 security_requirements: vec![],
1364 default_input_modes: vec![],
1365 default_output_modes: vec![],
1366 skills: vec![],
1367 signatures: vec![],
1368 icon_url: None,
1369 };
1370
1371 let json = serde_json::to_string(&card).unwrap();
1372 let parsed: AgentCard = serde_json::from_str(&json).unwrap();
1373 assert_eq!(parsed.name, "Test Agent");
1374 assert_eq!(parsed.security_schemes.len(), 1);
1375 assert_eq!(parsed.endpoint(), Some("https://example.com/v1/rpc"));
1376
1377 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1379 assert!(value.get("supportedInterfaces").is_some());
1380 assert!(value.get("securitySchemes").is_some());
1381 assert!(value.get("securityRequirements").is_some());
1382 }
1383
1384 #[test]
1385 fn validate_task_id_helper() {
1386 let valid_uuid = Uuid::new_v4().to_string();
1387 assert!(validate_task_id(&valid_uuid));
1388 assert!(!validate_task_id("not-a-uuid"));
1389 }
1390
1391 #[test]
1392 fn error_codes() {
1393 use errors::*;
1394 assert_eq!(message_for_code(TASK_NOT_FOUND), "Task not found");
1395 assert_eq!(
1396 message_for_code(VERSION_NOT_SUPPORTED),
1397 "Protocol version not supported"
1398 );
1399 assert_eq!(
1400 message_for_code(INVALID_AGENT_RESPONSE),
1401 "Invalid agent response"
1402 );
1403 assert_eq!(
1404 message_for_code(EXTENSION_SUPPORT_REQUIRED),
1405 "Extension support required"
1406 );
1407 assert_eq!(message_for_code(999), "Unknown error");
1408 }
1409
1410 #[test]
1411 fn send_message_result_serialization() {
1412 let task = Task {
1413 kind: "task".to_string(),
1414 id: "t-1".to_string(),
1415 context_id: "ctx-1".to_string(),
1416 status: TaskStatus {
1417 state: TaskState::Completed,
1418 message: None,
1419 timestamp: None,
1420 },
1421 artifacts: None,
1422 history: None,
1423 metadata: None,
1424 };
1425
1426 let result = SendMessageResult::Task(task.clone());
1428 let json = serde_json::to_string(&result).unwrap();
1429 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1430 assert!(value.get("task").is_some(), "should have task wrapper key");
1431 let inner = value.get("task").unwrap();
1432 assert_eq!(inner.get("id").unwrap().as_str().unwrap(), "t-1");
1433
1434 let parsed: SendMessageResult = serde_json::from_str(&json).unwrap();
1436 assert_eq!(parsed, SendMessageResult::Task(task));
1437 }
1438
1439 #[test]
1440 fn stream_response_serialization() {
1441 let event = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
1442 kind: "status-update".to_string(),
1443 task_id: "t-1".to_string(),
1444 context_id: "ctx-1".to_string(),
1445 status: TaskStatus {
1446 state: TaskState::Working,
1447 message: None,
1448 timestamp: None,
1449 },
1450 is_final: false,
1451 metadata: None,
1452 });
1453 let json = serde_json::to_string(&event).unwrap();
1454 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1455 assert!(value.get("statusUpdate").is_some());
1456 }
1457
1458 #[test]
1459 fn push_notification_config_serialization() {
1460 let config = PushNotificationConfig {
1461 id: Some("cfg-1".to_string()),
1462 url: "https://example.com/webhook".to_string(),
1463 token: Some("secret".to_string()),
1464 authentication: Some(AuthenticationInfo {
1465 scheme: "bearer".to_string(),
1466 credentials: Some("token123".to_string()),
1467 }),
1468 };
1469 let json = serde_json::to_string(&config).unwrap();
1470 let parsed: PushNotificationConfig = serde_json::from_str(&json).unwrap();
1471 assert_eq!(parsed.url, "https://example.com/webhook");
1472 assert_eq!(parsed.authentication.unwrap().scheme, "bearer");
1473 }
1474}