Skip to main content

deepseek/
types.rs

1use serde::{Deserialize, Serialize};
2
3// ── DeepSeek Models ───────────────────────────────────────────────────────
4
5/// DeepSeek model identifiers — parity with Anthropic's multi-model routing
6#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
7pub enum DeepSeekModel {
8    /// DeepSeek V4-Pro — flagship with thinking mode + reasoning_effort.
9    #[serde(rename = "deepseek-v4-pro")]
10    #[default]
11    V4Pro,
12    /// DeepSeek-R1 — kept for back-compat. New code should prefer V4Pro.
13    #[serde(rename = "deepseek-reasoner")]
14    Reasoner,
15    /// DeepSeek-V3 — kept for back-compat.
16    #[serde(rename = "deepseek-chat")]
17    Chat,
18}
19
20impl DeepSeekModel {
21    pub fn as_str(&self) -> &'static str {
22        match self {
23            Self::V4Pro => "deepseek-v4-pro",
24            Self::Reasoner => "deepseek-reasoner",
25            Self::Chat => "deepseek-chat",
26        }
27    }
28
29    pub fn from_alias(alias: &str) -> Self {
30        match alias {
31            "v4" | "v4-pro" | "pro" | "latest" => Self::V4Pro,
32            "reasoner" | "r1" | "deep" | "opus" => Self::Reasoner,
33            "chat" | "v3" | "fast" | "sonnet" | "haiku" => Self::Chat,
34            _ => Self::V4Pro,
35        }
36    }
37}
38
39// ── DeepSeek API Types (OpenAI-compatible) ────────────────────────────────
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ChatMessage {
43    pub role: String,
44    pub content: ChatContent,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub reasoning_content: Option<String>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub tool_calls: Option<Vec<ToolCall>>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub tool_call_id: Option<String>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub name: Option<String>,
53}
54
55#[derive(Debug, Clone)]
56pub enum ChatContent {
57    Text(String),
58    Null,
59}
60
61impl ChatContent {
62    pub fn as_str(&self) -> &str {
63        match self {
64            Self::Text(s) => s,
65            Self::Null => "",
66        }
67    }
68}
69
70impl Serialize for ChatContent {
71    fn serialize<S: serde::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
72        match self {
73            Self::Text(s) => serializer.serialize_str(s),
74            Self::Null => serializer.serialize_none(),
75        }
76    }
77}
78
79impl<'de> Deserialize<'de> for ChatContent {
80    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
81        let value = serde_json::Value::deserialize(deserializer)?;
82        match value {
83            serde_json::Value::String(s) => Ok(Self::Text(s)),
84            serde_json::Value::Null => Ok(Self::Null),
85            other => Ok(Self::Text(other.to_string())),
86        }
87    }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ToolCall {
92    pub id: String,
93    pub r#type: String,
94    pub function: FunctionCall,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct FunctionCall {
99    pub name: String,
100    pub arguments: String,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct ChatRequest {
105    pub model: String,
106    pub messages: Vec<ChatMessage>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub tools: Option<Vec<ToolSchema>>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub tool_choice: Option<serde_json::Value>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub temperature: Option<f64>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub max_tokens: Option<u32>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub stream: Option<bool>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub reasoning_effort: Option<String>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub thinking: Option<serde_json::Value>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct ToolSchema {
125    pub r#type: String,
126    pub function: FunctionSchema,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct FunctionSchema {
131    pub name: String,
132    pub description: String,
133    pub parameters: serde_json::Value,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct ChatResponse {
138    pub id: String,
139    pub choices: Vec<Choice>,
140    pub usage: Option<UsageInfo>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct Choice {
145    pub index: u32,
146    pub message: ChatMessage,
147    pub finish_reason: Option<String>,
148}
149
150#[derive(Debug, Clone, Deserialize, Serialize)]
151pub struct UsageInfo {
152    pub prompt_tokens: u32,
153    pub completion_tokens: u32,
154    pub total_tokens: u32,
155}
156
157// ── Agent Result (parity with Anthropic AgentResult) ──────────────────────
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AgentResult {
161    pub success: bool,
162    pub result: Option<String>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub error: Option<String>,
165    pub turns: u32,
166    pub usage: UsageInfo,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub tool_calls_made: Option<Vec<String>>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub session_id: Option<String>,
171}
172
173// ── Agent Definition (parity with Anthropic AgentDefinition) ──────────────
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct AgentDefinition {
177    pub name: String,
178    pub description: String,
179    pub prompt: String,
180    #[serde(default)]
181    pub model: DeepSeekModel,
182    #[serde(default)]
183    pub tools: Vec<String>,
184    #[serde(default)]
185    pub disallowed_tools: Vec<String>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub max_turns: Option<u32>,
188}
189
190// ── Effort Level (parity with Anthropic adaptive thinking) ────────────────
191
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub enum EffortLevel {
194    Low,
195    Medium,
196    #[default]
197    High,
198    Max,
199}
200
201impl EffortLevel {
202    /// Map effort to DeepSeek temperature (lower = more focused)
203    pub fn temperature(&self) -> f64 {
204        match self {
205            Self::Low => 0.1,
206            Self::Medium => 0.5,
207            Self::High => 0.7,
208            Self::Max => 1.0,
209        }
210    }
211
212    /// Map effort to max tokens
213    pub fn max_tokens(&self) -> u32 {
214        match self {
215            Self::Low => 2048,
216            Self::Medium => 4096,
217            Self::High => 8192,
218            Self::Max => 16384,
219        }
220    }
221}
222
223// ── Message constructors ──────────────────────────────────────────────────
224
225pub fn system_msg(content: &str) -> ChatMessage {
226    ChatMessage {
227        role: "system".into(),
228        content: ChatContent::Text(content.into()),
229        reasoning_content: None,
230        tool_calls: None,
231        tool_call_id: None,
232        name: None,
233    }
234}
235
236pub fn user_msg(content: &str) -> ChatMessage {
237    ChatMessage {
238        role: "user".into(),
239        content: ChatContent::Text(content.into()),
240        reasoning_content: None,
241        tool_calls: None,
242        tool_call_id: None,
243        name: None,
244    }
245}
246
247pub fn assistant_msg(content: &str) -> ChatMessage {
248    ChatMessage {
249        role: "assistant".into(),
250        content: ChatContent::Text(content.into()),
251        reasoning_content: None,
252        tool_calls: None,
253        tool_call_id: None,
254        name: None,
255    }
256}
257
258// ── Reasoner output ───────────────────────────────────────────────────────
259
260pub struct ReasonerOutput {
261    /// R1 chain-of-thought — logged at DEBUG, not passed to next agent.
262    pub reasoning: String,
263    /// Final answer — passed forward in the pipeline.
264    pub content: String,
265}
266
267pub fn tool_result_msg(tool_call_id: &str, content: &str) -> ChatMessage {
268    ChatMessage {
269        role: "tool".into(),
270        content: ChatContent::Text(content.into()),
271        reasoning_content: None,
272        tool_calls: None,
273        tool_call_id: Some(tool_call_id.into()),
274        name: None,
275    }
276}