1use std::path::PathBuf;
7
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16#[non_exhaustive]
17pub enum TaskKind {
18 Init,
20 Plan,
22 Execute,
24 Review,
26 Verification,
28 Fix,
30 Pr,
32 Custom(String),
34}
35
36impl TaskKind {
37 #[must_use]
42 pub fn dir_name(&self) -> &str {
43 match self {
44 Self::Init => "init",
45 Self::Plan => "plan",
46 Self::Execute => "execute",
47 Self::Review => "review",
48 Self::Verification => "verification",
49 Self::Fix => "fix",
50 Self::Pr => "pr",
51 Self::Custom(name) => name.as_str(),
52 }
53 }
54}
55
56impl std::fmt::Display for TaskKind {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 write!(f, "{}", self.dir_name())
59 }
60}
61
62#[derive(Debug, Clone)]
67pub struct Task {
68 pub kind: TaskKind,
70
71 pub context: serde_json::Value,
76
77 pub system_prompt: Option<String>,
82}
83
84impl Task {
85 #[must_use]
101 pub fn new(kind: TaskKind, context: serde_json::Value) -> Self {
102 Self {
103 kind,
104 context,
105 system_prompt: None,
106 }
107 }
108
109 #[must_use]
113 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
114 self.system_prompt = Some(prompt.into());
115 self
116 }
117}
118
119#[derive(Debug, Clone)]
121#[non_exhaustive]
122pub struct TaskResult {
123 pub success: bool,
125
126 pub output: String,
128
129 pub artifacts: Vec<Artifact>,
131
132 pub stats: TaskStats,
134}
135
136#[derive(Debug, Clone)]
138#[non_exhaustive]
139pub struct Artifact {
140 pub path: PathBuf,
142
143 pub content: String,
145}
146
147#[derive(Debug, Clone, Default)]
149#[non_exhaustive]
150pub struct TaskStats {
151 pub turns: u32,
153
154 pub input_tokens: u64,
156
157 pub output_tokens: u64,
159
160 pub cost_usd: f64,
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use serde_json::json;
168
169 #[test]
170 fn test_task_kind_dir_names() {
171 assert_eq!(TaskKind::Init.dir_name(), "init");
172 assert_eq!(TaskKind::Plan.dir_name(), "plan");
173 assert_eq!(TaskKind::Execute.dir_name(), "execute");
174 assert_eq!(TaskKind::Review.dir_name(), "review");
175 assert_eq!(TaskKind::Verification.dir_name(), "verification");
176 assert_eq!(TaskKind::Fix.dir_name(), "fix");
177 assert_eq!(
178 TaskKind::Custom("my_task".to_string()).dir_name(),
179 "my_task"
180 );
181 }
182
183 #[test]
184 fn test_task_kind_display() {
185 assert_eq!(format!("{}", TaskKind::Init), "init");
186 assert_eq!(
187 format!("{}", TaskKind::Custom("custom".to_string())),
188 "custom"
189 );
190 }
191
192 #[test]
193 fn test_should_create_task() {
194 let task = Task::new(TaskKind::Init, json!({"repo_path": "/tmp/repo"}));
195
196 assert_eq!(task.kind, TaskKind::Init);
197 assert_eq!(task.context["repo_path"], "/tmp/repo");
198 assert!(task.system_prompt.is_none());
199 }
200
201 #[test]
202 fn test_should_create_task_with_system_prompt() {
203 let task = Task::new(TaskKind::Plan, json!({})).with_system_prompt("Custom system prompt");
204
205 assert_eq!(task.system_prompt, Some("Custom system prompt".to_string()));
206 }
207
208 #[test]
209 fn test_task_kind_serialization() {
210 assert_eq!(serde_json::to_string(&TaskKind::Init).unwrap(), "\"init\"");
211 assert_eq!(
212 serde_json::to_string(&TaskKind::Custom("test".to_string())).unwrap(),
213 "{\"custom\":\"test\"}"
214 );
215 }
216
217 #[test]
218 fn test_task_kind_deserialization() {
219 let kind: TaskKind = serde_json::from_str("\"init\"").unwrap();
220 assert_eq!(kind, TaskKind::Init);
221
222 let kind: TaskKind = serde_json::from_str("{\"custom\":\"my_task\"}").unwrap();
223 assert_eq!(kind, TaskKind::Custom("my_task".to_string()));
224 }
225}