1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::macros::enum_names;
use crate::{ChatCapability, DecisionCapability, EmbeddingCapability};
/// A portable capability enum: [`ChatCapability`], [`EmbeddingCapability`]
/// or [`DecisionCapability`].
///
/// It names the capabilities a [`Validation`](crate::Validation) reports as
/// [`relied_on_unknown`](crate::Validation::relied_on_unknown). It is
/// sealed: each operation has its own enum.
pub trait Capability: Copy + Eq + fmt::Debug + Send + Sync + 'static + sealed::Sealed {}
impl Capability for ChatCapability {}
impl Capability for EmbeddingCapability {}
impl Capability for DecisionCapability {}
pub(crate) mod sealed {
use crate::{ChatCapability, DecisionCapability, EmbeddingCapability};
/// A capability of any operation, so one record holds them all.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnyCapability {
Chat(ChatCapability),
Embedding(EmbeddingCapability),
Decision(DecisionCapability),
}
pub trait Sealed: Sized {
fn into_any(self) -> AnyCapability;
fn from_any(any: AnyCapability) -> Option<Self>;
}
impl Sealed for ChatCapability {
fn into_any(self) -> AnyCapability {
AnyCapability::Chat(self)
}
fn from_any(any: AnyCapability) -> Option<Self> {
match any {
AnyCapability::Chat(capability) => Some(capability),
_ => None,
}
}
}
impl Sealed for EmbeddingCapability {
fn into_any(self) -> AnyCapability {
AnyCapability::Embedding(self)
}
fn from_any(any: AnyCapability) -> Option<Self> {
match any {
AnyCapability::Embedding(capability) => Some(capability),
_ => None,
}
}
}
impl Sealed for DecisionCapability {
fn into_any(self) -> AnyCapability {
AnyCapability::Decision(self)
}
fn from_any(any: AnyCapability) -> Option<Self> {
match any {
AnyCapability::Decision(capability) => Some(capability),
_ => None,
}
}
}
}
/// Support state for a provider/model capability query.
///
/// The enum is intentionally a closed three-state set (`Supported`,
/// `Unsupported`, `Unknown`). It is not `#[non_exhaustive]` because the answer
/// to a capability query is either yes, no, or unknown — there is no fourth
/// state to add later.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CapabilitySupport {
/// The provider explicitly supports this capability for the queried model.
Supported,
/// The provider explicitly does not support this capability for the queried model.
Unsupported,
/// The provider does not provide a definitive answer for this capability.
///
/// Treat this as "unknown, ask carefully" rather than as support.
#[default]
Unknown,
}
enum_names!(CapabilitySupport, "capability support", {
Supported => "supported",
Unsupported => "unsupported",
Unknown => "unknown",
});
impl CapabilitySupport {
/// Returns `true` only when the provider explicitly reports support.
///
/// [`Unknown`](Self::Unknown) is **not** treated as supported: code that
/// gates behavior on capability reporting should opt in only when the
/// provider is certain.
#[must_use]
pub const fn is_supported(self) -> bool {
matches!(self, Self::Supported)
}
/// Returns `true` when the provider gave a definitive answer, regardless
/// of whether that answer was positive or negative.
///
/// Equivalent to `self != CapabilitySupport::Unknown`. Useful when a
/// caller wants to distinguish "the provider told us" from "we have to
/// guess."
#[must_use]
pub const fn is_known(self) -> bool {
!matches!(self, Self::Unknown)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_supported_matches_only_supported() {
assert!(CapabilitySupport::Supported.is_supported());
assert!(!CapabilitySupport::Unsupported.is_supported());
assert!(!CapabilitySupport::Unknown.is_supported());
}
#[test]
fn is_known_matches_supported_and_unsupported() {
assert!(CapabilitySupport::Supported.is_known());
assert!(CapabilitySupport::Unsupported.is_known());
assert!(!CapabilitySupport::Unknown.is_known());
}
#[test]
fn default_is_unknown_and_not_known() {
let value = CapabilitySupport::default();
assert_eq!(value, CapabilitySupport::Unknown);
assert!(!value.is_known());
assert!(!value.is_supported());
}
}