2026-03-25 00:52:41 -04:00
|
|
|
// tools/control.rs — Agent control tools
|
|
|
|
|
//
|
|
|
|
|
// Tools that affect agent control flow rather than performing work.
|
|
|
|
|
// These return Result<ToolOutput> to maintain consistency with other
|
|
|
|
|
// tools that can fail. The dispatch function handles error wrapping.
|
|
|
|
|
|
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
|
|
|
|
|
|
use super::ToolOutput;
|
|
|
|
|
|
2026-04-04 15:50:14 -04:00
|
|
|
fn pause(_args: &serde_json::Value) -> Result<ToolOutput> {
|
2026-03-25 00:52:41 -04:00
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 15:50:14 -04:00
|
|
|
fn switch_model(args: &serde_json::Value) -> Result<ToolOutput> {
|
2026-03-25 00:52:41 -04:00
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 15:50:14 -04:00
|
|
|
fn yield_to_user(args: &serde_json::Value) -> Result<ToolOutput> {
|
2026-03-25 00:52:41 -04:00
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 15:50:14 -04:00
|
|
|
pub(super) fn tools() -> [super::Tool; 3] {
|
2026-04-04 15:22:03 -04:00
|
|
|
use super::Tool;
|
2026-04-04 15:50:14 -04:00
|
|
|
[
|
|
|
|
|
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"]}"#,
|
2026-04-04 15:22:03 -04:00
|
|
|
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))
|
|
|
|
|
}) },
|
2026-04-04 15:50:14 -04:00
|
|
|
Tool { name: "pause",
|
|
|
|
|
description: "Pause all autonomous behavior. Only the user can unpause (Ctrl+P or /wake).",
|
|
|
|
|
parameters_json: r#"{"type":"object","properties":{}}"#,
|
2026-04-04 15:22:03 -04:00
|
|
|
handler: |_a, _v| Box::pin(async { Ok("Pausing autonomous behavior. Only user input will wake you.".into()) }) },
|
2026-04-04 15:50:14 -04:00
|
|
|
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"}}}"#,
|
2026-04-04 15:22:03 -04:00
|
|
|
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())
|
|
|
|
|
}) },
|
|
|
|
|
]
|
|
|
|
|
}
|