use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub project: ProjectConfig,
pub runtime: RuntimeConfig,
#[cfg(feature = "openai")]
pub ai: Option<AiConfig>,
pub tool_dirs: Vec<PathBuf>,
pub agent_dirs: Vec<PathBuf>,
pub env: HashMap<String, String>,
}
impl Config {
pub async fn load(path: Option<PathBuf>) -> Result<Self> {
if let Some(path) = path {
Self::from_file(&path).await
} else if Path::new("openfunctions.toml").exists() {
Self::from_file("openfunctions.toml").await
} else if Path::new(".openfunctions/config.toml").exists() {
Self::from_file(".openfunctions/config.toml").await
} else {
Ok(Self::default())
}
}
async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = tokio::fs::read_to_string(path).await?;
let config: Self = toml::from_str(&content)?;
config.validate()?;
Ok(config)
}
fn validate(&self) -> Result<()> {
for dir in &self.tool_dirs {
if !dir.exists() {
anyhow::bail!("Tool directory does not exist: {}", dir.display());
}
}
for dir in &self.agent_dirs {
if !dir.exists() {
anyhow::bail!("Agent directory does not exist: {}", dir.display());
}
}
Ok(())
}
}
impl Default for Config {
fn default() -> Self {
Self {
project: ProjectConfig::default(),
runtime: RuntimeConfig::default(),
#[cfg(feature = "openai")]
ai: None,
tool_dirs: vec![PathBuf::from("tools")],
agent_dirs: vec![PathBuf::from("agents")],
env: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
pub name: String,
pub version: String,
pub description: Option<String>,
pub cache_dir: PathBuf,
pub output_dir: PathBuf,
}
impl Default for ProjectConfig {
fn default() -> Self {
Self {
name: "openfunctions-project".to_string(),
version: "0.1.0".to_string(),
description: None,
cache_dir: PathBuf::from(".openfunctions/cache"),
output_dir: PathBuf::from("bin"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeConfig {
pub max_parallel_executions: usize,
pub execution_timeout: u64,
pub memory_limit: Option<u64>,
pub allowed_commands: Vec<String>,
pub env_passthrough: Vec<String>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
max_parallel_executions: 4,
execution_timeout: 300,
memory_limit: None,
allowed_commands: vec![],
env_passthrough: vec!["PATH".to_string(), "HOME".to_string()],
}
}
}
#[cfg(feature = "openai")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiConfig {
pub api_key: Option<String>,
pub model: String,
pub max_tokens: u32,
pub temperature: f32,
pub embedding_model: String,
}
#[cfg(feature = "openai")]
impl Default for AiConfig {
fn default() -> Self {
Self {
api_key: None,
model: "gpt-4o-mini".to_string(),
max_tokens: 2000,
temperature: 0.7,
embedding_model: "text-embedding-3-small".to_string(),
}
}
}