pub mod builtin;
use crate::config::PermissionAction;
use crate::provider::{CompletionRequest, ContentPart, Message, Provider, Role};
use crate::session::Session;
use crate::swarm::{Actor, ActorStatus, Handler, SwarmMessage};
use crate::tool::{Tool, ToolRegistry, ToolResult};
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentInfo {
pub name: String,
pub description: Option<String>,
pub mode: AgentMode,
pub native: bool,
pub hidden: bool,
pub model: Option<String>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub max_steps: Option<usize>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AgentMode {
Primary,
Subagent,
All,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolMetadata {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
pub struct Agent {
pub info: AgentInfo,
pub provider: Arc<dyn Provider>,
pub tools: ToolRegistry,
pub permissions: HashMap<String, PermissionAction>,
pub metadata: HashMap<String, ToolMetadata>,
system_prompt: String,
}
impl Agent {
pub fn new(
info: AgentInfo,
provider: Arc<dyn Provider>,
tools: ToolRegistry,
system_prompt: String,
) -> Self {
Self {
info,
provider,
tools,
permissions: HashMap::new(),
metadata: HashMap::new(),
system_prompt,
}
}
pub async fn execute(&self, session: &mut Session, prompt: &str) -> Result<AgentResponse> {
session.add_message(Message {
role: Role::User,
content: vec![ContentPart::Text {
text: prompt.to_string(),
}],
});
let mut steps = 0;
let max_steps = self.info.max_steps.unwrap_or(100);
loop {
steps += 1;
if steps > max_steps {
anyhow::bail!("Exceeded maximum steps ({})", max_steps);
}
let request = CompletionRequest {
messages: self.build_messages(session),
tools: self.tools.definitions(),
model: self
.info
.model
.clone()
.unwrap_or_else(|| match self.provider.name() {
"zhipuai" | "zai" => "glm-5".to_string(),
"openrouter" => "z-ai/glm-5".to_string(),
_ => "glm-5".to_string(),
}),
temperature: self.info.temperature,
top_p: self.info.top_p,
max_tokens: None,
stop: vec![],
};
let response = self.provider.complete(request).await?;
session.add_message(response.message.clone());
let tool_calls: Vec<_> = response
.message
.content
.iter()
.filter_map(|p| match p {
ContentPart::ToolCall {
id,
name,
arguments,
..
} => Some((id.clone(), name.clone(), arguments.clone())),
_ => None,
})
.collect();
if tool_calls.is_empty() {
let text = response
.message
.content
.iter()
.filter_map(|p| match p {
ContentPart::Text { text } => Some(text.clone()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");
return Ok(AgentResponse {
text,
tool_uses: session.tool_uses.clone(),
usage: session.usage.clone(),
});
}
for (id, name, arguments) in tool_calls {
let result = self.execute_tool(&name, &arguments).await;
session.tool_uses.push(ToolUse {
id: id.clone(),
name: name.clone(),
input: arguments.clone(),
output: result.output.clone(),
success: result.success,
});
session.add_message(Message {
role: Role::Tool,
content: vec![ContentPart::ToolResult {
tool_call_id: id,
content: result.output,
}],
});
}
}
}
fn build_messages(&self, session: &Session) -> Vec<Message> {
let mut messages = vec![Message {
role: Role::System,
content: vec![ContentPart::Text {
text: self.system_prompt.clone(),
}],
}];
messages.extend(session.messages.clone());
messages
}
async fn execute_tool(&self, name: &str, arguments: &str) -> ToolResult {
if let Some(permission) = self.permissions.get(name) {
tracing::debug!(tool = name, permission = ?permission, "Checking tool permission");
}
match self.tools.get(name) {
Some(tool) => {
let args: serde_json::Value = match serde_json::from_str(arguments) {
Ok(v) => v,
Err(e) => {
return ToolResult {
output: format!("Failed to parse arguments: {}", e),
success: false,
metadata: HashMap::new(),
};
}
};
match tool.execute(args).await {
Ok(result) => result,
Err(e) => ToolResult {
output: format!("Tool execution failed: {}", e),
success: false,
metadata: HashMap::new(),
},
}
}
None => {
let available_tools = self.tools.list().iter().map(|s| s.to_string()).collect();
let invalid_tool = crate::tool::invalid::InvalidTool::with_context(
name.to_string(),
available_tools,
);
let args = serde_json::json!({
"requested_tool": name,
"args": serde_json::from_str::<serde_json::Value>(arguments).unwrap_or(serde_json::json!({}))
});
match invalid_tool.execute(args).await {
Ok(result) => result,
Err(e) => ToolResult {
output: format!("Unknown tool: {}. Error: {}", name, e),
success: false,
metadata: HashMap::new(),
},
}
}
}
}
pub fn get_tool(&self, name: &str) -> Option<Arc<dyn Tool>> {
self.tools.get(name)
}
pub fn register_tool(&mut self, tool: Arc<dyn Tool>) {
self.tools.register(tool);
}
pub fn list_tools(&self) -> Vec<&str> {
self.tools.list()
}
pub fn has_tool(&self, name: &str) -> bool {
self.tools.get(name).is_some()
}
}
#[async_trait]
impl Actor for Agent {
fn actor_id(&self) -> &str {
&self.info.name
}
fn actor_status(&self) -> ActorStatus {
ActorStatus::Ready
}
async fn initialize(&mut self) -> Result<()> {
tracing::info!(
"Agent '{}' initialized for swarm participation",
self.info.name
);
Ok(())
}
async fn shutdown(&mut self) -> Result<()> {
tracing::info!("Agent '{}' shutting down", self.info.name);
Ok(())
}
}
#[async_trait]
impl Handler<SwarmMessage> for Agent {
type Response = SwarmMessage;
async fn handle(&mut self, message: SwarmMessage) -> Result<Self::Response> {
match message {
SwarmMessage::ExecuteTask {
task_id,
instruction,
} => {
let mut session = Session::new().await?;
match self.execute(&mut session, &instruction).await {
Ok(response) => Ok(SwarmMessage::TaskCompleted {
task_id,
result: response.text,
}),
Err(e) => Ok(SwarmMessage::TaskFailed {
task_id,
error: e.to_string(),
}),
}
}
SwarmMessage::ToolRequest { tool_id, arguments } => {
let result = if let Some(tool) = self.get_tool(&tool_id) {
match tool.execute(arguments).await {
Ok(r) => r,
Err(e) => ToolResult::error(format!("Tool execution failed: {}", e)),
}
} else {
let available_tools = self.tools.list().iter().map(|s| s.to_string()).collect();
let invalid_tool = crate::tool::invalid::InvalidTool::with_context(
tool_id.clone(),
available_tools,
);
let args = serde_json::json!({
"requested_tool": tool_id,
"args": arguments
});
match invalid_tool.execute(args).await {
Ok(r) => r,
Err(e) => ToolResult::error(format!("Tool '{}' not found: {}", tool_id, e)),
}
};
Ok(SwarmMessage::ToolResponse { tool_id, result })
}
_ => {
Ok(SwarmMessage::TaskFailed {
task_id: "unknown".to_string(),
error: "Unsupported message type".to_string(),
})
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentResponse {
pub text: String,
pub tool_uses: Vec<ToolUse>,
pub usage: crate::provider::Usage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUse {
pub id: String,
pub name: String,
pub input: String,
pub output: String,
pub success: bool,
}
pub struct AgentRegistry {
agents: HashMap<String, AgentInfo>,
}
impl AgentRegistry {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
agents: HashMap::new(),
}
}
pub fn register(&mut self, info: AgentInfo) {
self.agents.insert(info.name.clone(), info);
}
#[allow(dead_code)]
pub fn get(&self, name: &str) -> Option<&AgentInfo> {
self.agents.get(name)
}
pub fn list(&self) -> Vec<&AgentInfo> {
self.agents.values().collect()
}
#[allow(dead_code)]
pub fn list_primary(&self) -> Vec<&AgentInfo> {
self.agents
.values()
.filter(|a| a.mode == AgentMode::Primary && !a.hidden)
.collect()
}
pub fn with_builtins() -> Self {
let mut registry = Self::new();
registry.register(builtin::build_agent());
registry.register(builtin::plan_agent());
registry.register(builtin::explore_agent());
registry
}
}
impl Default for AgentRegistry {
fn default() -> Self {
Self::with_builtins()
}
}