1use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
25#[serde(rename_all = "lowercase")]
26#[non_exhaustive]
27pub enum Kind {
28 Chat,
30 #[cfg(feature = "image")]
32 Image,
33 #[cfg(feature = "video")]
35 Video,
36 #[cfg(feature = "tts")]
38 Tts,
39}
40
41impl Kind {
42 pub const fn label_key(self) -> &'static str {
44 match self {
45 Kind::Chat => "kind.chat",
46 #[cfg(feature = "image")]
47 Kind::Image => "kind.image",
48 #[cfg(feature = "video")]
49 Kind::Video => "kind.video",
50 #[cfg(feature = "tts")]
51 Kind::Tts => "kind.tts",
52 }
53 }
54
55 pub const fn label(self) -> &'static str {
57 match self {
58 Kind::Chat => "对话",
59 #[cfg(feature = "image")]
60 Kind::Image => "生图",
61 #[cfg(feature = "video")]
62 Kind::Video => "视频",
63 #[cfg(feature = "tts")]
64 Kind::Tts => "配音",
65 }
66 }
67
68 pub const fn supports_dry_run(self) -> bool {
73 match self {
74 Kind::Chat => true,
75 #[cfg(feature = "image")]
76 Kind::Image => true,
77 #[cfg(feature = "video")]
78 Kind::Video => false,
79 #[cfg(feature = "tts")]
80 Kind::Tts => true,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[non_exhaustive]
93pub enum Protocol {
94 #[serde(rename = "anthropic")]
96 Anthropic,
97 #[serde(rename = "openai_compatible")]
99 OpenAiCompatible,
100}
101
102impl Protocol {
103 pub const fn as_str(self) -> &'static str {
105 match self {
106 Protocol::Anthropic => "anthropic",
107 Protocol::OpenAiCompatible => "openai_compatible",
108 }
109 }
110
111 pub fn parse(s: &str) -> Option<Self> {
117 match s.trim() {
118 "anthropic" => Some(Protocol::Anthropic),
119 "openai_compatible" => Some(Protocol::OpenAiCompatible),
120 _ => None,
121 }
122 }
123
124 pub const fn default_base_url(self) -> &'static str {
128 match self {
129 Protocol::Anthropic => "https://api.anthropic.com/v1",
130 Protocol::OpenAiCompatible => "https://api.openai.com/v1",
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
141 fn video_has_no_dry_run() {
142 assert!(Kind::Chat.supports_dry_run());
143 #[cfg(feature = "video")]
144 assert!(
145 !Kind::Video.supports_dry_run(),
146 "视频单次约 90 秒且费用高,不提供试运行"
147 );
148 }
149
150 #[test]
154 fn protocol_spellings_agree() {
155 for p in [Protocol::Anthropic, Protocol::OpenAiCompatible] {
156 let wire = serde_json::to_value(p).unwrap();
157 assert_eq!(wire, p.as_str(), "serde 线格式与 as_str 不一致");
158 assert_eq!(Protocol::parse(p.as_str()), Some(p));
159 let back: Protocol = serde_json::from_value(wire).unwrap();
160 assert_eq!(back, p);
161 }
162 assert_eq!(
163 Protocol::parse("open_ai_compatible"),
164 None,
165 "旧拼写不再接受"
166 );
167 }
168
169 #[test]
171 fn default_base_url_has_version_segment() {
172 for p in [Protocol::Anthropic, Protocol::OpenAiCompatible] {
173 assert!(
174 crate::endpoint::ends_with_version_segment(p.default_base_url()),
175 "{} 缺版本段",
176 p.default_base_url()
177 );
178 }
179 }
180}