1use serde::{Deserialize, Serialize};
6pub mod compat;
7
8use std::collections::HashMap;
9use uuid::Uuid;
10
11pub const PROTOCOL_VERSION: &str = "1.0";
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
17#[serde(rename_all = "camelCase")]
18pub struct AgentCard {
19 pub name: String,
21 pub description: String,
23 #[serde(default)]
25 pub supported_interfaces: Vec<AgentInterface>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub provider: Option<AgentProvider>,
29 pub version: String,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub documentation_url: Option<String>,
34 #[serde(default)]
36 pub capabilities: AgentCapabilities,
37 #[serde(default)]
39 pub security_schemes: HashMap<String, SecurityScheme>,
40 #[serde(default)]
42 pub security_requirements: Vec<SecurityRequirement>,
43 #[serde(default)]
45 pub default_input_modes: Vec<String>,
46 #[serde(default)]
48 pub default_output_modes: Vec<String>,
49 #[serde(default)]
51 pub skills: Vec<AgentSkill>,
52 #[serde(default, skip_serializing_if = "Vec::is_empty")]
54 pub signatures: Vec<AgentCardSignature>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub icon_url: Option<String>,
58}
59
60impl AgentCard {
61 pub fn endpoint(&self) -> Option<&str> {
63 self.supported_interfaces
64 .iter()
65 .find(|i| i.protocol_binding.eq_ignore_ascii_case("jsonrpc"))
66 .map(|i| i.url.as_str())
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72#[serde(rename_all = "camelCase")]
73pub struct AgentInterface {
74 pub url: String,
75 pub protocol_binding: String,
76 pub protocol_version: String,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub tenant: Option<String>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
83#[serde(rename_all = "camelCase")]
84pub struct AgentProvider {
85 pub organization: String,
87 pub url: String,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93#[serde(rename_all = "camelCase")]
94#[non_exhaustive]
95pub enum SecurityScheme {
96 ApiKeySecurityScheme(ApiKeySecurityScheme),
97 HttpAuthSecurityScheme(HttpAuthSecurityScheme),
98 Oauth2SecurityScheme(OAuth2SecurityScheme),
99 OpenIdConnectSecurityScheme(OpenIdConnectSecurityScheme),
100 MtlsSecurityScheme(MutualTlsSecurityScheme),
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
104#[serde(rename_all = "camelCase")]
105pub struct ApiKeySecurityScheme {
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub description: Option<String>,
108 pub location: String,
110 pub name: String,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115#[serde(rename_all = "camelCase")]
116pub struct HttpAuthSecurityScheme {
117 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub description: Option<String>,
119 pub scheme: String,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub bearer_format: Option<String>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126#[serde(rename_all = "camelCase")]
127pub struct OAuth2SecurityScheme {
128 #[serde(default, skip_serializing_if = "Option::is_none")]
129 pub description: Option<String>,
130 pub flows: OAuthFlows,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub oauth2_metadata_url: Option<String>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
136#[serde(rename_all = "camelCase")]
137pub struct OpenIdConnectSecurityScheme {
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub description: Option<String>,
140 pub open_id_connect_url: String,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
144#[serde(rename_all = "camelCase")]
145pub struct MutualTlsSecurityScheme {
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub description: Option<String>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152#[serde(rename_all = "camelCase")]
153#[non_exhaustive]
154pub enum OAuthFlows {
155 AuthorizationCode(AuthorizationCodeOAuthFlow),
156 ClientCredentials(ClientCredentialsOAuthFlow),
157 Implicit(ImplicitOAuthFlow),
159 Password(PasswordOAuthFlow),
161 DeviceCode(DeviceCodeOAuthFlow),
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
165#[serde(rename_all = "camelCase")]
166pub struct AuthorizationCodeOAuthFlow {
167 pub authorization_url: String,
168 pub token_url: String,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub refresh_url: Option<String>,
171 #[serde(default)]
172 pub scopes: HashMap<String, String>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub pkce_required: Option<bool>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
178#[serde(rename_all = "camelCase")]
179pub struct ClientCredentialsOAuthFlow {
180 pub token_url: String,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub refresh_url: Option<String>,
183 #[serde(default)]
184 pub scopes: HashMap<String, String>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
188#[serde(rename_all = "camelCase")]
189pub struct ImplicitOAuthFlow {
190 pub authorization_url: String,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub refresh_url: Option<String>,
193 #[serde(default)]
194 pub scopes: HashMap<String, String>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
198#[serde(rename_all = "camelCase")]
199pub struct PasswordOAuthFlow {
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 DeviceCodeOAuthFlow {
210 pub device_authorization_url: String,
211 pub token_url: String,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub refresh_url: Option<String>,
214 #[serde(default)]
215 pub scopes: HashMap<String, String>,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
220#[serde(rename_all = "camelCase")]
221pub struct StringList {
222 #[serde(default)]
223 pub list: Vec<String>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
228#[serde(rename_all = "camelCase")]
229pub struct SecurityRequirement {
230 #[serde(default)]
232 pub schemes: HashMap<String, StringList>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237#[serde(rename_all = "camelCase")]
238pub struct AgentCardSignature {
239 pub protected: String,
241 pub signature: String,
243 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub header: Option<serde_json::Value>,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
249#[serde(rename_all = "camelCase")]
250pub struct AgentCapabilities {
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub streaming: Option<bool>,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub push_notifications: Option<bool>,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub extended_agent_card: Option<bool>,
257 #[serde(default, skip_serializing_if = "Vec::is_empty")]
258 pub extensions: Vec<AgentExtension>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
262#[serde(rename_all = "camelCase")]
263pub struct AgentExtension {
264 pub uri: String,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub description: Option<String>,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub required: Option<bool>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub params: Option<serde_json::Value>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
275#[serde(rename_all = "camelCase")]
276pub struct AgentSkill {
277 pub id: String,
279 pub name: String,
281 pub description: String,
283 #[serde(default)]
285 pub tags: Vec<String>,
286 #[serde(default)]
288 pub examples: Vec<String>,
289 #[serde(default)]
291 pub input_modes: Vec<String>,
292 #[serde(default)]
294 pub output_modes: Vec<String>,
295 #[serde(default)]
297 pub security_requirements: Vec<SecurityRequirement>,
298}
299
300#[derive(Debug, Clone, PartialEq)]
311pub enum Part {
312 Text {
314 text: String,
316 metadata: Option<serde_json::Value>,
318 },
319 File {
321 file: FileContent,
323 metadata: Option<serde_json::Value>,
325 },
326 Data {
328 data: serde_json::Value,
330 metadata: Option<serde_json::Value>,
332 },
333}
334
335impl Serialize for Part {
336 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
337 use serde::ser::SerializeMap;
338 match self {
339 Part::Text { text, metadata } => {
340 let mut map = serializer.serialize_map(None)?;
341 map.serialize_entry("text", text)?;
342 if let Some(m) = metadata {
343 map.serialize_entry("metadata", m)?;
344 }
345 map.end()
346 }
347 Part::File { file, metadata } => {
348 let mut map = serializer.serialize_map(None)?;
349 map.serialize_entry("file", file)?;
350 if let Some(m) = metadata {
351 map.serialize_entry("metadata", m)?;
352 }
353 map.end()
354 }
355 Part::Data { data, metadata } => {
356 let mut map = serializer.serialize_map(None)?;
357 map.serialize_entry("data", data)?;
358 if let Some(m) = metadata {
359 map.serialize_entry("metadata", m)?;
360 }
361 map.end()
362 }
363 }
364 }
365}
366
367impl<'de> Deserialize<'de> for Part {
368 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
369 let value = serde_json::Value::deserialize(deserializer)?;
370 let obj = value
371 .as_object()
372 .ok_or_else(|| serde::de::Error::custom("expected object for Part"))?;
373 let metadata = obj.get("metadata").cloned();
374
375 if let Some(text) = obj.get("text") {
376 Ok(Part::Text {
377 text: text
378 .as_str()
379 .ok_or_else(|| serde::de::Error::custom("text must be a string"))?
380 .to_string(),
381 metadata,
382 })
383 } else if let Some(file) = obj.get("file") {
384 let file: FileContent = serde_json::from_value(file.clone())
385 .map_err(serde::de::Error::custom)?;
386 Ok(Part::File { file, metadata })
387 } else if let Some(data) = obj.get("data") {
388 Ok(Part::Data {
389 data: data.clone(),
390 metadata,
391 })
392 } else if obj.contains_key("kind") {
393 let kind = obj["kind"].as_str().unwrap_or("");
395 match kind {
396 "text" => Ok(Part::Text {
397 text: obj
398 .get("text")
399 .and_then(|v| v.as_str())
400 .unwrap_or("")
401 .to_string(),
402 metadata,
403 }),
404 "file" => {
405 let file: FileContent = serde_json::from_value(
406 obj.get("file").cloned().unwrap_or_default(),
407 )
408 .map_err(serde::de::Error::custom)?;
409 Ok(Part::File { file, metadata })
410 }
411 "data" => Ok(Part::Data {
412 data: obj.get("data").cloned().unwrap_or_default(),
413 metadata,
414 }),
415 _ => Err(serde::de::Error::custom(format!(
416 "unknown part kind: {kind}"
417 ))),
418 }
419 } else {
420 Err(serde::de::Error::custom(
421 "Part must have text, file, or data field",
422 ))
423 }
424 }
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
429#[serde(rename_all = "camelCase")]
430pub struct FileContent {
431 #[serde(default, skip_serializing_if = "Option::is_none")]
433 pub bytes: Option<String>,
434 #[serde(default, skip_serializing_if = "Option::is_none")]
436 pub uri: Option<String>,
437 #[serde(default, skip_serializing_if = "Option::is_none")]
439 pub name: Option<String>,
440 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub mime_type: Option<String>,
443}
444
445impl Part {
446 pub fn text(text: impl Into<String>) -> Self {
448 Part::Text {
449 text: text.into(),
450 metadata: None,
451 }
452 }
453
454 pub fn file_uri(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
456 Part::File {
457 file: FileContent {
458 bytes: None,
459 uri: Some(uri.into()),
460 name: None,
461 mime_type: Some(mime_type.into()),
462 },
463 metadata: None,
464 }
465 }
466
467 pub fn file_bytes(bytes: impl Into<String>, mime_type: impl Into<String>) -> Self {
469 Part::File {
470 file: FileContent {
471 bytes: Some(bytes.into()),
472 uri: None,
473 name: None,
474 mime_type: Some(mime_type.into()),
475 },
476 metadata: None,
477 }
478 }
479
480 pub fn data(data: serde_json::Value) -> Self {
482 Part::Data {
483 data,
484 metadata: None,
485 }
486 }
487
488 pub fn as_text(&self) -> Option<&str> {
490 match self {
491 Part::Text { text, .. } => Some(text),
492 _ => None,
493 }
494 }
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
501#[non_exhaustive]
502pub enum Role {
503 #[serde(rename = "ROLE_UNSPECIFIED")]
504 Unspecified,
505 #[serde(rename = "ROLE_USER")]
506 User,
507 #[serde(rename = "ROLE_AGENT")]
508 Agent,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
513#[serde(rename_all = "camelCase")]
514pub struct Message {
515 #[serde(default = "default_message_kind", skip_serializing)]
517 pub kind: String,
518 pub message_id: String,
520 #[serde(default, skip_serializing_if = "Option::is_none")]
522 pub context_id: Option<String>,
523 #[serde(default, skip_serializing_if = "Option::is_none")]
525 pub task_id: Option<String>,
526 pub role: Role,
528 pub parts: Vec<Part>,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
532 pub metadata: Option<serde_json::Value>,
533 #[serde(default, skip_serializing_if = "Vec::is_empty")]
535 pub extensions: Vec<String>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
538 pub reference_task_ids: Option<Vec<String>>,
539}
540
541fn default_message_kind() -> String {
542 "message".to_string()
543}
544
545fn default_task_kind() -> String {
546 "task".to_string()
547}
548
549fn default_status_update_kind() -> String {
550 "status-update".to_string()
551}
552
553fn default_artifact_update_kind() -> String {
554 "artifact-update".to_string()
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
559#[serde(rename_all = "camelCase")]
560pub struct Artifact {
561 pub artifact_id: String,
563 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub name: Option<String>,
566 #[serde(default, skip_serializing_if = "Option::is_none")]
568 pub description: Option<String>,
569 pub parts: Vec<Part>,
571 #[serde(default, skip_serializing_if = "Option::is_none")]
573 pub metadata: Option<serde_json::Value>,
574 #[serde(default, skip_serializing_if = "Vec::is_empty")]
576 pub extensions: Vec<String>,
577}
578
579#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
581#[non_exhaustive]
582pub enum TaskState {
583 #[serde(rename = "TASK_STATE_UNSPECIFIED")]
584 Unspecified,
585 #[serde(rename = "TASK_STATE_SUBMITTED")]
586 Submitted,
587 #[serde(rename = "TASK_STATE_WORKING")]
588 Working,
589 #[serde(rename = "TASK_STATE_COMPLETED")]
590 Completed,
591 #[serde(rename = "TASK_STATE_FAILED")]
592 Failed,
593 #[serde(rename = "TASK_STATE_CANCELED")]
594 Canceled,
595 #[serde(rename = "TASK_STATE_INPUT_REQUIRED")]
596 InputRequired,
597 #[serde(rename = "TASK_STATE_REJECTED")]
598 Rejected,
599 #[serde(rename = "TASK_STATE_AUTH_REQUIRED")]
600 AuthRequired,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
605#[serde(rename_all = "camelCase")]
606pub struct TaskStatus {
607 pub state: TaskState,
609 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub message: Option<Message>,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
614 pub timestamp: Option<String>,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619#[serde(rename_all = "camelCase")]
620pub struct Task {
621 #[serde(default = "default_task_kind", skip_serializing)]
623 pub kind: String,
624 pub id: String,
626 pub context_id: String,
628 pub status: TaskStatus,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub artifacts: Option<Vec<Artifact>>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub history: Option<Vec<Message>>,
636 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub metadata: Option<serde_json::Value>,
639}
640
641impl TaskState {
642 pub fn is_terminal(&self) -> bool {
644 matches!(
645 self,
646 TaskState::Completed | TaskState::Failed | TaskState::Canceled | TaskState::Rejected
647 )
648 }
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
658#[serde(rename_all = "camelCase")]
659pub enum SendMessageResponse {
660 Task(Task),
661 Message(Message),
662}
663
664#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
669#[serde(rename_all = "camelCase")]
670pub enum SendMessageResult {
671 Task(Task),
672 Message(Message),
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
678pub struct JsonRpcRequest {
679 pub jsonrpc: String,
680 pub method: String,
681 #[serde(default)]
682 pub params: Option<serde_json::Value>,
683 pub id: serde_json::Value,
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
687pub struct JsonRpcResponse {
688 pub jsonrpc: String,
689 pub id: serde_json::Value,
690 #[serde(skip_serializing_if = "Option::is_none")]
691 pub result: Option<serde_json::Value>,
692 #[serde(skip_serializing_if = "Option::is_none")]
693 pub error: Option<JsonRpcError>,
694}
695
696#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
697pub struct JsonRpcError {
698 pub code: i32,
699 pub message: String,
700 #[serde(skip_serializing_if = "Option::is_none")]
701 pub data: Option<serde_json::Value>,
702}
703
704pub mod errors {
706 pub const PARSE_ERROR: i32 = -32700;
708 pub const INVALID_REQUEST: i32 = -32600;
709 pub const METHOD_NOT_FOUND: i32 = -32601;
710 pub const INVALID_PARAMS: i32 = -32602;
711 pub const INTERNAL_ERROR: i32 = -32603;
712
713 pub const TASK_NOT_FOUND: i32 = -32001;
715 pub const TASK_NOT_CANCELABLE: i32 = -32002;
716 pub const PUSH_NOTIFICATION_NOT_SUPPORTED: i32 = -32003;
717 pub const UNSUPPORTED_OPERATION: i32 = -32004;
718 pub const CONTENT_TYPE_NOT_SUPPORTED: i32 = -32005;
719 pub const EXTENDED_AGENT_CARD_NOT_CONFIGURED: i32 = -32006;
720 pub const VERSION_NOT_SUPPORTED: i32 = -32007;
721 pub const INVALID_AGENT_RESPONSE: i32 = -32008;
722 pub const EXTENSION_SUPPORT_REQUIRED: i32 = -32009;
723
724 pub fn message_for_code(code: i32) -> &'static str {
725 match code {
726 PARSE_ERROR => "Parse error",
727 INVALID_REQUEST => "Invalid request",
728 METHOD_NOT_FOUND => "Method not found",
729 INVALID_PARAMS => "Invalid params",
730 INTERNAL_ERROR => "Internal error",
731 TASK_NOT_FOUND => "Task not found",
732 TASK_NOT_CANCELABLE => "Task not cancelable",
733 PUSH_NOTIFICATION_NOT_SUPPORTED => "Push notifications not supported",
734 UNSUPPORTED_OPERATION => "Unsupported operation",
735 CONTENT_TYPE_NOT_SUPPORTED => "Content type not supported",
736 EXTENDED_AGENT_CARD_NOT_CONFIGURED => "Extended agent card not configured",
737 VERSION_NOT_SUPPORTED => "Protocol version not supported",
738 INVALID_AGENT_RESPONSE => "Invalid agent response",
739 EXTENSION_SUPPORT_REQUIRED => "Extension support required",
740 _ => "Unknown error",
741 }
742 }
743}
744
745pub fn success(id: serde_json::Value, result: serde_json::Value) -> JsonRpcResponse {
746 JsonRpcResponse {
747 jsonrpc: "2.0".to_string(),
748 id,
749 result: Some(result),
750 error: None,
751 }
752}
753
754pub fn error(
755 id: serde_json::Value,
756 code: i32,
757 message: &str,
758 data: Option<serde_json::Value>,
759) -> JsonRpcResponse {
760 JsonRpcResponse {
761 jsonrpc: "2.0".to_string(),
762 id,
763 result: None,
764 error: Some(JsonRpcError {
765 code,
766 message: message.to_string(),
767 data,
768 }),
769 }
770}
771
772#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
776#[serde(rename_all = "camelCase")]
777pub struct SendMessageRequest {
778 #[serde(default, skip_serializing_if = "Option::is_none")]
780 pub tenant: Option<String>,
781 pub message: Message,
783 #[serde(default, skip_serializing_if = "Option::is_none")]
785 pub configuration: Option<SendMessageConfiguration>,
786 #[serde(default, skip_serializing_if = "Option::is_none")]
788 pub metadata: Option<serde_json::Value>,
789}
790
791#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
793#[serde(rename_all = "camelCase")]
794pub struct SendMessageConfiguration {
795 #[serde(default, skip_serializing_if = "Option::is_none")]
797 pub accepted_output_modes: Option<Vec<String>>,
798 #[serde(default, skip_serializing_if = "Option::is_none")]
800 pub push_notification_config: Option<PushNotificationConfig>,
801 #[serde(default, skip_serializing_if = "Option::is_none")]
803 pub history_length: Option<u32>,
804 #[serde(default, skip_serializing_if = "Option::is_none")]
806 pub blocking: Option<bool>,
807 #[serde(default, skip_serializing_if = "Option::is_none")]
810 pub return_immediately: Option<bool>,
811}
812
813#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
815#[serde(rename_all = "camelCase")]
816pub struct GetTaskRequest {
817 pub id: String,
819 #[serde(default, skip_serializing_if = "Option::is_none")]
821 pub history_length: Option<u32>,
822 #[serde(default, skip_serializing_if = "Option::is_none")]
824 pub tenant: Option<String>,
825}
826
827#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
829#[serde(rename_all = "camelCase")]
830pub struct CancelTaskRequest {
831 pub id: String,
833 #[serde(default, skip_serializing_if = "Option::is_none")]
835 pub tenant: Option<String>,
836}
837
838#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
840#[serde(rename_all = "camelCase")]
841pub struct ListTasksRequest {
842 #[serde(default, skip_serializing_if = "Option::is_none")]
844 pub context_id: Option<String>,
845 #[serde(default, skip_serializing_if = "Option::is_none")]
847 pub status: Option<TaskState>,
848 #[serde(default, skip_serializing_if = "Option::is_none")]
850 pub page_size: Option<u32>,
851 #[serde(default, skip_serializing_if = "Option::is_none")]
853 pub page_token: Option<String>,
854 #[serde(default, skip_serializing_if = "Option::is_none")]
856 pub history_length: Option<u32>,
857 #[serde(default, skip_serializing_if = "Option::is_none")]
859 pub status_timestamp_after: Option<i64>,
860 #[serde(default, skip_serializing_if = "Option::is_none")]
862 pub include_artifacts: Option<bool>,
863 #[serde(default, skip_serializing_if = "Option::is_none")]
865 pub tenant: Option<String>,
866}
867
868#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
870#[serde(rename_all = "camelCase")]
871pub struct TaskListResponse {
872 pub tasks: Vec<Task>,
874 pub next_page_token: String,
876 pub page_size: u32,
878 pub total_size: u32,
880}
881
882#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
884#[serde(rename_all = "camelCase")]
885pub struct SubscribeToTaskRequest {
886 pub id: String,
888 #[serde(default, skip_serializing_if = "Option::is_none")]
890 pub tenant: Option<String>,
891}
892
893#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
895#[serde(rename_all = "camelCase")]
896pub struct PushNotificationConfig {
897 #[serde(default, skip_serializing_if = "Option::is_none")]
899 pub id: Option<String>,
900 pub url: String,
902 #[serde(default, skip_serializing_if = "Option::is_none")]
904 pub token: Option<String>,
905 #[serde(default, skip_serializing_if = "Option::is_none")]
907 pub authentication: Option<AuthenticationInfo>,
908}
909
910#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
912#[serde(rename_all = "camelCase")]
913pub struct AuthenticationInfo {
914 pub scheme: String,
916 #[serde(default, skip_serializing_if = "Option::is_none")]
918 pub credentials: Option<String>,
919}
920
921#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
923#[serde(rename_all = "camelCase")]
924pub struct TaskPushNotificationConfig {
925 pub id: String,
927 pub task_id: String,
929 pub push_notification_config: PushNotificationConfig,
931 #[serde(default, skip_serializing_if = "Option::is_none")]
933 pub tenant: Option<String>,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
938#[serde(rename_all = "camelCase")]
939pub struct CreateTaskPushNotificationConfigRequest {
940 pub task_id: String,
942 pub config_id: String,
944 pub push_notification_config: PushNotificationConfig,
946 #[serde(default, skip_serializing_if = "Option::is_none")]
948 pub tenant: Option<String>,
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
953#[serde(rename_all = "camelCase")]
954pub struct GetTaskPushNotificationConfigRequest {
955 pub id: String,
957 pub task_id: String,
959 #[serde(default, skip_serializing_if = "Option::is_none")]
961 pub tenant: Option<String>,
962}
963
964#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
966#[serde(rename_all = "camelCase")]
967pub struct ListTaskPushNotificationConfigRequest {
968 pub task_id: String,
970 #[serde(default, skip_serializing_if = "Option::is_none")]
972 pub page_size: Option<u32>,
973 #[serde(default, skip_serializing_if = "Option::is_none")]
975 pub page_token: Option<String>,
976 #[serde(default, skip_serializing_if = "Option::is_none")]
978 pub tenant: Option<String>,
979}
980
981#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
983#[serde(rename_all = "camelCase")]
984pub struct ListTaskPushNotificationConfigResponse {
985 pub configs: Vec<TaskPushNotificationConfig>,
987 #[serde(default)]
989 pub next_page_token: String,
990}
991
992#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
994#[serde(rename_all = "camelCase")]
995pub struct DeleteTaskPushNotificationConfigRequest {
996 pub id: String,
998 pub task_id: String,
1000 #[serde(default, skip_serializing_if = "Option::is_none")]
1002 pub tenant: Option<String>,
1003}
1004
1005#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1007#[serde(rename_all = "camelCase")]
1008pub struct GetExtendedAgentCardRequest {
1009 #[serde(default, skip_serializing_if = "Option::is_none")]
1011 pub tenant: Option<String>,
1012}
1013
1014#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1018#[serde(rename_all = "camelCase")]
1019pub enum StreamResponse {
1020 Task(Task),
1022 Message(Message),
1024 StatusUpdate(TaskStatusUpdateEvent),
1026 ArtifactUpdate(TaskArtifactUpdateEvent),
1028}
1029
1030#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1032#[serde(rename_all = "camelCase")]
1033pub struct TaskStatusUpdateEvent {
1034 #[serde(default = "default_status_update_kind", skip_serializing)]
1036 pub kind: String,
1037 pub task_id: String,
1039 pub context_id: String,
1041 pub status: TaskStatus,
1043 #[serde(rename = "final", default)]
1045 pub is_final: bool,
1046 #[serde(default, skip_serializing_if = "Option::is_none")]
1048 pub metadata: Option<serde_json::Value>,
1049}
1050
1051#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1053#[serde(rename_all = "camelCase")]
1054pub struct TaskArtifactUpdateEvent {
1055 #[serde(default = "default_artifact_update_kind", skip_serializing)]
1057 pub kind: String,
1058 pub task_id: String,
1060 pub context_id: String,
1062 pub artifact: Artifact,
1064 #[serde(default, skip_serializing_if = "Option::is_none")]
1066 pub append: Option<bool>,
1067 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 pub last_chunk: Option<bool>,
1070 #[serde(default, skip_serializing_if = "Option::is_none")]
1072 pub metadata: Option<serde_json::Value>,
1073}
1074
1075#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1080#[serde(rename_all = "camelCase")]
1081pub enum StreamingMessageResult {
1082 StatusUpdate(TaskStatusUpdateEvent),
1083 ArtifactUpdate(TaskArtifactUpdateEvent),
1084 Task(Task),
1085 Message(Message),
1086}
1087
1088pub fn new_message(role: Role, text: &str, context_id: Option<String>) -> Message {
1092 Message {
1093 kind: "message".to_string(),
1094 message_id: Uuid::new_v4().to_string(),
1095 context_id,
1096 task_id: None,
1097 role,
1098 parts: vec![Part::text(text)],
1099 metadata: None,
1100 extensions: vec![],
1101 reference_task_ids: None,
1102 }
1103}
1104
1105pub fn completed_task_with_text(user_message: Message, reply_text: &str) -> Task {
1107 let context_id = user_message
1108 .context_id
1109 .clone()
1110 .unwrap_or_else(|| Uuid::new_v4().to_string());
1111 let task_id = Uuid::new_v4().to_string();
1112 let agent_msg = new_message(Role::Agent, reply_text, Some(context_id.clone()));
1113
1114 Task {
1115 kind: "task".to_string(),
1116 id: task_id,
1117 context_id,
1118 status: TaskStatus {
1119 state: TaskState::Completed,
1120 message: Some(agent_msg.clone()),
1121 timestamp: Some(chrono::Utc::now().to_rfc3339()),
1122 },
1123 history: Some(vec![user_message, agent_msg]),
1124 artifacts: None,
1125 metadata: None,
1126 }
1127}
1128
1129pub fn now_iso8601() -> String {
1131 chrono::Utc::now().to_rfc3339()
1132}
1133
1134pub fn validate_task_id(id: &str) -> bool {
1136 Uuid::parse_str(id).is_ok()
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142
1143 #[test]
1144 fn jsonrpc_helpers_round_trip() {
1145 let resp = success(serde_json::json!(1), serde_json::json!({"ok": true}));
1146 assert_eq!(resp.jsonrpc, "2.0");
1147 assert!(resp.error.is_none());
1148 assert!(resp.result.is_some());
1149 }
1150
1151 #[test]
1152 fn task_state_is_terminal() {
1153 assert!(TaskState::Completed.is_terminal());
1154 assert!(TaskState::Failed.is_terminal());
1155 assert!(TaskState::Canceled.is_terminal());
1156 assert!(TaskState::Rejected.is_terminal());
1157 assert!(!TaskState::Working.is_terminal());
1158 assert!(!TaskState::Submitted.is_terminal());
1159 assert!(!TaskState::InputRequired.is_terminal());
1160 }
1161
1162 #[test]
1163 fn task_state_serialization() {
1164 let state = TaskState::Working;
1165 let json = serde_json::to_string(&state).unwrap();
1166 assert_eq!(json, r#""TASK_STATE_WORKING""#);
1167
1168 let parsed: TaskState = serde_json::from_str(&json).unwrap();
1169 assert_eq!(parsed, TaskState::Working);
1170 }
1171
1172 #[test]
1173 fn role_serialization() {
1174 let role = Role::User;
1175 let json = serde_json::to_string(&role).unwrap();
1176 assert_eq!(json, r#""ROLE_USER""#);
1177 }
1178
1179 #[test]
1180 fn message_serialization() {
1181 let msg = new_message(Role::User, "hello", Some("ctx-123".to_string()));
1182 let json = serde_json::to_string(&msg).unwrap();
1183 let parsed: Message = serde_json::from_str(&json).unwrap();
1184 assert_eq!(parsed.role, Role::User);
1185 assert_eq!(parsed.parts.len(), 1);
1186 assert_eq!(parsed.parts[0].as_text(), Some("hello"));
1187
1188 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1190 assert!(value.get("messageId").is_some());
1191 assert!(value.get("contextId").is_some());
1192 }
1193
1194 #[test]
1195 fn task_serialization() {
1196 let user_msg = new_message(Role::User, "test", None);
1197 let task = completed_task_with_text(user_msg, "response");
1198 let json = serde_json::to_string(&task).unwrap();
1199 let parsed: Task = serde_json::from_str(&json).unwrap();
1200 assert_eq!(parsed.status.state, TaskState::Completed);
1201 assert!(parsed.history.is_some());
1202 assert_eq!(parsed.history.unwrap().len(), 2);
1203
1204 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1206 assert!(value.get("contextId").is_some());
1207 }
1208
1209 #[test]
1210 fn part_text_serialization() {
1211 let part = Part::text("hello");
1212 let json = serde_json::to_string(&part).unwrap();
1213 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1214 assert!(value.get("kind").is_none());
1216 assert_eq!(value.get("text").unwrap().as_str().unwrap(), "hello");
1217 }
1218
1219 #[test]
1220 fn part_text_round_trip() {
1221 let part = Part::text("hello");
1222 let json = serde_json::to_string(&part).unwrap();
1223 let parsed: Part = serde_json::from_str(&json).unwrap();
1224 assert_eq!(parsed, part);
1225 assert_eq!(parsed.as_text(), Some("hello"));
1226 }
1227
1228 #[test]
1229 fn part_file_uri_serialization() {
1230 let part = Part::file_uri("https://example.com/file.pdf", "application/pdf");
1231 let json = serde_json::to_string(&part).unwrap();
1232 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1233 assert!(value.get("kind").is_none());
1234 let file = value.get("file").unwrap();
1235 assert_eq!(
1236 file.get("uri").unwrap().as_str().unwrap(),
1237 "https://example.com/file.pdf"
1238 );
1239 assert_eq!(
1240 file.get("mimeType").unwrap().as_str().unwrap(),
1241 "application/pdf"
1242 );
1243 }
1244
1245 #[test]
1246 fn part_data_serialization() {
1247 let part = Part::data(serde_json::json!({"key": "value"}));
1248 let json = serde_json::to_string(&part).unwrap();
1249 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1250 assert!(value.get("kind").is_none());
1251 assert_eq!(
1252 value.get("data").unwrap(),
1253 &serde_json::json!({"key": "value"})
1254 );
1255 }
1256
1257 #[test]
1258 fn part_deserialization_from_wire_format() {
1259 let text: Part = serde_json::from_str(r#"{"text":"hello"}"#).unwrap();
1261 assert_eq!(text.as_text(), Some("hello"));
1262
1263 let file: Part = serde_json::from_str(
1264 r#"{"file":{"uri":"https://example.com/f.pdf","mimeType":"application/pdf"}}"#,
1265 )
1266 .unwrap();
1267 match &file {
1268 Part::File { file, .. } => {
1269 assert_eq!(file.uri.as_deref(), Some("https://example.com/f.pdf"));
1270 assert_eq!(file.mime_type.as_deref(), Some("application/pdf"));
1271 }
1272 _ => panic!("expected File part"),
1273 }
1274
1275 let data: Part = serde_json::from_str(r#"{"data":{"k":"v"}}"#).unwrap();
1276 match &data {
1277 Part::Data { data, .. } => assert_eq!(data, &serde_json::json!({"k": "v"})),
1278 _ => panic!("expected Data part"),
1279 }
1280
1281 let text_v03: Part =
1283 serde_json::from_str(r#"{"kind":"text","text":"hello v03"}"#).unwrap();
1284 assert_eq!(text_v03.as_text(), Some("hello v03"));
1285 }
1286
1287 #[test]
1288 fn agent_card_with_security() {
1289 let card = AgentCard {
1290 name: "Test Agent".to_string(),
1291 description: "Test description".to_string(),
1292 supported_interfaces: vec![AgentInterface {
1293 url: "https://example.com/v1/rpc".to_string(),
1294 protocol_binding: "JSONRPC".to_string(),
1295 protocol_version: PROTOCOL_VERSION.to_string(),
1296 tenant: None,
1297 }],
1298 provider: Some(AgentProvider {
1299 organization: "Test Org".to_string(),
1300 url: "https://example.com".to_string(),
1301 }),
1302 version: PROTOCOL_VERSION.to_string(),
1303 documentation_url: None,
1304 capabilities: AgentCapabilities::default(),
1305 security_schemes: {
1306 let mut m = HashMap::new();
1307 m.insert(
1308 "apiKey".to_string(),
1309 SecurityScheme::ApiKeySecurityScheme(ApiKeySecurityScheme {
1310 name: "X-API-Key".to_string(),
1311 location: "header".to_string(),
1312 description: None,
1313 }),
1314 );
1315 m
1316 },
1317 security_requirements: vec![],
1318 default_input_modes: vec![],
1319 default_output_modes: vec![],
1320 skills: vec![],
1321 signatures: vec![],
1322 icon_url: None,
1323 };
1324
1325 let json = serde_json::to_string(&card).unwrap();
1326 let parsed: AgentCard = serde_json::from_str(&json).unwrap();
1327 assert_eq!(parsed.name, "Test Agent");
1328 assert_eq!(parsed.security_schemes.len(), 1);
1329 assert_eq!(parsed.endpoint(), Some("https://example.com/v1/rpc"));
1330
1331 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1333 assert!(value.get("supportedInterfaces").is_some());
1334 assert!(value.get("securitySchemes").is_some());
1335 assert!(value.get("securityRequirements").is_some());
1336 }
1337
1338 #[test]
1339 fn validate_task_id_helper() {
1340 let valid_uuid = Uuid::new_v4().to_string();
1341 assert!(validate_task_id(&valid_uuid));
1342 assert!(!validate_task_id("not-a-uuid"));
1343 }
1344
1345 #[test]
1346 fn error_codes() {
1347 use errors::*;
1348 assert_eq!(message_for_code(TASK_NOT_FOUND), "Task not found");
1349 assert_eq!(
1350 message_for_code(VERSION_NOT_SUPPORTED),
1351 "Protocol version not supported"
1352 );
1353 assert_eq!(
1354 message_for_code(INVALID_AGENT_RESPONSE),
1355 "Invalid agent response"
1356 );
1357 assert_eq!(
1358 message_for_code(EXTENSION_SUPPORT_REQUIRED),
1359 "Extension support required"
1360 );
1361 assert_eq!(message_for_code(999), "Unknown error");
1362 }
1363
1364 #[test]
1365 fn send_message_result_serialization() {
1366 let task = Task {
1367 kind: "task".to_string(),
1368 id: "t-1".to_string(),
1369 context_id: "ctx-1".to_string(),
1370 status: TaskStatus {
1371 state: TaskState::Completed,
1372 message: None,
1373 timestamp: None,
1374 },
1375 artifacts: None,
1376 history: None,
1377 metadata: None,
1378 };
1379
1380 let result = SendMessageResult::Task(task.clone());
1382 let json = serde_json::to_string(&result).unwrap();
1383 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1384 assert!(value.get("task").is_some(), "should have task wrapper key");
1385 let inner = value.get("task").unwrap();
1386 assert_eq!(inner.get("id").unwrap().as_str().unwrap(), "t-1");
1387
1388 let parsed: SendMessageResult = serde_json::from_str(&json).unwrap();
1390 assert_eq!(parsed, SendMessageResult::Task(task));
1391 }
1392
1393 #[test]
1394 fn stream_response_serialization() {
1395 let event = StreamResponse::StatusUpdate(TaskStatusUpdateEvent {
1396 kind: "status-update".to_string(),
1397 task_id: "t-1".to_string(),
1398 context_id: "ctx-1".to_string(),
1399 status: TaskStatus {
1400 state: TaskState::Working,
1401 message: None,
1402 timestamp: None,
1403 },
1404 is_final: false,
1405 metadata: None,
1406 });
1407 let json = serde_json::to_string(&event).unwrap();
1408 let value: serde_json::Value = serde_json::from_str(&json).unwrap();
1409 assert!(value.get("statusUpdate").is_some());
1410 }
1411
1412 #[test]
1413 fn push_notification_config_serialization() {
1414 let config = PushNotificationConfig {
1415 id: Some("cfg-1".to_string()),
1416 url: "https://example.com/webhook".to_string(),
1417 token: Some("secret".to_string()),
1418 authentication: Some(AuthenticationInfo {
1419 scheme: "bearer".to_string(),
1420 credentials: Some("token123".to_string()),
1421 }),
1422 };
1423 let json = serde_json::to_string(&config).unwrap();
1424 let parsed: PushNotificationConfig = serde_json::from_str(&json).unwrap();
1425 assert_eq!(parsed.url, "https://example.com/webhook");
1426 assert_eq!(parsed.authentication.unwrap().scheme, "bearer");
1427 }
1428}