Skip to main content

starweaver_context/config/
tool.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5/// Runtime policy for tools hidden by context-aware availability predicates.
6#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ToolAvailabilityPolicy {
9    /// Skip unavailable tools and publish diagnostics.
10    #[default]
11    SkipAndReport,
12    /// Fail the run before the model request when any configured tool is unavailable.
13    FailRun,
14}
15
16/// Tool-level configuration stored on [`crate::AgentContext`].
17#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18pub struct ToolConfig {
19    /// Runtime behavior when a registered tool is unavailable for the current context.
20    #[serde(default)]
21    pub unavailable_tool_policy: ToolAvailabilityPolicy,
22    /// Skip SSRF URL verification for URL-fetching tools.
23    #[serde(default = "default_true")]
24    pub skip_url_verification: bool,
25    /// Enable document URL parsing in load-style document tools.
26    #[serde(default)]
27    pub enable_load_document: bool,
28    /// Model used for image understanding fallback.
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub image_understanding_model: Option<String>,
31    /// Model used for video understanding fallback.
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub video_understanding_model: Option<String>,
34    /// Model used for audio understanding fallback.
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub audio_understanding_model: Option<String>,
37    /// Google Custom Search API key.
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub google_search_api_key: Option<String>,
40    /// Google Custom Search Engine id.
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub google_search_cx: Option<String>,
43    /// Tavily API key.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub tavily_api_key: Option<String>,
46    /// Brave Search API key.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub brave_search_api_key: Option<String>,
49    /// Pixabay API key.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub pixabay_api_key: Option<String>,
52    /// `RapidAPI` key.
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub rapidapi_api_key: Option<String>,
55    /// Firecrawl API key.
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub firecrawl_api_key: Option<String>,
58    /// Maximum text file size the view tool will inspect.
59    #[serde(default = "default_view_max_text_file_size")]
60    pub view_max_text_file_size: u64,
61    /// Static relaxed text view path patterns.
62    #[serde(default, skip_serializing_if = "Vec::is_empty")]
63    pub view_relaxed_text_patterns: Vec<String>,
64    /// Runtime relaxed text view patterns keyed by source id.
65    #[serde(default, skip)]
66    pub view_relaxed_text_dynamic_patterns: BTreeMap<String, Vec<String>>,
67    /// Maximum text file size for relaxed text paths.
68    #[serde(default = "default_view_relaxed_text_file_size")]
69    pub view_relaxed_text_file_size: u64,
70    /// Default line limit for relaxed text paths.
71    #[serde(default = "default_view_relaxed_line_limit")]
72    pub view_relaxed_line_limit: usize,
73    /// Default max line length for relaxed text paths.
74    #[serde(default = "default_view_relaxed_max_line_length")]
75    pub view_relaxed_max_line_length: usize,
76    /// Maximum returned text characters for normal view output.
77    #[serde(default = "default_view_max_content_chars")]
78    pub view_max_content_chars: usize,
79    /// Maximum returned text characters for relaxed view output.
80    #[serde(default = "default_view_relaxed_max_content_chars")]
81    pub view_relaxed_max_content_chars: usize,
82    /// Maximum file size `edit` and `multi_edit` will process.
83    #[serde(default = "default_edit_max_file_size")]
84    pub edit_max_file_size: u64,
85    /// Maximum file size grep will read per file.
86    #[serde(default = "default_grep_max_file_size")]
87    pub grep_max_file_size: u64,
88    /// Maximum image size view will inline.
89    #[serde(default = "default_view_max_inline_image_bytes")]
90    pub view_max_inline_image_bytes: u64,
91    /// Maximum video size view will inline.
92    #[serde(default = "default_view_max_inline_video_bytes")]
93    pub view_max_inline_video_bytes: u64,
94    /// Maximum audio size view will inline.
95    #[serde(default = "default_view_max_inline_audio_bytes")]
96    pub view_max_inline_audio_bytes: u64,
97    /// Chunk size for streamed HTTP reads.
98    #[serde(default = "default_fetch_stream_chunk_size")]
99    pub fetch_stream_chunk_size: usize,
100    /// Maximum binary response size fetch will inline.
101    #[serde(default = "default_fetch_max_inline_binary_bytes")]
102    pub fetch_max_inline_binary_bytes: u64,
103    /// Maximum concurrent downloads.
104    #[serde(default = "default_download_max_concurrency")]
105    pub download_max_concurrency: usize,
106    /// Maximum file size for document conversion tools.
107    #[serde(default = "default_document_max_file_size")]
108    pub document_max_file_size: u64,
109    /// Filesystem generic output truncation limit.
110    #[serde(default = "default_filesystem_output_truncate_limit")]
111    pub filesystem_output_truncate_limit: usize,
112    /// Grep soft truncation threshold.
113    #[serde(default = "default_grep_truncation_threshold")]
114    pub grep_truncation_threshold: usize,
115    /// Grep matching line max characters after truncation.
116    #[serde(default = "default_grep_truncated_line_max")]
117    pub grep_truncated_line_max: usize,
118    /// Shell output truncation limit.
119    #[serde(default = "default_shell_output_truncate_limit")]
120    pub shell_output_truncate_limit: usize,
121    /// Cold-start history tool return character limit.
122    #[serde(default = "default_cold_start_tool_return_limit")]
123    pub cold_start_tool_return_limit: usize,
124}
125
126impl Default for ToolConfig {
127    fn default() -> Self {
128        Self {
129            unavailable_tool_policy: ToolAvailabilityPolicy::SkipAndReport,
130            skip_url_verification: true,
131            enable_load_document: false,
132            image_understanding_model: None,
133            video_understanding_model: None,
134            audio_understanding_model: None,
135            google_search_api_key: None,
136            google_search_cx: None,
137            tavily_api_key: None,
138            brave_search_api_key: None,
139            pixabay_api_key: None,
140            rapidapi_api_key: None,
141            firecrawl_api_key: None,
142            view_max_text_file_size: default_view_max_text_file_size(),
143            view_relaxed_text_patterns: Vec::new(),
144            view_relaxed_text_dynamic_patterns: BTreeMap::new(),
145            view_relaxed_text_file_size: default_view_relaxed_text_file_size(),
146            view_relaxed_line_limit: default_view_relaxed_line_limit(),
147            view_relaxed_max_line_length: default_view_relaxed_max_line_length(),
148            view_max_content_chars: default_view_max_content_chars(),
149            view_relaxed_max_content_chars: default_view_relaxed_max_content_chars(),
150            edit_max_file_size: default_edit_max_file_size(),
151            grep_max_file_size: default_grep_max_file_size(),
152            view_max_inline_image_bytes: default_view_max_inline_image_bytes(),
153            view_max_inline_video_bytes: default_view_max_inline_video_bytes(),
154            view_max_inline_audio_bytes: default_view_max_inline_audio_bytes(),
155            fetch_stream_chunk_size: default_fetch_stream_chunk_size(),
156            fetch_max_inline_binary_bytes: default_fetch_max_inline_binary_bytes(),
157            download_max_concurrency: default_download_max_concurrency(),
158            document_max_file_size: default_document_max_file_size(),
159            filesystem_output_truncate_limit: default_filesystem_output_truncate_limit(),
160            grep_truncation_threshold: default_grep_truncation_threshold(),
161            grep_truncated_line_max: default_grep_truncated_line_max(),
162            shell_output_truncate_limit: default_shell_output_truncate_limit(),
163            cold_start_tool_return_limit: default_cold_start_tool_return_limit(),
164        }
165    }
166}
167
168impl ToolConfig {
169    /// Return whether the persisted config only contains default values.
170    ///
171    /// Runtime-only dynamic relaxed patterns do not make the config persistent.
172    #[must_use]
173    pub fn is_default(&self) -> bool {
174        let mut persisted = self.clone();
175        persisted.view_relaxed_text_dynamic_patterns.clear();
176        persisted == Self::default()
177    }
178
179    /// Return all static and runtime relaxed text patterns in deterministic order.
180    #[must_use]
181    pub fn effective_view_relaxed_text_patterns(&self) -> Vec<&str> {
182        let mut patterns = self
183            .view_relaxed_text_patterns
184            .iter()
185            .map(String::as_str)
186            .collect::<Vec<_>>();
187        for source_patterns in self.view_relaxed_text_dynamic_patterns.values() {
188            patterns.extend(source_patterns.iter().map(String::as_str));
189        }
190        patterns
191    }
192
193    /// Register runtime relaxed text patterns for a source id.
194    pub fn register_view_relaxed_text_patterns(
195        &mut self,
196        source: impl Into<String>,
197        patterns: impl IntoIterator<Item = String>,
198    ) {
199        let patterns = patterns
200            .into_iter()
201            .map(|pattern| pattern.trim().to_string())
202            .filter(|pattern| !pattern.is_empty())
203            .collect::<Vec<_>>();
204        let source = source.into();
205        if patterns.is_empty() {
206            self.view_relaxed_text_dynamic_patterns.remove(&source);
207        } else {
208            self.view_relaxed_text_dynamic_patterns
209                .insert(source, patterns);
210        }
211    }
212
213    /// Remove runtime relaxed text patterns for a source id.
214    pub fn unregister_view_relaxed_text_patterns(&mut self, source: &str) {
215        self.view_relaxed_text_dynamic_patterns.remove(source);
216    }
217
218    /// Normalize concurrency fields after deserialization or direct mutation.
219    pub fn normalize(&mut self) {
220        self.download_max_concurrency = self.download_max_concurrency.max(1);
221    }
222}
223
224const fn default_true() -> bool {
225    true
226}
227
228const fn default_view_max_text_file_size() -> u64 {
229    10 * 1024 * 1024
230}
231
232const fn default_view_relaxed_text_file_size() -> u64 {
233    50 * 1024 * 1024
234}
235
236const fn default_view_relaxed_line_limit() -> usize {
237    5000
238}
239
240const fn default_view_relaxed_max_line_length() -> usize {
241    20_000
242}
243
244const fn default_view_max_content_chars() -> usize {
245    60_000
246}
247
248const fn default_view_relaxed_max_content_chars() -> usize {
249    250_000
250}
251
252const fn default_edit_max_file_size() -> u64 {
253    20 * 1024 * 1024
254}
255
256const fn default_grep_max_file_size() -> u64 {
257    10 * 1024 * 1024
258}
259
260const fn default_view_max_inline_image_bytes() -> u64 {
261    20 * 1024 * 1024
262}
263
264const fn default_view_max_inline_video_bytes() -> u64 {
265    50 * 1024 * 1024
266}
267
268const fn default_view_max_inline_audio_bytes() -> u64 {
269    50 * 1024 * 1024
270}
271
272const fn default_fetch_stream_chunk_size() -> usize {
273    64 * 1024
274}
275
276const fn default_fetch_max_inline_binary_bytes() -> u64 {
277    30 * 1024 * 1024
278}
279
280const fn default_download_max_concurrency() -> usize {
281    4
282}
283
284const fn default_document_max_file_size() -> u64 {
285    200 * 1024 * 1024
286}
287
288const fn default_filesystem_output_truncate_limit() -> usize {
289    20_000
290}
291
292const fn default_grep_truncation_threshold() -> usize {
293    30_000
294}
295
296const fn default_grep_truncated_line_max() -> usize {
297    300
298}
299
300const fn default_shell_output_truncate_limit() -> usize {
301    20_000
302}
303
304const fn default_cold_start_tool_return_limit() -> usize {
305    500
306}