parser.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from opendevin.action import (
  2. Action,
  3. AgentFinishAction,
  4. CmdRunAction,
  5. FileReadAction,
  6. FileWriteAction,
  7. BrowseURLAction,
  8. AgentEchoAction,
  9. AgentThinkAction,
  10. )
  11. import re
  12. from .prompts import CUSTOM_DOCS, COMMAND_USAGE
  13. # commands: exit, read, write, browse, kill, search_file, search_dir
  14. no_open_file_error = AgentEchoAction(
  15. '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.')
  16. def invalid_error(cmd, docs):
  17. return f"""ERROR:
  18. Invalid command structure for
  19. ```
  20. {cmd}
  21. ```
  22. You may have caused this error by having multiple commands within your command block.
  23. If so, try again by running only one of the commands:
  24. Try again using this format:
  25. {COMMAND_USAGE[docs]}
  26. """
  27. def get_action_from_string(command_string: str, path: str, line: int, thoughts: str = '') -> Action | None:
  28. """
  29. Parses the command string to find which command the agent wants to run
  30. Converts the command into a proper Action and returns
  31. """
  32. vars = command_string.split(' ')
  33. cmd = vars[0]
  34. args = [] if len(vars) == 1 else vars[1:]
  35. if 'exit' == cmd:
  36. return AgentFinishAction()
  37. elif 'think' == cmd:
  38. return AgentThinkAction(' '.join(args))
  39. elif 'scroll_up' == cmd:
  40. if not path:
  41. return no_open_file_error
  42. return FileReadAction(path, line + 100, line + 200, thoughts)
  43. elif 'scroll_down' == cmd:
  44. if not path:
  45. return no_open_file_error
  46. return FileReadAction(path, line - 100, line, thoughts)
  47. elif 'goto' == cmd:
  48. if not path:
  49. return no_open_file_error
  50. rex = r'^goto\s+(\d+)$'
  51. valid = re.match(rex, command_string)
  52. if valid:
  53. start = int(valid.group(1))
  54. end = start + 100
  55. return FileReadAction(path, start, end, thoughts)
  56. else:
  57. return AgentEchoAction(invalid_error(command_string, 'goto'))
  58. elif 'edit' == cmd:
  59. if not path:
  60. return no_open_file_error
  61. rex = r'^edit\s+(\d+)\s+(-?\d+)\s+(\S.*)$'
  62. valid = re.match(rex, command_string, re.DOTALL)
  63. if valid:
  64. start = int(valid.group(1))
  65. end = int(valid.group(2))
  66. change = valid.group(3)
  67. if '"' == change[-1] and '"' == change[0]:
  68. change = change[1:-1]
  69. return FileWriteAction(path, change, start, end, thoughts)
  70. else:
  71. return AgentEchoAction(invalid_error(command_string, 'edit'))
  72. elif 'read' == cmd:
  73. rex = r'^read\s+(\S+)(?:\s+(\d+))?(?:\s+(-?\d+))?$'
  74. valid = re.match(rex, command_string, re.DOTALL)
  75. if valid:
  76. file = valid.group(1)
  77. start_str = valid.group(2)
  78. end_str = valid.group(3)
  79. start = 0 if not start_str else int(start_str)
  80. end = -1 if not end_str else int(end_str)
  81. return FileReadAction(file, start, end, thoughts)
  82. else:
  83. return AgentEchoAction(invalid_error(command_string, 'read'))
  84. elif 'write' == cmd:
  85. rex = r'^write\s+(\S+)\s+(.*?)\s*(\d+)?\s*(-?\d+)?$'
  86. valid = re.match(rex, command_string, re.DOTALL)
  87. if valid:
  88. file = valid.group(1)
  89. content = valid.group(2)
  90. start_str = valid.group(3)
  91. end_str = valid.group(4)
  92. start = 0 if not start_str else int(start_str)
  93. end = -1 if not end_str else int(end_str)
  94. if '"' == content[-1] and '"' == content[0]:
  95. content = content[1:-1]
  96. return FileWriteAction(file, content, start, end, thoughts)
  97. else:
  98. return AgentEchoAction(invalid_error(command_string, 'write'))
  99. elif 'browse' == cmd:
  100. return BrowseURLAction(args[0].strip())
  101. elif cmd in ['search_file', 'search_dir', 'find_file']:
  102. rex = r'^(search_file|search_dir|find_file)\s+(\S+)(?:\s+(\S+))?$'
  103. valid = re.match(rex, command_string, re.DOTALL)
  104. if valid:
  105. return CmdRunAction(command_string)
  106. else:
  107. return AgentEchoAction(f'Invalid command structure for\n ```\n{command_string}\n```.\nTry again using this format:\n{CUSTOM_DOCS}')
  108. else:
  109. # check bash command
  110. obs = str(CmdRunAction(f'type {cmd}'))
  111. if obs.split(':')[-1].strip() == 'not found':
  112. # echo not found error for llm
  113. return AgentEchoAction(content=obs)
  114. else:
  115. # run valid command
  116. return CmdRunAction(command_string)
  117. def parse_command(input_str: str, path: str, line: int):
  118. """
  119. Parses a given string and separates the command (enclosed in triple backticks) from any accompanying text.
  120. Args:
  121. input_str (str): The input string to be parsed.
  122. Returns:
  123. tuple: A tuple containing the command and the accompanying text (if any).
  124. """
  125. input_str = input_str.strip()
  126. if '```' in input_str:
  127. parts = input_str.split('```')
  128. command_str = parts[1].strip()
  129. ind = 2 if len(parts) > 2 else 1
  130. accompanying_text = ''.join(parts[:-ind]).strip()
  131. action = get_action_from_string(
  132. command_str, path, line, accompanying_text)
  133. if action:
  134. return action, accompanying_text
  135. return None, input_str # used for retry