Skip to main content

muse_codes/
models.rs

1//! Convenience enum for the models the Meta provider exposes.
2//!
3//! Variants are keyed by human-friendly names; [`MuseModel::cli_arg`]
4//! returns the exact string to pass to `muse exec --model` (and therefore
5//! to `MuseExecBuilder::model`, which accepts the enum via `Into<String>`).
6//!
7//! The table mirrors Muse Code's on-disk model catalog
8//! (`~/.local/share/muse/model-catalog/`, `provider_catalog` source) as of
9//! Muse Code 1.0.2. Unknown or future model ids round-trip through
10//! [`MuseModel::Custom`]. Context/output limits from the same catalog are
11//! exposed via [`MuseModel::context_limit`] / [`MuseModel::output_limit`].
12
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use std::fmt;
15
16/// A model id accepted by `muse exec --model` (Meta provider).
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum MuseModel {
19    /// muse-spark-1.3 (`muse-spark-1.3`), released 2026-09-02.
20    Spark13,
21    /// muse-spark-1.3 contributor build (`muse-spark-1.3-contributor`),
22    /// released 2026-09-02. The catalog default as of Muse Code 1.0.2.
23    Spark13Contributor,
24    /// muse-spark-1.2 (`muse-spark-1.2`), released 2026-08-05.
25    Spark12,
26    /// muse-spark-1.2 contributor build (`muse-spark-1.2-contributor`),
27    /// released 2026-08-05.
28    Spark12Contributor,
29    /// A model id not yet known to this version of the crate. Passed to
30    /// the CLI verbatim.
31    Custom(String),
32}
33
34impl MuseModel {
35    /// The string to pass to `muse exec --model` for this model.
36    pub fn cli_arg(&self) -> &str {
37        match self {
38            Self::Spark13 => "muse-spark-1.3",
39            Self::Spark13Contributor => "muse-spark-1.3-contributor",
40            Self::Spark12 => "muse-spark-1.2",
41            Self::Spark12Contributor => "muse-spark-1.2-contributor",
42            Self::Custom(s) => s.as_str(),
43        }
44    }
45
46    /// Alias for [`cli_arg`](Self::cli_arg), matching the crate's
47    /// string-enum convention.
48    pub fn as_str(&self) -> &str {
49        self.cli_arg()
50    }
51
52    /// Human-friendly display name (the catalog's `display_label` equals
53    /// the id for every current row).
54    pub fn display_name(&self) -> &str {
55        self.cli_arg()
56    }
57
58    /// Context window in tokens, from the provider catalog. `None` for
59    /// [`Custom`](Self::Custom) ids the catalog hasn't described.
60    pub fn context_limit(&self) -> Option<u64> {
61        match self {
62            Self::Spark13 | Self::Spark13Contributor | Self::Spark12 | Self::Spark12Contributor => {
63                Some(1_007_997)
64            }
65            Self::Custom(_) => None,
66        }
67    }
68
69    /// Maximum output tokens, from the provider catalog. `None` for
70    /// [`Custom`](Self::Custom) ids the catalog hasn't described.
71    pub fn output_limit(&self) -> Option<u64> {
72        match self {
73            Self::Spark13 | Self::Spark13Contributor | Self::Spark12 | Self::Spark12Contributor => {
74                Some(128_000)
75            }
76            Self::Custom(_) => None,
77        }
78    }
79
80    /// The catalog-default model as of Muse Code 1.0.2 — what a run
81    /// resolves to when `--model` is omitted.
82    pub fn catalog_default() -> Self {
83        Self::Spark13Contributor
84    }
85
86    /// Every model known to this version of the crate, newest first.
87    pub fn known() -> &'static [MuseModel] {
88        &[
89            Self::Spark13,
90            Self::Spark13Contributor,
91            Self::Spark12,
92            Self::Spark12Contributor,
93        ]
94    }
95}
96
97impl fmt::Display for MuseModel {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.write_str(self.cli_arg())
100    }
101}
102
103impl From<&str> for MuseModel {
104    fn from(s: &str) -> Self {
105        match s {
106            "muse-spark-1.3" => Self::Spark13,
107            "muse-spark-1.3-contributor" => Self::Spark13Contributor,
108            "muse-spark-1.2" => Self::Spark12,
109            "muse-spark-1.2-contributor" => Self::Spark12Contributor,
110            other => Self::Custom(other.to_string()),
111        }
112    }
113}
114
115impl From<MuseModel> for String {
116    fn from(model: MuseModel) -> Self {
117        model.cli_arg().to_string()
118    }
119}
120
121impl Serialize for MuseModel {
122    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
123        serializer.serialize_str(self.cli_arg())
124    }
125}
126
127impl<'de> Deserialize<'de> for MuseModel {
128    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
129        let s = String::deserialize(deserializer)?;
130        Ok(Self::from(s.as_str()))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::MuseModel;
137
138    #[test]
139    fn cli_arg_round_trips_for_all_known_models() {
140        for model in MuseModel::known() {
141            assert_eq!(&MuseModel::from(model.cli_arg()), model);
142        }
143        assert_eq!(
144            MuseModel::from("muse-nova-9"),
145            MuseModel::Custom("muse-nova-9".to_string())
146        );
147    }
148
149    #[test]
150    fn catalog_metadata_present_for_known_absent_for_custom() {
151        for model in MuseModel::known() {
152            assert!(model.context_limit().is_some());
153            assert!(model.output_limit().is_some());
154        }
155        assert_eq!(MuseModel::from("muse-nova-9").context_limit(), None);
156    }
157
158    #[test]
159    fn default_is_spark_13_contributor() {
160        assert_eq!(
161            MuseModel::catalog_default().cli_arg(),
162            "muse-spark-1.3-contributor"
163        );
164    }
165}