refactor: align project directory architecture

This commit is contained in:
leookun
2026-08-29 20:45:51 +08:00
parent 88f258f184
commit 279e6bb07c
236 changed files with 1982 additions and 57487 deletions
-28
View File
@@ -1,28 +0,0 @@
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>,
}
-93
View File
@@ -1,93 +0,0 @@
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,
}
+2 -8
View File
@@ -1,31 +1,25 @@
mod configuration;
mod conversation;
mod cursor_trace;
mod inference;
mod llm_call;
mod message;
mod model_spec;
mod overview;
mod observability;
mod projection;
mod run;
mod runtime_tag;
mod token_count;
mod tool;
mod tool_result_replay;
mod usage;
pub use configuration::*;
pub use conversation::*;
pub use cursor_trace::*;
pub use inference::*;
pub use llm_call::*;
pub use message::*;
pub use model_spec::*;
pub use overview::*;
pub use observability::*;
pub use projection::*;
pub use run::*;
pub use runtime_tag::*;
pub(crate) use token_count::*;
pub use tool::*;
pub(crate) use tool_result_replay::limit_tool_result_text;
pub use usage::*;
+297
View File
@@ -0,0 +1,297 @@
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 => 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?)
}
#[cfg(test)]
mod tests {
use super::Usage;
use crate::model::ProviderType;
#[test]
fn openai_context_input_does_not_double_count_cached_tokens() {
let usage = Usage {
input_tokens: Some(140_649),
cache_read_tokens: Some(120_000),
cache_write_tokens: Some(10_000),
..Usage::default()
};
assert_eq!(
usage.context_input_tokens(ProviderType::OpenAiResponses),
Some(140_649)
);
assert_eq!(
usage.context_input_tokens(ProviderType::OpenAiChat),
Some(140_649)
);
}
#[test]
fn anthropic_context_input_includes_disjoint_cache_tokens() {
let usage = Usage {
input_tokens: Some(10_649),
cache_read_tokens: Some(120_000),
cache_write_tokens: Some(10_000),
..Usage::default()
};
assert_eq!(
usage.context_input_tokens(ProviderType::Anthropic),
Some(140_649)
);
}
#[test]
fn turn_total_only_reports_fields_known_for_every_cycle() {
let mut total = Usage {
input_tokens: Some(10),
output_tokens: Some(2),
total_tokens: Some(12),
cache_read_tokens: None,
cache_write_tokens: None,
reasoning_tokens: Some(1),
};
total += Usage {
input_tokens: Some(20),
output_tokens: Some(3),
total_tokens: Some(23),
cache_read_tokens: Some(8),
cache_write_tokens: None,
reasoning_tokens: None,
};
assert_eq!(total.input_tokens, Some(30));
assert_eq!(total.output_tokens, Some(5));
assert_eq!(total.total_tokens, Some(35));
assert_eq!(total.cache_read_tokens, None);
assert_eq!(total.cache_write_tokens, None);
assert_eq!(total.reasoning_tokens, None);
}
}
}
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::*;
-50
View File
@@ -1,50 +0,0 @@
//! 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>,
}
-109
View File
@@ -1,109 +0,0 @@
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 => 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?)
}
#[cfg(test)]
mod tests {
use super::Usage;
use crate::model::ProviderType;
#[test]
fn openai_context_input_does_not_double_count_cached_tokens() {
let usage = Usage {
input_tokens: Some(140_649),
cache_read_tokens: Some(120_000),
cache_write_tokens: Some(10_000),
..Usage::default()
};
assert_eq!(
usage.context_input_tokens(ProviderType::OpenAiResponses),
Some(140_649)
);
assert_eq!(
usage.context_input_tokens(ProviderType::OpenAiChat),
Some(140_649)
);
}
#[test]
fn anthropic_context_input_includes_disjoint_cache_tokens() {
let usage = Usage {
input_tokens: Some(10_649),
cache_read_tokens: Some(120_000),
cache_write_tokens: Some(10_000),
..Usage::default()
};
assert_eq!(
usage.context_input_tokens(ProviderType::Anthropic),
Some(140_649)
);
}
#[test]
fn turn_total_only_reports_fields_known_for_every_cycle() {
let mut total = Usage {
input_tokens: Some(10),
output_tokens: Some(2),
total_tokens: Some(12),
cache_read_tokens: None,
cache_write_tokens: None,
reasoning_tokens: Some(1),
};
total += Usage {
input_tokens: Some(20),
output_tokens: Some(3),
total_tokens: Some(23),
cache_read_tokens: Some(8),
cache_write_tokens: None,
reasoning_tokens: None,
};
assert_eq!(total.input_tokens, Some(30));
assert_eq!(total.output_tokens, Some(5));
assert_eq!(total.total_tokens, Some(35));
assert_eq!(total.cache_read_tokens, None);
assert_eq!(total.cache_write_tokens, None);
assert_eq!(total.reasoning_tokens, None);
}
}