use std::path::{Path, PathBuf};
use super::{Client, Error};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Kind {
Agents,
Swarms,
Functions,
Profiles,
}
impl Kind {
pub fn as_str(self) -> &'static str {
match self {
Kind::Agents => "agents",
Kind::Swarms => "swarms",
Kind::Functions => "functions",
Kind::Profiles => "profiles",
}
}
fn filename(self) -> &'static str {
match self {
Kind::Agents => "agent.json",
Kind::Swarms => "swarm.json",
Kind::Functions => "function.json",
Kind::Profiles => "profile.json",
}
}
}
fn validate_repository_name(name: &str) -> Result<(), Error> {
if name.is_empty() || name.len() > 100 {
return Err(Error::InvalidRepositoryName(name.to_string()));
}
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') {
return Err(Error::InvalidRepositoryName(name.to_string()));
}
Ok(())
}
fn repo_path(client: &Client, kind: Kind, repository: &str) -> PathBuf {
client.base_dir().join(kind.as_str()).join(&client.commit_author_name).join(repository)
}
pub async fn publish_agent(
client: &Client,
repository: &str,
agent: &crate::agent::RemoteAgentBaseWithFallbacks,
message: &str,
overwrite: bool,
) -> Result<String, Error> {
let content = serde_json::to_string_pretty(agent).map_err(Error::Serialize)?;
publish(client, Kind::Agents, repository, &content, message, overwrite).await
}
pub async fn publish_swarm(
client: &Client,
repository: &str,
swarm: &crate::swarm::RemoteSwarmBase,
message: &str,
overwrite: bool,
) -> Result<String, Error> {
let content = serde_json::to_string_pretty(swarm).map_err(Error::Serialize)?;
publish(client, Kind::Swarms, repository, &content, message, overwrite).await
}
pub async fn publish_function(
client: &Client,
repository: &str,
function: &crate::functions::FullRemoteFunction,
message: &str,
overwrite: bool,
) -> Result<String, Error> {
let content = serde_json::to_string_pretty(function).map_err(Error::Serialize)?;
publish(client, Kind::Functions, repository, &content, message, overwrite).await
}
pub async fn publish_profile(
client: &Client,
repository: &str,
profile: &crate::functions::RemoteProfile,
message: &str,
overwrite: bool,
) -> Result<String, Error> {
let content = serde_json::to_string_pretty(profile).map_err(Error::Serialize)?;
publish(client, Kind::Profiles, repository, &content, message, overwrite).await
}
async fn publish(
client: &Client,
kind: Kind,
repository: &str,
content: &str,
message: &str,
overwrite: bool,
) -> Result<String, Error> {
validate_repository_name(repository)?;
let path = repo_path(client, kind, repository);
let filename = kind.filename();
tokio::fs::create_dir_all(&path).await?;
if !overwrite && tokio::fs::metadata(path.join(filename)).await.is_ok() {
return Err(Error::Write(
path.join(filename),
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
"file already exists (use overwrite to replace)",
),
));
}
tokio::fs::write(path.join(filename), content.as_bytes()).await?;
let commit_author_name = client.commit_author_name.clone();
let commit_author_email = client.commit_author_email.clone();
let message = message.to_string();
let filename = filename.to_string();
tokio::task::spawn_blocking(move || {
git_commit(&path, &filename, &commit_author_name, &commit_author_email, &message)
})
.await
.map_err(|e| Error::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?
}
fn git_commit(
repo_path: &Path,
filename: &str,
author_name: &str,
author_email: &str,
message: &str,
) -> Result<String, Error> {
let repo = match git2::Repository::open(repo_path) {
Ok(repo) => {
let mut checkout = git2::build::CheckoutBuilder::new();
checkout.force();
checkout.remove_untracked(true);
if let Ok(head) = repo.head() {
if let Ok(commit) = head.peel_to_commit() {
repo.reset(
commit.as_object(),
git2::ResetType::Hard,
Some(&mut checkout),
)?;
}
}
repo
}
Err(_) => git2::Repository::init(repo_path)?,
};
let mut index = repo.index()?;
index.add_path(Path::new(filename))?;
index.write()?;
let tree_oid = index.write_tree()?;
let parent = repo.head().ok().and_then(|h| h.peel_to_commit().ok());
if let Some(ref parent) = parent {
if parent.tree_id() == tree_oid {
return Ok(parent.id().to_string());
}
}
let tree = repo.find_tree(tree_oid)?;
let sig = git2::Signature::now(author_name, author_email)?;
let parents: Vec<&git2::Commit> = parent.iter().collect();
let commit_oid = repo.commit(
Some("HEAD"),
&sig,
&sig,
message,
&tree,
&parents,
)?;
Ok(commit_oid.to_string())
}