use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::model::{default_input_modes, default_output_modes, default_protocol_version};
pub const A2A_VERSION_V101: &str = "1.0.1";
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum A2ATransport {
JsonRpc,
HttpJson,
Grpc,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AgentInterface {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub transport: A2ATransport,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant: Option<String>,
}
impl AgentInterface {
pub fn new(
protocol_version: impl Into<String>,
transport: A2ATransport,
url: impl Into<String>,
) -> Self {
Self {
protocol_version: protocol_version.into(),
transport,
url: url.into(),
tenant: None,
}
}
pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
self.tenant = Some(tenant.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSkill {
pub id: String,
pub name: String,
pub description: String,
}
impl AgentSkill {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
description: description.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCard {
pub name: String,
pub description: String,
pub url: String,
#[serde(default)]
pub skills: Vec<AgentSkill>,
#[serde(default = "default_protocol_version", rename = "protocolVersion")]
pub protocol_version: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "securitySchemes")]
pub security_schemes: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interfaces: Option<Value>,
#[serde(
default,
skip_serializing_if = "Vec::is_empty",
rename = "supportedInterfaces"
)]
pub supported_interfaces: Vec<AgentInterface>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub documentation_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authentication: Option<Vec<String>>,
#[serde(default = "default_input_modes")]
pub default_input_modes: Vec<String>,
#[serde(default = "default_output_modes")]
pub default_output_modes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "dataClass")]
pub data_class: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub jurisdiction: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub capabilities: Vec<String>,
}
impl AgentCard {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
url: impl Into<String>,
) -> Self {
Self {
name: name.into(),
description: description.into(),
url: url.into(),
skills: Vec::new(),
protocol_version: default_protocol_version(),
security_schemes: None,
interfaces: None,
supported_interfaces: Vec::new(),
provider: None,
documentation_url: None,
authentication: None,
default_input_modes: default_input_modes(),
default_output_modes: default_output_modes(),
signature: None,
data_class: None,
jurisdiction: None,
capabilities: Vec::new(),
}
}
pub fn with_skill(mut self, skill: AgentSkill) -> Self {
self.skills.push(skill);
self
}
pub fn with_protocol_version(mut self, version: impl Into<String>) -> Self {
self.protocol_version = version.into();
self
}
pub fn with_security_schemes(mut self, schemes: Value) -> Self {
self.security_schemes = Some(schemes);
self
}
pub fn with_interfaces(mut self, interfaces: Value) -> Self {
self.interfaces = Some(interfaces);
self
}
pub fn with_supported_interface(mut self, interface: AgentInterface) -> Self {
self.supported_interfaces.push(interface);
self
}
pub fn negotiate(
&self,
transport: A2ATransport,
client_versions: &[&str],
) -> Result<AgentInterface, String> {
self.supported_interfaces
.iter()
.find(|i| {
i.transport == transport && client_versions.contains(&i.protocol_version.as_str())
})
.cloned()
.ok_or_else(|| {
format!(
"no mutually supported interface: transport={transport:?} client_versions={client_versions:?}"
)
})
}
pub fn is_v101(&self) -> bool {
!self.supported_interfaces.is_empty()
}
pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
self.provider = Some(provider.into());
self
}
pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
self.documentation_url = Some(url.into());
self
}
pub fn with_authentication(mut self, schemes: Vec<String>) -> Self {
self.authentication = Some(schemes);
self
}
pub fn with_signature(mut self, signature: impl Into<String>) -> Self {
self.signature = Some(signature.into());
self
}
pub fn with_data_class(mut self, class: impl Into<String>) -> Self {
self.data_class = Some(class.into());
self
}
pub fn with_jurisdiction(mut self, jurisdiction: impl Into<String>) -> Self {
self.jurisdiction = Some(jurisdiction.into());
self
}
pub fn with_capability(mut self, capability: impl Into<String>) -> Self {
self.capabilities.push(capability.into());
self
}
}