Skip to main content

substrate_core/
tiers.rs

1//! Tiered model dispatch definitions.
2//!
3//! The tier table is pure core data so drivers and adapters can agree on the
4//! same model/effort mapping without depending on each other.
5
6use std::fmt;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11/// Heavy tier model id.
12pub const HEAVY_MODEL: &str = "gpt-5.5";
13/// Heavy tier reasoning effort.
14pub const HEAVY_REASONING_EFFORT: &str = "low";
15/// Main tier model id.
16pub const MAIN_MODEL: &str = "gpt-5.4-mini";
17/// Main tier reasoning effort.
18pub const MAIN_REASONING_EFFORT: &str = "low";
19/// Worker tier model id.
20pub const WORKER_MODEL: &str = "gpt-5.3-codex-spark";
21/// Worker tier reasoning effort.
22pub const WORKER_REASONING_EFFORT: &str = "medium";
23
24/// Tiered model dispatch level.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum Tier {
28    /// Most capable and most expensive route.
29    Heavy,
30    /// Default middle route for normal work.
31    Main,
32    /// Cheapest worker route for delegated/background work.
33    Worker,
34}
35
36impl Tier {
37    /// Return the next higher tier for retry-on-failure routing.
38    pub fn escalate(self) -> Option<Tier> {
39        match self {
40            Tier::Worker => Some(Tier::Main),
41            Tier::Main => Some(Tier::Heavy),
42            Tier::Heavy => None,
43        }
44    }
45
46    /// Return the next lower tier for budget-aware retry routing.
47    pub fn downgrade(self) -> Option<Tier> {
48        match self {
49            Tier::Heavy => Some(Tier::Main),
50            Tier::Main => Some(Tier::Worker),
51            Tier::Worker => None,
52        }
53    }
54
55    /// Return the model/effort pair for this tier.
56    pub fn spec(self) -> TierSpec {
57        match self {
58            Tier::Heavy => TierSpec {
59                model_id: HEAVY_MODEL,
60                reasoning_effort: HEAVY_REASONING_EFFORT,
61            },
62            Tier::Main => TierSpec {
63                model_id: MAIN_MODEL,
64                reasoning_effort: MAIN_REASONING_EFFORT,
65            },
66            Tier::Worker => TierSpec {
67                model_id: WORKER_MODEL,
68                reasoning_effort: WORKER_REASONING_EFFORT,
69            },
70        }
71    }
72}
73
74impl fmt::Display for Tier {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        let value = match self {
77            Tier::Heavy => "heavy",
78            Tier::Main => "main",
79            Tier::Worker => "worker",
80        };
81        f.write_str(value)
82    }
83}
84
85impl FromStr for Tier {
86    type Err = String;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        match s {
90            "heavy" => Ok(Tier::Heavy),
91            "main" => Ok(Tier::Main),
92            "worker" => Ok(Tier::Worker),
93            other => Err(format!("invalid tier {other}: use heavy, main, or worker")),
94        }
95    }
96}
97
98/// Concrete model settings for a [`Tier`].
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
100pub struct TierSpec {
101    /// Model identifier passed to the engine.
102    pub model_id: &'static str,
103    /// Reasoning effort passed as `model_reasoning_effort`.
104    pub reasoning_effort: &'static str,
105}