feat(provider): add capabilities detection mechanism

Add ProviderCapabilities struct to enable runtime detection of
provider-specific features, starting with native tool calling support.

This is a foundational change that enables future PRs to implement
intelligent tool calling mode selection (native vs prompt-guided).

Changes:
- Add ProviderCapabilities struct with native_tool_calling field
- Add capabilities() method to Provider trait with default impl
- Add unit tests for capabilities equality and defaults

Why:
- Current design cannot distinguish providers with native tool calling
- Needed to enable Gemini/Anthropic/OpenAI native function calling
- Fully backward compatible (all providers inherit default)

What did NOT change:
- No existing Provider methods modified
- No behavior changes for existing code
- Zero breaking changes

Testing:
- cargo test: all tests passed
- cargo fmt: pass
- cargo clippy: pass
This commit is contained in:
YubinghanBai 2026-02-16 16:48:15 -06:00 committed by Chummy
parent 42fa802bad
commit b5869d424e

View file

@ -191,8 +191,30 @@ pub enum StreamError {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
} }
/// Provider capabilities declaration.
///
/// Describes what features a provider supports, enabling intelligent
/// adaptation of tool calling modes and request formatting.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ProviderCapabilities {
/// Whether the provider supports native tool calling via API primitives.
///
/// When `true`, the provider can convert tool definitions to API-native
/// formats (e.g., Gemini's functionDeclarations, Anthropic's input_schema).
///
/// When `false`, tools must be injected via system prompt as text.
pub native_tool_calling: bool,
}
#[async_trait] #[async_trait]
pub trait Provider: Send + Sync { pub trait Provider: Send + Sync {
/// Query provider capabilities.
///
/// Default implementation returns minimal capabilities (no native tool calling).
/// Providers should override this to declare their actual capabilities.
fn capabilities(&self) -> ProviderCapabilities {
ProviderCapabilities::default()
}
/// Simple one-shot chat (single user message, no explicit system prompt). /// Simple one-shot chat (single user message, no explicit system prompt).
/// ///
/// This is the preferred API for non-agentic direct interactions. /// This is the preferred API for non-agentic direct interactions.
@ -398,4 +420,26 @@ mod tests {
let json = serde_json::to_string(&tool_result).unwrap(); let json = serde_json::to_string(&tool_result).unwrap();
assert!(json.contains("\"type\":\"ToolResults\"")); assert!(json.contains("\"type\":\"ToolResults\""));
} }
#[test]
fn provider_capabilities_default() {
let caps = ProviderCapabilities::default();
assert!(!caps.native_tool_calling);
}
#[test]
fn provider_capabilities_equality() {
let caps1 = ProviderCapabilities {
native_tool_calling: true,
};
let caps2 = ProviderCapabilities {
native_tool_calling: true,
};
let caps3 = ProviderCapabilities {
native_tool_calling: false,
};
assert_eq!(caps1, caps2);
assert_ne!(caps1, caps3);
}
} }