use crate::Result;
use serde_json::Value;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
mod bash;
pub(crate) mod context_checkpoint;
mod edit;
mod extension;
mod find;
pub mod forum;
mod grep;
mod ls;
pub mod memory;
mod memory_context;
mod powershell;
pub(crate) mod read;
mod secret_prompt;
mod subagent;
#[doc(hidden)]
pub use subagent::{legacy_fresh_runtime, spawn_runtime};
mod write;
pub mod activation;
mod agent;
pub mod catalog;
pub mod discovery;
pub mod ledger;
pub mod output;
mod registry;
pub mod respond;
pub mod send_channel;
pub mod shell;
pub(crate) mod util;
pub(crate) use util::expand_path;
pub mod watcher_exit;
pub use crate::runtime::subagent::{
SubagentDisplayRow, SubagentHandle, SubagentRegistry, SubagentResult, SubagentState,
SubagentStatus,
};
pub use agent::resolve_agent_prompt;
pub use bash::{bash_intermediary_snapshot, BashIntermediarySnapshot, BashTool};
pub use discovery::{ActivateToolsTool, SearchToolsTool};
pub use edit::EditTool;
pub use extension::ExtensionTool;
pub use find::FindTool;
pub use grep::GrepTool;
pub use ls::LsTool;
pub use memory_context::MemoryContextTool;
pub use powershell::PowerShellTool;
pub use read::ReadTool;
pub use registry::{DroppedSessionMember, SessionSchemaProjection, ToolRegistry};
pub use respond::RespondTool;
pub use secret_prompt::SecretPromptQueue;
pub use secret_prompt::{PromptKind, SecretPromptHandle, SecretPromptRequest};
pub use send_channel::SendChannelTool;
pub use shell::{ShellEndTool, ShellSendTool, ShellStartTool};
pub use subagent::{
SubagentCollectTool, SubagentModelAuthorizeTool, SubagentModelsTool, SubagentResumeTool,
SubagentStartTool, SubagentStatusTool, SubagentSteerTool, SubagentTool,
};
pub use watcher_exit::WatcherExitTool;
pub use write::WriteTool;
pub(crate) use util::{resolve_path_in, strip_ansi, NEXT_SUBAGENT_ID};
#[doc(hidden)]
pub use subagent::finalize::{build_completion_event, finalize_subagent};
pub struct ToolChannels {
pub tx_delta: Option<crate::tools::output::DeltaSender>,
pub tx_events: Option<tokio::sync::mpsc::UnboundedSender<crate::StreamEvent>>,
}
pub struct ToolCapabilities {
pub memory_backend: Option<crate::memory_backend::MemoryBinding>,
pub watcher_exit_path: Option<PathBuf>,
pub tool_register_tx: Option<tokio::sync::mpsc::UnboundedSender<Vec<Arc<dyn Tool>>>>,
pub session_manager: Option<std::sync::Arc<crate::tools::shell::SessionManager>>,
pub subagent_registry: Option<Arc<Mutex<SubagentRegistry>>>,
pub launch_cancel: Option<crate::CancellationToken>,
pub event_queue: Option<Arc<crate::events::EventQueue>>,
pub delegation_parent: Option<String>,
pub codex_parent_plan: Option<crate::runtime::openai::catalog::CodexExecutionPlan>,
pub secret_prompt: Option<SecretPromptHandle>,
pub orchestration: Option<Arc<crate::orchestration::OrchestrationRuntime>>,
pub tool_activation: Option<crate::tools::discovery::ActivationCapability>,
pub mcp_leases: Option<crate::mcp::McpLeaseCapability>,
pub extension_leases: Option<crate::extensions::lease::ExtensionLeaseCapability>,
pub memory_context: Option<crate::runtime::memory_context::MemoryContextCapability>,
pub cwd: Option<PathBuf>,
pub env: Option<crate::session::types::SessionEnv>,
pub env_stripped: Vec<String>,
pub env_warned: std::sync::Arc<std::sync::Mutex<std::collections::HashSet<String>>>,
}
pub struct ToolLimits {
pub max_tool_output: usize,
pub max_tool_buffer: usize,
pub bash_timeout: u64,
pub bash_max_timeout: u64,
pub subagent_timeout: u64,
}
pub struct ToolContext {
pub channels: ToolChannels,
pub capabilities: ToolCapabilities,
pub limits: ToolLimits,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ToolOrigin {
Builtin,
Extension { extension_id: String },
Mcp {
server_id: String,
server_tool_name: String,
},
Plugin {
plugin_id: String,
tool_name: String,
},
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConcurrencyKey {
Key(String),
Serialize,
}
#[derive(Debug, Clone)]
pub enum ToolOutput {
Text(String),
Blocks { blocks: Vec<Value>, summary: String },
}
impl ToolOutput {
pub fn summary(&self) -> &str {
match self {
Self::Text(s) => s,
Self::Blocks { summary, .. } => summary,
}
}
pub fn into_parts(self) -> (String, Option<Vec<Value>>) {
match self {
Self::Text(s) => (s, None),
Self::Blocks {
mut blocks,
summary,
} => {
let text_first = blocks.first().is_some_and(|b| b["type"] == "text");
if !text_first {
blocks.insert(0, serde_json::json!({"type": "text", "text": summary}));
}
debug_assert!(
blocks[0]["text"] == summary,
"ToolOutput::Blocks invariant: blocks[0] text must equal summary"
);
(summary, Some(blocks))
}
}
}
pub fn into_summary(self) -> String {
self.into_parts().0
}
}
#[async_trait::async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> Value;
async fn execute(&self, params: Value, ctx: ToolContext) -> Result<String>;
async fn execute_rich(&self, params: Value, ctx: ToolContext) -> Result<ToolOutput> {
self.execute(params, ctx).await.map(ToolOutput::Text)
}
fn extension_id(&self) -> Option<&str> {
None
}
fn origin(&self) -> ToolOrigin {
match self.extension_id() {
Some(extension_id) => ToolOrigin::Extension {
extension_id: extension_id.to_string(),
},
None => ToolOrigin::Unknown,
}
}
fn effect(&self) -> crate::tools::catalog::ToolEffect {
crate::tools::catalog::ToolEffect::NonIdempotent
}
fn runs_independently(&self) -> bool {
false
}
fn concurrency_key(&self, _validated_input: &Value) -> Option<ConcurrencyKey> {
None
}
}
#[cfg(test)]
pub(crate) mod test_helpers;
#[cfg(test)]
mod tool_output_tests {
use super::ToolOutput;
use serde_json::json;
#[test]
fn into_parts_prepends_summary_text_when_blocks_not_text_first() {
let img = json!({"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "AA=="}});
let out = ToolOutput::Blocks {
blocks: vec![img.clone()],
summary: "[image 1x1]".into(),
};
let (summary, blocks) = out.into_parts();
let blocks = blocks.unwrap();
assert_eq!(summary, "[image 1x1]");
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0], json!({"type": "text", "text": "[image 1x1]"}));
assert_eq!(blocks[1], img);
}
#[test]
fn into_parts_prepends_summary_text_when_blocks_empty() {
let out = ToolOutput::Blocks {
blocks: vec![],
summary: "s".into(),
};
let (_, blocks) = out.into_parts();
assert_eq!(blocks.unwrap(), vec![json!({"type": "text", "text": "s"})]);
}
#[test]
fn into_parts_leaves_well_formed_blocks_alone() {
let blocks = vec![
json!({"type": "text", "text": "ok"}),
json!({"type": "image"}),
];
let out = ToolOutput::Blocks {
blocks: blocks.clone(),
summary: "ok".into(),
};
let (_, got) = out.into_parts();
assert_eq!(got.unwrap(), blocks);
}
}