1use crate::reasoning::ReasoningEffort;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct ScheduleSpec {
11 pub id: String,
12 pub name: String,
13 pub enabled: bool,
14 pub trigger: ScheduleTrigger,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub timezone: Option<String>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub start_at: Option<DateTime<Utc>>,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub end_at: Option<DateTime<Utc>>,
21 #[serde(default)]
22 pub misfire_policy: MisFirePolicy,
23 #[serde(default)]
24 pub overlap_policy: OverlapPolicy,
25 #[serde(default)]
26 pub run_config: ScheduleRunConfig,
27 pub created_at: DateTime<Utc>,
28 pub updated_at: DateTime<Utc>,
29}
30
31impl ScheduleSpec {
32 pub fn window(&self) -> ScheduleWindow {
33 ScheduleWindow {
34 start_at: self.start_at,
35 end_at: self.end_at,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(tag = "type", rename_all = "snake_case")]
46pub enum ScheduleTrigger {
47 Interval {
48 every_seconds: u64,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 anchor_at: Option<DateTime<Utc>>,
51 },
52 Daily {
53 hour: u8,
54 minute: u8,
55 #[serde(default)]
56 second: u8,
57 },
58 Weekly {
59 weekdays: Vec<ScheduleWeekday>,
60 hour: u8,
61 minute: u8,
62 #[serde(default)]
63 second: u8,
64 },
65 Monthly {
66 days: Vec<u8>,
67 hour: u8,
68 minute: u8,
69 #[serde(default)]
70 second: u8,
71 },
72 Cron {
73 expr: String,
74 },
75}
76
77impl ScheduleTrigger {
78 pub fn legacy_interval(every_seconds: u64, anchor_at: Option<DateTime<Utc>>) -> Self {
79 Self::Interval {
80 every_seconds,
81 anchor_at,
82 }
83 }
84
85 pub fn kind_name(&self) -> &'static str {
86 match self {
87 Self::Interval { .. } => "interval",
88 Self::Daily { .. } => "daily",
89 Self::Weekly { .. } => "weekly",
90 Self::Monthly { .. } => "monthly",
91 Self::Cron { .. } => "cron",
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(rename_all = "snake_case")]
98pub enum ScheduleWeekday {
99 Mon,
100 Tue,
101 Wed,
102 Thu,
103 Fri,
104 Sat,
105 Sun,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
109#[serde(tag = "type", rename_all = "snake_case")]
110pub enum MisFirePolicy {
111 #[default]
112 RunOnce,
113 Skip,
114 CatchUpAll,
115 CatchUpWindow {
116 max_catch_up_runs: u32,
117 max_lateness_seconds: u64,
118 },
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
122#[serde(rename_all = "snake_case")]
123pub enum OverlapPolicy {
124 Allow,
125 Skip,
126 #[default]
127 QueueOne,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
131pub struct ScheduleWindow {
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub start_at: Option<DateTime<Utc>>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub end_at: Option<DateTime<Utc>>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
139pub struct ScheduleState {
140 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub next_fire_at: Option<DateTime<Utc>>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub last_scheduled_at: Option<DateTime<Utc>>,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub last_started_at: Option<DateTime<Utc>>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub last_finished_at: Option<DateTime<Utc>>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub last_success_at: Option<DateTime<Utc>>,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub last_failure_at: Option<DateTime<Utc>>,
152 #[serde(default)]
153 pub queued_run_count: u32,
154 #[serde(default)]
155 pub running_run_count: u32,
156 #[serde(default)]
157 pub consecutive_failures: u32,
158 #[serde(default)]
159 pub total_run_count: u64,
160 #[serde(default)]
161 pub total_success_count: u64,
162 #[serde(default)]
163 pub total_failure_count: u64,
164 #[serde(default)]
165 pub total_missed_count: u64,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
169#[serde(rename_all = "snake_case")]
170pub enum ScheduleRunStatus {
171 Queued,
172 Running,
173 Success,
174 Failed,
175 Skipped,
176 Missed,
177 Cancelled,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct ScheduleRunRecord {
182 pub run_id: String,
183 pub schedule_id: String,
184 pub scheduled_for: DateTime<Utc>,
185 pub claimed_at: DateTime<Utc>,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub started_at: Option<DateTime<Utc>>,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub completed_at: Option<DateTime<Utc>>,
190 pub status: ScheduleRunStatus,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub outcome_reason: Option<String>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub session_id: Option<String>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub dispatch_lag_ms: Option<u64>,
197 #[serde(default, skip_serializing_if = "Option::is_none")]
198 pub execution_duration_ms: Option<u64>,
199 #[serde(default)]
200 pub was_catch_up: bool,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
205pub struct ScheduleRunConfig {
206 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub system_prompt: Option<String>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub task_message: Option<String>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub model: Option<String>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub reasoning_effort: Option<ReasoningEffort>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub workspace_path: Option<String>,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub enhance_prompt: Option<String>,
224 #[serde(default)]
226 pub auto_execute: bool,
227}