use super::AgentRunner;
use crate::config::types::CapabilityLevel;
use crate::core::agent::harness_kernel::SessionToolCatalogSnapshot;
use crate::llm::provider::{LLMRequest, ToolDefinition};
use crate::tools::handlers::{SessionSurface, SessionToolsConfig, ToolModelCapabilities};
use anyhow::{Result, anyhow};
use hashbrown::HashSet;
impl AgentRunner {
fn session_tools_config_for_snapshot(
&self,
planning_active: bool,
request_user_input_enabled: bool,
) -> SessionToolsConfig {
SessionToolsConfig {
surface: SessionSurface::AgentRunner,
capability_level: CapabilityLevel::CodeSearch,
documentation_mode: self.config().agent.tool_documentation_mode,
planning_active,
request_user_input_enabled,
model_capabilities: ToolModelCapabilities::for_model_name(&self.model),
deferred_tool_policy: crate::tools::handlers::deferred_tool_policy_for_runtime(
crate::llm::factory::infer_provider(
Some(&self.config().agent.provider),
&self.model,
),
self.provider_client
.supports_responses_compaction(&self.model),
Some(self.config()),
),
anthropic_native_memory_enabled:
crate::tools::handlers::anthropic_native_memory_enabled_for_runtime(
crate::llm::factory::infer_provider(
Some(&self.config().agent.provider),
&self.model,
),
&self.model,
Some(self.config()),
),
}
}
pub(super) async fn build_exposed_tool_snapshot(&self) -> Result<SessionToolCatalogSnapshot> {
let planning_active = self.tool_registry.is_planning_active();
let request_user_input_enabled = false;
let stable_config = self.session_tools_config_for_snapshot(false, true);
let definitions = self.tool_registry.model_tools(stable_config).await;
let mut active_tool_names = Vec::new();
let mut exposed_definitions = Vec::new();
for tool in definitions {
let tool_name = tool.function_name().to_string();
if self.is_tool_exposed(tool_name.as_str()).await {
active_tool_names.push(tool_name);
exposed_definitions.push(tool);
}
}
Ok(self
.tool_registry
.tool_catalog_state()
.snapshot_for_stable_defs_with_active_names(
exposed_definitions,
active_tool_names,
planning_active,
request_user_input_enabled,
))
}
pub(super) async fn build_exposed_tool_definitions(&self) -> Result<Vec<ToolDefinition>> {
let snapshot = self.build_exposed_tool_snapshot().await?;
Ok(snapshot
.snapshot
.as_ref()
.map(|defs| defs.as_ref().clone())
.unwrap_or_default())
}
pub(crate) async fn build_universal_tools(&self) -> Result<Vec<ToolDefinition>> {
if let Some(definitions) = self.tool_definitions_override.read().clone() {
return Ok(definitions);
}
self.build_exposed_tool_definitions().await
}
pub(crate) async fn build_universal_tool_snapshot(&self) -> Result<SessionToolCatalogSnapshot> {
if let Some(definitions) = self.tool_definitions_override.read().clone() {
return Ok(self.tool_registry.tool_catalog_state().snapshot_for_defs(
definitions,
self.tool_registry.is_planning_active(),
false,
));
}
self.build_exposed_tool_snapshot().await
}
pub(super) fn validate_llm_request(&self, request: &LLMRequest) -> Result<()> {
if request
.system_prompt
.as_ref()
.is_none_or(|s| s.trim().is_empty())
{
return Err(anyhow!("system prompt cannot be empty"));
}
if request.messages.is_empty() {
return Err(anyhow!("message history cannot be empty"));
}
if request.model.trim().is_empty() {
return Err(anyhow!("model identifier cannot be empty"));
}
if let Some(tools) = request.tools.as_deref() {
let mut seen: HashSet<&str> = HashSet::with_capacity(tools.len());
for tool in tools {
validate_tool_definition(tool, &mut seen)?;
}
}
Ok(())
}
}
fn validate_tool_definition<'a>(
tool: &'a ToolDefinition,
seen: &mut HashSet<&'a str>,
) -> Result<()> {
let Some(func) = tool.function.as_ref() else {
return Ok(());
};
let name = func.name.as_str();
if name.trim().is_empty() {
return Err(anyhow!("tool function name cannot be empty"));
}
if !seen.insert(name) {
return Err(anyhow!("duplicate tool name: {name}"));
}
if let Some(obj) = func.parameters.as_object()
&& let Some(required_arr) = obj.get("required").and_then(|v| v.as_array())
&& let Some(props) = obj.get("properties").and_then(|p| p.as_object())
{
for req in required_arr.iter().filter_map(|v| v.as_str()) {
if !props.contains_key(req) {
return Err(anyhow!(
"tool '{name}' has required field '{req}' not in properties"
));
}
}
}
Ok(())
}