1use serde::{Deserialize, Serialize};
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub enum BetaHeader {
35 MessageBatches,
37 PromptCaching,
39 ComputerUse20241022,
41 ComputerUse20250124,
43 Pdfs,
45 TokenCounting,
47 TokenEfficientTools,
49 Output128k,
51 FilesApi,
53 McpClient20250404,
55 McpClient20251120,
57 DevFullThinking,
59 InterleavedThinking,
61 CodeExecution,
63 ExtendedCacheTtl,
65 Context1m,
67 ContextManagement,
69 ModelContextWindowExceeded,
71 Skills,
73 FastMode,
75 Output300k,
77 UserProfiles,
79 AdvisorTool,
81 Other(String),
84}
85
86impl BetaHeader {
87 #[must_use]
89 pub fn as_str(&self) -> &str {
90 match self {
91 Self::MessageBatches => "message-batches-2024-09-24",
92 Self::PromptCaching => "prompt-caching-2024-07-31",
93 Self::ComputerUse20241022 => "computer-use-2024-10-22",
94 Self::ComputerUse20250124 => "computer-use-2025-01-24",
95 Self::Pdfs => "pdfs-2024-09-25",
96 Self::TokenCounting => "token-counting-2024-11-01",
97 Self::TokenEfficientTools => "token-efficient-tools-2025-02-19",
98 Self::Output128k => "output-128k-2025-02-19",
99 Self::FilesApi => "files-api-2025-04-14",
100 Self::McpClient20250404 => "mcp-client-2025-04-04",
101 Self::McpClient20251120 => "mcp-client-2025-11-20",
102 Self::DevFullThinking => "dev-full-thinking-2025-05-14",
103 Self::InterleavedThinking => "interleaved-thinking-2025-05-14",
104 Self::CodeExecution => "code-execution-2025-05-22",
105 Self::ExtendedCacheTtl => "extended-cache-ttl-2025-04-11",
106 Self::Context1m => "context-1m-2025-08-07",
107 Self::ContextManagement => "context-management-2025-06-27",
108 Self::ModelContextWindowExceeded => "model-context-window-exceeded-2025-08-26",
109 Self::Skills => "skills-2025-10-02",
110 Self::FastMode => "fast-mode-2026-02-01",
111 Self::Output300k => "output-300k-2026-03-24",
112 Self::UserProfiles => "user-profiles-2026-03-24",
113 Self::AdvisorTool => "advisor-tool-2026-03-01",
114 Self::Other(v) => v,
115 }
116 }
117
118 #[must_use]
120 pub fn is_known(&self) -> bool {
121 !matches!(self, Self::Other(_))
122 }
123}
124
125impl std::fmt::Display for BetaHeader {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 f.write_str(self.as_str())
128 }
129}
130
131impl From<BetaHeader> for String {
132 fn from(b: BetaHeader) -> Self {
133 b.as_str().to_owned()
134 }
135}
136
137impl From<&BetaHeader> for String {
138 fn from(b: &BetaHeader) -> Self {
139 b.as_str().to_owned()
140 }
141}
142
143impl From<String> for BetaHeader {
144 fn from(s: String) -> Self {
145 Self::from_wire(&s).unwrap_or(Self::Other(s))
146 }
147}
148
149impl From<&str> for BetaHeader {
150 fn from(s: &str) -> Self {
151 Self::from_wire(s).unwrap_or_else(|| Self::Other(s.to_owned()))
152 }
153}
154
155impl BetaHeader {
156 fn from_wire(s: &str) -> Option<Self> {
159 Some(match s {
160 "message-batches-2024-09-24" => Self::MessageBatches,
161 "prompt-caching-2024-07-31" => Self::PromptCaching,
162 "computer-use-2024-10-22" => Self::ComputerUse20241022,
163 "computer-use-2025-01-24" => Self::ComputerUse20250124,
164 "pdfs-2024-09-25" => Self::Pdfs,
165 "token-counting-2024-11-01" => Self::TokenCounting,
166 "token-efficient-tools-2025-02-19" => Self::TokenEfficientTools,
167 "output-128k-2025-02-19" => Self::Output128k,
168 "files-api-2025-04-14" => Self::FilesApi,
169 "mcp-client-2025-04-04" => Self::McpClient20250404,
170 "mcp-client-2025-11-20" => Self::McpClient20251120,
171 "dev-full-thinking-2025-05-14" => Self::DevFullThinking,
172 "interleaved-thinking-2025-05-14" => Self::InterleavedThinking,
173 "code-execution-2025-05-22" => Self::CodeExecution,
174 "extended-cache-ttl-2025-04-11" => Self::ExtendedCacheTtl,
175 "context-1m-2025-08-07" => Self::Context1m,
176 "context-management-2025-06-27" => Self::ContextManagement,
177 "model-context-window-exceeded-2025-08-26" => Self::ModelContextWindowExceeded,
178 "skills-2025-10-02" => Self::Skills,
179 "fast-mode-2026-02-01" => Self::FastMode,
180 "output-300k-2026-03-24" => Self::Output300k,
181 "user-profiles-2026-03-24" => Self::UserProfiles,
182 "advisor-tool-2026-03-01" => Self::AdvisorTool,
183 _ => return None,
184 })
185 }
186}
187
188impl Serialize for BetaHeader {
189 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
190 s.serialize_str(self.as_str())
191 }
192}
193
194impl<'de> Deserialize<'de> for BetaHeader {
195 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
196 let s = String::deserialize(d)?;
197 Ok(Self::from(s))
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use pretty_assertions::assert_eq;
205
206 const ALL_WIRE: &[&str] = &[
209 "message-batches-2024-09-24",
210 "prompt-caching-2024-07-31",
211 "computer-use-2024-10-22",
212 "computer-use-2025-01-24",
213 "pdfs-2024-09-25",
214 "token-counting-2024-11-01",
215 "token-efficient-tools-2025-02-19",
216 "output-128k-2025-02-19",
217 "files-api-2025-04-14",
218 "mcp-client-2025-04-04",
219 "mcp-client-2025-11-20",
220 "dev-full-thinking-2025-05-14",
221 "interleaved-thinking-2025-05-14",
222 "code-execution-2025-05-22",
223 "extended-cache-ttl-2025-04-11",
224 "context-1m-2025-08-07",
225 "context-management-2025-06-27",
226 "model-context-window-exceeded-2025-08-26",
227 "skills-2025-10-02",
228 "fast-mode-2026-02-01",
229 "output-300k-2026-03-24",
230 "user-profiles-2026-03-24",
231 "advisor-tool-2026-03-01",
232 ];
233
234 #[test]
235 fn every_known_wire_value_round_trips_through_from_and_as_str() {
236 for &wire in ALL_WIRE {
237 let parsed = BetaHeader::from(wire);
238 assert!(
239 parsed.is_known(),
240 "expected {wire} to parse to a known variant, got {parsed:?}"
241 );
242 assert_eq!(parsed.as_str(), wire, "round-trip mismatch on {wire}");
243 }
244 }
245
246 #[test]
247 fn unknown_values_fall_through_to_other_and_round_trip() {
248 let unknown = BetaHeader::from("brand-new-beta-2099-12-31");
249 assert!(!unknown.is_known());
250 assert_eq!(unknown.as_str(), "brand-new-beta-2099-12-31");
251 let s: String = unknown.clone().into();
252 assert_eq!(s, "brand-new-beta-2099-12-31");
253 }
254
255 #[test]
256 fn serde_round_trips_known_and_unknown_values() {
257 let known = BetaHeader::Skills;
258 let json = serde_json::to_string(&known).unwrap();
259 assert_eq!(json, r#""skills-2025-10-02""#);
260 let back: BetaHeader = serde_json::from_str(&json).unwrap();
261 assert_eq!(back, BetaHeader::Skills);
262
263 let unknown = BetaHeader::from("custom-x");
264 let json = serde_json::to_string(&unknown).unwrap();
265 assert_eq!(json, r#""custom-x""#);
266 let back: BetaHeader = serde_json::from_str(&json).unwrap();
267 assert_eq!(back, BetaHeader::Other("custom-x".into()));
268 }
269
270 #[test]
271 fn display_matches_wire_format() {
272 assert_eq!(format!("{}", BetaHeader::Skills), "skills-2025-10-02");
273 assert_eq!(
274 format!("{}", BetaHeader::UserProfiles),
275 "user-profiles-2026-03-24"
276 );
277 }
278
279 #[test]
280 fn into_string_works_for_client_builder_beta() {
281 let v: String = BetaHeader::Skills.into();
284 assert_eq!(v, "skills-2025-10-02");
285 let v: String = (&BetaHeader::UserProfiles).into();
286 assert_eq!(v, "user-profiles-2026-03-24");
287 }
288}