Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c0648de
[TEMP] Commit `apply_patch` Convo from ChatGPT for Reference
GuyPaddock Dec 17, 2025
48d3205
feat(apply_patch): Replace str_replace editor with apply_patch tool
GuyPaddock Dec 17, 2025
2270206
test(apply_patch): Add apply_patch tests and refresh tool examples
GuyPaddock Dec 17, 2025
8c9ca06
feat(view_file): Add view_file tool for line-numbered file inspection
GuyPaddock Dec 20, 2025
1d79a2e
fix(apply_patch): Fix context handling for multi-block hunks
GuyPaddock Dec 20, 2025
3fa7a13
fix(fn_call_converter_examples): Handle `view_file` in call examples
GuyPaddock Dec 20, 2025
9c65676
fix(replay trajectories): Align with apply_patch tool args
GuyPaddock Dec 20, 2025
fae2607
fix(README): correct bullet indents
GuyPaddock Dec 21, 2025
5c48c27
feat(apply_patch.sh): Add wrapper for apply_patch CLI
GuyPaddock Dec 21, 2025
8eac0e5
fix(apply_patch): Resolve modules relative to repo root
GuyPaddock Dec 21, 2025
35b10ee
refactor(apply_patch): Inline apply_patch execution
GuyPaddock Dec 21, 2025
01471f3
refactor(apply_patch): Simplify invocation
GuyPaddock Dec 21, 2025
5d32064
refactor(ApplyPatchAction): Directly execute apply_patch from an Action
GuyPaddock Dec 21, 2025
2ec50e4
test(apply_patch): Update apply_patch examples and trajectories
GuyPaddock Dec 21, 2025
c39d4cc
fix(security): Restrict `apply_patch` targets to within repo root
GuyPaddock Dec 21, 2025
72ec38e
chore(str_replace_editor): Remove unused/replaced editor tool
GuyPaddock Dec 21, 2025
76cd1e5
feat(SuccessObservation): Handle success observations
GuyPaddock Dec 21, 2025
1df8d8b
fix(apply_patch): Track patch tool responses in conversation memory
GuyPaddock Dec 21, 2025
cb53e26
feat(apply_patch): Format observations for readable tool output
GuyPaddock Dec 21, 2025
bc137d4
feat(apply_patch): Improve patch observation formatting
GuyPaddock Dec 21, 2025
833a8d3
fix(apply_patch): Handle leading blank lines in patch input
GuyPaddock Dec 21, 2025
4c358bc
feat(apply_patch): Render patch observations cleanly in UI
GuyPaddock Dec 21, 2025
9257c61
feat(apply_patch): Add patch-specific observation types
GuyPaddock Dec 21, 2025
574b921
refactor(apply_patch): Simplify observation typing and keep failed pa…
GuyPaddock Dec 21, 2025
83dcc57
refactor(apply_patch): Streamline observations and remove generic suc…
GuyPaddock Dec 21, 2025
090742d
refactor(observations): Streamline frontend handling of patch observ.
GuyPaddock Dec 21, 2025
a2d1e26
fix(pyproject): Remove unnecessary poetry script decl. of `apply_patch`
GuyPaddock Dec 21, 2025
e1ddaf8
fix(translation,apply_patch): Add translations for ApplyPatch outcomes
GuyPaddock Dec 21, 2025
0f745ae
fix(SuccessObservation): Restore lost `SuccessObservation` code
GuyPaddock Dec 21, 2025
a30dcba
feat(apply_patch): Expand LLM guidance on how to use tool
GuyPaddock Dec 21, 2025
5160559
refactor(ApplyPatchObservation): Consolidate success and error observat.
GuyPaddock Dec 21, 2025
d9abba8
feat(translation): Display proper UI text when applying patches
GuyPaddock Dec 21, 2025
8ab4e87
refactor(ApplyPatchObservation): Clean up data shape & UI display
GuyPaddock Dec 21, 2025
5e448fb
chore(patch_id): Remove unused "Patch-ID" in Patches
GuyPaddock Dec 21, 2025
d94e747
chore(ApplyPatchAction): Move to separate action file
GuyPaddock Dec 21, 2025
7eb200f
fix(apply_patch,ui,v0): Display "Apply patch" properly for v0 conversat.
GuyPaddock Dec 21, 2025
c46382c
fix(apply_patch,observation_content,v0): Correct loop structure
GuyPaddock Dec 21, 2025
0077cd2
feat(apply_patch): Include attempted patch in error output
GuyPaddock Dec 22, 2025
57b0084
feat(apply_patch): Advise LLM about context line formatting
GuyPaddock Dec 22, 2025
2cb0ffa
chore(apply_patch): Clean up quote formatting
GuyPaddock Dec 22, 2025
dda2a81
fix(apply_patch): Remove Redundant Tool Call Example & Revise Other Exa.
GuyPaddock Dec 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 6940ed8d-87ac-8331-8c1a-653841289646.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ enable_browsing = true
# Whether the LLM draft editor is enabled
enable_llm_editor = false

