mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Fix sticky environment CI fallout
Shrink app.rs below the blob policy threshold, clean up dead-code fallout, and update stale exec-server listen URL test expectations. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -8,6 +8,13 @@ use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
const APPLY_PATCH_LARK_GRAMMAR: &str = include_str!("tool_apply_patch.lark");
|
||||
const APPLY_PATCH_ENVIRONMENT_LARK_GRAMMAR: &str =
|
||||
include_str!("tool_apply_patch_environment.lark");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ApplyPatchToolOptions {
|
||||
pub multi_environment_tools: bool,
|
||||
}
|
||||
|
||||
const APPLY_PATCH_JSON_TOOL_DESCRIPTION: &str = r#"Use the `apply_patch` tool to edit files.
|
||||
Your patch language is a stripped‑down, file‑oriented diff format designed to be easy to parse and safe to apply. You can think of it as a high‑level envelope:
|
||||
@@ -82,30 +89,50 @@ It is important to remember:
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ApplyPatchToolArgs {
|
||||
pub input: String,
|
||||
#[serde(default, alias = "environmentId")]
|
||||
pub environment_id: Option<String>,
|
||||
}
|
||||
|
||||
/// Returns a custom tool that can be used to edit files. Well-suited for GPT-5 models
|
||||
/// https://platform.openai.com/docs/guides/function-calling#custom-tools
|
||||
pub fn create_apply_patch_freeform_tool() -> ToolSpec {
|
||||
pub fn create_apply_patch_freeform_tool(options: ApplyPatchToolOptions) -> ToolSpec {
|
||||
let description = if options.multi_environment_tools {
|
||||
"Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON. To target a non-primary selected environment, add a first line `*** Environment: <environment_id>` before `*** Begin Patch`."
|
||||
} else {
|
||||
"Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON."
|
||||
};
|
||||
let definition = if options.multi_environment_tools {
|
||||
APPLY_PATCH_ENVIRONMENT_LARK_GRAMMAR
|
||||
} else {
|
||||
APPLY_PATCH_LARK_GRAMMAR
|
||||
};
|
||||
ToolSpec::Freeform(FreeformTool {
|
||||
name: "apply_patch".to_string(),
|
||||
description: "Use the `apply_patch` tool to edit files. This is a FREEFORM tool, so do not wrap the patch in JSON.".to_string(),
|
||||
description: description.to_string(),
|
||||
format: FreeformToolFormat {
|
||||
r#type: "grammar".to_string(),
|
||||
syntax: "lark".to_string(),
|
||||
definition: APPLY_PATCH_LARK_GRAMMAR.to_string(),
|
||||
definition: definition.to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a json tool that can be used to edit files. Should only be used with gpt-oss models
|
||||
pub fn create_apply_patch_json_tool() -> ToolSpec {
|
||||
let properties = BTreeMap::from([(
|
||||
pub fn create_apply_patch_json_tool(options: ApplyPatchToolOptions) -> ToolSpec {
|
||||
let mut properties = BTreeMap::from([(
|
||||
"input".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"The entire contents of the apply_patch command".to_string(),
|
||||
)),
|
||||
)]);
|
||||
if options.multi_environment_tools {
|
||||
properties.insert(
|
||||
"environment_id".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Optional selected environment id to apply the patch in; omit to use the primary environment.".to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "apply_patch".to_string(),
|
||||
|
||||
@@ -6,7 +6,9 @@ use std::collections::BTreeMap;
|
||||
#[test]
|
||||
fn create_apply_patch_freeform_tool_matches_expected_spec() {
|
||||
assert_eq!(
|
||||
create_apply_patch_freeform_tool(),
|
||||
create_apply_patch_freeform_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: false,
|
||||
}),
|
||||
ToolSpec::Freeform(FreeformTool {
|
||||
name: "apply_patch".to_string(),
|
||||
description:
|
||||
@@ -24,7 +26,9 @@ fn create_apply_patch_freeform_tool_matches_expected_spec() {
|
||||
#[test]
|
||||
fn create_apply_patch_json_tool_matches_expected_spec() {
|
||||
assert_eq!(
|
||||
create_apply_patch_json_tool(),
|
||||
create_apply_patch_json_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: false,
|
||||
}),
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "apply_patch".to_string(),
|
||||
description: APPLY_PATCH_JSON_TOOL_DESCRIPTION.to_string(),
|
||||
@@ -44,3 +48,31 @@ fn create_apply_patch_json_tool_matches_expected_spec() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_apply_patch_freeform_tool_with_environment_matches_expected_spec() {
|
||||
let ToolSpec::Freeform(tool) = create_apply_patch_freeform_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: true,
|
||||
}) else {
|
||||
panic!("apply_patch should be a freeform tool");
|
||||
};
|
||||
assert!(
|
||||
tool.description
|
||||
.contains("*** Environment: <environment_id>")
|
||||
);
|
||||
assert_eq!(tool.format.definition, APPLY_PATCH_ENVIRONMENT_LARK_GRAMMAR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_apply_patch_json_tool_with_environment_includes_environment_id() {
|
||||
let ToolSpec::Function(tool) = create_apply_patch_json_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: true,
|
||||
}) else {
|
||||
panic!("apply_patch should be a function tool");
|
||||
};
|
||||
let properties = tool
|
||||
.parameters
|
||||
.properties
|
||||
.expect("apply_patch parameters should include properties");
|
||||
assert!(properties.contains_key("environment_id"));
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ pub use agent_tool::create_spawn_agent_tool_v2;
|
||||
pub use agent_tool::create_wait_agent_tool_v1;
|
||||
pub use agent_tool::create_wait_agent_tool_v2;
|
||||
pub use apply_patch_tool::ApplyPatchToolArgs;
|
||||
pub use apply_patch_tool::ApplyPatchToolOptions;
|
||||
pub use apply_patch_tool::create_apply_patch_freeform_tool;
|
||||
pub use apply_patch_tool::create_apply_patch_json_tool;
|
||||
pub use code_mode::augment_tool_spec_for_code_mode;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
start: environment? begin_patch hunk+ end_patch
|
||||
environment: "*** Environment: " environment_id LF
|
||||
environment_id: /[A-Za-z0-9_.:-]+/
|
||||
begin_patch: "*** Begin Patch" LF
|
||||
end_patch: "*** End Patch" LF?
|
||||
|
||||
hunk: add_hunk | delete_hunk | update_hunk
|
||||
add_hunk: "*** Add File: " filename LF add_line+
|
||||
delete_hunk: "*** Delete File: " filename LF
|
||||
update_hunk: "*** Update File: " filename LF change_move? change?
|
||||
|
||||
filename: /(.+)/
|
||||
add_line: "+" /(.*)/ LF -> line
|
||||
|
||||
change_move: "*** Move to: " filename LF
|
||||
change: (change_context | change_line)+ eof_line?
|
||||
change_context: ("@@" | "@@ " /(.+)/) LF
|
||||
change_line: ("+" | "-" | " ") /(.*)/ LF
|
||||
eof_line: "*** End of File" LF
|
||||
|
||||
%import common.LF
|
||||
@@ -272,6 +272,11 @@ impl ToolsConfig {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_multi_environment_tools(mut self, multi_environment_tools: bool) -> Self {
|
||||
self.multi_environment_tools = multi_environment_tools;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_unified_exec_shell_mode(
|
||||
mut self,
|
||||
unified_exec_shell_mode: UnifiedExecShellMode,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::ApplyPatchToolOptions;
|
||||
use crate::CommandToolOptions;
|
||||
use crate::REQUEST_USER_INPUT_TOOL_NAME;
|
||||
use crate::ResponsesApiNamespace;
|
||||
@@ -317,14 +318,18 @@ pub fn build_tool_registry_plan(
|
||||
match apply_patch_tool_type {
|
||||
ApplyPatchToolType::Freeform => {
|
||||
plan.push_spec(
|
||||
create_apply_patch_freeform_tool(),
|
||||
create_apply_patch_freeform_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: config.multi_environment_tools,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
}
|
||||
ApplyPatchToolType::Function => {
|
||||
plan.push_spec(
|
||||
create_apply_patch_json_tool(),
|
||||
create_apply_patch_json_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: config.multi_environment_tools,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ false,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
@@ -384,6 +389,7 @@ pub fn build_tool_registry_plan(
|
||||
plan.push_spec(
|
||||
create_view_image_tool(ViewImageToolOptions {
|
||||
can_request_original_image_detail: config.can_request_original_image_detail,
|
||||
multi_environment_tools: config.multi_environment_tools,
|
||||
}),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
config.code_mode_enabled,
|
||||
|
||||
@@ -90,7 +90,9 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
create_write_stdin_tool(),
|
||||
create_update_plan_tool(),
|
||||
request_user_input_tool_spec(/*default_mode_request_user_input*/ false),
|
||||
create_apply_patch_freeform_tool(),
|
||||
create_apply_patch_freeform_tool(ApplyPatchToolOptions {
|
||||
multi_environment_tools: false,
|
||||
}),
|
||||
ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
filters: None,
|
||||
@@ -101,6 +103,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
create_image_generation_tool("png"),
|
||||
create_view_image_tool(ViewImageToolOptions {
|
||||
can_request_original_image_detail: config.can_request_original_image_detail,
|
||||
multi_environment_tools: false,
|
||||
}),
|
||||
] {
|
||||
expected.insert(spec.name().to_string(), spec);
|
||||
@@ -433,6 +436,37 @@ fn view_image_tool_includes_detail_with_original_detail_support() {
|
||||
assert!(description.contains("omit this field for default resized behavior"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_image_tool_includes_environment_id_when_multi_environment_tools_enabled() {
|
||||
let mut model_info = model_info();
|
||||
model_info.supports_image_detail_original = false;
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::MultiEnvironmentTools);
|
||||
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::Cached),
|
||||
session_source: SessionSource::Cli,
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = &view_image.spec else {
|
||||
panic!("view_image should be a function tool");
|
||||
};
|
||||
let (properties, _) = expect_object_schema(parameters);
|
||||
assert!(properties.contains_key("environment_id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_environment_omits_environment_backed_tools() {
|
||||
let model_info = model_info();
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::collections::BTreeMap;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ViewImageToolOptions {
|
||||
pub can_request_original_image_detail: bool,
|
||||
pub multi_environment_tools: bool,
|
||||
}
|
||||
|
||||
pub fn create_view_image_tool(options: ViewImageToolOptions) -> ToolSpec {
|
||||
@@ -24,6 +25,14 @@ pub fn create_view_image_tool(options: ViewImageToolOptions) -> ToolSpec {
|
||||
)),
|
||||
);
|
||||
}
|
||||
if options.multi_environment_tools {
|
||||
properties.insert(
|
||||
"environment_id".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Optional selected environment id to load the image from; omit to use the primary environment.".to_string(),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: VIEW_IMAGE_TOOL_NAME.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user