Make multi_agent_v2 wait_agent timeouts configurable (#22528)

## Why

`multi_agent_v2` already allowed configuring the minimum `wait_agent`
timeout, but the default timeout and upper bound were still hard-coded.
That made it hard to tune waits for subagent mailbox activity in
sessions that need either faster wakeups or longer waits, and it meant
the model-visible `wait_agent` schema could not fully reflect the
resolved runtime limits.

## What Changed

- Added `features.multi_agent_v2.max_wait_timeout_ms` and
`features.multi_agent_v2.default_wait_timeout_ms` alongside the existing
`min_wait_timeout_ms` setting.
- Validated all three timeouts in config as `0..=3_600_000`, with
`min_wait_timeout_ms <= default_wait_timeout_ms <= max_wait_timeout_ms`.
- Thread and review session tool config now passes the resolved
min/default/max values into the `wait_agent` tool schema.
- `wait_agent` now uses the configured default when `timeout_ms` is
omitted and rejects explicit values outside the configured min/max range
instead of silently clamping them.
- Updated the generated config schema and config-lock test coverage for
the new fields.
This commit is contained in:
Andrey Mishchenko authored and GitHub committed 2026-05-13 14:43:06 -07:00
1 parent 8ae0c837f0
commit 7c57a59f51
14 files changed
+541 -53

No files matched your search

+13 -1
View File
@@ -1465,6 +1465,12 @@
"MultiAgentV2ConfigToml": { "MultiAgentV2ConfigToml": {
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"default_wait_timeout_ms": {
"format": "int64",
"maximum": 3600000.0,
"minimum": 0.0,
"type": "integer"
},
"enabled": { "enabled": {
"type": "boolean" "type": "boolean"
}, },
@@ -1476,10 +1482,16 @@
"minimum": 1.0, "minimum": 1.0,
"type": "integer" "type": "integer"
}, },
"max_wait_timeout_ms": {
"format": "int64",
"maximum": 3600000.0,
"minimum": 0.0,
"type": "integer"
},
"min_wait_timeout_ms": { "min_wait_timeout_ms": {
"format": "int64", "format": "int64",
"maximum": 3600000.0, "maximum": 3600000.0,
"minimum": 1.0, "minimum": 0.0,
"type": "integer" "type": "integer"
}, },
"non_code_mode_only": { "non_code_mode_only": {
+166 -3
View File
@@ -9687,6 +9687,8 @@ async fn multi_agent_v2_config_from_feature_table() -> std::io::Result<()> {
enabled = true enabled = true
max_concurrent_threads_per_session = 5 max_concurrent_threads_per_session = 5
min_wait_timeout_ms = 2500 min_wait_timeout_ms = 2500
max_wait_timeout_ms = 120000
default_wait_timeout_ms = 30000
usage_hint_enabled = false usage_hint_enabled = false
usage_hint_text = "Custom delegation guidance." usage_hint_text = "Custom delegation guidance."
root_agent_usage_hint_text = "Root guidance." root_agent_usage_hint_text = "Root guidance."
@@ -9705,6 +9707,8 @@ non_code_mode_only = true
assert!(config.features.enabled(Feature::MultiAgentV2)); assert!(config.features.enabled(Feature::MultiAgentV2));
assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 5); assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 5);
assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 2500); assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 2500);
assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 120000);
assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 30000);
assert_eq!(config.agent_max_threads, Some(4)); assert_eq!(config.agent_max_threads, Some(4));
assert!(!config.multi_agent_v2.usage_hint_enabled); assert!(!config.multi_agent_v2.usage_hint_enabled);
assert_eq!( assert_eq!(
@@ -9735,6 +9739,8 @@ async fn profile_multi_agent_v2_config_overrides_base() -> std::io::Result<()> {
[features.multi_agent_v2] [features.multi_agent_v2]
max_concurrent_threads_per_session = 4 max_concurrent_threads_per_session = 4
min_wait_timeout_ms = 3000 min_wait_timeout_ms = 3000
max_wait_timeout_ms = 120000
default_wait_timeout_ms = 30000
usage_hint_enabled = true usage_hint_enabled = true
usage_hint_text = "base hint" usage_hint_text = "base hint"
root_agent_usage_hint_text = "base root hint" root_agent_usage_hint_text = "base root hint"
@@ -9745,6 +9751,8 @@ non_code_mode_only = false
[profiles.no_hint.features.multi_agent_v2] [profiles.no_hint.features.multi_agent_v2]
max_concurrent_threads_per_session = 6 max_concurrent_threads_per_session = 6
min_wait_timeout_ms = 1500 min_wait_timeout_ms = 1500
max_wait_timeout_ms = 90000
default_wait_timeout_ms = 15000
usage_hint_enabled = false usage_hint_enabled = false
usage_hint_text = "profile hint" usage_hint_text = "profile hint"
root_agent_usage_hint_text = "profile root hint" root_agent_usage_hint_text = "profile root hint"
@@ -9762,6 +9770,8 @@ non_code_mode_only = true
assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 6); assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 6);
assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 1500); assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 1500);
assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 90000);
assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 15000);
assert!(!config.multi_agent_v2.usage_hint_enabled); assert!(!config.multi_agent_v2.usage_hint_enabled);
assert_eq!( assert_eq!(
config.multi_agent_v2.usage_hint_text.as_deref(), config.multi_agent_v2.usage_hint_text.as_deref(),
@@ -9799,6 +9809,8 @@ enabled = true
assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 4); assert_eq!(config.multi_agent_v2.max_concurrent_threads_per_session, 4);
assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 10_000); assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 10_000);
assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 3_600_000);
assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 30_000);
assert_eq!(config.agent_max_threads, Some(3)); assert_eq!(config.agent_max_threads, Some(3));
assert!(!config.multi_agent_v2.non_code_mode_only); assert!(!config.multi_agent_v2.non_code_mode_only);
@@ -9835,13 +9847,33 @@ max_threads = 3
} }
#[tokio::test] #[tokio::test]
async fn multi_agent_v2_rejects_invalid_min_wait_timeout() -> std::io::Result<()> { async fn multi_agent_v2_rejects_invalid_wait_timeouts() -> std::io::Result<()> {
let codex_home = TempDir::new()?; let codex_home = TempDir::new()?;
std::fs::write( std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE), codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2] r#"[features.multi_agent_v2]
enabled = true enabled = true
min_wait_timeout_ms = 0 min_wait_timeout_ms = 0
max_wait_timeout_ms = 0
default_wait_timeout_ms = 0
"#,
)?;
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await?;
assert_eq!(config.multi_agent_v2.min_wait_timeout_ms, 0);
assert_eq!(config.multi_agent_v2.max_wait_timeout_ms, 0);
assert_eq!(config.multi_agent_v2.default_wait_timeout_ms, 0);
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
min_wait_timeout_ms = -1
"#, "#,
)?; )?;
@@ -9850,12 +9882,12 @@ min_wait_timeout_ms = 0
.fallback_cwd(Some(codex_home.path().to_path_buf())) .fallback_cwd(Some(codex_home.path().to_path_buf()))
.build() .build()
.await .await
.expect_err("zero min_wait_timeout_ms should be rejected"); .expect_err("negative min_wait_timeout_ms should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!( assert_eq!(
err.to_string(), err.to_string(),
"features.multi_agent_v2.min_wait_timeout_ms must be at least 1" "features.multi_agent_v2.min_wait_timeout_ms must be at least 0"
); );
std::fs::write( std::fs::write(
@@ -9879,6 +9911,137 @@ min_wait_timeout_ms = 3600001
"features.multi_agent_v2.min_wait_timeout_ms must be at most 3600000" "features.multi_agent_v2.min_wait_timeout_ms must be at most 3600000"
); );
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
max_wait_timeout_ms = -1
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.expect_err("negative max_wait_timeout_ms should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"features.multi_agent_v2.max_wait_timeout_ms must be at least 0"
);
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
max_wait_timeout_ms = 3600001
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.expect_err("too large max_wait_timeout_ms should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"features.multi_agent_v2.max_wait_timeout_ms must be at most 3600000"
);
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
default_wait_timeout_ms = -1
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.expect_err("negative default_wait_timeout_ms should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"features.multi_agent_v2.default_wait_timeout_ms must be at least 0"
);
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
min_wait_timeout_ms = 1000
max_wait_timeout_ms = 500
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.expect_err("min greater than max should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"features.multi_agent_v2.min_wait_timeout_ms must be at most features.multi_agent_v2.max_wait_timeout_ms"
);
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
min_wait_timeout_ms = 1000
max_wait_timeout_ms = 2000
default_wait_timeout_ms = 500
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.expect_err("default less than min should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"features.multi_agent_v2.default_wait_timeout_ms must be at least features.multi_agent_v2.min_wait_timeout_ms"
);
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"[features.multi_agent_v2]
enabled = true
min_wait_timeout_ms = 1000
max_wait_timeout_ms = 2000
default_wait_timeout_ms = 2500
"#,
)?;
let err = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await
.expect_err("default greater than max should be rejected");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"features.multi_agent_v2.default_wait_timeout_ms must be at most features.multi_agent_v2.max_wait_timeout_ms"
);
Ok(()) Ok(())
} }
+57 -7
View File
@@ -177,7 +177,11 @@ pub(crate) const AGENTS_MD_MAX_BYTES: usize = DEFAULT_PROJECT_DOC_MAX_BYTES; //
pub(crate) const DEFAULT_AGENT_MAX_THREADS: Option<usize> = Some(6); pub(crate) const DEFAULT_AGENT_MAX_THREADS: Option<usize> = Some(6);
pub(crate) const DEFAULT_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION: usize = 4; pub(crate) const DEFAULT_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION: usize = 4;
pub(crate) const DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS: i64 = 10_000; pub(crate) const DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS: i64 = 10_000;
pub(crate) const MAX_MULTI_AGENT_V2_WAIT_TIMEOUT_MS: i64 = 3600 * 1000; pub(crate) const DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS: i64 = 3600 * 1000;
pub(crate) const DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS: i64 = 30_000;
pub(crate) const HARD_MIN_MULTI_AGENT_V2_TIMEOUT_MS: i64 = 0;
pub(crate) const HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS: i64 =
DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS;
pub(crate) const DEFAULT_AGENT_MAX_DEPTH: i32 = 1; pub(crate) const DEFAULT_AGENT_MAX_DEPTH: i32 = 1;
pub(crate) const DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS: Option<u64> = None; pub(crate) const DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS: Option<u64> = None;
const LOCAL_DEV_BUILD_VERSION: &str = "0.0.0"; const LOCAL_DEV_BUILD_VERSION: &str = "0.0.0";
@@ -832,6 +836,8 @@ pub struct Config {
pub struct MultiAgentV2Config { pub struct MultiAgentV2Config {
pub max_concurrent_threads_per_session: usize, pub max_concurrent_threads_per_session: usize,
pub min_wait_timeout_ms: i64, pub min_wait_timeout_ms: i64,
pub max_wait_timeout_ms: i64,
pub default_wait_timeout_ms: i64,
pub usage_hint_enabled: bool, pub usage_hint_enabled: bool,
pub usage_hint_text: Option<String>, pub usage_hint_text: Option<String>,
pub root_agent_usage_hint_text: Option<String>, pub root_agent_usage_hint_text: Option<String>,
@@ -846,6 +852,8 @@ impl Default for MultiAgentV2Config {
max_concurrent_threads_per_session: max_concurrent_threads_per_session:
DEFAULT_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION, DEFAULT_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION,
min_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS, min_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS,
max_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS,
default_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS,
usage_hint_enabled: true, usage_hint_enabled: true,
usage_hint_text: None, usage_hint_text: None,
root_agent_usage_hint_text: None, root_agent_usage_hint_text: None,
@@ -1965,6 +1973,14 @@ fn resolve_multi_agent_v2_config(
.and_then(|config| config.min_wait_timeout_ms) .and_then(|config| config.min_wait_timeout_ms)
.or_else(|| base.and_then(|config| config.min_wait_timeout_ms)) .or_else(|| base.and_then(|config| config.min_wait_timeout_ms))
.unwrap_or(default.min_wait_timeout_ms); .unwrap_or(default.min_wait_timeout_ms);
let max_wait_timeout_ms = profile
.and_then(|config| config.max_wait_timeout_ms)
.or_else(|| base.and_then(|config| config.max_wait_timeout_ms))
.unwrap_or(default.max_wait_timeout_ms);
let default_wait_timeout_ms = profile
.and_then(|config| config.default_wait_timeout_ms)
.or_else(|| base.and_then(|config| config.default_wait_timeout_ms))
.unwrap_or(default.default_wait_timeout_ms);
let usage_hint_enabled = profile let usage_hint_enabled = profile
.and_then(|config| config.usage_hint_enabled) .and_then(|config| config.usage_hint_enabled)
.or_else(|| base.and_then(|config| config.usage_hint_enabled)) .or_else(|| base.and_then(|config| config.usage_hint_enabled))
@@ -1996,6 +2012,8 @@ fn resolve_multi_agent_v2_config(
MultiAgentV2Config { MultiAgentV2Config {
max_concurrent_threads_per_session, max_concurrent_threads_per_session,
min_wait_timeout_ms, min_wait_timeout_ms,
max_wait_timeout_ms,
default_wait_timeout_ms,
usage_hint_enabled, usage_hint_enabled,
usage_hint_text, usage_hint_text,
root_agent_usage_hint_text, root_agent_usage_hint_text,
@@ -2078,6 +2096,22 @@ pub(crate) fn resolve_web_search_mode_for_turn(
WebSearchMode::Disabled WebSearchMode::Disabled
} }
fn validate_multi_agent_v2_wait_timeout(label: &str, value: i64) -> std::io::Result<()> {
if value < HARD_MIN_MULTI_AGENT_V2_TIMEOUT_MS {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{label} must be at least {HARD_MIN_MULTI_AGENT_V2_TIMEOUT_MS}"),
));
}
if value > HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{label} must be at most {HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS}"),
));
}
Ok(())
}
impl Config { impl Config {
#[cfg(test)] #[cfg(test)]
async fn load_from_base_config_with_overrides( async fn load_from_base_config_with_overrides(
@@ -2652,18 +2686,34 @@ impl Config {
"features.multi_agent_v2.max_concurrent_threads_per_session must be at least 1", "features.multi_agent_v2.max_concurrent_threads_per_session must be at least 1",
)); ));
} }
if multi_agent_v2.min_wait_timeout_ms <= 0 { validate_multi_agent_v2_wait_timeout(
"features.multi_agent_v2.min_wait_timeout_ms",
multi_agent_v2.min_wait_timeout_ms,
)?;
validate_multi_agent_v2_wait_timeout(
"features.multi_agent_v2.max_wait_timeout_ms",
multi_agent_v2.max_wait_timeout_ms,
)?;
validate_multi_agent_v2_wait_timeout(
"features.multi_agent_v2.default_wait_timeout_ms",
multi_agent_v2.default_wait_timeout_ms,
)?;
if multi_agent_v2.min_wait_timeout_ms > multi_agent_v2.max_wait_timeout_ms {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput, std::io::ErrorKind::InvalidInput,
"features.multi_agent_v2.min_wait_timeout_ms must be at least 1", "features.multi_agent_v2.min_wait_timeout_ms must be at most features.multi_agent_v2.max_wait_timeout_ms",
)); ));
} }
if multi_agent_v2.min_wait_timeout_ms > MAX_MULTI_AGENT_V2_WAIT_TIMEOUT_MS { if multi_agent_v2.default_wait_timeout_ms < multi_agent_v2.min_wait_timeout_ms {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput, std::io::ErrorKind::InvalidInput,
format!( "features.multi_agent_v2.default_wait_timeout_ms must be at least features.multi_agent_v2.min_wait_timeout_ms",
"features.multi_agent_v2.min_wait_timeout_ms must be at most {MAX_MULTI_AGENT_V2_WAIT_TIMEOUT_MS}" ));
), }
if multi_agent_v2.default_wait_timeout_ms > multi_agent_v2.max_wait_timeout_ms {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"features.multi_agent_v2.default_wait_timeout_ms must be at most features.multi_agent_v2.max_wait_timeout_ms",
)); ));
} }
let agent_max_threads_from_config = cfg.agents.as_ref().and_then(|agents| agents.max_threads); let agent_max_threads_from_config = cfg.agents.as_ref().and_then(|agents| agents.max_threads);
+2
View File
@@ -264,6 +264,8 @@ mod tests {
enabled: Some(false), enabled: Some(false),
max_concurrent_threads_per_session: Some(_), max_concurrent_threads_per_session: Some(_),
min_wait_timeout_ms: Some(_), min_wait_timeout_ms: Some(_),
max_wait_timeout_ms: Some(_),
default_wait_timeout_ms: Some(_),
usage_hint_enabled: Some(_), usage_hint_enabled: Some(_),
hide_spawn_agent_metadata: Some(_), hide_spawn_agent_metadata: Some(_),
.. ..
+10
View File
@@ -64,6 +64,16 @@ pub(super) async fn spawn_review_thread(
.enabled(Feature::MultiAgentV2) .enabled(Feature::MultiAgentV2)
.then_some(config.multi_agent_v2.min_wait_timeout_ms), .then_some(config.multi_agent_v2.min_wait_timeout_ms),
) )
.with_wait_agent_max_timeout_ms(
review_features
.enabled(Feature::MultiAgentV2)
.then_some(config.multi_agent_v2.max_wait_timeout_ms),
)
.with_wait_agent_default_timeout_ms(
review_features
.enabled(Feature::MultiAgentV2)
.then_some(config.multi_agent_v2.default_wait_timeout_ms),
)
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build( .with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
&config.agent_roles, &config.agent_roles,
)); ));
+24
View File
@@ -234,6 +234,18 @@ impl TurnContext {
.enabled(Feature::MultiAgentV2) .enabled(Feature::MultiAgentV2)
.then_some(config.multi_agent_v2.min_wait_timeout_ms), .then_some(config.multi_agent_v2.min_wait_timeout_ms),
) )
.with_wait_agent_max_timeout_ms(
config
.features
.enabled(Feature::MultiAgentV2)
.then_some(config.multi_agent_v2.max_wait_timeout_ms),
)
.with_wait_agent_default_timeout_ms(
config
.features
.enabled(Feature::MultiAgentV2)
.then_some(config.multi_agent_v2.default_wait_timeout_ms),
)
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build( .with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
&config.agent_roles, &config.agent_roles,
)); ));
@@ -539,6 +551,18 @@ impl Session {
.enabled(Feature::MultiAgentV2) .enabled(Feature::MultiAgentV2)
.then_some(per_turn_config.multi_agent_v2.min_wait_timeout_ms), .then_some(per_turn_config.multi_agent_v2.min_wait_timeout_ms),
) )
.with_wait_agent_max_timeout_ms(
per_turn_config
.features
.enabled(Feature::MultiAgentV2)
.then_some(per_turn_config.multi_agent_v2.max_wait_timeout_ms),
)
.with_wait_agent_default_timeout_ms(
per_turn_config
.features
.enabled(Feature::MultiAgentV2)
.then_some(per_turn_config.multi_agent_v2.default_wait_timeout_ms),
)
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build( .with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
&per_turn_config.agent_roles, &per_turn_config.agent_roles,
)); ));
@@ -1,7 +1,7 @@
use crate::agent::AgentStatus; use crate::agent::AgentStatus;
use crate::config::Config; use crate::config::Config;
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS; use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
use crate::config::MAX_MULTI_AGENT_V2_WAIT_TIMEOUT_MS; use crate::config::HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS;
use crate::function_tool::FunctionCallError; use crate::function_tool::FunctionCallError;
use crate::session::session::Session; use crate::session::session::Session;
use crate::session::turn_context::TurnContext; use crate::session::turn_context::TurnContext;
@@ -30,7 +30,7 @@ use std::collections::HashMap;
/// Minimum wait timeout to prevent tight polling loops from burning CPU. /// Minimum wait timeout to prevent tight polling loops from burning CPU.
pub(crate) const MIN_WAIT_TIMEOUT_MS: i64 = DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS; pub(crate) const MIN_WAIT_TIMEOUT_MS: i64 = DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
pub(crate) const DEFAULT_WAIT_TIMEOUT_MS: i64 = 30_000; pub(crate) const DEFAULT_WAIT_TIMEOUT_MS: i64 = 30_000;
pub(crate) const MAX_WAIT_TIMEOUT_MS: i64 = MAX_MULTI_AGENT_V2_WAIT_TIMEOUT_MS; pub(crate) const MAX_WAIT_TIMEOUT_MS: i64 = HARD_MAX_MULTI_AGENT_V2_TIMEOUT_MS;
pub(crate) fn function_arguments(payload: ToolPayload) -> Result<String, FunctionCallError> { pub(crate) fn function_arguments(payload: ToolPayload) -> Result<String, FunctionCallError> {
match payload { match payload {
@@ -2775,7 +2775,7 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
session, session,
turn, turn,
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1000})), function_payload(json!({"timeout_ms": 10_000})),
)) ))
.await .await
} }
@@ -2808,7 +2808,7 @@ async fn multi_agent_v2_wait_agent_accepts_timeout_only_argument() {
} }
#[tokio::test] #[tokio::test]
async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() { async fn multi_agent_v2_wait_agent_rejects_timeout_below_configured_min() {
let (session, mut turn) = make_session_and_context().await; let (session, mut turn) = make_session_and_context().await;
let mut config = (*turn.config).clone(); let mut config = (*turn.config).clone();
config config
@@ -2816,6 +2816,73 @@ async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() {
.enable(Feature::MultiAgentV2) .enable(Feature::MultiAgentV2)
.expect("test config should allow feature update"); .expect("test config should allow feature update");
config.multi_agent_v2.min_wait_timeout_ms = 50; config.multi_agent_v2.min_wait_timeout_ms = 50;
config.multi_agent_v2.max_wait_timeout_ms = 1_000;
config.multi_agent_v2.default_wait_timeout_ms = 50;
turn.config = Arc::new(config);
let Err(err) = WaitAgentHandlerV2::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"wait_agent",
function_payload(json!({"timeout_ms": 1})),
))
.await
else {
panic!("timeout below configured minimum should be rejected");
};
assert_eq!(
err,
FunctionCallError::RespondToModel("timeout_ms must be at least 50".to_string())
);
}
#[tokio::test]
async fn multi_agent_v2_wait_agent_accepts_explicit_timeout_at_configured_min() {
let (session, mut turn) = make_session_and_context().await;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.min_wait_timeout_ms = 1;
config.multi_agent_v2.max_wait_timeout_ms = 1_000;
config.multi_agent_v2.default_wait_timeout_ms = 50;
turn.config = Arc::new(config);
let output = WaitAgentHandlerV2::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"wait_agent",
function_payload(json!({"timeout_ms": 1})),
))
.await
.expect("wait_agent should succeed");
let (content, success) = expect_text_output(output);
let result: crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult =
serde_json::from_str(&content).expect("wait_agent result should be json");
assert_eq!(
result,
crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult {
message: "Wait timed out.".to_string(),
timed_out: true,
}
);
assert_eq!(success, None);
}
#[tokio::test]
async fn multi_agent_v2_wait_agent_uses_configured_default_timeout() {
let (session, mut turn) = make_session_and_context().await;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.min_wait_timeout_ms = 1;
config.multi_agent_v2.max_wait_timeout_ms = 1_000;
config.multi_agent_v2.default_wait_timeout_ms = 50;
turn.config = Arc::new(config); turn.config = Arc::new(config);
let session = Arc::new(session); let session = Arc::new(session);
let turn = Arc::new(turn); let turn = Arc::new(turn);
@@ -2826,13 +2893,13 @@ async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() {
session.clone(), session.clone(),
turn.clone(), turn.clone(),
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1})), function_payload(json!({})),
)), )),
) )
.await; .await;
assert!( assert!(
early.is_err(), early.is_err(),
"wait_agent should not return before the configured minimum timeout" "wait_agent should not return before the configured default timeout"
); );
let output = timeout( let output = timeout(
@@ -2841,11 +2908,11 @@ async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() {
session, session,
turn, turn,
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1})), function_payload(json!({})),
)), )),
) )
.await .await
.expect("configured minimum should be shorter than the test timeout") .expect("configured default should be shorter than the test timeout")
.expect("wait_agent should succeed"); .expect("wait_agent should succeed");
let (content, success) = expect_text_output(output); let (content, success) = expect_text_output(output);
let result: crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult = let result: crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult =
@@ -2860,6 +2927,111 @@ async fn multi_agent_v2_wait_agent_uses_configured_min_timeout() {
assert_eq!(success, None); assert_eq!(success, None);
} }
#[tokio::test]
async fn multi_agent_v2_wait_agent_allows_zero_configured_timeout() {
let (session, mut turn) = make_session_and_context().await;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.min_wait_timeout_ms = 0;
config.multi_agent_v2.max_wait_timeout_ms = 0;
config.multi_agent_v2.default_wait_timeout_ms = 0;
turn.config = Arc::new(config);
let session = Arc::new(session);
let turn = Arc::new(turn);
let output = timeout(
Duration::from_secs(/*secs*/ 1),
WaitAgentHandlerV2::default().handle(invocation(
session,
turn,
"wait_agent",
function_payload(json!({})),
)),
)
.await
.expect("zero timeout should complete immediately")
.expect("wait_agent should succeed");
let (content, success) = expect_text_output(output);
let result: crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult =
serde_json::from_str(&content).expect("wait_agent result should be json");
assert_eq!(
result,
crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult {
message: "Wait timed out.".to_string(),
timed_out: true,
}
);
assert_eq!(success, None);
}
#[tokio::test]
async fn multi_agent_v2_wait_agent_rejects_timeout_above_configured_max() {
let (session, mut turn) = make_session_and_context().await;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.min_wait_timeout_ms = 1;
config.multi_agent_v2.max_wait_timeout_ms = 50;
config.multi_agent_v2.default_wait_timeout_ms = 1;
turn.config = Arc::new(config);
let Err(err) = WaitAgentHandlerV2::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"wait_agent",
function_payload(json!({"timeout_ms": 500})),
))
.await
else {
panic!("timeout above configured maximum should be rejected");
};
assert_eq!(
err,
FunctionCallError::RespondToModel("timeout_ms must be at most 50".to_string())
);
}
#[tokio::test]
async fn multi_agent_v2_wait_agent_accepts_explicit_timeout_at_configured_max() {
let (session, mut turn) = make_session_and_context().await;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.min_wait_timeout_ms = 1;
config.multi_agent_v2.max_wait_timeout_ms = 1;
config.multi_agent_v2.default_wait_timeout_ms = 1;
turn.config = Arc::new(config);
let output = WaitAgentHandlerV2::default()
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"wait_agent",
function_payload(json!({"timeout_ms": 1})),
))
.await
.expect("wait_agent should succeed");
let (content, success) = expect_text_output(output);
let result: crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult =
serde_json::from_str(&content).expect("wait_agent result should be json");
assert_eq!(
result,
crate::tools::handlers::multi_agents_v2::wait::WaitAgentResult {
message: "Wait timed out.".to_string(),
timed_out: true,
}
);
assert_eq!(success, None);
}
#[tokio::test] #[tokio::test]
async fn wait_agent_returns_not_found_for_missing_agents() { async fn wait_agent_returns_not_found_for_missing_agents() {
let (mut session, turn) = make_session_and_context().await; let (mut session, turn) = make_session_and_context().await;
@@ -2873,7 +3045,7 @@ async fn wait_agent_returns_not_found_for_missing_agents() {
"wait_agent", "wait_agent",
function_payload(json!({ function_payload(json!({
"targets": [id_a.to_string(), id_b.to_string()], "targets": [id_a.to_string(), id_b.to_string()],
"timeout_ms": 1000 "timeout_ms": 10_000
})), })),
); );
let output = WaitAgentHandler::default() let output = WaitAgentHandler::default()
@@ -3009,7 +3181,7 @@ async fn wait_agent_returns_final_status_without_timeout() {
"wait_agent", "wait_agent",
function_payload(json!({ function_payload(json!({
"targets": [agent_id.to_string()], "targets": [agent_id.to_string()],
"timeout_ms": 1000 "timeout_ms": 10_000
})), })),
); );
let output = WaitAgentHandler::default() let output = WaitAgentHandler::default()
@@ -3088,7 +3260,7 @@ async fn multi_agent_v2_wait_agent_returns_summary_for_mailbox_activity() {
session, session,
turn, turn,
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1000})), function_payload(json!({"timeout_ms": 10_000})),
)) ))
.await .await
} }
@@ -3179,7 +3351,7 @@ async fn multi_agent_v2_wait_agent_returns_for_already_queued_mail() {
session, session,
turn, turn,
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1000})), function_payload(json!({"timeout_ms": 10_000})),
)), )),
) )
.await .await
@@ -3254,7 +3426,7 @@ async fn multi_agent_v2_wait_agent_wakes_on_any_mailbox_notification() {
session, session,
turn, turn,
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1000})), function_payload(json!({"timeout_ms": 10_000})),
)) ))
.await .await
} }
@@ -3339,7 +3511,7 @@ async fn multi_agent_v2_wait_agent_does_not_return_completed_content() {
session, session,
turn, turn,
"wait_agent", "wait_agent",
function_payload(json!({"timeout_ms": 1000})), function_payload(json!({"timeout_ms": 10_000})),
)) ))
.await .await
} }
@@ -40,19 +40,22 @@ impl ToolExecutor<ToolInvocation> for Handler {
} = invocation; } = invocation;
let arguments = function_arguments(payload)?; let arguments = function_arguments(payload)?;
let args: WaitArgs = parse_arguments(&arguments)?; let args: WaitArgs = parse_arguments(&arguments)?;
let timeout_ms = args.timeout_ms.unwrap_or(DEFAULT_WAIT_TIMEOUT_MS); let min_timeout_ms = turn.config.multi_agent_v2.min_wait_timeout_ms;
let min_timeout_ms = turn let max_timeout_ms = turn.config.multi_agent_v2.max_wait_timeout_ms;
.config let default_timeout_ms = turn.config.multi_agent_v2.default_wait_timeout_ms;
.multi_agent_v2 let timeout_ms = match args.timeout_ms {
.min_wait_timeout_ms Some(ms) if ms < min_timeout_ms => {
.clamp(1, MAX_WAIT_TIMEOUT_MS); return Err(FunctionCallError::RespondToModel(format!(
let timeout_ms = match timeout_ms { "timeout_ms must be at least {min_timeout_ms}"
ms if ms <= 0 => { )));
return Err(FunctionCallError::RespondToModel(
"timeout_ms must be greater than zero".to_owned(),
));
} }
ms => ms.clamp(min_timeout_ms, MAX_WAIT_TIMEOUT_MS), Some(ms) if ms > max_timeout_ms => {
return Err(FunctionCallError::RespondToModel(format!(
"timeout_ms must be at most {max_timeout_ms}"
)));
}
Some(ms) => ms,
None => default_timeout_ms,
}; };
let mut mailbox_seq_rx = session.subscribe_mailbox_seq(); let mut mailbox_seq_rx = session.subscribe_mailbox_seq();
+27 -11
View File
@@ -1,3 +1,6 @@
use crate::config::DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS;
use crate::config::DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS;
use crate::shell::Shell; use crate::shell::Shell;
use crate::shell::ShellType; use crate::shell::ShellType;
use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS; use crate::tools::handlers::multi_agents_common::DEFAULT_WAIT_TIMEOUT_MS;
@@ -35,16 +38,29 @@ pub(crate) fn build_specs_with_discoverable_tools(
) -> ToolRegistryBuilder { ) -> ToolRegistryBuilder {
let default_agent_type_description = let default_agent_type_description =
crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new()); crate::agent::role::spawn_tool_spec::build(&std::collections::BTreeMap::new());
let min_wait_timeout_ms = if config.multi_agent_v2 { let (min_wait_timeout_ms, max_wait_timeout_ms, default_wait_timeout_ms) =
config if config.multi_agent_v2 {
.wait_agent_min_timeout_ms let min_wait_timeout_ms = config
.unwrap_or(MIN_WAIT_TIMEOUT_MS) .wait_agent_min_timeout_ms
.clamp(1, MAX_WAIT_TIMEOUT_MS) .unwrap_or(DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS);
} else { let max_wait_timeout_ms = config
MIN_WAIT_TIMEOUT_MS .wait_agent_max_timeout_ms
}; .unwrap_or(DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS);
let default_wait_timeout_ms = let default_wait_timeout_ms = config
DEFAULT_WAIT_TIMEOUT_MS.clamp(min_wait_timeout_ms, MAX_WAIT_TIMEOUT_MS); .wait_agent_default_timeout_ms
.unwrap_or(DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS);
(
min_wait_timeout_ms,
max_wait_timeout_ms,
default_wait_timeout_ms,
)
} else {
(
MIN_WAIT_TIMEOUT_MS,
MAX_WAIT_TIMEOUT_MS,
DEFAULT_WAIT_TIMEOUT_MS,
)
};
build_tool_registry_builder( build_tool_registry_builder(
config, config,
ToolRegistryBuildParams { ToolRegistryBuildParams {
@@ -57,7 +73,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
wait_agent_timeouts: WaitAgentTimeoutOptions { wait_agent_timeouts: WaitAgentTimeoutOptions {
default_timeout_ms: default_wait_timeout_ms, default_timeout_ms: default_wait_timeout_ms,
min_timeout_ms: min_wait_timeout_ms, min_timeout_ms: min_wait_timeout_ms,
max_timeout_ms: MAX_WAIT_TIMEOUT_MS, max_timeout_ms: max_wait_timeout_ms,
}, },
}, },
) )
+8 -4
View File
@@ -743,11 +743,15 @@ async fn spawn_agent_description_uses_configured_usage_hint_text() {
} }
#[tokio::test] #[tokio::test]
async fn multi_agent_v2_wait_agent_schema_uses_configured_min_timeout() { async fn multi_agent_v2_wait_agent_schema_uses_configured_timeouts() {
let wait_agent_min_timeout_ms = Some(60_000); let wait_agent_min_timeout_ms = Some(20_000);
let wait_agent_max_timeout_ms = Some(120_000);
let wait_agent_default_timeout_ms = Some(60_000);
let tools_config = multi_agent_v2_tools_config() let tools_config = multi_agent_v2_tools_config()
.await .await
.with_wait_agent_min_timeout_ms(wait_agent_min_timeout_ms); .with_wait_agent_min_timeout_ms(wait_agent_min_timeout_ms)
.with_wait_agent_max_timeout_ms(wait_agent_max_timeout_ms)
.with_wait_agent_default_timeout_ms(wait_agent_default_timeout_ms);
let (tools, _) = build_specs( let (tools, _) = build_specs(
&tools_config, &tools_config,
/*mcp_tools*/ None, /*mcp_tools*/ None,
@@ -767,7 +771,7 @@ async fn multi_agent_v2_wait_agent_schema_uses_configured_min_timeout() {
assert_eq!( assert_eq!(
timeout_description, timeout_description,
Some("Optional timeout in milliseconds. Defaults to 60000, min 60000, max 3600000.") Some("Optional timeout in milliseconds. Defaults to 60000, min 20000, max 120000.")
); );
} }
+7 -1
View File
@@ -13,9 +13,15 @@ pub struct MultiAgentV2ConfigToml {
#[schemars(range(min = 1))] #[schemars(range(min = 1))]
pub max_concurrent_threads_per_session: Option<usize>, pub max_concurrent_threads_per_session: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1, max = 3600000))] #[schemars(range(min = 0, max = 3600000))]
pub min_wait_timeout_ms: Option<i64>, pub min_wait_timeout_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0, max = 3600000))]
pub max_wait_timeout_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0, max = 3600000))]
pub default_wait_timeout_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage_hint_enabled: Option<bool>, pub usage_hint_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub usage_hint_text: Option<String>, pub usage_hint_text: Option<String>,
+6
View File
@@ -482,6 +482,8 @@ fn multi_agent_v2_feature_config_deserializes_table() {
enabled = true enabled = true
max_concurrent_threads_per_session = 4 max_concurrent_threads_per_session = 4
min_wait_timeout_ms = 2500 min_wait_timeout_ms = 2500
max_wait_timeout_ms = 120000
default_wait_timeout_ms = 30000
usage_hint_enabled = false usage_hint_enabled = false
usage_hint_text = "Custom delegation guidance." usage_hint_text = "Custom delegation guidance."
root_agent_usage_hint_text = "Root guidance." root_agent_usage_hint_text = "Root guidance."
@@ -502,6 +504,8 @@ non_code_mode_only = true
enabled: Some(true), enabled: Some(true),
max_concurrent_threads_per_session: Some(4), max_concurrent_threads_per_session: Some(4),
min_wait_timeout_ms: Some(2500), min_wait_timeout_ms: Some(2500),
max_wait_timeout_ms: Some(120000),
default_wait_timeout_ms: Some(30000),
usage_hint_enabled: Some(false), usage_hint_enabled: Some(false),
usage_hint_text: Some("Custom delegation guidance.".to_string()), usage_hint_text: Some("Custom delegation guidance.".to_string()),
root_agent_usage_hint_text: Some("Root guidance.".to_string()), root_agent_usage_hint_text: Some("Root guidance.".to_string()),
@@ -538,6 +542,8 @@ usage_hint_enabled = false
enabled: None, enabled: None,
max_concurrent_threads_per_session: None, max_concurrent_threads_per_session: None,
min_wait_timeout_ms: None, min_wait_timeout_ms: None,
max_wait_timeout_ms: None,
default_wait_timeout_ms: None,
usage_hint_enabled: Some(false), usage_hint_enabled: Some(false),
usage_hint_text: None, usage_hint_text: None,
root_agent_usage_hint_text: None, root_agent_usage_hint_text: None,
+20
View File
@@ -123,6 +123,8 @@ pub struct ToolsConfig {
pub spawn_agent_usage_hint_text: Option<String>, pub spawn_agent_usage_hint_text: Option<String>,
pub max_concurrent_threads_per_session: Option<usize>, pub max_concurrent_threads_per_session: Option<usize>,
pub wait_agent_min_timeout_ms: Option<i64>, pub wait_agent_min_timeout_ms: Option<i64>,
pub wait_agent_max_timeout_ms: Option<i64>,
pub wait_agent_default_timeout_ms: Option<i64>,
pub request_user_input_available_modes: Vec<ModeKind>, pub request_user_input_available_modes: Vec<ModeKind>,
pub experimental_supported_tools: Vec<String>, pub experimental_supported_tools: Vec<String>,
pub agent_jobs_tools: bool, pub agent_jobs_tools: bool,
@@ -264,6 +266,8 @@ impl ToolsConfig {
spawn_agent_usage_hint_text: None, spawn_agent_usage_hint_text: None,
max_concurrent_threads_per_session: None, max_concurrent_threads_per_session: None,
wait_agent_min_timeout_ms: None, wait_agent_min_timeout_ms: None,
wait_agent_max_timeout_ms: None,
wait_agent_default_timeout_ms: None,
request_user_input_available_modes: request_user_input_available_modes(features), request_user_input_available_modes: request_user_input_available_modes(features),
experimental_supported_tools: model_info.experimental_supported_tools.clone(), experimental_supported_tools: model_info.experimental_supported_tools.clone(),
agent_jobs_tools: include_agent_jobs, agent_jobs_tools: include_agent_jobs,
@@ -346,6 +350,22 @@ impl ToolsConfig {
self self
} }
pub fn with_wait_agent_max_timeout_ms(
mut self,
wait_agent_max_timeout_ms: Option<i64>,
) -> Self {
self.wait_agent_max_timeout_ms = wait_agent_max_timeout_ms;
self
}
pub fn with_wait_agent_default_timeout_ms(
mut self,
wait_agent_default_timeout_ms: Option<i64>,
) -> Self {
self.wait_agent_default_timeout_ms = wait_agent_default_timeout_ms;
self
}
pub fn with_allow_login_shell(mut self, allow_login_shell: bool) -> Self { pub fn with_allow_login_shell(mut self, allow_login_shell: bool) -> Self {
self.allow_login_shell = allow_login_shell; self.allow_login_shell = allow_login_shell;
self self