seher-sdk 0.0.32

Seher SDK: agent resolution, rate-limit checks, and provider clients
Documentation
use chrono::{DateTime, TimeZone, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct CodexWindow {
    pub used_percent: f64,
    pub limit_window_seconds: i64,
    pub reset_after_seconds: i64,
    pub reset_at: i64,
}

impl CodexWindow {
    #[must_use]
    pub fn is_limited(&self) -> bool {
        self.used_percent >= 100.0
    }

    #[must_use]
    pub fn reset_at_datetime(&self) -> Option<DateTime<Utc>> {
        Utc.timestamp_opt(self.reset_at, 0).single()
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct CodexRateLimit {
    pub allowed: bool,
    pub limit_reached: bool,
    pub primary_window: Option<CodexWindow>,
    pub secondary_window: Option<CodexWindow>,
}

impl CodexRateLimit {
    #[must_use]
    pub fn is_limited(&self) -> bool {
        !self.allowed
            || self.limit_reached
            || [self.primary_window.as_ref(), self.secondary_window.as_ref()]
                .into_iter()
                .flatten()
                .any(CodexWindow::is_limited)
    }

    #[must_use]
    pub fn next_reset_time(&self) -> Option<DateTime<Utc>> {
        [self.primary_window.as_ref(), self.secondary_window.as_ref()]
            .into_iter()
            .flatten()
            .filter(|window| !self.allowed || self.limit_reached || window.is_limited())
            .filter_map(CodexWindow::reset_at_datetime)
            .max()
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct CodexCredits {
    pub has_credits: bool,
    pub unlimited: bool,
    pub balance: String,
    pub approx_local_messages: Vec<i64>,
    pub approx_cloud_messages: Vec<i64>,
    #[serde(default)]
    pub overage_limit_reached: bool,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct CodexUsageResponse {
    pub user_id: String,
    pub account_id: String,
    pub email: String,
    pub plan_type: String,
    pub rate_limit: CodexRateLimit,
    pub code_review_rate_limit: Option<CodexRateLimit>,
    pub additional_rate_limits: Option<serde_json::Value>,
    pub credits: CodexCredits,
    pub spend_control: Option<serde_json::Value>,
    pub promo: Option<serde_json::Value>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_usage_response_and_keeps_unlimited_state() -> Result<(), serde_json::Error> {
        let json = r#"
        {
          "user_id": "user-1",
          "account_id": "user-1",
          "email": "user@example.com",
          "plan_type": "plus",
          "rate_limit": {
            "allowed": true,
            "limit_reached": false,
            "primary_window": {
              "used_percent": 6,
              "limit_window_seconds": 18000,
              "reset_after_seconds": 13837,
              "reset_at": 1773200619
            },
            "secondary_window": {
              "used_percent": 2,
              "limit_window_seconds": 604800,
              "reset_after_seconds": 600637,
              "reset_at": 1773787419
            }
          },
          "code_review_rate_limit": {
            "allowed": true,
            "limit_reached": false,
            "primary_window": {
              "used_percent": 0,
              "limit_window_seconds": 604800,
              "reset_after_seconds": 604800,
              "reset_at": 1773791583
            },
            "secondary_window": null
          },
          "additional_rate_limits": null,
          "credits": {
            "has_credits": false,
            "unlimited": false,
            "balance": "0",
            "approx_local_messages": [0, 0],
            "approx_cloud_messages": [0, 0]
          },
          "promo": null
        }"#;

        let usage: CodexUsageResponse = serde_json::from_str(json)?;

        assert!(!usage.rate_limit.is_limited());
        assert_eq!(usage.rate_limit.next_reset_time(), None);
        let primary_used = usage
            .code_review_rate_limit
            .as_ref()
            .and_then(|cr| cr.primary_window.as_ref())
            .map(|w| w.used_percent);
        assert_eq!(primary_used, Some(0.0));
        Ok(())
    }

    #[test]
    fn parses_null_code_review_rate_limit_and_missing_overage_field()
    -> Result<(), serde_json::Error> {
        let json = r#"
        {
          "user_id": "user-1",
          "account_id": "user-1",
          "email": "user@example.com",
          "plan_type": "plus",
          "rate_limit": {
            "allowed": true,
            "limit_reached": false,
            "primary_window": null,
            "secondary_window": null
          },
          "code_review_rate_limit": null,
          "additional_rate_limits": null,
          "credits": {
            "has_credits": false,
            "unlimited": false,
            "balance": "0",
            "approx_local_messages": [0, 0],
            "approx_cloud_messages": [0, 0]
          },
          "spend_control": { "reached": false },
          "promo": null
        }"#;

        let usage: CodexUsageResponse = serde_json::from_str(json)?;

        assert!(usage.code_review_rate_limit.is_none());
        assert!(!usage.credits.overage_limit_reached);
        Ok(())
    }

    #[test]
    fn picks_latest_reset_when_multiple_windows_are_limited() {
        let limit = CodexRateLimit {
            allowed: false,
            limit_reached: true,
            primary_window: Some(CodexWindow {
                used_percent: 100.0,
                limit_window_seconds: 18000,
                reset_after_seconds: 100,
                reset_at: 1_773_200_619,
            }),
            secondary_window: Some(CodexWindow {
                used_percent: 100.0,
                limit_window_seconds: 604_800,
                reset_after_seconds: 200,
                reset_at: 1_773_787_419,
            }),
        };

        assert!(limit.is_limited());
        assert_eq!(
            limit.next_reset_time().map(|time| time.timestamp()),
            Some(1_773_787_419)
        );
    }

    #[test]
    fn uses_reset_time_when_allowed_flag_blocks_usage_before_window_hits_100_percent() {
        let limit = CodexRateLimit {
            allowed: false,
            limit_reached: false,
            primary_window: Some(CodexWindow {
                used_percent: 85.0,
                limit_window_seconds: 18000,
                reset_after_seconds: 100,
                reset_at: 1_773_200_619,
            }),
            secondary_window: Some(CodexWindow {
                used_percent: 20.0,
                limit_window_seconds: 604_800,
                reset_after_seconds: 200,
                reset_at: 1_773_787_419,
            }),
        };

        assert!(limit.is_limited());
        assert_eq!(
            limit.next_reset_time().map(|time| time.timestamp()),
            Some(1_773_787_419)
        );
    }
}