mirror of
https://github.com/openai/codex.git
synced 2026-05-16 01:02:48 +00:00
feat: expose multi-agent v2 as model-only tools (#22514)
## Why `code_mode_only` filters code-mode nested tools out of the top-level tool list. For multi-agent v2, we need a rollout shape where the collaboration tools remain callable as normal model tools without also being embedded into the code-mode `exec` tool declaration. Related to this: https://openai-corpws.slack.com/archives/C0AQLHB4U75/p1778660267922549 ## What Changed - Adds `features.multi_agent_v2.non_code_mode_only`, including config resolution, profile override handling, and generated schema coverage. - Introduces `ToolExposure::DirectModelOnly` so a tool can be included in the initial model-visible list while staying out of the nested code-mode tool surface. - Applies that exposure to the multi-agent v2 tools when the new flag is set: `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `close_agent`, and `list_agents`. - Updates code-mode-only filtering so direct-model-only tools remain visible while ordinary nested code-mode tools are still hidden. ## Verification - Added config parsing/profile tests for `non_code_mode_only`. - Added tool spec coverage for the code-mode-only multi-agent v2 exposure behavior.
This commit is contained in:
@@ -1485,6 +1485,9 @@
|
||||
"minimum": 1.0,
|
||||
"type": "integer"
|
||||
},
|
||||
"non_code_mode_only": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"root_agent_usage_hint_text": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -9656,6 +9656,7 @@ usage_hint_text = "Custom delegation guidance."
|
||||
root_agent_usage_hint_text = "Root guidance."
|
||||
subagent_usage_hint_text = "Subagent guidance."
|
||||
hide_spawn_agent_metadata = true
|
||||
non_code_mode_only = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -9683,6 +9684,7 @@ hide_spawn_agent_metadata = true
|
||||
Some("Subagent guidance.")
|
||||
);
|
||||
assert!(config.multi_agent_v2.hide_spawn_agent_metadata);
|
||||
assert!(config.multi_agent_v2.non_code_mode_only);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -9702,6 +9704,7 @@ usage_hint_text = "base hint"
|
||||
root_agent_usage_hint_text = "base root hint"
|
||||
subagent_usage_hint_text = "base subagent hint"
|
||||
hide_spawn_agent_metadata = true
|
||||
non_code_mode_only = false
|
||||
|
||||
[profiles.no_hint.features.multi_agent_v2]
|
||||
max_concurrent_threads_per_session = 6
|
||||
@@ -9711,6 +9714,7 @@ usage_hint_text = "profile hint"
|
||||
root_agent_usage_hint_text = "profile root hint"
|
||||
subagent_usage_hint_text = "profile subagent hint"
|
||||
hide_spawn_agent_metadata = false
|
||||
non_code_mode_only = true
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -9736,6 +9740,7 @@ hide_spawn_agent_metadata = false
|
||||
Some("profile subagent hint")
|
||||
);
|
||||
assert!(!config.multi_agent_v2.hide_spawn_agent_metadata);
|
||||
assert!(config.multi_agent_v2.non_code_mode_only);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -9759,6 +9764,7 @@ enabled = true
|
||||
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.agent_max_threads, Some(3));
|
||||
assert!(!config.multi_agent_v2.non_code_mode_only);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -846,6 +846,7 @@ pub struct MultiAgentV2Config {
|
||||
pub root_agent_usage_hint_text: Option<String>,
|
||||
pub subagent_usage_hint_text: Option<String>,
|
||||
pub hide_spawn_agent_metadata: bool,
|
||||
pub non_code_mode_only: bool,
|
||||
}
|
||||
|
||||
impl Default for MultiAgentV2Config {
|
||||
@@ -859,6 +860,7 @@ impl Default for MultiAgentV2Config {
|
||||
root_agent_usage_hint_text: None,
|
||||
subagent_usage_hint_text: None,
|
||||
hide_spawn_agent_metadata: false,
|
||||
non_code_mode_only: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1995,6 +1997,10 @@ fn resolve_multi_agent_v2_config(
|
||||
.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);
|
||||
let non_code_mode_only = profile
|
||||
.and_then(|config| config.non_code_mode_only)
|
||||
.or_else(|| base.and_then(|config| config.non_code_mode_only))
|
||||
.unwrap_or(default.non_code_mode_only);
|
||||
|
||||
MultiAgentV2Config {
|
||||
max_concurrent_threads_per_session,
|
||||
@@ -2004,6 +2010,7 @@ fn resolve_multi_agent_v2_config(
|
||||
root_agent_usage_hint_text,
|
||||
subagent_usage_hint_text,
|
||||
hide_spawn_agent_metadata,
|
||||
non_code_mode_only,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ pub(super) async fn spawn_review_thread(
|
||||
.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_multi_agent_v2_non_code_mode_only(config.multi_agent_v2.non_code_mode_only)
|
||||
.with_goal_tools_allowed(goal_tools_supported)
|
||||
.with_max_concurrent_threads_per_session(config.agent_max_threads)
|
||||
.with_wait_agent_min_timeout_ms(
|
||||
|
||||
@@ -218,6 +218,7 @@ impl TurnContext {
|
||||
.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_multi_agent_v2_non_code_mode_only(config.multi_agent_v2.non_code_mode_only)
|
||||
.with_goal_tools_allowed(self.tools_config.goal_tools)
|
||||
.with_max_concurrent_threads_per_session(
|
||||
config
|
||||
@@ -512,6 +513,7 @@ impl Session {
|
||||
.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_multi_agent_v2_non_code_mode_only(per_turn_config.multi_agent_v2.non_code_mode_only)
|
||||
.with_goal_tools_allowed(goal_tools_supported)
|
||||
.with_max_concurrent_threads_per_session(
|
||||
per_turn_config
|
||||
|
||||
@@ -263,6 +263,79 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn override_tool_exposure(
|
||||
handler: Arc<dyn RegisteredTool>,
|
||||
exposure: ToolExposure,
|
||||
) -> Arc<dyn RegisteredTool> {
|
||||
if handler.exposure() == exposure {
|
||||
return handler;
|
||||
}
|
||||
|
||||
Arc::new(ExposureOverride { handler, exposure })
|
||||
}
|
||||
|
||||
struct ExposureOverride {
|
||||
handler: Arc<dyn RegisteredTool>,
|
||||
exposure: ToolExposure,
|
||||
}
|
||||
|
||||
impl RegisteredTool for ExposureOverride {
|
||||
fn tool_name(&self) -> ToolName {
|
||||
self.handler.tool_name()
|
||||
}
|
||||
|
||||
fn spec(&self) -> Option<ToolSpec> {
|
||||
self.handler.spec()
|
||||
}
|
||||
|
||||
fn exposure(&self) -> ToolExposure {
|
||||
self.exposure
|
||||
}
|
||||
|
||||
fn search_info(&self) -> Option<ToolSearchInfo> {
|
||||
self.handler.search_info()
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
self.handler.supports_parallel_tool_calls()
|
||||
}
|
||||
|
||||
fn matches_kind(&self, payload: &ToolPayload) -> bool {
|
||||
self.handler.matches_kind(payload)
|
||||
}
|
||||
|
||||
fn pre_tool_use_payload(&self, invocation: &ToolInvocation) -> Option<PreToolUsePayload> {
|
||||
self.handler.pre_tool_use_payload(invocation)
|
||||
}
|
||||
|
||||
fn with_updated_hook_input(
|
||||
&self,
|
||||
invocation: ToolInvocation,
|
||||
updated_input: Value,
|
||||
) -> Result<ToolInvocation, FunctionCallError> {
|
||||
self.handler
|
||||
.with_updated_hook_input(invocation, updated_input)
|
||||
}
|
||||
|
||||
fn telemetry_tags<'a>(
|
||||
&'a self,
|
||||
invocation: &'a ToolInvocation,
|
||||
) -> BoxFuture<'a, ToolTelemetryTags> {
|
||||
self.handler.telemetry_tags(invocation)
|
||||
}
|
||||
|
||||
fn create_diff_consumer(&self) -> Option<Box<dyn ToolArgumentDiffConsumer>> {
|
||||
self.handler.create_diff_consumer()
|
||||
}
|
||||
|
||||
fn handle_any<'a>(
|
||||
&'a self,
|
||||
invocation: ToolInvocation,
|
||||
) -> BoxFuture<'a, Result<AnyToolResult, FunctionCallError>> {
|
||||
self.handler.handle_any(invocation)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToolRegistry {
|
||||
handlers: HashMap<ToolName, Arc<dyn RegisteredTool>>,
|
||||
}
|
||||
@@ -290,6 +363,10 @@ impl ToolRegistry {
|
||||
self.handlers.get(name).map(Arc::clone)
|
||||
}
|
||||
|
||||
pub(crate) fn tool_exposure(&self, name: &ToolName) -> Option<ToolExposure> {
|
||||
self.handlers.get(name).map(|handler| handler.exposure())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn has_handler(&self, name: &ToolName) -> bool {
|
||||
self.handler(name).is_some()
|
||||
@@ -584,7 +661,7 @@ impl ToolRegistryBuilder {
|
||||
}
|
||||
|
||||
if include_spec
|
||||
&& handler.exposure() == ToolExposure::Direct
|
||||
&& handler.exposure().is_direct()
|
||||
&& let Some(spec) = handler.spec()
|
||||
{
|
||||
self.push_spec(spec);
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::registry::AnyToolResult;
|
||||
use crate::tools::registry::ToolArgumentDiffConsumer;
|
||||
use crate::tools::registry::ToolExposure;
|
||||
use crate::tools::registry::ToolRegistry;
|
||||
use crate::tools::spec::build_specs_with_discoverable_tools;
|
||||
use codex_extension_api::ExtensionToolExecutor;
|
||||
@@ -63,10 +64,7 @@ impl ToolRouter {
|
||||
let (specs, registry) = builder.build();
|
||||
let model_visible_specs = specs
|
||||
.into_iter()
|
||||
.filter(|spec| {
|
||||
!config.code_mode_only_enabled
|
||||
|| !codex_code_mode::is_code_mode_nested_tool(spec.name())
|
||||
})
|
||||
.filter(|spec| !is_hidden_by_code_mode_only(config, ®istry, spec))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
@@ -173,6 +171,21 @@ impl ToolRouter {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_hidden_by_code_mode_only(
|
||||
config: &ToolsConfig,
|
||||
registry: &ToolRegistry,
|
||||
spec: &ToolSpec,
|
||||
) -> bool {
|
||||
if !config.code_mode_only_enabled || !codex_code_mode::is_code_mode_nested_tool(spec.name()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let exposure = registry
|
||||
.tool_exposure(&ToolName::plain(spec.name()))
|
||||
.unwrap_or(ToolExposure::Direct);
|
||||
exposure != ToolExposure::DirectModelOnly
|
||||
}
|
||||
|
||||
pub(crate) fn extension_tool_executors(session: &Session) -> Vec<Arc<dyn ExtensionToolExecutor>> {
|
||||
session
|
||||
.services
|
||||
|
||||
@@ -43,6 +43,7 @@ use crate::tools::hosted_spec::create_web_search_tool;
|
||||
use crate::tools::registry::RegisteredTool;
|
||||
use crate::tools::registry::ToolExposure;
|
||||
use crate::tools::registry::ToolRegistryBuilder;
|
||||
use crate::tools::registry::override_tool_exposure;
|
||||
use crate::tools::spec_plan_types::ToolRegistryBuildParams;
|
||||
use crate::tools::spec_plan_types::agent_type_description;
|
||||
use codex_extension_api::ExtensionToolExecutor;
|
||||
@@ -79,9 +80,9 @@ pub fn build_tool_registry_builder(
|
||||
let mut deferred_search_infos = Vec::new();
|
||||
for handler in &handlers {
|
||||
match handler.exposure() {
|
||||
ToolExposure::Direct => {
|
||||
ToolExposure::Direct | ToolExposure::DirectModelOnly => {
|
||||
if let Some(spec) = handler.spec() {
|
||||
non_deferred_specs.push(spec);
|
||||
non_deferred_specs.push((spec, handler.exposure()));
|
||||
}
|
||||
}
|
||||
ToolExposure::Deferred => {
|
||||
@@ -97,21 +98,27 @@ pub fn build_tool_registry_builder(
|
||||
web_search_config: config.web_search_config.as_ref(),
|
||||
web_search_tool_type: config.web_search_tool_type,
|
||||
}) {
|
||||
non_deferred_specs.push(web_search_tool);
|
||||
non_deferred_specs.push((web_search_tool, ToolExposure::Direct));
|
||||
}
|
||||
if config.image_gen_tool {
|
||||
non_deferred_specs.push(create_image_generation_tool("png"));
|
||||
non_deferred_specs.push((create_image_generation_tool("png"), ToolExposure::Direct));
|
||||
}
|
||||
|
||||
let non_deferred_specs = non_deferred_specs
|
||||
.into_iter()
|
||||
.map(|(spec, exposure)| {
|
||||
if config.code_mode_enabled && exposure != ToolExposure::DirectModelOnly {
|
||||
codex_tools::augment_tool_spec_for_code_mode(spec)
|
||||
} else {
|
||||
spec
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for spec in merge_into_namespaces(non_deferred_specs) {
|
||||
if !config.namespace_tools && matches!(spec, ToolSpec::Namespace(_)) {
|
||||
continue;
|
||||
}
|
||||
let spec = if config.code_mode_enabled {
|
||||
codex_tools::augment_tool_spec_for_code_mode(spec)
|
||||
} else {
|
||||
spec
|
||||
};
|
||||
builder.push_spec(spec);
|
||||
}
|
||||
|
||||
@@ -142,7 +149,14 @@ fn build_code_mode_handlers(
|
||||
|
||||
let mut code_mode_nested_tool_specs = handlers
|
||||
.iter()
|
||||
.filter_map(|handler| handler.spec())
|
||||
.filter_map(|handler| {
|
||||
if handler.exposure() == ToolExposure::DirectModelOnly {
|
||||
return None;
|
||||
}
|
||||
|
||||
let spec = handler.spec()?;
|
||||
Some(spec)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
code_mode_nested_tool_specs.extend(
|
||||
extension_tool_executors
|
||||
@@ -344,23 +358,32 @@ fn collect_handler_tools(
|
||||
|
||||
if config.collab_tools {
|
||||
if config.multi_agent_v2 {
|
||||
let exposure = if config.multi_agent_v2_non_code_mode_only {
|
||||
ToolExposure::DirectModelOnly
|
||||
} else {
|
||||
ToolExposure::Direct
|
||||
};
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
handlers.push(Arc::new(SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
|
||||
available_models: config.available_models.clone(),
|
||||
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(),
|
||||
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
|
||||
})));
|
||||
handlers.push(Arc::new(SendMessageHandlerV2));
|
||||
handlers.push(Arc::new(FollowupTaskHandlerV2));
|
||||
handlers.push(Arc::new(WaitAgentHandlerV2::new(
|
||||
params.wait_agent_timeouts,
|
||||
)));
|
||||
handlers.push(Arc::new(CloseAgentHandlerV2));
|
||||
handlers.push(Arc::new(ListAgentsHandlerV2));
|
||||
handlers.push(multi_agent_v2_handler(
|
||||
SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
|
||||
available_models: config.available_models.clone(),
|
||||
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(),
|
||||
max_concurrent_threads_per_session: config.max_concurrent_threads_per_session,
|
||||
}),
|
||||
exposure,
|
||||
));
|
||||
handlers.push(multi_agent_v2_handler(SendMessageHandlerV2, exposure));
|
||||
handlers.push(multi_agent_v2_handler(FollowupTaskHandlerV2, exposure));
|
||||
handlers.push(multi_agent_v2_handler(
|
||||
WaitAgentHandlerV2::new(params.wait_agent_timeouts),
|
||||
exposure,
|
||||
));
|
||||
handlers.push(multi_agent_v2_handler(CloseAgentHandlerV2, exposure));
|
||||
handlers.push(multi_agent_v2_handler(ListAgentsHandlerV2, exposure));
|
||||
} else {
|
||||
let agent_type_description =
|
||||
agent_type_description(config, params.default_agent_type_description);
|
||||
@@ -416,6 +439,13 @@ fn collect_handler_tools(
|
||||
handlers
|
||||
}
|
||||
|
||||
fn multi_agent_v2_handler(
|
||||
handler: impl RegisteredTool + 'static,
|
||||
exposure: ToolExposure,
|
||||
) -> Arc<dyn RegisteredTool> {
|
||||
override_tool_exposure(Arc::new(handler), exposure)
|
||||
}
|
||||
|
||||
fn compare_code_mode_tools(
|
||||
left: &codex_code_mode::ToolDefinition,
|
||||
right: &codex_code_mode::ToolDefinition,
|
||||
|
||||
@@ -1384,3 +1384,67 @@ async fn code_mode_only_restricts_model_tools_to_exec_tools() {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn code_mode_only_can_expose_multi_agent_v2_as_normal_tools() {
|
||||
let config = test_config().await;
|
||||
let model_info = construct_model_info_offline("gpt-5.4", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::CodeMode);
|
||||
features.enable(Feature::CodeModeOnly);
|
||||
features.enable(Feature::MultiAgentV2);
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Live),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
})
|
||||
.with_multi_agent_v2_non_code_mode_only(/*multi_agent_v2_non_code_mode_only*/ true);
|
||||
let router = ToolRouter::from_config(
|
||||
&tools_config,
|
||||
ToolRouterParams {
|
||||
mcp_tools: None,
|
||||
deferred_mcp_tools: None,
|
||||
discoverable_tools: None,
|
||||
extension_tool_executors: Vec::new(),
|
||||
dynamic_tools: &[],
|
||||
},
|
||||
);
|
||||
let model_visible_specs = router.model_visible_specs();
|
||||
let tool_names = model_visible_specs
|
||||
.iter()
|
||||
.map(ToolSpec::name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
tool_names,
|
||||
vec![
|
||||
"exec",
|
||||
"wait",
|
||||
"spawn_agent",
|
||||
"send_message",
|
||||
"followup_task",
|
||||
"wait_agent",
|
||||
"close_agent",
|
||||
"list_agents",
|
||||
]
|
||||
);
|
||||
|
||||
let exec = find_tool(&model_visible_specs, "exec");
|
||||
let ToolSpec::Freeform(exec) = exec else {
|
||||
panic!("exec should be a freeform tool");
|
||||
};
|
||||
assert!(!exec.description.contains("spawn_agent"));
|
||||
assert!(!exec.description.contains("wait_agent"));
|
||||
|
||||
let spawn_agent = find_tool(&model_visible_specs, "spawn_agent");
|
||||
let ToolSpec::Function(spawn_agent) = spawn_agent else {
|
||||
panic!("spawn_agent should be a function tool");
|
||||
};
|
||||
assert!(!spawn_agent.description.contains("exec tool declaration"));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ pub struct MultiAgentV2ConfigToml {
|
||||
pub subagent_usage_hint_text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hide_spawn_agent_metadata: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub non_code_mode_only: Option<bool>,
|
||||
}
|
||||
|
||||
impl FeatureConfig for MultiAgentV2ConfigToml {
|
||||
|
||||
@@ -481,6 +481,7 @@ usage_hint_text = "Custom delegation guidance."
|
||||
root_agent_usage_hint_text = "Root guidance."
|
||||
subagent_usage_hint_text = "Subagent guidance."
|
||||
hide_spawn_agent_metadata = true
|
||||
non_code_mode_only = true
|
||||
"#,
|
||||
)
|
||||
.expect("features table should deserialize");
|
||||
@@ -500,6 +501,7 @@ hide_spawn_agent_metadata = true
|
||||
root_agent_usage_hint_text: Some("Root guidance.".to_string()),
|
||||
subagent_usage_hint_text: Some("Subagent guidance.".to_string()),
|
||||
hide_spawn_agent_metadata: Some(true),
|
||||
non_code_mode_only: Some(true),
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -535,6 +537,7 @@ usage_hint_enabled = false
|
||||
root_agent_usage_hint_text: None,
|
||||
subagent_usage_hint_text: None,
|
||||
hide_spawn_agent_metadata: None,
|
||||
non_code_mode_only: None,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ pub struct ToolsConfig {
|
||||
pub collab_tools: bool,
|
||||
pub goal_tools: bool,
|
||||
pub multi_agent_v2: bool,
|
||||
pub multi_agent_v2_non_code_mode_only: bool,
|
||||
pub hide_spawn_agent_metadata: bool,
|
||||
pub spawn_agent_usage_hint: bool,
|
||||
pub spawn_agent_usage_hint_text: Option<String>,
|
||||
@@ -257,6 +258,7 @@ impl ToolsConfig {
|
||||
collab_tools: include_collab_tools,
|
||||
goal_tools: include_goal_tools,
|
||||
multi_agent_v2: include_multi_agent_v2,
|
||||
multi_agent_v2_non_code_mode_only: false,
|
||||
hide_spawn_agent_metadata: false,
|
||||
spawn_agent_usage_hint: true,
|
||||
spawn_agent_usage_hint_text: None,
|
||||
@@ -314,6 +316,15 @@ impl ToolsConfig {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_multi_agent_v2_non_code_mode_only(
|
||||
mut self,
|
||||
multi_agent_v2_non_code_mode_only: bool,
|
||||
) -> Self {
|
||||
self.multi_agent_v2_non_code_mode_only =
|
||||
self.multi_agent_v2 && multi_agent_v2_non_code_mode_only;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_goal_tools_allowed(mut self, allowed: bool) -> Self {
|
||||
self.goal_tools = self.goal_tools && allowed;
|
||||
self
|
||||
|
||||
@@ -5,12 +5,30 @@ use crate::ToolName;
|
||||
use crate::ToolOutput;
|
||||
use crate::ToolSpec;
|
||||
|
||||
/// Controls whether a tool is exposed in the initial model-visible tool list
|
||||
/// or registered for later discovery.
|
||||
/// Controls where a tool is exposed to the model.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ToolExposure {
|
||||
/// Include this tool in the initial model-visible tool list.
|
||||
///
|
||||
/// When code mode is enabled, this tool is also available as a nested
|
||||
/// code-mode tool.
|
||||
Direct,
|
||||
|
||||
/// Register this tool for later discovery, but omit it from the initial
|
||||
/// model-visible tool list.
|
||||
Deferred,
|
||||
|
||||
/// Include this tool in the initial model-visible tool list only.
|
||||
///
|
||||
/// In code-mode-only sessions, this keeps the tool callable as a normal
|
||||
/// model tool while excluding it from the nested code-mode tool surface.
|
||||
DirectModelOnly,
|
||||
}
|
||||
|
||||
impl ToolExposure {
|
||||
pub fn is_direct(self) -> bool {
|
||||
matches!(self, Self::Direct | Self::DirectModelOnly)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared runtime contract for model-visible tools.
|
||||
|
||||
Reference in New Issue
Block a user