use serde::{Deserialize, Serialize};
use crate::types::BuiltinTool;
const MANIFEST_PATH: &str = "agent.json";
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub(crate) struct AgentManifest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<String>>,
}
pub(crate) async fn load() -> AgentManifest {
let fs = super::shared_opfs();
if let Ok(bytes) = fs.read(MANIFEST_PATH).await {
if let Ok(manifest) = serde_json::from_slice::<AgentManifest>(&bytes) {
return manifest;
}
}
AgentManifest::default()
}
pub(crate) async fn save(manifest: &AgentManifest) -> Result<(), String> {
let fs = super::shared_opfs();
let json = serde_json::to_vec_pretty(manifest).map_err(|e| format!("serialize: {e}"))?;
fs.write_atomic(MANIFEST_PATH, &json)
.await
.map_err(|e| format!("write: {e}"))
}
pub(crate) async fn set_system_prompt(prompt: Option<&str>) -> Result<(), String> {
let mut manifest = load().await;
manifest.system_prompt = prompt
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string);
save(&manifest).await
}
pub(crate) async fn set_tools(tools: Option<&[BuiltinTool]>) -> Result<(), String> {
let mut manifest = load().await;
manifest.tools = tools
.filter(|t| !t.is_empty())
.map(|t| t.iter().map(|tool| tool.wire_name().to_string()).collect());
save(&manifest).await
}
pub(crate) async fn set_tool_names(names: Option<&[String]>) -> Result<(), String> {
let mut manifest = load().await;
manifest.tools = names.filter(|n| !n.is_empty()).map(|n| n.to_vec());
save(&manifest).await
}
pub(crate) async fn tool_names() -> Option<Vec<String>> {
load().await.tools
}
pub(crate) async fn system_prompt() -> Option<String> {
load()
.await
.system_prompt
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
pub(crate) async fn closure_tool_allowed(name: &str) -> bool {
match load().await.tools {
None => true, Some(tools) => tools.iter().any(|t| t == name),
}
}
pub(crate) async fn tool_allowlist() -> Option<Vec<BuiltinTool>> {
let names = load().await.tools?;
let tools: Vec<BuiltinTool> = names
.iter()
.filter_map(|n| BuiltinTool::ALL.iter().find(|t| t.wire_name() == n).copied())
.collect();
if tools.is_empty() {
None
} else {
Some(tools)
}
}