Skip to main content

behest_core/
capabilities.rs

1//! Capability flags advertised by provider implementations.
2//!
3//! Used by the runtime for feature negotiation and routing decisions.
4
5use serde::{Deserialize, Serialize};
6
7use crate::cache::CacheTtl;
8
9/// Static capability advertisement returned by provider implementations.
10#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
11pub struct ProviderCapabilities {
12    /// Provider can return a complete chat response.
13    pub chat: bool,
14    /// Provider can stream chat events.
15    pub chat_stream: bool,
16    /// Provider accepts tool definitions in chat requests.
17    pub tool_calling: bool,
18    /// Provider can execute or request multiple tool calls concurrently.
19    pub parallel_tool_calls: bool,
20    /// Provider can constrain output with a JSON schema.
21    pub json_schema_output: bool,
22    /// Provider can consume image content.
23    pub vision: bool,
24    /// Provider can produce embeddings.
25    pub embeddings: bool,
26    /// Maximum supported input tokens, when known.
27    pub max_input_tokens: Option<u32>,
28    /// Maximum supported output tokens, when known.
29    pub max_output_tokens: Option<u32>,
30    /// Provider supports prompt caching.
31    ///
32    /// `false` for providers with no caching (e.g. local models without a
33    /// cache layer). `true` for providers that either accept explicit
34    /// cache markers (Anthropic) or perform automatic prefix caching
35    /// (OpenAI, DeepSeek).
36    pub prompt_caching: bool,
37    /// Maximum number of cache breakpoints the provider accepts.
38    ///
39    /// `Some(4)` for Anthropic (which permits up to four `cache_control`
40    /// markers per request). `None` for providers that perform automatic
41    /// caching without an explicit breakpoint concept (OpenAI, DeepSeek).
42    pub max_cache_breakpoints: Option<u8>,
43    /// Cache TTLs the provider supports.
44    ///
45    /// Empty for providers with automatic caching. Non-empty for Anthropic
46    /// (`[FiveMinutes, OneHour]`).
47    pub cache_ttl_options: Vec<CacheTtl>,
48}
49
50impl ProviderCapabilities {
51    /// Returns a capability set for a basic non-streaming chat provider.
52    #[must_use]
53    pub fn chat() -> Self {
54        Self {
55            chat: true,
56            ..Self::empty()
57        }
58    }
59
60    /// Returns a capability set for an embedding-only provider.
61    #[must_use]
62    pub fn embeddings() -> Self {
63        Self {
64            embeddings: true,
65            ..Self::empty()
66        }
67    }
68
69    /// Returns an empty capability set.
70    #[must_use]
71    pub const fn empty() -> Self {
72        Self {
73            chat: false,
74            chat_stream: false,
75            tool_calling: false,
76            parallel_tool_calls: false,
77            json_schema_output: false,
78            vision: false,
79            embeddings: false,
80            max_input_tokens: None,
81            max_output_tokens: None,
82            prompt_caching: false,
83            max_cache_breakpoints: None,
84            cache_ttl_options: Vec::new(),
85        }
86    }
87}
88
89#[cfg(test)]
90#[allow(clippy::unwrap_used)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn default_capabilities_disables_all_flags() {
96        let caps = ProviderCapabilities::default();
97        assert!(!caps.chat);
98        assert!(!caps.chat_stream);
99        assert!(!caps.tool_calling);
100        assert!(!caps.prompt_caching);
101        assert_eq!(caps.max_cache_breakpoints, None);
102        assert!(caps.cache_ttl_options.is_empty());
103    }
104
105    #[test]
106    fn chat_helper_enables_chat_only() {
107        let caps = ProviderCapabilities::chat();
108        assert!(caps.chat);
109        assert!(!caps.embeddings);
110        assert!(!caps.prompt_caching);
111    }
112
113    #[test]
114    fn empty_helper_is_all_false() {
115        let caps = ProviderCapabilities::empty();
116        assert!(!caps.chat);
117        assert!(!caps.embeddings);
118        assert!(!caps.prompt_caching);
119        assert!(caps.cache_ttl_options.is_empty());
120    }
121}