use crate::models::a2a::jsonrpc::{JSON_RPC_VERSION_2_0, JsonRpcResponse, RequestId};
use crate::models::a2a::{AgentCard, Message, Task, TaskState};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use systemprompt_identifiers::{ContextId, TaskId};
use systemprompt_models::a2a::{SendMessageResult, methods};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
pub message: Message,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub configuration: Option<SendMessageConfiguration>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageConfiguration {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accepted_output_modes: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub history_length: Option<u32>,
#[serde(default)]
pub return_immediately: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetTaskParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
pub id: TaskId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub history_length: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CancelTaskParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
pub id: TaskId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SubscribeToTaskParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
pub id: TaskId,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct GetExtendedAgentCardParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ListTasksParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_id: Option<ContextId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status: Option<TaskState>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub page_size: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub page_token: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub history_length: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub status_timestamp_after: Option<chrono::DateTime<chrono::Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_artifacts: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ListTasksResult {
pub tasks: Vec<Task>,
pub next_page_token: String,
pub page_size: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub total_size: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct A2aRequest {
pub method: String,
pub params: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum A2aResponse {
SendMessage(SendMessageResponse),
GetTask(GetTaskResponse),
ListTasks(ListTasksResponse),
CancelTask(CancelTaskResponse),
GetExtendedAgentCard(GetExtendedAgentCardResponse),
}
pub type SendMessageResponse = JsonRpcResponse<SendMessageResult>;
pub type GetTaskResponse = JsonRpcResponse<Task>;
pub type ListTasksResponse = JsonRpcResponse<ListTasksResult>;
pub type CancelTaskResponse = JsonRpcResponse<Task>;
pub type GetExtendedAgentCardResponse = JsonRpcResponse<AgentCard>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct A2aJsonRpcRequest {
pub jsonrpc: String,
pub method: String,
#[serde(default)]
pub params: serde_json::Value,
pub id: RequestId,
}
impl A2aJsonRpcRequest {
pub fn parse_request(&self) -> Result<A2aRequestParams, A2aParseError> {
match self.method.as_str() {
methods::SEND_MESSAGE => Ok(A2aRequestParams::SendMessage(self.parse_params()?)),
methods::SEND_STREAMING_MESSAGE => {
Ok(A2aRequestParams::SendStreamingMessage(self.parse_params()?))
},
methods::GET_TASK => Ok(A2aRequestParams::GetTask(self.parse_params()?)),
methods::LIST_TASKS => Ok(A2aRequestParams::ListTasks(self.parse_params()?)),
methods::CANCEL_TASK => Ok(A2aRequestParams::CancelTask(self.parse_params()?)),
methods::SUBSCRIBE_TO_TASK => {
Ok(A2aRequestParams::SubscribeToTask(self.parse_params()?))
},
methods::GET_EXTENDED_AGENT_CARD => {
Ok(A2aRequestParams::GetExtendedAgentCard(self.parse_params()?))
},
_ => Err(A2aParseError::UnsupportedMethod {
method: self.method.clone(),
}),
}
}
fn parse_params<T: serde::de::DeserializeOwned>(&self) -> Result<T, A2aParseError> {
let params = if self.params.is_null() {
serde_json::Value::Object(serde_json::Map::new())
} else {
self.params.clone()
};
serde_json::from_value(params).map_err(|e| A2aParseError::InvalidParams {
method: self.method.clone(),
source: Arc::new(e),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum A2aRequestParams {
SendMessage(SendMessageParams),
SendStreamingMessage(SendMessageParams),
GetTask(GetTaskParams),
ListTasks(ListTasksParams),
CancelTask(CancelTaskParams),
SubscribeToTask(SubscribeToTaskParams),
GetExtendedAgentCard(GetExtendedAgentCardParams),
}
impl A2aRequestParams {
#[must_use]
pub fn tenant(&self) -> Option<&str> {
match self {
Self::SendMessage(p) | Self::SendStreamingMessage(p) => p.tenant.as_deref(),
Self::GetTask(p) => p.tenant.as_deref(),
Self::ListTasks(p) => p.tenant.as_deref(),
Self::CancelTask(p) => p.tenant.as_deref(),
Self::SubscribeToTask(p) => p.tenant.as_deref(),
Self::GetExtendedAgentCard(p) => p.tenant.as_deref(),
}
}
}
#[derive(Debug, thiserror::Error, Clone)]
pub enum A2aParseError {
#[error("Unsupported method: {method}")]
UnsupportedMethod { method: String },
#[error("Invalid parameters for method '{method}': {source}")]
InvalidParams {
method: String,
#[source]
source: Arc<serde_json::Error>,
},
}
fn ok<T>(result: T, id: RequestId) -> JsonRpcResponse<T> {
JsonRpcResponse {
jsonrpc: JSON_RPC_VERSION_2_0.to_owned(),
result: Some(result),
error: None,
id,
}
}
impl A2aResponse {
pub fn send_message(result: SendMessageResult, id: RequestId) -> Self {
Self::SendMessage(ok(result, id))
}
pub fn get_task(task: Task, id: RequestId) -> Self {
Self::GetTask(ok(task, id))
}
pub fn list_tasks(result: ListTasksResult, id: RequestId) -> Self {
Self::ListTasks(ok(result, id))
}
pub fn cancel_task(task: Task, id: RequestId) -> Self {
Self::CancelTask(ok(task, id))
}
pub fn extended_agent_card(card: AgentCard, id: RequestId) -> Self {
Self::GetExtendedAgentCard(ok(card, id))
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct TaskNotFoundError {
pub task_id: TaskId,
pub message: String,
pub code: i32,
pub data: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct TaskNotCancelableError {
pub task_id: TaskId,
pub state: TaskState,
pub message: String,
pub code: i32,
pub data: serde_json::Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct UnsupportedOperationError {
pub operation: String,
pub message: String,
pub code: i32,
pub data: serde_json::Value,
}