mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
Gate multi-environment process tool model surface
Keep the legacy single-environment surface unchanged, expose environment_id only when multiple turn environments are selected, and add coverage plus a probe for the multi-environment model surface. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -9,11 +9,13 @@ use std::collections::BTreeMap;
|
||||
pub struct CommandToolOptions {
|
||||
pub allow_login_shell: bool,
|
||||
pub exec_permission_approvals_enabled: bool,
|
||||
pub include_environment_id: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ShellToolOptions {
|
||||
pub exec_permission_approvals_enabled: bool,
|
||||
pub include_environment_id: bool,
|
||||
}
|
||||
|
||||
pub fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec {
|
||||
@@ -63,6 +65,7 @@ pub fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec {
|
||||
)),
|
||||
);
|
||||
}
|
||||
maybe_insert_environment_id_parameter(&mut properties, options.include_environment_id);
|
||||
properties.extend(create_approval_parameters(
|
||||
options.exec_permission_approvals_enabled,
|
||||
));
|
||||
@@ -158,6 +161,7 @@ pub fn create_shell_tool(options: ShellToolOptions) -> ToolSpec {
|
||||
properties.extend(create_approval_parameters(
|
||||
options.exec_permission_approvals_enabled,
|
||||
));
|
||||
maybe_insert_environment_id_parameter(&mut properties, options.include_environment_id);
|
||||
|
||||
let description = if cfg!(windows) {
|
||||
format!(
|
||||
@@ -226,6 +230,7 @@ pub fn create_shell_command_tool(options: CommandToolOptions) -> ToolSpec {
|
||||
)),
|
||||
);
|
||||
}
|
||||
maybe_insert_environment_id_parameter(&mut properties, options.include_environment_id);
|
||||
properties.extend(create_approval_parameters(
|
||||
options.exec_permission_approvals_enabled,
|
||||
));
|
||||
@@ -266,6 +271,20 @@ Examples of valid command strings:
|
||||
})
|
||||
}
|
||||
|
||||
fn maybe_insert_environment_id_parameter(
|
||||
properties: &mut BTreeMap<String, JsonSchema>,
|
||||
include_environment_id: bool,
|
||||
) {
|
||||
if include_environment_id {
|
||||
properties.insert(
|
||||
"environment_id".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Optional environment id from the <environment_context> block. If omitted, uses the primary environment.".to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_request_permissions_tool(description: String) -> ToolSpec {
|
||||
let properties = BTreeMap::from([
|
||||
(
|
||||
|
||||
@@ -10,6 +10,7 @@ fn windows_shell_guidance_description() -> String {
|
||||
fn shell_tool_matches_expected_spec() {
|
||||
let tool = create_shell_tool(ShellToolOptions {
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: false,
|
||||
});
|
||||
|
||||
let description = if cfg!(windows) {
|
||||
@@ -96,6 +97,7 @@ fn exec_command_tool_matches_expected_spec() {
|
||||
let tool = create_exec_command_tool(CommandToolOptions {
|
||||
allow_login_shell: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: false,
|
||||
});
|
||||
|
||||
let description = if cfg!(windows) {
|
||||
@@ -174,6 +176,38 @@ fn exec_command_tool_matches_expected_spec() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_tools_include_environment_id_when_requested() {
|
||||
for tool in [
|
||||
create_shell_tool(ShellToolOptions {
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: true,
|
||||
}),
|
||||
create_shell_command_tool(CommandToolOptions {
|
||||
allow_login_shell: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: true,
|
||||
}),
|
||||
create_exec_command_tool(CommandToolOptions {
|
||||
allow_login_shell: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: true,
|
||||
}),
|
||||
] {
|
||||
let ToolSpec::Function(tool) = tool else {
|
||||
panic!("expected function tool");
|
||||
};
|
||||
assert!(
|
||||
tool.parameters
|
||||
.properties
|
||||
.as_ref()
|
||||
.is_some_and(|properties| properties.contains_key("environment_id")),
|
||||
"{} should include environment_id",
|
||||
tool.name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_stdin_tool_matches_expected_spec() {
|
||||
let tool = create_write_stdin_tool();
|
||||
@@ -228,6 +262,7 @@ fn write_stdin_tool_matches_expected_spec() {
|
||||
fn shell_tool_with_request_permission_includes_additional_permissions() {
|
||||
let tool = create_shell_tool(ShellToolOptions {
|
||||
exec_permission_approvals_enabled: true,
|
||||
include_environment_id: false,
|
||||
});
|
||||
|
||||
let mut properties = BTreeMap::from([
|
||||
@@ -332,6 +367,7 @@ fn shell_command_tool_matches_expected_spec() {
|
||||
let tool = create_shell_command_tool(CommandToolOptions {
|
||||
allow_login_shell: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: false,
|
||||
});
|
||||
|
||||
let description = if cfg!(windows) {
|
||||
|
||||
@@ -89,6 +89,7 @@ pub struct ToolsConfig {
|
||||
pub shell_command_backend: ShellCommandBackendConfig,
|
||||
pub unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
pub has_environment: bool,
|
||||
pub has_multiple_selected_environments: bool,
|
||||
pub allow_login_shell: bool,
|
||||
pub apply_patch_tool_type: Option<ApplyPatchToolType>,
|
||||
pub web_search_mode: Option<WebSearchMode>,
|
||||
@@ -206,6 +207,7 @@ impl ToolsConfig {
|
||||
shell_command_backend,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode::Direct,
|
||||
has_environment: true,
|
||||
has_multiple_selected_environments: false,
|
||||
allow_login_shell: true,
|
||||
apply_patch_tool_type,
|
||||
web_search_mode: *web_search_mode,
|
||||
@@ -311,6 +313,14 @@ impl ToolsConfig {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_has_multiple_selected_environments(
|
||||
mut self,
|
||||
has_multiple_selected_environments: bool,
|
||||
) -> Self {
|
||||
self.has_multiple_selected_environments = has_multiple_selected_environments;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unified_exec_shell_mode(
|
||||
mut self,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
|
||||
@@ -141,6 +141,7 @@ pub fn build_tool_registry_plan(
|
||||
plan.push_spec(
|
||||
create_shell_tool(ShellToolOptions {
|
||||
exec_permission_approvals_enabled,
|
||||
include_environment_id: config.has_multiple_selected_environments,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
config.code_mode_enabled,
|
||||
@@ -158,6 +159,7 @@ pub fn build_tool_registry_plan(
|
||||
create_exec_command_tool(CommandToolOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
include_environment_id: config.has_multiple_selected_environments,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
config.code_mode_enabled,
|
||||
@@ -176,6 +178,7 @@ pub fn build_tool_registry_plan(
|
||||
create_shell_command_tool(CommandToolOptions {
|
||||
allow_login_shell: config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
include_environment_id: config.has_multiple_selected_environments,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
config.code_mode_enabled,
|
||||
|
||||
@@ -87,6 +87,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
create_exec_command_tool(CommandToolOptions {
|
||||
allow_login_shell: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: false,
|
||||
}),
|
||||
create_write_stdin_tool(),
|
||||
create_update_plan_tool(),
|
||||
@@ -158,6 +159,88 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_tool_specs_include_environment_id_only_for_multiple_selected_environments() {
|
||||
let model_info = model_info();
|
||||
let available_models = Vec::new();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::UnifiedExec);
|
||||
let 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::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (single_environment_tools, _) = build_specs(
|
||||
&config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert_process_tool_environment_id(
|
||||
&single_environment_tools,
|
||||
"exec_command",
|
||||
/*expected_present*/ false,
|
||||
);
|
||||
|
||||
let multi_environment_config = config
|
||||
.with_has_multiple_selected_environments(/*has_multiple_selected_environments*/ true);
|
||||
let (multi_environment_tools, _) = build_specs(
|
||||
&multi_environment_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert_process_tool_environment_id(
|
||||
&multi_environment_tools,
|
||||
"exec_command",
|
||||
/*expected_present*/ true,
|
||||
);
|
||||
|
||||
let mut shell_command_features = Features::with_defaults();
|
||||
shell_command_features.disable(Feature::UnifiedExec);
|
||||
let shell_command_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
available_models: &available_models,
|
||||
features: &shell_command_features,
|
||||
image_generation_tool_auth_allowed: true,
|
||||
web_search_mode: Some(WebSearchMode::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
permission_profile: &PermissionProfile::Disabled,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (single_environment_shell_tools, _) = build_specs(
|
||||
&shell_command_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert_process_tool_environment_id(
|
||||
&single_environment_shell_tools,
|
||||
"shell_command",
|
||||
/*expected_present*/ false,
|
||||
);
|
||||
|
||||
let multi_environment_shell_config = shell_command_config
|
||||
.with_has_multiple_selected_environments(/*has_multiple_selected_environments*/ true);
|
||||
let (multi_environment_shell_tools, _) = build_specs(
|
||||
&multi_environment_shell_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert_process_tool_environment_id(
|
||||
&multi_environment_shell_tools,
|
||||
"shell_command",
|
||||
/*expected_present*/ true,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_specs_collab_tools_enabled() {
|
||||
let model_info = model_info();
|
||||
@@ -2429,6 +2512,23 @@ fn find_tool<'a>(tools: &'a [ConfiguredToolSpec], expected_name: &str) -> &'a Co
|
||||
.unwrap_or_else(|| panic!("expected tool {expected_name}"))
|
||||
}
|
||||
|
||||
fn assert_process_tool_environment_id(
|
||||
tools: &[ConfiguredToolSpec],
|
||||
expected_name: &str,
|
||||
expected_present: bool,
|
||||
) {
|
||||
let tool = find_tool(tools, expected_name);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &tool.spec else {
|
||||
panic!("expected function tool {expected_name}");
|
||||
};
|
||||
let (properties, _) = expect_object_schema(parameters);
|
||||
assert_eq!(
|
||||
properties.contains_key("environment_id"),
|
||||
expected_present,
|
||||
"{expected_name} environment_id parameter presence"
|
||||
);
|
||||
}
|
||||
|
||||
fn find_namespace_function_tool<'a>(
|
||||
tools: &'a [ConfiguredToolSpec],
|
||||
expected_namespace: &str,
|
||||
|
||||
Reference in New Issue
Block a user