Skip to main content

deepseek/agent/
options.rs

1//! Options passed into [`crate::agent::run`].
2
3use std::sync::Arc;
4
5use crate::types::EffortLevel;
6
7use super::permissions::{PermissionMode, PreToolHook};
8
9/// Configuration for natural-language history compaction.
10///
11/// When set on [`RunOptions::compaction`], the agent loop monitors the
12/// previous turn's `prompt_tokens` and — once it crosses
13/// `threshold_prompt_tokens` — replaces the middle of the conversation with
14/// a single synthetic system message containing a compacted summary
15/// produced by a separate (typically cheaper) DeepSeek call.
16///
17/// # Cost trade-off
18///
19/// Compaction costs one extra API call (input = serialized middle slice,
20/// output ≤ `max_summary_tokens`) and **invalidates the prompt cache prefix
21/// for subsequent turns** because the front of the message vector changes.
22/// DeepSeek's cache-hit input price is ~25% of the cache-miss price, so:
23///
24/// - For short loops (a few turns): leave `RunOptions::compaction = None` —
25///   cache hits already make resending cheap.
26/// - For long loops where `prompt_tokens` crosses ~30K: one compaction call
27///   on `deepseek-chat` costs ~$0.008, but saves resending ~30K tokens ×
28///   each remaining turn at the cache-hit rate. Break-even is roughly four
29///   future turns.
30///
31/// Defaults are tuned for the long-loop case; tune
32/// `threshold_prompt_tokens` deliberately if your workload is short.
33#[derive(Clone, Debug)]
34pub struct CompactionConfig {
35    /// Trigger compaction when the previous turn's `prompt_tokens >= this`.
36    pub threshold_prompt_tokens: u32,
37    /// Number of complete recent turns to keep verbatim after the summary.
38    /// A "turn" is one assistant message plus its associated tool-result
39    /// group (if the assistant requested tool calls). Tool-call/result
40    /// pairs are never split — compaction may keep one extra turn to
41    /// preserve atomicity.
42    pub keep_recent_turns: u32,
43    /// Model used for the compaction call. Default: `deepseek-chat`
44    /// (cheapest input rate).
45    pub compactor_model: String,
46    /// Soft cap on summary completion tokens.
47    pub max_summary_tokens: u32,
48}
49
50impl Default for CompactionConfig {
51    fn default() -> Self {
52        Self {
53            threshold_prompt_tokens: 32_000,
54            keep_recent_turns: 3,
55            compactor_model: "deepseek-chat".into(),
56            max_summary_tokens: 512,
57        }
58    }
59}
60
61/// Configuration for an agent run.
62///
63/// Mirrors `ClaudeAgentOptions` / `Options` from the Claude Agent SDK, scoped
64/// to the fields meaningful for a non-streaming OpenAI-compatible loop.
65#[derive(Clone)]
66pub struct RunOptions {
67    pub model: String,
68    pub system_prompt: String,
69    /// If `Some`, only listed tools may be invoked. Tools not on the list are
70    /// hidden from the model entirely.
71    pub allowed_tools: Option<Vec<String>>,
72    /// Tools listed here are hidden from the model and any call is denied.
73    pub disallowed_tools: Vec<String>,
74    pub max_turns: Option<u32>,
75    pub max_budget_usd: Option<f64>,
76    pub effort: EffortLevel,
77    pub permission_mode: PermissionMode,
78    pub pre_tool_hook: Option<Arc<dyn PreToolHook>>,
79    pub session_id: Option<String>,
80    pub base_url: String,
81    /// Opt-in history compaction. `None` (default) disables compaction —
82    /// the full conversation is resent every turn. See [`CompactionConfig`].
83    pub compaction: Option<CompactionConfig>,
84}
85
86impl Default for RunOptions {
87    fn default() -> Self {
88        Self {
89            model: "deepseek-v4-pro".into(),
90            system_prompt: String::new(),
91            allowed_tools: None,
92            disallowed_tools: Vec::new(),
93            max_turns: None,
94            max_budget_usd: None,
95            effort: EffortLevel::default(),
96            permission_mode: PermissionMode::default(),
97            pre_tool_hook: None,
98            session_id: None,
99            base_url: "https://api.deepseek.com/v1".into(),
100            compaction: None,
101        }
102    }
103}
104
105impl RunOptions {
106    pub fn new(model: impl Into<String>) -> Self {
107        Self {
108            model: model.into(),
109            ..Self::default()
110        }
111    }
112
113    pub fn system_prompt(mut self, p: impl Into<String>) -> Self {
114        self.system_prompt = p.into();
115        self
116    }
117
118    pub fn max_turns(mut self, n: u32) -> Self {
119        self.max_turns = Some(n);
120        self
121    }
122
123    pub fn max_budget_usd(mut self, b: f64) -> Self {
124        self.max_budget_usd = Some(b);
125        self
126    }
127
128    pub fn effort(mut self, e: EffortLevel) -> Self {
129        self.effort = e;
130        self
131    }
132
133    pub fn permission_mode(mut self, m: PermissionMode) -> Self {
134        self.permission_mode = m;
135        self
136    }
137
138    pub fn allowed_tools<I, S>(mut self, tools: I) -> Self
139    where
140        I: IntoIterator<Item = S>,
141        S: Into<String>,
142    {
143        self.allowed_tools = Some(tools.into_iter().map(Into::into).collect());
144        self
145    }
146
147    pub fn disallowed_tools<I, S>(mut self, tools: I) -> Self
148    where
149        I: IntoIterator<Item = S>,
150        S: Into<String>,
151    {
152        self.disallowed_tools = tools.into_iter().map(Into::into).collect();
153        self
154    }
155
156    pub fn pre_tool_hook(mut self, hook: Arc<dyn PreToolHook>) -> Self {
157        self.pre_tool_hook = Some(hook);
158        self
159    }
160
161    pub fn session_id(mut self, id: impl Into<String>) -> Self {
162        self.session_id = Some(id.into());
163        self
164    }
165
166    pub fn base_url(mut self, url: impl Into<String>) -> Self {
167        self.base_url = url.into().trim_end_matches('/').to_string();
168        self
169    }
170
171    /// Enable natural-language history compaction with the given config.
172    pub fn compaction(mut self, cfg: CompactionConfig) -> Self {
173        self.compaction = Some(cfg);
174        self
175    }
176}