mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
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:
1 parent
8ae0c837f0
commit
7c57a59f51
14 files changed
+541
-53
No files matched your search
@@ -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": {
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(_),
|
||||||
..
|
..
|
||||||
|
|||||||
@@ -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,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user