#[cfg(feature = "ai-protocol")]
use crate::agent::dispatcher::{build_tool_dispatcher, ToolDispatcher};
#[cfg(feature = "ai-protocol")]
use crate::providers::Provider;
#[cfg(feature = "ai-protocol")]
use ai_lib_rust::ToolCallingPolicy;
#[cfg(feature = "ai-protocol")]
#[derive(Debug, Clone)]
pub struct EffectivePolicy {
pub tool_dispatcher: String,
pub tool_calling: ToolCallingPolicy,
}
#[cfg(feature = "ai-protocol")]
impl EffectivePolicy {
pub fn resolve(
config_tool_dispatcher: &str,
session_tool_dispatcher: Option<&str>,
tool_calling: ToolCallingPolicy,
) -> Self {
let tool_dispatcher = session_tool_dispatcher
.map(str::to_string)
.unwrap_or_else(|| config_tool_dispatcher.to_string());
Self {
tool_dispatcher,
tool_calling,
}
}
pub fn build_dispatcher(&self, provider: &dyn Provider) -> Box<dyn ToolDispatcher> {
build_tool_dispatcher(&self.tool_dispatcher, provider, self.tool_calling.clone())
}
}
#[cfg(all(test, feature = "ai-protocol"))]
mod tests {
use super::*;
use ai_lib_rust::types::{
text_tool::{PromptLevel, TextToolConfig},
StandardTextToolParser,
};
use ai_lib_rust::{NativeStrategy, ToolCallingPolicy};
use async_trait::async_trait;
fn sample_policy() -> ToolCallingPolicy {
ToolCallingPolicy {
parser: StandardTextToolParser::new(TextToolConfig {
lenient_parsing: true,
prompt_level: PromptLevel::L2,
..Default::default()
}),
native_strategy: NativeStrategy::Hybrid,
}
}
struct NativeCapableProvider;
#[async_trait]
impl Provider for NativeCapableProvider {
fn supports_native_tools(&self) -> bool {
true
}
async fn chat_with_system(
&self,
_system: Option<&str>,
_message: &str,
_model: &str,
_temperature: f64,
) -> anyhow::Result<String> {
Ok(String::new())
}
}
struct NoNativeProvider;
#[async_trait]
impl Provider for NoNativeProvider {
fn supports_native_tools(&self) -> bool {
false
}
async fn chat_with_system(
&self,
_system: Option<&str>,
_message: &str,
_model: &str,
_temperature: f64,
) -> anyhow::Result<String> {
Ok(String::new())
}
}
#[test]
fn resolve_uses_config_when_no_session_override() {
let policy = EffectivePolicy::resolve("auto", None, sample_policy());
assert_eq!(policy.tool_dispatcher, "auto");
}
#[test]
fn resolve_session_override_wins_over_config() {
let policy = EffectivePolicy::resolve("auto", Some("xml"), sample_policy());
assert_eq!(policy.tool_dispatcher, "xml");
}
#[test]
fn build_dispatcher_auto_prefers_native_when_capable() {
let effective = EffectivePolicy::resolve("auto", None, sample_policy());
let dispatcher = effective.build_dispatcher(&NativeCapableProvider);
assert!(dispatcher.should_send_tool_specs());
}
#[test]
fn build_dispatcher_xml_never_sends_native_specs() {
let effective = EffectivePolicy::resolve("xml", None, sample_policy());
let dispatcher = effective.build_dispatcher(&NativeCapableProvider);
assert!(!dispatcher.should_send_tool_specs());
}
#[test]
fn build_dispatcher_auto_falls_back_to_xml_without_native() {
let effective = EffectivePolicy::resolve("auto", None, sample_policy());
let dispatcher = effective.build_dispatcher(&NoNativeProvider);
assert!(!dispatcher.should_send_tool_specs());
}
}