use std::sync::OnceLock;
use bamboo_agent_core::tools::SharedTool;
use bamboo_domain::tool_names::BUILTIN_TOOL_NAMES;
use bamboo_tools::BuiltinToolExecutor;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolSpec {
pub name: String,
pub description: String,
pub disabled: bool,
}
impl ToolSpec {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
disabled: false,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn disabled(mut self) -> Self {
self.disabled = true;
self
}
}
pub fn builtin_tool_names() -> Vec<String> {
BUILTIN_TOOL_NAMES
.iter()
.map(|s| (*s).to_string())
.collect()
}
pub fn builtin_tool_specs() -> Vec<ToolSpec> {
BUILTIN_TOOL_NAMES
.iter()
.map(|s| ToolSpec::new(*s))
.collect()
}
pub use bamboo_domain::tool_names::BUILTIN_TOOL_NAMES as CANONICAL_TOOL_NAMES;
fn default_builtin_executor() -> &'static BuiltinToolExecutor {
static EXEC: OnceLock<BuiltinToolExecutor> = OnceLock::new();
EXEC.get_or_init(BuiltinToolExecutor::new)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinTool {
ConclusionWithOptions,
Bash,
BashOutput,
Edit,
EnterPlanMode,
ExitPlanMode,
GetFileInfo,
Glob,
Grep,
JsRepl,
KillShell,
SessionNote,
NotebookEdit,
Read,
RequestPermissions,
Sleep,
Task,
WebFetch,
WebSearch,
Workspace,
Write,
}
impl BuiltinTool {
pub const ALL: [BuiltinTool; 21] = [
BuiltinTool::ConclusionWithOptions,
BuiltinTool::Bash,
BuiltinTool::BashOutput,
BuiltinTool::Edit,
BuiltinTool::EnterPlanMode,
BuiltinTool::ExitPlanMode,
BuiltinTool::GetFileInfo,
BuiltinTool::Glob,
BuiltinTool::Grep,
BuiltinTool::JsRepl,
BuiltinTool::KillShell,
BuiltinTool::SessionNote,
BuiltinTool::NotebookEdit,
BuiltinTool::Read,
BuiltinTool::RequestPermissions,
BuiltinTool::Sleep,
BuiltinTool::Task,
BuiltinTool::WebFetch,
BuiltinTool::WebSearch,
BuiltinTool::Workspace,
BuiltinTool::Write,
];
pub const fn name(self) -> &'static str {
match self {
BuiltinTool::ConclusionWithOptions => "conclusion_with_options",
BuiltinTool::Bash => "Bash",
BuiltinTool::BashOutput => "BashOutput",
BuiltinTool::Edit => "Edit",
BuiltinTool::EnterPlanMode => "EnterPlanMode",
BuiltinTool::ExitPlanMode => "ExitPlanMode",
BuiltinTool::GetFileInfo => "GetFileInfo",
BuiltinTool::Glob => "Glob",
BuiltinTool::Grep => "Grep",
BuiltinTool::JsRepl => "js_repl",
BuiltinTool::KillShell => "KillShell",
BuiltinTool::SessionNote => "session_note",
BuiltinTool::NotebookEdit => "NotebookEdit",
BuiltinTool::Read => "Read",
BuiltinTool::RequestPermissions => "request_permissions",
BuiltinTool::Sleep => "Sleep",
BuiltinTool::Task => "Task",
BuiltinTool::WebFetch => "WebFetch",
BuiltinTool::WebSearch => "WebSearch",
BuiltinTool::Workspace => "Workspace",
BuiltinTool::Write => "Write",
}
}
pub fn tool(self) -> SharedTool {
default_builtin_executor()
.registry()
.get(self.name())
.expect("built-in tool must be registered in the default executor")
}
pub fn from_name(name: &str) -> Option<BuiltinTool> {
BuiltinTool::ALL.into_iter().find(|t| t.name() == name)
}
}
impl From<BuiltinTool> for SharedTool {
fn from(tool: BuiltinTool) -> Self {
tool.tool()
}
}
impl std::fmt::Display for BuiltinTool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_tool_names_match_canonical_const() {
let names = builtin_tool_names();
assert_eq!(names.len(), BUILTIN_TOOL_NAMES.len());
for (got, want) in names.iter().zip(BUILTIN_TOOL_NAMES.iter()) {
assert_eq!(got, want);
}
}
#[test]
fn builtin_tool_specs_are_enabled_by_default() {
let specs = builtin_tool_specs();
assert_eq!(specs.len(), BUILTIN_TOOL_NAMES.len());
assert!(specs.iter().all(|s| !s.disabled));
}
#[test]
fn tool_spec_builder_sets_fields() {
let spec = ToolSpec::new("Read")
.with_description("read a file")
.disabled();
assert_eq!(spec.name, "Read");
assert_eq!(spec.description, "read a file");
assert!(spec.disabled);
}
#[test]
fn builtin_tool_enum_matches_canonical_names_exactly() {
assert_eq!(BuiltinTool::ALL.len(), BUILTIN_TOOL_NAMES.len());
for (tool, canonical) in BuiltinTool::ALL.iter().zip(BUILTIN_TOOL_NAMES.iter()) {
assert_eq!(tool.name(), *canonical);
}
}
#[test]
fn builtin_tool_from_name_round_trips() {
for tool in BuiltinTool::ALL {
assert_eq!(BuiltinTool::from_name(tool.name()), Some(tool));
}
assert_eq!(BuiltinTool::from_name("NotARealTool"), None);
}
#[test]
fn builtin_tool_vends_real_instance_with_matching_name() {
for variant in BuiltinTool::ALL {
let tool: SharedTool = variant.tool();
assert_eq!(tool.name(), variant.name());
}
}
}