zai_rs/client/
endpoints.rs1pub const PAAS_V4_BASE: &str = "https://open.bigmodel.cn/api/paas/v4";
8pub const CODING_PAAS_V4_BASE: &str = "https://open.bigmodel.cn/api/coding/paas/v4";
10pub const LLM_APPLICATION_BASE: &str = "https://open.bigmodel.cn/api/llm-application/open";
12pub const REALTIME_BASE: &str = "wss://open.bigmodel.cn/api/paas/v4/realtime";
19
20pub const MONITOR_BASE: &str = "https://open.bigmodel.cn/api/monitor";
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum ApiBase {
33 PaasV4,
34 CodingPaasV4,
35 LlmApplication,
36 Realtime,
37 Monitor,
39 Custom(String),
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct EndpointConfig {
45 pub paas_v4_base: String,
46 pub coding_paas_v4_base: String,
47 pub llm_application_base: String,
48 pub realtime_base: String,
49 pub monitor_base: String,
51}
52
53impl Default for EndpointConfig {
54 fn default() -> Self {
55 Self {
56 paas_v4_base: PAAS_V4_BASE.to_string(),
57 coding_paas_v4_base: CODING_PAAS_V4_BASE.to_string(),
58 llm_application_base: LLM_APPLICATION_BASE.to_string(),
59 realtime_base: REALTIME_BASE.to_string(),
60 monitor_base: MONITOR_BASE.to_string(),
61 }
62 }
63}
64
65impl EndpointConfig {
66 pub fn with_paas_v4_base(mut self, base: impl Into<String>) -> Self {
67 self.paas_v4_base = base.into();
68 self
69 }
70
71 pub fn with_coding_paas_v4_base(mut self, base: impl Into<String>) -> Self {
72 self.coding_paas_v4_base = base.into();
73 self
74 }
75
76 pub fn with_llm_application_base(mut self, base: impl Into<String>) -> Self {
77 self.llm_application_base = base.into();
78 self
79 }
80
81 pub fn with_realtime_base(mut self, base: impl Into<String>) -> Self {
82 self.realtime_base = base.into();
83 self
84 }
85
86 pub fn with_monitor_base(mut self, base: impl Into<String>) -> Self {
88 self.monitor_base = base.into();
89 self
90 }
91
92 pub fn base<'a>(&'a self, api_base: &'a ApiBase) -> &'a str {
93 match api_base {
94 ApiBase::PaasV4 => &self.paas_v4_base,
95 ApiBase::CodingPaasV4 => &self.coding_paas_v4_base,
96 ApiBase::LlmApplication => &self.llm_application_base,
97 ApiBase::Realtime => &self.realtime_base,
98 ApiBase::Monitor => &self.monitor_base,
99 ApiBase::Custom(base) => base,
100 }
101 }
102
103 pub fn url(&self, api_base: &ApiBase, path: &str) -> String {
104 join_url(self.base(api_base), path)
105 }
106}
107
108pub fn join_url(base: &str, path: &str) -> String {
110 let base = base.trim_end_matches('/');
111 let path = path.trim_start_matches('/');
112 if path.is_empty() {
113 base.to_string()
114 } else {
115 format!("{}/{}", base, path)
116 }
117}
118
119pub fn build_query<K, V, I>(base_url: &str, params: I) -> String
130where
131 K: AsRef<str>,
132 V: AsRef<str>,
133 I: IntoIterator<Item = (K, V)>,
134{
135 let collected: Vec<(K, V)> = params.into_iter().collect();
136 if let Ok(mut url) = url::Url::parse(base_url) {
137 if !collected.is_empty() {
138 url.query_pairs_mut()
139 .extend_pairs(collected.iter().map(|(k, v)| (k.as_ref(), v.as_ref())));
140 }
141 return url.to_string();
142 }
143
144 if collected.is_empty() {
146 return base_url.to_string();
147 }
148 let query = collected
149 .iter()
150 .map(|(k, v)| format!("{}={}", k.as_ref(), v.as_ref()))
151 .collect::<Vec<_>>()
152 .join("&");
153 format!("{base_url}?{query}")
154}
155
156pub mod paths {
157 pub const CHAT_COMPLETIONS: &str = "chat/completions";
158 pub const ASYNC_CHAT_COMPLETIONS: &str = "async/chat/completions";
159 pub const ASYNC_RESULT: &str = "async-result";
160 pub const EMBEDDINGS: &str = "embeddings";
161 pub const RERANK: &str = "rerank";
162 pub const TOKENIZER: &str = "tokenizer";
163 pub const MODERATIONS: &str = "moderations";
164 pub const IMAGES_GENERATIONS: &str = "images/generations";
165 pub const VIDEOS_GENERATIONS: &str = "videos/generations";
166 pub const AUDIO_TRANSCRIPTIONS: &str = "audio/transcriptions";
167 pub const AUDIO_SPEECH: &str = "audio/speech";
168 pub const VOICE_CLONE: &str = "voice/clone";
169 pub const VOICE_LIST: &str = "voice/list";
170 pub const VOICE_DELETE: &str = "voice/delete";
171 pub const FILES: &str = "files";
172 pub const FILES_OCR: &str = "files/ocr";
173 pub const FILE_PARSER_CREATE: &str = "files/parser/create";
174 pub const FILE_PARSER_RESULT: &str = "files/parser/result";
175 pub const WEB_SEARCH: &str = "web_search";
176 pub const BATCHES: &str = "batches";
177 pub const AGENTS: &str = "agents";
178
179 pub const KNOWLEDGE: &str = "knowledge";
180 pub const KNOWLEDGE_CAPACITY: &str = "knowledge/capacity";
181 pub const DOCUMENT: &str = "document";
182 pub const DOCUMENT_UPLOAD_URL: &str = "document/upload_url";
183 pub const DOCUMENT_UPLOAD_DOCUMENT: &str = "document/upload_document";
184 pub const DOCUMENT_EMBEDDING: &str = "document/embedding";
185 pub const DOCUMENT_SLICE_IMAGE_LIST: &str = "document/slice/image_list";
186
187 pub const MONITOR_USAGE_QUOTA_LIMIT: &str = "usage/quota/limit";
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn joins_base_and_path_without_double_slashes() {
198 assert_eq!(
199 join_url("https://open.bigmodel.cn/api/paas/v4/", "/chat/completions"),
200 "https://open.bigmodel.cn/api/paas/v4/chat/completions"
201 );
202 }
203
204 #[test]
205 fn default_config_uses_official_bases() {
206 let config = EndpointConfig::default();
207 assert_eq!(config.paas_v4_base, PAAS_V4_BASE);
208 assert_eq!(config.coding_paas_v4_base, CODING_PAAS_V4_BASE);
209 assert_eq!(config.llm_application_base, LLM_APPLICATION_BASE);
210 }
211
212 #[test]
213 fn realtime_base_matches_official_endpoint() {
214 assert_eq!(REALTIME_BASE, "wss://open.bigmodel.cn/api/paas/v4/realtime");
217 }
218
219 #[test]
220 fn realtime_url_is_buildable_from_endpoint_config() {
221 let url = EndpointConfig::default().url(&ApiBase::Realtime, "");
222 assert_eq!(url, REALTIME_BASE);
223 assert!(url.starts_with("wss://"));
225 }
226
227 #[test]
228 fn build_query_appends_percent_encoded_pairs() {
229 let url = build_query(
230 "https://open.bigmodel.cn/api/paas/v4/files",
231 [("purpose", "batch".to_string()), ("limit", "2".to_string())],
232 );
233 assert_eq!(
234 url,
235 "https://open.bigmodel.cn/api/paas/v4/files?purpose=batch&limit=2"
236 );
237 }
238
239 #[test]
240 fn build_query_without_params_returns_base() {
241 let url = build_query(
242 "https://open.bigmodel.cn/api/paas/v4/files",
243 Vec::<(String, String)>::new(),
244 );
245 assert_eq!(url, "https://open.bigmodel.cn/api/paas/v4/files");
246 }
247
248 #[test]
249 fn build_query_falls_back_for_malformed_base() {
250 let url = build_query("not a url", [("limit", "5".to_string())]);
252 assert_eq!(url, "not a url?limit=5");
253 }
254}