scx_forge_agent/
model_timeout.rs1use std::error::Error;
5use std::fmt;
6use std::time::{Duration, Instant};
7
8use anyhow::Error as AnyhowError;
9
10pub const DEFAULT_TURN_TIMEOUT_SECS: u64 = 5 * 60;
12
13#[derive(Debug, Clone)]
14pub struct ModelTurnTimeout {
15 limit: Duration,
16 elapsed: Duration,
17}
18
19impl ModelTurnTimeout {
20 fn new(limit: Duration, elapsed: Duration) -> Self {
21 Self { limit, elapsed }
22 }
23
24 pub fn summary(&self) -> String {
25 format!(
26 "model turn exceeded the {} time limit (elapsed {})",
27 format_duration(self.limit),
28 format_duration(self.elapsed)
29 )
30 }
31}
32
33impl fmt::Display for ModelTurnTimeout {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.write_str(&self.summary())
36 }
37}
38
39impl Error for ModelTurnTimeout {}
40
41#[derive(Debug, Clone, Copy)]
42pub struct ModelTurnDeadline {
43 started: Instant,
44 limit: Duration,
45}
46
47impl ModelTurnDeadline {
48 pub fn new(limit: Duration) -> Self {
49 Self {
50 started: Instant::now(),
51 limit,
52 }
53 }
54
55 pub fn check(&self) -> Result<(), ModelTurnTimeout> {
56 if self.expired() {
57 Err(self.timeout())
58 } else {
59 Ok(())
60 }
61 }
62
63 pub fn expired(&self) -> bool {
64 self.started.elapsed() >= self.limit
65 }
66
67 pub fn remaining(&self) -> Duration {
68 self.limit
69 .checked_sub(self.started.elapsed())
70 .unwrap_or(Duration::ZERO)
71 }
72
73 pub fn timeout(&self) -> ModelTurnTimeout {
74 ModelTurnTimeout::new(self.limit, self.started.elapsed())
75 }
76}
77
78pub fn is(err: &AnyhowError) -> bool {
79 err.downcast_ref::<ModelTurnTimeout>().is_some()
80}
81
82pub fn summary(err: &AnyhowError) -> String {
83 err.downcast_ref::<ModelTurnTimeout>()
84 .map(ModelTurnTimeout::summary)
85 .unwrap_or_else(|| err.to_string())
86}
87
88fn format_duration(duration: Duration) -> String {
89 let secs = duration.as_secs();
90 if secs >= 60 && secs % 60 == 0 {
91 format!("{}m", secs / 60)
92 } else {
93 format!("{secs}s")
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn summarizes_timeout() {
103 let err = ModelTurnTimeout::new(Duration::from_secs(300), Duration::from_secs(301));
104
105 assert_eq!(
106 err.summary(),
107 "model turn exceeded the 5m time limit (elapsed 301s)"
108 );
109 }
110}