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(method: impl Into<String>, params: Option<Value>, id: impl Into<String>) -> Self {
76 Self::new(method, params, Value::String(id.into()))
77 }
78
79 pub fn with_numeric_id(method: impl Into<String>, params: Option<Value>, id: i64) -> Self {
81 Self::new(method, params, Value::Number(id.into()))
82 }
83
84 pub fn message_send(params: MessageSendParams, id: Value) -> Self {
86 Self::new(METHOD_MESSAGE_SEND, Some(serde_json::to_value(params).unwrap_or_default()), id)
87 }
88
89 pub fn tasks_get(task_id: impl Into<String>, id: Value) -> Self {
91 Self::new(METHOD_TASKS_GET, Some(serde_json::json!({ "id": task_id.into() })), id)
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct JsonRpcResponse {
98 pub jsonrpc: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub result: Option<Value>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub error: Option<JsonRpcError>,
106 pub id: Value,
108}
109
110impl JsonRpcResponse {
111 pub fn success(result: Value, id: Value) -> Self {
113 Self {
114 jsonrpc: JSONRPC_VERSION.to_string(),
115 result: Some(result),
116 error: None,
117 id,
118 }
119 }
120
121 pub fn error(error: JsonRpcError, id: Value) -> Self {
123 Self {
124 jsonrpc: JSONRPC_VERSION.to_string(),
125 result: None,
126 error: Some(error),
127 id,
128 }
129 }
130
131 pub fn is_success(&self) -> bool {
133 self.result.is_some() && self.error.is_none()
134 }
135
136 pub fn is_error(&self) -> bool {
138 self.error.is_some()
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct JsonRpcError {
145 pub code: i32,
147 pub message: String,
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub data: Option<Value>,
152}
153
154impl JsonRpcError {
155 pub fn new(code: i32, message: impl Into<String>) -> Self {
157 Self { code, message: message.into(), data: None }
158 }
159
160 pub fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
162 Self { code, message: message.into(), data: Some(data) }
163 }
164
165 pub fn from_code(code: A2aErrorCode, message: impl Into<String>) -> Self {
167 Self::new(code.into(), message)
168 }
169
170 pub fn parse_error(message: impl Into<String>) -> Self {
172 Self::from_code(A2aErrorCode::JsonParseError, message)
173 }
174
175 pub fn invalid_request(message: impl Into<String>) -> Self {
177 Self::from_code(A2aErrorCode::InvalidRequest, message)
178 }
179
180 pub fn method_not_found(method: impl Into<String>) -> Self {
182 Self::from_code(A2aErrorCode::MethodNotFound, format!("Method not found: {}", method.into()))
183 }
184
185 pub fn invalid_params(message: impl Into<String>) -> Self {
187 Self::from_code(A2aErrorCode::InvalidParams, message)
188 }
189
190 pub fn internal_error(message: impl Into<String>) -> Self {
192 Self::from_code(A2aErrorCode::InternalError, message)
193 }
194
195 pub fn task_not_found(task_id: impl Into<String>) -> Self {
197 Self::from_code(A2aErrorCode::TaskNotFound, format!("Task not found: {}", task_id.into()))
198 }
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct MessageSendParams {
209 pub message: super::types::Message,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub task_id: Option<String>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub context_id: Option<String>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub configuration: Option<MessageConfiguration>,
220}
221
222impl MessageSendParams {
223 pub fn new(message: super::types::Message) -> Self {
225 Self {
226 message,
227 task_id: None,
228 context_id: None,
229 configuration: None,
230 }
231 }
232
233 pub fn with_task_id(mut self, task_id: impl Into<String>) -> Self {
235 self.task_id = Some(task_id.into());
236 self
237 }
238
239 pub fn with_context_id(mut self, context_id: impl Into<String>) -> Self {
241 self.context_id = Some(context_id.into());
242 self
243 }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249pub struct MessageConfiguration {
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub accepted_input_modes: Option<Vec<String>>,
253 #[serde(skip_serializing_if = "Option::is_none")]
255 pub accepted_output_modes: Option<Vec<String>>,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub history_length: Option<u32>,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub push_notification_config: Option<PushNotificationConfig>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct PushNotificationConfig {
268 pub url: String,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub authentication: Option<String>,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct TaskQueryParams {
279 pub id: String,
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub history_length: Option<u32>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, Default)]
288#[serde(rename_all = "camelCase")]
289pub struct ListTasksParams {
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub context_id: Option<String>,
293 #[serde(skip_serializing_if = "Option::is_none")]
295 pub status: Option<super::types::TaskState>,
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub page_size: Option<u32>,
299 #[serde(skip_serializing_if = "Option::is_none")]
301 pub page_token: Option<String>,
302 #[serde(skip_serializing_if = "Option::is_none")]
304 pub history_length: Option<u32>,
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub last_updated_after: Option<String>,
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub include_artifacts: Option<bool>,
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub metadata: Option<Value>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct ListTasksResult {
320 pub tasks: Vec<super::types::Task>,
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub total_size: Option<u32>,
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub page_size: Option<u32>,
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub next_page_token: Option<String>,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct TaskIdParams {
337 pub id: String,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct TaskPushNotificationConfig {
345 pub task_id: String,
347 pub url: String,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub authentication: Option<String>,
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
364#[serde(rename_all = "camelCase")]
365pub struct SendStreamingMessageResponse {
366 #[serde(flatten)]
368 pub event: StreamingEvent,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(tag = "type", rename_all = "kebab-case")]
374pub enum StreamingEvent {
375 #[serde(rename = "message")]
377 Message {
378 message: super::types::Message,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 context_id: Option<String>,
383 #[serde(default = "default_message_kind")]
385 kind: String,
386 #[serde(default)]
388 r#final: bool,
389 },
390 #[serde(rename = "task-status")]
392 TaskStatus {
393 task_id: String,
395 #[serde(skip_serializing_if = "Option::is_none")]
397 context_id: Option<String>,
398 status: super::types::TaskStatus,
400 #[serde(default = "default_status_kind")]
402 kind: String,
403 #[serde(default)]
405 r#final: bool,
406 },
407 #[serde(rename = "task-artifact")]
409 TaskArtifact {
410 task_id: String,
412 artifact: super::types::Artifact,
414 #[serde(default)]
416 append: bool,
417 #[serde(default)]
419 last_chunk: bool,
420 #[serde(default)]
422 r#final: bool,
423 },
424 #[serde(other)]
426 Unknown,
427}
428
429fn default_message_kind() -> String {
430 "streaming-response".to_string()
431}
432
433fn default_status_kind() -> String {
434 "status-update".to_string()
435}
436
437impl StreamingEvent {
438 pub fn is_final(&self) -> bool {
440 match self {
441 StreamingEvent::Message { r#final, .. } => *r#final,
442 StreamingEvent::TaskStatus { r#final, .. } => *r#final,
443 StreamingEvent::TaskArtifact { r#final, .. } => *r#final,
444 StreamingEvent::Unknown => false,
445 }
446 }
447
448 pub fn task_id(&self) -> Option<&str> {
450 match self {
451 StreamingEvent::Message { .. } => None,
452 StreamingEvent::TaskStatus { task_id, .. } => Some(task_id),
453 StreamingEvent::TaskArtifact { task_id, .. } => Some(task_id),
454 StreamingEvent::Unknown => None,
455 }
456 }
457
458 pub fn context_id(&self) -> Option<&str> {
460 match self {
461 StreamingEvent::Message { context_id, .. } => context_id.as_deref(),
462 StreamingEvent::TaskStatus { context_id, .. } => context_id.as_deref(),
463 StreamingEvent::TaskArtifact { .. } | StreamingEvent::Unknown => None,
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
473 fn test_json_rpc_request_creation() {
474 let request =
475 JsonRpcRequest::with_string_id(METHOD_MESSAGE_SEND, Some(serde_json::json!({"message": {}})), "req-1");
476 assert_eq!(request.jsonrpc, "2.0");
477 assert_eq!(request.method, "message/send");
478 assert_eq!(request.id, Value::String("req-1".to_string()));
479 }
480
481 #[test]
482 fn test_json_rpc_response_success() {
483 let response =
484 JsonRpcResponse::success(serde_json::json!({"status": "ok"}), Value::String("req-1".to_string()));
485 assert!(response.is_success());
486 assert!(!response.is_error());
487 }
488
489 #[test]
490 fn test_json_rpc_response_error() {
491 let error = JsonRpcError::task_not_found("task-123");
492 let response = JsonRpcResponse::error(error, Value::String("req-1".to_string()));
493 assert!(!response.is_success());
494 assert!(response.is_error());
495 }
496
497 #[test]
498 fn test_error_code_serialization() {
499 let error = JsonRpcError::from_code(A2aErrorCode::TaskNotFound, "Task not found");
500 assert_eq!(error.code, -32001);
501 }
502
503 #[test]
504 fn test_streaming_event_message() {
505 let event = StreamingEvent::Message {
506 message: super::super::types::Message::agent_text("Response"),
507 context_id: Some("ctx-1".to_string()),
508 kind: "streaming-response".to_string(),
509 r#final: false,
510 };
511 assert!(!event.is_final());
512 assert_eq!(event.context_id(), Some("ctx-1"));
513 }
514
515 #[test]
516 fn test_streaming_event_task_status() {
517 let event = StreamingEvent::TaskStatus {
518 task_id: "task-1".to_string(),
519 context_id: None,
520 status: super::super::types::TaskStatus::new(super::super::types::TaskState::Completed),
521 kind: "status-update".to_string(),
522 r#final: true,
523 };
524 assert!(event.is_final());
525 assert_eq!(event.task_id(), Some("task-1"));
526 }
527
528 #[test]
529 fn test_streaming_event_artifact() {
530 let artifact = super::super::types::Artifact::text("art-1", "Output");
531 let event = StreamingEvent::TaskArtifact {
532 task_id: "task-1".to_string(),
533 artifact,
534 append: false,
535 last_chunk: true,
536 r#final: false,
537 };
538 assert!(!event.is_final());
539 assert_eq!(event.task_id(), Some("task-1"));
540 }
541
542 #[test]
543 fn test_send_streaming_message_response_serialization() {
544 let msg = super::super::types::Message::agent_text("Hello");
545 let response = SendStreamingMessageResponse {
546 event: StreamingEvent::Message {
547 message: msg,
548 context_id: Some("ctx-1".to_string()),
549 kind: "streaming-response".to_string(),
550 r#final: false,
551 },
552 };
553
554 let json = serde_json::to_string(&response).expect("serialize");
555 assert!(json.contains("streaming-response"));
556 assert!(json.contains("message"));
557
558 let deserialized: SendStreamingMessageResponse = serde_json::from_str(&json).expect("deserialize");
559 match deserialized.event {
560 StreamingEvent::Message { ref kind, .. } => {
561 assert_eq!(kind, "streaming-response");
562 }
563 _ => panic!("Expected Message event"),
564 }
565 }
566
567 #[test]
568 fn test_task_push_notification_config() {
569 let config = TaskPushNotificationConfig {
570 task_id: "task-1".to_string(),
571 url: "https://example.com/webhook".to_string(),
572 authentication: Some("Bearer token123".to_string()),
573 };
574
575 let json = serde_json::to_string(&config).expect("serialize");
576 let deserialized: TaskPushNotificationConfig = serde_json::from_str(&json).expect("deserialize");
577
578 assert_eq!(deserialized.task_id, "task-1");
579 assert_eq!(deserialized.url, "https://example.com/webhook");
580 assert!(deserialized.authentication.is_some());
581 }
582}