harn-cli 0.8.36

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct ModelSelector {
    pub(crate) selector: String,
    pub(crate) provider: String,
    pub(crate) model: String,
}

pub(crate) fn resolve_selector(raw: &str) -> ModelSelector {
    let trimmed = raw.trim();
    if let Some((provider, model)) = parse_provider_model_kv(trimmed) {
        return ModelSelector {
            selector: trimmed.to_string(),
            provider,
            model,
        };
    }
    if let Some((provider, model)) = trimmed.split_once(':') {
        if !provider.is_empty() && !model.is_empty() {
            return ModelSelector {
                selector: trimmed.to_string(),
                provider: provider.to_string(),
                model: model.to_string(),
            };
        }
    }
    let resolved = harn_vm::llm_config::resolve_model_info(trimmed);
    ModelSelector {
        selector: trimmed.to_string(),
        provider: resolved.provider,
        model: resolved.id,
    }
}

fn parse_provider_model_kv(raw: &str) -> Option<(String, String)> {
    let mut provider = None;
    let mut model = None;
    for part in raw.split(',') {
        let (key, value) = part.split_once('=')?;
        match key.trim() {
            "provider" => provider = Some(value.trim().to_string()),
            "model" => model = Some(value.trim().to_string()),
            _ => {}
        }
    }
    match (provider, model) {
        (Some(provider), Some(model)) if !provider.is_empty() && !model.is_empty() => {
            Some((provider, model))
        }
        _ => None,
    }
}

pub(crate) fn selector_label(selector: &ModelSelector) -> String {
    format!("{}:{}", selector.provider, selector.model)
}

pub(crate) fn selector_is_local(selector: &ModelSelector) -> bool {
    matches!(
        selector.provider.as_str(),
        "ollama" | "llamacpp" | "mlx" | "local" | "vllm" | "tgi"
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn selector_accepts_key_value_and_colon_forms() {
        let kv = resolve_selector("provider=openrouter,model=google/gemma");
        assert_eq!(kv.provider, "openrouter");
        assert_eq!(kv.model, "google/gemma");

        let colon = resolve_selector("mock:mock");
        assert_eq!(colon.provider, "mock");
        assert_eq!(colon.model, "mock");
    }
}