use super::{MVP_TOOL_CAPABILITIES, ToolCapability, ToolRuntime};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ToolCeiling {
pub(super) cwd: std::path::PathBuf,
pub(super) inspection: bool,
pub(super) enabled_builtins: BTreeSet<String>,
pub(super) disabled_tools: BTreeSet<String>,
#[serde(default)]
pub(super) code_mode_disabled_tools: Option<BTreeSet<String>>,
#[serde(default)]
pub(super) client_tools: std::collections::BTreeMap<String, String>,
pub(super) read_absolute_paths: bool,
pub(super) view_image_absolute_paths: bool,
pub(super) hashline_absolute_paths: bool,
pub(super) write_absolute_paths: bool,
pub(super) grep_absolute_paths: bool,
pub(super) find_absolute_paths: bool,
pub(super) list_files_absolute_paths: bool,
pub(super) ast_grep_absolute_paths: bool,
pub(super) bash_absolute_paths: bool,
pub(super) bash_shell_expansion: bool,
pub(super) subagents_absolute_paths: bool,
pub(super) remaining_depth: usize,
}
pub(super) fn inspection_allows(name: &str) -> bool {
matches!(
ToolCapability::from_dispatch_name(name),
Some(
ToolCapability::Read
| ToolCapability::Find
| ToolCapability::ListFiles
| ToolCapability::Grep
| ToolCapability::AstGrep
)
)
}
impl ToolCeiling {
pub(crate) fn capture(tools: &ToolRuntime) -> anyhow::Result<Self> {
tools.capture_tool_ceiling()
}
pub(crate) fn validate(&self) -> anyhow::Result<()> {
anyhow::ensure!(
self.remaining_depth <= crate::subagents::MAX_SUBAGENT_MAX_DEPTH,
"invalid task delegation ceiling"
);
anyhow::ensure!(
self.disabled_tools.len() <= 1024
&& self.enabled_builtins.len()
<= MVP_TOOL_CAPABILITIES.len()
+ usize::from(self.enabled_builtins.contains("jev")),
"oversized task tool ceiling"
);
for name in self.enabled_builtins.iter().chain(&self.disabled_tools) {
anyhow::ensure!(
name.len() <= 512 && ToolRuntime::canonical_disabled_key(name)? == *name,
"invalid task tool ceiling"
);
}
if let Some(names) = &self.code_mode_disabled_tools {
anyhow::ensure!(names.len() <= 1024, "oversized Code Mode tool ceiling");
for name in names {
anyhow::ensure!(
name.len() <= 512 && ToolRuntime::canonical_disabled_key(name)? == *name,
"invalid Code Mode tool ceiling"
);
}
}
anyhow::ensure!(
self.client_tools.len() <= super::client::MAX_TOOLS,
"oversized client tool ceiling"
);
for (name, fingerprint) in &self.client_tools {
anyhow::ensure!(
super::client::valid_name(name)
&& fingerprint.len() == 64
&& fingerprint.bytes().all(|c| c.is_ascii_hexdigit()),
"invalid client tool ceiling"
);
}
Ok(())
}
pub(crate) fn is_inspection(&self) -> bool {
self.inspection
}
pub(crate) fn apply(&self, tools: &mut ToolRuntime) -> anyhow::Result<()> {
tools.apply_tool_ceiling(self)
}
}