Skip to main content

auth_cloudflare/
capabilities.rs

1//! Capabilities - marker-based capability inference from a model id.
2//!
3//! The three-state contract (feedback 01/05) forbids encoding incomplete
4//! metadata as `Unsupported`: only a positive safety marker yields
5//! `Unsupported`, and only a positive tool-family marker yields `Confirmed`.
6//! Every other verdict stays `Unknown` - absence of evidence is never
7//! evidence of absence.
8
9use crate::catalog::{CapabilityState, ModelCapabilities};
10
11/// Tool-capable model families, per Cloudflare docs/schema (feedback 01/02).
12/// Family-level markers: each matches every released variant of the family
13/// (e.g. `glm-5` covers glm-5.2, glm-5.3, glm-5.3-flash).
14const TOOL_CAPABLE_FAMILIES: &[&str] = &[
15	"deepseek-v4",
16	"kimi",
17	"gpt-oss",
18	"qwen3",
19	"nemotron",
20	"mistral-small",
21	"llama-3.3",
22	"llama-4-scout",
23	"granite",
24	"glm-5",
25];
26
27/// Non-chat variants whose id also matches a tool-capable family marker
28/// (image/video models must never be inferred tool-capable).
29const NON_CHAT_OVERRIDES: &[&str] = &["kimi-k3", "qwen3-vl", "qwen-image"];
30
31/// Safety/classification models - never chat-capable, never tool-capable.
32const SAFETY_MARKERS: &[&str] = &["llama-guard"];
33
34/// Infer the capability matrix from a model id (marker-based).
35///
36/// - Safety models: `chat` and `tools` are `Unsupported` (positive marker).
37/// - Tool-capable families: `tools` is `Confirmed`.
38/// - Anything else: `Unknown` - never `Unsupported` from absence alone.
39pub fn infer_from_id(id: &str) -> ModelCapabilities {
40	let lower = id.to_lowercase();
41	let mut capabilities = ModelCapabilities::default();
42
43	if SAFETY_MARKERS.iter().any(|marker| lower.contains(marker)) {
44		capabilities.chat = CapabilityState::Unsupported;
45		capabilities.tools = CapabilityState::Unsupported;
46		return capabilities;
47	}
48
49	if NON_CHAT_OVERRIDES.iter().any(|marker| lower.contains(marker)) {
50		return capabilities;
51	}
52
53	if TOOL_CAPABLE_FAMILIES.iter().any(|marker| lower.contains(marker)) {
54		capabilities.tools = CapabilityState::Confirmed;
55	}
56
57	capabilities
58}
59
60#[cfg(test)]
61mod tests {
62	use super::*;
63
64	#[test]
65	fn deepseek_is_tool_capable() {
66		let caps = infer_from_id("@cf/deepseek-ai/deepseek-v4-flash-0731");
67		assert_eq!(caps.tools, CapabilityState::Confirmed);
68		assert_eq!(caps.chat, CapabilityState::Confirmed);
69	}
70
71	#[test]
72	fn premium_models_are_tool_capable() {
73		assert_eq!(
74			infer_from_id("@cf/deepseek-ai/deepseek-v4-pro-0813").tools,
75			CapabilityState::Confirmed
76		);
77		assert_eq!(infer_from_id("@cf/moonshotai/kimi-k2.7-code").tools, CapabilityState::Confirmed);
78		assert_eq!(infer_from_id("@cf/zai-org/glm-5.3-flash").tools, CapabilityState::Confirmed);
79		assert_eq!(infer_from_id("@cf/openai/gpt-oss-120b").tools, CapabilityState::Confirmed);
80	}
81
82	#[test]
83	fn safety_model_is_unsupported_not_unknown() {
84		let caps = infer_from_id("@cf/meta/llama-guard-3-8b");
85		assert_eq!(caps.chat, CapabilityState::Unsupported);
86		assert_eq!(caps.tools, CapabilityState::Unsupported);
87	}
88
89	#[test]
90	fn unknown_model_stays_unknown_not_unsupported() {
91		let caps = infer_from_id("@cf/acme/mystery-model");
92		assert_eq!(caps.chat, CapabilityState::Confirmed);
93		assert_eq!(caps.tools, CapabilityState::Unknown);
94		assert_ne!(caps.tools, CapabilityState::Unsupported);
95	}
96
97	#[test]
98	fn unlisted_chat_family_stays_unknown() {
99		// llama-3.2 has no tool marker - absence must yield Unknown, not Unsupported.
100		let caps = infer_from_id("@cf/meta/llama-3.2-1b-instruct");
101		assert_eq!(caps.tools, CapabilityState::Unknown);
102		assert_ne!(caps.tools, CapabilityState::Unsupported);
103	}
104
105	#[test]
106	fn non_chat_override_never_tool_capable() {
107		// kimi-k3 matches the "kimi" family marker but is an image model.
108		let caps = infer_from_id("@cf/moonshotai/kimi-k3");
109		assert_eq!(caps.tools, CapabilityState::Unknown);
110		assert_ne!(caps.tools, CapabilityState::Confirmed);
111	}
112}