mirror of
https://github.com/openai/codex.git
synced 2026-04-21 05:04:49 +00:00
Compare commits
4 Commits
codex-debu
...
rust-v0.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
295d79043c | ||
|
|
4c07dd4d25 | ||
|
|
2250fdd54a | ||
|
|
34fd336e7b |
@@ -91,7 +91,7 @@ members = [
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.0"
|
||||
version = "0.119.0-alpha.20"
|
||||
# Track the edition for all workspace crates in one place. Individual
|
||||
# crates can still override this value, but keeping it here means new
|
||||
# crates created with `cargo new -w ...` automatically inherit the 2024
|
||||
|
||||
@@ -25,6 +25,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
|
||||
if feature.id == codex_features::Feature::Artifact {
|
||||
continue;
|
||||
}
|
||||
if feature.id == codex_features::Feature::MultiAgentV2 {
|
||||
validation.properties.insert(
|
||||
feature.key.to_string(),
|
||||
schema_gen.subschema_for::<codex_features::FeatureToml<
|
||||
codex_features::MultiAgentV2ConfigToml,
|
||||
>>(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
validation
|
||||
.properties
|
||||
.insert(feature.key.to_string(), schema_gen.subschema_for::<bool>());
|
||||
|
||||
@@ -362,9 +362,6 @@
|
||||
"connectors": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"debug_hide_spawn_agent_metadata": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"default_mode_request_user_input": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -426,7 +423,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"multi_agent_v2": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
||||
},
|
||||
"personality": {
|
||||
"type": "boolean"
|
||||
@@ -621,6 +618,16 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FeatureToml_for_MultiAgentV2ConfigToml": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/MultiAgentV2ConfigToml"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FeedbackConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -980,6 +987,24 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MultiAgentV2ConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hide_spawn_agent_metadata": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"usage_hint_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"usage_hint_text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkDomainPermissionToml": {
|
||||
"enum": [
|
||||
"allow",
|
||||
@@ -2075,9 +2100,6 @@
|
||||
"connectors": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"debug_hide_spawn_agent_metadata": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"default_mode_request_user_input": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -2139,7 +2161,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"multi_agent_v2": {
|
||||
"type": "boolean"
|
||||
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
|
||||
},
|
||||
"personality": {
|
||||
"type": "boolean"
|
||||
|
||||
@@ -964,6 +964,9 @@ impl TurnContext {
|
||||
.with_web_search_config(self.tools_config.web_search_config.clone())
|
||||
.with_allow_login_shell(self.tools_config.allow_login_shell)
|
||||
.with_has_environment(self.tools_config.has_environment)
|
||||
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
));
|
||||
@@ -1488,6 +1491,9 @@ impl Session {
|
||||
.with_web_search_config(per_turn_config.web_search_config.clone())
|
||||
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
|
||||
.with_has_environment(environment.is_some())
|
||||
.with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&per_turn_config.agent_roles,
|
||||
));
|
||||
@@ -5676,6 +5682,9 @@ async fn spawn_review_thread(
|
||||
.with_web_search_config(/*web_search_config*/ None)
|
||||
.with_allow_login_shell(config.permissions.allow_login_shell)
|
||||
.with_has_environment(parent_turn_context.environment.is_some())
|
||||
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
|
||||
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
|
||||
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
|
||||
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
|
||||
&config.agent_roles,
|
||||
));
|
||||
|
||||
@@ -1746,7 +1746,7 @@ fn feature_table_overrides_legacy_flags() -> std::io::Result<()> {
|
||||
let mut entries = BTreeMap::new();
|
||||
entries.insert("apply_patch_freeform".to_string(), false);
|
||||
let cfg = ConfigToml {
|
||||
features: Some(FeaturesToml { entries }),
|
||||
features: Some(FeaturesToml::from(entries)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -1794,7 +1794,7 @@ fn responses_websocket_features_do_not_change_wire_api() -> std::io::Result<()>
|
||||
let mut entries = BTreeMap::new();
|
||||
entries.insert(feature_key.to_string(), true);
|
||||
let cfg = ConfigToml {
|
||||
features: Some(FeaturesToml { entries }),
|
||||
features: Some(FeaturesToml::from(entries)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -3005,14 +3005,14 @@ async fn set_feature_enabled_updates_profile() -> anyhow::Result<()> {
|
||||
profile
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.entries.get("guardian_approval")),
|
||||
Some(&true),
|
||||
.and_then(|features| features.entries().get("guardian_approval").copied()),
|
||||
Some(true),
|
||||
);
|
||||
assert_eq!(
|
||||
parsed
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.entries.get("guardian_approval")),
|
||||
.and_then(|features| features.entries().get("guardian_approval").copied()),
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -3047,14 +3047,14 @@ async fn set_feature_enabled_persists_default_false_feature_disable_in_profile()
|
||||
profile
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.entries.get("guardian_approval")),
|
||||
Some(&false),
|
||||
.and_then(|features| features.entries().get("guardian_approval").copied()),
|
||||
Some(false),
|
||||
);
|
||||
assert_eq!(
|
||||
parsed
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.entries.get("guardian_approval")),
|
||||
.and_then(|features| features.entries().get("guardian_approval").copied()),
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -3087,15 +3087,15 @@ async fn set_feature_enabled_profile_disable_overrides_root_enable() -> anyhow::
|
||||
parsed
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.entries.get("guardian_approval")),
|
||||
Some(&true),
|
||||
.and_then(|features| features.entries().get("guardian_approval").copied()),
|
||||
Some(true),
|
||||
);
|
||||
assert_eq!(
|
||||
profile
|
||||
.features
|
||||
.as_ref()
|
||||
.and_then(|features| features.entries.get("guardian_approval")),
|
||||
Some(&false),
|
||||
.and_then(|features| features.entries().get("guardian_approval").copied()),
|
||||
Some(false),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -4520,6 +4520,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
use_experimental_unified_exec_tool: !cfg!(windows),
|
||||
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
|
||||
ghost_snapshot: GhostSnapshotConfig::default(),
|
||||
multi_agent_v2: MultiAgentV2Config::default(),
|
||||
features: Features::with_defaults().into(),
|
||||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("o3".to_string()),
|
||||
@@ -4665,6 +4666,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
use_experimental_unified_exec_tool: !cfg!(windows),
|
||||
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
|
||||
ghost_snapshot: GhostSnapshotConfig::default(),
|
||||
multi_agent_v2: MultiAgentV2Config::default(),
|
||||
features: Features::with_defaults().into(),
|
||||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("gpt3".to_string()),
|
||||
@@ -4808,6 +4810,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
use_experimental_unified_exec_tool: !cfg!(windows),
|
||||
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
|
||||
ghost_snapshot: GhostSnapshotConfig::default(),
|
||||
multi_agent_v2: MultiAgentV2Config::default(),
|
||||
features: Features::with_defaults().into(),
|
||||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("zdr".to_string()),
|
||||
@@ -4937,6 +4940,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
use_experimental_unified_exec_tool: !cfg!(windows),
|
||||
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
|
||||
ghost_snapshot: GhostSnapshotConfig::default(),
|
||||
multi_agent_v2: MultiAgentV2Config::default(),
|
||||
features: Features::with_defaults().into(),
|
||||
suppress_unstable_features_warning: false,
|
||||
active_profile: Some("gpt5".to_string()),
|
||||
@@ -6096,6 +6100,71 @@ smart_approvals = true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multi_agent_v2_config_from_feature_table() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"[features.multi_agent_v2]
|
||||
enabled = true
|
||||
usage_hint_enabled = false
|
||||
usage_hint_text = "Custom delegation guidance."
|
||||
hide_spawn_agent_metadata = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
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!(config.features.enabled(Feature::MultiAgentV2));
|
||||
assert!(!config.multi_agent_v2.usage_hint_enabled);
|
||||
assert_eq!(
|
||||
config.multi_agent_v2.usage_hint_text.as_deref(),
|
||||
Some("Custom delegation guidance.")
|
||||
);
|
||||
assert!(config.multi_agent_v2.hide_spawn_agent_metadata);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn profile_multi_agent_v2_config_overrides_base() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
std::fs::write(
|
||||
codex_home.path().join(CONFIG_TOML_FILE),
|
||||
r#"profile = "no_hint"
|
||||
|
||||
[features.multi_agent_v2]
|
||||
usage_hint_enabled = true
|
||||
usage_hint_text = "base hint"
|
||||
hide_spawn_agent_metadata = true
|
||||
|
||||
[profiles.no_hint.features.multi_agent_v2]
|
||||
usage_hint_enabled = false
|
||||
usage_hint_text = "profile hint"
|
||||
hide_spawn_agent_metadata = false
|
||||
"#,
|
||||
)?;
|
||||
|
||||
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!(!config.multi_agent_v2.usage_hint_enabled);
|
||||
assert_eq!(
|
||||
config.multi_agent_v2.usage_hint_text.as_deref(),
|
||||
Some("profile hint")
|
||||
);
|
||||
assert!(!config.multi_agent_v2.hide_spawn_agent_metadata);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn feature_requirements_normalize_runtime_feature_mutations() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -202,9 +202,9 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
|
||||
let mut explicit_settings = Vec::new();
|
||||
|
||||
if let Some(features) = cfg.features.as_ref() {
|
||||
for (key, enabled) in &features.entries {
|
||||
if let Some(feature) = feature_for_key(key) {
|
||||
explicit_settings.push((format!("features.{key}"), feature, *enabled));
|
||||
for (key, enabled) in features.entries() {
|
||||
if let Some(feature) = feature_for_key(&key) {
|
||||
explicit_settings.push((format!("features.{key}"), feature, enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,12 +224,12 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
|
||||
}
|
||||
for (profile_name, profile) in &cfg.profiles {
|
||||
if let Some(features) = profile.features.as_ref() {
|
||||
for (key, enabled) in &features.entries {
|
||||
if let Some(feature) = feature_for_key(key) {
|
||||
for (key, enabled) in features.entries() {
|
||||
if let Some(feature) = feature_for_key(&key) {
|
||||
explicit_settings.push((
|
||||
format!("profiles.{profile_name}.features.{key}"),
|
||||
feature,
|
||||
*enabled,
|
||||
enabled,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,10 @@ use codex_config::types::WindowsSandboxModeToml;
|
||||
use codex_features::Feature;
|
||||
use codex_features::FeatureConfigSource;
|
||||
use codex_features::FeatureOverrides;
|
||||
use codex_features::FeatureToml;
|
||||
use codex_features::Features;
|
||||
use codex_features::FeaturesToml;
|
||||
use codex_features::MultiAgentV2ConfigToml;
|
||||
use codex_login::AuthManagerConfig;
|
||||
use codex_mcp::McpConfig;
|
||||
use codex_model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
|
||||
@@ -520,6 +523,9 @@ pub struct Config {
|
||||
/// Settings for ghost snapshots (used for undo).
|
||||
pub ghost_snapshot: GhostSnapshotConfig,
|
||||
|
||||
/// Settings specific to the task-path-based multi-agent tool surface.
|
||||
pub multi_agent_v2: MultiAgentV2Config,
|
||||
|
||||
/// Centralized feature flags; source of truth for feature gating.
|
||||
pub features: ManagedFeatures,
|
||||
|
||||
@@ -564,6 +570,23 @@ pub struct Config {
|
||||
pub otel: codex_config::types::OtelConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MultiAgentV2Config {
|
||||
pub usage_hint_enabled: bool,
|
||||
pub usage_hint_text: Option<String>,
|
||||
pub hide_spawn_agent_metadata: bool,
|
||||
}
|
||||
|
||||
impl Default for MultiAgentV2Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
usage_hint_enabled: true,
|
||||
usage_hint_text: None,
|
||||
hide_spawn_agent_metadata: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthManagerConfig for Config {
|
||||
fn codex_home(&self) -> PathBuf {
|
||||
self.codex_home.clone()
|
||||
@@ -1279,6 +1302,42 @@ fn resolve_web_search_config(
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_multi_agent_v2_config(
|
||||
config_toml: &ConfigToml,
|
||||
config_profile: &ConfigProfile,
|
||||
) -> MultiAgentV2Config {
|
||||
let base = multi_agent_v2_toml_config(config_toml.features.as_ref());
|
||||
let profile = multi_agent_v2_toml_config(config_profile.features.as_ref());
|
||||
let default = MultiAgentV2Config::default();
|
||||
|
||||
let usage_hint_enabled = profile
|
||||
.and_then(|config| config.usage_hint_enabled)
|
||||
.or_else(|| base.and_then(|config| config.usage_hint_enabled))
|
||||
.unwrap_or(default.usage_hint_enabled);
|
||||
let usage_hint_text = profile
|
||||
.and_then(|config| config.usage_hint_text.as_ref())
|
||||
.or_else(|| base.and_then(|config| config.usage_hint_text.as_ref()))
|
||||
.cloned()
|
||||
.or(default.usage_hint_text);
|
||||
let hide_spawn_agent_metadata = profile
|
||||
.and_then(|config| config.hide_spawn_agent_metadata)
|
||||
.or_else(|| base.and_then(|config| config.hide_spawn_agent_metadata))
|
||||
.unwrap_or(default.hide_spawn_agent_metadata);
|
||||
|
||||
MultiAgentV2Config {
|
||||
usage_hint_enabled,
|
||||
usage_hint_text,
|
||||
hide_spawn_agent_metadata,
|
||||
}
|
||||
}
|
||||
|
||||
fn multi_agent_v2_toml_config(features: Option<&FeaturesToml>) -> Option<&MultiAgentV2ConfigToml> {
|
||||
match features?.multi_agent_v2.as_ref()? {
|
||||
FeatureToml::Enabled(_) => None,
|
||||
FeatureToml::Config(config) => Some(config),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_web_search_mode_for_turn(
|
||||
web_search_mode: &Constrained<WebSearchMode>,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
@@ -1607,6 +1666,7 @@ impl Config {
|
||||
let web_search_mode = resolve_web_search_mode(&cfg, &config_profile, &features)
|
||||
.unwrap_or(WebSearchMode::Cached);
|
||||
let web_search_config = resolve_web_search_config(&cfg, &config_profile);
|
||||
let multi_agent_v2 = resolve_multi_agent_v2_config(&cfg, &config_profile);
|
||||
|
||||
let agent_roles =
|
||||
agent_roles::load_agent_roles(&cfg, &config_layer_stack, &mut startup_warnings)?;
|
||||
@@ -2040,6 +2100,7 @@ impl Config {
|
||||
use_experimental_unified_exec_tool,
|
||||
background_terminal_max_timeout,
|
||||
ghost_snapshot,
|
||||
multi_agent_v2,
|
||||
features,
|
||||
suppress_unstable_features_warning: cfg
|
||||
.suppress_unstable_features_warning
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::agent::control::render_input_preview;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::role::apply_role_to_config;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::models::DeveloperInstructions;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
@@ -207,10 +206,7 @@ impl ToolHandler for Handler {
|
||||
)
|
||||
})?;
|
||||
|
||||
let hide_agent_metadata = turn
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::DebugHideSpawnAgentMetadata);
|
||||
let hide_agent_metadata = turn.config.multi_agent_v2.hide_spawn_agent_metadata;
|
||||
if hide_agent_metadata {
|
||||
Ok(SpawnAgentResult::HiddenMetadata { task_name })
|
||||
} else {
|
||||
|
||||
@@ -32,6 +32,7 @@ use codex_tools::ZshForkConfig;
|
||||
use codex_tools::mcp_call_tool_result_output_schema;
|
||||
use codex_tools::mcp_tool_to_deferred_responses_api_tool;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::assert_regex_match;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
@@ -158,6 +159,39 @@ fn find_tool<'a>(tools: &'a [ConfiguredToolSpec], expected_name: &str) -> &'a Co
|
||||
.unwrap_or_else(|| panic!("expected tool {expected_name}"))
|
||||
}
|
||||
|
||||
fn multi_agent_v2_tools_config() -> ToolsConfig {
|
||||
let config = test_config();
|
||||
let model_info = construct_model_info_offline("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Collab);
|
||||
features.enable(Feature::MultiAgentV2);
|
||||
let available_models = Vec::new();
|
||||
ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
}
|
||||
|
||||
fn multi_agent_v2_spawn_agent_description(tools_config: &ToolsConfig) -> String {
|
||||
let (tools, _) = build_specs(
|
||||
tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
&[],
|
||||
)
|
||||
.build();
|
||||
let spawn_agent = find_tool(&tools, "spawn_agent");
|
||||
let ToolSpec::Function(ResponsesApiTool { description, .. }) = &spawn_agent.spec else {
|
||||
panic!("spawn_agent should be a function tool");
|
||||
};
|
||||
description.clone()
|
||||
}
|
||||
|
||||
fn model_info_from_models_json(slug: &str) -> ModelInfo {
|
||||
let config = test_config();
|
||||
let response = bundled_models_response()
|
||||
@@ -599,6 +633,44 @@ fn shell_zsh_fork_prefers_shell_command_over_unified_exec() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_agent_description_omits_usage_hint_when_disabled() {
|
||||
let tools_config = multi_agent_v2_tools_config()
|
||||
.with_spawn_agent_usage_hint(/*spawn_agent_usage_hint*/ false);
|
||||
let description = multi_agent_v2_spawn_agent_description(&tools_config);
|
||||
|
||||
assert_regex_match(
|
||||
r#"(?sx)
|
||||
^\s*
|
||||
No\ picker-visible\ models\ are\ currently\ loaded\.
|
||||
\s+Spawn\ a\ sub-agent\ for\ a\ well-scoped\ task\.
|
||||
\s+Returns\ the\ canonical\ task\ name\ for\ the\ spawned\ agent,\ plus\ the\ user-facing\ nickname\ when\ available\.
|
||||
\s*$
|
||||
"#,
|
||||
&description,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_agent_description_uses_configured_usage_hint_text() {
|
||||
let tools_config = multi_agent_v2_tools_config().with_spawn_agent_usage_hint_text(Some(
|
||||
/*spawn_agent_usage_hint_text*/ "Custom delegation guidance only.".to_string(),
|
||||
));
|
||||
let description = multi_agent_v2_spawn_agent_description(&tools_config);
|
||||
|
||||
assert_regex_match(
|
||||
r#"(?sx)
|
||||
^\s*
|
||||
No\ picker-visible\ models\ are\ currently\ loaded\.
|
||||
\s+Spawn\ a\ sub-agent\ for\ a\ well-scoped\ task\.
|
||||
\s+Returns\ the\ canonical\ task\ name\ for\ the\ spawned\ agent,\ plus\ the\ user-facing\ nickname\ when\ available\.
|
||||
\s+Custom\ delegation\ guidance\ only\.
|
||||
\s*$
|
||||
"#,
|
||||
&description,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_suggest_requires_apps_and_plugins_features() {
|
||||
let model_info = search_capable_model_info();
|
||||
|
||||
@@ -89,7 +89,7 @@ pub fn resolve_windows_sandbox_private_desktop(cfg: &ConfigToml, profile: &Confi
|
||||
}
|
||||
|
||||
fn legacy_windows_sandbox_keys_present(features: Option<&FeaturesToml>) -> bool {
|
||||
let Some(entries) = features.map(|features| &features.entries) else {
|
||||
let Some(entries) = features.map(FeaturesToml::entries) else {
|
||||
return false;
|
||||
};
|
||||
entries.contains_key(Feature::WindowsSandboxElevated.key())
|
||||
@@ -100,8 +100,8 @@ fn legacy_windows_sandbox_keys_present(features: Option<&FeaturesToml>) -> bool
|
||||
pub fn legacy_windows_sandbox_mode(
|
||||
features: Option<&FeaturesToml>,
|
||||
) -> Option<WindowsSandboxModeToml> {
|
||||
let entries = features.map(|features| &features.entries)?;
|
||||
legacy_windows_sandbox_mode_from_entries(entries)
|
||||
let entries = features.map(FeaturesToml::entries)?;
|
||||
legacy_windows_sandbox_mode_from_entries(&entries)
|
||||
}
|
||||
|
||||
pub fn legacy_windows_sandbox_mode_from_entries(
|
||||
|
||||
@@ -109,7 +109,7 @@ fn resolve_windows_sandbox_mode_falls_back_to_legacy_keys() {
|
||||
/*value*/ true,
|
||||
);
|
||||
let cfg = ConfigToml {
|
||||
features: Some(FeaturesToml { entries }),
|
||||
features: Some(FeaturesToml::from(entries)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -127,9 +127,7 @@ fn resolve_windows_sandbox_mode_profile_legacy_false_blocks_top_level_legacy_tru
|
||||
/*value*/ false,
|
||||
);
|
||||
let profile = ConfigProfile {
|
||||
features: Some(FeaturesToml {
|
||||
entries: profile_entries,
|
||||
}),
|
||||
features: Some(FeaturesToml::from(profile_entries)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -139,9 +137,7 @@ fn resolve_windows_sandbox_mode_profile_legacy_false_blocks_top_level_legacy_tru
|
||||
/*value*/ true,
|
||||
);
|
||||
let cfg = ConfigToml {
|
||||
features: Some(FeaturesToml {
|
||||
entries: cfg_entries,
|
||||
}),
|
||||
features: Some(FeaturesToml::from(cfg_entries)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
23
codex-rs/features/src/feature_configs.rs
Normal file
23
codex-rs/features/src/feature_configs.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::FeatureConfig;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MultiAgentV2ConfigToml {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub usage_hint_enabled: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub usage_hint_text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hide_spawn_agent_metadata: Option<bool>,
|
||||
}
|
||||
|
||||
impl FeatureConfig for MultiAgentV2ConfigToml {
|
||||
fn enabled(&self) -> Option<bool> {
|
||||
self.enabled
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use toml::Table;
|
||||
|
||||
mod feature_configs;
|
||||
mod legacy;
|
||||
pub use feature_configs::MultiAgentV2ConfigToml;
|
||||
use legacy::LegacyFeatureToggles;
|
||||
pub use legacy::legacy_feature_keys;
|
||||
|
||||
@@ -138,8 +140,6 @@ pub enum Feature {
|
||||
Collab,
|
||||
/// Enable task-path-based multi-agent routing.
|
||||
MultiAgentV2,
|
||||
/// Hide spawn_agent agent/model override fields from the model-visible tool schema.
|
||||
DebugHideSpawnAgentMetadata,
|
||||
/// Enable CSV-backed agent job tools.
|
||||
SpawnCsv,
|
||||
/// Enable apps.
|
||||
@@ -398,7 +398,7 @@ impl Features {
|
||||
.apply(&mut features);
|
||||
|
||||
if let Some(feature_entries) = source.features {
|
||||
features.apply_map(&feature_entries.entries);
|
||||
features.apply_toml(feature_entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,8 +492,61 @@ pub fn is_known_feature_key(key: &str) -> bool {
|
||||
/// Deserializable features table for TOML.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
|
||||
pub struct FeaturesToml {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub multi_agent_v2: Option<FeatureToml<MultiAgentV2ConfigToml>>,
|
||||
/// Boolean feature toggles keyed by canonical or legacy feature name.
|
||||
#[serde(flatten)]
|
||||
pub entries: BTreeMap<String, bool>,
|
||||
entries: BTreeMap<String, bool>,
|
||||
}
|
||||
|
||||
impl Features {
|
||||
fn apply_toml(&mut self, features: &FeaturesToml) {
|
||||
let entries = features.entries();
|
||||
self.apply_map(&entries);
|
||||
}
|
||||
}
|
||||
|
||||
impl FeaturesToml {
|
||||
pub fn entries(&self) -> BTreeMap<String, bool> {
|
||||
let mut entries = self.entries.clone();
|
||||
if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) {
|
||||
entries.insert(Feature::MultiAgentV2.key().to_string(), enabled);
|
||||
}
|
||||
entries
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<String, bool>> for FeaturesToml {
|
||||
fn from(entries: BTreeMap<String, bool>) -> Self {
|
||||
Self {
|
||||
entries,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To be used for features that need more configuration than just enabled/disabled and
|
||||
// require a custom config struct under `[features]`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum FeatureToml<T> {
|
||||
Enabled(bool),
|
||||
Config(T),
|
||||
}
|
||||
|
||||
impl<T: FeatureConfig> FeatureToml<T> {
|
||||
pub fn enabled(&self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Enabled(enabled) => Some(*enabled),
|
||||
Self::Config(config) => config.enabled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A trait to be implemented by custom feature config structs when defining a feature that needs more configuration than
|
||||
// just enabled/disabled.
|
||||
pub trait FeatureConfig {
|
||||
fn enabled(&self) -> Option<bool>;
|
||||
}
|
||||
|
||||
/// Single, easy-to-read registry of all feature definitions.
|
||||
@@ -708,12 +761,6 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::DebugHideSpawnAgentMetadata,
|
||||
key: "debug_hide_spawn_agent_metadata",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::SpawnCsv,
|
||||
key: "enable_fanout",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::Feature;
|
||||
use crate::FeatureConfigSource;
|
||||
use crate::FeatureOverrides;
|
||||
use crate::FeatureToml;
|
||||
use crate::Features;
|
||||
use crate::FeaturesToml;
|
||||
use crate::Stage;
|
||||
@@ -211,12 +212,14 @@ fn from_sources_applies_base_profile_and_overrides() {
|
||||
base_entries.insert("plugins".to_string(), true);
|
||||
let base_features = FeaturesToml {
|
||||
entries: base_entries,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut profile_entries = BTreeMap::new();
|
||||
profile_entries.insert("code_mode_only".to_string(), true);
|
||||
let profile_features = FeaturesToml {
|
||||
entries: profile_entries,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let features = Features::from_sources(
|
||||
@@ -242,6 +245,81 @@ fn from_sources_applies_base_profile_and_overrides() {
|
||||
assert_eq!(features.enabled(Feature::WebSearchRequest), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_agent_v2_feature_config_deserializes_boolean_toggle() {
|
||||
let features: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
multi_agent_v2 = true
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
||||
);
|
||||
assert_eq!(features.multi_agent_v2, Some(FeatureToml::Enabled(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_agent_v2_feature_config_deserializes_table() {
|
||||
let features: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[multi_agent_v2]
|
||||
enabled = true
|
||||
usage_hint_enabled = false
|
||||
usage_hint_text = "Custom delegation guidance."
|
||||
hide_spawn_agent_metadata = true
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
features.entries(),
|
||||
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
||||
);
|
||||
assert_eq!(
|
||||
features.multi_agent_v2,
|
||||
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: Some(true),
|
||||
usage_hint_enabled: Some(false),
|
||||
usage_hint_text: Some("Custom delegation guidance.".to_string()),
|
||||
hide_spawn_agent_metadata: Some(true),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_agent_v2_feature_config_usage_hint_enabled_does_not_enable_feature() {
|
||||
let features_toml: FeaturesToml = toml::from_str(
|
||||
r#"
|
||||
[multi_agent_v2]
|
||||
usage_hint_enabled = false
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
let features = Features::from_sources(
|
||||
FeatureConfigSource {
|
||||
features: Some(&features_toml),
|
||||
..Default::default()
|
||||
},
|
||||
FeatureConfigSource::default(),
|
||||
FeatureOverrides::default(),
|
||||
);
|
||||
|
||||
assert_eq!(features.enabled(Feature::MultiAgentV2), false);
|
||||
assert_eq!(features_toml.entries(), BTreeMap::new());
|
||||
assert_eq!(
|
||||
features_toml.multi_agent_v2,
|
||||
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||
enabled: None,
|
||||
usage_hint_enabled: Some(false),
|
||||
usage_hint_text: None,
|
||||
hide_spawn_agent_metadata: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unstable_warning_event_only_mentions_enabled_under_development_features() {
|
||||
let mut configured_features = Table::new();
|
||||
|
||||
@@ -11,6 +11,8 @@ pub struct SpawnAgentToolOptions<'a> {
|
||||
pub available_models: &'a [ModelPreset],
|
||||
pub agent_type_description: String,
|
||||
pub hide_agent_type_model_reasoning: bool,
|
||||
pub include_usage_hint: bool,
|
||||
pub usage_hint_text: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -35,6 +37,8 @@ pub fn create_spawn_agent_tool_v1(options: SpawnAgentToolOptions<'_>) -> ToolSpe
|
||||
description: spawn_agent_tool_description(
|
||||
available_models_description.as_deref(),
|
||||
return_value_description,
|
||||
options.include_usage_hint,
|
||||
options.usage_hint_text,
|
||||
),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
@@ -68,6 +72,8 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions<'_>) -> ToolSpe
|
||||
description: spawn_agent_tool_description(
|
||||
available_models_description.as_deref(),
|
||||
return_value_description,
|
||||
options.include_usage_hint,
|
||||
options.usage_hint_text,
|
||||
),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
@@ -580,20 +586,40 @@ fn hide_spawn_agent_metadata_options(properties: &mut BTreeMap<String, JsonSchem
|
||||
fn spawn_agent_tool_description(
|
||||
available_models_description: Option<&str>,
|
||||
return_value_description: &str,
|
||||
include_usage_hint: bool,
|
||||
usage_hint_text: Option<String>,
|
||||
) -> String {
|
||||
let agent_role_guidance = available_models_description
|
||||
.map(|description| {
|
||||
format!(
|
||||
"Agent-role guidance below only helps choose which agent to use after spawning is already authorized; it never authorizes spawning by itself.\n{description}"
|
||||
)
|
||||
let agent_role_guidance = available_models_description.unwrap_or_default();
|
||||
|
||||
let tool_description = format!(
|
||||
r#"
|
||||
{agent_role_guidance}
|
||||
Spawn a sub-agent for a well-scoped task. {return_value_description}"#
|
||||
);
|
||||
|
||||
if !include_usage_hint {
|
||||
return tool_description;
|
||||
}
|
||||
if let Some(usage_hint_text) = usage_hint_text {
|
||||
return format!(
|
||||
r#"
|
||||
{tool_description}
|
||||
{usage_hint_text}"#
|
||||
);
|
||||
}
|
||||
let agent_role_usage_hint = available_models_description
|
||||
.map(|_| {
|
||||
"Agent-role guidance below only helps choose which agent to use after spawning is already authorized; it never authorizes spawning by itself."
|
||||
})
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
r#"
|
||||
Only use `spawn_agent` if and only if the user explicitly asks for sub-agents, delegation, or parallel agent work.
|
||||
Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn.
|
||||
{agent_role_guidance}
|
||||
Spawn a sub-agent for a well-scoped task. {return_value_description} This spawn_agent tool provides you access to smaller but more efficient sub-agents. A mini model can solve many tasks faster than the main model. You should follow the rules and guidelines below to use this tool.
|
||||
{tool_description}
|
||||
This spawn_agent tool provides you access to smaller but more efficient sub-agents. A mini model can solve many tasks faster than the main model. You should follow the rules and guidelines below to use this tool.
|
||||
|
||||
Only use `spawn_agent` if and only if the user explicitly asks for sub-agents, delegation, or parallel agent work.
|
||||
Requests for depth, thoroughness, research, investigation, or detailed codebase analysis do not count as permission to spawn.
|
||||
{agent_role_usage_hint}
|
||||
|
||||
### When to delegate vs. do the subtask yourself
|
||||
- First, quickly analyze the overall user task and form a succinct high-level plan. Identify which tasks are immediate blockers on the critical path, and which tasks are sidecar tasks that are needed but can run in parallel without blocking the next local step. As part of that plan, explicitly decide what immediate task you should do locally right now. Do this planning step before delegating to agents so you do not hand off the immediate blocking task to a submodel and then waste time waiting on it.
|
||||
|
||||
@@ -38,6 +38,8 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
|
||||
],
|
||||
agent_type_description: "role help".to_string(),
|
||||
hide_agent_type_model_reasoning: false,
|
||||
include_usage_hint: true,
|
||||
usage_hint_text: None,
|
||||
});
|
||||
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
@@ -84,6 +86,8 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() {
|
||||
available_models: &[],
|
||||
agent_type_description: "role help".to_string(),
|
||||
hide_agent_type_model_reasoning: false,
|
||||
include_usage_hint: true,
|
||||
usage_hint_text: None,
|
||||
});
|
||||
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = tool else {
|
||||
|
||||
@@ -105,6 +105,8 @@ pub struct ToolsConfig {
|
||||
pub collab_tools: bool,
|
||||
pub multi_agent_v2: bool,
|
||||
pub hide_spawn_agent_metadata: bool,
|
||||
pub spawn_agent_usage_hint: bool,
|
||||
pub spawn_agent_usage_hint_text: Option<String>,
|
||||
pub default_mode_request_user_input: bool,
|
||||
pub experimental_supported_tools: Vec<String>,
|
||||
pub agent_jobs_tools: bool,
|
||||
@@ -141,7 +143,6 @@ impl ToolsConfig {
|
||||
include_js_repl && features.enabled(Feature::JsReplToolsOnly);
|
||||
let include_collab_tools = features.enabled(Feature::Collab);
|
||||
let include_multi_agent_v2 = features.enabled(Feature::MultiAgentV2);
|
||||
let hide_spawn_agent_metadata = features.enabled(Feature::DebugHideSpawnAgentMetadata);
|
||||
let include_agent_jobs = features.enabled(Feature::SpawnCsv);
|
||||
let include_default_mode_request_user_input =
|
||||
features.enabled(Feature::DefaultModeRequestUserInput);
|
||||
@@ -219,7 +220,9 @@ impl ToolsConfig {
|
||||
can_request_original_image_detail: include_original_image_detail,
|
||||
collab_tools: include_collab_tools,
|
||||
multi_agent_v2: include_multi_agent_v2,
|
||||
hide_spawn_agent_metadata,
|
||||
hide_spawn_agent_metadata: false,
|
||||
spawn_agent_usage_hint: true,
|
||||
spawn_agent_usage_hint_text: None,
|
||||
default_mode_request_user_input: include_default_mode_request_user_input,
|
||||
experimental_supported_tools: model_info.experimental_supported_tools.clone(),
|
||||
agent_jobs_tools: include_agent_jobs,
|
||||
@@ -233,6 +236,24 @@ impl ToolsConfig {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_spawn_agent_usage_hint(mut self, spawn_agent_usage_hint: bool) -> Self {
|
||||
self.spawn_agent_usage_hint = spawn_agent_usage_hint;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_spawn_agent_usage_hint_text(
|
||||
mut self,
|
||||
spawn_agent_usage_hint_text: Option<String>,
|
||||
) -> Self {
|
||||
self.spawn_agent_usage_hint_text = spawn_agent_usage_hint_text;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hide_spawn_agent_metadata(mut self, hide_spawn_agent_metadata: bool) -> Self {
|
||||
self.hide_spawn_agent_metadata = hide_spawn_agent_metadata;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_allow_login_shell(mut self, allow_login_shell: bool) -> Self {
|
||||
self.allow_login_shell = allow_login_shell;
|
||||
self
|
||||
|
||||
@@ -376,6 +376,8 @@ pub fn build_tool_registry_plan(
|
||||
available_models: &config.available_models,
|
||||
agent_type_description,
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
include_usage_hint: config.spawn_agent_usage_hint,
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
@@ -419,6 +421,8 @@ pub fn build_tool_registry_plan(
|
||||
available_models: &config.available_models,
|
||||
agent_type_description,
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
include_usage_hint: config.spawn_agent_usage_hint,
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
|
||||
@@ -1940,6 +1940,8 @@ fn spawn_agent_tool_options(config: &ToolsConfig) -> SpawnAgentToolOptions<'_> {
|
||||
available_models: &config.available_models,
|
||||
agent_type_description: agent_type_description(config, DEFAULT_AGENT_TYPE_DESCRIPTION),
|
||||
hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata,
|
||||
include_usage_hint: config.spawn_agent_usage_hint,
|
||||
usage_hint_text: config.spawn_agent_usage_hint_text.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user