kernel/event/record/prefix_held.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//! One wait a model call took at its endpoint's gate behind a pre-warm
7//! of the prefix a building shares, so a person can read off the history
8//! why a call went out late and what the pre-warm cost
9//! (`crates/kernel/spec/Event/Record.lean` D61, gateway D43).
10//!
11//! The pre-warm's request is absent for the reason `ModelCalled` carries
12//! no body: its bytes are the shared part of the request that waited,
13//! which `prompt_assembled` already recorded.
14
15use serde::{Deserialize, Serialize};
16
17use crate::budget::UsdMicros;
18use crate::error::AxError;
19use crate::model::ModelUsage;
20
21/// `prefix_held`: one attempt's wait at its endpoint's gate.
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
24pub struct PrefixHeld {
25 /// Whole microseconds from reaching the gate to being let on toward
26 /// a ticket, on the gate's injected monotonic clock.
27 pub waited_us: u64,
28 /// How the pre-warm this call waited for ended.
29 pub ended: PrewarmEnd,
30 /// What the pre-warm cost or why it was refused; only on the line of
31 /// the call that sent it, so one request is counted once.
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub sent: Option<PrewarmSent>,
34}
35
36/// How one pre-warm ended, as every call that waited for it saw it.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
39#[serde(rename_all = "snake_case")]
40pub enum PrewarmEnd {
41 /// The provider answered it.
42 Answered,
43 /// The provider refused it, or it failed on the way.
44 Refused,
45 /// `HOLD_MAX` passed before it came back, or it found no permit in
46 /// time.
47 TimedOut,
48 /// The thread that sent it stopped before it came back.
49 Abandoned,
50}
51
52/// The pre-warm's own answer, on the line of the call that sent it.
53///
54/// `Refused` is tried first when reading, as in `CacheRenewed`: every
55/// field of `Answered` may be absent, so it would read any line.
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
58#[serde(untagged)]
59pub enum PrewarmSent {
60 /// The failure the pre-warm came back with, unchanged.
61 Refused {
62 /// The failure the provider or the gate returned.
63 refused: AxError,
64 },
65 /// The provider answered the pre-warm.
66 Answered {
67 /// Four token counts, as the provider reported them. Absent when
68 /// it reported none, which is not the same fact as zero.
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 #[cfg_attr(
71 feature = "schema",
72 schemars(with = "Option<std::collections::BTreeMap<String, u64>>")
73 )]
74 usage: Option<ModelUsage>,
75 /// What the provider itself billed, when it returns a figure.
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 billed_usd_micros: Option<UsdMicros>,
78 },
79}