use serde::{Deserialize, Serialize};
pub const DEEPSEEK_API_BASE: &str = "https://api.deepseek.com";
pub const DEEPSEEK_BETA_API_BASE: &str = "https://api.deepseek.com/beta";
pub const DEEPSEEK_ANTHROPIC_API_BASE: &str = "https://api.deepseek.com/anthropic";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ThinkingMode {
Enabled,
Disabled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ReasoningEffort {
High,
Max,
}
impl std::fmt::Display for ReasoningEffort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::High => write!(f, "high"),
Self::Max => write!(f, "max"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeepSeekConfig {
pub api_key: String,
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking: Option<ThinkingMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(default)]
pub beta: bool,
#[serde(default)]
pub strict_tools: bool,
#[serde(default)]
pub thinking_enabled: bool,
}
impl Default for DeepSeekConfig {
fn default() -> Self {
Self {
api_key: String::new(),
model: "deepseek-v4-flash".to_string(),
base_url: None,
thinking: None,
reasoning_effort: None,
max_tokens: None,
beta: false,
strict_tools: false,
thinking_enabled: false,
}
}
}
impl DeepSeekConfig {
pub fn new(api_key: impl Into<String>, model: impl Into<String>) -> Self {
Self { api_key: api_key.into(), model: model.into(), ..Default::default() }
}
pub fn v4_pro(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
model: "deepseek-v4-pro".to_string(),
thinking: Some(ThinkingMode::Enabled),
reasoning_effort: Some(ReasoningEffort::High),
max_tokens: Some(8192),
..Default::default()
}
}
pub fn v4_flash(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
model: "deepseek-v4-flash".to_string(),
..Default::default()
}
}
pub fn chat(api_key: impl Into<String>) -> Self {
Self::new(api_key, "deepseek-chat")
}
pub fn reasoner(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
model: "deepseek-reasoner".to_string(),
thinking: Some(ThinkingMode::Enabled),
thinking_enabled: true,
max_tokens: Some(8192),
..Default::default()
}
}
pub fn with_thinking_mode(mut self, mode: ThinkingMode) -> Self {
self.thinking = Some(mode);
if mode == ThinkingMode::Enabled {
self.thinking_enabled = true;
} else {
self.thinking_enabled = false;
self.reasoning_effort = None; }
self
}
pub fn with_thinking(mut self, enabled: bool) -> Self {
self.thinking_enabled = enabled;
self.thinking = Some(if enabled { ThinkingMode::Enabled } else { ThinkingMode::Disabled });
if !enabled {
self.reasoning_effort = None; }
self
}
pub fn with_reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
self.reasoning_effort = Some(effort);
self
}
pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
self.max_tokens = Some(max_tokens);
self
}
pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
self.base_url = Some(base_url.into());
self
}
pub fn with_beta(mut self) -> Self {
self.beta = true;
self
}
pub fn with_strict_tools(mut self) -> Self {
self.strict_tools = true;
self.beta = true; self
}
pub fn effective_base_url(&self) -> &str {
if let Some(ref url) = self.base_url {
return url;
}
if self.beta {
return DEEPSEEK_BETA_API_BASE;
}
DEEPSEEK_API_BASE
}
pub fn is_thinking_enabled(&self) -> bool {
match self.thinking {
Some(ThinkingMode::Enabled) => true,
Some(ThinkingMode::Disabled) => false,
None => self.thinking_enabled, }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_model_is_v4_flash() {
let config = DeepSeekConfig::default();
assert_eq!(config.model, "deepseek-v4-flash");
}
#[test]
fn test_v4_pro_constructor() {
let config = DeepSeekConfig::v4_pro("key");
assert_eq!(config.model, "deepseek-v4-pro");
assert_eq!(config.thinking, Some(ThinkingMode::Enabled));
assert_eq!(config.reasoning_effort, Some(ReasoningEffort::High));
assert!(config.is_thinking_enabled());
}
#[test]
fn test_v4_flash_constructor() {
let config = DeepSeekConfig::v4_flash("key");
assert_eq!(config.model, "deepseek-v4-flash");
assert!(config.thinking.is_none());
}
#[test]
fn test_legacy_chat_constructor() {
let config = DeepSeekConfig::chat("key");
assert_eq!(config.model, "deepseek-chat");
assert!(!config.is_thinking_enabled());
}
#[test]
fn test_legacy_reasoner_constructor() {
let config = DeepSeekConfig::reasoner("key");
assert_eq!(config.model, "deepseek-reasoner");
assert!(config.is_thinking_enabled());
}
#[test]
fn test_with_reasoning_effort() {
let config = DeepSeekConfig::v4_pro("key").with_reasoning_effort(ReasoningEffort::Max);
assert_eq!(config.reasoning_effort, Some(ReasoningEffort::Max));
}
#[test]
fn test_with_thinking_mode_disabled() {
let config = DeepSeekConfig::v4_pro("key").with_thinking_mode(ThinkingMode::Disabled);
assert!(!config.is_thinking_enabled());
}
#[test]
fn test_beta_base_url() {
let config = DeepSeekConfig::v4_pro("key").with_beta();
assert_eq!(config.effective_base_url(), DEEPSEEK_BETA_API_BASE);
}
#[test]
fn test_strict_tools_enables_beta() {
let config = DeepSeekConfig::v4_pro("key").with_strict_tools();
assert!(config.beta);
assert!(config.strict_tools);
assert_eq!(config.effective_base_url(), DEEPSEEK_BETA_API_BASE);
}
#[test]
fn test_custom_base_url_overrides_beta() {
let config =
DeepSeekConfig::v4_pro("key").with_beta().with_base_url("https://custom.api.com");
assert_eq!(config.effective_base_url(), "https://custom.api.com");
}
#[test]
fn test_legacy_with_thinking_true() {
let config = DeepSeekConfig::chat("key").with_thinking(true);
assert!(config.is_thinking_enabled());
assert_eq!(config.thinking, Some(ThinkingMode::Enabled));
}
#[test]
fn test_legacy_with_thinking_false() {
let config = DeepSeekConfig::reasoner("key").with_thinking(false);
assert!(!config.is_thinking_enabled());
assert_eq!(config.thinking, Some(ThinkingMode::Disabled));
}
#[test]
fn test_reasoning_effort_display() {
assert_eq!(ReasoningEffort::High.to_string(), "high");
assert_eq!(ReasoningEffort::Max.to_string(), "max");
}
}