Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,15 @@ max_subagents = 10 # optional (1-20)
# # baidu: 百度 AI Search via qianfan.baidubce.com,需 api_key
# # volcengine: 火山引擎 Ark web_search (免费 2 万次/月), 需 api_key
# # 也回退到 VOLCENGINE_API_KEY / VOLCENGINE_ARK_API_KEY / ARK_API_KEY 环境变量
# base_url = "https://search.example/html/" # optional DuckDuckGo-compatible HTML endpoint
# api_key = "YOUR_SEARCH_KEY" # required for tavily, bocha, and baidu; optional for metaso
# # WARNING: treat config.toml like a secret file when
# # storing API keys. Prefer env vars for local smoke tests.
#
# Env-var overrides:
# DEEPSEEK_SEARCH_PROVIDER → search.provider
# DEEPSEEK_SEARCH_API_KEY → search.api_key
# DEEPSEEK_SEARCH_BASE_URL → search.base_url
# METASO_API_KEY → metaso key fallback
# BAIDU_SEARCH_API_KEY → baidu key fallback

Expand Down
54 changes: 54 additions & 0 deletions crates/tui/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,11 @@ pub struct SearchConfig {
/// Search provider: `bing` | `duckduckgo` | `tavily` | `bocha` | `metaso` | `baidu` | `volcengine`. Default: `duckduckgo`.
#[serde(default)]
pub provider: Option<SearchProvider>,
/// Optional DuckDuckGo-compatible HTML endpoint. When set with the
/// DuckDuckGo provider, `web_search` appends the `q` query parameter to
/// this URL instead of using `https://html.duckduckgo.com/html/`.
#[serde(default)]
pub base_url: Option<String>,
/// API key for Tavily, Bocha, Metaso, Baidu, or Volcengine. Not required for Bing or DuckDuckGo.
/// Metaso also falls back to `METASO_API_KEY` env var, then a built-in default.
/// Baidu also falls back to `BAIDU_SEARCH_API_KEY` env var.
Expand Down Expand Up @@ -3340,6 +3345,15 @@ fn apply_env_overrides(config: &mut Config) {
.get_or_insert_with(SearchConfig::default)
.api_key = Some(value);
}
match std::env::var("DEEPSEEK_SEARCH_BASE_URL") {
Ok(value) if !value.trim().is_empty() => {
config
.search
.get_or_insert_with(SearchConfig::default)
.base_url = Some(value);
}
_ => {}
}
if let Ok(value) = std::env::var("DEEPSEEK_REQUIREMENTS_PATH") {
config.requirements_path = Some(value);
}
Expand Down Expand Up @@ -4868,6 +4882,25 @@ mod tests {
);
}

#[test]
fn search_config_preserves_custom_base_url() {
let config: Config = toml::from_str(
r#"
[search]
provider = "duckduckgo"
base_url = "https://search.internal.example/html/"
"#,
)
.expect("search config");

let search = config.search.expect("search table");
assert_eq!(search.provider, Some(SearchProvider::DuckDuckGo));
assert_eq!(
search.base_url.as_deref(),
Some("https://search.internal.example/html/")
);
}

