Skip to main content

agent_sdk_eval/
cost.rs

1//! Cost report helpers over deterministic usage reports.
2
3use serde::{Deserialize, Serialize};
4
5use crate::UsageReport;
6
7/// Policy contract for estimating cost from usage evidence.
8pub trait CostPolicy: Send + Sync {
9    /// Estimates cost from one usage report.
10    fn estimate_cost(&self, usage: &UsageReport) -> CostReport;
11}
12
13#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
14/// Static provider-neutral rate table.
15///
16/// Token rates are expressed in micros of `currency` per million tokens.
17/// Tool rates are expressed in micros of `currency` per tool call.
18pub struct StaticRateTable {
19    /// Currency code or accounting unit.
20    pub currency: String,
21    /// Input-token rate per one million input tokens, in micros.
22    pub input_token_micros_per_million: u64,
23    /// Output-token rate per one million output tokens, in micros.
24    pub output_token_micros_per_million: u64,
25    /// Tool-call rate per call, in micros.
26    pub tool_call_micros: u64,
27}
28
29impl StaticRateTable {
30    /// Creates a static rate table.
31    pub fn new(
32        currency: impl Into<String>,
33        input_token_micros_per_million: u64,
34        output_token_micros_per_million: u64,
35        tool_call_micros: u64,
36    ) -> Self {
37        Self {
38            currency: currency.into(),
39            input_token_micros_per_million,
40            output_token_micros_per_million,
41            tool_call_micros,
42        }
43    }
44}
45
46impl CostPolicy for StaticRateTable {
47    fn estimate_cost(&self, usage: &UsageReport) -> CostReport {
48        let input_cost_micros = usage
49            .provider_input_tokens
50            .saturating_mul(self.input_token_micros_per_million)
51            / 1_000_000;
52        let output_cost_micros = usage
53            .provider_output_tokens
54            .saturating_mul(self.output_token_micros_per_million)
55            / 1_000_000;
56        let tool_cost_micros = usage.tool_call_count.saturating_mul(self.tool_call_micros);
57        let total_cost_micros = input_cost_micros
58            .saturating_add(output_cost_micros)
59            .saturating_add(tool_cost_micros);
60        let mut limitations = Vec::new();
61        if usage.provider_call_count == 0 && usage.tool_call_count == 0 {
62            limitations.push("cost report has no provider or tool usage".to_string());
63        }
64        CostReport {
65            currency: self.currency.clone(),
66            input_cost_micros,
67            output_cost_micros,
68            tool_cost_micros,
69            total_cost_micros,
70            limitations,
71        }
72    }
73}
74
75#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
76/// Deterministic cost estimate from a usage report and cost policy.
77pub struct CostReport {
78    /// Currency code or accounting unit.
79    pub currency: String,
80    /// Input token cost in micros.
81    pub input_cost_micros: u64,
82    /// Output token cost in micros.
83    pub output_cost_micros: u64,
84    /// Tool call cost in micros.
85    pub tool_cost_micros: u64,
86    /// Total estimated cost in micros.
87    pub total_cost_micros: u64,
88    /// Limitations found while estimating cost.
89    pub limitations: Vec<String>,
90}