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