use crate::identity::AgentIdentity;
use crate::logger::{SekuireLogger, LoggerConfig};
use crate::llm::{LLMProvider, OpenAIProvider, AnthropicProvider, GeminiProvider};
use anyhow::{Result, Context};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentConfig {
pub name: String,
pub version: String,
pub description: Option<String>,
pub llm: LLMConfig,
#[serde(default)]
pub llm_providers: Vec<LLMConfig>,
#[serde(default = "default_system_prompt_path")]
pub system_prompt_path: PathBuf,
#[serde(default = "default_tools_directory")]
pub tools_directory: PathBuf,
#[serde(default = "default_private_key_path")]
pub private_key_path: PathBuf,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(default)]
pub compliance: ComplianceConfig,
}
fn default_system_prompt_path() -> PathBuf {
PathBuf::from("system.md")
}
fn default_tools_directory() -> PathBuf {
PathBuf::from("tools")
}
fn default_private_key_path() -> PathBuf {
PathBuf::from("sekuire.key")
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LLMConfig {
pub provider: String,
pub model: String,
pub api_key: Option<String>,
#[serde(default = "default_temperature")]
pub temperature: f32,
#[serde(default = "default_max_tokens")]
pub max_tokens: u32,
}
fn default_temperature() -> f32 {
0.7
}
fn default_max_tokens() -> u32 {
2000
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct ComplianceConfig {
#[serde(default)]
pub logging: LoggerConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ToolConfig {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
pub implementation: Option<PathBuf>,
}
pub struct SekuireAgent {
config: AgentConfig,
identity: AgentIdentity,
logger: Option<SekuireLogger>,
system_prompt: String,
tools: Vec<ToolConfig>,
llm: Box<dyn LLMProvider>,
}
impl SekuireAgent {
pub async fn new(config_path: impl AsRef<Path>) -> Result<Self> {
let config = Self::load_config(config_path)?;
let identity = AgentIdentity::new(&config.private_key_path)?;
let logger = if config.compliance.logging.enabled {
Some(SekuireLogger::new(
identity.sekuire_id.clone(),
config.compliance.logging.clone(),
))
} else {
None
};
let system_prompt = fs::read_to_string(&config.system_prompt_path)
.context("Failed to load system prompt")?;
let tools = Self::load_tools(&config.tools_directory)?;
let llm = Self::init_llm(&config.llm)?;
Ok(Self {
config,
identity,
logger,
system_prompt,
tools,
llm,
})
}
fn load_config(path: impl AsRef<Path>) -> Result<AgentConfig> {
let content = fs::read_to_string(path)?;
let config: AgentConfig = serde_yaml::from_str(&content)?;
Ok(config)
}
fn load_tools(tools_dir: &Path) -> Result<Vec<ToolConfig>> {
if !tools_dir.exists() {
return Ok(Vec::new());
}
let mut tools = Vec::new();
for entry in fs::read_dir(tools_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("yml") {
let content = fs::read_to_string(&path)?;
let tool: ToolConfig = serde_yaml::from_str(&content)?;
tools.push(tool);
}
}
Ok(tools)
}
fn init_llm(config: &LLMConfig) -> Result<Box<dyn LLMProvider>> {
let provider: Box<dyn LLMProvider> = match config.provider.to_lowercase().as_str() {
"openai" => Box::new(OpenAIProvider::new(config.clone())?),
"anthropic" => Box::new(AnthropicProvider::new(config.clone())?),
"gemini" => Box::new(GeminiProvider::new(config.clone())?),
_ => anyhow::bail!("Unknown LLM provider: {}", config.provider),
};
Ok(provider)
}
pub async fn run(
&mut self,
user_message: &str,
context: Option<HashMap<String, serde_json::Value>>,
) -> Result<AgentResponse> {
if let Some(logger) = &mut self.logger {
logger.log_tool_call(
"agent_run",
serde_json::json!({
"message": user_message,
"context": context,
}),
None,
true,
).await?;
}
let response = self.llm.generate(
&self.system_prompt,
user_message,
&self.tools,
context.as_ref(),
).await?;
if let Some(logger) = &mut self.logger {
logger.log_tool_call(
"agent_run",
serde_json::json!({"message": user_message}),
Some(serde_json::json!({"response": &response})),
true,
).await?;
}
Ok(AgentResponse {
response,
agent_id: self.identity.sekuire_id.clone(),
metadata: ResponseMetadata {
provider: self.llm.provider_name().to_string(),
model: self.config.llm.model.clone(),
tools_available: self.tools.len(),
},
})
}
pub fn get_manifest(&self) -> AgentManifest {
AgentManifest {
name: self.config.name.clone(),
version: self.config.version.clone(),
sekuire_id: self.identity.sekuire_id.clone(),
model: Some(self.config.llm.model.clone()),
tools: self.tools.iter().map(|t| t.name.clone()).collect(),
capabilities: self.config.capabilities.clone(),
compliance: serde_json::to_value(&self.config.compliance).ok(),
}
}
pub fn identity(&self) -> &AgentIdentity {
&self.identity
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentResponse {
pub response: String,
pub agent_id: String,
pub metadata: ResponseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMetadata {
pub provider: String,
pub model: String,
pub tools_available: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentManifest {
pub name: String,
pub version: String,
pub sekuire_id: String,
pub model: Option<String>,
pub tools: Vec<String>,
pub capabilities: Vec<String>,
pub compliance: Option<serde_json::Value>,
}
pub struct MultiAgentOrchestrator {
base_config: AgentConfig,
agents: HashMap<String, SekuireAgent>,
}
impl MultiAgentOrchestrator {
pub async fn new(config_path: impl AsRef<Path>) -> Result<Self> {
let base_config = SekuireAgent::load_config(&config_path)?;
let mut agents = HashMap::new();
for provider_config in &base_config.llm_providers {
let mut agent_config = base_config.clone();
agent_config.llm = provider_config.clone();
}
Ok(Self {
base_config,
agents,
})
}
pub async fn run_with_provider(
&mut self,
provider: &str,
user_message: &str,
context: Option<HashMap<String, serde_json::Value>>,
) -> Result<AgentResponse> {
let agent = self.agents.get_mut(provider)
.context(format!("Provider not configured: {}", provider))?;
agent.run(user_message, context).await
}
pub async fn run_all(
&mut self,
user_message: &str,
context: Option<HashMap<String, serde_json::Value>>,
) -> Result<HashMap<String, Result<AgentResponse>>> {
let mut results = HashMap::new();
for (provider, agent) in &mut self.agents {
let result = agent.run(user_message, context.clone()).await;
results.insert(provider.clone(), result);
}
Ok(results)
}
}