Skip to main content

seher/agent/
mod.rs

1use crate::Cookie;
2use crate::config::AgentConfig;
3use chrono::{DateTime, Utc};
4use serde::Serialize;
5
6pub struct Agent {
7    pub config: AgentConfig,
8    pub cookies: Vec<Cookie>,
9    pub domain: String,
10}
11
12#[derive(Debug)]
13pub enum AgentLimit {
14    NotLimited,
15    Limited { reset_time: Option<DateTime<Utc>> },
16}
17
18#[derive(Debug, Serialize)]
19pub struct UsageEntry {
20    #[serde(rename = "type")]
21    pub entry_type: String,
22    pub limited: bool,
23    pub utilization: f64,
24    pub resets_at: Option<DateTime<Utc>>,
25}
26
27#[derive(Debug, Serialize)]
28pub struct AgentStatus {
29    pub command: String,
30    pub usage: Vec<UsageEntry>,
31}
32
33impl Agent {
34    pub fn new(config: AgentConfig, cookies: Vec<Cookie>, domain: String) -> Self {
35        Self {
36            config,
37            cookies,
38            domain,
39        }
40    }
41
42    pub fn command(&self) -> &str {
43        &self.config.command
44    }
45
46    pub fn args(&self) -> &[String] {
47        &self.config.args
48    }
49
50    pub async fn check_limit(&self) -> Result<AgentLimit, Box<dyn std::error::Error>> {
51        match self.domain.as_str() {
52            "claude.ai" => self.check_claude_limit().await,
53            "github.com" => self.check_copilot_limit().await,
54            _ => Err(format!("Unknown domain: {}", self.domain).into()),
55        }
56    }
57
58    pub async fn fetch_status(&self) -> Result<AgentStatus, Box<dyn std::error::Error>> {
59        match self.domain.as_str() {
60            "claude.ai" => {
61                let usage = crate::claude::ClaudeClient::fetch_usage(&self.cookies).await?;
62                let windows = [
63                    ("five_hour", &usage.five_hour),
64                    ("seven_day", &usage.seven_day),
65                    ("seven_day_sonnet", &usage.seven_day_sonnet),
66                ];
67                let entries = windows
68                    .into_iter()
69                    .filter_map(|(name, w)| {
70                        w.as_ref().map(|w| UsageEntry {
71                            entry_type: name.to_string(),
72                            limited: w.utilization >= 100.0,
73                            utilization: w.utilization,
74                            resets_at: w.resets_at,
75                        })
76                    })
77                    .collect();
78                Ok(AgentStatus {
79                    command: self.config.command.clone(),
80                    usage: entries,
81                })
82            }
83            "github.com" => {
84                let quota = crate::copilot::CopilotClient::fetch_quota(&self.cookies).await?;
85                let entries = vec![
86                    UsageEntry {
87                        entry_type: "chat_utilization".to_string(),
88                        limited: quota.chat_utilization >= 100.0,
89                        utilization: quota.chat_utilization,
90                        resets_at: quota.reset_time,
91                    },
92                    UsageEntry {
93                        entry_type: "premium_utilization".to_string(),
94                        limited: quota.premium_utilization >= 100.0,
95                        utilization: quota.premium_utilization,
96                        resets_at: quota.reset_time,
97                    },
98                ];
99                Ok(AgentStatus {
100                    command: self.config.command.clone(),
101                    usage: entries,
102                })
103            }
104            _ => Err(format!("Unknown domain: {}", self.domain).into()),
105        }
106    }
107
108    async fn check_claude_limit(&self) -> Result<AgentLimit, Box<dyn std::error::Error>> {
109        let usage = crate::claude::ClaudeClient::fetch_usage(&self.cookies).await?;
110
111        if let Some(reset_time) = usage.next_reset_time() {
112            Ok(AgentLimit::Limited {
113                reset_time: Some(reset_time),
114            })
115        } else {
116            let is_limited = [
117                usage.five_hour.as_ref(),
118                usage.seven_day.as_ref(),
119                usage.seven_day_sonnet.as_ref(),
120            ]
121            .into_iter()
122            .flatten()
123            .any(|w| w.utilization >= 100.0);
124
125            if is_limited {
126                Ok(AgentLimit::Limited { reset_time: None })
127            } else {
128                Ok(AgentLimit::NotLimited)
129            }
130        }
131    }
132
133    async fn check_copilot_limit(&self) -> Result<AgentLimit, Box<dyn std::error::Error>> {
134        let quota = crate::copilot::CopilotClient::fetch_quota(&self.cookies).await?;
135
136        if quota.is_limited() {
137            Ok(AgentLimit::Limited {
138                reset_time: quota.reset_time,
139            })
140        } else {
141            Ok(AgentLimit::NotLimited)
142        }
143    }
144
145    pub fn execute(&self, extra_args: &[String]) -> std::io::Result<std::process::ExitStatus> {
146        let mut cmd = std::process::Command::new(self.command());
147        cmd.args(self.args());
148        cmd.args(extra_args);
149        cmd.status()
150    }
151}