Skip to main content

kernel/event/record/
renewal.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What one keep-warm renewal cost, so a person can read off the history
7//! what keeping a prefix's cache alive spends.
8//!
9//! The request is absent for the reason `ModelCalled` carries no body: a
10//! renewal resends a prefix whose bytes the run already recorded, and the
11//! room address the line is written under says which one.
12
13use serde::{Deserialize, Serialize};
14
15use crate::budget::UsdMicros;
16use crate::error::AxError;
17use crate::model::ModelUsage;
18
19/// `cache_renewed`: one renewal a worker sent, answered or refused.
20///
21/// A refusal is a line of its own rather than a note in the diagnostic
22/// log, because a provider that refuses renewals is a fact about what
23/// keep-warm costs, and the log is not the history.
24///
25/// `Refused` is tried first when reading: every field of `Answered` may
26/// be absent, so it would read any line.
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[serde(untagged)]
30pub enum CacheRenewed {
31    /// The provider refused the renewal; its refusal, unchanged.
32    Refused {
33        /// The failure the provider returned.
34        refused: AxError,
35    },
36    /// The provider answered the renewal.
37    Answered {
38        /// Four token counts, as the provider reported them. Absent when
39        /// it reported none, which is not the same fact as zero.
40        #[serde(default, skip_serializing_if = "Option::is_none")]
41        #[cfg_attr(
42            feature = "schema",
43            schemars(with = "Option<std::collections::BTreeMap<String, u64>>")
44        )]
45        usage: Option<ModelUsage>,
46        /// What the provider itself billed, when it returns a figure.
47        #[serde(default, skip_serializing_if = "Option::is_none")]
48        billed_usd_micros: Option<UsdMicros>,
49    },
50}
51
52#[cfg(test)]
53#[allow(clippy::unwrap_used, reason = "test code")]
54mod tests {
55    use super::*;
56    use crate::event::Payload;
57
58    #[test]
59    fn a_refusal_reads_back_as_a_refusal_and_an_empty_answer_as_an_answer() {
60        let refused = CacheRenewed::Refused {
61            refused: AxError::failure(
62                crate::error::AxCode::Provider,
63                "renew a cached prefix",
64                "rate limited",
65            )
66            .with_recovery("renewal is skipped until the next real request"),
67        };
68        let answered = CacheRenewed::Answered {
69            usage: None,
70            billed_usd_micros: None,
71        };
72        for line in [refused, answered] {
73            assert_eq!(
74                Payload::of(&line).unwrap().read::<CacheRenewed>().unwrap(),
75                line
76            );
77        }
78    }
79}