use anyhow::{Context, Result, anyhow};
use colored::*;
use git2::{IndexAddOption, Repository, Signature};
use std::borrow::Cow;
use std::env::var;
use tokio::fs;
use tracing::{debug, error, info};
use crate::agents::agent::AgentGPT;
#[cfg(feature = "net")]
use crate::collaboration::Collaborator;
use crate::common::utils::{
Capability, ClientType, Communication, ContextManager, Knowledge, Persona, Planner, Reflection,
Status, Task, TaskScheduler, Tool,
};
use crate::traits::agent::Agent;
use crate::traits::functions::{AsyncFunctions, Executor, Functions};
use async_trait::async_trait;
use auto_derive::Auto;
use std::fmt;
use tokio::sync::Mutex;
#[cfg(feature = "mem")]
use {
crate::common::memory::load_long_term_memory, crate::common::memory::long_term_memory_context,
crate::common::memory::save_long_term_memory,
};
#[cfg(feature = "oai")]
use {openai_dive::v1::models::FlagshipModel, openai_dive::v1::resources::chat::*};
#[cfg(feature = "gem")]
use gems::{
chat::ChatBuilder,
imagen::ImageGenBuilder,
messages::{Content, Message},
models::Model,
stream::StreamBuilder,
traits::CTrait,
};
#[cfg(any(feature = "oai", feature = "gem", feature = "cld", feature = "xai"))]
use crate::traits::functions::ReqResponse;
#[cfg(feature = "xai")]
use x_ai::{
chat_compl::{ChatCompletionsRequestBuilder, Message as XaiMessage},
traits::ChatCompletionsFetcher,
};
#[cfg(feature = "cld")]
use anthropic_ai_sdk::types::message::{
ContentBlock, CreateMessageParams, Message as AnthMessage, MessageClient,
RequiredMessageParams, Role,
};
#[allow(dead_code)]
#[derive(Auto)]
pub struct GitGPT {
workspace: Cow<'static, str>,
agent: AgentGPT,
client: ClientType,
repo: Mutex<Repository>,
repo_path: String,
}
impl Clone for GitGPT {
fn clone(&self) -> Self {
let repo = Repository::open(&*self.workspace)
.expect("Failed to reopen Git repository during clone");
let repo_path = repo.path().to_string_lossy().to_string();
Self {
workspace: self.workspace.clone(),
agent: self.agent.clone(),
repo: repo.into(),
client: self.client.clone(),
repo_path,
}
}
}
impl fmt::Debug for GitGPT {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GitGPT")
.field("workspace", &self.workspace)
.field("agent", &self.agent)
.field("repo", &"Repository { ... }")
.field("repo_path", &self.repo_path)
.finish()
}
}
impl Default for GitGPT {
fn default() -> Self {
let temp_path = "/tmp/gitgpt";
let repo =
Repository::init(temp_path).expect("Failed to initialize default Git repository");
GitGPT {
workspace: Cow::Borrowed(temp_path),
agent: AgentGPT::default(),
repo: Mutex::new(repo),
repo_path: temp_path.to_string(),
client: ClientType::default(),
}
}
}
impl GitGPT {
pub async fn new(objective: &'static str, position: &'static str) -> Self {
let workspace = var("AUTOGPT_WORKSPACE").unwrap_or_else(|_| "workspace/".to_string());
if !fs::try_exists(&workspace).await.unwrap_or(false) {
match fs::create_dir_all(&workspace).await {
Ok(_) => debug!("Directory '{}' created successfully!", workspace),
Err(e) => error!("Error creating directory '{}': {}", workspace, e),
}
} else {
debug!("Workspace directory '{}' already exists.", workspace);
}
let mut agent = AgentGPT::new_borrowed(objective, position);
agent.id = agent.position().to_string().into();
let repo = if fs::try_exists(format!("{}/.git", &workspace))
.await
.unwrap_or(false)
{
Repository::open(&workspace).expect("Failed to open existing repository")
} else {
Repository::init(&workspace).expect("Failed to initialize git repository")
};
let repo_path = repo.path().to_string_lossy().to_string();
info!(
"{}",
format!("[*] {:?}: GitGPT initialized.", agent.position())
);
Self {
workspace: workspace.into(),
repo: Mutex::new(repo),
agent,
repo_path,
client: ClientType::default(),
}
}
fn author_signature(&self) -> Signature<'_> {
let name = self.agent.position().to_string();
let email = format!("{}@kevin-rs.dev", name.to_lowercase().replace(" ", "_"));
Signature::now(&name, &email).expect("Failed to create signature")
}
async fn stage_all(&self) -> Result<()> {
let repo = self.repo.lock().await;
let mut index = repo.index().context("Failed to get index")?;
index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None)?;
index.write().context("Failed to write index")?;
Ok(())
}
async fn commit_changes(&self, message: &str) -> Result<()> {
let repo = self.repo.lock().await;
let sig = self.author_signature();
let tree_oid = {
let mut index = repo.index()?;
index.write_tree()?
};
let tree = repo.find_tree(tree_oid)?;
let parent_commit = match repo.head().ok().and_then(|h| h.target()) {
Some(oid) => vec![repo.find_commit(oid)?],
None => vec![],
};
let parents: Vec<&git2::Commit> = parent_commit.iter().collect();
let commit_oid = repo.commit(Some("HEAD"), &sig, &sig, message, &tree, &parents)?;
info!(
"{}",
format!(
"[*] {:?}: Commit created: {}",
self.agent.position(),
commit_oid
)
.bright_blue()
);
Ok(())
}
}
#[async_trait]
impl Executor for GitGPT {
async fn execute<'a>(
&'a mut self,
tasks: &'a mut Task,
_execute: bool,
_browse: bool,
_max_tries: u64,
) -> Result<()> {
info!(
"{}",
format!(
"[*] {:?}: Executing Git commit task.",
self.agent.position()
)
.bright_white()
.bold()
);
for task in tasks.description.clone().split("- ") {
if !task.trim().is_empty() {
info!("{} {}", "•".bright_white().bold(), task.trim().cyan());
}
}
match self.agent.status() {
Status::Idle => {
debug!("Agent is idle, proceeding to stage and commit files.");
self.stage_all()
.await
.context("Staging files with git2 failed")?;
self.commit_changes(&tasks.description)
.await
.context("Git commit failed")?;
self.agent.update(Status::Completed);
}
_ => {
debug!(
"[*] {:?}: GitGPT status is not Idle. Skipping commit.",
self.agent.position()
);
}
}
Ok(())
}
}