# Whether the standard editor tool (str_replace_editor) is enabled
# Whether the standard editor tool (apply_patch) is enabled
# Only has an effect if enable_llm_editor is False
enable_editor = true

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ApplyPatchObservation,
ReadObservation,
CommandObservation,
IPythonObservation,
Expand All @@ -12,6 +13,18 @@ import { getObservationResult } from "./get-observation-result";
import { getDefaultEventContent, MAX_CONTENT_LENGTH } from "./shared";
import i18n from "#/i18n";

const getApplyPatchObservationContent = (event: ApplyPatchObservation): string => {
let contentDetails = '';

event?.extras?.applied_hunks?.forEach(({ action, file }) => {
contentDetails += `✔ ${action} \`${file}\`\n`;
});

contentDetails += `\n\`\`\`${event.extras.patch || ""}\n\`\`\``

return contentDetails;
}

const getReadObservationContent = (event: ReadObservation): string =>
`\`\`\`\n${event.content}\n\`\`\``;

Expand Down Expand Up @@ -142,6 +155,8 @@ const getTaskTrackingObservationContent = (

export const getObservationContent = (event: OpenHandsObservation): string => {
switch (event.observation) {
case "apply_patch":
return getApplyPatchObservationContent(event);
case "read":
return getReadObservationContent(event);
case "edit":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const getObservationResult = (event: OpenHandsObservation) => {
return hasContent ? "success" : "error";
}
return hasContent ? "success" : "error";
case "apply_patch":
return "success";
default:
return "success";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ const getActionEventTitle = (event: OpenHandsEvent): React.ReactNode => {
let actionValues: Record<string, unknown> = {};

switch (actionType) {
case "ApplyPatchAction":
actionKey = "ACTION_MESSAGE$APPLY_PATCH";
break;
case "ExecuteBashAction":
actionKey = "ACTION_MESSAGE$RUN";
actionValues = {
Expand Down Expand Up @@ -109,6 +112,9 @@ const getObservationEventTitle = (event: OpenHandsEvent): React.ReactNode => {
let observationValues: Record<string, unknown> = {};

switch (observationType) {
case "ApplyPatchObservation":
actionKey = "OBSERVATION_MESSAGE$APPLY_PATCH";
break;
case "ExecuteBashObservation":
observationKey = "OBSERVATION_MESSAGE$RUN";
observationValues = {
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/i18n/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -8079,6 +8079,22 @@
"tr": "<path>{{path}}</path> dosyasına yazılıyor",
"uk": "Записую в <path>{{path}}</path>"
},
"ACTION_MESSAGE$APPLY_PATCH": {
"en": "Applying patch",
"ja": "パッチを適用中",
"zh-CN": "正在应用补丁",
"zh-TW": "正在套用修補程式",
"ko-KR": "패치를 적용 중입니다",
"no": "Patch brukes",
"ar": "جارٍ تطبيق التصحيح",
"de": "Patch wird angewendet",
"fr": "Application du correctif",
"it": "Applicazione della patch",
"pt": "Aplicando correção",
"es": "Aplicando parche",
"tr": "Yama uygulanıyor",
"uk": "Застосовується патч"
},
"ACTION_MESSAGE$BROWSE": {
"en": "Browsing the web",
"zh-CN": "浏览",
Expand Down Expand Up @@ -8255,6 +8271,22 @@
"tr": "<path>{{path}}</path> dosyasına yazıldı",
"uk": "Записав на <path>{{path}}</path>"
},
"OBSERVATION_MESSAGE$APPLY_PATCH": {
"en": "Applied patch",
"ja": "パッチを適用しました",
"zh-CN": "已应用补丁",
"zh-TW": "已套用修補程式",
"ko-KR": "패치를 적용했습니다",
"no": "Patch ble brukt",
"ar": "تم تطبيق التصحيح",
"de": "Patch angewendet",
"fr": "Correctif appliqué",
"it": "Patch applicata",
"pt": "Correção aplicada",
"es": "Parche aplicado",
"tr": "Yama uygulandı",
"uk": "Патч застосовано"
},
"OBSERVATION_MESSAGE$BROWSE": {
"en": "Browsing completed",
"zh-CN": "浏览",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/mocks/mock-ws-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const createMockAgentErrorEvent = (
id: "error-event-123",
timestamp: new Date().toISOString(),
source: "agent",
tool_name: "str_replace_editor",
tool_name: "apply_patch",
tool_call_id: "tool-call-456",
error: "Failed to execute command: Permission denied",
...overrides,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/core/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export type OpenHandsEventType =
| "think"
| "finish"
| "error"
| "patch_error"
| "patch_success"
| "recall"
| "mcp"
| "call_tool_mcp"
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/types/core/observations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ export interface ErrorObservation extends OpenHandsObservationEvent<"error"> {
};
}

export interface ApplyPatchObservation extends OpenHandsObservationEvent<"apply_patch"> {
source: "agent";
extras: {
patch: string;
applied_hunks: {};
};
}

export interface AgentThinkObservation
extends OpenHandsObservationEvent<"think"> {
source: "agent";
Expand Down Expand Up @@ -173,6 +181,7 @@ export type OpenHandsObservation =
| ReadObservation
| EditObservation
| ErrorObservation
| ApplyPatchObservation
| RecallObservation
| MCPObservation
| UserRejectedObservation
Expand Down
2 changes: 1 addition & 1 deletion openhands/agenthub/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Here is a list of available Observations:
- [`FileReadObservation`](../events/observation/files.py)
- [`FileWriteObservation`](../events/observation/files.py)
- [`ErrorObservation`](../events/observation/error.py)
- [`SuccessObservation`](../events/observation/success.py)
- [`ApplyPatchObservation`](../events/observation/apply_patch.py)

You can use `observation.to_dict()` and `observation_from_dict` to serialize and deserialize observations.

Expand Down
19 changes: 11 additions & 8 deletions openhands/agenthub/codeact_agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ The CodeAct agent operates through a function calling interface. At each turn, t
- Execute Linux `bash` commands with `execute_bash`
- Run Python code in an [IPython](https://ipython.org/) environment with `execute_ipython_cell`
- Interact with web browsers using `browser` and `fetch`
- Edit files using `str_replace_editor` or `edit_file`
- Edit files using `apply_patch` or `edit_file`
- View files with line numbers using `view_file`

![image](https://github.com/OpenHands/OpenHands/assets/38853559/92b622e3-72ad-4a61-8f41-8c040b6d5fb3)

Expand All @@ -37,14 +38,16 @@ The agent provides several built-in tools:
- Supports common browser actions like navigation, clicking, form filling, scrolling
- Handles file uploads and drag-and-drop operations

### 4. `str_replace_editor`
- View, create and edit files through string replacement
- Persistent state across command calls
- File viewing with line numbers
- String replacement with exact matching
- Undo functionality for edits
### 4. `view_file`
- View files (with line numbers) or list directories
- Supports optional line ranges
- Works with absolute or workspace-relative paths

### 5. `edit_file` (LLM-based)
### 5. `apply_patch`
- Atomic patch-based editing for create/update/delete
- Structured JSON diagnostics when available

### 6. `edit_file` (LLM-based)
- Edit files using LLM-based content generation
- Support for partial file edits with line ranges
- Handles large files by editing specific sections
Expand Down
14 changes: 6 additions & 8 deletions openhands/agenthub/codeact_agent/codeact_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
from openhands.agenthub.codeact_agent.tools.finish import FinishTool
from openhands.agenthub.codeact_agent.tools.ipython import IPythonTool
from openhands.agenthub.codeact_agent.tools.llm_based_edit import LLMBasedFileEditTool
from openhands.agenthub.codeact_agent.tools.str_replace_editor import (
create_str_replace_editor_tool,
from openhands.agenthub.codeact_agent.tools.apply_patch import (
create_apply_patch_tool,
)
from openhands.agenthub.codeact_agent.tools.task_tracker import (
create_task_tracker_tool,
)
from openhands.agenthub.codeact_agent.tools.think import ThinkTool
from openhands.agenthub.codeact_agent.tools.view import create_view_file_tool
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
Expand Down Expand Up @@ -141,15 +142,12 @@ def _get_tools(self) -> list['ChatCompletionToolParam']:
if self.config.enable_plan_mode:
# In plan mode, we use the task_tracker tool for task management
tools.append(create_task_tracker_tool(use_short_tool_desc))
if self.config.enable_editor or self.config.enable_llm_editor:
tools.append(create_view_file_tool())
if self.config.enable_llm_editor:
tools.append(LLMBasedFileEditTool)
elif self.config.enable_editor:
tools.append(
create_str_replace_editor_tool(
use_short_description=use_short_tool_desc,
runtime_type=self.config.runtime,
)
)
tools.append(create_apply_patch_tool())
return tools

def reset(self) -> None:
Expand Down
66 changes: 14 additions & 52 deletions openhands/agenthub/codeact_agent/function_calling.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
LLMBasedFileEditTool,
ThinkTool,
create_cmd_run_tool,
create_str_replace_editor_tool,
create_view_file_tool,
)
from openhands.agenthub.codeact_agent.tools.security_utils import RISK_LEVELS
from openhands.core.exceptions import (
Expand All @@ -31,6 +31,7 @@
AgentDelegateAction,
AgentFinishAction,
AgentThinkAction,
ApplyPatchAction,
BrowseInteractiveAction,
CmdRunAction,
FileEditAction,
Expand All @@ -43,8 +44,7 @@
from openhands.events.action.mcp import MCPAction
from openhands.events.event import FileEditSource, FileReadSource
from openhands.events.tool import ToolCallMetadata
from openhands.llm.tool_names import TASK_TRACKER_TOOL_NAME

from openhands.llm.tool_names import APPLY_PATCH_TOOL_NAME, TASK_TRACKER_TOOL_NAME

def combine_thought(action: Action, thought: str) -> Action:
if not hasattr(action, 'thought'):
Expand Down Expand Up @@ -170,62 +170,24 @@ def response_to_actions(
'impl_source', FileEditSource.LLM_BASED_EDIT
),
)
elif (
tool_call.function.name
== create_str_replace_editor_tool()['function']['name']
):
if 'command' not in arguments:
elif tool_call.function.name == APPLY_PATCH_TOOL_NAME:
if 'patch' not in arguments:
raise FunctionCallValidationError(
f'Missing required argument "command" in tool call {tool_call.function.name}'
f'Missing required argument "patch" in tool call {tool_call.function.name}'
)
action = ApplyPatchAction(patch=arguments['patch'].rstrip('\n'))
set_security_risk(action, arguments)
elif tool_call.function.name == create_view_file_tool()['function']['name']:
if 'path' not in arguments:
raise FunctionCallValidationError(
f'Missing required argument "path" in tool call {tool_call.function.name}'
)
path = arguments['path']
command = arguments['command']
other_kwargs = {
k: v for k, v in arguments.items() if k not in ['command', 'path']
}

if command == 'view':
action = FileReadAction(
path=path,
impl_source=FileReadSource.OH_ACI,
view_range=other_kwargs.get('view_range', None),
)
else:
if 'view_range' in other_kwargs:
# Remove view_range from other_kwargs since it is not needed for FileEditAction
other_kwargs.pop('view_range')

# Filter out unexpected arguments
valid_kwargs_for_editor = {}
# Get valid parameters from the str_replace_editor tool definition
str_replace_editor_tool = create_str_replace_editor_tool()
valid_params = set(
str_replace_editor_tool['function']['parameters'][
'properties'
].keys()
)

for key, value in other_kwargs.items():
if key in valid_params:
# security_risk is valid but should NOT be part of editor kwargs
if key != 'security_risk':
valid_kwargs_for_editor[key] = value
else:
raise FunctionCallValidationError(
f'Unexpected argument {key} in tool call {tool_call.function.name}. Allowed arguments are: {valid_params}'
)

action = FileEditAction(
path=path,
command=command,
impl_source=FileEditSource.OH_ACI,
**valid_kwargs_for_editor,
)

action = FileReadAction(
path=arguments['path'],
impl_source=FileReadSource.OH_ACI,
view_range=arguments.get('view_range'),
)
set_security_risk(action, arguments)
# ================================================
# AgentThinkAction
Expand Down
Loading
Loading