#[test]
fn explicit_baidu_search_provider_is_preserved() {
let config: Config = toml::from_str(
Expand Down Expand Up @@ -5011,6 +5044,27 @@ mod tests {
);
}

#[test]
fn apply_env_overrides_sets_search_base_url() {
let _guard = lock_test_env();
let prev = env::var_os("DEEPSEEK_SEARCH_BASE_URL");
unsafe {
env::set_var(
"DEEPSEEK_SEARCH_BASE_URL",
"https://search.internal.example/html/",
)
};
let mut config = Config::default();

apply_env_overrides(&mut config);

unsafe { EnvGuard::restore_var("DEEPSEEK_SEARCH_BASE_URL", prev) };
assert_eq!(
config.search.and_then(|search| search.base_url),
Some("https://search.internal.example/html/".to_string())
);
}

#[test]
fn search_provider_resolution_ignores_invalid_env_override() {
let _guard = lock_test_env();
Expand Down
4 changes: 4 additions & 0 deletions crates/tui/src/core/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ pub struct EngineConfig {
/// Metaso also falls back to `METASO_API_KEY` env var, then a built-in key.
/// Baidu also falls back to `BAIDU_SEARCH_API_KEY`.
pub search_api_key: Option<String>,
/// Optional DuckDuckGo-compatible HTML endpoint override.
pub search_base_url: Option<String>,
/// Per-step DeepSeek API timeout for sub-agent `create_message` requests.
/// Resolved from `[subagents] api_timeout_secs` (clamped to 1..=1800)
/// once at engine construction, then threaded onto every
Expand Down Expand Up @@ -241,6 +243,7 @@ impl Default for EngineConfig {
workshop: None,
search_provider: crate::config::SearchProvider::default(),
search_api_key: None,
search_base_url: None,
subagent_api_timeout: Duration::from_secs(
crate::config::DEFAULT_SUBAGENT_API_TIMEOUT_SECS,
),
Expand Down Expand Up @@ -1711,6 +1714,7 @@ impl Engine {
// Wire search provider config.
ctx.search_provider = self.config.search_provider;
ctx.search_api_key = self.config.search_api_key.clone();
ctx.search_base_url = self.config.search_base_url.clone();

let policy = sandbox_policy_for_mode(mode, &self.session.workspace);
let mut ctx = ctx.with_elevated_sandbox_policy(policy);
Expand Down
3 changes: 3 additions & 0 deletions crates/tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5385,6 +5385,7 @@ async fn run_exec_agent(
workshop: config.workshop.clone(),
search_provider: config.search_provider(),
search_api_key: config.search.as_ref().and_then(|s| s.api_key.clone()),
search_base_url: config.search.as_ref().and_then(|s| s.base_url.clone()),
tools_always_load: config.tools_always_load(),
tools: config.tools.clone(),
};
Expand Down Expand Up @@ -5956,6 +5957,7 @@ mod doctor_endpoint_tests {
let config = Config {
search: Some(crate::config::SearchConfig {
provider: Some(crate::config::SearchProvider::DuckDuckGo),
base_url: None,
api_key: None,
}),
..Default::default()
Expand Down Expand Up @@ -5995,6 +5997,7 @@ mod doctor_endpoint_tests {
let config = Config {
search: Some(crate::config::SearchConfig {
provider: Some(crate::config::SearchProvider::Bing),
base_url: None,
api_key: None,
}),
..Default::default()
Expand Down
1 change: 1 addition & 0 deletions crates/tui/src/runtime_threads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,7 @@ impl RuntimeThreadManager {
workshop: self.config.workshop.clone(),
search_provider: self.config.search_provider(),
search_api_key: self.config.search.as_ref().and_then(|s| s.api_key.clone()),
search_base_url: self.config.search.as_ref().and_then(|s| s.base_url.clone()),
tools_always_load: self.config.tools_always_load(),
tools: self.config.tools.clone(),
};
Expand Down
5 changes: 5 additions & 0 deletions crates/tui/src/tools/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ pub struct ToolContext {
/// Metaso also falls back to `METASO_API_KEY` env var, then a built-in key.
/// Baidu also falls back to `BAIDU_SEARCH_API_KEY`.
pub search_api_key: Option<String>,
/// Optional DuckDuckGo-compatible HTML endpoint override for `web_search`.
pub search_base_url: Option<String>,

/// Per-session workshop variable store (#548). Holds the raw content of
/// the most recent large-tool routing event so the parent can call
Expand Down Expand Up @@ -210,6 +212,7 @@ impl ToolContext {
large_output_router: None,
search_provider: crate::config::SearchProvider::default(),
search_api_key: None,
search_base_url: None,
workshop_vars: None,
}
}
Expand Down Expand Up @@ -247,6 +250,7 @@ impl ToolContext {
large_output_router: None,
search_provider: crate::config::SearchProvider::default(),
search_api_key: None,
search_base_url: None,
workshop_vars: None,
}
}
Expand Down Expand Up @@ -284,6 +288,7 @@ impl ToolContext {
large_output_router: None,
search_provider: crate::config::SearchProvider::default(),
search_api_key: None,
search_base_url: None,
workshop_vars: None,
}
}
Expand Down
Loading
Loading