mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Gate stable environment tool exposure behind a feature flag (#50962)
## What changed Add the default-off `stable_environment_tools` feature flag. When enabled, advertise environment-backed tools before an executor is ready, keep environment selectors stable as readiness changes, and include the `shell` and `login` parameters. Execution still requires a usable environment and its policy. When disabled, expose environment-backed tools only when a usable environment exists, derive selectors from usable environments, and condition shell parameters on the execution configuration. Restore tool-specific unavailable-environment errors and skip the environment readiness check for `write_stdin`. ## Testing Parameterize the empty-environment tool availability test for both flag states, check that commands require a ready transport by default, and explicitly opt tests that rely on stable tool exposure into the feature. GitOrigin-RevId: 68c6240fb3adf85d7f9b407804d4823c515abfdc
This commit is contained in:
@@ -1151,6 +1151,9 @@
|
||||
"sqlite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stable_environment_tools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"standalone_web_search": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -7318,6 +7321,9 @@
|
||||
"sqlite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"stable_environment_tools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"standalone_web_search": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -346,8 +346,9 @@ impl ApplyPatchHandler {
|
||||
|
||||
// Verify the parsed patch against the selected environment filesystem.
|
||||
let turn_environment = resolve_tool_environment(
|
||||
&step_context.environments,
|
||||
&step_context,
|
||||
selected_environment_id.as_deref(),
|
||||
"apply_patch is unavailable in this session",
|
||||
)?;
|
||||
let fs = turn_environment.environment.get_filesystem();
|
||||
let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None);
|
||||
|
||||
@@ -36,6 +36,7 @@ mod view_image;
|
||||
pub(crate) mod view_image_spec;
|
||||
mod wait_for_environment;
|
||||
|
||||
use codex_features::Feature;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_sandboxing::policy_transforms::materialize_additional_permissions_with_context;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
@@ -47,7 +48,6 @@ use serde::Deserialize;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::session::step_context::StepContext;
|
||||
@@ -158,9 +158,17 @@ where
|
||||
}
|
||||
|
||||
fn resolve_tool_environment<'a>(
|
||||
environments: &'a TurnEnvironmentSnapshot,
|
||||
step_context: &'a StepContext,
|
||||
environment_id: Option<&str>,
|
||||
legacy_unavailable_message: &'static str,
|
||||
) -> Result<&'a TurnEnvironment, FunctionCallError> {
|
||||
let environments = &step_context.environments;
|
||||
let stable_environment_tools = step_context
|
||||
.turn
|
||||
.config
|
||||
.features
|
||||
.get()
|
||||
.enabled(Feature::StableEnvironmentTools);
|
||||
environment_id.map_or_else(
|
||||
|| environments.primary(),
|
||||
|environment_id| {
|
||||
@@ -170,19 +178,21 @@ fn resolve_tool_environment<'a>(
|
||||
},
|
||||
).ok_or_else(|| {
|
||||
if let Some(environment_id) = environment_id
|
||||
&& !environments
|
||||
.all_selections()
|
||||
.iter()
|
||||
.any(|selection| selection.environment_id == environment_id)
|
||||
&& (!stable_environment_tools
|
||||
|| !environments
|
||||
.all_selections()
|
||||
.iter()
|
||||
.any(|selection| selection.environment_id == environment_id))
|
||||
{
|
||||
return FunctionCallError::RespondToModel(format!(
|
||||
"unknown turn environment id `{environment_id}`"
|
||||
));
|
||||
}
|
||||
FunctionCallError::RespondToModel(
|
||||
"No usable execution environment is available. Wait for an environment to become available before using this tool."
|
||||
.to_string(),
|
||||
)
|
||||
FunctionCallError::RespondToModel(if stable_environment_tools {
|
||||
"No usable execution environment is available. Wait for an environment to become available before using this tool.".to_string()
|
||||
} else {
|
||||
legacy_unavailable_message.to_string()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,9 @@ impl RequestPermissionsHandler {
|
||||
|
||||
let environment_args: RequestPermissionsEnvironmentArgs = parse_arguments(&arguments)?;
|
||||
let turn_environment = resolve_tool_environment(
|
||||
&step_context.environments,
|
||||
&step_context,
|
||||
environment_args.environment_id.as_deref(),
|
||||
"request_permissions requires a primary environment",
|
||||
)?;
|
||||
let sandbox_context =
|
||||
turn_environment.sandbox_context(/*additional_permissions*/ None);
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CommandToolOptions {
|
||||
pub include_login_parameter: bool,
|
||||
pub exec_permission_approvals_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -15,6 +16,7 @@ pub fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec {
|
||||
create_exec_command_tool_with_environment_id(
|
||||
options,
|
||||
/*include_environment_id*/ false,
|
||||
/*include_shell_parameter*/ true,
|
||||
/*include_windows_shell_guidance*/ cfg!(windows),
|
||||
)
|
||||
}
|
||||
@@ -22,6 +24,7 @@ pub fn create_exec_command_tool(options: CommandToolOptions) -> ToolSpec {
|
||||
pub(crate) fn create_exec_command_tool_with_environment_id(
|
||||
options: CommandToolOptions,
|
||||
include_environment_id: bool,
|
||||
include_shell_parameter: bool,
|
||||
include_windows_shell_guidance: bool,
|
||||
) -> ToolSpec {
|
||||
let yield_time_ms_description = if cfg!(windows) {
|
||||
@@ -59,19 +62,23 @@ pub(crate) fn create_exec_command_tool_with_environment_id(
|
||||
)),
|
||||
),
|
||||
]);
|
||||
properties.insert(
|
||||
"shell".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Shell binary to launch. Defaults to the user's default shell.".to_string(),
|
||||
)),
|
||||
);
|
||||
properties.insert(
|
||||
"login".to_string(),
|
||||
JsonSchema::boolean(Some(
|
||||
"True runs the shell with -l/-i semantics; false disables them. Defaults to true."
|
||||
.to_string(),
|
||||
)),
|
||||
);
|
||||
if include_shell_parameter {
|
||||
properties.insert(
|
||||
"shell".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Shell binary to launch. Defaults to the user's default shell.".to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
if options.include_login_parameter {
|
||||
properties.insert(
|
||||
"login".to_string(),
|
||||
JsonSchema::boolean(Some(
|
||||
"True runs the shell with -l/-i semantics; false disables them. Defaults to true."
|
||||
.to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
if include_environment_id {
|
||||
properties.insert(
|
||||
"environment_id".to_string(),
|
||||
|
||||
@@ -9,6 +9,7 @@ fn windows_shell_guidance_description() -> String {
|
||||
#[test]
|
||||
fn exec_command_tool_matches_expected_spec() {
|
||||
let tool = create_exec_command_tool(CommandToolOptions {
|
||||
include_login_parameter: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ const EXEC_COMMAND_REJECTION_MAX_BYTES: usize = 900;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ExecCommandHandlerOptions {
|
||||
pub(crate) include_login_parameter: bool,
|
||||
pub(crate) include_shell_parameter: bool,
|
||||
pub(crate) allow_tty: bool,
|
||||
pub(crate) exec_permission_approvals_enabled: bool,
|
||||
pub(crate) include_environment_id: bool,
|
||||
@@ -80,6 +82,8 @@ impl Default for ExecCommandHandler {
|
||||
Self {
|
||||
lifetime: ExecCommandLifetime::Interactive,
|
||||
options: ExecCommandHandlerOptions {
|
||||
include_login_parameter: false,
|
||||
include_shell_parameter: true,
|
||||
allow_tty: true,
|
||||
exec_permission_approvals_enabled: false,
|
||||
include_environment_id: false,
|
||||
@@ -113,9 +117,11 @@ impl ToolExecutor<ToolInvocation> for ExecCommandHandler {
|
||||
fn spec(&self) -> ToolSpec {
|
||||
let spec = create_exec_command_tool_with_environment_id(
|
||||
CommandToolOptions {
|
||||
include_login_parameter: self.options.include_login_parameter,
|
||||
exec_permission_approvals_enabled: self.options.exec_permission_approvals_enabled,
|
||||
},
|
||||
self.options.include_environment_id,
|
||||
self.options.include_shell_parameter,
|
||||
self.options.include_windows_shell_guidance,
|
||||
);
|
||||
let mut spec = match self.lifetime {
|
||||
@@ -179,8 +185,9 @@ impl ExecCommandHandler {
|
||||
);
|
||||
let environment_args: ExecCommandEnvironmentArgs = parse_arguments(&arguments)?;
|
||||
let turn_environment = resolve_tool_environment(
|
||||
&step_context.environments,
|
||||
&step_context,
|
||||
environment_args.environment_id.as_deref(),
|
||||
"unified exec is unavailable in this session",
|
||||
)?;
|
||||
let native_environment_cwd = turn_environment.cwd().clone();
|
||||
let cwd = environment_args
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use codex_features::Feature;
|
||||
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::context::boxed_tool_output;
|
||||
@@ -80,7 +82,18 @@ impl WriteStdinHandler {
|
||||
};
|
||||
|
||||
let args: WriteStdinArgs = parse_arguments(&arguments)?;
|
||||
resolve_tool_environment(&step_context.environments, /*environment_id*/ None)?;
|
||||
if turn
|
||||
.config
|
||||
.features
|
||||
.get()
|
||||
.enabled(Feature::StableEnvironmentTools)
|
||||
{
|
||||
resolve_tool_environment(
|
||||
&step_context,
|
||||
/*environment_id*/ None,
|
||||
"unified exec is unavailable in this session",
|
||||
)?;
|
||||
}
|
||||
let context =
|
||||
UnifiedExecContext::new(session.clone(), step_context, cancellation_token, call_id);
|
||||
let response = session
|
||||
|
||||
@@ -142,8 +142,11 @@ impl ViewImageHandler {
|
||||
}
|
||||
};
|
||||
|
||||
let turn_environment =
|
||||
resolve_tool_environment(&step_context.environments, environment_id.as_deref())?;
|
||||
let turn_environment = resolve_tool_environment(
|
||||
&step_context,
|
||||
environment_id.as_deref(),
|
||||
"view_image is unavailable in this session",
|
||||
)?;
|
||||
let path_uri = turn_environment.cwd().join(&path).map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!(
|
||||
"unable to resolve image path `{path}` against environment cwd `{}`: {err}",
|
||||
|
||||
@@ -89,6 +89,7 @@ use codex_tools::ToolExecutor;
|
||||
use codex_tools::ToolExposures;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolSpec;
|
||||
use codex_tools::UnifiedExecShellMode;
|
||||
use codex_tools::can_request_original_image_detail;
|
||||
use codex_tools::collect_code_mode_exec_prompt_tool_definitions;
|
||||
use codex_tools::collect_request_plugin_install_entries;
|
||||
@@ -1079,9 +1080,20 @@ fn standalone_web_search_enabled(turn_context: &TurnContext, model_info: &ModelI
|
||||
.enabled(Feature::StandaloneWebSearch))
|
||||
}
|
||||
|
||||
fn tool_environment_mode(environments: &TurnEnvironmentSnapshot) -> ToolEnvironmentMode {
|
||||
// Keep environment selectors stable as selected attachments change readiness.
|
||||
ToolEnvironmentMode::from_count(environments.environments.len())
|
||||
fn tool_environment_mode(context: &CoreToolPlanContext<'_>) -> ToolEnvironmentMode {
|
||||
let count = if context
|
||||
.turn_context
|
||||
.config
|
||||
.features
|
||||
.get()
|
||||
.enabled(Feature::StableEnvironmentTools)
|
||||
{
|
||||
// Keep selectors stable as selected attachments change readiness.
|
||||
context.environments.environments.len()
|
||||
} else {
|
||||
context.environments.turn_environments().count()
|
||||
};
|
||||
ToolEnvironmentMode::from_count(count)
|
||||
}
|
||||
|
||||
fn should_include_windows_shell_guidance(environments: &TurnEnvironmentSnapshot) -> bool {
|
||||
@@ -1107,8 +1119,12 @@ fn should_include_windows_shell_guidance(environments: &TurnEnvironmentSnapshot)
|
||||
fn add_shell_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistry) {
|
||||
let turn_context = context.turn_context;
|
||||
let features = turn_context.config.features.get();
|
||||
let environment_mode = tool_environment_mode(context.environments);
|
||||
if !features.enabled(Feature::ShellTool)
|
||||
let environment_mode = tool_environment_mode(context);
|
||||
let stable_environment_tools = features.enabled(Feature::StableEnvironmentTools);
|
||||
let advertise_environment_tools =
|
||||
stable_environment_tools || environment_mode.has_environment();
|
||||
if !advertise_environment_tools
|
||||
|| !features.enabled(Feature::ShellTool)
|
||||
|| matches!(context.model_info.shell_type, ConfigShellToolType::Disabled)
|
||||
{
|
||||
return;
|
||||
@@ -1121,6 +1137,20 @@ fn add_shell_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistr
|
||||
&& context.tool_policy.expose_additional_permissions;
|
||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||
let options = ExecCommandHandlerOptions {
|
||||
include_login_parameter: stable_environment_tools
|
||||
|| context
|
||||
.environments
|
||||
.turn_environments()
|
||||
.any(|environment| environment.config().allow_login_shell),
|
||||
include_shell_parameter: stable_environment_tools
|
||||
|| !matches!(
|
||||
&turn_context.unified_exec_shell_mode,
|
||||
UnifiedExecShellMode::ZshFork(_)
|
||||
)
|
||||
|| context
|
||||
.environments
|
||||
.turn_environments()
|
||||
.any(|environment| environment.environment.is_remote()),
|
||||
allow_tty: features.enabled(Feature::UnifiedExecTty),
|
||||
exec_permission_approvals_enabled,
|
||||
include_environment_id,
|
||||
@@ -1159,7 +1189,10 @@ fn add_mcp_resource_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
||||
fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut ToolRegistry) {
|
||||
let turn_context = context.turn_context;
|
||||
let features = turn_context.config.features.get();
|
||||
let environment_mode = tool_environment_mode(context.environments);
|
||||
let environment_mode = tool_environment_mode(context);
|
||||
let stable_environment_tools = features.enabled(Feature::StableEnvironmentTools);
|
||||
let advertise_environment_tools =
|
||||
stable_environment_tools || environment_mode.has_environment();
|
||||
|
||||
if turn_context.config.update_plan_enabled {
|
||||
registry.add(PlanHandler);
|
||||
@@ -1224,7 +1257,7 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
||||
registry.add_with_exposure(SendMessageToUserAsyncHandler, ToolExposure::DirectModelOnly);
|
||||
}
|
||||
|
||||
if features.enabled(Feature::RequestPermissionsTool) {
|
||||
if advertise_environment_tools && features.enabled(Feature::RequestPermissionsTool) {
|
||||
registry.add(RequestPermissionsHandler);
|
||||
}
|
||||
|
||||
@@ -1277,7 +1310,7 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
||||
));
|
||||
}
|
||||
|
||||
if context.model_info.apply_patch_tool_type.is_some() {
|
||||
if advertise_environment_tools && context.model_info.apply_patch_tool_type.is_some() {
|
||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||
registry.add(ApplyPatchHandler::new(include_environment_id));
|
||||
}
|
||||
@@ -1291,7 +1324,7 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, registry: &mut Tool
|
||||
registry.add(TestSyncHandler);
|
||||
}
|
||||
|
||||
if features.enabled(Feature::ViewImage) {
|
||||
if advertise_environment_tools && features.enabled(Feature::ViewImage) {
|
||||
let include_environment_id = matches!(environment_mode, ToolEnvironmentMode::Multiple);
|
||||
registry.add(ViewImageHandler::new(ViewImageToolOptions {
|
||||
can_request_original_image_detail: can_request_original_image_detail(
|
||||
|
||||
@@ -1907,6 +1907,10 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
||||
let mut builder = test_codex()
|
||||
.with_extensions(Arc::new(extensions.build()))
|
||||
.with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::StableEnvironmentTools)
|
||||
.expect("enable stable environment tools");
|
||||
assert!(config.features.enable(Feature::DeferredExecutor).is_ok());
|
||||
if cfg!(unix) {
|
||||
assert!(config.features.enable(Feature::ShellZshFork).is_ok());
|
||||
@@ -2830,7 +2834,6 @@ async fn ready_before_selection_resolves_resumed_thread_capability_root_after_wa
|
||||
// The first request may legally see either Starting or Ready; the wait makes step two ready.
|
||||
let first_tools = tool_names(&requests[0].body_json());
|
||||
assert!(first_tools.contains(&"wait_for_environment".to_string()));
|
||||
assert!(first_tools.contains(&"exec_command".to_string()));
|
||||
let first_user_context = requests[0].message_input_texts("user");
|
||||
let first_environment_context = first_user_context
|
||||
.iter()
|
||||
@@ -2839,8 +2842,13 @@ async fn ready_before_selection_resolves_resumed_thread_capability_root_after_wa
|
||||
let first_has_ready_root = first_user_context
|
||||
.iter()
|
||||
.any(|text| text.contains("<ready_capability_roots>ready-first-root"));
|
||||
// Tool availability no longer distinguishes Starting from Ready.
|
||||
if first_environment_context.contains("<status>starting</status>") {
|
||||
let first_is_starting = first_environment_context.contains("<status>starting</status>");
|
||||
assert_eq!(
|
||||
first_tools.contains(&"exec_command".to_string()),
|
||||
!first_is_starting,
|
||||
"without stable_environment_tools, commands require a ready transport"
|
||||
);
|
||||
if first_is_starting {
|
||||
assert!(!first_has_ready_root);
|
||||
} else {
|
||||
assert!(first_environment_context.contains("<shell>"));
|
||||
|
||||
@@ -1710,9 +1710,14 @@ async fn subagent_waits_for_its_inherited_environment_configuration() -> Result<
|
||||
use super::remote_env::spawn_tests::PendingSpawnCase;
|
||||
use super::remote_env::spawn_tests::pending_subagent_scenario;
|
||||
|
||||
let requests =
|
||||
pending_subagent_scenario(PendingSpawnCase::TurnSettings, configure_scenario_catalog)
|
||||
.await?;
|
||||
let requests = pending_subagent_scenario(PendingSpawnCase::TurnSettings, |config| {
|
||||
configure_scenario_catalog(config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::StableEnvironmentTools)
|
||||
.expect("enable stable environment tools");
|
||||
})
|
||||
.await?;
|
||||
let labels = [
|
||||
"parent delegates while the environment is starting",
|
||||
"parent waits for the worker",
|
||||
|
||||
@@ -3062,6 +3062,10 @@ async fn production_turn_keeps_rebalanced_catalogs_stable_after_compaction_and_r
|
||||
model_info.max_context_window = None;
|
||||
})
|
||||
.with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::StableEnvironmentTools)
|
||||
.expect("enable stable environment tools");
|
||||
configure_catalog_test(config);
|
||||
config.cloud_skill_enabled = true;
|
||||
config.model_provider.name = "Skills compaction test".to_string();
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@ Settings:
|
||||
text: {"verbosity":"low"}
|
||||
tool_choice: "auto"
|
||||
-- request 1 (turn) --
|
||||
00:additional_tools/developer (3; hash=91d402a1b9d55c34):
|
||||
- namespace/functions; hash=7d26e086c1dbba28
|
||||
00:additional_tools/developer (3; hash=b6ebf543280ec271):
|
||||
- namespace/functions; hash=bd9e182b4235cdc1
|
||||
- custom/exec
|
||||
- function/wait
|
||||
- function/request_user_input
|
||||
|
||||
@@ -244,8 +244,12 @@ async fn strict_tool_collisions_do_not_duplicate_unrelated_compaction_errors() -
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case::test_case(false; "default_off")]
|
||||
#[test_case::test_case(true; "opted_in")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn empty_turn_environments_keeps_environment_backed_tools() -> Result<()> {
|
||||
async fn empty_turn_environments_gate_environment_backed_tools(
|
||||
stable_environment_tools: bool,
|
||||
) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -259,7 +263,17 @@ async fn empty_turn_environments_keeps_environment_backed_tools() -> Result<()>
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex().with_config(|config| {
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
if stable_environment_tools {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::StableEnvironmentTools)
|
||||
.expect("enable stable environment tools");
|
||||
}
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissionsTool)
|
||||
.expect("enable permissions tool");
|
||||
config.update_plan_enabled = true;
|
||||
config
|
||||
.features
|
||||
@@ -276,10 +290,17 @@ async fn empty_turn_environments_keeps_environment_backed_tools() -> Result<()>
|
||||
tools.contains(&"update_plan".to_string()),
|
||||
"non-environment tool should remain available; got {tools:?}"
|
||||
);
|
||||
for environment_tool in ["exec_command", "write_stdin", "apply_patch", "view_image"] {
|
||||
assert!(
|
||||
for environment_tool in [
|
||||
"exec_command",
|
||||
"write_stdin",
|
||||
"apply_patch",
|
||||
"view_image",
|
||||
"request_permissions",
|
||||
] {
|
||||
assert_eq!(
|
||||
tools.contains(&environment_tool.to_string()),
|
||||
"{environment_tool} should remain available for explicit empty turn environments; got {tools:?}"
|
||||
stable_environment_tools,
|
||||
"unexpected {environment_tool} availability for empty environments; got {tools:?}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,9 @@ pub enum Feature {
|
||||
DaemonAutoStart,
|
||||
|
||||
// Experimental
|
||||
/// Advertise enabled environment-backed tools and their shell parameters before the
|
||||
/// executor is ready. Actual execution still requires a usable environment and its policy.
|
||||
StableEnvironmentTools,
|
||||
/// Send per-content-entry classifications in internal Responses metadata.
|
||||
ContentItemKinds,
|
||||
/// Record model-attempted tool calls in internal Responses metadata.
|
||||
@@ -1095,6 +1098,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::StableEnvironmentTools,
|
||||
key: "stable_environment_tools",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CodeMode,
|
||||
key: "code_mode",
|
||||
|
||||
Reference in New Issue
Block a user