use actix_web::{web, HttpResponse};
use bamboo_agent_core::tools::ToolSchema;
use bamboo_tools::BuiltinToolExecutor;
use tracing::{debug, info};
use crate::app_state::AppState;
use crate::error::AppError;
use super::types::{
AvailableToolsResponse, FilteredToolsQuery, FilteredToolsResponse, OpenAiFunction, OpenAiTool,
};
pub async fn get_available_tools(_state: web::Data<AppState>) -> Result<HttpResponse, AppError> {
let tool_names: Vec<String> = BuiltinToolExecutor::tool_schemas()
.into_iter()
.map(|tool| tool.function.name)
.collect();
Ok(HttpResponse::Ok().json(AvailableToolsResponse { tools: tool_names }))
}
pub async fn get_filtered_tools(
state: web::Data<AppState>,
query: web::Query<FilteredToolsQuery>,
) -> Result<HttpResponse, AppError> {
let session_id = resolve_session_identifier(&query);
let selected_skill_ids = selected_skill_ids_for_session(state.get_ref(), session_id).await;
let selected_skill_mode = selected_skill_mode_for_session(state.get_ref(), session_id).await;
let disabled_skill_ids = {
let config = state.config.read().await;
config.disabled_skill_ids()
};
let allowed_tools = state
.skill_manager
.as_ref()
.get_allowed_tools_for_selection_with_mode(
&disabled_skill_ids,
selected_skill_ids.as_deref(),
selected_skill_mode.as_deref(),
)
.await;
debug!("Skill filtered tools allowed list: {:?}", allowed_tools);
let all_tools = BuiltinToolExecutor::tool_schemas();
let all_tool_names: Vec<String> = all_tools
.iter()
.map(|tool| tool.function.name.clone())
.collect();
debug!("Built-in tools discovered: {:?}", all_tool_names);
let tools = to_openai_tools(select_tools_by_allowlist(all_tools, &allowed_tools));
Ok(HttpResponse::Ok().json(FilteredToolsResponse { tools }))
}
pub(super) fn resolve_session_identifier(query: &FilteredToolsQuery) -> Option<&str> {
query.session_id.as_deref().or(query.chat_id.as_deref())
}
async fn selected_skill_ids_for_session(
state: &AppState,
session_id: Option<&str>,
) -> Option<Vec<String>> {
let session_id = session_id?;
let in_memory = {
let sessions = state.sessions.read().await;
sessions.get(session_id).cloned()
};
let session = match in_memory {
Some(session) => Some(session),
None => state.storage.load_session(session_id).await.ok().flatten(),
}?;
session
.metadata
.get("selected_skill_ids")
.and_then(|raw| bamboo_engine::selection::parse_selected_skill_ids_metadata(raw))
}
async fn selected_skill_mode_for_session(
state: &AppState,
session_id: Option<&str>,
) -> Option<String> {
let session_id = session_id?;
let in_memory = {
let sessions = state.sessions.read().await;
sessions.get(session_id).cloned()
};
let session = match in_memory {
Some(session) => Some(session),
None => state.storage.load_session(session_id).await.ok().flatten(),
}?;
let mode = session
.metadata
.get("skill_mode")
.or_else(|| session.metadata.get("mode"))?;
let trimmed = mode.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
pub(super) fn select_tools_by_allowlist(
all_tools: Vec<ToolSchema>,
allowed_tools: &[String],
) -> Vec<ToolSchema> {
if allowed_tools.is_empty() {
info!("No enabled skills; returning all {} tools", all_tools.len());
return all_tools;
}
let filtered: Vec<_> = all_tools
.into_iter()
.filter(|tool| {
allowed_tools
.iter()
.any(|allowed| allowed == &tool.function.name)
})
.collect();
info!(
"Filtered tools: allowed={}, matched={}",
allowed_tools.len(),
filtered.len()
);
filtered
}
pub(super) fn to_openai_tools(tools: Vec<ToolSchema>) -> Vec<OpenAiTool> {
tools
.into_iter()
.map(|tool| OpenAiTool {
tool_type: "function".to_string(),
function: OpenAiFunction {
name: tool.function.name,
description: tool.function.description,
parameters: tool.function.parameters,
},
})
.collect()
}