Skip to main content

mermaid_cli/providers/
capabilities.rs

1//! Per-provider capability flags.
2//!
3//! Providers advertise what they support via `Capabilities`. The
4//! reducer uses this to decide (e.g.) whether to show the reasoning
5//! slider, whether an MCP tool call shape works, or whether vision
6//! inputs are accepted. Future replacements for models.dev
7//! (static-lookup) or `/api/show` (runtime probe) plug in here.
8//!
9//! `Capabilities` is a superset of the adapter-layer
10//! `ModelCapabilities`. Adapters translate via `from_legacy` so the
11//! provider layer doesn't duplicate capability logic.
12
13use crate::models::{ModelCapabilities, ReasoningCapability};
14
15/// Capabilities of a model as advertised by its provider adapter.
16#[derive(Debug, Clone)]
17pub struct Capabilities {
18    /// Native tool/function calling supported.
19    pub supports_tools: bool,
20    /// Vision (image inputs on user messages) supported.
21    pub supports_vision: bool,
22    /// Reasoning controls exposed — binary, enumerated, or numeric.
23    pub supports_reasoning: ReasoningCapability,
24    /// Maximum context window in tokens, if the adapter knows.
25    pub max_context_tokens: Option<usize>,
26    /// Does the provider emit a verifiable "thinking signature" that
27    /// must round-trip on the next request (Anthropic's extended
28    /// thinking)? When true, the reducer preserves the signature on
29    /// the assistant message via `thinking_signature`.
30    pub emits_thinking_signature: bool,
31}
32
33impl Capabilities {
34    /// Construct from the adapter-layer `ModelCapabilities`. Used by
35    /// provider wrappers so we don't duplicate adapter-side capability
36    /// logic.
37    pub fn from_legacy(caps: &ModelCapabilities) -> Self {
38        Self {
39            supports_tools: caps.supports_tools,
40            supports_vision: caps.supports_vision,
41            supports_reasoning: caps.supports_reasoning.clone(),
42            max_context_tokens: caps.max_context_tokens,
43            // Only Anthropic emits signatures; we can't tell from
44            // `ModelCapabilities` alone. Adapters set this explicitly.
45            emits_thinking_signature: false,
46        }
47    }
48
49    /// Builder: mark that this provider round-trips thinking signatures.
50    pub fn with_thinking_signature(mut self) -> Self {
51        self.emits_thinking_signature = true;
52        self
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn from_legacy_preserves_flags() {
62        let legacy = ModelCapabilities {
63            supports_tools: true,
64            supports_vision: true,
65            supports_reasoning: ReasoningCapability::Binary,
66            max_context_tokens: Some(32_000),
67        };
68        let caps = Capabilities::from_legacy(&legacy);
69        assert!(caps.supports_tools);
70        assert!(caps.supports_vision);
71        assert_eq!(caps.max_context_tokens, Some(32_000));
72        assert!(!caps.emits_thinking_signature);
73    }
74
75    #[test]
76    fn with_thinking_signature_sets_flag() {
77        let legacy = ModelCapabilities::ollama_default();
78        let caps = Capabilities::from_legacy(&legacy).with_thinking_signature();
79        assert!(caps.emits_thinking_signature);
80    }
81}