Skip to main content

quantum_sdk/
account.rs

1use serde::{Deserialize, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6/// Response from balance check.
7#[derive(Debug, Clone, Deserialize)]
8pub struct BalanceResponse {
9    #[serde(default)]
10    pub user_id: Option<String>,
11    pub balance_ticks: i64,
12    pub balance_usd: f64,
13    #[serde(default)]
14    pub ticks_per_usd: i64,
15}
16
17/// A single usage entry from the ledger.
18#[derive(Debug, Clone, Deserialize)]
19pub struct UsageEntry {
20    pub id: String,
21    #[serde(default)]
22    pub request_id: Option<String>,
23    #[serde(default)]
24    pub model: Option<String>,
25    #[serde(default)]
26    pub provider: Option<String>,
27    #[serde(default)]
28    pub endpoint: Option<String>,
29    #[serde(default)]
30    pub delta_ticks: Option<i64>,
31    #[serde(default)]
32    pub balance_after: Option<i64>,
33    #[serde(default)]
34    pub input_tokens: Option<i64>,
35    #[serde(default)]
36    pub output_tokens: Option<i64>,
37    #[serde(default)]
38    pub created_at: Option<String>,
39}
40
41/// Paginated usage history response.
42#[derive(Debug, Clone, Deserialize)]
43pub struct UsageResponse {
44    pub entries: Vec<UsageEntry>,
45    pub has_more: bool,
46    #[serde(default)]
47    pub next_cursor: Option<String>,
48}
49
50/// Monthly usage summary.
51#[derive(Debug, Clone, Deserialize)]
52pub struct UsageSummaryMonth {
53    pub month: String,
54    pub total_requests: i64,
55    pub total_input_tokens: i64,
56    pub total_output_tokens: i64,
57    pub total_cost_usd: f64,
58    pub total_margin_usd: f64,
59    #[serde(default)]
60    pub by_provider: Vec<serde_json::Value>,
61}
62
63/// Response from usage summary.
64#[derive(Debug, Clone, Deserialize)]
65pub struct UsageSummaryResponse {
66    pub months: Vec<UsageSummaryMonth>,
67}
68
69/// Usage query parameters.
70#[derive(Debug, Clone, Serialize, Default)]
71pub struct UsageQuery {
72    /// Max entries per page (default 20, max 100).
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub limit: Option<i32>,
75
76    /// Cursor for pagination (from previous response's next_cursor).
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub start_after: Option<String>,
79}
80
81/// Pricing entry from the pricing table.
82#[derive(Debug, Clone, Deserialize)]
83pub struct PricingEntry {
84    #[serde(default)]
85    pub provider: String,
86    #[serde(default)]
87    pub model: String,
88    #[serde(default)]
89    pub display_name: String,
90    #[serde(default)]
91    pub category: Option<String>,
92    #[serde(default)]
93    pub context_window: Option<String>,
94    #[serde(default)]
95    pub input_per_million: f64,
96    #[serde(default)]
97    pub output_per_million: f64,
98    #[serde(default)]
99    pub cached_per_million: f64,
100    #[serde(default)]
101    pub per_unit_price: Option<f64>,
102    #[serde(default)]
103    pub price_unit: Option<String>,
104}
105
106/// Pricing response (map of model_id → entry).
107#[derive(Debug, Clone, Deserialize)]
108pub struct PricingResponse {
109    pub pricing: std::collections::HashMap<String, PricingEntry>,
110}
111
112impl Client {
113    /// Gets the account credit balance.
114    pub async fn account_balance(&self) -> Result<BalanceResponse> {
115        let (resp, _meta) = self
116            .get_json::<BalanceResponse>("/qai/v1/account/balance")
117            .await?;
118        Ok(resp)
119    }
120
121    /// Gets paginated usage history.
122    pub async fn account_usage(&self, query: &UsageQuery) -> Result<UsageResponse> {
123        let mut path = "/qai/v1/account/usage".to_string();
124        let mut params = Vec::new();
125        if let Some(limit) = query.limit {
126            params.push(format!("limit={limit}"));
127        }
128        if let Some(ref cursor) = query.start_after {
129            params.push(format!("start_after={cursor}"));
130        }
131        if !params.is_empty() {
132            path.push('?');
133            path.push_str(&params.join("&"));
134        }
135        let (resp, _meta) = self.get_json::<UsageResponse>(&path).await?;
136        Ok(resp)
137    }
138
139    /// Gets monthly usage summary.
140    pub async fn account_usage_summary(&self, months: Option<i32>) -> Result<UsageSummaryResponse> {
141        let path = if let Some(m) = months {
142            format!("/qai/v1/account/usage/summary?months={m}")
143        } else {
144            "/qai/v1/account/usage/summary".to_string()
145        };
146        let (resp, _meta) = self.get_json::<UsageSummaryResponse>(&path).await?;
147        Ok(resp)
148    }
149
150    /// Gets the full pricing table (model → pricing entry map).
151    pub async fn account_pricing(&self) -> Result<PricingResponse> {
152        let (resp, _meta) = self
153            .get_json::<PricingResponse>("/qai/v1/pricing")
154            .await?;
155        Ok(resp)
156    }
157}