use serde::{Deserialize, Serialize};
use crate::agent_card::AgentCard;
use crate::message::Message;
use crate::task::Task;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum SendMessageResponse {
Task(Task),
Message(Message),
}
impl Serialize for SendMessageResponse {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Task(task) => task.serialize(serializer),
Self::Message(msg) => msg.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for SendMessageResponse {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(deserializer)?;
if value.get("role").is_some() {
serde_json::from_value::<Message>(value.clone())
.map(SendMessageResponse::Message)
.or_else(|_| {
serde_json::from_value::<Task>(value)
.map(SendMessageResponse::Task)
.map_err(serde::de::Error::custom)
})
} else {
serde_json::from_value::<Task>(value)
.map(SendMessageResponse::Task)
.map_err(serde::de::Error::custom)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskListResponse {
pub tasks: Vec<Task>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_page_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_size: Option<u32>,
}
impl TaskListResponse {
#[must_use]
pub const fn new(tasks: Vec<Task>) -> Self {
Self {
tasks,
next_page_token: None,
page_size: None,
total_size: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListPushConfigsResponse {
pub configs: Vec<crate::push::TaskPushNotificationConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_page_token: Option<String>,
}
pub type AuthenticatedExtendedCardResponse = AgentCard;
#[cfg(test)]
mod tests {
use super::*;
use crate::message::{MessageId, MessageRole, Part};
use crate::task::{ContextId, TaskId, TaskState, TaskStatus};
fn make_task() -> Task {
Task {
id: TaskId::new("t1"),
context_id: ContextId::new("c1"),
status: TaskStatus::new(TaskState::Completed),
history: None,
artifacts: None,
metadata: None,
}
}
fn make_message() -> Message {
Message {
id: MessageId::new("m1"),
role: MessageRole::Agent,
parts: vec![Part::text("hi")],
task_id: None,
context_id: None,
reference_task_ids: None,
extensions: None,
metadata: None,
}
}
#[test]
fn send_message_response_task_variant() {
let resp = SendMessageResponse::Task(make_task());
let json = serde_json::to_string(&resp).expect("serialize");
assert!(
!json.contains("\"kind\""),
"v1.0 should not have kind: {json}"
);
let back: SendMessageResponse = serde_json::from_str(&json).expect("deserialize");
match &back {
SendMessageResponse::Task(t) => {
assert_eq!(t.id, TaskId::new("t1"));
assert_eq!(t.status.state, TaskState::Completed);
}
_ => panic!("expected Task variant"),
}
}
#[test]
fn send_message_response_message_variant() {
let resp = SendMessageResponse::Message(make_message());
let json = serde_json::to_string(&resp).expect("serialize");
assert!(
!json.contains("\"kind\""),
"v1.0 should not have kind: {json}"
);
let back: SendMessageResponse = serde_json::from_str(&json).expect("deserialize");
match &back {
SendMessageResponse::Message(m) => {
assert_eq!(m.id, MessageId::new("m1"));
assert_eq!(m.role, MessageRole::Agent);
}
_ => panic!("expected Message variant"),
}
}
#[test]
fn send_message_response_fallback_role_field_to_task() {
let json = serde_json::json!({
"id": "t1",
"contextId": "c1",
"status": {"state": "completed"},
"role": "unexpected_extra_field"
});
let back: SendMessageResponse =
serde_json::from_value(json).expect("should fall back to Task");
match back {
SendMessageResponse::Task(task) => {
assert_eq!(task.id.as_ref(), "t1");
assert_eq!(task.context_id.as_ref(), "c1");
}
other => panic!("expected Task variant, got {other:?}"),
}
}
#[test]
fn task_list_response_roundtrip() {
let resp = TaskListResponse {
tasks: vec![make_task()],
next_page_token: Some("cursor-abc".into()),
page_size: Some(10),
total_size: Some(1),
};
let json = serde_json::to_string(&resp).expect("serialize");
assert!(json.contains("\"nextPageToken\":\"cursor-abc\""));
let back: TaskListResponse = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.tasks.len(), 1);
assert_eq!(back.next_page_token.as_deref(), Some("cursor-abc"));
}
#[test]
fn task_list_response_no_token_omitted() {
let resp = TaskListResponse::new(vec![]);
let json = serde_json::to_string(&resp).expect("serialize");
assert!(
!json.contains("\"nextPageToken\""),
"token should be absent: {json}"
);
}
#[test]
fn send_message_response_disambiguates_task() {
let json = serde_json::json!({
"id": "t1",
"contextId": "c1",
"status": { "state": "completed" }
});
let resp: SendMessageResponse =
serde_json::from_value(json).expect("should deserialize as Task");
assert!(
matches!(resp, SendMessageResponse::Task(_)),
"expected Task variant"
);
}
#[test]
fn send_message_response_disambiguates_message() {
let json = serde_json::json!({
"messageId": "m1",
"role": "agent",
"parts": [{ "type": "text", "text": "hi" }]
});
let resp: SendMessageResponse =
serde_json::from_value(json).expect("should deserialize as Message");
assert!(
matches!(resp, SendMessageResponse::Message(_)),
"expected Message variant"
);
}
#[test]
fn send_message_response_message_with_task_like_fields() {
let json = serde_json::json!({
"messageId": "m1",
"role": "agent",
"parts": [{ "type": "text", "text": "hi" }],
"contextId": "c1",
"taskId": "t1"
});
let resp: SendMessageResponse =
serde_json::from_value(json).expect("should deserialize as Message");
assert!(
matches!(resp, SendMessageResponse::Message(_)),
"expected Message variant even with task-like fields"
);
}
}