1use crate::error::Result;
2use crate::types::*;
3use async_trait::async_trait;
4
5pub const DEFAULT_BASE_URL: &str = "https://api.deepseek.com/v1";
6
7#[cfg_attr(not(feature = "wasm"), async_trait)]
11#[cfg_attr(feature = "wasm", async_trait(?Send))]
12pub trait HttpClient {
13 async fn post_json(
14 &self,
15 url: &str,
16 bearer_token: &str,
17 body: &ChatRequest,
18 ) -> Result<ChatResponse>;
19}
20
21pub struct DeepSeekClient<H: HttpClient> {
23 pub http: H,
24 pub api_key: String,
25 pub base_url: String,
26}
27
28impl<H: HttpClient + Clone> Clone for DeepSeekClient<H> {
29 fn clone(&self) -> Self {
30 Self {
31 http: self.http.clone(),
32 api_key: self.api_key.clone(),
33 base_url: self.base_url.clone(),
34 }
35 }
36}
37
38impl<H: HttpClient> DeepSeekClient<H> {
39 pub fn new(http: H, api_key: impl Into<String>) -> Self {
40 Self {
41 http,
42 api_key: api_key.into(),
43 base_url: DEFAULT_BASE_URL.to_string(),
44 }
45 }
46
47 pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
48 self.base_url = url.into();
49 self
50 }
51
52 pub async fn chat(&self, request: &ChatRequest) -> Result<ChatResponse> {
54 let url = format!("{}/chat/completions", self.base_url);
55 self.http.post_json(&url, &self.api_key, request).await
56 }
57}
58
59pub fn build_request(
62 model: &DeepSeekModel,
63 messages: Vec<ChatMessage>,
64 tools: Option<Vec<ToolSchema>>,
65 effort: &EffortLevel,
66) -> ChatRequest {
67 let has_tools = tools.is_some();
68 let reasoning_effort = match effort {
69 EffortLevel::Max => Some("max".to_string()),
70 _ => Some("high".to_string()),
71 };
72 ChatRequest {
73 model: model.as_str().to_string(),
74 messages,
75 tools,
76 tool_choice: if has_tools {
77 Some(serde_json::json!("auto"))
78 } else {
79 None
80 },
81 temperature: Some(effort.temperature()),
82 max_tokens: Some(effort.max_tokens()),
83 stream: Some(false),
84 reasoning_effort,
85 thinking: Some(serde_json::json!({"type": "enabled"})),
86 }
87}