use std::process::Command;
use crate::{AiBackend, AiError, GenerateRequest, GenerateResponse};
#[derive(Debug, Clone)]
pub struct Copilot {
bin: String,
}
impl Copilot {
pub fn new() -> Self {
Self {
bin: "copilot".into(),
}
}
pub fn with_bin(mut self, bin: impl Into<String>) -> Self {
self.bin = bin.into();
self
}
pub fn bin(&self) -> &str {
&self.bin
}
fn build_prompt(&self, request: &GenerateRequest) -> String {
let mut parts = Vec::new();
if let Some(persona) = &request.persona {
if !persona.trim().is_empty() {
parts.push(format!("Persona:\n{}", persona));
}
}
if let Some(instructions) = &request.instructions {
if !instructions.trim().is_empty() {
parts.push(format!("Instructions:\n{}", instructions));
}
}
if let Some(memory) = &request.memory {
if !memory.trim().is_empty() {
parts.push(format!("Memory:\n{}", memory));
}
}
parts.push(format!("Prompt:\n{}", request.prompt));
parts.join("\n\n")
}
}
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 = self.build_prompt(request);
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(),
})
}
}