use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub(crate) enum PartDto {
Text {
text: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
},
File {
file: FileContentDto,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
},
Data {
data: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct FileContentDto {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) bytes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) mime_type: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub(crate) enum RoleDto {
User,
Agent,
}
fn message_kind() -> String {
"message".to_owned()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MessageDto {
pub(crate) message_id: String,
pub(crate) role: RoleDto,
pub(crate) parts: Vec<PartDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) context_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) task_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) metadata: Option<serde_json::Value>,
#[serde(default = "message_kind")]
pub(crate) kind: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum TaskStateDto {
Submitted,
Working,
InputRequired,
AuthRequired,
Completed,
Failed,
Canceled,
Rejected,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TaskStatusDto {
pub(crate) state: TaskStateDto,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) message: Option<MessageDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ArtifactDto {
pub(crate) artifact_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) description: Option<String>,
pub(crate) parts: Vec<PartDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) metadata: Option<serde_json::Value>,
}
fn task_kind() -> String {
"task".to_owned()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TaskDto {
pub(crate) id: String,
pub(crate) context_id: String,
pub(crate) status: TaskStatusDto,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) artifacts: Vec<ArtifactDto>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) history: Vec<MessageDto>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) metadata: Option<serde_json::Value>,
#[serde(default = "task_kind")]
pub(crate) kind: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AgentCardDto {
pub(crate) name: String,
pub(crate) description: String,
pub(crate) version: String,
pub(crate) protocol_version: String,
pub(crate) url: String,
pub(crate) preferred_transport: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) additional_interfaces: Vec<AgentInterfaceDto>,
pub(crate) capabilities: AgentCapabilitiesDto,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) default_input_modes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) default_output_modes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) skills: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) security_schemes: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) security: Vec<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) provider: Option<AgentProviderDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AgentInterfaceDto {
pub(crate) url: String,
pub(crate) transport: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AgentCapabilitiesDto {
pub(crate) streaming: bool,
pub(crate) push_notifications: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AgentProviderDto {
pub(crate) organization: String,
pub(crate) url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MessageSendParams {
pub(crate) message: MessageDto,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) configuration: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TaskQueryParams {
pub(crate) id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) history_length: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TaskIdParams {
pub(crate) id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TaskPushConfigParams {
pub(crate) task_id: String,
pub(crate) push_notification_config: PushNotificationConfigDto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GetTaskPushConfigParams {
pub(crate) id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) push_notification_config_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ListTaskPushConfigParams {
pub(crate) id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DeleteTaskPushConfigParams {
pub(crate) id: String,
pub(crate) push_notification_config_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PushNotificationConfigDto {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) id: Option<String>,
pub(crate) url: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub(crate) token: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) authentication: Option<PushAuthDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct PushAuthDto {
pub(crate) scheme: String,
pub(crate) credentials: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TaskPushNotificationConfigDto {
pub(crate) task_id: String,
pub(crate) push_notification_config: PushNotificationConfigDto,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn part_text_serializes_with_kind_text() {
let part = PartDto::Text {
text: "hello".to_owned(),
metadata: None,
};
let value = serde_json::to_value(&part).expect("serialize must succeed");
assert_eq!(value, json!({"kind": "text", "text": "hello"}));
}
#[test]
fn part_file_with_bytes_serializes_with_kind_file() {
let part = PartDto::File {
file: FileContentDto {
bytes: Some("aGk=".to_owned()),
uri: None,
mime_type: None,
},
metadata: None,
};
let value = serde_json::to_value(&part).expect("serialize must succeed");
assert_eq!(value, json!({"kind": "file", "file": {"bytes": "aGk="}}));
}
#[test]
fn part_data_serializes_with_kind_data() {
let part = PartDto::Data {
data: json!({"a": 1}),
metadata: None,
};
let value = serde_json::to_value(&part).expect("serialize must succeed");
assert_eq!(value, json!({"kind": "data", "data": {"a": 1}}));
}
#[test]
fn file_content_mime_type_serializes_camel_case() {
let file = FileContentDto {
bytes: None,
uri: Some("https://x/y".to_owned()),
mime_type: Some("text/plain".to_owned()),
};
let value = serde_json::to_value(&file).expect("serialize must succeed");
assert_eq!(
value,
json!({"uri": "https://x/y", "mimeType": "text/plain"})
);
}
#[test]
fn task_state_input_required_is_kebab_case() {
let value =
serde_json::to_value(TaskStateDto::InputRequired).expect("serialize must succeed");
assert_eq!(value, json!("input-required"));
}
#[test]
fn task_state_auth_required_is_kebab_case() {
let value =
serde_json::to_value(TaskStateDto::AuthRequired).expect("serialize must succeed");
assert_eq!(value, json!("auth-required"));
}
#[test]
fn message_serializes_message_id_and_kind() {
let msg = MessageDto {
message_id: "abc".to_owned(),
role: RoleDto::User,
parts: vec![],
context_id: None,
task_id: None,
metadata: None,
kind: message_kind(),
};
let value = serde_json::to_value(&msg).expect("serialize must succeed");
assert_eq!(value["messageId"], json!("abc"));
assert_eq!(value["kind"], json!("message"));
assert_eq!(value["role"], json!("user"));
}
#[test]
fn message_deserializes_with_default_kind() {
let value = json!({"messageId": "abc", "role": "agent", "parts": []});
let msg: MessageDto = serde_json::from_value(value).expect("deserialize must succeed");
assert_eq!(msg.kind, "message", "kind must default to 'message'");
assert_eq!(msg.role, RoleDto::Agent);
}
#[test]
fn task_serializes_context_id_and_kind() {
let task = TaskDto {
id: "t1".to_owned(),
context_id: "c1".to_owned(),
status: TaskStatusDto {
state: TaskStateDto::Working,
message: None,
timestamp: None,
},
artifacts: vec![],
history: vec![],
metadata: None,
kind: task_kind(),
};
let value = serde_json::to_value(&task).expect("serialize must succeed");
assert_eq!(value["contextId"], json!("c1"));
assert_eq!(value["kind"], json!("task"));
assert_eq!(value["status"]["state"], json!("working"));
}
}