cliai 0.1.0

A small Rust library for invoking AI tools through CLI backends like Ollama and GitHub Copilot CLI.
Documentation
use std::process::Command;

use crate::{AiBackend, AiError, GenerateRequest, GenerateResponse};

/// AI backend that invokes the GitHub Copilot CLI.
///
/// By default this backend executes the `copilot` binary from `PATH`.
#[derive(Debug, Clone)]
pub struct Copilot {
    bin: String,
}

impl Copilot {
    /// Creates a new Copilot backend using the default `copilot` binary.
    pub fn new() -> Self {
        Self {
            bin: "copilot".into(),
        }
    }

    /// Overrides the executable name or path.
    pub fn with_bin(mut self, bin: impl Into<String>) -> Self {
        self.bin = bin.into();
        self
    }

    /// Returns the configured executable name or path.
    pub fn bin(&self) -> &str {
        &self.bin
    }
}

impl Default for Copilot {
    fn default() -> Self {
        Self::new()
    }
}

impl AiBackend for Copilot {
    fn name(&self) -> &'static str {
        "copilot"
    }

    fn generate(&self, request: &GenerateRequest) -> Result<GenerateResponse, AiError> {
        let full_prompt = match &request.instructions {
            Some(instructions) if !instructions.trim().is_empty() => {
                format!(
                    "Instructions:\n{}\n\nPrompt:\n{}",
                    instructions, request.prompt
                )
            }
            _ => request.prompt.clone(),
        };

        let output = Command::new(&self.bin)
            .args(["-s", "-p", &full_prompt])
            .output()?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();

            return Err(AiError::CommandFailed(format!(
                "{} exited with status {}{}",
                self.bin,
                output.status,
                if stderr.is_empty() {
                    String::new()
                } else {
                    format!(": {}", stderr)
                }
            )));
        }

        Ok(GenerateResponse {
            text: String::from_utf8(output.stdout)?.trim().to_string(),
        })
    }
}