use async_trait::async_trait;
use serde_json::{json, Value};
use super::Tool;
use crate::party::{self, PartyConfig};
pub struct PartyModeTool;
#[async_trait]
impl Tool for PartyModeTool {
fn name(&self) -> &str {
"party_mode"
}
fn capability(&self) -> crate::risk::Capability {
crate::risk::Capability::Delegating
}
fn description(&self) -> &str {
"Run a multi-agent roundtable discussion. Multiple personas (Tyler, Elliot, Nicole, etc.) \
respond in parallel, each from their own perspective. Use when the user wants multiple \
opinions, a group discussion, or 'party mode'."
}
fn input_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The topic or question for the roundtable"
},
"personas": {
"type": "array",
"items": { "type": "string" },
"description": "Optional list of persona names/skills to include. If omitted, all available personas participate."
},
"model": {
"type": "string",
"description": "Optional model override for all sub-agents"
}
},
"required": ["message"]
})
}
async fn execute(&self, input: Value) -> Result<String, String> {
let message = input
.get("message")
.and_then(|v| v.as_str())
.ok_or_else(|| "Missing 'message' field".to_string())?
.to_string();
let personas = input.get("personas").and_then(|v| v.as_array()).map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect::<Vec<_>>()
});
let model = input
.get("model")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let config = crate::config::AppConfig::load().unwrap_or_default();
let party_config = PartyConfig {
message,
personas,
model,
timeout: None, };
let responses = party::run_party(&config, party_config).await;
Ok(party::format_responses(&responses))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tools::ToolRegistry;
#[test]
fn party_mode_tool_has_correct_name() {
let tool = PartyModeTool;
assert_eq!(tool.name(), "party_mode");
}
#[test]
fn party_mode_schema_requires_message() {
let tool = PartyModeTool;
let schema = tool.input_schema();
let required = schema["required"].as_array().unwrap();
assert!(required.contains(&json!("message")));
}
#[test]
fn party_mode_schema_has_optional_fields() {
let tool = PartyModeTool;
let schema = tool.input_schema();
let props = schema["properties"].as_object().unwrap();
assert!(props.contains_key("personas"));
assert!(props.contains_key("model"));
}
#[tokio::test]
async fn party_mode_rejects_missing_message() {
let tool = PartyModeTool;
let err = tool.execute(json!({})).await.unwrap_err();
assert!(err.contains("Missing"), "got: {}", err);
}
#[tokio::test]
async fn party_mode_tool_is_callable() {
let tool = PartyModeTool;
assert!(!tool.description().is_empty());
}
#[tokio::test]
async fn party_mode_register_in_registry() {
let mut registry = ToolRegistry::new();
registry.register(Box::new(PartyModeTool));
assert!(registry.get_tool("party_mode").is_some());
}
}