1#![deny(unsafe_code)]
2
3#[cfg(feature = "balance")]
33pub mod balance;
34#[cfg(feature = "chat")]
36pub mod chat;
37#[cfg(feature = "completion")]
39pub mod completion;
40pub mod error;
41#[cfg(feature = "files")]
43pub mod files;
44#[cfg(feature = "models")]
46pub mod models;
47#[cfg(feature = "responses")]
49pub mod responses;
50
51use crate::error::{ApiErrorEnvelope, DeepSeekError};
52
53use futures_util::StreamExt;
54use reqwest::{Client, Method, RequestBuilder, Response, header::AUTHORIZATION};
55use reqwest_eventsource::{Event, EventSource, RequestBuilderExt};
56use serde::{Serialize, de::DeserializeOwned};
57use std::future::Future;
58use std::sync::LazyLock;
59use tokio::sync::mpsc;
60
61pub static DEFAULT_BASE_URL: LazyLock<String> =
63 LazyLock::new(|| String::from("https://api.deepseek.com"));
64pub static DEFAULT_BETA_BASE_URL: LazyLock<String> =
66 LazyLock::new(|| String::from("https://api.deepseek.com/beta"));
67
68#[derive(Clone, Debug, Eq, PartialEq)]
70struct Credentials {
71 pub(crate) api_key: String,
72 pub(crate) base_url: String,
73}
74
75#[derive(Clone, Debug)]
76pub struct DeepSeekClient {
77 pub(crate) credentials: Credentials,
78 pub client: Client,
79}
80
81impl PartialEq for DeepSeekClient {
82 fn eq(&self, other: &Self) -> bool {
83 self.credentials == other.credentials
84 }
85}
86
87impl Eq for DeepSeekClient {}
88
89impl DeepSeekClient {
90 pub fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
91 DeepSeekClient {
92 credentials: Credentials::new(api_key, base_url),
93 client: Client::new(),
94 }
95 }
96
97 pub fn with_client(mut self, client: Client) -> Self {
98 self.client = client;
99 self
100 }
101
102 pub fn with_credentials(
103 mut self,
104 api_key: impl Into<String>,
105 base_url: impl Into<String>,
106 ) -> Self {
107 self.credentials = Credentials::new(api_key, base_url);
108 self
109 }
110}
111
112impl Credentials {
113 pub(crate) fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
115 Credentials {
116 api_key: api_key.into(),
117 base_url: base_url.into(),
118 }
119 }
120}
121
122pub trait DeepSeekRequest: Sized {
127 type Response;
129
130 type StreamItem;
132 type BlockingStream: Iterator<Item = Self::StreamItem>;
134
135 fn send(self) -> impl Future<Output = Result<Self::Response, DeepSeekError>> + Send;
137 fn stream(
139 self,
140 ) -> impl Future<Output = Result<mpsc::Receiver<Self::StreamItem>, DeepSeekError>> + Send;
141 fn stream_blocking(self) -> Result<Self::BlockingStream, DeepSeekError>;
143}
144
145#[allow(dead_code)]
146async fn api_request_json<F, T>(
147 method: Method,
148 route: &str,
149 builder: F,
150 deepseek_client: DeepSeekClient,
151) -> Result<T, DeepSeekError>
152where
153 F: FnOnce(RequestBuilder) -> RequestBuilder,
154 T: DeserializeOwned,
155{
156 let response = api_request(method, route, builder, deepseek_client).await?;
157 let status = response.status();
158
159 let text = response.text().await?;
160
161 if !status.is_success() {
162 if let Ok(envelope) = serde_json::from_str::<ApiErrorEnvelope>(&text) {
163 return Err(DeepSeekError::api(
164 envelope.error,
165 Some(status.as_u16()),
166 Some(text),
167 ));
168 }
169
170 return Err(DeepSeekError::http(status.as_u16(), text));
171 }
172
173 serde_json::from_str::<T>(&text).map_err(|err| DeepSeekError::decode(err.to_string(), text))
174}
175
176#[allow(dead_code)]
177async fn api_request<F>(
178 method: Method,
179 route: &str,
180 builder: F,
181 deepseek_client: DeepSeekClient,
182) -> Result<Response, DeepSeekError>
183where
184 F: FnOnce(RequestBuilder) -> RequestBuilder,
185{
186 let client = deepseek_client.client;
187 let mut request = client.request(
188 method,
189 format!("{}{route}", deepseek_client.credentials.base_url),
190 );
191 request = builder(request);
192 let response = request
193 .header(
194 AUTHORIZATION,
195 format!("Bearer {}", deepseek_client.credentials.api_key),
196 )
197 .send()
198 .await?;
199 Ok(response)
200}
201
202#[allow(dead_code)]
203async fn api_request_stream<F>(
204 method: Method,
205 route: &str,
206 builder: F,
207 deepseek_client: DeepSeekClient,
208) -> Result<EventSource, DeepSeekError>
209where
210 F: FnOnce(RequestBuilder) -> RequestBuilder,
211{
212 let mut request = deepseek_client.client.request(
213 method,
214 format!("{}{route}", deepseek_client.credentials.base_url),
215 );
216 request = builder(request);
217 let stream = request
218 .header(
219 AUTHORIZATION,
220 format!("Bearer {}", deepseek_client.credentials.api_key),
221 )
222 .eventsource()
223 .map_err(|err| DeepSeekError::decode(err.to_string(), String::new()))?;
224 Ok(stream)
225}
226
227#[allow(dead_code)]
234pub(crate) fn consume_sse<T, F>(
235 mut event_source: EventSource,
236 mut parse: F,
237) -> mpsc::Receiver<Result<T, DeepSeekError>>
238where
239 T: Send + 'static,
240 F: FnMut(String) -> Result<Option<T>, DeepSeekError> + Send + 'static,
241{
242 let (tx, rx) = mpsc::channel(32);
243
244 tokio::spawn(async move {
245 while let Some(event) = event_source.next().await {
246 match event {
247 Ok(Event::Open) => {}
248 Ok(Event::Message(message)) => {
249 if message.data == "[DONE]" {
250 break;
251 }
252 match parse(message.data) {
253 Ok(Some(item)) => {
254 if tx.send(Ok(item)).await.is_err() {
255 break;
256 }
257 }
258 Ok(None) => break,
259 Err(err) => {
260 let _ = tx.send(Err(err)).await;
261 break;
262 }
263 }
264 }
265 Err(err) => {
266 if matches!(err, reqwest_eventsource::Error::StreamEnded) {
267 break;
268 }
269 let _ = tx
270 .send(Err(DeepSeekError::decode(err.to_string(), String::new())))
271 .await;
272 break;
273 }
274 }
275 }
276 });
277
278 rx
279}
280
281#[allow(dead_code)]
287pub(crate) fn spawn_blocking_stream<T>(
288 fut: impl Future<Output = Result<mpsc::Receiver<Result<T, DeepSeekError>>, DeepSeekError>>
289 + Send
290 + 'static,
291) -> Result<std::sync::mpsc::Receiver<Result<T, DeepSeekError>>, DeepSeekError>
292where
293 T: Send + 'static,
294{
295 let (tx, rx) = std::sync::mpsc::channel();
296
297 std::thread::spawn(move || {
298 let runtime = match tokio::runtime::Builder::new_current_thread()
299 .enable_all()
300 .build()
301 {
302 Ok(runtime) => runtime,
303 Err(err) => {
304 let _ = tx.send(Err(DeepSeekError::decode(err.to_string(), String::new())));
305 return;
306 }
307 };
308
309 runtime.block_on(async move {
310 match fut.await {
311 Ok(mut stream_rx) => {
312 while let Some(item) = stream_rx.recv().await {
313 if tx.send(item).is_err() {
314 break;
315 }
316 }
317 }
318 Err(err) => {
319 let _ = tx.send(Err(err));
320 }
321 }
322 });
323 });
324
325 Ok(rx)
326}
327
328#[allow(dead_code)]
330async fn api_get<T>(route: &str, client: DeepSeekClient) -> Result<T, DeepSeekError>
331where
332 T: DeserializeOwned,
333{
334 api_request_json(Method::GET, route, |request| request, client).await
335}
336
337#[allow(dead_code)]
339async fn api_post<J, T>(route: &str, json: &J, client: DeepSeekClient) -> Result<T, DeepSeekError>
340where
341 J: Serialize + ?Sized,
342 T: DeserializeOwned,
343{
344 api_request_json(Method::POST, route, |request| request.json(json), client).await
345}
346
347#[allow(dead_code)]
349async fn api_delete<T>(route: &str, client: DeepSeekClient) -> Result<T, DeepSeekError>
350where
351 T: DeserializeOwned,
352{
353 api_request_json(Method::DELETE, route, |request| request, client).await
354}
355
356#[allow(dead_code)]
358async fn api_post_multipart<T>(
359 route: &str,
360 form: reqwest::multipart::Form,
361 client: DeepSeekClient,
362) -> Result<T, DeepSeekError>
363where
364 T: DeserializeOwned,
365{
366 api_request_json(
367 Method::POST,
368 route,
369 |request| request.multipart(form),
370 client,
371 )
372 .await
373}