use serde::{Deserialize, Serialize};
use crate::artifact::Artifact;
use crate::message::Message;
use crate::task::{ContextId, Task, TaskId, TaskStatus};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskStatusUpdateEvent {
pub task_id: TaskId,
pub context_id: ContextId,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskArtifactUpdateEvent {
pub task_id: TaskId,
pub context_id: ContextId,
pub artifact: Artifact,
#[serde(skip_serializing_if = "Option::is_none")]
pub append: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_chunk: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum StreamResponse {
Task(Task),
Message(Message),
StatusUpdate(TaskStatusUpdateEvent),
ArtifactUpdate(TaskArtifactUpdateEvent),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::artifact::ArtifactId;
use crate::message::Part;
use crate::task::{ContextId, TaskState};
#[test]
fn status_update_event_roundtrip() {
let event = TaskStatusUpdateEvent {
task_id: TaskId::new("task-1"),
context_id: ContextId::new("ctx-1"),
status: TaskStatus::new(TaskState::Completed),
metadata: None,
};
let json = serde_json::to_string(&event).expect("serialize");
assert!(!json.contains("\"final\""), "v1.0 removed final field");
assert!(json.contains("\"status\""), "should have status field");
let back: TaskStatusUpdateEvent = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.status.state, TaskState::Completed);
}
#[test]
fn artifact_update_event_roundtrip() {
let event = TaskArtifactUpdateEvent {
task_id: TaskId::new("task-1"),
context_id: ContextId::new("ctx-1"),
artifact: Artifact::new(ArtifactId::new("art-1"), vec![Part::text("output")]),
append: Some(false),
last_chunk: Some(true),
metadata: None,
};
let json = serde_json::to_string(&event).expect("serialize");
let back: TaskArtifactUpdateEvent = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.last_chunk, Some(true));
}
#[test]
fn stream_response_task_variant() {
let task = Task {
id: TaskId::new("t1"),
context_id: ContextId::new("c1"),
status: TaskStatus::new(TaskState::Working),
history: None,
artifacts: None,
metadata: None,
};
let resp = StreamResponse::Task(task);
let json = serde_json::to_string(&resp).expect("serialize");
assert!(
!json.contains("\"kind\""),
"v1.0 should not have kind tag: {json}"
);
let back: StreamResponse = serde_json::from_str(&json).expect("deserialize");
match &back {
StreamResponse::Task(t) => {
assert_eq!(t.id, TaskId::new("t1"));
assert_eq!(t.context_id, ContextId::new("c1"));
assert_eq!(t.status.state, TaskState::Working);
}
_ => panic!("expected Task variant"),
}
}
#[test]
fn stream_response_status_update_variant() {
let event = TaskStatusUpdateEvent {
task_id: TaskId::new("t1"),
context_id: ContextId::new("c1"),
status: TaskStatus::new(TaskState::Failed),
metadata: None,
};
let resp = StreamResponse::StatusUpdate(event);
let json = serde_json::to_string(&resp).expect("serialize");
assert!(
!json.contains("\"kind\""),
"v1.0 should not have kind tag: {json}"
);
let back: StreamResponse = serde_json::from_str(&json).expect("deserialize");
match &back {
StreamResponse::StatusUpdate(e) => {
assert_eq!(e.task_id, TaskId::new("t1"));
assert_eq!(e.status.state, TaskState::Failed);
}
_ => panic!("expected StatusUpdate variant"),
}
}
#[test]
fn stream_response_message_variant_roundtrip() {
use crate::message::{MessageId, MessageRole, Part};
use crate::task::TaskId;
let msg = crate::message::Message {
id: MessageId::new("msg-stream-1"),
role: MessageRole::Agent,
parts: vec![Part::text("streaming response")],
task_id: Some(TaskId::new("t1")),
context_id: Some(ContextId::new("c1")),
reference_task_ids: None,
extensions: None,
metadata: None,
};
let resp = StreamResponse::Message(msg);
let json = serde_json::to_string(&resp).expect("serialize");
assert!(
!json.contains("\"kind\""),
"v1.0 should not have kind tag: {json}"
);
assert!(json.contains("\"messageId\":\"msg-stream-1\""));
assert!(json.contains("\"role\":\"ROLE_AGENT\""));
let back: StreamResponse = serde_json::from_str(&json).expect("deserialize");
match back {
StreamResponse::Message(m) => {
assert_eq!(m.id, MessageId::new("msg-stream-1"));
assert_eq!(m.role, MessageRole::Agent);
assert_eq!(m.parts.len(), 1);
}
other => panic!("expected Message variant, got {other:?}"),
}
}
}