use super::{Capability, CapabilityLocalization, CapabilityStatus, SystemPromptContext};
use crate::driver_registry::ToolSearchConfig;
use async_trait::async_trait;
pub use super::openai_tool_search::DEFAULT_TOOL_SEARCH_THRESHOLD;
pub const CLAUDE_TOOL_SEARCH_CAPABILITY_ID: &str = "claude_tool_search";
pub struct ClaudeToolSearchCapability {
threshold: usize,
}
impl ClaudeToolSearchCapability {
pub fn new() -> Self {
Self {
threshold: DEFAULT_TOOL_SEARCH_THRESHOLD,
}
}
pub fn with_threshold(threshold: usize) -> Self {
Self { threshold }
}
pub fn tool_search_config(&self) -> ToolSearchConfig {
ToolSearchConfig {
enabled: true,
threshold: self.threshold,
}
}
}
pub fn model_supports_native_tool_search(model: &str) -> bool {
crate::model_profiles::get_model_profile(&crate::provider::DriverId::Anthropic, model)
.is_some_and(|profile| profile.tool_search)
}
impl Default for ClaudeToolSearchCapability {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Capability for ClaudeToolSearchCapability {
fn id(&self) -> &str {
CLAUDE_TOOL_SEARCH_CAPABILITY_ID
}
fn name(&self) -> &str {
"Claude Tool Search"
}
fn description(&self) -> &str {
"Enables deferred tool loading for Claude models that support it \
(Sonnet 4, Opus 4, Haiku 4.5, and Fable 5 and newer). Reduces token \
usage by loading tool schemas on-demand instead of upfront."
}
fn localizations(&self) -> Vec<CapabilityLocalization> {
vec![CapabilityLocalization::text(
"uk",
"Пошук інструментів Claude",
"Вмикає відкладене завантаження інструментів для моделей Claude, які його підтримують (Sonnet 4, Opus 4, Haiku 4.5 та Fable 5 і новіші). Зменшує використання токенів, завантажуючи схеми інструментів на вимогу, а не заздалегідь.",
)]
}
fn status(&self) -> CapabilityStatus {
CapabilityStatus::Available
}
fn category(&self) -> Option<&str> {
Some("Optimization")
}
fn tool_search_config(&self, config: &serde_json::Value) -> Option<ToolSearchConfig> {
let threshold = config
.get("threshold")
.and_then(serde_json::Value::as_u64)
.map(|value| value as usize)
.unwrap_or(self.threshold);
Some(ToolSearchConfig {
enabled: true,
threshold,
})
}
async fn system_prompt_contribution(&self, _ctx: &SystemPromptContext) -> Option<String> {
None }
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn runtime_threshold_overrides_constructor_with_safe_fallback() {
for (cap, fallback) in [
(
ClaudeToolSearchCapability::new(),
DEFAULT_TOOL_SEARCH_THRESHOLD,
),
(ClaudeToolSearchCapability::with_threshold(7), 7),
] {
for (config, threshold) in [
(json!(null), fallback),
(json!({}), fallback),
(json!({"threshold":2}), 2),
(json!({"threshold":0}), 0),
(json!({"threshold":-1}), fallback),
(json!({"threshold":"2"}), fallback),
(json!({"threshold":1.5}), fallback),
] {
let actual =
Capability::tool_search_config(&cap, &config).expect("hosted search config");
assert!(actual.enabled);
assert_eq!(
actual.threshold, threshold,
"config={config}, fallback={fallback}"
);
}
}
}
#[test]
fn test_native_support_lookup() {
assert!(model_supports_native_tool_search("claude-opus-4-8"));
assert!(model_supports_native_tool_search("claude-sonnet-5"));
assert!(model_supports_native_tool_search("claude-sonnet-4-6"));
assert!(model_supports_native_tool_search("claude-haiku-4-5"));
assert!(model_supports_native_tool_search("claude-fable-5"));
assert!(model_supports_native_tool_search("claude-fable-5-1"));
assert!(!model_supports_native_tool_search("claude-3-5-haiku"));
assert!(!model_supports_native_tool_search("gpt-5.5"));
}
}