use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum A2ATaskState {
Pending,
Running,
Completed,
Failed,
Cancelled,
}
impl Default for A2ATaskState {
fn default() -> Self {
Self::Pending
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A2AAgentSkill {
pub name: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_schema: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct A2AAgentCapabilities {
#[serde(default)]
pub streaming: bool,
#[serde(default)]
pub push_notifications: bool,
#[serde(default)]
pub state_transfer: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A2AAgentCard {
pub name: String,
pub description: String,
pub url: String,
pub version: String,
#[serde(default)]
pub capabilities: A2AAgentCapabilities,
#[serde(default)]
pub skills: Vec<A2AAgentSkill>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl A2AAgentCard {
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(),
version: "1.0.0".to_string(),
capabilities: A2AAgentCapabilities::default(),
skills: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
pub fn with_streaming(mut self) -> Self {
self.capabilities.streaming = true;
self
}
pub fn skill(mut self, skill: A2AAgentSkill) -> Self {
self.skills.push(skill);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A2ATask {
pub id: String,
pub state: A2ATaskState,
pub input: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct A2A {
pub name: String,
pub description: String,
pub url: String,
pub version: String,
pub prefix: String,
pub tags: Vec<String>,
agent_card: Option<A2AAgentCard>,
}
impl A2A {
pub fn new(name: impl Into<String>, url: impl Into<String>) -> Self {
let name = name.into();
Self {
description: format!("{} via A2A", &name),
name,
url: url.into(),
version: "1.0.0".to_string(),
prefix: String::new(),
tags: vec!["A2A".to_string()],
agent_card: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = prefix.into();
self
}
pub fn get_agent_card(&self) -> A2AAgentCard {
A2AAgentCard::new(&self.name, &self.description, &self.url)
.version(&self.version)
.with_streaming()
}
pub fn get_status(&self) -> HashMap<String, String> {
let mut status = HashMap::new();
status.insert("status".to_string(), "ok".to_string());
status.insert("name".to_string(), self.name.clone());
status.insert("version".to_string(), self.version.clone());
status
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AGUIRole {
User,
Assistant,
System,
Tool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AGUIMessage {
pub role: AGUIRole,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AGUIRunInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub run_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thread_id: Option<String>,
#[serde(default)]
pub messages: Vec<AGUIMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub forwarded_props: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AGUIEventType {
RunStarted,
RunFinished,
RunError,
TextDelta,
ToolCallStarted,
ToolCallFinished,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AGUIEvent {
pub event_type: AGUIEventType,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub run_id: Option<String>,
}
impl AGUIEvent {
pub fn run_started(run_id: impl Into<String>) -> Self {
Self {
event_type: AGUIEventType::RunStarted,
data: None,
run_id: Some(run_id.into()),
}
}
pub fn run_finished(run_id: impl Into<String>) -> Self {
Self {
event_type: AGUIEventType::RunFinished,
data: None,
run_id: Some(run_id.into()),
}
}
pub fn run_error(run_id: impl Into<String>, error: impl Into<String>) -> Self {
Self {
event_type: AGUIEventType::RunError,
data: Some(serde_json::json!({ "error": error.into() })),
run_id: Some(run_id.into()),
}
}
pub fn text_delta(run_id: impl Into<String>, delta: impl Into<String>) -> Self {
Self {
event_type: AGUIEventType::TextDelta,
data: Some(serde_json::json!({ "delta": delta.into() })),
run_id: Some(run_id.into()),
}
}
}
#[derive(Debug, Clone)]
pub struct AGUI {
pub name: String,
pub description: String,
pub prefix: String,
pub tags: Vec<String>,
}
impl AGUI {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
Self {
description: format!("{} via AG-UI", &name),
name,
prefix: String::new(),
tags: vec!["AGUI".to_string()],
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = prefix.into();
self
}
pub fn get_status(&self) -> HashMap<String, String> {
let mut status = HashMap::new();
status.insert("status".to_string(), "available".to_string());
status.insert("name".to_string(), self.name.clone());
status
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_a2a_creation() {
let a2a = A2A::new("TestAgent", "http://localhost:8000/a2a")
.description("Test agent description")
.version("2.0.0");
assert_eq!(a2a.name, "TestAgent");
assert_eq!(a2a.url, "http://localhost:8000/a2a");
assert_eq!(a2a.version, "2.0.0");
}
#[test]
fn test_a2a_agent_card() {
let a2a = A2A::new("TestAgent", "http://localhost:8000/a2a");
let card = a2a.get_agent_card();
assert_eq!(card.name, "TestAgent");
assert!(card.capabilities.streaming);
}
#[test]
fn test_agui_creation() {
let agui = AGUI::new("TestAgent")
.description("Test description")
.prefix("/api/v1");
assert_eq!(agui.name, "TestAgent");
assert_eq!(agui.prefix, "/api/v1");
}
#[test]
fn test_agui_events() {
let event = AGUIEvent::run_started("run-123");
assert_eq!(event.event_type, AGUIEventType::RunStarted);
assert_eq!(event.run_id, Some("run-123".to_string()));
let error_event = AGUIEvent::run_error("run-123", "Something went wrong");
assert_eq!(error_event.event_type, AGUIEventType::RunError);
}
#[test]
fn test_a2a_task_state() {
assert_eq!(A2ATaskState::default(), A2ATaskState::Pending);
}
}