Skip to main content

claude_codex/providers/cursor/
model.rs

1/// Cursor model catalog resolves incoming model names to Cursor wire IDs.
2pub const CURSOR_LEGACY_MODELS: &[&str] = &[
3    "cursor",
4    "cursor-agent",
5    "cursor-composer",
6    "cursor-composer-fast",
7    "cursor-plan",
8    "cursor-ask",
9    "composer-2.5",
10    "composer-2.5-fast",
11];
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CursorAgentMode {
15    Agent,
16    Plan,
17    Ask,
18}
19
20impl CursorAgentMode {
21    pub fn as_str(&self) -> &'static str {
22        match self {
23            CursorAgentMode::Agent => "AGENT_MODE_AGENT",
24            CursorAgentMode::Plan => "AGENT_MODE_PLAN",
25            CursorAgentMode::Ask => "AGENT_MODE_ASK",
26        }
27    }
28}
29
30fn resolve_wire_model(raw: &str) -> (String, bool) {
31    raw.strip_suffix("-fast")
32        .map(|model| (model.to_string(), true))
33        .unwrap_or_else(|| (raw.to_string(), false))
34}
35
36pub fn resolve_cursor_model(model: &str) -> Result<CursorModelResolution, String> {
37    let model = model.trim();
38    for (prefix, mode) in [
39        ("cursor-agent:", CursorAgentMode::Agent),
40        ("cursor-plan:", CursorAgentMode::Plan),
41        ("cursor-ask:", CursorAgentMode::Ask),
42        ("cursor:", CursorAgentMode::Agent),
43    ] {
44        if let Some(raw) = model.strip_prefix(prefix) {
45            let (model_id, fast) = resolve_wire_model(raw);
46            return Ok(CursorModelResolution {
47                model_id,
48                mode,
49                fast,
50            });
51        }
52    }
53
54    let (model_id, mode, fast) = match model {
55        "cursor" | "cursor-agent" => ("default", CursorAgentMode::Agent, false),
56        "cursor-plan" => ("default", CursorAgentMode::Plan, false),
57        "cursor-ask" => ("default", CursorAgentMode::Ask, false),
58        "cursor-composer" | "composer-2.5" => ("composer-2.5", CursorAgentMode::Agent, false),
59        "cursor-composer-fast" | "composer-2.5-fast" => {
60            ("composer-2.5", CursorAgentMode::Agent, true)
61        }
62        _ => {
63            return Err(format!(
64                "unknown cursor model: {model}. Supported: cursor:<id>, cursor-plan:<id>, cursor-ask:<id>, cursor-agent"
65            ));
66        }
67    };
68    Ok(CursorModelResolution {
69        model_id: model_id.to_string(),
70        mode,
71        fast,
72    })
73}
74
75#[derive(Debug, Clone)]
76pub struct CursorModelResolution {
77    pub model_id: String,
78    pub mode: CursorAgentMode,
79    pub fast: bool,
80}
81
82pub fn cursor_supported_models() -> Vec<String> {
83    let mut out: Vec<String> = CURSOR_LEGACY_MODELS
84        .iter()
85        .map(|model| (*model).to_string())
86        .collect();
87    out.sort_unstable();
88    out
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn resolve_legacy_cursor() {
97        let resolution = resolve_cursor_model("cursor").unwrap();
98        assert_eq!(resolution.model_id, "default");
99        assert_eq!(resolution.mode, CursorAgentMode::Agent);
100    }
101
102    #[test]
103    fn resolve_legacy_cursor_agent() {
104        let resolution = resolve_cursor_model("cursor-agent").unwrap();
105        assert_eq!(resolution.model_id, "default");
106        assert_eq!(resolution.mode, CursorAgentMode::Agent);
107    }
108
109    #[test]
110    fn resolve_legacy_cursor_plan() {
111        let resolution = resolve_cursor_model("cursor-plan").unwrap();
112        assert_eq!(resolution.model_id, "default");
113        assert_eq!(resolution.mode, CursorAgentMode::Plan);
114    }
115
116    #[test]
117    fn resolve_legacy_cursor_ask() {
118        let resolution = resolve_cursor_model("cursor-ask").unwrap();
119        assert_eq!(resolution.model_id, "default");
120        assert_eq!(resolution.mode, CursorAgentMode::Ask);
121    }
122
123    #[test]
124    fn resolve_prefixed_cursor() {
125        let resolution = resolve_cursor_model("cursor:gpt-5.5").unwrap();
126        assert_eq!(resolution.model_id, "gpt-5.5");
127        assert_eq!(resolution.mode, CursorAgentMode::Agent);
128    }
129
130    #[test]
131    fn resolve_prefixed_cursor_plan() {
132        let resolution = resolve_cursor_model("cursor-plan:gpt-5.5").unwrap();
133        assert_eq!(resolution.model_id, "gpt-5.5");
134        assert_eq!(resolution.mode, CursorAgentMode::Plan);
135    }
136
137    #[test]
138    fn resolve_prefixed_cursor_ask() {
139        let resolution = resolve_cursor_model("cursor-ask:gpt-5.5").unwrap();
140        assert_eq!(resolution.model_id, "gpt-5.5");
141        assert_eq!(resolution.mode, CursorAgentMode::Ask);
142    }
143
144    #[test]
145    fn resolve_prefixed_cursor_agent() {
146        let resolution = resolve_cursor_model("cursor-agent:gpt-5.5").unwrap();
147        assert_eq!(resolution.model_id, "gpt-5.5");
148        assert_eq!(resolution.mode, CursorAgentMode::Agent);
149    }
150
151    #[test]
152    fn resolve_unknown_model_errors() {
153        assert!(resolve_cursor_model("unknown-model").is_err());
154    }
155
156    #[test]
157    fn resolve_composer_models() {
158        let regular = resolve_cursor_model("composer-2.5").unwrap();
159        assert_eq!(regular.mode, CursorAgentMode::Agent);
160        assert_eq!(regular.model_id, "composer-2.5");
161        assert!(!regular.fast);
162
163        let fast = resolve_cursor_model("composer-2.5-fast").unwrap();
164        assert_eq!(fast.mode, CursorAgentMode::Agent);
165        assert_eq!(fast.model_id, "composer-2.5");
166        assert!(fast.fast);
167    }
168
169    #[test]
170    fn supported_models_includes_all_legacy() {
171        let models = cursor_supported_models();
172        for model in CURSOR_LEGACY_MODELS {
173            assert!(models.contains(&model.to_string()), "missing {model}");
174        }
175    }
176}