#![deny(unsafe_code)]
#[cfg(feature = "balance")]
pub mod balance;
#[cfg(feature = "chat")]
pub mod chat;
#[cfg(feature = "completion")]
pub mod completion;
pub mod error;
#[cfg(feature = "models")]
pub mod models;
#[cfg(feature = "responses")]
pub mod responses;
use crate::error::{ApiErrorEnvelope, DeepSeekError};
use futures_util::StreamExt;
use reqwest::{Client, Method, RequestBuilder, Response, header::AUTHORIZATION};
use reqwest_eventsource::{Event, EventSource, RequestBuilderExt};
use serde::{Serialize, de::DeserializeOwned};
use std::future::Future;
use std::sync::LazyLock;
use tokio::sync::mpsc;
pub static DEFAULT_BASE_URL: LazyLock<String> =
LazyLock::new(|| String::from("https://api.deepseek.com"));
pub static DEFAULT_BETA_BASE_URL: LazyLock<String> =
LazyLock::new(|| String::from("https://api.deepseek.com/beta"));
#[derive(Clone, Debug, Eq, PartialEq)]
struct Credentials {
pub(crate) api_key: String,
pub(crate) base_url: String,
}
#[derive(Clone, Debug)]
pub struct DeepSeekClient {
pub(crate) credentials: Credentials,
pub client: Client,
}
impl PartialEq for DeepSeekClient {
fn eq(&self, other: &Self) -> bool {
self.credentials == other.credentials
}
}
impl Eq for DeepSeekClient {}
impl DeepSeekClient {
pub fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
DeepSeekClient {
credentials: Credentials::new(api_key, base_url),
client: Client::new(),
}
}
pub fn with_client(mut self, client: Client) -> Self {
self.client = client;
self
}
pub fn with_credentials(
mut self,
api_key: impl Into<String>,
base_url: impl Into<String>,
) -> Self {
self.credentials = Credentials::new(api_key, base_url);
self
}
}
impl Credentials {
pub(crate) fn new(api_key: impl Into<String>, base_url: impl Into<String>) -> Self {
Credentials {
api_key: api_key.into(),
base_url: base_url.into(),
}
}
}
pub trait DeepSeekRequest: Sized {
type Response;
type StreamItem;
type BlockingStream: Iterator<Item = Self::StreamItem>;
fn send(self) -> impl Future<Output = Result<Self::Response, DeepSeekError>> + Send;
fn stream(
self,
) -> impl Future<Output = Result<mpsc::Receiver<Self::StreamItem>, DeepSeekError>> + Send;
fn stream_blocking(self) -> Result<Self::BlockingStream, DeepSeekError>;
}
#[allow(dead_code)]
async fn api_request_json<F, T>(
method: Method,
route: &str,
builder: F,
deepseek_client: DeepSeekClient,
) -> Result<T, DeepSeekError>
where
F: FnOnce(RequestBuilder) -> RequestBuilder,
T: DeserializeOwned,
{
let response = api_request(method, route, builder, deepseek_client).await?;
let status = response.status();
let text = response.text().await?;
if !status.is_success() {
if let Ok(envelope) = serde_json::from_str::<ApiErrorEnvelope>(&text) {
return Err(DeepSeekError::api(
envelope.error,
Some(status.as_u16()),
Some(text),
));
}
return Err(DeepSeekError::http(status.as_u16(), text));
}
serde_json::from_str::<T>(&text).map_err(|err| DeepSeekError::decode(err.to_string(), text))
}
#[allow(dead_code)]
async fn api_request<F>(
method: Method,
route: &str,
builder: F,
deepseek_client: DeepSeekClient,
) -> Result<Response, DeepSeekError>
where
F: FnOnce(RequestBuilder) -> RequestBuilder,
{
let client = deepseek_client.client;
let mut request = client.request(
method,
format!("{}{route}", deepseek_client.credentials.base_url),
);
request = builder(request);
let response = request
.header(
AUTHORIZATION,
format!("Bearer {}", deepseek_client.credentials.api_key),
)
.send()
.await?;
Ok(response)
}
#[allow(dead_code)]
async fn api_request_stream<F>(
method: Method,
route: &str,
builder: F,
deepseek_client: DeepSeekClient,
) -> Result<EventSource, DeepSeekError>
where
F: FnOnce(RequestBuilder) -> RequestBuilder,
{
let mut request = deepseek_client.client.request(
method,
format!("{}{route}", deepseek_client.credentials.base_url),
);
request = builder(request);
let stream = request
.header(
AUTHORIZATION,
format!("Bearer {}", deepseek_client.credentials.api_key),
)
.eventsource()
.map_err(|err| DeepSeekError::decode(err.to_string(), String::new()))?;
Ok(stream)
}
#[allow(dead_code)]
pub(crate) fn consume_sse<T, F>(
mut event_source: EventSource,
mut parse: F,
) -> mpsc::Receiver<Result<T, DeepSeekError>>
where
T: Send + 'static,
F: FnMut(String) -> Result<Option<T>, DeepSeekError> + Send + 'static,
{
let (tx, rx) = mpsc::channel(32);
tokio::spawn(async move {
while let Some(event) = event_source.next().await {
match event {
Ok(Event::Open) => {}
Ok(Event::Message(message)) => {
if message.data == "[DONE]" {
break;
}
match parse(message.data) {
Ok(Some(item)) => {
if tx.send(Ok(item)).await.is_err() {
break;
}
}
Ok(None) => break,
Err(err) => {
let _ = tx.send(Err(err)).await;
break;
}
}
}
Err(err) => {
if matches!(err, reqwest_eventsource::Error::StreamEnded) {
break;
}
let _ = tx
.send(Err(DeepSeekError::decode(err.to_string(), String::new())))
.await;
break;
}
}
}
});
rx
}
#[allow(dead_code)]
pub(crate) fn spawn_blocking_stream<T>(
fut: impl Future<Output = Result<mpsc::Receiver<Result<T, DeepSeekError>>, DeepSeekError>>
+ Send
+ 'static,
) -> Result<std::sync::mpsc::Receiver<Result<T, DeepSeekError>>, DeepSeekError>
where
T: Send + 'static,
{
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let runtime = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(runtime) => runtime,
Err(err) => {
let _ = tx.send(Err(DeepSeekError::decode(err.to_string(), String::new())));
return;
}
};
runtime.block_on(async move {
match fut.await {
Ok(mut stream_rx) => {
while let Some(item) = stream_rx.recv().await {
if tx.send(item).is_err() {
break;
}
}
}
Err(err) => {
let _ = tx.send(Err(err));
}
}
});
});
Ok(rx)
}
#[allow(dead_code)]
async fn api_get<T>(route: &str, client: DeepSeekClient) -> Result<T, DeepSeekError>
where
T: DeserializeOwned,
{
api_request_json(Method::GET, route, |request| request, client).await
}
#[allow(dead_code)]
async fn api_post<J, T>(route: &str, json: &J, client: DeepSeekClient) -> Result<T, DeepSeekError>
where
J: Serialize + ?Sized,
T: DeserializeOwned,
{
api_request_json(Method::POST, route, |request| request.json(json), client).await
}