use crate::error::A2aError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: serde_json::Value,
pub method: String,
#[serde(default)]
pub params: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum A2aMethod {
SendMessage,
SendStreamingMessage,
GetTask,
ListTasks,
CancelTask,
SubscribeToTask,
CreateTaskPushNotificationConfig,
GetTaskPushNotificationConfig,
ListTaskPushNotificationConfigs,
DeleteTaskPushNotificationConfig,
GetExtendedAgentCard,
}
impl A2aMethod {
#[allow(clippy::should_implement_trait)] pub fn from_str(s: &str) -> Result<Self, A2aError> {
Ok(match s {
"SendMessage" => Self::SendMessage,
"SendStreamingMessage" => Self::SendStreamingMessage,
"GetTask" => Self::GetTask,
"ListTasks" => Self::ListTasks,
"CancelTask" => Self::CancelTask,
"SubscribeToTask" => Self::SubscribeToTask,
"CreateTaskPushNotificationConfig" => Self::CreateTaskPushNotificationConfig,
"GetTaskPushNotificationConfig" => Self::GetTaskPushNotificationConfig,
"ListTaskPushNotificationConfigs" => Self::ListTaskPushNotificationConfigs,
"DeleteTaskPushNotificationConfig" => Self::DeleteTaskPushNotificationConfig,
"GetExtendedAgentCard" => Self::GetExtendedAgentCard,
other => return Err(A2aError::MethodNotFound(other.to_string())),
})
}
}
#[derive(Debug, Clone)]
pub struct A2aHeaders {
pub authorization: Option<String>,
pub content_type: Option<String>,
pub a2a_version: String,
pub a2a_extensions: Vec<String>,
}
impl A2aHeaders {
pub fn decode_from(headers: &klieo_core::Headers) -> Self {
let get = |k: &str| headers.get(k).cloned();
Self {
authorization: get("Authorization"),
content_type: get("Content-Type"),
a2a_version: get("A2A-Version").unwrap_or_else(|| "1.0".to_string()),
a2a_extensions: get("A2A-Extensions")
.map(|v| {
v.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
})
.unwrap_or_default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn envelope_request_round_trips() {
let req = JsonRpcRequest {
jsonrpc: "2.0".into(),
id: json!(42),
method: "SendMessage".into(),
params: json!({"message": {"messageId": "m-1", "role": "user", "parts": []}}),
};
let v = serde_json::to_value(&req).unwrap();
let back: JsonRpcRequest = serde_json::from_value(v).unwrap();
assert_eq!(back.method, "SendMessage");
assert_eq!(back.id, json!(42));
}
#[test]
fn method_send_message_parses() {
let m = A2aMethod::from_str("SendMessage").unwrap();
assert_eq!(m, A2aMethod::SendMessage);
}
#[test]
fn unknown_method_returns_method_not_found() {
let err = A2aMethod::from_str("Frobnicate").unwrap_err();
assert!(matches!(err, crate::error::A2aError::MethodNotFound(_)));
}
#[test]
fn headers_decode_with_all_four_fields() {
let mut h = klieo_core::Headers::new();
h.insert("Authorization".into(), "Bearer abc".into());
h.insert("Content-Type".into(), "application/json".into());
h.insert("A2A-Version".into(), "1.0".into());
h.insert("A2A-Extensions".into(), "ext1,ext2".into());
let decoded = A2aHeaders::decode_from(&h);
assert_eq!(decoded.authorization.as_deref(), Some("Bearer abc"));
assert_eq!(decoded.a2a_version, "1.0");
assert_eq!(
decoded.a2a_extensions,
vec!["ext1".to_string(), "ext2".to_string()]
);
}
#[test]
fn missing_version_defaults_to_one_zero() {
let h = klieo_core::Headers::new();
let decoded = A2aHeaders::decode_from(&h);
assert_eq!(decoded.a2a_version, "1.0");
}
}