use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolAvailabilityPolicy {
#[default]
SkipAndReport,
FailRun,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ToolConfig {
#[serde(default)]
pub unavailable_tool_policy: ToolAvailabilityPolicy,
#[serde(default = "default_true")]
pub skip_url_verification: bool,
#[serde(default)]
pub enable_load_document: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub image_understanding_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub video_understanding_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audio_understanding_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub google_search_api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub google_search_cx: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tavily_api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub brave_search_api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pixabay_api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rapidapi_api_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub firecrawl_api_key: Option<String>,
#[serde(default = "default_view_max_text_file_size")]
pub view_max_text_file_size: u64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub view_relaxed_text_patterns: Vec<String>,
#[serde(default, skip)]
pub view_relaxed_text_dynamic_patterns: BTreeMap<String, Vec<String>>,
#[serde(default = "default_view_relaxed_text_file_size")]
pub view_relaxed_text_file_size: u64,
#[serde(default = "default_view_relaxed_line_limit")]
pub view_relaxed_line_limit: usize,
#[serde(default = "default_view_relaxed_max_line_length")]
pub view_relaxed_max_line_length: usize,
#[serde(default = "default_view_max_content_chars")]
pub view_max_content_chars: usize,
#[serde(default = "default_view_relaxed_max_content_chars")]
pub view_relaxed_max_content_chars: usize,
#[serde(default = "default_edit_max_file_size")]
pub edit_max_file_size: u64,
#[serde(default = "default_grep_max_file_size")]
pub grep_max_file_size: u64,
#[serde(default = "default_view_max_inline_image_bytes")]
pub view_max_inline_image_bytes: u64,
#[serde(default = "default_view_max_inline_video_bytes")]
pub view_max_inline_video_bytes: u64,
#[serde(default = "default_view_max_inline_audio_bytes")]
pub view_max_inline_audio_bytes: u64,
#[serde(default = "default_fetch_stream_chunk_size")]
pub fetch_stream_chunk_size: usize,
#[serde(default = "default_fetch_max_inline_binary_bytes")]
pub fetch_max_inline_binary_bytes: u64,
#[serde(default = "default_download_max_concurrency")]
pub download_max_concurrency: usize,
#[serde(default = "default_document_max_file_size")]
pub document_max_file_size: u64,
#[serde(default = "default_filesystem_output_truncate_limit")]
pub filesystem_output_truncate_limit: usize,
#[serde(default = "default_grep_truncation_threshold")]
pub grep_truncation_threshold: usize,
#[serde(default = "default_grep_truncated_line_max")]
pub grep_truncated_line_max: usize,
#[serde(default = "default_shell_output_truncate_limit")]
pub shell_output_truncate_limit: usize,
#[serde(default = "default_cold_start_tool_return_limit")]
pub cold_start_tool_return_limit: usize,
}
impl Default for ToolConfig {
fn default() -> Self {
Self {
unavailable_tool_policy: ToolAvailabilityPolicy::SkipAndReport,
skip_url_verification: true,
enable_load_document: false,
image_understanding_model: None,
video_understanding_model: None,
audio_understanding_model: None,
google_search_api_key: None,
google_search_cx: None,
tavily_api_key: None,
brave_search_api_key: None,
pixabay_api_key: None,
rapidapi_api_key: None,
firecrawl_api_key: None,
view_max_text_file_size: default_view_max_text_file_size(),
view_relaxed_text_patterns: Vec::new(),
view_relaxed_text_dynamic_patterns: BTreeMap::new(),
view_relaxed_text_file_size: default_view_relaxed_text_file_size(),
view_relaxed_line_limit: default_view_relaxed_line_limit(),
view_relaxed_max_line_length: default_view_relaxed_max_line_length(),
view_max_content_chars: default_view_max_content_chars(),
view_relaxed_max_content_chars: default_view_relaxed_max_content_chars(),
edit_max_file_size: default_edit_max_file_size(),
grep_max_file_size: default_grep_max_file_size(),
view_max_inline_image_bytes: default_view_max_inline_image_bytes(),
view_max_inline_video_bytes: default_view_max_inline_video_bytes(),
view_max_inline_audio_bytes: default_view_max_inline_audio_bytes(),
fetch_stream_chunk_size: default_fetch_stream_chunk_size(),
fetch_max_inline_binary_bytes: default_fetch_max_inline_binary_bytes(),
download_max_concurrency: default_download_max_concurrency(),
document_max_file_size: default_document_max_file_size(),
filesystem_output_truncate_limit: default_filesystem_output_truncate_limit(),
grep_truncation_threshold: default_grep_truncation_threshold(),
grep_truncated_line_max: default_grep_truncated_line_max(),
shell_output_truncate_limit: default_shell_output_truncate_limit(),
cold_start_tool_return_limit: default_cold_start_tool_return_limit(),
}
}
}
impl ToolConfig {
#[must_use]
pub fn is_default(&self) -> bool {
let mut persisted = self.clone();
persisted.view_relaxed_text_dynamic_patterns.clear();
persisted == Self::default()
}
#[must_use]
pub fn effective_view_relaxed_text_patterns(&self) -> Vec<&str> {
let mut patterns = self
.view_relaxed_text_patterns
.iter()
.map(String::as_str)
.collect::<Vec<_>>();
for source_patterns in self.view_relaxed_text_dynamic_patterns.values() {
patterns.extend(source_patterns.iter().map(String::as_str));
}
patterns
}
pub fn register_view_relaxed_text_patterns(
&mut self,
source: impl Into<String>,
patterns: impl IntoIterator<Item = String>,
) {
let patterns = patterns
.into_iter()
.map(|pattern| pattern.trim().to_string())
.filter(|pattern| !pattern.is_empty())
.collect::<Vec<_>>();
let source = source.into();
if patterns.is_empty() {
self.view_relaxed_text_dynamic_patterns.remove(&source);
} else {
self.view_relaxed_text_dynamic_patterns
.insert(source, patterns);
}
}
pub fn unregister_view_relaxed_text_patterns(&mut self, source: &str) {
self.view_relaxed_text_dynamic_patterns.remove(source);
}
pub fn normalize(&mut self) {
self.download_max_concurrency = self.download_max_concurrency.max(1);
}
}
const fn default_true() -> bool {
true
}
const fn default_view_max_text_file_size() -> u64 {
10 * 1024 * 1024
}
const fn default_view_relaxed_text_file_size() -> u64 {
50 * 1024 * 1024
}
const fn default_view_relaxed_line_limit() -> usize {
5000
}
const fn default_view_relaxed_max_line_length() -> usize {
20_000
}
const fn default_view_max_content_chars() -> usize {
60_000
}
const fn default_view_relaxed_max_content_chars() -> usize {
250_000
}
const fn default_edit_max_file_size() -> u64 {
20 * 1024 * 1024
}
const fn default_grep_max_file_size() -> u64 {
10 * 1024 * 1024
}
const fn default_view_max_inline_image_bytes() -> u64 {
20 * 1024 * 1024
}
const fn default_view_max_inline_video_bytes() -> u64 {
50 * 1024 * 1024
}
const fn default_view_max_inline_audio_bytes() -> u64 {
50 * 1024 * 1024
}
const fn default_fetch_stream_chunk_size() -> usize {
64 * 1024
}
const fn default_fetch_max_inline_binary_bytes() -> u64 {
30 * 1024 * 1024
}
const fn default_download_max_concurrency() -> usize {
4
}
const fn default_document_max_file_size() -> u64 {
200 * 1024 * 1024
}
const fn default_filesystem_output_truncate_limit() -> usize {
20_000
}
const fn default_grep_truncation_threshold() -> usize {
30_000
}
const fn default_grep_truncated_line_max() -> usize {
300
}
const fn default_shell_output_truncate_limit() -> usize {
20_000
}
const fn default_cold_start_tool_return_limit() -> usize {
500
}