use std::future::Future;
use std::path::{Path, PathBuf};
use serde_json::{Map, Value};
use crate::git::GitRepo;
tokio::task_local! {
static ACTIVE_REPO_ROOT: PathBuf;
}
#[must_use]
pub fn parameters_schema<T: schemars::JsonSchema>() -> Value {
use schemars::schema_for;
let schema = schema_for!(T);
let mut value = serde_json::to_value(schema).expect("tool schema should serialize");
enforce_required_properties(&mut value);
value
}
fn enforce_required_properties(value: &mut Value) {
let Some(obj) = value.as_object_mut() else {
return;
};
let props_entry = obj
.entry("properties")
.or_insert_with(|| Value::Object(Map::new()));
let props_obj = props_entry.as_object().expect("properties must be object");
let required_keys: Vec<Value> = props_obj.keys().cloned().map(Value::String).collect();
obj.insert("required".to_string(), Value::Array(required_keys));
}
pub fn get_current_repo() -> anyhow::Result<GitRepo> {
let repo_root = current_repo_root()?;
GitRepo::new(&repo_root)
}
pub async fn with_active_repo_root<F, T>(repo_path: &Path, future: F) -> T
where
F: Future<Output = T>,
{
ACTIVE_REPO_ROOT
.scope(repo_path.to_path_buf(), future)
.await
}
pub fn current_repo_root() -> anyhow::Result<PathBuf> {
if let Ok(repo_root) = ACTIVE_REPO_ROOT.try_with(Clone::clone) {
return Ok(repo_root);
}
let current_dir = std::env::current_dir()?;
let repo = GitRepo::new(¤t_dir)?;
Ok(repo.repo_path().clone())
}
#[macro_export]
macro_rules! define_tool_error {
($name:ident) => {
#[derive(Debug)]
pub struct $name(pub String);
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for $name {}
impl From<anyhow::Error> for $name {
fn from(err: anyhow::Error) -> Self {
$name(err.to_string())
}
}
impl From<std::io::Error> for $name {
fn from(err: std::io::Error) -> Self {
$name(err.to_string())
}
}
};
}
pub use define_tool_error;