Files
cursor-byok/server/src/model/observability.rs
T
2026-08-30 20:00:53 +08:00

235 lines
7.2 KiB
Rust

//! 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<u64>,
pub output_tokens: Option<u64>,
pub total_tokens: Option<u64>,
pub cache_read_tokens: Option<u64>,
pub cache_write_tokens: Option<u64>,
pub reasoning_tokens: Option<u64>,
}
impl Usage {
/// Returns the provider-visible input context without counting cached tokens twice.
pub(crate) fn context_input_tokens(self, provider: ProviderType) -> Option<u64> {
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<u64>, right: Option<u64>) -> Option<u64> {
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<String>,
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<String>,
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<String>,
pub fast: Option<bool>,
pub status: String,
pub finish_reason: Option<String>,
pub created_at_ms: i64,
pub request_started_at_ms: Option<i64>,
pub response_headers_at_ms: Option<i64>,
pub first_event_at_ms: Option<i64>,
pub first_text_at_ms: Option<i64>,
pub first_valid_response_at_ms: Option<i64>,
pub finished_at_ms: Option<i64>,
pub queue_ms: Option<i64>,
pub ttfb_ms: Option<i64>,
pub ttft_ms: Option<i64>,
pub ttfr_ms: Option<i64>,
pub duration_ms: Option<i64>,
pub input_tokens: Option<i64>,
pub output_tokens: Option<i64>,
pub total_tokens: Option<i64>,
pub cache_read_tokens: Option<i64>,
pub cache_write_tokens: Option<i64>,
pub reasoning_tokens: Option<i64>,
pub usage: Option<serde_json::Value>,
pub message_count: i64,
pub tool_count: i64,
pub request_bytes: Option<i64>,
pub response_bytes: i64,
pub stream_event_count: i64,
pub http_status: Option<i64>,
pub error_kind: Option<String>,
pub error_message: Option<String>,
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<String>,
pub route: String,
pub model_id: Option<String>,
pub status: String,
pub request_bytes: i64,
pub response_bytes: i64,
pub response_event_count: i64,
pub http_status: Option<i64>,
pub received_at_ms: i64,
pub first_response_at_ms: Option<i64>,
pub finished_at_ms: Option<i64>,
pub error_message: Option<String>,
}
#[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<u8>,
}
}
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<TokenUsageBucket>,
}
}
pub use overview::*;