//! Defines provider call and usage observability records. use super::ProviderType; mod usage { use std::ops::AddAssign; use serde::{Deserialize, Serialize}; use super::ProviderType; #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct Usage { pub input_tokens: Option, pub output_tokens: Option, pub total_tokens: Option, pub cache_read_tokens: Option, pub cache_write_tokens: Option, pub reasoning_tokens: Option, } impl Usage { /// Returns the provider-visible input context without counting cached tokens twice. pub(crate) fn context_input_tokens(self, provider: ProviderType) -> Option { let input = self.input_tokens?; match provider { ProviderType::OpenAiChat | ProviderType::OpenAiResponses | ProviderType::Plugin => { Some(input) } ProviderType::Anthropic => input .checked_add(self.cache_read_tokens.unwrap_or_default())? .checked_add(self.cache_write_tokens.unwrap_or_default()), } } } impl AddAssign for Usage { fn add_assign(&mut self, rhs: Self) { self.input_tokens = sum(self.input_tokens, rhs.input_tokens); self.output_tokens = sum(self.output_tokens, rhs.output_tokens); self.total_tokens = sum(self.total_tokens, rhs.total_tokens); self.cache_read_tokens = sum(self.cache_read_tokens, rhs.cache_read_tokens); self.cache_write_tokens = sum(self.cache_write_tokens, rhs.cache_write_tokens); self.reasoning_tokens = sum(self.reasoning_tokens, rhs.reasoning_tokens); } } fn sum(left: Option, right: Option) -> Option { left?.checked_add(right?) } } pub use usage::*; mod llm_call { use serde::Serialize; use super::{ProviderType, Usage}; #[derive(Clone, Debug)] pub struct NewLlmCall { pub call_id: String, pub run_id: String, pub conversation_id: String, pub provider_call_index: i64, pub model_hash: String, pub provider_type: ProviderType, pub provider_url: String, pub request_type: ProviderType, pub request_url: String, pub model_id: String, pub display_name: String, pub reasoning_effort: Option, pub fast: bool, pub message_count: usize, pub tool_count: usize, pub detailed: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct LlmCallUsageAnchor { pub request_type: ProviderType, pub usage: Usage, pub message_count: usize, pub tool_count: usize, } #[derive(Clone, Debug, Serialize)] pub struct LlmCallSummary { pub call_id: String, pub run_id: String, pub conversation_id: String, pub provider_call_index: i64, pub model_hash: Option, pub provider_type: String, pub provider_url: String, pub request_type: String, pub request_url: String, pub model_id: String, pub display_name: String, pub reasoning_effort: Option, pub fast: Option, pub status: String, pub finish_reason: Option, pub created_at_ms: i64, pub request_started_at_ms: Option, pub response_headers_at_ms: Option, pub first_event_at_ms: Option, pub first_text_at_ms: Option, pub first_valid_response_at_ms: Option, pub finished_at_ms: Option, pub queue_ms: Option, pub ttfb_ms: Option, pub ttft_ms: Option, pub ttfr_ms: Option, pub duration_ms: Option, pub input_tokens: Option, pub output_tokens: Option, pub total_tokens: Option, pub cache_read_tokens: Option, pub cache_write_tokens: Option, pub reasoning_tokens: Option, pub usage: Option, pub message_count: i64, pub tool_count: i64, pub request_bytes: Option, pub response_bytes: i64, pub stream_event_count: i64, pub http_status: Option, pub error_kind: Option, pub error_message: Option, pub detailed: bool, } #[derive(Clone, Debug, Serialize)] pub struct LlmCallRequest { pub headers: serde_json::Value, pub body: serde_json::Value, pub byte_count: i64, } #[derive(Clone, Debug, Serialize)] pub struct LlmCallResponseChunk { pub seq: i64, pub received_offset_ms: i64, pub data: String, pub byte_count: i64, } } pub use llm_call::*; mod cursor_trace { use serde::Serialize; #[derive(Clone, Debug, Serialize)] pub struct CursorRunTraceSummary { pub request_id: String, pub conversation_id: Option, pub route: String, pub model_id: Option, pub status: String, pub request_bytes: i64, pub response_bytes: i64, pub response_event_count: i64, pub http_status: Option, pub received_at_ms: i64, pub first_response_at_ms: Option, pub finished_at_ms: Option, pub error_message: Option, } #[derive(Clone, Debug)] pub struct CursorRunTraceArtifact { pub seq: i64, pub artifact_type: String, pub source: String, pub metadata: serde_json::Value, pub created_at_ms: i64, pub data: Vec, } } pub use cursor_trace::*; mod overview { //! Read-only usage aggregates rendered by the desktop overview page. use serde::Serialize; #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct OverviewMetrics { pub llm_calls: i64, pub successful_calls: i64, pub failed_calls: i64, pub token_usage: i64, pub prompt_tokens: i64, pub input_tokens: i64, pub cache_read_tokens: i64, pub cache_write_tokens: i64, pub output_tokens: i64, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] pub enum TokenUsageGranularity { Minute, Hour, #[default] Day, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct TokenUsageBucket { pub bucket_start_ms: i64, pub input_tokens: i64, pub cache_read_tokens: i64, pub cache_write_tokens: i64, pub output_tokens: i64, } impl TokenUsageBucket { pub fn total_tokens(&self) -> i64 { self.input_tokens .saturating_add(self.cache_read_tokens) .saturating_add(self.cache_write_tokens) .saturating_add(self.output_tokens) } } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] pub struct Overview { pub metrics: OverviewMetrics, pub token_usage_granularity: TokenUsageGranularity, pub token_usage_series: Vec, } } pub use overview::*;