feat: Freeform apply_patch with simple shell output (#4718)

## Summary
This PR is an alternative approach to #4711, but instead of changing our
storage, parses out shell calls in the client and reserializes them on
the fly before we send them out as part of the request.

What this changes:
1. Adds additional serialization logic when the
ApplyPatchToolType::Freeform is in use.
2. Adds a --custom-apply-patch flag to enable this setting on a
session-by-session basis.

This change is delicate, but is not meant to be permanent. It is meant
to be the first step in a migration:
1. (This PR) Add in-flight serialization with config
2. Update model_family default
3. Update serialization logic to store turn outputs in a structured
format, with logic to serialize based on model_family setting.
4. Remove this rewrite in-flight logic.

## Test Plan
- [x] Additional unit tests added
- [x] Integration tests added
- [x] Tested locally
This commit is contained in:
Dylan
2025-10-04 19:16:36 -07:00
committed by GitHub
parent 90ef94d3b3
commit 4764fc1ee7
11 changed files with 420 additions and 15 deletions

View File

@@ -106,7 +106,7 @@ pub enum ApplyPatchToolType {
pub(crate) fn create_apply_patch_freeform_tool() -> ToolSpec {
ToolSpec::Freeform(FreeformTool {
name: "apply_patch".to_string(),
description: "Use the `apply_patch` tool to edit files".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(),
format: FreeformToolFormat {
r#type: "grammar".to_string(),
syntax: "lark".to_string(),

View File

@@ -695,7 +695,7 @@ mod tests {
});
let (tools, _) = build_specs(&config, Some(HashMap::new())).build();
assert_eq_tool_names(&tools, &["unified_exec", "read_file"]);
assert_eq_tool_names(&tools, &["unified_exec", "apply_patch", "read_file"]);
}
#[test]
@@ -921,6 +921,7 @@ mod tests {
&tools,
&[
"unified_exec",
"apply_patch",
"read_file",
"web_search",
"view_image",
@@ -929,7 +930,7 @@ mod tests {
);
assert_eq!(
tools[4],
tools[5],
ToolSpec::Function(ResponsesApiTool {
name: "dash/search".to_string(),
parameters: JsonSchema::Object {
@@ -988,6 +989,7 @@ mod tests {
&tools,
&[
"unified_exec",
"apply_patch",
"read_file",
"web_search",
"view_image",
@@ -995,7 +997,7 @@ mod tests {
],
);
assert_eq!(
tools[4],
tools[5],
ToolSpec::Function(ResponsesApiTool {
name: "dash/paginate".to_string(),
parameters: JsonSchema::Object {
@@ -1019,7 +1021,7 @@ mod tests {
let config = ToolsConfig::new(&ToolsConfigParams {
model_family: &model_family,
include_plan_tool: false,
include_apply_patch_tool: false,
include_apply_patch_tool: true,
include_web_search_request: true,
use_streamable_shell_tool: false,
include_view_image_tool: true,
@@ -1052,6 +1054,7 @@ mod tests {
&tools,
&[
"unified_exec",
"apply_patch",
"read_file",
"web_search",
"view_image",
@@ -1059,7 +1062,7 @@ mod tests {
],
);
assert_eq!(
tools[4],
tools[5],
ToolSpec::Function(ResponsesApiTool {
name: "dash/tags".to_string(),
parameters: JsonSchema::Object {
@@ -1119,6 +1122,7 @@ mod tests {
&tools,
&[
"unified_exec",
"apply_patch",
"read_file",
"web_search",
"view_image",
@@ -1126,7 +1130,7 @@ mod tests {
],
);
assert_eq!(
tools[4],
tools[5],
ToolSpec::Function(ResponsesApiTool {
name: "dash/value".to_string(),
parameters: JsonSchema::Object {
@@ -1223,6 +1227,7 @@ mod tests {
&tools,
&[
"unified_exec",
"apply_patch",
"read_file",
"web_search",
"view_image",
@@ -1231,7 +1236,7 @@ mod tests {
);
assert_eq!(
tools[4],
tools[5],
ToolSpec::Function(ResponsesApiTool {
name: "test_server/do_something_cool".to_string(),
parameters: JsonSchema::Object {