leash-harness 0.1.0

Composable local-LLM and robotics harness with MCP, CLI, and safe robot adapters
Documentation
use anyhow::{anyhow, Result};

use crate::{
    config::{AgentProvider, HarnessConfig},
    types::AgentModelResponse,
};

pub fn complete(config: &HarnessConfig, prompt: &str) -> Result<Option<AgentModelResponse>> {
    let prompt = prompt.trim();
    if prompt.is_empty() {
        return Err(anyhow!("agent prompt cannot be empty"));
    }

    match config.agent_provider {
        AgentProvider::DeterministicTest => Ok(Some(deterministic_response(config, prompt))),
        AgentProvider::OpenAiCompatibleHttp | AgentProvider::LocalHttp => Ok(None),
    }
}

fn deterministic_response(config: &HarnessConfig, prompt: &str) -> AgentModelResponse {
    AgentModelResponse {
        ok: true,
        provider: AgentProvider::DeterministicTest.as_str().to_string(),
        model: config.agent_model.clone(),
        prompt: prompt.to_string(),
        text: format!("deterministic-agent: {}", normalize_prompt(prompt)),
    }
}

fn normalize_prompt(prompt: &str) -> String {
    prompt.split_whitespace().collect::<Vec<_>>().join(" ")
}

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

    #[test]
    fn deterministic_agent_normalizes_prompt_without_network() {
        let config = HarnessConfig::default();

        let response = complete(&config, " inspect   the battery ")
            .unwrap()
            .unwrap();

        assert_eq!(response.provider, "deterministic-test");
        assert_eq!(response.model, "deterministic-test");
        assert_eq!(response.text, "deterministic-agent: inspect the battery");
    }

    #[test]
    fn hosted_provider_does_not_make_network_call() {
        let config = HarnessConfig {
            agent_provider: AgentProvider::OpenAiCompatibleHttp,
            agent_base_url: Some("https://example.test/v1".to_string()),
            agent_api_key: Some("secret".to_string()),
            ..HarnessConfig::default()
        };

        assert!(complete(&config, "inspect the battery").unwrap().is_none());
    }
}