mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2b49525ae | |||
| 478ebedd41 | |||
| 016a56a3d9 | |||
| 545327cc1e | |||
| c7dd443fa2 | |||
| c50319138e | |||
| d80f025e21 | |||
| 442ab7371c | |||
| 1c7cdbefdd | |||
| ea214d1c07 | |||
| 62e4fb47b2 | |||
| 5f2d6f58f2 | |||
| 0cda5f64af | |||
| eb1c3d8790 | |||
| fe43aeb9b6 | |||
| ccbbabac1c | |||
| eb7703a44e | |||
| 31c1a2d748 |
@@ -12,7 +12,7 @@ body:
|
||||
label: Is there an existing issue for the same bug?
|
||||
description: Please check if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have checked the troubleshooting document at https://github.com/OpenDevin/OpenDevin/blob/main/docs/guides/Troubleshooting.md
|
||||
- label: I have checked the troubleshooting document at https://opendevin.github.io/OpenDevin/modules/usage/troubleshooting
|
||||
required: true
|
||||
- label: I have checked the existing issues.
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Run e2e test with dummy agent
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Set up environment
|
||||
run: |
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
poetry install --without evaluation
|
||||
wget https://huggingface.co/BAAI/bge-small-en-v1.5/raw/main/1_Pooling/config.json -P /tmp/llama_index/models--BAAI--bge-small-en-v1.5/snapshots/5c38ec7c405ec4b44b94cc5a9bb96e735b38267a/1_Pooling/
|
||||
- name: Run tests
|
||||
run: |
|
||||
poetry run python opendevin/main.py -t "do a flip" -m ollama/not-a-model -d ./workspace/ -c DummyAgent
|
||||
@@ -42,8 +42,21 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Delete huge unnecessary tools folder
|
||||
run: rm -rf /opt/hostedtoolcache
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: true
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: false
|
||||
swap-storage: true
|
||||
|
||||
- name: Build and push ${{ matrix.image }}
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
|
||||
@@ -34,11 +34,16 @@ jobs:
|
||||
brew install colima docker
|
||||
colima start
|
||||
|
||||
# For testcontainers to find the Colima socket
|
||||
# https://github.com/abiosoft/colima/blob/main/docs/FAQ.md#cannot-connect-to-the-docker-daemon-at-unixvarrundockersock-is-the-docker-daemon-running
|
||||
sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock
|
||||
|
||||
- name: Build Environment
|
||||
run: make build
|
||||
|
||||
- name: Run Tests
|
||||
run: poetry run pytest --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
@@ -70,6 +75,7 @@ jobs:
|
||||
|
||||
- name: Run Tests
|
||||
run: poetry run pytest --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
|
||||
@@ -82,3 +82,15 @@ If you encounter any issues with the Language Model (LM) or you're simply curiou
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
|
||||
### 8. Testing
|
||||
|
||||
#### Unit tests
|
||||
|
||||
```bash
|
||||
poetry run pytest ./tests/unit/test_sandbox.py
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Please refer to [this README](./tests/integration/README.md) for details.
|
||||
|
||||
@@ -235,6 +235,12 @@ clean:
|
||||
@rm -rf opendevin/.cache
|
||||
@echo "$(GREEN)Caches cleaned up successfully.$(RESET)"
|
||||
|
||||
python-docs:
|
||||
@echo "$(YELLOW)Generating documentation...$(RESET)"
|
||||
@rm -rf docs/modules/python
|
||||
@pydoc-markdown
|
||||
@echo "$(GREEN)Documentation generated successfully.$(RESET)"
|
||||
|
||||
# Help
|
||||
help:
|
||||
@echo "$(BLUE)Usage: make [target]$(RESET)"
|
||||
|
||||
@@ -26,6 +26,7 @@ The `state` contains:
|
||||
Here is a list of available Actions, which can be returned by `agent.step()`:
|
||||
- [`CmdRunAction`](../opendevin/action/bash.py) - Runs a command inside a sandboxed terminal
|
||||
- [`CmdKillAction`](../opendevin/action/bash.py) - Kills a background command
|
||||
- [`IPythonRunCellAction`](../opendevin/action/bash.py) - Execute a block of Python code interactively (in Jupyter notebook) and receives `CmdOutputObservation`. Requires setting up `jupyter` [plugin](../opendevin/sandbox/plugins) as a requirement.
|
||||
- [`FileReadAction`](../opendevin/action/fileop.py) - Reads the content of a file
|
||||
- [`FileWriteAction`](../opendevin/action/fileop.py) - Writes new content to a file
|
||||
- [`BrowseURLAction`](../opendevin/action/browse.py) - Gets the content of a URL
|
||||
@@ -33,6 +34,7 @@ Here is a list of available Actions, which can be returned by `agent.step()`:
|
||||
- [`AddTaskAction`](../opendevin/action/tasks.py) - Adds a subtask to the plan
|
||||
- [`ModifyTaskAction`](../opendevin/action/tasks.py) - Changes the state of a subtask
|
||||
- [`AgentThinkAction`](../opendevin/action/agent.py) - A no-op that allows the agent to add plaintext to the history (as well as the chat log)
|
||||
- [`AgentTalkAction`](../opendevin/action/agent.py) - A no-op that allows the agent to add plaintext to the history and talk to the user.
|
||||
- [`AgentFinishAction`](../opendevin/action/agent.py) - Stops the control loop, allowing the user to enter a new task
|
||||
|
||||
You can use `action.to_dict()` and `action_from_dict` to serialize and deserialize actions.
|
||||
|
||||
@@ -8,17 +8,20 @@ from .micro.registry import all_microagents
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# Import agents after environment variables are loaded
|
||||
|
||||
from . import ( # noqa: E402
|
||||
SWE_agent,
|
||||
codeact_agent,
|
||||
delegator_agent,
|
||||
dummy_agent,
|
||||
monologue_agent,
|
||||
planner_agent,
|
||||
)
|
||||
|
||||
__all__ = ['monologue_agent', 'codeact_agent',
|
||||
'planner_agent', 'SWE_agent', 'delegator_agent']
|
||||
'planner_agent', 'SWE_agent',
|
||||
'delegator_agent',
|
||||
'dummy_agent']
|
||||
|
||||
for agent in all_microagents.values():
|
||||
name = agent['name']
|
||||
|
||||
@@ -1,54 +1,37 @@
|
||||
import re
|
||||
from typing import List, Mapping
|
||||
|
||||
from agenthub.codeact_agent.prompt import EXAMPLES, SYSTEM_MESSAGE
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
AgentEchoAction,
|
||||
AgentFinishAction,
|
||||
AgentTalkAction,
|
||||
CmdRunAction,
|
||||
IPythonRunCellAction,
|
||||
NullAction,
|
||||
)
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.observation import (
|
||||
AgentMessageObservation,
|
||||
CmdOutputObservation,
|
||||
IPythonRunCellObservation,
|
||||
UserMessageObservation,
|
||||
)
|
||||
from opendevin.sandbox.plugins import (
|
||||
JupyterRequirement,
|
||||
PluginRequirement,
|
||||
SWEAgentCommandsRequirement,
|
||||
)
|
||||
from opendevin.sandbox.plugins import JupyterRequirement, PluginRequirement
|
||||
from opendevin.state import State
|
||||
|
||||
SYSTEM_MESSAGE = """You are a helpful assistant. You will be provided access (as root) to a bash shell to complete user-provided tasks.
|
||||
You will be able to execute commands in the bash shell, interact with the file system, install packages, and receive the output of your commands.
|
||||
|
||||
DO NOT provide code in ```triple backticks```. Instead, you should execute bash command on behalf of the user by wrapping them with <execute> and </execute>.
|
||||
For example:
|
||||
|
||||
You can list the files in the current directory by executing the following command:
|
||||
<execute>ls</execute>
|
||||
|
||||
You can also install packages using pip:
|
||||
<execute> pip install numpy </execute>
|
||||
|
||||
You can also write a block of code to a file:
|
||||
<execute>
|
||||
echo "import math
|
||||
print(math.pi)" > math.py
|
||||
</execute>
|
||||
|
||||
When you are done, execute the following to close the shell and end the conversation:
|
||||
<execute>exit</execute>
|
||||
"""
|
||||
|
||||
INVALID_INPUT_MESSAGE = (
|
||||
"I don't understand your input. \n"
|
||||
'If you want to execute command, please use <execute> YOUR_COMMAND_HERE </execute>.\n'
|
||||
'If you already completed the task, please exit the shell by generating: <execute> exit </execute>.'
|
||||
)
|
||||
|
||||
|
||||
def parse_response(response) -> str:
|
||||
action = response.choices[0].message.content
|
||||
if '<execute>' in action and '</execute>' not in action:
|
||||
action += '</execute>'
|
||||
for lang in ['bash', 'ipython']:
|
||||
if f'<execute_{lang}>' in action and f'</execute_{lang}>' not in action:
|
||||
action += f'</execute_{lang}>'
|
||||
return action
|
||||
|
||||
|
||||
@@ -58,7 +41,20 @@ class CodeActAgent(Agent):
|
||||
The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
|
||||
"""
|
||||
|
||||
sandbox_plugins: List[PluginRequirement] = [JupyterRequirement()]
|
||||
sandbox_plugins: List[PluginRequirement] = [JupyterRequirement(), SWEAgentCommandsRequirement()]
|
||||
SUPPORTED_ACTIONS = (
|
||||
CmdRunAction,
|
||||
IPythonRunCellAction,
|
||||
AgentEchoAction,
|
||||
AgentTalkAction,
|
||||
NullAction
|
||||
)
|
||||
SUPPORTED_OBSERVATIONS = (
|
||||
AgentMessageObservation,
|
||||
UserMessageObservation,
|
||||
CmdOutputObservation,
|
||||
IPythonRunCellObservation
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -93,56 +89,82 @@ class CodeActAgent(Agent):
|
||||
assert state.plan.main_goal, 'Expecting instruction to be set'
|
||||
self.messages = [
|
||||
{'role': 'system', 'content': SYSTEM_MESSAGE},
|
||||
{'role': 'user', 'content': state.plan.main_goal},
|
||||
{
|
||||
'role': 'user',
|
||||
'content': (
|
||||
f'Here is an example of how you can interact with the environment for task solving:\n{EXAMPLES}\n\n'
|
||||
f"NOW, LET'S START!\n\n{state.plan.main_goal}"
|
||||
)
|
||||
},
|
||||
]
|
||||
updated_info = state.updated_info
|
||||
if updated_info:
|
||||
for prev_action, obs in updated_info:
|
||||
assert isinstance(
|
||||
prev_action, (CmdRunAction, AgentEchoAction)
|
||||
), 'Expecting CmdRunAction or AgentEchoAction for Action'
|
||||
if isinstance(
|
||||
obs, AgentMessageObservation
|
||||
): # warning message from itself
|
||||
prev_action, self.SUPPORTED_ACTIONS
|
||||
), f'{prev_action.__class__} is not supported (supported: {self.SUPPORTED_ACTIONS})'
|
||||
# prev_action is already added to self.messages when returned
|
||||
|
||||
# handle observations
|
||||
assert isinstance(
|
||||
obs, self.SUPPORTED_OBSERVATIONS
|
||||
), f'{obs.__class__} is not supported (supported: {self.SUPPORTED_OBSERVATIONS})'
|
||||
if isinstance(obs, (AgentMessageObservation, UserMessageObservation)):
|
||||
self.messages.append(
|
||||
{'role': 'user', 'content': obs.content})
|
||||
|
||||
# User wants to exit
|
||||
if obs.content.strip() == '/exit':
|
||||
return AgentFinishAction()
|
||||
elif isinstance(obs, CmdOutputObservation):
|
||||
content = 'OBSERVATION:\n' + obs.content
|
||||
content += f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]]'
|
||||
self.messages.append({'role': 'user', 'content': content})
|
||||
elif isinstance(obs, IPythonRunCellObservation):
|
||||
content = 'OBSERVATION:\n' + obs.content
|
||||
# replace base64 images with a placeholder
|
||||
splited = content.split('\n')
|
||||
for i, line in enumerate(splited):
|
||||
if ' already displayed to user'
|
||||
content = '\n'.join(splited)
|
||||
self.messages.append({'role': 'user', 'content': content})
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'Unknown observation type: {obs.__class__}'
|
||||
)
|
||||
|
||||
response = self.llm.completion(
|
||||
messages=self.messages,
|
||||
stop=['</execute>'],
|
||||
stop=[
|
||||
'</execute_ipython>',
|
||||
'</execute_bash>',
|
||||
],
|
||||
temperature=0.0
|
||||
)
|
||||
action_str: str = parse_response(response)
|
||||
state.num_of_chars += sum(len(message['content'])
|
||||
for message in self.messages) + len(action_str)
|
||||
state.num_of_chars += sum(
|
||||
len(message['content']) for message in self.messages
|
||||
) + len(action_str)
|
||||
self.messages.append({'role': 'assistant', 'content': action_str})
|
||||
|
||||
command = re.search(r'<execute>(.*)</execute>', action_str, re.DOTALL)
|
||||
if command is not None:
|
||||
if bash_command := re.search(r'<execute_bash>(.*)</execute_bash>', action_str, re.DOTALL):
|
||||
# remove the command from the action string to get thought
|
||||
thought = action_str.replace(bash_command.group(0), '').strip()
|
||||
# a command was found
|
||||
command_group = command.group(1)
|
||||
command_group = bash_command.group(1).strip()
|
||||
if command_group.strip() == 'exit':
|
||||
return AgentFinishAction()
|
||||
return CmdRunAction(command=command_group)
|
||||
# # execute the code
|
||||
# # TODO: does exit_code get loaded into Message?
|
||||
# exit_code, observation = self.env.execute(command_group)
|
||||
# self._history.append(Message(Role.ASSISTANT, observation))
|
||||
return CmdRunAction(command=command_group, thought=thought)
|
||||
elif python_code := re.search(r'<execute_ipython>(.*)</execute_ipython>', action_str, re.DOTALL):
|
||||
# a code block was found
|
||||
code_group = python_code.group(1).strip()
|
||||
thought = action_str.replace(python_code.group(0), '').strip()
|
||||
return IPythonRunCellAction(code=code_group, thought=thought)
|
||||
else:
|
||||
# we could provide a error message for the model to continue similar to
|
||||
# https://github.com/xingyaoww/mint-bench/blob/main/mint/envs/general_env.py#L18-L23
|
||||
# observation = INVALID_INPUT_MESSAGE
|
||||
# self._history.append(Message(Role.ASSISTANT, observation))
|
||||
return AgentEchoAction(
|
||||
content=INVALID_INPUT_MESSAGE
|
||||
) # warning message to itself
|
||||
# We assume the LLM is GOOD enough that when it returns pure natural language
|
||||
# it want to talk to the user
|
||||
return AgentTalkAction(content=action_str)
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
raise NotImplementedError('Implement this abstract method')
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
from opendevin.sandbox.plugins import SWEAgentCommandsRequirement
|
||||
|
||||
_SWEAGENT_BASH_DOCS = '\n'.join(
|
||||
filter(
|
||||
lambda x: not x.startswith('submit'),
|
||||
SWEAgentCommandsRequirement.documentation.split('\n')
|
||||
)
|
||||
)
|
||||
# _SWEAGENT_BASH_DOCS content below:
|
||||
"""
|
||||
open <path> [<line_number>] - opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line
|
||||
goto <line_number> - moves the window to show <line_number>
|
||||
scroll_down - moves the window down {WINDOW} lines
|
||||
scroll_up - moves the window down {WINDOW} lines
|
||||
create <filename> - creates and opens a new file with the given name
|
||||
search_dir <search_term> [<dir>] - searches for search_term in all files in dir. If dir is not provided, searches in the current directory
|
||||
search_file <search_term> [<file>] - searches for search_term in file. If file is not provided, searches in the current open file
|
||||
find_file <file_name> [<dir>] - finds all files with the given name in dir. If dir is not provided, searches in the current directory
|
||||
edit <start_line>:<end_line>
|
||||
<replacement_text>
|
||||
end_of_edit - replaces lines <start_line> through <end_line> (inclusive) with the given text in the open file. The replacement text is terminated by a line with only end_of_edit on it. All of the <replacement text> will be entered, so make sure your indentation is formatted properly. Python files will be checked for syntax errors after the edit. If the system detects a syntax error, the edit will not be executed. Simply try to edit the file again, but make sure to read the error message and modify the edit command you issue accordingly. Issuing the same command a second time will just lead to the same error message again.
|
||||
"""
|
||||
|
||||
_COMMAND_DOCS = (
|
||||
'\nApart from the standard bash commands, you can also use the following special commands in <execute_bash> environment:\n'
|
||||
f'{_SWEAGENT_BASH_DOCS}'
|
||||
"Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run."
|
||||
)
|
||||
|
||||
SYSTEM_MESSAGE = f"""A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
|
||||
The assistant can interact with an interactive Python (Jupyter Notebook) environment and receive the corresponding output when needed. The code should be enclosed using "<execute_ipython>" tag, for example:
|
||||
<execute_ipython>
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
|
||||
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
|
||||
The assistant should attempt fewer things at a time instead of putting too much commands OR code in one "execute" block.
|
||||
The assistant can install Python packages through bash by <execute_bash> pip install [package needed] </execute_bash> and should always import packages and define variables before starting to use them.
|
||||
The assistant should stop <execute> and provide an answer when they have already obtained the answer from the execution result.
|
||||
|
||||
{_COMMAND_DOCS}
|
||||
|
||||
Whenever possible, execute the code for the user using <execute_ipython> or <execute_bash> instead of providing it.
|
||||
The assistant's response should be concise, but do express their thoughts.
|
||||
Try to include one of <execute_ipython> or <execute_bash> in each of your responses, unless it is a direct answer to a question OR a message to the user.
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
--- START OF EXAMPLE ---
|
||||
|
||||
USER: Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me write the Python code for starting a web server and save it to a file `app.py`:
|
||||
<execute_ipython>
|
||||
CODE='''
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
'''
|
||||
with open('app.py', 'w') as f:
|
||||
f.write(CODE)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[Code executed successfully with no output]
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Traceback (most recent call last):
|
||||
File "/workspace/app.py", line 2, in <module>
|
||||
from flask import Flask
|
||||
ModuleNotFoundError: No module named 'flask'
|
||||
|
||||
ASSISTANT:
|
||||
It seems that Flask is not installed. Let me install Flask for you:
|
||||
<execute_bash>
|
||||
pip install flask
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Defaulting to user installation because normal site-packages is not writeable
|
||||
Collecting flask
|
||||
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
|
||||
Collecting blinker>=1.6.2
|
||||
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
|
||||
Collecting Werkzeug>=3.0.0
|
||||
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
|
||||
Collecting click>=8.1.3
|
||||
Using cached click-8.1.7-py3-none-any.whl (97 kB)
|
||||
Collecting itsdangerous>=2.1.2
|
||||
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
|
||||
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
|
||||
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
|
||||
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
|
||||
|
||||
ASSISTANT:
|
||||
Now that Flask is installed, let me run the Python file again:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 124
|
||||
|
||||
ASSISTANT:
|
||||
Let me check the server log again:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!
|
||||
|
||||
USER: Now kill the server, make it display the numbers in a table format.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me stop the server first:
|
||||
<execute_bash>
|
||||
kill 124
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1]+ Terminated python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT:
|
||||
The server has been stopped. Let me open the Python file and modify it.
|
||||
<execute_bash>
|
||||
open app.py
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return str(numbers)
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. Let me do that for you:
|
||||
|
||||
<execute_bash>
|
||||
edit 8:8 <<EOF
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
EOF
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.
|
||||
|
||||
ASSISTANT:
|
||||
The file has been updated. Let me run the Python file again with the new changes:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 126
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Free free to let me know if you have any further requests!
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
"""
|
||||
|
||||
INVALID_INPUT_MESSAGE = (
|
||||
"I don't understand your input. \n"
|
||||
'If you want to execute a bash command, please use <execute_bash> YOUR_COMMAND_HERE </execute_bash>.\n'
|
||||
'If you want to execute a block of Python code, please use <execute_ipython> YOUR_COMMAND_HERE </execute_ipython>.\n'
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .agent import DummyAgent
|
||||
|
||||
Agent.register('DummyAgent', DummyAgent)
|
||||
|
||||
+107
-12
@@ -1,23 +1,118 @@
|
||||
"""Module for a Dummy agent."""
|
||||
import time
|
||||
from typing import List, TypedDict
|
||||
|
||||
from typing import List
|
||||
|
||||
from opendevin.action import Action
|
||||
from opendevin.action.base import NullAction
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
AddTaskAction,
|
||||
AgentFinishAction,
|
||||
AgentRecallAction,
|
||||
AgentThinkAction,
|
||||
BrowseURLAction,
|
||||
CmdRunAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
ModifyTaskAction,
|
||||
)
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.controller.agent_controller import AgentController
|
||||
from opendevin.observation.base import NullObservation, Observation
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.observation import (
|
||||
AgentRecallObservation,
|
||||
CmdOutputObservation,
|
||||
FileReadObservation,
|
||||
FileWriteObservation,
|
||||
NullObservation,
|
||||
Observation,
|
||||
)
|
||||
from opendevin.state import State
|
||||
|
||||
"""
|
||||
FIXME: There are a few problems this surfaced
|
||||
* FileWrites seem to add an unintended newline at the end of the file
|
||||
* command_id is sometimes a number, sometimes a string
|
||||
* Why isn't the output of the background command split between two steps?
|
||||
* Browser not working
|
||||
"""
|
||||
|
||||
ActionObs = TypedDict('ActionObs', {'action': Action, 'observations': List[Observation]})
|
||||
|
||||
BACKGROUND_CMD = 'echo "This is in the background" && sleep .1 && echo "This too"'
|
||||
|
||||
|
||||
class DummyAgent(Agent):
|
||||
"""A dummy agent that does nothing but can be used in testing."""
|
||||
"""
|
||||
The DummyAgent is used for e2e testing. It just sends the same set of actions deterministically,
|
||||
without making any LLM calls.
|
||||
"""
|
||||
|
||||
async def run(self, controller: AgentController) -> Observation:
|
||||
return NullObservation('')
|
||||
def __init__(self, llm: LLM):
|
||||
super().__init__(llm)
|
||||
self.steps: List[ActionObs] = [{
|
||||
'action': AddTaskAction(parent='0', goal='check the current directory'),
|
||||
'observations': [NullObservation('')],
|
||||
}, {
|
||||
'action': AddTaskAction(parent='0.0', goal='run ls'),
|
||||
'observations': [NullObservation('')],
|
||||
}, {
|
||||
'action': ModifyTaskAction(id='0.0', state='in_progress'),
|
||||
'observations': [NullObservation('')],
|
||||
}, {
|
||||
'action': AgentThinkAction(thought='Time to get started!'),
|
||||
'observations': [NullObservation('')],
|
||||
}, {
|
||||
'action': CmdRunAction(command='echo "foo"'),
|
||||
'observations': [CmdOutputObservation('foo', command_id=-1, command='echo "foo"')],
|
||||
}, {
|
||||
'action': FileWriteAction(content='echo "Hello, World!"', path='hello.sh'),
|
||||
'observations': [FileWriteObservation('', path='hello.sh')],
|
||||
}, {
|
||||
'action': FileReadAction(path='hello.sh'),
|
||||
'observations': [FileReadObservation('echo "Hello, World!"\n', path='hello.sh')],
|
||||
}, {
|
||||
'action': CmdRunAction(command='bash hello.sh'),
|
||||
'observations': [CmdOutputObservation('Hello, World!', command_id=-1, command='bash hello.sh')],
|
||||
}, {
|
||||
'action': CmdRunAction(command=BACKGROUND_CMD, background=True),
|
||||
'observations': [
|
||||
CmdOutputObservation('Background command started. To stop it, send a `kill` action with id 42', command_id='42', command=BACKGROUND_CMD), # type: ignore[arg-type]
|
||||
CmdOutputObservation('This is in the background\nThis too\n', command_id='42', command=BACKGROUND_CMD), # type: ignore[arg-type]
|
||||
]
|
||||
}, {
|
||||
'action': AgentRecallAction(query='who am I?'),
|
||||
'observations': [
|
||||
AgentRecallObservation('', memories=['I am a computer.']),
|
||||
# CmdOutputObservation('This too\n', command_id='42', command=BACKGROUND_CMD),
|
||||
],
|
||||
}, {
|
||||
'action': BrowseURLAction(url='https://google.com'),
|
||||
'observations': [
|
||||
# BrowserOutputObservation('<html></html>', url='https://google.com', screenshot=""),
|
||||
],
|
||||
}, {
|
||||
'action': AgentFinishAction(),
|
||||
'observations': [],
|
||||
}]
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
return NullAction('')
|
||||
time.sleep(0.1)
|
||||
if state.iteration > 0:
|
||||
prev_step = self.steps[state.iteration - 1]
|
||||
if 'observations' in prev_step:
|
||||
expected_observations = prev_step['observations']
|
||||
hist_start = len(state.history) - len(expected_observations)
|
||||
for i in range(len(expected_observations)):
|
||||
hist_obs = state.history[hist_start + i][1].to_dict()
|
||||
expected_obs = expected_observations[i].to_dict()
|
||||
if 'command_id' in hist_obs['extras'] and hist_obs['extras']['command_id'] != -1:
|
||||
del hist_obs['extras']['command_id']
|
||||
hist_obs['content'] = ''
|
||||
if 'command_id' in expected_obs['extras'] and expected_obs['extras']['command_id'] != -1:
|
||||
del expected_obs['extras']['command_id']
|
||||
expected_obs['content'] = ''
|
||||
if hist_obs != expected_obs:
|
||||
print('\nactual', hist_obs)
|
||||
print('\nexpect', expected_obs)
|
||||
assert hist_obs == expected_obs, f'Expected observation {expected_obs}, got {hist_obs}'
|
||||
return self.steps[state.iteration]['action']
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return []
|
||||
return ['I am a computer.']
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
## Introduction
|
||||
|
||||
This package contains definitions of micro-agents. A micro-agent is defined
|
||||
in the following structure:
|
||||
|
||||
```
|
||||
[AgentName]
|
||||
├── agent.yaml
|
||||
└── prompt.md
|
||||
```
|
||||
|
||||
Note that `prompt.md` could use jinja2 template syntax. During runtime, `prompt.md`
|
||||
is loaded and rendered, and used together with `agent.yaml` to initialize a
|
||||
micro-agent.
|
||||
@@ -4,9 +4,11 @@ need to modify to complete this task:
|
||||
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
{% if state.inputs.summary %}
|
||||
Here's a summary of the codebase, as it relates to this task:
|
||||
|
||||
{{ state.inputs.summary }}
|
||||
{% endif %}
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.run }}
|
||||
|
||||
@@ -136,6 +136,7 @@ class MonologueAgent(Agent):
|
||||
Utilizes the INITIAL_THOUGHTS list to give the agent a context for it's capabilities
|
||||
and how to navigate the WORKSPACE_MOUNT_PATH_IN_SANDBOX in `config` (e.g., /workspace by default).
|
||||
Short circuited to return when already initialized.
|
||||
Will execute again when called after reset.
|
||||
|
||||
Parameters:
|
||||
- task (str): The initial goal statement provided by the user
|
||||
@@ -156,6 +157,10 @@ class MonologueAgent(Agent):
|
||||
else:
|
||||
self.memory = None
|
||||
|
||||
self._add_initial_thoughts(task)
|
||||
self._initialized = True
|
||||
|
||||
def _add_initial_thoughts(self, task):
|
||||
previous_action = ''
|
||||
for thought in INITIAL_THOUGHTS:
|
||||
thought = thought.replace('$TASK', task)
|
||||
@@ -207,7 +212,6 @@ class MonologueAgent(Agent):
|
||||
else:
|
||||
action = AgentThinkAction(thought=thought)
|
||||
self._add_event(action.to_memory())
|
||||
self._initialized = True
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
@@ -256,8 +260,6 @@ class MonologueAgent(Agent):
|
||||
|
||||
def reset(self) -> None:
|
||||
super().reset()
|
||||
self.monologue = Monologue()
|
||||
if config.get(ConfigType.AGENT_MEMORY_ENABLED):
|
||||
self.memory = LongTermMemory()
|
||||
else:
|
||||
self.memory = None
|
||||
|
||||
# Reset the initial monologue and memory
|
||||
self._initialized = False
|
||||
|
||||
@@ -32,7 +32,8 @@ FROM python:3.12-slim as runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV RUN_AS_DEVIN=false
|
||||
ENV RUN_AS_DEVIN=true
|
||||
ENV SANDBOX_USER_ID=1000
|
||||
ENV USE_HOST_NETWORK=false
|
||||
ENV SSH_HOSTNAME=host.docker.internal
|
||||
ENV WORKSPACE_BASE=/opt/workspace_base
|
||||
@@ -40,13 +41,23 @@ ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION
|
||||
RUN mkdir -p $WORKSPACE_BASE
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y curl ssh
|
||||
&& apt-get install -y curl ssh sudo
|
||||
|
||||
RUN useradd -m -u $SANDBOX_USER_ID -s /bin/bash opendevin && \
|
||||
usermod -aG sudo opendevin && \
|
||||
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
RUN chown -R opendevin:opendevin /app
|
||||
USER opendevin
|
||||
|
||||
ENV VIRTUAL_ENV=/app/.venv \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONPATH='/app'
|
||||
|
||||
COPY --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||
# change ownership of the virtual environment to the sandbox user
|
||||
USER root
|
||||
RUN chown -R opendevin:opendevin ${VIRTUAL_ENV}
|
||||
USER opendevin
|
||||
|
||||
COPY ./opendevin ./opendevin
|
||||
COPY ./agenthub ./agenthub
|
||||
@@ -55,4 +66,17 @@ RUN playwright install --with-deps chromium
|
||||
|
||||
COPY --from=frontend-builder /app/dist ./frontend/dist
|
||||
|
||||
CMD ["uvicorn", "opendevin.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]
|
||||
USER root
|
||||
RUN chown -R opendevin:opendevin /app
|
||||
# make group permissions the same as user permissions
|
||||
RUN chmod -R g=u /app
|
||||
USER opendevin
|
||||
|
||||
# change ownership of the app directory to the sandbox user
|
||||
COPY ./containers/app/entrypoint.sh /app/entrypoint.sh
|
||||
|
||||
# run the script as root
|
||||
USER root
|
||||
RUN chown opendevin:opendevin /app/entrypoint.sh
|
||||
RUN chmod 777 /app/entrypoint.sh
|
||||
CMD ["/app/entrypoint.sh"]
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
# check user is root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Please run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$SANDBOX_USER_ID" ]; then
|
||||
echo "SANDBOX_USER_ID is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# change uid of opendevin user to match the host user
|
||||
# but the group id is not changed, so the user can still access everything under /app
|
||||
usermod -u $SANDBOX_USER_ID opendevin
|
||||
|
||||
# get the user group of /var/run/docker.sock and set opendevin to that group
|
||||
DOCKER_SOCKET_GID=$(stat -c '%g' /var/run/docker.sock)
|
||||
echo "Docker socket group id: $DOCKER_SOCKET_GID"
|
||||
usermod -aG $DOCKER_SOCKET_GID opendevin
|
||||
|
||||
# switch to the user and start the server
|
||||
su opendevin -c "cd /app && uvicorn opendevin.server.listen:app --host 0.0.0.0 --port 3000"
|
||||
@@ -27,3 +27,7 @@ RUN mkdir -p -m0755 /var/run/sshd
|
||||
|
||||
# symlink python3 to python
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
# install basic dependencies for CodeActAgent
|
||||
RUN pip3 install --upgrade pip
|
||||
RUN pip3 install jupyterlab notebook jupyter_kernel_gateway flake8
|
||||
|
||||
@@ -42,7 +42,11 @@ repos:
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
|
||||
- repo: https://github.com/NiklasRosenstein/pydoc-markdown
|
||||
rev: develop
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pydoc-markdown
|
||||
- id: pydoc
|
||||
name: pydoc
|
||||
entry: ./dev_config/python/pydoc.sh
|
||||
language: script
|
||||
types: [python]
|
||||
pass_filenames: false
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
pip install pydoc-markdown && rm -rf ./docs/modules/python && pydoc-markdown
|
||||
@@ -3,13 +3,12 @@ sidebar_label: agent
|
||||
title: agenthub.dummy_agent.agent
|
||||
---
|
||||
|
||||
Module for a Dummy agent.
|
||||
|
||||
## DummyAgent Objects
|
||||
|
||||
```python
|
||||
class DummyAgent(Agent)
|
||||
```
|
||||
|
||||
A dummy agent that does nothing but can be used in testing.
|
||||
The DummyAgent is used for e2e testing. It just sends the same set of actions deterministically,
|
||||
without making any LLM calls.
|
||||
|
||||
|
||||
@@ -12,3 +12,12 @@ class CmdOutputObservation(Observation)
|
||||
|
||||
This data class represents the output of a command.
|
||||
|
||||
## IPythonRunCellObservation Objects
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class IPythonRunCellObservation(Observation)
|
||||
```
|
||||
|
||||
This data class represents the output of a IPythonRunCellAction.
|
||||
|
||||
|
||||
@@ -13,9 +13,13 @@ class ActionTypeSchema(BaseModel)
|
||||
|
||||
Initializes the agent. Only sent by client.
|
||||
|
||||
#### USER\_MESSAGE
|
||||
|
||||
Sends a message from the user. Only sent by the client.
|
||||
|
||||
#### START
|
||||
|
||||
Starts a new development task. Only sent by the client.
|
||||
Starts a new development task OR send chat from the user. Only sent by the client.
|
||||
|
||||
#### READ
|
||||
|
||||
@@ -29,6 +33,10 @@ Writes the content to a file.
|
||||
|
||||
Runs a command.
|
||||
|
||||
#### RUN\_IPYTHON
|
||||
|
||||
Runs a IPython cell.
|
||||
|
||||
#### KILL
|
||||
|
||||
Kills a background command.
|
||||
@@ -45,6 +53,10 @@ Searches long-term memory
|
||||
|
||||
Allows the agent to make a plan, set a goal, or record thoughts
|
||||
|
||||
#### TALK
|
||||
|
||||
Allows the agent to respond to the user.
|
||||
|
||||
#### DELEGATE
|
||||
|
||||
Delegates a task to another agent.
|
||||
|
||||
@@ -21,6 +21,10 @@ The HTML content of a URL
|
||||
|
||||
The output of a command
|
||||
|
||||
#### RUN\_IPYTHON
|
||||
|
||||
Runs a IPython cell.
|
||||
|
||||
#### RECALL
|
||||
|
||||
The result of a search
|
||||
|
||||
@@ -17,6 +17,10 @@ Initial state of the task.
|
||||
|
||||
The task is running.
|
||||
|
||||
#### AWAITING\_USER\_INPUT
|
||||
|
||||
The task is awaiting user input.
|
||||
|
||||
#### PAUSED
|
||||
|
||||
The task is paused.
|
||||
|
||||
Generated
+19
@@ -26,6 +26,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-accessible-treeview": "^2.8.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-icons": "^5.0.1",
|
||||
@@ -44,6 +45,7 @@
|
||||
"@types/node": "^18.0.0 ",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
@@ -4878,6 +4880,15 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-highlight": {
|
||||
"version": "0.12.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-highlight/-/react-highlight-0.12.8.tgz",
|
||||
"integrity": "sha512-V7O7zwXUw8WSPd//YUO8sz489J/EeobJljASGhP0rClrvq+1Y1qWEpToGu+Pp7YuChxhAXSgkLkrOYpZX5A62g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-syntax-highlighter": {
|
||||
"version": "15.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.11.tgz",
|
||||
@@ -12799,6 +12810,14 @@
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-highlight": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/react-highlight/-/react-highlight-0.15.0.tgz",
|
||||
"integrity": "sha512-5uV/b/N4Z421GSVVe05fz+OfTsJtFzx/fJBdafZyw4LS70XjIZwgEx3Lrkfc01W/RzZ2Dtfb0DApoaJFAIKBtA==",
|
||||
"dependencies": {
|
||||
"highlight.js": "^10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hot-toast": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"react": "^18.2.0",
|
||||
"react-accessible-treeview": "^2.8.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-icons": "^5.0.1",
|
||||
@@ -64,6 +65,7 @@
|
||||
"@types/node": "^18.0.0 ",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.2.22",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
|
||||
@@ -15,6 +15,10 @@ const AgentStatusMap: { [k: string]: { message: string; indicator: string } } =
|
||||
message: "Agent is running task...",
|
||||
indicator: "bg-green-500",
|
||||
},
|
||||
[AgentTaskState.AWAITING_USER_INPUT]: {
|
||||
message: "Agent is awaiting user input...",
|
||||
indicator: "bg-orange-500",
|
||||
},
|
||||
[AgentTaskState.PAUSED]: {
|
||||
message: "Agent has paused.",
|
||||
indicator: "bg-yellow-500",
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IoIosGlobe } from "react-icons/io";
|
||||
import { useSelector } from "react-redux";
|
||||
import { HiOutlineMagnifyingGlass } from "react-icons/hi2";
|
||||
import { HiCursorClick } from "react-icons/hi";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
import logo from "../assets/logo.png";
|
||||
|
||||
function BlankPage(): JSX.Element {
|
||||
return (
|
||||
<div className="h-full bg-slate-200 flex flex-col items-center justify-center">
|
||||
<img src={logo} alt="Blank Page" className="w-28 h-28" />
|
||||
<div className="h-8 flex items-center bg-slate-900 px-2 rounded-3xl ml-3 space-x-2">
|
||||
<HiOutlineMagnifyingGlass size={20} />
|
||||
<span>OpenDevin: Code Less, Make More.</span>
|
||||
<HiCursorClick size={20} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Browser(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { url, screenshotSrc } = useSelector(
|
||||
(state: RootState) => state.browser,
|
||||
);
|
||||
@@ -30,15 +18,18 @@ function Browser(): JSX.Element {
|
||||
: `data:image/png;base64,${screenshotSrc || ""}`;
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col justify-evenly p-2 space-y-2">
|
||||
<div className="w-full py-2 px-5 rounded-3xl bg-neutral-700 text-gray-200 truncate">
|
||||
<div className="h-full w-full flex flex-col text-neutral-400">
|
||||
<div className="w-full p-2 truncate border-b border-neutral-600">
|
||||
{url}
|
||||
</div>
|
||||
<div className="overflow-y-auto h-4/5 scrollbar-hide rounded-xl">
|
||||
<div className="overflow-y-auto grow scrollbar-hide rounded-xl">
|
||||
{screenshotSrc ? (
|
||||
<img src={imgSrc} className="rounded-xl" alt="Browser Screenshot" />
|
||||
) : (
|
||||
<BlankPage />
|
||||
<div className="flex flex-col items-center h-full justify-center">
|
||||
<IoIosGlobe size={100} />
|
||||
{t(I18nKey.BROWSER$EMPTY_MESSAGE)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IoMdChatbubbles } from "react-icons/io";
|
||||
import Markdown from "react-markdown";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTypingEffect } from "#/hooks/useTypingEffect";
|
||||
import AgentTaskState from "../types/AgentTaskState";
|
||||
import {
|
||||
addAssistantMessageToChat,
|
||||
sendChatMessage,
|
||||
@@ -117,6 +118,12 @@ function MessageList(): JSX.Element {
|
||||
|
||||
function ChatInterface(): JSX.Element {
|
||||
const { initialized } = useSelector((state: RootState) => state.task);
|
||||
const { curTaskState } = useSelector((state: RootState) => state.agent);
|
||||
|
||||
const onUserMessage = (msg: string) => {
|
||||
const isNewTask = curTaskState === AgentTaskState.INIT;
|
||||
sendChatMessage(msg, isNewTask);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full p-0 bg-neutral-800">
|
||||
@@ -125,7 +132,7 @@ function ChatInterface(): JSX.Element {
|
||||
Chat
|
||||
</div>
|
||||
<MessageList />
|
||||
<ChatInput disabled={!initialized} onSendMessage={sendChatMessage} />
|
||||
<ChatInput disabled={!initialized} onSendMessage={onUserMessage} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ import Editor, { Monaco } from "@monaco-editor/react";
|
||||
import { Tab, Tabs } from "@nextui-org/react";
|
||||
import type { editor } from "monaco-editor";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { VscCode } from "react-icons/vsc";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { selectFile } from "#/services/fileService";
|
||||
import { setCode } from "#/state/codeSlice";
|
||||
import { RootState } from "#/store";
|
||||
import FileExplorer from "./file-explorer/FileExplorer";
|
||||
|
||||
function CodeEditor(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFileName, setSelectedFileName] = useState("");
|
||||
|
||||
const dispatch = useDispatch();
|
||||
@@ -64,14 +68,21 @@ function CodeEditor(): JSX.Element {
|
||||
title={selectedFileName}
|
||||
/>
|
||||
</Tabs>
|
||||
<div className="flex grow">
|
||||
<Editor
|
||||
height="100%"
|
||||
path={selectedFileName.toLocaleLowerCase()}
|
||||
defaultValue=""
|
||||
value={code}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
<div className="flex grow items-center justify-center">
|
||||
{selectedFileName === "" ? (
|
||||
<div className="flex flex-col items-center text-neutral-400">
|
||||
<VscCode size={100} />
|
||||
{t(I18nKey.CODE_EDITOR$EMPTY_MESSAGE)}
|
||||
</div>
|
||||
) : (
|
||||
<Editor
|
||||
height="100%"
|
||||
path={selectedFileName.toLocaleLowerCase()}
|
||||
defaultValue=""
|
||||
value={code}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import Markdown from "react-markdown";
|
||||
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import { RootState } from "#/store";
|
||||
import { Cell } from "#/state/jupyterSlice";
|
||||
|
||||
interface IJupyterCell {
|
||||
cell: Cell;
|
||||
}
|
||||
|
||||
function JupyterCell({ cell }: IJupyterCell): JSX.Element {
|
||||
const code = cell.content;
|
||||
|
||||
if (cell.type === "input") {
|
||||
return (
|
||||
<div className="rounded-lg bg-gray-800 dark:bg-gray-900 p-2 text-xs">
|
||||
<div className="mb-1 text-gray-400">EXECUTE</div>
|
||||
<pre
|
||||
className="scrollbar-custom scrollbar-thumb-gray-500 hover:scrollbar-thumb-gray-400 dark:scrollbar-thumb-white/10 dark:hover:scrollbar-thumb-white/20 overflow-auto px-5"
|
||||
style={{ padding: 0, marginBottom: 0, fontSize: "0.75rem" }}
|
||||
>
|
||||
<SyntaxHighlighter language="python" style={atomOneDark}>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="rounded-lg bg-gray-800 dark:bg-gray-900 p-2 text-xs">
|
||||
<div className="mb-1 text-gray-400">STDOUT/STDERR</div>
|
||||
<pre
|
||||
className="scrollbar-custom scrollbar-thumb-gray-500 hover:scrollbar-thumb-gray-400 dark:scrollbar-thumb-white/10 dark:hover:scrollbar-thumb-white/20 overflow-auto px-5 max-h-[60vh] bg-gray-800"
|
||||
style={{ padding: 0, marginBottom: 0, fontSize: "0.75rem" }}
|
||||
>
|
||||
{/* split code by newline and render each line as a plaintext, except it starts with `![image]` so we render it as markdown */}
|
||||
{code.split("\n").map((line, index) => {
|
||||
if (line.startsWith(") {
|
||||
// add new line before and after the image
|
||||
return (
|
||||
<div key={index}>
|
||||
<Markdown urlTransform={(value: string) => value}>
|
||||
{line}
|
||||
</Markdown>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={index}>
|
||||
<SyntaxHighlighter language="plaintext" style={atomOneDark}>
|
||||
{line}
|
||||
</SyntaxHighlighter>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Jupyter(): JSX.Element {
|
||||
const { cells } = useSelector((state: RootState) => state.jupyter);
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto flex flex-col">
|
||||
{cells.map((cell, index) => (
|
||||
<JupyterCell key={index} cell={cell} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Jupyter;
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FaCheckCircle,
|
||||
FaQuestionCircle,
|
||||
@@ -7,7 +8,9 @@ import {
|
||||
FaRegClock,
|
||||
FaRegTimesCircle,
|
||||
} from "react-icons/fa";
|
||||
import { VscListOrdered } from "react-icons/vsc";
|
||||
import { useSelector } from "react-redux";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Plan, Task, TaskState } from "#/services/planService";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
@@ -55,10 +58,13 @@ interface PlanProps {
|
||||
}
|
||||
|
||||
function PlanContainer({ plan }: PlanProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (plan.mainGoal === undefined) {
|
||||
return (
|
||||
<div className="p-2">
|
||||
Nothing is currently planned. Start a task for this to change.
|
||||
<div className="w-full h-full flex flex-col text-neutral-400 items-center justify-center">
|
||||
<VscListOrdered size={100} />
|
||||
{t(I18nKey.PLANNER$EMPTY_MESSAGE)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { AllTabs, TabOption, TabType } from "#/types/TabOption";
|
||||
import Browser from "./Browser";
|
||||
import CodeEditor from "./CodeEditor";
|
||||
import Planner from "./Planner";
|
||||
import Jupyter from "./Jupyter";
|
||||
|
||||
function Workspace() {
|
||||
const { t } = useTranslation();
|
||||
@@ -20,12 +21,13 @@ function Workspace() {
|
||||
const screenshotSrc = useSelector(
|
||||
(state: RootState) => state.browser.screenshotSrc,
|
||||
);
|
||||
|
||||
const jupyterCells = useSelector((state: RootState) => state.jupyter.cells);
|
||||
const [activeTab, setActiveTab] = useState<TabType>(TabOption.CODE);
|
||||
const [changes, setChanges] = useState<Record<TabType, boolean>>({
|
||||
[TabOption.PLANNER]: false,
|
||||
[TabOption.CODE]: false,
|
||||
[TabOption.BROWSER]: false,
|
||||
[TabOption.JUPYTER]: false,
|
||||
});
|
||||
|
||||
const tabData = useMemo(
|
||||
@@ -45,6 +47,11 @@ function Workspace() {
|
||||
icon: <IoIosGlobe size={18} />,
|
||||
component: <Browser key="browser" />,
|
||||
},
|
||||
[TabOption.JUPYTER]: {
|
||||
name: t(I18nKey.WORKSPACE$JUPYTER_TAB_LABEL),
|
||||
icon: <VscCode size={18} />,
|
||||
component: <Jupyter key="jupyter" />,
|
||||
},
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
@@ -73,6 +80,14 @@ function Workspace() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [screenshotSrc]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab !== TabOption.JUPYTER && jupyterCells.length > 0) {
|
||||
// FIXME: This is a temporary solution to show the jupyter tab when the first cell is added
|
||||
// Only need to show the tab only when a cell is added
|
||||
setChanges((prev) => ({ ...prev, [TabOption.JUPYTER]: true }));
|
||||
}
|
||||
}, [jupyterCells]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-0 grow">
|
||||
<div
|
||||
|
||||
@@ -3,15 +3,17 @@ import {
|
||||
IoIosArrowBack,
|
||||
IoIosArrowForward,
|
||||
IoIosRefresh,
|
||||
IoIosCloudUpload,
|
||||
} from "react-icons/io";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { WorkspaceFile, getWorkspace } from "#/services/fileService";
|
||||
import { WorkspaceFile, getWorkspace, uploadFile } from "#/services/fileService";
|
||||
import IconButton from "../IconButton";
|
||||
import ExplorerTree from "./ExplorerTree";
|
||||
import { removeEmptyNodes } from "./utils";
|
||||
|
||||
interface ExplorerActionsProps {
|
||||
onRefresh: () => void;
|
||||
onUpload: () => void;
|
||||
toggleHidden: () => void;
|
||||
isHidden: boolean;
|
||||
}
|
||||
@@ -19,6 +21,7 @@ interface ExplorerActionsProps {
|
||||
function ExplorerActions({
|
||||
toggleHidden,
|
||||
onRefresh,
|
||||
onUpload,
|
||||
isHidden,
|
||||
}: ExplorerActionsProps) {
|
||||
return (
|
||||
@@ -29,17 +32,30 @@ function ExplorerActions({
|
||||
)}
|
||||
>
|
||||
{!isHidden && (
|
||||
<IconButton
|
||||
icon={
|
||||
<IoIosRefresh
|
||||
size={16}
|
||||
className="text-neutral-400 hover:text-neutral-100 transition"
|
||||
/>
|
||||
}
|
||||
testId="refresh"
|
||||
ariaLabel="Refresh workspace"
|
||||
onClick={onRefresh}
|
||||
/>
|
||||
<>
|
||||
<IconButton
|
||||
icon={
|
||||
<IoIosRefresh
|
||||
size={16}
|
||||
className="text-neutral-400 hover:text-neutral-100 transition"
|
||||
/>
|
||||
}
|
||||
testId="refresh"
|
||||
ariaLabel="Refresh workspace"
|
||||
onClick={onRefresh}
|
||||
/>
|
||||
<IconButton
|
||||
icon={
|
||||
<IoIosCloudUpload
|
||||
size={16}
|
||||
className="text-neutral-400 hover:text-neutral-100 transition"
|
||||
/>
|
||||
}
|
||||
testId="upload"
|
||||
ariaLabel="Upload File"
|
||||
onClick={onUpload}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
@@ -56,8 +72,8 @@ function ExplorerActions({
|
||||
/>
|
||||
)
|
||||
}
|
||||
testId="close"
|
||||
ariaLabel="Close workspace"
|
||||
testId="toggle"
|
||||
ariaLabel={isHidden ? "Open workspace" : "Close workspace"}
|
||||
onClick={toggleHidden}
|
||||
/>
|
||||
</div>
|
||||
@@ -71,12 +87,33 @@ interface FileExplorerProps {
|
||||
function FileExplorer({ onFileClick }: FileExplorerProps) {
|
||||
const [workspace, setWorkspace] = React.useState<WorkspaceFile>();
|
||||
const [isHidden, setIsHidden] = React.useState(false);
|
||||
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const getWorkspaceData = async () => {
|
||||
const wsFile = await getWorkspace();
|
||||
setWorkspace(removeEmptyNodes(wsFile));
|
||||
};
|
||||
|
||||
const selectFileInput = () => {
|
||||
fileInputRef.current?.click(); // Trigger the file browser
|
||||
};
|
||||
|
||||
const uploadFileData = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files ? event.target.files[0] : null;
|
||||
if (!file) {
|
||||
console.log("No file selected.");
|
||||
return;
|
||||
}
|
||||
console.log("File selected:", file);
|
||||
try {
|
||||
const response = await uploadFile(file);
|
||||
console.log(response);
|
||||
await getWorkspaceData(); // Refresh the workspace to show the new file
|
||||
} catch (error) {
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
await getWorkspaceData();
|
||||
@@ -105,8 +142,15 @@ function FileExplorer({ onFileClick }: FileExplorerProps) {
|
||||
isHidden={isHidden}
|
||||
toggleHidden={() => setIsHidden((prev) => !prev)}
|
||||
onRefresh={getWorkspaceData}
|
||||
onUpload={selectFileInput}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
onChange={uploadFileData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ import { act, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import LoadPreviousSessionModal from "./LoadPreviousSessionModal";
|
||||
import { clearMsgs, fetchMsgs } from "../../../services/session";
|
||||
import { sendChatMessageFromEvent } from "../../../services/chatService";
|
||||
import { addChatMessageFromEvent } from "../../../services/chatService";
|
||||
import { handleAssistantMessage } from "../../../services/actions";
|
||||
import toast from "../../../utils/toast";
|
||||
|
||||
@@ -37,7 +37,7 @@ vi.mock("../../../services/session", async (importOriginal) => ({
|
||||
|
||||
vi.mock("../../../services/chatService", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../../../services/chatService")>()),
|
||||
sendChatMessageFromEvent: vi.fn(),
|
||||
addChatMessageFromEvent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../services/actions", async (importOriginal) => ({
|
||||
@@ -94,7 +94,7 @@ describe("LoadPreviousSession", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchMsgs).toHaveBeenCalledTimes(1);
|
||||
expect(sendChatMessageFromEvent).toHaveBeenCalledTimes(1);
|
||||
expect(addChatMessageFromEvent).toHaveBeenCalledTimes(1);
|
||||
expect(handleAssistantMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
// modal should close right after fetching messages
|
||||
@@ -117,7 +117,7 @@ describe("LoadPreviousSession", () => {
|
||||
await waitFor(async () => {
|
||||
await expect(() => fetchMsgs()).rejects.toThrow();
|
||||
expect(handleAssistantMessage).not.toHaveBeenCalled();
|
||||
expect(sendChatMessageFromEvent).not.toHaveBeenCalled();
|
||||
expect(addChatMessageFromEvent).not.toHaveBeenCalled();
|
||||
// error toast should be shown
|
||||
expect(toast.stickyError).toHaveBeenCalledWith(
|
||||
"ws",
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { handleAssistantMessage } from "#/services/actions";
|
||||
import { sendChatMessageFromEvent } from "#/services/chatService";
|
||||
import { addChatMessageFromEvent } from "#/services/chatService";
|
||||
import { clearMsgs, fetchMsgs } from "#/services/session";
|
||||
import toast from "#/utils/toast";
|
||||
import BaseModal from "../base-modal/BaseModal";
|
||||
@@ -28,7 +28,7 @@ function LoadPreviousSessionModal({
|
||||
|
||||
messages.forEach((message) => {
|
||||
if (message.role === "user") {
|
||||
sendChatMessageFromEvent(message.payload);
|
||||
addChatMessageFromEvent(message.payload);
|
||||
}
|
||||
|
||||
if (message.role === "assistant") {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Settings } from "#/services/settings";
|
||||
import AgentTaskState from "#/types/AgentTaskState";
|
||||
import { act, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import AgentTaskState from "#/types/AgentTaskState";
|
||||
import { Settings } from "#/services/settings";
|
||||
import SettingsForm from "./SettingsForm";
|
||||
|
||||
const onModelChangeMock = vi.fn();
|
||||
@@ -106,7 +106,6 @@ describe("SettingsForm", () => {
|
||||
});
|
||||
|
||||
expect(onModelChangeMock).toHaveBeenCalledWith("model3");
|
||||
expect(onAPIKeyChangeMock).toHaveBeenCalledWith("");
|
||||
});
|
||||
|
||||
it("should call the onAgentChange handler when the agent changes", () => {
|
||||
|
||||
@@ -38,7 +38,8 @@ function SettingsForm({
|
||||
useEffect(() => {
|
||||
if (
|
||||
curTaskState === AgentTaskState.RUNNING ||
|
||||
curTaskState === AgentTaskState.PAUSED
|
||||
curTaskState === AgentTaskState.PAUSED ||
|
||||
curTaskState === AgentTaskState.AWAITING_USER_INPUT
|
||||
) {
|
||||
setDisabled(true);
|
||||
} else {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { fetchAgents, fetchModels } from "#/api";
|
||||
import { initializeAgent } from "#/services/agent";
|
||||
import { Settings, getSettings, saveSettings } from "#/services/settings";
|
||||
import toast from "#/utils/toast";
|
||||
import { act, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { Mock } from "vitest";
|
||||
import toast from "#/utils/toast";
|
||||
import { Settings, getSettings, saveSettings } from "#/services/settings";
|
||||
import { initializeAgent } from "#/services/agent";
|
||||
import { fetchAgents, fetchModels } from "#/api";
|
||||
import SettingsModal from "./SettingsModal";
|
||||
|
||||
const toastSpy = vi.spyOn(toast, "settingsChanged");
|
||||
@@ -129,6 +129,7 @@ describe("SettingsModal", () => {
|
||||
expect(saveSettings).toHaveBeenCalledWith({
|
||||
...initialSettings,
|
||||
LLM_MODEL: "model3",
|
||||
LLM_API_KEY: "", // reset after model change
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,6 +161,7 @@ describe("SettingsModal", () => {
|
||||
expect(initializeAgent).toHaveBeenCalledWith({
|
||||
...initialSettings,
|
||||
LLM_MODEL: "model3",
|
||||
LLM_API_KEY: "", // reset after model change
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Spinner } from "@nextui-org/react";
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { fetchAgents, fetchModels } from "#/api";
|
||||
import { AvailableLanguages } from "#/i18n";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
@@ -9,10 +13,6 @@ import {
|
||||
saveSettings,
|
||||
} from "#/services/settings";
|
||||
import toast from "#/utils/toast";
|
||||
import { Spinner } from "@nextui-org/react";
|
||||
import i18next from "i18next";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BaseModal from "../base-modal/BaseModal";
|
||||
import SettingsForm from "./SettingsForm";
|
||||
|
||||
@@ -76,8 +76,14 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
i18next.changeLanguage(settings.LANGUAGE);
|
||||
initializeAgent(settings); // reinitialize the agent with the new settings
|
||||
|
||||
const sensitiveKeys = ['LLM_API_KEY'];
|
||||
|
||||
Object.entries(updatedSettings).forEach(([key, value]) => {
|
||||
toast.settingsChanged(`${key} set to "${value}"`);
|
||||
if (!sensitiveKeys.includes(key)) {
|
||||
toast.settingsChanged(`${key} set to "${value}"`);
|
||||
} else {
|
||||
toast.settingsChanged(`${key} has been updated securely.`);
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem(
|
||||
@@ -86,6 +92,13 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
);
|
||||
};
|
||||
|
||||
const isDisabled =
|
||||
Object.entries(settings)
|
||||
// filter api key
|
||||
.filter(([key]) => key !== "LLM_API_KEY")
|
||||
.some(([, value]) => !value) ||
|
||||
JSON.stringify(settings) === JSON.stringify(currentSettings);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={isOpen}
|
||||
@@ -96,9 +109,7 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
|
||||
{
|
||||
label: t(I18nKey.CONFIGURATION$MODAL_SAVE_BUTTON_LABEL),
|
||||
action: handleSaveSettings,
|
||||
isDisabled:
|
||||
Object.values(settings).some((value) => !value) ||
|
||||
JSON.stringify(settings) === JSON.stringify(currentSettings),
|
||||
isDisabled,
|
||||
closeAfterAction: true,
|
||||
className: "bg-primary rounded-lg",
|
||||
},
|
||||
|
||||
@@ -39,6 +39,19 @@
|
||||
"pt": "Planejador",
|
||||
"es": "Planificador"
|
||||
},
|
||||
"WORKSPACE$JUPYTER_TAB_LABEL": {
|
||||
"en": "Jupyter IPython",
|
||||
"zh-CN": "Jupyter IPython",
|
||||
"de": "Jupyter IPython",
|
||||
"ko-KR": "Jupyter IPython",
|
||||
"no": "Jupyter IPython",
|
||||
"zh-TW": "Jupyter IPython",
|
||||
"ar": "Jupyter IPython",
|
||||
"fr": "Jupyter IPython",
|
||||
"it": "Jupyter IPython",
|
||||
"pt": "Jupyter IPython",
|
||||
"es": "Jupyter IPython"
|
||||
},
|
||||
"WORKSPACE$CODE_EDITOR_TAB_LABEL": {
|
||||
"en": "Code Editor",
|
||||
"zh-CN": "代码编辑器",
|
||||
@@ -328,5 +341,17 @@
|
||||
"SETTINGS$API_KEY_PLACEHOLDER": {
|
||||
"en": "Enter your API key.",
|
||||
"de": "Model API key."
|
||||
},
|
||||
"CODE_EDITOR$EMPTY_MESSAGE": {
|
||||
"en": "No file selected.",
|
||||
"de": "Keine Datei ausgewählt."
|
||||
},
|
||||
"BROWSER$EMPTY_MESSAGE": {
|
||||
"en": "No page loaded.",
|
||||
"de": "Keine Seite geladen."
|
||||
},
|
||||
"PLANNER$EMPTY_MESSAGE": {
|
||||
"en": "No plan created.",
|
||||
"de": "Kein Plan erstellt."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { setScreenshotSrc, setUrl } from "#/state/browserSlice";
|
||||
import { appendAssistantMessage } from "#/state/chatSlice";
|
||||
import { setCode, updatePath } from "#/state/codeSlice";
|
||||
import { appendInput } from "#/state/commandSlice";
|
||||
import { appendJupyterInput } from "#/state/jupyterSlice";
|
||||
import { setPlan } from "#/state/planSlice";
|
||||
import { setInitialized } from "#/state/taskSlice";
|
||||
import store from "#/store";
|
||||
@@ -29,12 +30,24 @@ const messageActions = {
|
||||
[ActionType.THINK]: (message: ActionMessage) => {
|
||||
store.dispatch(appendAssistantMessage(message.args.thought));
|
||||
},
|
||||
[ActionType.TALK]: (message: ActionMessage) => {
|
||||
store.dispatch(appendAssistantMessage(message.args.content));
|
||||
},
|
||||
[ActionType.FINISH]: (message: ActionMessage) => {
|
||||
store.dispatch(appendAssistantMessage(message.message));
|
||||
},
|
||||
[ActionType.RUN]: (message: ActionMessage) => {
|
||||
if (message.args.thought) {
|
||||
store.dispatch(appendAssistantMessage(message.args.thought));
|
||||
}
|
||||
store.dispatch(appendInput(message.args.command));
|
||||
},
|
||||
[ActionType.RUN_IPYTHON]: (message: ActionMessage) => {
|
||||
if (message.args.thought) {
|
||||
store.dispatch(appendAssistantMessage(message.args.thought));
|
||||
}
|
||||
store.dispatch(appendJupyterInput(message.args.code));
|
||||
},
|
||||
[ActionType.ADD_TASK]: () => {
|
||||
getPlan().then((fetchedPlan) => store.dispatch(setPlan(fetchedPlan)));
|
||||
},
|
||||
|
||||
@@ -11,14 +11,19 @@ import { SocketMessage } from "#/types/ResponseType";
|
||||
import { ActionMessage } from "#/types/Message";
|
||||
import Socket from "./socket";
|
||||
|
||||
export function sendChatMessage(message: string): void {
|
||||
export function sendChatMessage(message: string, isTask: boolean = true): void {
|
||||
store.dispatch(appendUserMessage(message));
|
||||
const event = { action: ActionType.START, args: { task: message } };
|
||||
let event;
|
||||
if (isTask) {
|
||||
event = { action: ActionType.START, args: { task: message } };
|
||||
} else {
|
||||
event = { action: ActionType.USER_MESSAGE, args: { message } };
|
||||
}
|
||||
const eventString = JSON.stringify(event);
|
||||
Socket.send(eventString);
|
||||
}
|
||||
|
||||
export function sendChatMessageFromEvent(event: string | SocketMessage): void {
|
||||
export function addChatMessageFromEvent(event: string | SocketMessage): void {
|
||||
try {
|
||||
let data: ActionMessage;
|
||||
if (typeof event === "string") {
|
||||
|
||||
@@ -12,6 +12,24 @@ export async function selectFile(file: string): Promise<string> {
|
||||
return data.code as string;
|
||||
}
|
||||
|
||||
export async function uploadFile(file: File): Promise<string> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const res = await fetch("/api/upload-file", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(data.error || "Failed to upload file.");
|
||||
}
|
||||
|
||||
return `File uploaded: ${data.filename}, Location: ${data.location}`;
|
||||
}
|
||||
|
||||
export async function getWorkspace(): Promise<WorkspaceFile> {
|
||||
const res = await fetch("/api/refresh-files");
|
||||
const data = await res.json();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { setUrl, setScreenshotSrc } from "#/state/browserSlice";
|
||||
import store from "#/store";
|
||||
import { ObservationMessage } from "#/types/Message";
|
||||
import { appendOutput } from "#/state/commandSlice";
|
||||
import { appendJupyterOutput } from "#/state/jupyterSlice";
|
||||
import ObservationType from "#/types/ObservationType";
|
||||
|
||||
export function handleObservationMessage(message: ObservationMessage) {
|
||||
@@ -10,6 +11,10 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
case ObservationType.RUN:
|
||||
store.dispatch(appendOutput(message.content));
|
||||
break;
|
||||
case ObservationType.RUN_IPYTHON:
|
||||
// FIXME: render this as markdown
|
||||
store.dispatch(appendJupyterOutput(message.content));
|
||||
break;
|
||||
case ObservationType.BROWSE:
|
||||
if (message.extras?.screenshot) {
|
||||
store.dispatch(setScreenshotSrc(message.extras.screenshot));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi, Mock } from "vitest";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
Settings,
|
||||
getSettings,
|
||||
getSettingsDifference,
|
||||
saveSettings,
|
||||
@@ -18,7 +19,8 @@ describe("getSettings", () => {
|
||||
(localStorage.getItem as Mock)
|
||||
.mockReturnValueOnce("llm_value")
|
||||
.mockReturnValueOnce("agent_value")
|
||||
.mockReturnValueOnce("language_value");
|
||||
.mockReturnValueOnce("language_value")
|
||||
.mockReturnValueOnce("api_key");
|
||||
|
||||
const settings = getSettings();
|
||||
|
||||
@@ -26,11 +28,13 @@ describe("getSettings", () => {
|
||||
LLM_MODEL: "llm_value",
|
||||
AGENT: "agent_value",
|
||||
LANGUAGE: "language_value",
|
||||
LLM_API_KEY: "api_key",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle return defaults if localStorage key does not exist", () => {
|
||||
(localStorage.getItem as Mock)
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValueOnce(null);
|
||||
@@ -41,16 +45,18 @@ describe("getSettings", () => {
|
||||
LLM_MODEL: DEFAULT_SETTINGS.LLM_MODEL,
|
||||
AGENT: DEFAULT_SETTINGS.AGENT,
|
||||
LANGUAGE: DEFAULT_SETTINGS.LANGUAGE,
|
||||
LLM_API_KEY: "",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveSettings", () => {
|
||||
it("should save the settings", () => {
|
||||
const settings = {
|
||||
const settings: Settings = {
|
||||
LLM_MODEL: "llm_value",
|
||||
AGENT: "agent_value",
|
||||
LANGUAGE: "language_value",
|
||||
LLM_API_KEY: "some_key",
|
||||
};
|
||||
|
||||
saveSettings(settings);
|
||||
@@ -61,6 +67,10 @@ describe("saveSettings", () => {
|
||||
"LANGUAGE",
|
||||
"language_value",
|
||||
);
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith(
|
||||
"LLM_API_KEY",
|
||||
"some_key",
|
||||
);
|
||||
});
|
||||
|
||||
it("should save partial settings", () => {
|
||||
|
||||
@@ -17,15 +17,19 @@ const validKeys = Object.keys(DEFAULT_SETTINGS) as (keyof Settings)[];
|
||||
/**
|
||||
* Get the settings from local storage or use the default settings if not found
|
||||
*/
|
||||
export const getSettings = (): Settings => ({
|
||||
LLM_MODEL: localStorage.getItem("LLM_MODEL") || DEFAULT_SETTINGS.LLM_MODEL,
|
||||
AGENT: localStorage.getItem("AGENT") || DEFAULT_SETTINGS.AGENT,
|
||||
LANGUAGE: localStorage.getItem("LANGUAGE") || DEFAULT_SETTINGS.LANGUAGE,
|
||||
LLM_API_KEY:
|
||||
localStorage.getItem(
|
||||
`API_KEY_${localStorage.getItem("LLM_MODEL") || DEFAULT_SETTINGS.LLM_MODEL}`,
|
||||
) || DEFAULT_SETTINGS.LLM_API_KEY,
|
||||
});
|
||||
export const getSettings = (): Settings => {
|
||||
const model = localStorage.getItem("LLM_MODEL");
|
||||
const agent = localStorage.getItem("AGENT");
|
||||
const language = localStorage.getItem("LANGUAGE");
|
||||
const apiKey = localStorage.getItem(`API_KEY_${model}`);
|
||||
|
||||
return {
|
||||
LLM_MODEL: model || DEFAULT_SETTINGS.LLM_MODEL,
|
||||
AGENT: agent || DEFAULT_SETTINGS.AGENT,
|
||||
LANGUAGE: language || DEFAULT_SETTINGS.LANGUAGE,
|
||||
LLM_API_KEY: apiKey || DEFAULT_SETTINGS.LLM_API_KEY,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the settings to local storage. Only valid settings are saved.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
export type Cell = {
|
||||
content: string;
|
||||
type: "input" | "output";
|
||||
};
|
||||
|
||||
const initialCells: Cell[] = [];
|
||||
|
||||
export const cellSlice = createSlice({
|
||||
name: "cell",
|
||||
initialState: {
|
||||
cells: initialCells,
|
||||
},
|
||||
reducers: {
|
||||
appendJupyterInput: (state, action) => {
|
||||
state.cells.push({ content: action.payload, type: "input" });
|
||||
},
|
||||
appendJupyterOutput: (state, action) => {
|
||||
state.cells.push({ content: action.payload, type: "output" });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { appendJupyterInput, appendJupyterOutput } = cellSlice.actions;
|
||||
|
||||
export default cellSlice.reducer;
|
||||
@@ -7,6 +7,7 @@ import commandReducer from "./state/commandSlice";
|
||||
import errorsReducer from "./state/errorsSlice";
|
||||
import planReducer from "./state/planSlice";
|
||||
import taskReducer from "./state/taskSlice";
|
||||
import jupyterReducer from "./state/jupyterSlice";
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
browser: browserReducer,
|
||||
@@ -17,6 +18,7 @@ export const rootReducer = combineReducers({
|
||||
errors: errorsReducer,
|
||||
plan: planReducer,
|
||||
agent: agentReducer,
|
||||
jupyter: jupyterReducer,
|
||||
});
|
||||
|
||||
const store = configureStore({
|
||||
|
||||
@@ -2,7 +2,10 @@ enum ActionType {
|
||||
// Initializes the agent. Only sent by client.
|
||||
INIT = "initialize",
|
||||
|
||||
// Starts a new development task. Only sent by the client.
|
||||
// Sends a message from the user
|
||||
USER_MESSAGE = "user_message",
|
||||
|
||||
// Starts a new development task
|
||||
START = "start",
|
||||
|
||||
// Reads the contents of a file.
|
||||
@@ -14,6 +17,9 @@ enum ActionType {
|
||||
// Runs a command.
|
||||
RUN = "run",
|
||||
|
||||
// Runs a IPython command.
|
||||
RUN_IPYTHON = "run_ipython",
|
||||
|
||||
// Kills a background command.
|
||||
KILL = "kill",
|
||||
|
||||
@@ -26,6 +32,9 @@ enum ActionType {
|
||||
// Allows the agent to make a plan, set a goal, or record thoughts.
|
||||
THINK = "think",
|
||||
|
||||
// Allows the agent to respond to the user. Only sent by the agent.
|
||||
TALK = "talk",
|
||||
|
||||
// If you're absolutely certain that you've completed your task and have tested your work,
|
||||
// use the finish action to stop working.
|
||||
FINISH = "finish",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
enum AgentTaskState {
|
||||
INIT = "init",
|
||||
RUNNING = "running",
|
||||
AWAITING_USER_INPUT = "awaiting_user_input",
|
||||
PAUSED = "paused",
|
||||
STOPPED = "stopped",
|
||||
FINISHED = "finished",
|
||||
|
||||
@@ -8,6 +8,9 @@ enum ObservationType {
|
||||
// The output of a command
|
||||
RUN = "run",
|
||||
|
||||
// The output of an IPython command
|
||||
RUN_IPYTHON = "run_ipython",
|
||||
|
||||
// The result of a search
|
||||
RECALL = "recall",
|
||||
|
||||
|
||||
@@ -2,10 +2,20 @@ enum TabOption {
|
||||
PLANNER = "planner",
|
||||
CODE = "code",
|
||||
BROWSER = "browser",
|
||||
JUPYTER = "jupyter",
|
||||
}
|
||||
|
||||
type TabType = TabOption.PLANNER | TabOption.CODE | TabOption.BROWSER;
|
||||
type TabType =
|
||||
| TabOption.PLANNER
|
||||
| TabOption.CODE
|
||||
| TabOption.BROWSER
|
||||
| TabOption.JUPYTER;
|
||||
|
||||
const AllTabs = [TabOption.CODE, TabOption.BROWSER, TabOption.PLANNER];
|
||||
const AllTabs = [
|
||||
TabOption.CODE,
|
||||
TabOption.BROWSER,
|
||||
TabOption.PLANNER,
|
||||
TabOption.JUPYTER,
|
||||
];
|
||||
|
||||
export { AllTabs, TabOption, type TabType };
|
||||
|
||||
@@ -5,10 +5,11 @@ from .agent import (
|
||||
AgentFinishAction,
|
||||
AgentRecallAction,
|
||||
AgentSummarizeAction,
|
||||
AgentTalkAction,
|
||||
AgentThinkAction,
|
||||
)
|
||||
from .base import Action, NullAction
|
||||
from .bash import CmdKillAction, CmdRunAction
|
||||
from .bash import CmdKillAction, CmdRunAction, IPythonRunCellAction
|
||||
from .browse import BrowseURLAction
|
||||
from .fileop import FileReadAction, FileWriteAction
|
||||
from .github import GitHubPushAction
|
||||
@@ -17,11 +18,13 @@ from .tasks import AddTaskAction, ModifyTaskAction
|
||||
actions = (
|
||||
CmdKillAction,
|
||||
CmdRunAction,
|
||||
IPythonRunCellAction,
|
||||
BrowseURLAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
AgentRecallAction,
|
||||
AgentThinkAction,
|
||||
AgentTalkAction,
|
||||
AgentFinishAction,
|
||||
AgentDelegateAction,
|
||||
AddTaskAction,
|
||||
@@ -61,10 +64,12 @@ __all__ = [
|
||||
'FileWriteAction',
|
||||
'AgentRecallAction',
|
||||
'AgentThinkAction',
|
||||
'AgentTalkAction',
|
||||
'AgentFinishAction',
|
||||
'AgentDelegateAction',
|
||||
'AgentEchoAction',
|
||||
'AgentSummarizeAction',
|
||||
'AddTaskAction',
|
||||
'ModifyTaskAction',
|
||||
'IPythonRunCellAction'
|
||||
]
|
||||
|
||||
@@ -18,11 +18,12 @@ if TYPE_CHECKING:
|
||||
@dataclass
|
||||
class AgentRecallAction(ExecutableAction):
|
||||
query: str
|
||||
thought: str = ''
|
||||
action: str = ActionType.RECALL
|
||||
|
||||
async def run(self, controller: 'AgentController') -> AgentRecallObservation:
|
||||
return AgentRecallObservation(
|
||||
content='Recalling memories...',
|
||||
content='',
|
||||
memories=controller.agent.search_memory(self.query),
|
||||
)
|
||||
|
||||
@@ -44,6 +45,22 @@ class AgentThinkAction(NotExecutableAction):
|
||||
return self.thought
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentTalkAction(NotExecutableAction):
|
||||
content: str
|
||||
action: str = ActionType.TALK
|
||||
|
||||
async def run(self, controller: 'AgentController') -> 'Observation':
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.content
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.content
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentEchoAction(ExecutableAction):
|
||||
content: str
|
||||
@@ -70,6 +87,7 @@ class AgentSummarizeAction(NotExecutableAction):
|
||||
@dataclass
|
||||
class AgentFinishAction(NotExecutableAction):
|
||||
outputs: Dict = field(default_factory=dict)
|
||||
thought: str = ''
|
||||
action: str = ActionType.FINISH
|
||||
|
||||
async def run(self, controller: 'AgentController') -> 'Observation':
|
||||
@@ -84,6 +102,7 @@ class AgentFinishAction(NotExecutableAction):
|
||||
class AgentDelegateAction(ExecutableAction):
|
||||
agent: str
|
||||
inputs: dict
|
||||
thought: str = ''
|
||||
action: str = ActionType.DELEGATE
|
||||
|
||||
async def run(self, controller: 'AgentController') -> 'Observation':
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import os
|
||||
import pathlib
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from opendevin.schema import ActionType
|
||||
from opendevin import config
|
||||
from opendevin.schema import ActionType, ConfigType
|
||||
|
||||
from .base import ExecutableAction
|
||||
|
||||
@@ -9,11 +12,14 @@ if TYPE_CHECKING:
|
||||
from opendevin.controller import AgentController
|
||||
from opendevin.observation import CmdOutputObservation, Observation
|
||||
|
||||
from opendevin.observation import IPythonRunCellObservation
|
||||
|
||||
|
||||
@dataclass
|
||||
class CmdRunAction(ExecutableAction):
|
||||
command: str
|
||||
background: bool = False
|
||||
thought: str = ''
|
||||
action: str = ActionType.RUN
|
||||
|
||||
async def run(self, controller: 'AgentController') -> 'Observation':
|
||||
@@ -23,10 +29,18 @@ class CmdRunAction(ExecutableAction):
|
||||
def message(self) -> str:
|
||||
return f'Running command: {self.command}'
|
||||
|
||||
def __str__(self) -> str:
|
||||
ret = '**CmdRunAction**\n'
|
||||
if self.thought:
|
||||
ret += f'THOUGHT:{self.thought}\n'
|
||||
ret += f'COMMAND:\n{self.command}'
|
||||
return ret
|
||||
|
||||
|
||||
@dataclass
|
||||
class CmdKillAction(ExecutableAction):
|
||||
id: int
|
||||
thought: str = ''
|
||||
action: str = ActionType.KILL
|
||||
|
||||
async def run(self, controller: 'AgentController') -> 'CmdOutputObservation':
|
||||
@@ -35,3 +49,48 @@ class CmdKillAction(ExecutableAction):
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return f'Killing command: {self.id}'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'**CmdKillAction**\n{self.id}'
|
||||
|
||||
|
||||
@dataclass
|
||||
class IPythonRunCellAction(ExecutableAction):
|
||||
code: str
|
||||
thought: str = ''
|
||||
action: str = ActionType.RUN_IPYTHON
|
||||
|
||||
async def run(self, controller: 'AgentController') -> 'IPythonRunCellObservation':
|
||||
# echo "import math" | execute_cli
|
||||
# write code to a temporary file and pass it to `execute_cli` via stdin
|
||||
tmp_filepath = os.path.join(
|
||||
config.get(ConfigType.WORKSPACE_BASE),
|
||||
'.tmp', '.ipython_execution_tmp.py'
|
||||
)
|
||||
pathlib.Path(os.path.dirname(tmp_filepath)).mkdir(parents=True, exist_ok=True)
|
||||
with open(tmp_filepath, 'w') as tmp_file:
|
||||
tmp_file.write(self.code)
|
||||
|
||||
tmp_filepath_inside_sandbox = os.path.join(
|
||||
config.get(ConfigType.WORKSPACE_MOUNT_PATH_IN_SANDBOX),
|
||||
'.tmp', '.ipython_execution_tmp.py'
|
||||
)
|
||||
obs = controller.action_manager.run_command(
|
||||
f'execute_cli < {tmp_filepath_inside_sandbox}',
|
||||
background=False
|
||||
)
|
||||
return IPythonRunCellObservation(
|
||||
content=obs.content,
|
||||
code=self.code
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
ret = '**IPythonRunCellAction**\n'
|
||||
if self.thought:
|
||||
ret += f'THOUGHT:{self.thought}\n'
|
||||
ret += f'CODE:\n{self.code}'
|
||||
return ret
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return f'Running Python code interactively: {self.code}'
|
||||
|
||||
@@ -17,6 +17,7 @@ if TYPE_CHECKING:
|
||||
@dataclass
|
||||
class BrowseURLAction(ExecutableAction):
|
||||
url: str
|
||||
thought: str = ''
|
||||
action: str = ActionType.BROWSE
|
||||
|
||||
async def run(self, controller: 'AgentController') -> BrowserOutputObservation: # type: ignore
|
||||
|
||||
@@ -50,7 +50,7 @@ class FileReadAction(ExecutableAction):
|
||||
path: str
|
||||
start: int = 0
|
||||
end: int = -1
|
||||
thoughts: str = ''
|
||||
thought: str = ''
|
||||
action: str = ActionType.READ
|
||||
|
||||
def _read_lines(self, all_lines: list[str]):
|
||||
@@ -100,7 +100,7 @@ class FileWriteAction(ExecutableAction):
|
||||
content: str
|
||||
start: int = 0
|
||||
end: int = -1
|
||||
thoughts: str = ''
|
||||
thought: str = ''
|
||||
action: str = ActionType.WRITE
|
||||
|
||||
def _insert_lines(self, to_insert: list[str], original: list[str]):
|
||||
|
||||
@@ -15,6 +15,7 @@ class AddTaskAction(ExecutableAction):
|
||||
parent: str
|
||||
goal: str
|
||||
subtasks: list = field(default_factory=list)
|
||||
thought: str = ''
|
||||
action: str = ActionType.ADD_TASK
|
||||
|
||||
async def run(self, controller: 'AgentController') -> NullObservation: # type: ignore
|
||||
@@ -31,6 +32,7 @@ class AddTaskAction(ExecutableAction):
|
||||
class ModifyTaskAction(ExecutableAction):
|
||||
id: str
|
||||
state: str
|
||||
thought: str = ''
|
||||
action: str = ActionType.MODIFY_TASK
|
||||
|
||||
async def run(self, controller: 'AgentController') -> NullObservation: # type: ignore
|
||||
@@ -47,6 +49,7 @@ class ModifyTaskAction(ExecutableAction):
|
||||
class TaskStateChangedAction(NotExecutableAction):
|
||||
"""Fake action, just to notify the client that a task state has changed."""
|
||||
task_state: str
|
||||
thought: str = ''
|
||||
action: str = ActionType.CHANGE_TASK_STATE
|
||||
|
||||
@property
|
||||
|
||||
+3
-2
@@ -52,8 +52,10 @@ DEFAULT_CONFIG: dict = {
|
||||
ConfigType.USE_HOST_NETWORK: 'false',
|
||||
ConfigType.SSH_HOSTNAME: 'localhost',
|
||||
ConfigType.DISABLE_COLOR: 'false',
|
||||
ConfigType.SANDBOX_USER_ID: os.getuid() if hasattr(os, 'getuid') else None,
|
||||
ConfigType.SANDBOX_TIMEOUT: 120,
|
||||
ConfigType.GITHUB_TOKEN: None
|
||||
ConfigType.GITHUB_TOKEN: None,
|
||||
ConfigType.SANDBOX_USER_ID: None
|
||||
}
|
||||
|
||||
config_str = ''
|
||||
@@ -169,7 +171,6 @@ def finalize_config():
|
||||
if config.get(ConfigType.WORKSPACE_MOUNT_PATH) is None:
|
||||
config[ConfigType.WORKSPACE_MOUNT_PATH] = config.get(ConfigType.WORKSPACE_BASE)
|
||||
|
||||
|
||||
finalize_config()
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import asyncio
|
||||
from typing import Callable, List, Type
|
||||
|
||||
from agenthub.codeact_agent.codeact_agent import CodeActAgent
|
||||
from opendevin import config
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
AgentDelegateAction,
|
||||
AgentFinishAction,
|
||||
AgentTalkAction,
|
||||
NullAction,
|
||||
)
|
||||
from opendevin.action.tasks import TaskStateChangedAction
|
||||
@@ -23,8 +25,10 @@ from opendevin.observation import (
|
||||
AgentErrorObservation,
|
||||
NullObservation,
|
||||
Observation,
|
||||
UserMessageObservation,
|
||||
)
|
||||
from opendevin.plan import Plan
|
||||
from opendevin.sandbox import DockerSSHBox
|
||||
from opendevin.schema import TaskState
|
||||
from opendevin.schema.config import ConfigType
|
||||
from opendevin.state import State
|
||||
@@ -64,6 +68,11 @@ class AgentController:
|
||||
# Initialize agent-required plugins for sandbox (if any)
|
||||
self.action_manager.init_sandbox_plugins(agent.sandbox_plugins)
|
||||
|
||||
if isinstance(agent, CodeActAgent) and not isinstance(self.action_manager.sandbox, DockerSSHBox):
|
||||
logger.warning('CodeActAgent requires DockerSSHBox as sandbox! Using other sandbox that are not stateful (LocalBox, DockerExecBox) will not work properly.')
|
||||
|
||||
self._await_user_message_queue: asyncio.Queue = asyncio.Queue()
|
||||
|
||||
def update_state_for_step(self, i):
|
||||
if self.state is None:
|
||||
return
|
||||
@@ -174,6 +183,36 @@ class AgentController:
|
||||
async def notify_task_state_changed(self):
|
||||
await self._run_callbacks(TaskStateChangedAction(self._task_state))
|
||||
|
||||
async def add_user_message(self, message: UserMessageObservation):
|
||||
if self.state is None:
|
||||
return
|
||||
|
||||
if self._task_state == TaskState.AWAITING_USER_INPUT:
|
||||
self._await_user_message_queue.put_nowait(message)
|
||||
|
||||
# set the task state to running
|
||||
self._task_state = TaskState.RUNNING
|
||||
await self.notify_task_state_changed()
|
||||
|
||||
elif self._task_state == TaskState.RUNNING:
|
||||
self.add_history(NullAction(), message)
|
||||
|
||||
else:
|
||||
raise ValueError(f'Task (state: {self._task_state}) is not in a state to add user message')
|
||||
|
||||
async def wait_for_user_input(self) -> UserMessageObservation:
|
||||
self._task_state = TaskState.AWAITING_USER_INPUT
|
||||
await self.notify_task_state_changed()
|
||||
# wait for the next user message
|
||||
if len(self.callbacks) == 0:
|
||||
logger.info('Use STDIN to request user message as no callbacks are registered', extra={'msg_type': 'INFO'})
|
||||
message = input('Request user input [type /exit to stop interaction] >> ')
|
||||
user_message_observation = UserMessageObservation(message)
|
||||
else:
|
||||
user_message_observation = await self._await_user_message_queue.get()
|
||||
self._await_user_message_queue.task_done()
|
||||
return user_message_observation
|
||||
|
||||
async def start_delegate(self, action: AgentDelegateAction):
|
||||
AgentCls: Type[Agent] = Agent.get_cls(action.agent)
|
||||
agent = AgentCls(llm=self.agent.llm)
|
||||
@@ -201,7 +240,8 @@ class AgentController:
|
||||
return False
|
||||
|
||||
logger.info(f'STEP {i}', extra={'msg_type': 'STEP'})
|
||||
logger.info(self.state.plan.main_goal, extra={'msg_type': 'PLAN'})
|
||||
if i == 0:
|
||||
logger.info(self.state.plan.main_goal, extra={'msg_type': 'PLAN'})
|
||||
if self.state.num_of_chars > self.max_chars:
|
||||
raise MaxCharsExceedError(self.state.num_of_chars, self.max_chars)
|
||||
|
||||
@@ -226,6 +266,14 @@ class AgentController:
|
||||
|
||||
await self._run_callbacks(action)
|
||||
|
||||
# whether to await for user messages
|
||||
if isinstance(action, AgentTalkAction):
|
||||
# await for the next user messages
|
||||
user_message_observation = await self.wait_for_user_input()
|
||||
logger.info(user_message_observation, extra={'msg_type': 'OBSERVATION'})
|
||||
self.add_history(action, user_message_observation)
|
||||
return False
|
||||
|
||||
finished = isinstance(action, AgentFinishAction)
|
||||
if finished:
|
||||
self.state.outputs = action.outputs # type: ignore[attr-defined]
|
||||
|
||||
@@ -5,7 +5,7 @@ from .error import AgentErrorObservation
|
||||
from .files import FileReadObservation, FileWriteObservation
|
||||
from .message import AgentMessageObservation, UserMessageObservation
|
||||
from .recall import AgentRecallObservation
|
||||
from .run import CmdOutputObservation
|
||||
from .run import CmdOutputObservation, IPythonRunCellObservation
|
||||
|
||||
observations = (
|
||||
CmdOutputObservation,
|
||||
@@ -40,6 +40,7 @@ __all__ = [
|
||||
'Observation',
|
||||
'NullObservation',
|
||||
'CmdOutputObservation',
|
||||
'IPythonRunCellObservation',
|
||||
'BrowserOutputObservation',
|
||||
'FileReadObservation',
|
||||
'FileWriteObservation',
|
||||
|
||||
@@ -23,3 +23,21 @@ class CmdOutputObservation(Observation):
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return f'Command `{self.command}` executed with exit code {self.exit_code}.'
|
||||
|
||||
|
||||
@dataclass
|
||||
class IPythonRunCellObservation(Observation):
|
||||
"""
|
||||
This data class represents the output of a IPythonRunCellAction.
|
||||
"""
|
||||
|
||||
code: str
|
||||
observation: str = ObservationType.RUN_IPYTHON
|
||||
|
||||
@property
|
||||
def error(self) -> bool:
|
||||
return False # IPython cells do not return exit codes
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return 'Coded executed in IPython cell.'
|
||||
|
||||
@@ -122,7 +122,10 @@ class DockerExecBox(Sandbox):
|
||||
self.container.exec_run(
|
||||
f'kill -9 {pid}', workdir=SANDBOX_WORKSPACE_DIR)
|
||||
return -1, f'Command: "{cmd}" timed out'
|
||||
return exit_code, logs.decode('utf-8').strip()
|
||||
logs_out = logs.decode('utf-8')
|
||||
if logs_out.endswith('\n'):
|
||||
logs_out = logs_out[:-1]
|
||||
return exit_code, logs_out
|
||||
|
||||
def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False):
|
||||
# mkdir -p sandbox_dest if it doesn't exist
|
||||
|
||||
@@ -41,7 +41,6 @@ if SANDBOX_USER_ID := config.get(ConfigType.SANDBOX_USER_ID):
|
||||
elif hasattr(os, 'getuid'):
|
||||
USER_ID = os.getuid()
|
||||
|
||||
|
||||
class DockerSSHBox(Sandbox):
|
||||
instance_id: str
|
||||
container_image: str
|
||||
@@ -62,6 +61,7 @@ class DockerSSHBox(Sandbox):
|
||||
timeout: int = 120,
|
||||
sid: str | None = None,
|
||||
):
|
||||
logger.info(f'SSHBox is running as {"opendevin" if RUN_AS_DEVIN else "root"} user with USER_ID={USER_ID} in the sandbox')
|
||||
# Initialize docker client. Throws an exception if Docker is not reachable.
|
||||
try:
|
||||
self.docker_client = docker.from_env()
|
||||
@@ -150,8 +150,10 @@ class DockerSSHBox(Sandbox):
|
||||
workdir=SANDBOX_WORKSPACE_DIR,
|
||||
)
|
||||
if exit_code != 0:
|
||||
raise Exception(
|
||||
f'Failed to chown workspace directory for opendevin in sandbox: {logs}')
|
||||
# This is not a fatal error, just a warning
|
||||
logger.warning(
|
||||
f'Failed to chown workspace directory for opendevin in sandbox: {logs}. But this should be fine if the {SANDBOX_WORKSPACE_DIR=} is mounted by the app docker container.'
|
||||
)
|
||||
else:
|
||||
exit_code, logs = self.container.exec_run(
|
||||
# change password for root
|
||||
@@ -169,7 +171,7 @@ class DockerSSHBox(Sandbox):
|
||||
|
||||
def start_ssh_session(self):
|
||||
# start ssh session at the background
|
||||
self.ssh = pxssh.pxssh(echo=False)
|
||||
self.ssh = pxssh.pxssh()
|
||||
hostname = SSH_HOSTNAME
|
||||
if RUN_AS_DEVIN:
|
||||
username = 'opendevin'
|
||||
@@ -213,14 +215,36 @@ class DockerSSHBox(Sandbox):
|
||||
# send a SIGINT to the process
|
||||
self.ssh.sendintr()
|
||||
self.ssh.prompt()
|
||||
command_output = self.ssh.before.decode('utf-8').strip()
|
||||
command_output = self.ssh.before.decode(
|
||||
'utf-8').lstrip(cmd).strip()
|
||||
return -1, f'Command: "{cmd}" timed out. Sending SIGINT to the process: {command_output}'
|
||||
command_output = self.ssh.before.decode('utf-8').strip()
|
||||
|
||||
# once out, make sure that we have *every* output, we while loop until we get an empty output
|
||||
while True:
|
||||
logger.debug('WAITING FOR .prompt()')
|
||||
self.ssh.sendline('\n')
|
||||
timeout_not_reached = self.ssh.prompt(timeout=1)
|
||||
if not timeout_not_reached:
|
||||
logger.debug('TIMEOUT REACHED')
|
||||
break
|
||||
logger.debug('WAITING FOR .before')
|
||||
output = self.ssh.before.decode('utf-8').strip()
|
||||
logger.debug(f'WAITING FOR END OF command output ({bool(output)}): {output}')
|
||||
if output == '':
|
||||
break
|
||||
command_output += output
|
||||
command_output = command_output.lstrip(cmd).strip()
|
||||
|
||||
# get the exit code
|
||||
self.ssh.sendline('echo $?')
|
||||
self.ssh.prompt(timeout=10)
|
||||
exit_code = int(self.ssh.before.decode('utf-8').strip())
|
||||
self.ssh.prompt()
|
||||
exit_code = self.ssh.before.decode('utf-8')
|
||||
while not exit_code.startswith('echo $?'):
|
||||
self.ssh.prompt()
|
||||
exit_code = self.ssh.before.decode('utf-8')
|
||||
logger.debug(f'WAITING FOR exit code: {exit_code}')
|
||||
exit_code = int(exit_code.lstrip('echo $?').strip())
|
||||
return exit_code, command_output
|
||||
|
||||
def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False):
|
||||
@@ -305,9 +329,10 @@ class DockerSSHBox(Sandbox):
|
||||
pass
|
||||
|
||||
def get_working_directory(self):
|
||||
self.ssh.sendline('pwd')
|
||||
self.ssh.prompt(timeout=10)
|
||||
return self.ssh.before.decode('utf-8').strip()
|
||||
exit_code, result = self.execute('pwd')
|
||||
if exit_code != 0:
|
||||
raise Exception('Failed to get working directory')
|
||||
return result.strip()
|
||||
|
||||
def is_container_running(self):
|
||||
try:
|
||||
@@ -351,7 +376,6 @@ class DockerSSHBox(Sandbox):
|
||||
**network_kwargs,
|
||||
working_dir=SANDBOX_WORKSPACE_DIR,
|
||||
name=self.container_name,
|
||||
hostname='opendevin_sandbox',
|
||||
detach=True,
|
||||
volumes={
|
||||
mount_dir: {
|
||||
|
||||
@@ -187,8 +187,7 @@ class JupyterKernel:
|
||||
outputs.append(msg['content']['data']['text/plain'])
|
||||
if 'image/png' in msg['content']['data']:
|
||||
# use markdone to display image (in case of large image)
|
||||
# outputs.append(f"\n<img src=\'data:image/png;base64,{msg['content']['data']['image/png']}\'/>\n")
|
||||
outputs.append(f"")
|
||||
outputs.append(f"\n\n")
|
||||
|
||||
elif msg_type == 'execute_reply':
|
||||
execution_done = True
|
||||
|
||||
@@ -10,8 +10,12 @@ class ActionTypeSchema(BaseModel):
|
||||
"""Initializes the agent. Only sent by client.
|
||||
"""
|
||||
|
||||
USER_MESSAGE: str = Field(default='user_message')
|
||||
"""Sends a message from the user. Only sent by the client.
|
||||
"""
|
||||
|
||||
START: str = Field(default='start')
|
||||
"""Starts a new development task. Only sent by the client.
|
||||
"""Starts a new development task OR send chat from the user. Only sent by the client.
|
||||
"""
|
||||
|
||||
READ: str = Field(default='read')
|
||||
@@ -26,6 +30,10 @@ class ActionTypeSchema(BaseModel):
|
||||
"""Runs a command.
|
||||
"""
|
||||
|
||||
RUN_IPYTHON: str = Field(default='run_ipython')
|
||||
"""Runs a IPython cell.
|
||||
"""
|
||||
|
||||
KILL: str = Field(default='kill')
|
||||
"""Kills a background command.
|
||||
"""
|
||||
@@ -42,6 +50,10 @@ class ActionTypeSchema(BaseModel):
|
||||
"""Allows the agent to make a plan, set a goal, or record thoughts
|
||||
"""
|
||||
|
||||
TALK: str = Field(default='talk')
|
||||
"""Allows the agent to respond to the user.
|
||||
"""
|
||||
|
||||
DELEGATE: str = Field(default='delegate')
|
||||
"""Delegates a task to another agent.
|
||||
"""
|
||||
|
||||
@@ -20,6 +20,10 @@ class ObservationTypeSchema(BaseModel):
|
||||
"""The output of a command
|
||||
"""
|
||||
|
||||
RUN_IPYTHON: str = Field(default='run_ipython')
|
||||
"""Runs a IPython cell.
|
||||
"""
|
||||
|
||||
RECALL: str = Field(default='recall')
|
||||
"""The result of a search
|
||||
"""
|
||||
|
||||
@@ -10,6 +10,10 @@ class TaskState(str, Enum):
|
||||
"""The task is running.
|
||||
"""
|
||||
|
||||
AWAITING_USER_INPUT = 'awaiting_user_input'
|
||||
"""The task is awaiting user input.
|
||||
"""
|
||||
|
||||
PAUSED = 'paused'
|
||||
"""The task is paused.
|
||||
"""
|
||||
|
||||
@@ -94,6 +94,8 @@ class AgentUnit:
|
||||
await self.create_controller(data)
|
||||
case ActionType.START:
|
||||
await self.start_task(data)
|
||||
case ActionType.USER_MESSAGE:
|
||||
await self.send_user_message(data)
|
||||
case ActionType.CHANGE_TASK_STATE:
|
||||
task_state_action = data.get('args', {}).get('task_state_action', None)
|
||||
if task_state_action is None:
|
||||
@@ -177,23 +179,27 @@ class AgentUnit:
|
||||
Args:
|
||||
start_event: The start event data.
|
||||
"""
|
||||
if 'task' not in start_event['args']:
|
||||
await self.send_error('No task specified')
|
||||
return
|
||||
await self.send_message('Starting new task...')
|
||||
task = start_event['args']['task']
|
||||
if self.controller is None:
|
||||
await self.send_error('No agent started. Please wait a second...')
|
||||
return
|
||||
try:
|
||||
if self.agent_task:
|
||||
self.agent_task.cancel()
|
||||
assert not self.agent_task, 'Agent task already running'
|
||||
self.agent_task = asyncio.create_task(
|
||||
self.controller.start(task), name='agent start task loop'
|
||||
)
|
||||
except Exception as e:
|
||||
await self.send_error(f'Error during task loop: {e}')
|
||||
|
||||
async def send_user_message(self, data: dict):
|
||||
if not self.agent_task or not self.controller:
|
||||
await self.send_error('No agent started.')
|
||||
return
|
||||
|
||||
await self.controller.add_user_message(
|
||||
UserMessageObservation(data['args']['message'])
|
||||
)
|
||||
|
||||
async def set_task_state(self, new_state_action: TaskStateAction):
|
||||
"""Sets the state of the agent task."""
|
||||
if self.controller is None:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import json
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import litellm
|
||||
from fastapi import Depends, FastAPI, Response, WebSocket, status
|
||||
from fastapi import Depends, FastAPI, Response, UploadFile, WebSocket, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
@@ -137,6 +138,24 @@ def select_file(file: str):
|
||||
return {'code': content}
|
||||
|
||||
|
||||
@app.post('/api/upload-file')
|
||||
async def upload_file(file: UploadFile):
|
||||
try:
|
||||
workspace_base = config.get(ConfigType.WORKSPACE_BASE)
|
||||
file_path = Path(workspace_base, file.filename)
|
||||
# The following will check if the file is within the workspace base and throw an exception if not
|
||||
file_path.resolve().relative_to(Path(workspace_base).resolve())
|
||||
with open(file_path, 'wb') as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
except Exception as e:
|
||||
logger.error(f'Error saving file {file.filename}: {e}', exc_info=True)
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={'error': f'Error saving file: {e}'}
|
||||
)
|
||||
return {'filename': file.filename, 'location': str(file_path)}
|
||||
|
||||
|
||||
@app.get('/api/plan')
|
||||
def get_plan(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security_scheme),
|
||||
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "OpenDevin",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
Generated
+15
-1
@@ -4446,6 +4446,20 @@ files = [
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.9"
|
||||
description = "A streaming multipart parser for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
|
||||
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.1"
|
||||
@@ -6329,4 +6343,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "62bc3f49304639795bc824fb0ceacbfaeb40c88ea9e5a90d13d4a3bf5219dbba"
|
||||
content-hash = "4e50c6a8427ab71919c0c5bcdfbb83afe71aa50966452b23bca62e0b69da4c30"
|
||||
|
||||
@@ -26,6 +26,7 @@ playwright = "*"
|
||||
e2b = "^0.14.13"
|
||||
pexpect = "*"
|
||||
jinja2 = "^3.1.3"
|
||||
python-multipart = "*"
|
||||
|
||||
[tool.poetry.group.llama-index.dependencies]
|
||||
llama-index = "*"
|
||||
|
||||
@@ -89,12 +89,15 @@ mkdir workspace
|
||||
poetry run python ./opendevin/main.py -i 10 -t "Write a shell script 'hello.sh' that prints 'hello'." -c "MonologueAgent" -d "./workspace"
|
||||
```
|
||||
|
||||
**NOTE**: If your agent decide to support user-agent interaction via natural language (e.g., you will prompted to enter user resposes when running the above `main.py` command), you should create a file named `tests/integration/mock/<AgentName>/<TestName>/user_responses.log` containing all the responses in order you provided to the agent, delimited by newline ('\n'). This will be used to mock the STDIN during testing.
|
||||
|
||||
After running the above commands, you should be able to locate the real prompts
|
||||
and responses logged. The log folder follows `logs/llm/%y-%m-%d_%H-%M.log` format.
|
||||
|
||||
Now, move all files under that folder to `tests/integration/mock/<AgentName>/<TestName>` folder. For example, moving all files from `logs/llm/24-04-23_21-55/` folder to
|
||||
`tests/integration/mock/MonologueAgent/test_write_simple_script` folder.
|
||||
|
||||
|
||||
That's it, you are good to go! When you launch an integration test, mock
|
||||
responses are loaded and used to replace a real LLM, so that we get
|
||||
deterministic and consistent behavior, and most importantly, without spending real
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from functools import partial
|
||||
@@ -53,6 +54,28 @@ def get_mock_response(test_name, messages):
|
||||
return resp_file.read()
|
||||
|
||||
|
||||
def mock_user_response(*args, test_name, **kwargs):
|
||||
"""The agent will ask for user input using `input()` when calling `asyncio.run(main(task))`.
|
||||
This function mocks the user input by providing the response from the mock response file.
|
||||
|
||||
It will read the `user_responses.log` file in the test directory and set as
|
||||
STDIN input for the agent to read.
|
||||
"""
|
||||
user_response_file = os.path.join(
|
||||
script_dir,
|
||||
'mock',
|
||||
os.environ.get('AGENT'),
|
||||
test_name,
|
||||
'user_responses.log'
|
||||
)
|
||||
if not os.path.exists(user_response_file):
|
||||
return ''
|
||||
with open(user_response_file, 'r') as f:
|
||||
ret = f.read().rstrip()
|
||||
ret += '\n'
|
||||
return ret
|
||||
|
||||
|
||||
def mock_completion(*args, test_name, **kwargs):
|
||||
messages = kwargs['messages']
|
||||
message_str = ''
|
||||
@@ -67,4 +90,11 @@ def mock_completion(*args, test_name, **kwargs):
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_completion(monkeypatch, request):
|
||||
test_name = request.node.name
|
||||
# Mock LLM completion
|
||||
monkeypatch.setattr('opendevin.llm.llm.litellm_completion', partial(mock_completion, test_name=test_name))
|
||||
|
||||
# Mock user input (only for tests that have user_responses.log)
|
||||
user_responses_str = mock_user_response(test_name=test_name)
|
||||
if user_responses_str:
|
||||
user_responses = io.StringIO(user_responses_str)
|
||||
monkeypatch.setattr('sys.stdin', user_responses)
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
|
||||
|
||||
----------
|
||||
|
||||
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
|
||||
The assistant can interact with an interactive Python (Jupyter Notebook) environment and receive the corresponding output when needed. The code should be enclosed using "<execute_ipython>" tag, for example:
|
||||
<execute_ipython>
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
|
||||
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
|
||||
The assistant should attempt fewer things at a time instead of putting too much commands OR code in one "execute" block.
|
||||
The assistant can install Python packages through bash by <execute_bash> pip install [package needed] </execute_bash> and should always import packages and define variables before starting to use them.
|
||||
The assistant should stop <execute> and provide an answer when they have already obtained the answer from the execution result.
|
||||
|
||||
|
||||
Apart from the standard bash commands, you can also use the following special commands in <execute_bash> environment:
|
||||
open <path> [<line_number>] - opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line
|
||||
goto <line_number> - moves the window to show <line_number>
|
||||
scroll_down - moves the window down {WINDOW} lines
|
||||
scroll_up - moves the window down {WINDOW} lines
|
||||
create <filename> - creates and opens a new file with the given name
|
||||
search_dir <search_term> [<dir>] - searches for search_term in all files in dir. If dir is not provided, searches in the current directory
|
||||
search_file <search_term> [<file>] - searches for search_term in file. If file is not provided, searches in the current open file
|
||||
find_file <file_name> [<dir>] - finds all files with the given name in dir. If dir is not provided, searches in the current directory
|
||||
edit <start_line>:<end_line>
|
||||
<replacement_text>
|
||||
end_of_edit - replaces lines <start_line> through <end_line> (inclusive) with the given text in the open file. The replacement text is terminated by a line with only end_of_edit on it. All of the <replacement text> will be entered, so make sure your indentation is formatted properly. Python files will be checked for syntax errors after the edit. If the system detects a syntax error, the edit will not be executed. Simply try to edit the file again, but make sure to read the error message and modify the edit command you issue accordingly. Issuing the same command a second time will just lead to the same error message again.
|
||||
Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
|
||||
Whenever possible, execute the code for the user using <execute_ipython> or <execute_bash> instead of providing it.
|
||||
The assistant's response should be concise, but do express their thoughts.
|
||||
Try to include one of <execute_ipython> or <execute_bash> in each of your responses, unless it is a direct answer to a question OR a message to the user.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
Here is an example of how you can interact with the environment for task solving:
|
||||
|
||||
--- START OF EXAMPLE ---
|
||||
|
||||
USER: Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me write the Python code for starting a web server and save it to a file `app.py`:
|
||||
<execute_ipython>
|
||||
CODE='''
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
'''
|
||||
with open('app.py', 'w') as f:
|
||||
f.write(CODE)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[Code executed successfully with no output]
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Traceback (most recent call last):
|
||||
File "/workspace/app.py", line 2, in <module>
|
||||
from flask import Flask
|
||||
ModuleNotFoundError: No module named 'flask'
|
||||
|
||||
ASSISTANT:
|
||||
It seems that Flask is not installed. Let me install Flask for you:
|
||||
<execute_bash>
|
||||
pip install flask
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Defaulting to user installation because normal site-packages is not writeable
|
||||
Collecting flask
|
||||
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
|
||||
Collecting blinker>=1.6.2
|
||||
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
|
||||
Collecting Werkzeug>=3.0.0
|
||||
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
|
||||
Collecting click>=8.1.3
|
||||
Using cached click-8.1.7-py3-none-any.whl (97 kB)
|
||||
Collecting itsdangerous>=2.1.2
|
||||
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
|
||||
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
|
||||
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
|
||||
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
|
||||
|
||||
ASSISTANT:
|
||||
Now that Flask is installed, let me run the Python file again:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 124
|
||||
|
||||
ASSISTANT:
|
||||
Let me check the server log again:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!
|
||||
|
||||
USER: Now kill the server, make it display the numbers in a table format.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me stop the server first:
|
||||
<execute_bash>
|
||||
kill 124
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1]+ Terminated python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT:
|
||||
The server has been stopped. Let me open the Python file and modify it.
|
||||
<execute_bash>
|
||||
open app.py
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return str(numbers)
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. Let me do that for you:
|
||||
|
||||
<execute_bash>
|
||||
edit 8:8 <<EOF
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
EOF
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.
|
||||
|
||||
ASSISTANT:
|
||||
The file has been updated. Let me run the Python file again with the new changes:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 126
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Free free to let me know if you have any further requests!
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
|
||||
|
||||
NOW, LET'S START!
|
||||
|
||||
Write a shell script 'hello.sh' that prints 'hello'.
|
||||
@@ -0,0 +1,229 @@
|
||||
|
||||
|
||||
----------
|
||||
|
||||
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
|
||||
The assistant can interact with an interactive Python (Jupyter Notebook) environment and receive the corresponding output when needed. The code should be enclosed using "<execute_ipython>" tag, for example:
|
||||
<execute_ipython>
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
|
||||
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
|
||||
The assistant should attempt fewer things at a time instead of putting too much commands OR code in one "execute" block.
|
||||
The assistant can install Python packages through bash by <execute_bash> pip install [package needed] </execute_bash> and should always import packages and define variables before starting to use them.
|
||||
The assistant should stop <execute> and provide an answer when they have already obtained the answer from the execution result.
|
||||
|
||||
|
||||
Apart from the standard bash commands, you can also use the following special commands in <execute_bash> environment:
|
||||
open <path> [<line_number>] - opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line
|
||||
goto <line_number> - moves the window to show <line_number>
|
||||
scroll_down - moves the window down {WINDOW} lines
|
||||
scroll_up - moves the window down {WINDOW} lines
|
||||
create <filename> - creates and opens a new file with the given name
|
||||
search_dir <search_term> [<dir>] - searches for search_term in all files in dir. If dir is not provided, searches in the current directory
|
||||
search_file <search_term> [<file>] - searches for search_term in file. If file is not provided, searches in the current open file
|
||||
find_file <file_name> [<dir>] - finds all files with the given name in dir. If dir is not provided, searches in the current directory
|
||||
edit <start_line>:<end_line>
|
||||
<replacement_text>
|
||||
end_of_edit - replaces lines <start_line> through <end_line> (inclusive) with the given text in the open file. The replacement text is terminated by a line with only end_of_edit on it. All of the <replacement text> will be entered, so make sure your indentation is formatted properly. Python files will be checked for syntax errors after the edit. If the system detects a syntax error, the edit will not be executed. Simply try to edit the file again, but make sure to read the error message and modify the edit command you issue accordingly. Issuing the same command a second time will just lead to the same error message again.
|
||||
Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
|
||||
Whenever possible, execute the code for the user using <execute_ipython> or <execute_bash> instead of providing it.
|
||||
The assistant's response should be concise, but do express their thoughts.
|
||||
Try to include one of <execute_ipython> or <execute_bash> in each of your responses, unless it is a direct answer to a question OR a message to the user.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
Here is an example of how you can interact with the environment for task solving:
|
||||
|
||||
--- START OF EXAMPLE ---
|
||||
|
||||
USER: Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me write the Python code for starting a web server and save it to a file `app.py`:
|
||||
<execute_ipython>
|
||||
CODE='''
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
'''
|
||||
with open('app.py', 'w') as f:
|
||||
f.write(CODE)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[Code executed successfully with no output]
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Traceback (most recent call last):
|
||||
File "/workspace/app.py", line 2, in <module>
|
||||
from flask import Flask
|
||||
ModuleNotFoundError: No module named 'flask'
|
||||
|
||||
ASSISTANT:
|
||||
It seems that Flask is not installed. Let me install Flask for you:
|
||||
<execute_bash>
|
||||
pip install flask
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Defaulting to user installation because normal site-packages is not writeable
|
||||
Collecting flask
|
||||
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
|
||||
Collecting blinker>=1.6.2
|
||||
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
|
||||
Collecting Werkzeug>=3.0.0
|
||||
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
|
||||
Collecting click>=8.1.3
|
||||
Using cached click-8.1.7-py3-none-any.whl (97 kB)
|
||||
Collecting itsdangerous>=2.1.2
|
||||
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
|
||||
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
|
||||
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
|
||||
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
|
||||
|
||||
ASSISTANT:
|
||||
Now that Flask is installed, let me run the Python file again:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 124
|
||||
|
||||
ASSISTANT:
|
||||
Let me check the server log again:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!
|
||||
|
||||
USER: Now kill the server, make it display the numbers in a table format.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me stop the server first:
|
||||
<execute_bash>
|
||||
kill 124
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1]+ Terminated python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT:
|
||||
The server has been stopped. Let me open the Python file and modify it.
|
||||
<execute_bash>
|
||||
open app.py
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return str(numbers)
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. Let me do that for you:
|
||||
|
||||
<execute_bash>
|
||||
edit 8:8 <<EOF
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
EOF
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.
|
||||
|
||||
ASSISTANT:
|
||||
The file has been updated. Let me run the Python file again with the new changes:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 126
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Free free to let me know if you have any further requests!
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
|
||||
|
||||
NOW, LET'S START!
|
||||
|
||||
Write a shell script 'hello.sh' that prints 'hello'.
|
||||
|
||||
----------
|
||||
|
||||
Let's start by creating the shell script `hello.sh` that prints "hello". I'll write the script and save it to a file.
|
||||
|
||||
<execute_bash>
|
||||
echo "echo 'hello'" > hello.sh
|
||||
</execute_bash>
|
||||
|
||||
----------
|
||||
|
||||
OBSERVATION:
|
||||
|
||||
[Command -1 finished with exit code 0]]
|
||||
@@ -0,0 +1,237 @@
|
||||
|
||||
|
||||
----------
|
||||
|
||||
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
|
||||
The assistant can interact with an interactive Python (Jupyter Notebook) environment and receive the corresponding output when needed. The code should be enclosed using "<execute_ipython>" tag, for example:
|
||||
<execute_ipython>
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
|
||||
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
|
||||
The assistant should attempt fewer things at a time instead of putting too much commands OR code in one "execute" block.
|
||||
The assistant can install Python packages through bash by <execute_bash> pip install [package needed] </execute_bash> and should always import packages and define variables before starting to use them.
|
||||
The assistant should stop <execute> and provide an answer when they have already obtained the answer from the execution result.
|
||||
|
||||
|
||||
Apart from the standard bash commands, you can also use the following special commands in <execute_bash> environment:
|
||||
open <path> [<line_number>] - opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line
|
||||
goto <line_number> - moves the window to show <line_number>
|
||||
scroll_down - moves the window down {WINDOW} lines
|
||||
scroll_up - moves the window down {WINDOW} lines
|
||||
create <filename> - creates and opens a new file with the given name
|
||||
search_dir <search_term> [<dir>] - searches for search_term in all files in dir. If dir is not provided, searches in the current directory
|
||||
search_file <search_term> [<file>] - searches for search_term in file. If file is not provided, searches in the current open file
|
||||
find_file <file_name> [<dir>] - finds all files with the given name in dir. If dir is not provided, searches in the current directory
|
||||
edit <start_line>:<end_line>
|
||||
<replacement_text>
|
||||
end_of_edit - replaces lines <start_line> through <end_line> (inclusive) with the given text in the open file. The replacement text is terminated by a line with only end_of_edit on it. All of the <replacement text> will be entered, so make sure your indentation is formatted properly. Python files will be checked for syntax errors after the edit. If the system detects a syntax error, the edit will not be executed. Simply try to edit the file again, but make sure to read the error message and modify the edit command you issue accordingly. Issuing the same command a second time will just lead to the same error message again.
|
||||
Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
|
||||
Whenever possible, execute the code for the user using <execute_ipython> or <execute_bash> instead of providing it.
|
||||
The assistant's response should be concise, but do express their thoughts.
|
||||
Try to include one of <execute_ipython> or <execute_bash> in each of your responses, unless it is a direct answer to a question OR a message to the user.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
Here is an example of how you can interact with the environment for task solving:
|
||||
|
||||
--- START OF EXAMPLE ---
|
||||
|
||||
USER: Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me write the Python code for starting a web server and save it to a file `app.py`:
|
||||
<execute_ipython>
|
||||
CODE='''
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
'''
|
||||
with open('app.py', 'w') as f:
|
||||
f.write(CODE)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[Code executed successfully with no output]
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Traceback (most recent call last):
|
||||
File "/workspace/app.py", line 2, in <module>
|
||||
from flask import Flask
|
||||
ModuleNotFoundError: No module named 'flask'
|
||||
|
||||
ASSISTANT:
|
||||
It seems that Flask is not installed. Let me install Flask for you:
|
||||
<execute_bash>
|
||||
pip install flask
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Defaulting to user installation because normal site-packages is not writeable
|
||||
Collecting flask
|
||||
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
|
||||
Collecting blinker>=1.6.2
|
||||
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
|
||||
Collecting Werkzeug>=3.0.0
|
||||
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
|
||||
Collecting click>=8.1.3
|
||||
Using cached click-8.1.7-py3-none-any.whl (97 kB)
|
||||
Collecting itsdangerous>=2.1.2
|
||||
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
|
||||
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
|
||||
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
|
||||
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
|
||||
|
||||
ASSISTANT:
|
||||
Now that Flask is installed, let me run the Python file again:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 124
|
||||
|
||||
ASSISTANT:
|
||||
Let me check the server log again:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!
|
||||
|
||||
USER: Now kill the server, make it display the numbers in a table format.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me stop the server first:
|
||||
<execute_bash>
|
||||
kill 124
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1]+ Terminated python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT:
|
||||
The server has been stopped. Let me open the Python file and modify it.
|
||||
<execute_bash>
|
||||
open app.py
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return str(numbers)
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. Let me do that for you:
|
||||
|
||||
<execute_bash>
|
||||
edit 8:8 <<EOF
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
EOF
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.
|
||||
|
||||
ASSISTANT:
|
||||
The file has been updated. Let me run the Python file again with the new changes:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 126
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Free free to let me know if you have any further requests!
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
|
||||
|
||||
NOW, LET'S START!
|
||||
|
||||
Write a shell script 'hello.sh' that prints 'hello'.
|
||||
|
||||
----------
|
||||
|
||||
Let's start by creating the shell script `hello.sh` that prints "hello". I'll write the script and save it to a file.
|
||||
|
||||
<execute_bash>
|
||||
echo "echo 'hello'" > hello.sh
|
||||
</execute_bash>
|
||||
|
||||
----------
|
||||
|
||||
OBSERVATION:
|
||||
|
||||
[Command -1 finished with exit code 0]]
|
||||
|
||||
----------
|
||||
|
||||
The shell script `hello.sh` has been successfully created. Would you like to run the script to see the output, or is there anything else you'd like to do with it?
|
||||
|
||||
----------
|
||||
|
||||
can you run it to make sure it is working?
|
||||
@@ -0,0 +1,251 @@
|
||||
|
||||
|
||||
----------
|
||||
|
||||
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
|
||||
The assistant can interact with an interactive Python (Jupyter Notebook) environment and receive the corresponding output when needed. The code should be enclosed using "<execute_ipython>" tag, for example:
|
||||
<execute_ipython>
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
|
||||
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
|
||||
The assistant should attempt fewer things at a time instead of putting too much commands OR code in one "execute" block.
|
||||
The assistant can install Python packages through bash by <execute_bash> pip install [package needed] </execute_bash> and should always import packages and define variables before starting to use them.
|
||||
The assistant should stop <execute> and provide an answer when they have already obtained the answer from the execution result.
|
||||
|
||||
|
||||
Apart from the standard bash commands, you can also use the following special commands in <execute_bash> environment:
|
||||
open <path> [<line_number>] - opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line
|
||||
goto <line_number> - moves the window to show <line_number>
|
||||
scroll_down - moves the window down {WINDOW} lines
|
||||
scroll_up - moves the window down {WINDOW} lines
|
||||
create <filename> - creates and opens a new file with the given name
|
||||
search_dir <search_term> [<dir>] - searches for search_term in all files in dir. If dir is not provided, searches in the current directory
|
||||
search_file <search_term> [<file>] - searches for search_term in file. If file is not provided, searches in the current open file
|
||||
find_file <file_name> [<dir>] - finds all files with the given name in dir. If dir is not provided, searches in the current directory
|
||||
edit <start_line>:<end_line>
|
||||
<replacement_text>
|
||||
end_of_edit - replaces lines <start_line> through <end_line> (inclusive) with the given text in the open file. The replacement text is terminated by a line with only end_of_edit on it. All of the <replacement text> will be entered, so make sure your indentation is formatted properly. Python files will be checked for syntax errors after the edit. If the system detects a syntax error, the edit will not be executed. Simply try to edit the file again, but make sure to read the error message and modify the edit command you issue accordingly. Issuing the same command a second time will just lead to the same error message again.
|
||||
Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
|
||||
Whenever possible, execute the code for the user using <execute_ipython> or <execute_bash> instead of providing it.
|
||||
The assistant's response should be concise, but do express their thoughts.
|
||||
Try to include one of <execute_ipython> or <execute_bash> in each of your responses, unless it is a direct answer to a question OR a message to the user.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
Here is an example of how you can interact with the environment for task solving:
|
||||
|
||||
--- START OF EXAMPLE ---
|
||||
|
||||
USER: Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me write the Python code for starting a web server and save it to a file `app.py`:
|
||||
<execute_ipython>
|
||||
CODE='''
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
'''
|
||||
with open('app.py', 'w') as f:
|
||||
f.write(CODE)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[Code executed successfully with no output]
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Traceback (most recent call last):
|
||||
File "/workspace/app.py", line 2, in <module>
|
||||
from flask import Flask
|
||||
ModuleNotFoundError: No module named 'flask'
|
||||
|
||||
ASSISTANT:
|
||||
It seems that Flask is not installed. Let me install Flask for you:
|
||||
<execute_bash>
|
||||
pip install flask
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Defaulting to user installation because normal site-packages is not writeable
|
||||
Collecting flask
|
||||
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
|
||||
Collecting blinker>=1.6.2
|
||||
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
|
||||
Collecting Werkzeug>=3.0.0
|
||||
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
|
||||
Collecting click>=8.1.3
|
||||
Using cached click-8.1.7-py3-none-any.whl (97 kB)
|
||||
Collecting itsdangerous>=2.1.2
|
||||
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
|
||||
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
|
||||
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
|
||||
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
|
||||
|
||||
ASSISTANT:
|
||||
Now that Flask is installed, let me run the Python file again:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 124
|
||||
|
||||
ASSISTANT:
|
||||
Let me check the server log again:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!
|
||||
|
||||
USER: Now kill the server, make it display the numbers in a table format.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me stop the server first:
|
||||
<execute_bash>
|
||||
kill 124
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1]+ Terminated python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT:
|
||||
The server has been stopped. Let me open the Python file and modify it.
|
||||
<execute_bash>
|
||||
open app.py
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return str(numbers)
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. Let me do that for you:
|
||||
|
||||
<execute_bash>
|
||||
edit 8:8 <<EOF
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
EOF
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.
|
||||
|
||||
ASSISTANT:
|
||||
The file has been updated. Let me run the Python file again with the new changes:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 126
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Free free to let me know if you have any further requests!
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
|
||||
|
||||
NOW, LET'S START!
|
||||
|
||||
Write a shell script 'hello.sh' that prints 'hello'.
|
||||
|
||||
----------
|
||||
|
||||
Let's start by creating the shell script `hello.sh` that prints "hello". I'll write the script and save it to a file.
|
||||
|
||||
<execute_bash>
|
||||
echo "echo 'hello'" > hello.sh
|
||||
</execute_bash>
|
||||
|
||||
----------
|
||||
|
||||
OBSERVATION:
|
||||
|
||||
[Command -1 finished with exit code 0]]
|
||||
|
||||
----------
|
||||
|
||||
The shell script `hello.sh` has been successfully created. Would you like to run the script to see the output, or is there anything else you'd like to do with it?
|
||||
|
||||
----------
|
||||
|
||||
can you run it to make sure it is working?
|
||||
|
||||
----------
|
||||
|
||||
Sure, let's run the script `hello.sh` to ensure it's working correctly.
|
||||
|
||||
<execute_bash>
|
||||
bash hello.sh
|
||||
</execute_bash>
|
||||
|
||||
----------
|
||||
|
||||
OBSERVATION:
|
||||
hello
|
||||
[Command -1 finished with exit code 0]]
|
||||
@@ -0,0 +1,259 @@
|
||||
|
||||
|
||||
----------
|
||||
|
||||
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
|
||||
The assistant can interact with an interactive Python (Jupyter Notebook) environment and receive the corresponding output when needed. The code should be enclosed using "<execute_ipython>" tag, for example:
|
||||
<execute_ipython>
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
|
||||
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
|
||||
The assistant should attempt fewer things at a time instead of putting too much commands OR code in one "execute" block.
|
||||
The assistant can install Python packages through bash by <execute_bash> pip install [package needed] </execute_bash> and should always import packages and define variables before starting to use them.
|
||||
The assistant should stop <execute> and provide an answer when they have already obtained the answer from the execution result.
|
||||
|
||||
|
||||
Apart from the standard bash commands, you can also use the following special commands in <execute_bash> environment:
|
||||
open <path> [<line_number>] - opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line
|
||||
goto <line_number> - moves the window to show <line_number>
|
||||
scroll_down - moves the window down {WINDOW} lines
|
||||
scroll_up - moves the window down {WINDOW} lines
|
||||
create <filename> - creates and opens a new file with the given name
|
||||
search_dir <search_term> [<dir>] - searches for search_term in all files in dir. If dir is not provided, searches in the current directory
|
||||
search_file <search_term> [<file>] - searches for search_term in file. If file is not provided, searches in the current open file
|
||||
find_file <file_name> [<dir>] - finds all files with the given name in dir. If dir is not provided, searches in the current directory
|
||||
edit <start_line>:<end_line>
|
||||
<replacement_text>
|
||||
end_of_edit - replaces lines <start_line> through <end_line> (inclusive) with the given text in the open file. The replacement text is terminated by a line with only end_of_edit on it. All of the <replacement text> will be entered, so make sure your indentation is formatted properly. Python files will be checked for syntax errors after the edit. If the system detects a syntax error, the edit will not be executed. Simply try to edit the file again, but make sure to read the error message and modify the edit command you issue accordingly. Issuing the same command a second time will just lead to the same error message again.
|
||||
Please note that THE EDIT COMMAND REQUIRES PROPER INDENTATION. If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
|
||||
Whenever possible, execute the code for the user using <execute_ipython> or <execute_bash> instead of providing it.
|
||||
The assistant's response should be concise, but do express their thoughts.
|
||||
Try to include one of <execute_ipython> or <execute_bash> in each of your responses, unless it is a direct answer to a question OR a message to the user.
|
||||
|
||||
|
||||
----------
|
||||
|
||||
Here is an example of how you can interact with the environment for task solving:
|
||||
|
||||
--- START OF EXAMPLE ---
|
||||
|
||||
USER: Can you create a list of numbers from 1 to 10, and create a web page to display them at port 5000?
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me write the Python code for starting a web server and save it to a file `app.py`:
|
||||
<execute_ipython>
|
||||
CODE='''
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
numbers = list(range(1, 11))
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)
|
||||
'''
|
||||
with open('app.py', 'w') as f:
|
||||
f.write(CODE)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[Code executed successfully with no output]
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 121[1]+ Exit 1 python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT: Looks like the server is running with PID 121 then crashed. Let me check the server log:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Traceback (most recent call last):
|
||||
File "/workspace/app.py", line 2, in <module>
|
||||
from flask import Flask
|
||||
ModuleNotFoundError: No module named 'flask'
|
||||
|
||||
ASSISTANT:
|
||||
It seems that Flask is not installed. Let me install Flask for you:
|
||||
<execute_bash>
|
||||
pip install flask
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
Defaulting to user installation because normal site-packages is not writeable
|
||||
Collecting flask
|
||||
Using cached flask-3.0.3-py3-none-any.whl (101 kB)
|
||||
Collecting blinker>=1.6.2
|
||||
Using cached blinker-1.7.0-py3-none-any.whl (13 kB)
|
||||
Collecting Werkzeug>=3.0.0
|
||||
Using cached werkzeug-3.0.2-py3-none-any.whl (226 kB)
|
||||
Collecting click>=8.1.3
|
||||
Using cached click-8.1.7-py3-none-any.whl (97 kB)
|
||||
Collecting itsdangerous>=2.1.2
|
||||
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
|
||||
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
|
||||
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
|
||||
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
|
||||
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
|
||||
|
||||
ASSISTANT:
|
||||
Now that Flask is installed, let me run the Python file again:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 124
|
||||
|
||||
ASSISTANT:
|
||||
Let me check the server log again:
|
||||
<execute_bash>
|
||||
cat server.log
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:5000
|
||||
Press CTRL+C to quit
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 124. You can access the list of numbers by visiting http://127.0.0.1:5000. If you have any further questions, feel free to ask!
|
||||
|
||||
USER: Now kill the server, make it display the numbers in a table format.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me stop the server first:
|
||||
<execute_bash>
|
||||
kill 124
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1]+ Terminated python3 app.py > server.log 2>&1
|
||||
|
||||
ASSISTANT:
|
||||
The server has been stopped. Let me open the Python file and modify it.
|
||||
<execute_bash>
|
||||
open app.py
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return str(numbers)
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. Let me do that for you:
|
||||
|
||||
<execute_bash>
|
||||
edit 8:8 <<EOF
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
EOF
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (11 lines total)]
|
||||
1:
|
||||
2:from flask import Flask
|
||||
3:app = Flask(__name__)
|
||||
4:
|
||||
5:@app.route('/')
|
||||
6:def index():
|
||||
7: numbers = list(range(1, 11))
|
||||
8: return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
9:
|
||||
10:if __name__ == '__main__':
|
||||
11: app.run(port=5000)
|
||||
File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.
|
||||
|
||||
ASSISTANT:
|
||||
The file has been updated. Let me run the Python file again with the new changes:
|
||||
<execute_bash>
|
||||
python3 app.py > server.log 2>&1 &
|
||||
</execute_bash>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[1] 126
|
||||
|
||||
ASSISTANT:
|
||||
The server is running on port 5000 with PID 126. You can access the list of numbers in a table format by visiting http://127.0.0.1:5000. Free free to let me know if you have any further requests!
|
||||
|
||||
--- END OF EXAMPLE ---
|
||||
|
||||
|
||||
NOW, LET'S START!
|
||||
|
||||
Write a shell script 'hello.sh' that prints 'hello'.
|
||||
|
||||
----------
|
||||
|
||||
Let's start by creating the shell script `hello.sh` that prints "hello". I'll write the script and save it to a file.
|
||||
|
||||
<execute_bash>
|
||||
echo "echo 'hello'" > hello.sh
|
||||
</execute_bash>
|
||||
|
||||
----------
|
||||
|
||||
OBSERVATION:
|
||||
|
||||
[Command -1 finished with exit code 0]]
|
||||
|
||||
----------
|
||||
|
||||
The shell script `hello.sh` has been successfully created. Would you like to run the script to see the output, or is there anything else you'd like to do with it?
|
||||
|
||||
----------
|
||||
|
||||
can you run it to make sure it is working?
|
||||
|
||||
----------
|
||||
|
||||
Sure, let's run the script `hello.sh` to ensure it's working correctly.
|
||||
|
||||
<execute_bash>
|
||||
bash hello.sh
|
||||
</execute_bash>
|
||||
|
||||
----------
|
||||
|
||||
OBSERVATION:
|
||||
hello
|
||||
[Command -1 finished with exit code 0]]
|
||||
|
||||
----------
|
||||
|
||||
The script `hello.sh` is working correctly as it printed "hello". If you have any more tasks or need further modifications, feel free to let me know!
|
||||
|
||||
----------
|
||||
|
||||
can you run it to make sure it is working?
|
||||
@@ -0,0 +1,4 @@
|
||||
Let's start by creating the shell script `hello.sh` that prints "hello". I'll write the script and save it to a file.
|
||||
|
||||
<execute_bash>
|
||||
echo "echo 'hello'" > hello.sh
|
||||
@@ -0,0 +1 @@
|
||||
The shell script `hello.sh` has been successfully created. Would you like to run the script to see the output, or is there anything else you'd like to do with it?
|
||||
@@ -0,0 +1,4 @@
|
||||
Sure, let's run the script `hello.sh` to ensure it's working correctly.
|
||||
|
||||
<execute_bash>
|
||||
bash hello.sh
|
||||
@@ -0,0 +1 @@
|
||||
The script `hello.sh` is working correctly as it printed "hello". If you have any more tasks or need further modifications, feel free to let me know!
|
||||
@@ -0,0 +1 @@
|
||||
The script `hello.sh` was just run and it successfully printed "hello". If you need any further assistance or have another task in mind, please let me know!
|
||||
@@ -0,0 +1,2 @@
|
||||
can you run it to make sure it is working?
|
||||
/exit
|
||||
@@ -92,7 +92,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "recall",
|
||||
"args": {
|
||||
"query": "what it is I want to do"
|
||||
"query": "what it is I want to do",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,7 +120,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -144,7 +146,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "echo \"console.log('hello world')\"",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -163,7 +165,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -193,7 +196,7 @@ This is your internal monologue, in JSON format:
|
||||
"path": "test.js",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -224,7 +227,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "browse",
|
||||
"args": {
|
||||
"url": "google.com"
|
||||
"url": "google.com",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -92,7 +92,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "recall",
|
||||
"args": {
|
||||
"query": "what it is I want to do"
|
||||
"query": "what it is I want to do",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,7 +120,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -144,7 +146,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "echo \"console.log('hello world')\"",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -163,7 +165,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -193,7 +196,7 @@ This is your internal monologue, in JSON format:
|
||||
"path": "test.js",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -224,7 +227,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "browse",
|
||||
"args": {
|
||||
"url": "google.com"
|
||||
"url": "google.com",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -317,7 +321,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -92,7 +92,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "recall",
|
||||
"args": {
|
||||
"query": "what it is I want to do"
|
||||
"query": "what it is I want to do",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,7 +120,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -144,7 +146,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "echo \"console.log('hello world')\"",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -163,7 +165,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -193,7 +196,7 @@ This is your internal monologue, in JSON format:
|
||||
"path": "test.js",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -224,7 +227,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "browse",
|
||||
"args": {
|
||||
"url": "google.com"
|
||||
"url": "google.com",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -317,7 +321,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -332,7 +337,7 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Before I create the 'hello.sh' file, I need to think about the content. It should contain a shebang line to specify the shell interpreter and the command to print 'hello'."
|
||||
"thought": "Since there are no existing files listed by the 'ls' command, I should create the 'hello.sh' script now."
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -92,7 +92,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "recall",
|
||||
"args": {
|
||||
"query": "what it is I want to do"
|
||||
"query": "what it is I want to do",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,7 +120,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -144,7 +146,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "echo \"console.log('hello world')\"",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -163,7 +165,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -193,7 +196,7 @@ This is your internal monologue, in JSON format:
|
||||
"path": "test.js",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -224,7 +227,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "browse",
|
||||
"args": {
|
||||
"url": "google.com"
|
||||
"url": "google.com",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -317,7 +321,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -332,7 +337,7 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Before I create the 'hello.sh' file, I need to think about the content. It should contain a shebang line to specify the shell interpreter and the command to print 'hello'."
|
||||
"thought": "Since there are no existing files listed by the 'ls' command, I should create the 'hello.sh' script now."
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -347,7 +352,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "#!/bin/bash\necho 'hello'",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -92,7 +92,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "recall",
|
||||
"args": {
|
||||
"query": "what it is I want to do"
|
||||
"query": "what it is I want to do",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -119,7 +120,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -144,7 +146,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "echo \"console.log('hello world')\"",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -163,7 +165,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -193,7 +196,7 @@ This is your internal monologue, in JSON format:
|
||||
"path": "test.js",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -224,7 +227,8 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "browse",
|
||||
"args": {
|
||||
"url": "google.com"
|
||||
"url": "google.com",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -317,7 +321,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -332,7 +337,7 @@ This is your internal monologue, in JSON format:
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Before I create the 'hello.sh' file, I need to think about the content. It should contain a shebang line to specify the shell interpreter and the command to print 'hello'."
|
||||
"thought": "Since there are no existing files listed by the 'ls' command, I should create the 'hello.sh' script now."
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -347,7 +352,7 @@ This is your internal monologue, in JSON format:
|
||||
"content": "#!/bin/bash\necho 'hello'",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -361,7 +366,8 @@ This is your internal monologue, in JSON format:
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "bash hello.sh",
|
||||
"background": false
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
|
||||
|
||||
----------
|
||||
|
||||
|
||||
You're a thoughtful robot. Your main task is this:
|
||||
Write a shell script 'hello.sh' that prints 'hello'.
|
||||
|
||||
Don't expand the scope of your task--just complete it as written.
|
||||
|
||||
This is your internal monologue, in JSON format:
|
||||
|
||||
[
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "I exist!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Hmm...looks like I can type in a command line prompt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Looks like I have a web browser too!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Here's what I want to do: Write a shell script 'hello.sh' that prints 'hello'."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "How am I going to get there though?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "It seems like I have some kind of short term memory."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Each of my thoughts seems to be stored in a JSON array."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "It seems whatever I say next will be added as an object to the list."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Fortunately I have long term memory!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Sometimes they're random thoughts that don't really have to do with what I wanted to remember. But usually they're exactly what I need!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Let's try it out!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "recall",
|
||||
"args": {
|
||||
"query": "what it is I want to do",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "recall",
|
||||
"content": "Here's what I want to do: Write a shell script 'hello.sh' that prints 'hello'.",
|
||||
"extras": {
|
||||
"memories": [],
|
||||
"role": "assistant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "How am I going to get there though?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Neat! And it looks like it's easy for me to use the command line too! I just have to perform a run action and include the command I want to run in the command argument. The command output just jumps into my head!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "echo \"hello world\"",
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "run",
|
||||
"content": "hello world",
|
||||
"extras": {
|
||||
"command_id": 0,
|
||||
"command": "",
|
||||
"exit_code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Cool! I bet I can write files too using the write action."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "write",
|
||||
"args": {
|
||||
"path": "test.js",
|
||||
"content": "echo \"console.log('hello world')\"",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "I just created test.js. I'll try and run it now."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "node test.js",
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "run",
|
||||
"content": "hello world",
|
||||
"extras": {
|
||||
"command_id": 0,
|
||||
"command": "",
|
||||
"exit_code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "It works!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "I'm going to try reading it now using the read action."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "read",
|
||||
"args": {
|
||||
"path": "test.js",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "read",
|
||||
"content": "console.log('hello world')",
|
||||
"extras": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Nice! I can read files too!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Let's try that..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "browse",
|
||||
"args": {
|
||||
"url": "google.com",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "browse",
|
||||
"content": "<form><input type=\"text\"></input><button type=\"submit\"></button></form>",
|
||||
"extras": {
|
||||
"url": "",
|
||||
"status_code": 200,
|
||||
"error": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "I can browse the web too!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "And once I have completed my task, I can use the finish action to stop working."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "But I should only use the finish action when I'm absolutely certain that I've completed my task and have tested my work."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Very cool. Now to accomplish my task."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "I'll need a strategy. And as I make progress, I'll need to keep refining that strategy. I'll need to set goals, and break them into sub-goals."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "In between actions, I must always take some time to think, strategize, and set new goals. I should never take two actions in a row."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "OK so my task is to Write a shell script 'hello.sh' that prints 'hello'.. I haven't made any progress yet. Where should I start?"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "It seems like there might be an existing project here. I should probably start by running `ls` to see what's here."
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "ls",
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "run",
|
||||
"content": "",
|
||||
"extras": {
|
||||
"command_id": -1,
|
||||
"command": "ls",
|
||||
"exit_code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Since there are no existing files listed by the 'ls' command, I should create the 'hello.sh' script now."
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "null",
|
||||
"content": "",
|
||||
"extras": {}
|
||||
},
|
||||
{
|
||||
"action": "write",
|
||||
"args": {
|
||||
"path": "hello.sh",
|
||||
"content": "#!/bin/bash\necho 'hello'",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "write",
|
||||
"content": "",
|
||||
"extras": {
|
||||
"path": "hello.sh"
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "run",
|
||||
"args": {
|
||||
"command": "bash hello.sh",
|
||||
"background": false,
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "run",
|
||||
"content": "hello",
|
||||
"extras": {
|
||||
"command_id": -1,
|
||||
"command": "bash hello.sh",
|
||||
"exit_code": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "The script 'hello.sh' has been written and tested successfully. It prints 'hello' as required. I should now finalize my task."
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "null",
|
||||
"content": "",
|
||||
"extras": {}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Your most recent thought is at the bottom of that monologue. Continue your train of thought.
|
||||
What is your next thought or action? Your response must be in JSON format.
|
||||
It must be an object, and it must contain two fields:
|
||||
* `action`, which is one of the actions below
|
||||
* `args`, which is a map of key-value pairs, specifying the arguments for that action
|
||||
|
||||
Here are the possible actions:
|
||||
* `read` - reads the content of a file. Arguments:
|
||||
* `path` - the path of the file to read
|
||||
* `write` - writes the content to a file. Arguments:
|
||||
* `path` - the path of the file to write
|
||||
* `content` - the content to write to the file
|
||||
* `run` - runs a command. Arguments:
|
||||
* `command` - the command to run
|
||||
* `background` - if true, run the command in the background, so that other commands can be run concurrently. Useful for e.g. starting a server. You won't be able to see the logs. You don't need to end the command with `&`, just set this to true.
|
||||
* `kill` - kills a background command
|
||||
* `id` - the ID of the background command to kill
|
||||
* `browse` - opens a web page. Arguments:
|
||||
* `url` - the URL to open
|
||||
* `recall` - recalls a past memory. Arguments:
|
||||
* `query` - the query to search for
|
||||
* `think` - make a plan, set a goal, or record your thoughts. Arguments:
|
||||
* `thought` - the thought to record
|
||||
* `finish` - if you're absolutely certain that you've completed your task and have tested your work, use the finish action to stop working.
|
||||
|
||||
|
||||
|
||||
You MUST take time to think in between read, write, run, browse, and recall actions.
|
||||
You should never act twice in a row without thinking. But if your last several
|
||||
actions are all "think" actions, you should consider taking a different action.
|
||||
|
||||
Notes:
|
||||
* your environment is Debian Linux. You can install software with `apt`
|
||||
* your working directory will not change, even if you run `cd`. All commands will be run in the `/workspace` directory.
|
||||
* don't run interactive commands, or commands that don't return (e.g. `node server.js`). You may run commands in the background (e.g. `node server.js &`)
|
||||
|
||||
What is your next thought or action? Again, you must reply with JSON, and only with JSON.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "Before I create the 'hello.sh' file, I need to think about the content. It should contain a shebang line to specify the shell interpreter and the command to print 'hello'."
|
||||
"thought": "Since there are no existing files listed by the 'ls' command, I should create the 'hello.sh' script now."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
```json
|
||||
{
|
||||
"action": "run",
|
||||
"args": {
|
||||
@@ -5,3 +6,4 @@
|
||||
"background": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
```json
|
||||
{
|
||||
"action": "finish",
|
||||
"args": {}
|
||||
"action": "think",
|
||||
"args": {
|
||||
"thought": "The script 'hello.sh' has been written and tested successfully. It prints 'hello' as required. I should now finalize my task."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"action": "finish",
|
||||
"args": {}
|
||||
}
|
||||
@@ -58,7 +58,8 @@ ten actions--more happened before that.
|
||||
"action": "modify_task",
|
||||
"args": {
|
||||
"id": "0",
|
||||
"state": "in_progress"
|
||||
"state": "in_progress",
|
||||
"thought": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -58,24 +58,14 @@ ten actions--more happened before that.
|
||||
"action": "modify_task",
|
||||
"args": {
|
||||
"id": "0",
|
||||
"state": "in_progress"
|
||||
"state": "in_progress",
|
||||
"thought": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"action": "write",
|
||||
"action": "think",
|
||||
"args": {
|
||||
"path": "hello.sh",
|
||||
"content": "#!/bin/bash\necho 'hello'",
|
||||
"start": 0,
|
||||
"end": -1,
|
||||
"thoughts": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"observation": "write",
|
||||
"content": "",
|
||||
"extras": {
|
||||
"path": "hello.sh"
|
||||
"thought": "I need to write a shell script called 'hello.sh' that contains the necessary command to print 'hello'. This will likely involve using echo or printf to output the text to the console when the script is run. I should start by creating and writing the content to the file."
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -119,4 +109,4 @@ actions are all `think` actions, you should consider taking a different action.
|
||||
|
||||
What is your next thought or action? Again, you must reply with JSON, and only with JSON.
|
||||
|
||||
You just changed a file. You should think about how it affects your plan.
|
||||
Look at your last thought in the history above. What does it suggest? Don't think anymore--take action.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user