mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
* lint: simplify hooks already covered by Ruff * prune dev dependency * setting E, W, F * poetry? * autopep8 * quote-style = "single" * double-quote-string-fixer * --all-files * apply * Q * drop double-quote-string-fixer * --all-files * apply pre-commit * python3.11 -m poetry lock --no-update --------- Co-authored-by: Robert Brennan <accounts@rbren.io>
165 lines
5.3 KiB
Python
165 lines
5.3 KiB
Python
from opendevin.action import (
|
|
Action,
|
|
AgentFinishAction,
|
|
CmdRunAction,
|
|
FileReadAction,
|
|
FileWriteAction,
|
|
BrowseURLAction,
|
|
AgentEchoAction,
|
|
AgentThinkAction,
|
|
)
|
|
|
|
import re
|
|
|
|
from .prompts import CUSTOM_DOCS, COMMAND_USAGE
|
|
|
|
# commands: exit, read, write, browse, kill, search_file, search_dir
|
|
|
|
no_open_file_error = AgentEchoAction(
|
|
'You are not currently in a file. You can use the read command to open a file and then use goto to navigate through it.')
|
|
|
|
|
|
def invalid_error(cmd, docs):
|
|
return f"""ERROR:
|
|
Invalid command structure for
|
|
```
|
|
{cmd}
|
|
```
|
|
You may have caused this error by having multiple commands within your command block.
|
|
If so, try again by running only one of the commands:
|
|
|
|
Try again using this format:
|
|
{COMMAND_USAGE[docs]}
|
|
"""
|
|
|
|
|
|
def get_action_from_string(command_string: str, path: str, line: int, thoughts: str = '') -> Action | None:
|
|
"""
|
|
Parses the command string to find which command the agent wants to run
|
|
Converts the command into a proper Action and returns
|
|
"""
|
|
vars = command_string.split(' ')
|
|
cmd = vars[0]
|
|
args = [] if len(vars) == 1 else vars[1:]
|
|
|
|
if 'exit' == cmd:
|
|
return AgentFinishAction()
|
|
|
|
elif 'think' == cmd:
|
|
return AgentThinkAction(' '.join(args))
|
|
|
|
elif 'scroll_up' == cmd:
|
|
if not path:
|
|
return no_open_file_error
|
|
return FileReadAction(path, line + 100, line + 200, thoughts)
|
|
|
|
elif 'scroll_down' == cmd:
|
|
if not path:
|
|
return no_open_file_error
|
|
return FileReadAction(path, line - 100, line, thoughts)
|
|
|
|
elif 'goto' == cmd:
|
|
if not path:
|
|
return no_open_file_error
|
|
rex = r'^goto\s+(\d+)$'
|
|
valid = re.match(rex, command_string)
|
|
if valid:
|
|
start = int(valid.group(1))
|
|
end = start + 100
|
|
return FileReadAction(path, start, end, thoughts)
|
|
else:
|
|
return AgentEchoAction(invalid_error(command_string, 'goto'))
|
|
|
|
elif 'edit' == cmd:
|
|
if not path:
|
|
return no_open_file_error
|
|
rex = r'^edit\s+(\d+)\s+(-?\d+)\s+(\S.*)$'
|
|
valid = re.match(rex, command_string, re.DOTALL)
|
|
if valid:
|
|
start = int(valid.group(1))
|
|
end = int(valid.group(2))
|
|
change = valid.group(3)
|
|
if '"' == change[-1] and '"' == change[0]:
|
|
change = change[1:-1]
|
|
return FileWriteAction(path, change, start, end, thoughts)
|
|
else:
|
|
return AgentEchoAction(invalid_error(command_string, 'edit'))
|
|
|
|
elif 'read' == cmd:
|
|
rex = r'^read\s+(\S+)(?:\s+(\d+))?(?:\s+(-?\d+))?$'
|
|
valid = re.match(rex, command_string, re.DOTALL)
|
|
if valid:
|
|
file = valid.group(1)
|
|
start_str = valid.group(2)
|
|
end_str = valid.group(3)
|
|
|
|
start = 0 if not start_str else int(start_str)
|
|
end = -1 if not end_str else int(end_str)
|
|
|
|
return FileReadAction(file, start, end, thoughts)
|
|
else:
|
|
return AgentEchoAction(invalid_error(command_string, 'read'))
|
|
|
|
elif 'write' == cmd:
|
|
rex = r'^write\s+(\S+)\s+(.*?)\s*(\d+)?\s*(-?\d+)?$'
|
|
valid = re.match(rex, command_string, re.DOTALL)
|
|
|
|
if valid:
|
|
file = valid.group(1)
|
|
content = valid.group(2)
|
|
start_str = valid.group(3)
|
|
end_str = valid.group(4)
|
|
|
|
start = 0 if not start_str else int(start_str)
|
|
end = -1 if not end_str else int(end_str)
|
|
|
|
if '"' == content[-1] and '"' == content[0]:
|
|
content = content[1:-1]
|
|
|
|
return FileWriteAction(file, content, start, end, thoughts)
|
|
else:
|
|
return AgentEchoAction(invalid_error(command_string, 'write'))
|
|
|
|
elif 'browse' == cmd:
|
|
return BrowseURLAction(args[0].strip())
|
|
|
|
elif cmd in ['search_file', 'search_dir', 'find_file']:
|
|
rex = r'^(search_file|search_dir|find_file)\s+(\S+)(?:\s+(\S+))?$'
|
|
valid = re.match(rex, command_string, re.DOTALL)
|
|
if valid:
|
|
return CmdRunAction(command_string)
|
|
else:
|
|
return AgentEchoAction(f'Invalid command structure for\n ```\n{command_string}\n```.\nTry again using this format:\n{CUSTOM_DOCS}')
|
|
else:
|
|
# check bash command
|
|
obs = str(CmdRunAction(f'type {cmd}'))
|
|
if obs.split(':')[-1].strip() == 'not found':
|
|
# echo not found error for llm
|
|
return AgentEchoAction(content=obs)
|
|
else:
|
|
# run valid command
|
|
return CmdRunAction(command_string)
|
|
|
|
|
|
def parse_command(input_str: str, path: str, line: int):
|
|
"""
|
|
Parses a given string and separates the command (enclosed in triple backticks) from any accompanying text.
|
|
|
|
Args:
|
|
input_str (str): The input string to be parsed.
|
|
|
|
Returns:
|
|
tuple: A tuple containing the command and the accompanying text (if any).
|
|
"""
|
|
input_str = input_str.strip()
|
|
if '```' in input_str:
|
|
parts = input_str.split('```')
|
|
command_str = parts[1].strip()
|
|
ind = 2 if len(parts) > 2 else 1
|
|
accompanying_text = ''.join(parts[:-ind]).strip()
|
|
action = get_action_from_string(
|
|
command_str, path, line, accompanying_text)
|
|
if action:
|
|
return action, accompanying_text
|
|
return None, input_str # used for retry
|