use crate::adapters::a2a::{A2aProtocolRequest, A2aProtocolResponse};
use crate::adapters::mcp::McpJsonRpcResponse;
use crate::models::RequestEnvelope;
use crate::wire::{SHARED_CLAIMS_KIND, SharedTrustClaims};
use serde_json::{Value, json};
use std::fmt;
#[derive(Debug)]
pub struct ClientError {
pub kind: ClientErrorKind,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClientErrorKind {
Network,
Serialization,
InvalidResponse,
}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}: {}", self.kind, self.message)
}
}
impl std::error::Error for ClientError {}
impl ClientError {
fn network(e: impl fmt::Display) -> Self {
Self {
kind: ClientErrorKind::Network,
message: e.to_string(),
}
}
fn serialization(e: impl fmt::Display) -> Self {
Self {
kind: ClientErrorKind::Serialization,
message: e.to_string(),
}
}
fn invalid_response(e: impl fmt::Display) -> Self {
Self {
kind: ClientErrorKind::InvalidResponse,
message: e.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct HttpTrustResponse {
pub status_code: u16,
pub allowed: bool,
pub stage: String,
pub reason: String,
}
impl HttpTrustResponse {
pub fn is_allowed(&self) -> bool {
self.allowed
}
}
#[derive(Debug, Clone)]
pub struct McpTrustResponse {
pub status_code: u16,
pub response: McpJsonRpcResponse,
}
impl McpTrustResponse {
pub fn is_allowed(&self) -> bool {
self.response.error.is_none()
}
}
#[derive(Debug, Clone)]
pub struct A2aTrustResponse {
pub status_code: u16,
pub response: A2aProtocolResponse,
}
impl A2aTrustResponse {
pub fn is_allowed(&self) -> bool {
self.response.status == "ok"
}
}
pub struct DelegatedClient {
inner: reqwest::Client,
}
impl Default for DelegatedClient {
fn default() -> Self {
Self::new()
}
}
impl DelegatedClient {
pub fn new() -> Self {
Self {
inner: reqwest::Client::new(),
}
}
pub fn with_client(client: reqwest::Client) -> Self {
Self { inner: client }
}
pub async fn evaluate_http(
&self,
url: &str,
envelope: &RequestEnvelope,
) -> Result<HttpTrustResponse, ClientError> {
let resp = self
.inner
.post(url)
.json(envelope)
.send()
.await
.map_err(ClientError::network)?;
let status_code = resp.status().as_u16();
let body: Value = resp.json().await.map_err(ClientError::invalid_response)?;
Ok(HttpTrustResponse {
status_code,
allowed: body
.get("allowed")
.and_then(Value::as_bool)
.unwrap_or(false),
stage: body
.get("stage")
.and_then(Value::as_str)
.unwrap_or("")
.to_string(),
reason: body
.get("reason")
.and_then(Value::as_str)
.unwrap_or("")
.to_string(),
})
}
pub async fn evaluate_mcp(
&self,
url: &str,
id: impl Into<Value>,
method: &str,
extra_params: Value,
envelope: &RequestEnvelope,
) -> Result<McpTrustResponse, ClientError> {
let claims = SharedTrustClaims {
spec_version: envelope.spec_version.clone(),
kind: SHARED_CLAIMS_KIND.to_string(),
request_id: envelope.request_id.clone(),
profile: envelope.profile.clone(),
agent_id: envelope.agent_id.clone(),
delegator_id: envelope.delegator_id.clone(),
audience: envelope.audience.clone(),
action: envelope.action.clone(),
resource: envelope.resource.clone(),
runtime_context: envelope.runtime_context.clone(),
identity_document: envelope.identity_document.clone(),
token: envelope.token.clone(),
};
let trust_value = serde_json::to_value(&claims).map_err(ClientError::serialization)?;
let mut params = extra_params.as_object().cloned().unwrap_or_default();
params.insert("_trust".to_string(), trust_value);
let body = json!({
"jsonrpc": "2.0",
"id": id.into(),
"method": method,
"params": params
});
let resp = self
.inner
.post(url)
.json(&body)
.send()
.await
.map_err(ClientError::network)?;
let status_code = resp.status().as_u16();
let response: McpJsonRpcResponse =
resp.json().await.map_err(ClientError::invalid_response)?;
Ok(McpTrustResponse {
status_code,
response,
})
}
pub async fn evaluate_a2a(
&self,
url: &str,
message_id: impl Into<String>,
protocol_version: impl Into<String>,
message_type: impl Into<String>,
payload: Value,
envelope: &RequestEnvelope,
) -> Result<A2aTrustResponse, ClientError> {
let claims = SharedTrustClaims {
spec_version: envelope.spec_version.clone(),
kind: SHARED_CLAIMS_KIND.to_string(),
request_id: envelope.request_id.clone(),
profile: envelope.profile.clone(),
agent_id: envelope.agent_id.clone(),
delegator_id: envelope.delegator_id.clone(),
audience: envelope.audience.clone(),
action: envelope.action.clone(),
resource: envelope.resource.clone(),
runtime_context: envelope.runtime_context.clone(),
identity_document: envelope.identity_document.clone(),
token: envelope.token.clone(),
};
let request = A2aProtocolRequest {
message_id: message_id.into(),
protocol_version: protocol_version.into(),
message_type: message_type.into(),
trust_claims: claims,
payload,
};
let resp = self
.inner
.post(url)
.json(&request)
.send()
.await
.map_err(ClientError::network)?;
let status_code = resp.status().as_u16();
let response: A2aProtocolResponse =
resp.json().await.map_err(ClientError::invalid_response)?;
Ok(A2aTrustResponse {
status_code,
response,
})
}
}