1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use super::errors::A2aErrorCode;
10
11pub const METHOD_MESSAGE_SEND: &str = "message/send";
17
18pub const METHOD_MESSAGE_STREAM: &str = "message/stream";
20
21pub const METHOD_TASKS_GET: &str = "tasks/get";
23
24pub const METHOD_TASKS_LIST: &str = "tasks/list";
26
27pub const METHOD_TASKS_CANCEL: &str = "tasks/cancel";
29
30pub const METHOD_TASKS_PUSH_CONFIG_SET: &str = "tasks/pushNotificationConfig/set";
32
33pub const METHOD_TASKS_PUSH_CONFIG_GET: &str = "tasks/pushNotificationConfig/get";
35
36pub const METHOD_TASKS_RESUBSCRIBE: &str = "tasks/resubscribe";
38
39pub const METHOD_AGENT_GET_EXTENDED_CARD: &str = "agent/getAuthenticatedExtendedCard";
41
42pub const JSONRPC_VERSION: &str = "2.0";
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct JsonRpcRequest {
52 pub jsonrpc: String,
54 pub method: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub params: Option<Value>,
59 pub id: Value,
61}
62
63impl JsonRpcRequest {
64 pub fn new(method: impl Into<String>, params: Option<Value>, id: Value) -> Self {
66 Self {
67 jsonrpc: JSONRPC_VERSION.to_string(),
68 method: method.into(),
69 params,
70 id,
71 }
72 }
73
74 pub fn with_string_id(
76 method: impl Into<String>,
77 params: Option<Value>,
78 id: impl Into<String>,
79 ) -> Self {
80 Self::new(method, params, Value::String(id.into()))
81 }
82
83 pub fn with_numeric_id(method: impl Into<String>, params: Option<Value>, id: i64) -> Self {
85 Self::new(method, params, Value::Number(id.into()))
86 }
87
88 pub fn message_send(params: MessageSendParams, id: Value) -> Self {
90 Self::new(
91 METHOD_MESSAGE_SEND,
92 Some(serde_json::to_value(params).unwrap_or_default()),
93 id,
94 )
95 }
96
97 pub fn tasks_get(task_id: impl Into<String>, id: Value) -> Self {
99 Self::new(
100 METHOD_TASKS_GET,
101 Some(serde_json::json!({ "id": task_id.into() })),
102 id,
103 )
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct JsonRpcResponse {
110 pub jsonrpc: String,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub result: Option<Value>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub error: Option<JsonRpcError>,
118 pub id: Value,
120}
121
122impl JsonRpcResponse {
123 pub fn success(result: Value, id: Value) -> Self {
125 Self {
126 jsonrpc: JSONRPC_VERSION.to_string(),
127 result: Some(result),
128 error: None,
129 id,
130 }
131 }
132
133 pub fn error(error: JsonRpcError, id: Value) -> Self {
135 Self {
136 jsonrpc: JSONRPC_VERSION.to_string(),
137 result: None,
138 error: Some(error),
139 id,
140 }
141 }
142
143 pub fn is_success(&self) -> bool {
145 self.result.is_some() && self.error.is_none()
146 }
147
148 pub fn is_error(&self) -> bool {
150 self.error.is_some()
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct JsonRpcError {
157 pub code: i32,
159 pub message: String,
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub data: Option<Value>,
164}
165
166impl JsonRpcError {
167 pub fn new(code: i32, message: impl Into<String>) -> Self {
169 Self {
170 code,
171 message: message.into(),
172 data: None,
173 }
174 }
175
176 pub fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
178 Self {
179 code,
180 message: message.into(),
181 data: Some(data),
182 }
183 }
184
185 pub fn from_code(code: A2aErrorCode, message: impl Into<String>) -> Self {
187 Self::new(code.into(), message)
188 }
189
190 pub fn parse_error(message: impl Into<String>) -> Self {
192 Self::from_code(A2aErrorCode::JsonParseError, message)
193 }
194
195 pub fn invalid_request(message: impl Into<String>) -> Self {
197 Self::from_code(A2aErrorCode::InvalidRequest, message)
198 }
199
200 pub fn method_not_found(method: impl Into<String>) -> Self {
202 Self::from_code(
203 A2aErrorCode::MethodNotFound,
204 format!("Method not found: {}", method.into()),
205 )
206 }
207
208 pub fn invalid_params(message: impl Into<String>) -> Self {
210 Self::from_code(A2aErrorCode::InvalidParams, message)
211 }
212
213 pub fn internal_error(message: impl Into<String>) -> Self {
215 Self::from_code(A2aErrorCode::InternalError, message)
216 }
217
218 pub fn task_not_found(task_id: impl Into<String>) -> Self {
220 Self::from_code(
221 A2aErrorCode::TaskNotFound,
222 format!("Task not found: {}", task_id.into()),
223 )
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct MessageSendParams {
235 pub message: super::types::Message,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub task_id: Option<String>,
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub context_id: Option<String>,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub configuration: Option<MessageConfiguration>,
246}
247
248impl MessageSendParams {
249 pub fn new(message: super::types::Message) -> Self {
251 Self {
252 message,
253 task_id: None,
254 context_id: None,
255 configuration: None,
256 }
257 }
258
259 pub fn with_task_id(mut self, task_id: impl Into<String>) -> Self {
261 self.task_id = Some(task_id.into());
262 self
263 }
264
265 pub fn with_context_id(mut self, context_id: impl Into<String>) -> Self {
267 self.context_id = Some(context_id.into());
268 self
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub struct MessageConfiguration {
276 #[serde(skip_serializing_if = "Option::is_none")]
278 pub accepted_input_modes: Option<Vec<String>>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub accepted_output_modes: Option<Vec<String>>,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub history_length: Option<u32>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub push_notification_config: Option<PushNotificationConfig>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct PushNotificationConfig {
294 pub url: String,
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub authentication: Option<String>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct TaskQueryParams {
305 pub id: String,
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub history_length: Option<u32>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize, Default)]
314#[serde(rename_all = "camelCase")]
315pub struct ListTasksParams {
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub context_id: Option<String>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub status: Option<super::types::TaskState>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub page_size: Option<u32>,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub page_token: Option<String>,
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub history_length: Option<u32>,
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub last_updated_after: Option<String>,
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub include_artifacts: Option<bool>,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub metadata: Option<Value>,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct ListTasksResult {
346 pub tasks: Vec<super::types::Task>,
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub total_size: Option<u32>,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub page_size: Option<u32>,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub next_page_token: Option<String>,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct TaskIdParams {
363 pub id: String,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
369#[serde(rename_all = "camelCase")]
370pub struct TaskPushNotificationConfig {
371 pub task_id: String,
373 pub url: String,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub authentication: Option<String>,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize)]
390#[serde(rename_all = "camelCase")]
391pub struct SendStreamingMessageResponse {
392 #[serde(flatten)]
394 pub event: StreamingEvent,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize)]
399#[serde(tag = "type", rename_all = "kebab-case")]
400pub enum StreamingEvent {
401 #[serde(rename = "message")]
403 Message {
404 message: super::types::Message,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 context_id: Option<String>,
409 #[serde(default = "default_message_kind")]
411 kind: String,
412 #[serde(default)]
414 r#final: bool,
415 },
416 #[serde(rename = "task-status")]
418 TaskStatus {
419 task_id: String,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 context_id: Option<String>,
424 status: super::types::TaskStatus,
426 #[serde(default = "default_status_kind")]
428 kind: String,
429 #[serde(default)]
431 r#final: bool,
432 },
433 #[serde(rename = "task-artifact")]
435 TaskArtifact {
436 task_id: String,
438 artifact: super::types::Artifact,
440 #[serde(default)]
442 append: bool,
443 #[serde(default)]
445 last_chunk: bool,
446 #[serde(default)]
448 r#final: bool,
449 },
450}
451
452fn default_message_kind() -> String {
453 "streaming-response".to_string()
454}
455
456fn default_status_kind() -> String {
457 "status-update".to_string()
458}
459
460impl StreamingEvent {
461 pub fn is_final(&self) -> bool {
463 match self {
464 StreamingEvent::Message { r#final, .. } => *r#final,
465 StreamingEvent::TaskStatus { r#final, .. } => *r#final,
466 StreamingEvent::TaskArtifact { r#final, .. } => *r#final,
467 }
468 }
469
470 pub fn task_id(&self) -> Option<&str> {
472 match self {
473 StreamingEvent::Message { .. } => None,
474 StreamingEvent::TaskStatus { task_id, .. } => Some(task_id),
475 StreamingEvent::TaskArtifact { task_id, .. } => Some(task_id),
476 }
477 }
478
479 pub fn context_id(&self) -> Option<&str> {
481 match self {
482 StreamingEvent::Message { context_id, .. } => context_id.as_deref(),
483 StreamingEvent::TaskStatus { context_id, .. } => context_id.as_deref(),
484 StreamingEvent::TaskArtifact { .. } => None,
485 }
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn test_json_rpc_request_creation() {
495 let request = JsonRpcRequest::with_string_id(
496 METHOD_MESSAGE_SEND,
497 Some(serde_json::json!({"message": {}})),
498 "req-1",
499 );
500 assert_eq!(request.jsonrpc, "2.0");
501 assert_eq!(request.method, "message/send");
502 assert_eq!(request.id, Value::String("req-1".to_string()));
503 }
504
505 #[test]
506 fn test_json_rpc_response_success() {
507 let response = JsonRpcResponse::success(
508 serde_json::json!({"status": "ok"}),
509 Value::String("req-1".to_string()),
510 );
511 assert!(response.is_success());
512 assert!(!response.is_error());
513 }
514
515 #[test]
516 fn test_json_rpc_response_error() {
517 let error = JsonRpcError::task_not_found("task-123");
518 let response = JsonRpcResponse::error(error, Value::String("req-1".to_string()));
519 assert!(!response.is_success());
520 assert!(response.is_error());
521 }
522
523 #[test]
524 fn test_error_code_serialization() {
525 let error = JsonRpcError::from_code(A2aErrorCode::TaskNotFound, "Task not found");
526 assert_eq!(error.code, -32001);
527 }
528
529 #[test]
530 fn test_streaming_event_message() {
531 let event = StreamingEvent::Message {
532 message: super::super::types::Message::agent_text("Response"),
533 context_id: Some("ctx-1".to_string()),
534 kind: "streaming-response".to_string(),
535 r#final: false,
536 };
537 assert!(!event.is_final());
538 assert_eq!(event.context_id(), Some("ctx-1"));
539 }
540
541 #[test]
542 fn test_streaming_event_task_status() {
543 let event = StreamingEvent::TaskStatus {
544 task_id: "task-1".to_string(),
545 context_id: None,
546 status: super::super::types::TaskStatus::new(super::super::types::TaskState::Completed),
547 kind: "status-update".to_string(),
548 r#final: true,
549 };
550 assert!(event.is_final());
551 assert_eq!(event.task_id(), Some("task-1"));
552 }
553
554 #[test]
555 fn test_streaming_event_artifact() {
556 let artifact = super::super::types::Artifact::text("art-1", "Output");
557 let event = StreamingEvent::TaskArtifact {
558 task_id: "task-1".to_string(),
559 artifact,
560 append: false,
561 last_chunk: true,
562 r#final: false,
563 };
564 assert!(!event.is_final());
565 assert_eq!(event.task_id(), Some("task-1"));
566 }
567
568 #[test]
569 fn test_send_streaming_message_response_serialization() {
570 let msg = super::super::types::Message::agent_text("Hello");
571 let response = SendStreamingMessageResponse {
572 event: StreamingEvent::Message {
573 message: msg,
574 context_id: Some("ctx-1".to_string()),
575 kind: "streaming-response".to_string(),
576 r#final: false,
577 },
578 };
579
580 let json = serde_json::to_string(&response).expect("serialize");
581 assert!(json.contains("streaming-response"));
582 assert!(json.contains("message"));
583
584 let deserialized: SendStreamingMessageResponse =
585 serde_json::from_str(&json).expect("deserialize");
586 match deserialized.event {
587 StreamingEvent::Message { ref kind, .. } => {
588 assert_eq!(kind, "streaming-response");
589 }
590 _ => panic!("Expected Message event"),
591 }
592 }
593
594 #[test]
595 fn test_task_push_notification_config() {
596 let config = TaskPushNotificationConfig {
597 task_id: "task-1".to_string(),
598 url: "https://example.com/webhook".to_string(),
599 authentication: Some("Bearer token123".to_string()),
600 };
601
602 let json = serde_json::to_string(&config).expect("serialize");
603 let deserialized: TaskPushNotificationConfig =
604 serde_json::from_str(&json).expect("deserialize");
605
606 assert_eq!(deserialized.task_id, "task-1");
607 assert_eq!(deserialized.url, "https://example.com/webhook");
608 assert!(deserialized.authentication.is_some());
609 }
610}