Skip to main content

autoagents_protocol/
task.rs

1use crate::SubmissionId;
2use crate::llm::ImageMime;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7/// A unit of work submitted to an agent.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Task {
10    pub prompt: String,
11    pub image: Option<(ImageMime, Vec<u8>)>,
12    #[serde(default)]
13    pub system_prompt: Option<String>,
14    pub submission_id: SubmissionId,
15    pub completed: bool,
16    pub result: Option<Value>,
17    /// Arbitrary application-provided metadata (session/chat isolation, app context, anything the app threads through).
18    #[serde(default)]
19    pub app_meta: Option<Value>,
20}
21
22impl Task {
23    /// Create a new text-only task with a fresh submission id.
24    pub fn new<T: Into<String>>(task: T) -> Self {
25        Self {
26            prompt: task.into(),
27            image: None,
28            system_prompt: None,
29            submission_id: Uuid::new_v4(),
30            completed: false,
31            result: None,
32            app_meta: None,
33        }
34    }
35
36    /// Create a new task with an image payload and a fresh submission id.
37    pub fn new_with_image<T: Into<String>>(
38        task: T,
39        image_mime: ImageMime,
40        image_data: Vec<u8>,
41    ) -> Self {
42        Self {
43            prompt: task.into(),
44            image: Some((image_mime, image_data)),
45            system_prompt: None,
46            submission_id: Uuid::new_v4(),
47            completed: false,
48            result: None,
49            app_meta: None,
50        }
51    }
52
53    pub fn with_system_prompt<T: Into<String>>(mut self, prompt: T) -> Self {
54        self.system_prompt = Some(prompt.into());
55        self
56    }
57
58    /// Attach arbitrary application metadata (a JSON value, typically an object).
59    pub fn with_app_meta(mut self, meta: Value) -> Self {
60        self.app_meta = Some(meta);
61        self
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn app_meta_roundtrip() {
71        let mut task = Task::new("hello");
72        task.app_meta = Some(serde_json::json!({
73            "session_id": "s1",
74            "chat_id": "c1",
75            "extra": { "nested": [1, 2, 3] },
76        }));
77        let back: Task = serde_json::from_str(&serde_json::to_string(&task).unwrap()).unwrap();
78        let meta = back
79            .app_meta
80            .expect("app_meta preserved across serde roundtrip");
81        assert_eq!(meta.get("session_id").and_then(|v| v.as_str()), Some("s1"));
82        assert_eq!(meta.get("chat_id").and_then(|v| v.as_str()), Some("c1"));
83        assert!(
84            meta.get("extra").is_some(),
85            "arbitrary nested app data preserved"
86        );
87    }
88
89    #[test]
90    fn app_meta_defaults_to_none_when_absent() {
91        // Back-compat: a payload serialized before app_meta existed must still deserialize.
92        let mut v = serde_json::to_value(Task::new("legacy")).unwrap();
93        v.as_object_mut().unwrap().remove("app_meta");
94        let back: Task = serde_json::from_value(v).unwrap();
95        assert!(back.app_meta.is_none());
96    }
97
98    #[test]
99    fn with_app_meta_builder_sets_field() {
100        let task =
101            Task::new("hi").with_app_meta(serde_json::json!({"session_id": "s1", "chat_id": "c1"}));
102        let meta = task.app_meta.expect("with_app_meta should set app_meta");
103        assert_eq!(meta.get("session_id").and_then(|v| v.as_str()), Some("s1"));
104        assert_eq!(meta.get("chat_id").and_then(|v| v.as_str()), Some("c1"));
105    }
106}