1use serde::{Deserialize, Serialize};
4
5use agent_sdk_core::{AgentError, RunTrace};
6
7use crate::{EvaluationScope, TraceMetrics};
8
9#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
10pub struct UsageReport {
12 pub scope: EvaluationScope,
14 pub record_count: usize,
16 pub run_count: usize,
18 pub turn_count: usize,
20 pub provider_call_count: u64,
22 pub provider_input_tokens: u64,
24 pub provider_output_tokens: u64,
26 pub provider_total_tokens: u64,
28 pub tool_call_count: u64,
30 pub tool_completed_count: u64,
32 pub tool_non_success_count: u64,
34 pub elapsed_ms: Option<u64>,
36 pub tool_total_elapsed_ms: Option<u64>,
38 pub limitations: Vec<String>,
40}
41
42impl UsageReport {
43 pub fn from_run_trace(trace: &RunTrace) -> Result<Self, AgentError> {
45 Self::from_trace_metrics(TraceMetrics::from_run_trace(trace)?)
46 }
47
48 pub fn from_trace_metrics(metrics: TraceMetrics) -> Result<Self, AgentError> {
50 let mut limitations = Vec::new();
51 if metrics.record_count == 0 {
52 limitations.push("usage report has no journal records".to_string());
53 }
54 if metrics.provider_call_count > 0 && metrics.provider_total_tokens == 0 {
55 limitations.push("provider usage did not include token counts".to_string());
56 }
57 if metrics.elapsed_ms.is_none() {
58 limitations
59 .push("scope elapsed time is unavailable from durable timestamps".to_string());
60 }
61 Ok(Self {
62 scope: metrics.scope,
63 record_count: metrics.record_count,
64 run_count: metrics.run_count,
65 turn_count: metrics.turn_count,
66 provider_call_count: metrics.provider_call_count,
67 provider_input_tokens: metrics.provider_input_tokens,
68 provider_output_tokens: metrics.provider_output_tokens,
69 provider_total_tokens: metrics.provider_total_tokens,
70 tool_call_count: metrics.tool_call_count,
71 tool_completed_count: metrics.tool_completed_count,
72 tool_non_success_count: metrics
73 .tool_failed_count
74 .saturating_add(metrics.tool_timed_out_count)
75 .saturating_add(metrics.tool_cancelled_count)
76 .saturating_add(metrics.tool_denied_count)
77 .saturating_add(metrics.tool_unknown_count)
78 .saturating_add(metrics.tool_recovery_required_count),
79 elapsed_ms: metrics.elapsed_ms,
80 tool_total_elapsed_ms: metrics.tool_total_elapsed_ms,
81 limitations,
82 })
83 }
84}