Skip to main content

mermaid_model/models/
capabilities.rs

1//! Per-model capability metadata.
2//!
3//! Adapters expose `ModelCapabilities` via `Model::capabilities()` so the
4//! rest of the codebase can ask facts like "does this model support tool
5//! calls?" or "what reasoning levels does it accept?" without per-provider
6//! string matching scattered through the codebase. This is the same
7//! pattern Roo Code uses on its `ModelInfo` struct (`supports_reasoning_*`
8//! flags) and Codex CLI uses on `ModelPreset.supported_reasoning_efforts`.
9//!
10//! For Step 1 the values are hardcoded conservative defaults. A future
11//! step can add per-model lookup (similar to <https://models.dev>) or
12//! runtime probing (Ollama `/api/show`).
13
14use super::reasoning::ReasoningCapability;
15
16/// Capability flags advertised by a model adapter.
17#[derive(Debug, Clone)]
18pub struct ModelCapabilities {
19    /// Model accepts tool/function-calling requests in the chat API.
20    pub supports_tools: bool,
21    /// Model accepts image inputs in messages (vision-capable).
22    pub supports_vision: bool,
23    /// Reasoning controls the model exposes — see `ReasoningCapability`.
24    pub supports_reasoning: ReasoningCapability,
25    /// Maximum context window in tokens, if known.
26    pub max_context_tokens: Option<usize>,
27    /// The model's per-response output ceiling in tokens, if known (from
28    /// `/models` metadata or a documented per-model table).
29    pub max_output_tokens: Option<usize>,
30    /// Does the provider emit opaque continuation data that must round-trip on
31    /// the next request (Anthropic thinking, Meta encrypted reasoning)?
32    ///
33    /// Lived on a near-identical `providers::Capabilities` that wrapped this
34    /// struct field-for-field just to carry it. Adapters default it to `false`
35    /// and opt in via `with_provider_continuation()`.
36    pub emits_provider_continuation: bool,
37}
38
39impl ModelCapabilities {
40    /// Builder: mark that this provider round-trips continuation state.
41    #[must_use]
42    pub fn with_provider_continuation(mut self) -> Self {
43        self.emits_provider_continuation = true;
44        self
45    }
46}
47
48impl ModelCapabilities {
49    /// Conservative defaults for an Ollama-served model. We assume tool
50    /// calling (every modern Ollama-supported model the project targets
51    /// has it), assume no vision (the safer static default — real vision
52    /// support is probed from the `/api/show` `capabilities` array by
53    /// `OllamaAdapter::vision_supported` and refreshed into the runtime
54    /// snapshot via `Msg::ProviderVisionResolved`), and treat reasoning as
55    /// binary on/off (matches the `think: bool` semantics for everything
56    /// except gpt-oss).
57    #[must_use]
58    pub fn ollama_default() -> Self {
59        Self {
60            supports_tools: true,
61            supports_vision: false,
62            supports_reasoning: ReasoningCapability::Binary,
63            max_context_tokens: None,
64            max_output_tokens: None,
65            emits_provider_continuation: false,
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn ollama_default_is_conservative() {
76        let caps = ModelCapabilities::ollama_default();
77        assert!(caps.supports_tools);
78        assert!(!caps.supports_vision);
79        assert_eq!(caps.supports_reasoning, ReasoningCapability::Binary);
80        assert!(caps.max_context_tokens.is_none());
81    }
82
83    #[test]
84    fn capabilities_are_cloneable() {
85        let caps = ModelCapabilities::ollama_default();
86        let cloned = caps.clone();
87        assert_eq!(cloned.supports_tools, caps.supports_tools);
88    }
89}