#![allow(non_snake_case)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
Agent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Submitted,
Working,
InputRequired,
Completed,
Failed,
Canceled,
Rejected,
Authenticated,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Part {
Text {
content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mediaType: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
filename: Option<String>,
},
Raw {
bytes: String,
mediaType: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
filename: Option<String>,
},
Url {
url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
mediaType: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
filename: Option<String>,
},
Data {
data: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mediaType: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
filename: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub messageId: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contextId: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub taskId: Option<String>,
pub role: Role,
pub parts: Vec<Part>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default)]
pub referenceTaskIds: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Artifact {
pub artifactId: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub parts: Vec<Part>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default)]
pub extensions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: String,
pub contextId: String,
pub status: TaskStatus,
#[serde(default)]
pub artifacts: Vec<Artifact>,
#[serde(default)]
pub history: Vec<Message>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentProvider {
pub organization: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCapabilities {
#[serde(default)]
pub streaming: bool,
#[serde(default)]
pub pushNotifications: bool,
#[serde(default)]
pub stateTransitionHistory: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentSkill {
pub id: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentInterface {
pub transport: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCardSignature {
pub alg: String,
pub kid: String,
pub signature: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentCard {
pub id: String,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<AgentProvider>,
pub capabilities: AgentCapabilities,
#[serde(default)]
pub skills: Vec<AgentSkill>,
#[serde(default)]
pub interfaces: Vec<AgentInterface>,
#[serde(default)]
pub securitySchemes: serde_json::Map<String, serde_json::Value>,
#[serde(default)]
pub security: Vec<serde_json::Value>,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<AgentCardSignature>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendMessageParams {
pub message: Message,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub configuration: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SendMessageResult {
Message(Message),
Task(Task),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskParams {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub historyLength: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTasksParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contextId: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListTasksResult {
pub tasks: Vec<Task>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nextCursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelTaskParams {
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscribeToTaskParams {
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushNotificationConfigParams {
pub taskId: String,
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushNotificationConfig {
pub id: String,
pub taskId: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPushNotificationConfigParams {
pub taskId: String,
pub id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPushNotificationConfigsParams {
pub taskId: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListPushNotificationConfigsResult {
pub configs: Vec<PushNotificationConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeletePushNotificationConfigParams {
pub taskId: String,
pub id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GetExtendedAgentCardParams {}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn part_text_serialises_with_type_text() {
let p = Part::Text {
content: "hello".into(),
metadata: None,
mediaType: None,
filename: None,
};
let v = serde_json::to_value(&p).unwrap();
assert_eq!(v["type"], "text");
assert_eq!(v["content"], "hello");
}
#[test]
fn part_raw_uses_base64_string_field() {
let p = Part::Raw {
bytes: "aGVsbG8=".into(),
mediaType: "text/plain".into(),
metadata: None,
filename: None,
};
let v = serde_json::to_value(&p).unwrap();
assert_eq!(v["type"], "raw");
assert_eq!(v["bytes"], "aGVsbG8=");
assert_eq!(v["mediaType"], "text/plain");
}
#[test]
fn message_round_trips() {
let m = Message {
messageId: "m-1".into(),
contextId: Some("c-1".into()),
taskId: Some("t-1".into()),
role: Role::User,
parts: vec![Part::Text {
content: "hi".into(),
metadata: None,
mediaType: None,
filename: None,
}],
metadata: None,
extensions: vec![],
referenceTaskIds: vec![],
};
let v = serde_json::to_value(&m).unwrap();
let back: Message = serde_json::from_value(v).unwrap();
assert_eq!(back.messageId, "m-1");
assert_eq!(back.role, Role::User);
assert_eq!(back.parts.len(), 1);
}
#[test]
fn task_with_empty_history_serialises_minimally() {
let t = Task {
id: "t-1".into(),
contextId: "c-1".into(),
status: TaskStatus::Submitted,
artifacts: vec![],
history: vec![],
metadata: None,
};
let v = serde_json::to_value(&t).unwrap();
assert_eq!(v["id"], "t-1");
assert_eq!(v["status"], "submitted");
}
#[test]
fn agent_card_round_trips() {
let card = AgentCard {
id: "agent-1".into(),
name: "Echo".into(),
description: Some("Echoes input".into()),
provider: None,
capabilities: AgentCapabilities {
streaming: false,
pushNotifications: false,
stateTransitionHistory: false,
},
skills: vec![],
interfaces: vec![],
securitySchemes: serde_json::Map::new(),
security: vec![],
extensions: vec![],
signature: None,
};
let v = serde_json::to_value(&card).unwrap();
let back: AgentCard = serde_json::from_value(v).unwrap();
assert_eq!(back.id, "agent-1");
}
#[test]
fn role_agent_serialises_as_lowercase() {
let v = serde_json::to_value(Role::Agent).unwrap();
assert_eq!(v, json!("agent"));
}
}