Skip to main content

okbot_agent/
model.rs

1//! Model/effort syntax supported by the pinned agent-call adapter.
2
3use crate::Provider;
4use okbot_core::AgentError;
5
6/// Splits the model and effort for CLIs that support `model@effort`.
7pub fn model_parts(provider: Provider, model: &str) -> (&str, Option<&str>) {
8    if matches!(provider, Provider::Claude | Provider::Codex) {
9        model
10            .rsplit_once('@')
11            .map_or((model, None), |(m, e)| (m, Some(e)))
12    } else {
13        (model, None)
14    }
15}
16
17/// Validates adapter syntax without guessing model availability or calling a provider.
18pub fn validate_model(provider: Provider, model: &str) -> Result<(), AgentError> {
19    let invalid = |message: &str| AgentError::Failed {
20        message: message.into(),
21        retryable: false,
22    };
23    let (name, effort) = model_parts(provider, model);
24    if name.trim().is_empty() {
25        return Err(invalid("model must not be empty"));
26    }
27    if let Some(effort) = effort {
28        let valid: &[&str] = match provider {
29            Provider::Claude => &["low", "medium", "high", "xhigh", "max"],
30            Provider::Codex => &["low", "medium", "high", "xhigh", "max", "ultra"],
31            _ => &[],
32        };
33        if !valid.contains(&effort) {
34            return Err(invalid(&format!(
35                "unsupported effort `{effort}`; use {}",
36                valid.join(", ")
37            )));
38        }
39    }
40    if provider == Provider::OpenCodeV2 && !name.contains('/') {
41        return Err(invalid("OpenCode model must be provider/model[#variant]"));
42    }
43    Ok(())
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    #[test]
50    fn model_syntax_matches_pinned_adapter() {
51        for (provider, model, valid) in [
52            (Provider::Claude, "sonnet@max", true),
53            (Provider::Claude, "sonnet@ultra", false),
54            (Provider::Codex, "test-model@ultra", true),
55            (Provider::Codex, "@high", false),
56            (Provider::Codex, "test@wrong", false),
57            (Provider::OpenCodeV2, "vendor/model#high", true),
58            (Provider::OpenCodeV2, "model", false),
59            (Provider::Devin, "test", true),
60        ] {
61            assert_eq!(validate_model(provider, model).is_ok(), valid, "{model}");
62        }
63    }
64}