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 /// The model's per-response output ceiling in tokens, if known.
27 pub max_output_tokens: Option<usize>,
28 /// Does the provider emit opaque continuation data that must round-trip on
29 /// the next request (Anthropic thinking or Meta encrypted reasoning)?
30 pub emits_provider_continuation: 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 max_output_tokens: caps.max_output_tokens,
44 // Provider wrappers opt into continuation explicitly.
45 emits_provider_continuation: false,
46 }
47 }
48
49 /// Builder: mark that this provider round-trips continuation state.
50 pub fn with_provider_continuation(mut self) -> Self {
51 self.emits_provider_continuation = 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 max_output_tokens: Some(8_192),
68 };
69 let caps = Capabilities::from_legacy(&legacy);
70 assert!(caps.supports_tools);
71 assert!(caps.supports_vision);
72 assert_eq!(caps.max_context_tokens, Some(32_000));
73 assert_eq!(caps.max_output_tokens, Some(8_192));
74 assert!(!caps.emits_provider_continuation);
75 }
76
77 #[test]
78 fn with_provider_continuation_sets_flag() {
79 let legacy = ModelCapabilities::ollama_default();
80 let caps = Capabilities::from_legacy(&legacy).with_provider_continuation();
81 assert!(caps.emits_provider_continuation);
82 }
83}