File size: 5,353 Bytes
afa0cbf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | //! Authenticated account analytics reads. Attribution and missing amounts remain unmodified.
use super::Client;
use super::PathStyle;
use super::RequestError;
use codex_backend_openapi_models::models::analytics as models;
use http::Method;
/// The bounded set of reports used by consumer and workspace Analytics views.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AnalyticsReport {
Usage,
EnterpriseTokens,
Credits,
WorkspaceCredits,
EnterpriseCredits { breakdown: &'static str },
Messages,
Plugins { limit: u8 },
Skills { limit: u8 },
}
/// Endpoint-specific backend contracts; normalization belongs to the consumer.
#[derive(Clone, Debug, PartialEq)]
pub enum AnalyticsResponse {
Usage(models::DailyProductSurfaceUsageResponse),
Credits(models::CreditUsageEventsResponse),
EnterpriseCredits(models::CurrentUserCreditUsageResponse),
Messages(models::DailyWorkspaceUsageCountResponse),
Plugins(models::PluginUsageMetricsResponse),
Skills(models::DailySkillUsageMetricsResponse),
}
impl Client {
/// Fetches one report for an inclusive UTC date range supplied by the caller.
pub async fn get_account_analytics(
&self,
report: AnalyticsReport,
start_date: &str,
end_date: &str,
) -> Result<AnalyticsResponse, RequestError> {
let route = match report {
AnalyticsReport::Usage => "usage/daily-token-usage-breakdown",
AnalyticsReport::Credits => "usage/credit-usage-events",
AnalyticsReport::WorkspaceCredits | AnalyticsReport::EnterpriseTokens => {
"usage/daily-workspace-user-token-usage-breakdown"
}
AnalyticsReport::EnterpriseCredits { .. } => "usage/daily-workspace-user-credit-usage",
AnalyticsReport::Messages => "analytics/daily-workspace-usage-counts",
AnalyticsReport::Plugins { .. } => "analytics/daily-plugin-usage-metrics",
AnalyticsReport::Skills { .. } => "analytics/daily-skill-usage-metrics",
};
let prefix = match self.path_style {
PathStyle::CodexApi => "api/codex",
PathStyle::ChatGptApi => "wham",
};
let mut url = url::Url::parse(&format!("{}/{prefix}/{route}", self.base_url))
.map_err(anyhow::Error::from)?;
if report != AnalyticsReport::Credits {
url.query_pairs_mut()
.append_pair("start_date", start_date)
.append_pair("end_date", end_date);
if let AnalyticsReport::EnterpriseCredits { breakdown } = report {
url.query_pairs_mut().append_pair("breakdown", breakdown);
} else {
url.query_pairs_mut().append_pair("group_by", "day");
}
}
if matches!(
report,
AnalyticsReport::Messages
| AnalyticsReport::Plugins { .. }
| AnalyticsReport::Skills { .. }
) {
url.query_pairs_mut().append_pair("workspace_user", "true");
}
match report {
AnalyticsReport::EnterpriseTokens => {
url.query_pairs_mut()
.append_pair("breakdown_by", "model")
.append_pair("modes", "codex")
.append_pair("modes", "work");
}
AnalyticsReport::Plugins { limit } => {
url.query_pairs_mut()
.append_pair("top_plugin_limit", &limit.to_string());
}
AnalyticsReport::Skills { limit } => {
url.query_pairs_mut()
.append_pair("top_skill_limit", &limit.to_string());
}
AnalyticsReport::Usage
| AnalyticsReport::Credits
| AnalyticsReport::WorkspaceCredits
| AnalyticsReport::EnterpriseCredits { .. }
| AnalyticsReport::Messages => {}
}
let request = self
.request(Method::GET, url.as_str())
.headers(self.headers());
let (body, _) = self
.exec_request_detailed(request, "GET", url.as_str())
.await?;
// Do not include an untyped billing response in errors or diagnostics.
let response = match report {
AnalyticsReport::Usage
| AnalyticsReport::EnterpriseTokens
| AnalyticsReport::WorkspaceCredits => {
serde_json::from_str(&body).map(AnalyticsResponse::Usage)
}
AnalyticsReport::Credits => serde_json::from_str(&body).map(AnalyticsResponse::Credits),
AnalyticsReport::EnterpriseCredits { .. } => {
serde_json::from_str(&body).map(AnalyticsResponse::EnterpriseCredits)
}
AnalyticsReport::Messages => {
serde_json::from_str(&body).map(AnalyticsResponse::Messages)
}
AnalyticsReport::Plugins { .. } => {
serde_json::from_str(&body).map(AnalyticsResponse::Plugins)
}
AnalyticsReport::Skills { .. } => {
serde_json::from_str(&body).map(AnalyticsResponse::Skills)
}
};
response.map_err(|_| RequestError::Other(anyhow::anyhow!("Invalid analytics response.")))
}
}
#[cfg(test)]
#[path = "analytics_tests.rs"]
mod tests;
|