// tools/control.rs — Agent control tools // // Tools that affect agent control flow rather than performing work. // These return Result to maintain consistency with other // tools that can fail. The dispatch function handles error wrapping. use anyhow::{Context, Result}; use super::ToolOutput; fn pause(_args: &serde_json::Value) -> Result { Ok(ToolOutput { text: "Pausing autonomous behavior. Only user input will wake you.".to_string(), is_yield: true, images: Vec::new(), model_switch: None, dmn_pause: true, }) } fn switch_model(args: &serde_json::Value) -> Result { let model = args .get("model") .and_then(|v| v.as_str()) .context("'model' parameter is required")?; if model.is_empty() { anyhow::bail!("'model' parameter cannot be empty"); } Ok(ToolOutput { text: format!("Switching to model '{}' after this turn.", model), is_yield: false, images: Vec::new(), model_switch: Some(model.to_string()), dmn_pause: false, }) } fn yield_to_user(args: &serde_json::Value) -> Result { let msg = args .get("message") .and_then(|v| v.as_str()) .unwrap_or("Waiting for input."); Ok(ToolOutput { text: format!("Yielding. {}", msg), is_yield: true, images: Vec::new(), model_switch: None, dmn_pause: false, }) } pub(super) fn tools() -> [super::Tool; 3] { use super::Tool; [ Tool { name: "switch_model", description: "Switch to a different LLM model mid-conversation. Memories and history carry over.", parameters_json: r#"{"type":"object","properties":{"model":{"type":"string","description":"Name of the model to switch to"}},"required":["model"]}"#, handler: |_a, v| Box::pin(async move { let model = v.get("model").and_then(|v| v.as_str()).unwrap_or(""); Ok(format!("Switching to model: {}", model)) }) }, Tool { name: "pause", description: "Pause all autonomous behavior. Only the user can unpause (Ctrl+P or /wake).", parameters_json: r#"{"type":"object","properties":{}}"#, handler: |_a, _v| Box::pin(async { Ok("Pausing autonomous behavior. Only user input will wake you.".into()) }) }, Tool { name: "yield_to_user", description: "Wait for user input before continuing. The only way to enter a waiting state.", parameters_json: r#"{"type":"object","properties":{"message":{"type":"string","description":"Optional status message"}}}"#, handler: |_a, v| Box::pin(async move { let msg = v.get("message").and_then(|v| v.as_str()).unwrap_or("(yielding to user)"); Ok(msg.to_string()) }) }, ] }