From 4915168da297108b8822b75ba593ef382ea3f0a2 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 9 Aug 2024 05:06:26 +0800 Subject: [PATCH 01/46] fix: copy over pyproject and poetry lock for App docker (#3299) * also copy over pyproject and poetry lock * add missing readme --- containers/app/Dockerfile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/containers/app/Dockerfile b/containers/app/Dockerfile index b25e6b3b5941..584750e12d32 100644 --- a/containers/app/Dockerfile +++ b/containers/app/Dockerfile @@ -46,8 +46,10 @@ RUN mkdir -p $WORKSPACE_BASE RUN apt-get update -y \ && apt-get install -y curl ssh sudo -RUN sed -i 's/^UID_MIN.*/UID_MIN 499/' /etc/login.defs # Default is 1000, but OSX is often 501 -RUN sed -i 's/^UID_MAX.*/UID_MAX 1000000/' /etc/login.defs # Default is 60000, but we've seen up to 200000 +# Default is 1000, but OSX is often 501 +RUN sed -i 's/^UID_MIN.*/UID_MIN 499/' /etc/login.defs +# Default is 60000, but we've seen up to 200000 +RUN sed -i 's/^UID_MAX.*/UID_MAX 1000000/' /etc/login.defs RUN groupadd app RUN useradd -l -m -u $OPENDEVIN_USER_ID -s /bin/bash opendevin && \ @@ -68,6 +70,9 @@ RUN playwright install --with-deps chromium COPY --chown=opendevin:app --chmod=770 ./opendevin ./opendevin COPY --chown=opendevin:app --chmod=777 ./opendevin/runtime/plugins ./opendevin/runtime/plugins COPY --chown=opendevin:app --chmod=770 ./agenthub ./agenthub +COPY --chown=opendevin:app --chmod=770 ./pyproject.toml ./pyproject.toml +COPY --chown=opendevin:app --chmod=770 ./poetry.lock ./poetry.lock +COPY --chown=opendevin:app --chmod=770 ./README.md ./README.md RUN python opendevin/core/download.py # No-op to download assets RUN chown -R opendevin:app /app/logs && chmod -R 770 /app/logs # This gets created by the download.py script From 040b9cb75c329ab1cba9e5b7b13eeec887e7c316 Mon Sep 17 00:00:00 2001 From: tofarr Date: Thu, 8 Aug 2024 16:06:58 -0600 Subject: [PATCH 02/46] Chore Readme updates (#3302) * Readme updates Added explicit installation instructions to server and frontend README * Documentation update * WIP * WIP --------- Co-authored-by: Tim O'Farrell --- frontend/README.md | 6 ++++++ opendevin/server/README.md | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/frontend/README.md b/frontend/README.md index 01530e63e222..4e6b58c79f4e 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,5 +1,11 @@ # Getting Started with the OpenDevin Frontend +The frontend code can be run against the docker image defined in the [Main README](../README.md) as a backend + +## Prerequisites + +A recent version of NodeJS / NPM (`brew install node`) + ## Available Scripts In the project directory, you can run: diff --git a/opendevin/server/README.md b/opendevin/server/README.md index fa4d6249e662..991f0fe7c03b 100644 --- a/opendevin/server/README.md +++ b/opendevin/server/README.md @@ -2,9 +2,23 @@ This is a WebSocket server that executes tasks using an agent. +## Recommended Prerequisites + +- [Initialize the frontend code](../../frontend/README.md) +- Install Python 3.12 (`brew install python` for those using homebrew) +- Install pipx: (`brew install pipx` followed by `pipx ensurepath`) +- Install poetry: (`pipx install poetry`) + ## Install -Follow the instructions in the base README.md to install dependencies and set up. +First build a distribution of the frontend code (From the project root directory): +``` +cd frontend +npm install +npm run build +cd .. +``` +Next run `poetry shell` (So you don't have to repeat `poetry run`) ## Start the Server From a5195b0e6501580a7365f3f3668af7fd04c3f902 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 9 Aug 2024 06:15:40 +0800 Subject: [PATCH 03/46] chore: clean up sandbox and ssh related configs (#3301) * clean up sandbox and ssh related stuff * remove ssh hostname * remove ssh hostname * remove ssh password * update config * fix typo that breaks the test --- .github/workflows/review-pr.yml | 1 - .github/workflows/solve-issue.yml | 1 - agenthub/micro/commit_writer/README.md | 2 +- config.template.toml | 16 +------- containers/app/Dockerfile | 1 - .../current/usage/intro.mdx | 2 - .../current/usage/intro.mdx | 2 - docs/modules/usage/openshift-example.md | 2 - opendevin/core/config.py | 18 -------- opendevin/core/logger.py | 1 - opendevin/core/schema/config.py | 2 - tests/integration/regenerate.sh | 5 --- tests/integration/test_agent.py | 26 ++---------- tests/unit/test_config.py | 41 +------------------ 14 files changed, 6 insertions(+), 114 deletions(-) diff --git a/.github/workflows/review-pr.yml b/.github/workflows/review-pr.yml index ac86778c648b..6d7771e5701f 100644 --- a/.github/workflows/review-pr.yml +++ b/.github/workflows/review-pr.yml @@ -53,7 +53,6 @@ jobs: env: LLM_API_KEY: ${{ secrets.LLM_API_KEY }} LLM_MODEL: ${{ vars.LLM_MODEL }} - SANDBOX_BOX_TYPE: ssh run: | # Append path to launch poetry export PATH="/github/home/.local/bin:$PATH" diff --git a/.github/workflows/solve-issue.yml b/.github/workflows/solve-issue.yml index 0e3a6efcea73..8e075761ab2a 100644 --- a/.github/workflows/solve-issue.yml +++ b/.github/workflows/solve-issue.yml @@ -45,7 +45,6 @@ jobs: ISSUE_BODY: ${{ github.event.issue.body }} LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - SANDBOX_BOX_TYPE: ssh run: | # Append path to launch poetry export PATH="/github/home/.local/bin:$PATH" diff --git a/agenthub/micro/commit_writer/README.md b/agenthub/micro/commit_writer/README.md index 927bc67da286..f82484b91d93 100644 --- a/agenthub/micro/commit_writer/README.md +++ b/agenthub/micro/commit_writer/README.md @@ -3,7 +3,7 @@ CommitWriterAgent can help write git commit message. Example: ```bash -WORKSPACE_MOUNT_PATH="`PWD`" SANDBOX_BOX_TYPE="ssh" \ +WORKSPACE_MOUNT_PATH="`PWD`" \ poetry run python opendevin/core/main.py -t "dummy task" -c CommitWriterAgent -d ./ ``` diff --git a/config.template.toml b/config.template.toml index b842cb4ee79c..1307852d3d1e 100644 --- a/config.template.toml +++ b/config.template.toml @@ -55,22 +55,11 @@ workspace_base = "./workspace" # Path to rewrite the workspace mount path to #workspace_mount_rewrite = "" - # Run as devin #run_as_devin = true # Runtime environment -#runtime = "server" - -# SSH hostname for the sandbox -#ssh_hostname = "localhost" - -# SSH password for the sandbox -#ssh_password = "" - -# SSH port for the sandbox -#ssh_port = 63710 - +#runtime = "eventstream" # Name of the default agent #default_agent = "CodeActAgent" @@ -181,9 +170,6 @@ llm_config = 'gpt3' # Sandbox timeout in seconds #timeout = 120 -# Sandbox type (ssh, e2b, local) -#box_type = "ssh" - # Sandbox user ID #user_id = 1000 diff --git a/containers/app/Dockerfile b/containers/app/Dockerfile index 584750e12d32..efd36fd0363c 100644 --- a/containers/app/Dockerfile +++ b/containers/app/Dockerfile @@ -38,7 +38,6 @@ ENV RUN_AS_DEVIN=true # A random number--we need this to be different from the user's UID on the host machine ENV OPENDEVIN_USER_ID=42420 ENV USE_HOST_NETWORK=false -ENV SSH_HOSTNAME=host.docker.internal ENV WORKSPACE_BASE=/opt/workspace_base ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION RUN mkdir -p $WORKSPACE_BASE diff --git a/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/intro.mdx b/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/intro.mdx index d8b6d858eda4..ed66081c48c4 100644 --- a/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/intro.mdx +++ b/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/intro.mdx @@ -72,8 +72,6 @@ WORKSPACE_BASE=$(pwd)/workspace docker run -it \ --pull=always \ -e SANDBOX_USER_ID=$(id -u) \ - -e PERSIST_SANDBOX="true" \ - -e SSH_PASSWORD="make something up here" \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -v $WORKSPACE_BASE:/opt/workspace_base \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/intro.mdx b/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/intro.mdx index 8150b3d5917d..e8a172e5d555 100644 --- a/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/intro.mdx +++ b/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/intro.mdx @@ -72,8 +72,6 @@ WORKSPACE_BASE=$(pwd)/workspace docker run -it \ --pull=always \ -e SANDBOX_USER_ID=$(id -u) \ - -e PERSIST_SANDBOX="true" \ - -e SSH_PASSWORD="make something up here" \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -v $WORKSPACE_BASE:/opt/workspace_base \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/docs/modules/usage/openshift-example.md b/docs/modules/usage/openshift-example.md index 360a18624cd4..a898690ffabc 100644 --- a/docs/modules/usage/openshift-example.md +++ b/docs/modules/usage/openshift-example.md @@ -158,8 +158,6 @@ spec: env: - name: SANDBOX_USER_ID value: "1000" - - name: SANDBOX_BOX_TYPE - value: 'local' - name: WORKSPACE_MOUNT_PATH value: "/opt/workspace_base" volumeMounts: diff --git a/opendevin/core/config.py b/opendevin/core/config.py index 516843c473fa..6d5d55637f00 100644 --- a/opendevin/core/config.py +++ b/opendevin/core/config.py @@ -145,7 +145,6 @@ class SandboxConfig(metaclass=Singleton): """Configuration for the sandbox. Attributes: - box_type: The type of sandbox to use. Options are: ssh, e2b, local. container_image: The container image to use for the sandbox. user_id: The user ID for the sandbox. timeout: The timeout for the sandbox. @@ -165,7 +164,6 @@ class SandboxConfig(metaclass=Singleton): Default is None for general purpose browsing. Check evaluation/miniwob and evaluation/webarena for examples. """ - box_type: str = 'ssh' container_image: str = ( 'ubuntu:22.04' # default to ubuntu:22.04 for eventstream runtime ) @@ -226,7 +224,6 @@ class AppConfig(metaclass=Singleton): max_iterations: The maximum number of iterations. max_budget_per_task: The maximum budget allowed per task, beyond which the agent will stop. e2b_api_key: The E2B API key. - ssh_hostname: The SSH hostname. disable_color: Whether to disable color. For terminals that don't support color. debug: Whether to enable debugging. enable_cli_session: Whether to enable saving and restoring the session when run from CLI. @@ -255,10 +252,7 @@ class AppConfig(metaclass=Singleton): max_iterations: int = _MAX_ITERATIONS max_budget_per_task: float | None = None e2b_api_key: str = '' - ssh_hostname: str = 'localhost' disable_color: bool = False - ssh_port: int = 63710 - ssh_password: str | None = None jwt_secret: str = uuid.uuid4().hex debug: bool = False enable_cli_session: bool = False @@ -330,7 +324,6 @@ def __str__(self): 'e2b_api_key', 'github_token', 'jwt_secret', - 'ssh_password', ]: attr_value = '******' if attr_value else None @@ -426,11 +419,6 @@ def set_attr_from_env(sub_config: Any, prefix=''): f'Error setting env var {env_var_name}={value}: check that the value is of the right type' ) - if 'SANDBOX_TYPE' in env_or_toml_dict: - logger.opendevin_logger.error( - 'SANDBOX_TYPE is deprecated. Please use SANDBOX_BOX_TYPE instead.' - ) - env_or_toml_dict['SANDBOX_BOX_TYPE'] = env_or_toml_dict.pop('SANDBOX_TYPE') # Start processing from the root of the config object set_attr_from_env(cfg) @@ -520,8 +508,6 @@ def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml'): keys_to_migrate = [key for key in core_config if key.startswith('sandbox_')] for key in keys_to_migrate: new_key = key.replace('sandbox_', '') - if new_key == 'type': - new_key = 'box_type' if new_key in sandbox_config.__annotations__: # read the key in sandbox and remove it from core setattr(sandbox_config, new_key, core_config.pop(key)) @@ -548,10 +534,6 @@ def finalize_config(cfg: AppConfig): cfg.workspace_mount_path = os.path.abspath(cfg.workspace_base) cfg.workspace_base = os.path.abspath(cfg.workspace_base) - # In local there is no sandbox, the workspace will have the same pwd as the host - if cfg.sandbox.box_type == 'local' and cfg.workspace_mount_path is not None: - cfg.workspace_mount_path_in_sandbox = cfg.workspace_mount_path - if cfg.workspace_mount_rewrite: # and not config.workspace_mount_path: # TODO why do we need to check if workspace_mount_path is None? base = cfg.workspace_base or os.getcwd() diff --git a/opendevin/core/logger.py b/opendevin/core/logger.py index a39530befc91..f765cdff9958 100644 --- a/opendevin/core/logger.py +++ b/opendevin/core/logger.py @@ -87,7 +87,6 @@ def filter(self, record): 'e2b_api_key', 'github_token', 'jwt_secret', - 'ssh_password', ] # add env var names diff --git a/opendevin/core/schema/config.py b/opendevin/core/schema/config.py index b10ebe7ad069..f7c4f25b55d3 100644 --- a/opendevin/core/schema/config.py +++ b/opendevin/core/schema/config.py @@ -36,11 +36,9 @@ class ConfigType(str, Enum): MAX_ITERATIONS = 'MAX_ITERATIONS' AGENT = 'AGENT' E2B_API_KEY = 'E2B_API_KEY' - SANDBOX_BOX_TYPE = 'SANDBOX_BOX_TYPE' SANDBOX_USER_ID = 'SANDBOX_USER_ID' SANDBOX_TIMEOUT = 'SANDBOX_TIMEOUT' USE_HOST_NETWORK = 'USE_HOST_NETWORK' - SSH_HOSTNAME = 'SSH_HOSTNAME' DISABLE_COLOR = 'DISABLE_COLOR' DEBUG = 'DEBUG' FILE_UPLOADS_MAX_FILE_SIZE_MB = 'FILE_UPLOADS_MAX_FILE_SIZE_MB' diff --git a/tests/integration/regenerate.sh b/tests/integration/regenerate.sh index 91e278883890..016be2ca89fa 100755 --- a/tests/integration/regenerate.sh +++ b/tests/integration/regenerate.sh @@ -56,7 +56,6 @@ cd "$PROJECT_ROOT" || exit 1 mkdir -p $WORKSPACE_BASE # use environmental variable if exists, otherwise use "ssh" -SANDBOX_BOX_TYPE="${SANDBOX_TYPE:-ssh}" TEST_RUNTIME="${TEST_RUNTIME:-eventstream}" # can be server or eventstream # TODO: set this as default after ServerRuntime is deprecated if [ "$TEST_RUNTIME" == "eventstream" ] && [ -z "$SANDBOX_CONTAINER_IMAGE" ]; then @@ -64,7 +63,6 @@ if [ "$TEST_RUNTIME" == "eventstream" ] && [ -z "$SANDBOX_CONTAINER_IMAGE" ]; th fi MAX_ITERATIONS=15 -echo "SANDBOX_BOX_TYPE: $SANDBOX_BOX_TYPE" echo "TEST_RUNTIME: $TEST_RUNTIME" agents=( @@ -112,7 +110,6 @@ run_test() { env SCRIPT_DIR="$SCRIPT_DIR" \ PROJECT_ROOT="$PROJECT_ROOT" \ - SANDBOX_BOX_TYPE="$SANDBOX_BOX_TYPE" \ WORKSPACE_BASE=$WORKSPACE_BASE \ WORKSPACE_MOUNT_PATH=$WORKSPACE_MOUNT_PATH \ MAX_ITERATIONS=$MAX_ITERATIONS \ @@ -183,7 +180,6 @@ regenerate_without_llm() { set -x env SCRIPT_DIR="$SCRIPT_DIR" \ PROJECT_ROOT="$PROJECT_ROOT" \ - SANDBOX_BOX_TYPE="$SANDBOX_BOX_TYPE" \ WORKSPACE_BASE=$WORKSPACE_BASE \ WORKSPACE_MOUNT_PATH=$WORKSPACE_MOUNT_PATH \ MAX_ITERATIONS=$MAX_ITERATIONS \ @@ -213,7 +209,6 @@ regenerate_with_llm() { env SCRIPT_DIR="$SCRIPT_DIR" \ PROJECT_ROOT="$PROJECT_ROOT" \ DEBUG=true \ - SANDBOX_BOX_TYPE="$SANDBOX_BOX_TYPE" \ WORKSPACE_BASE=$WORKSPACE_BASE \ WORKSPACE_MOUNT_PATH=$WORKSPACE_MOUNT_PATH \ DEFAULT_AGENT=$agent \ diff --git a/tests/integration/test_agent.py b/tests/integration/test_agent.py index 433c527df4ab..b9f61175c85e 100644 --- a/tests/integration/test_agent.py +++ b/tests/integration/test_agent.py @@ -29,7 +29,6 @@ workspace_base=os.getenv('WORKSPACE_BASE'), workspace_mount_path=os.getenv('WORKSPACE_MOUNT_PATH'), sandbox=SandboxConfig( - box_type=os.getenv('SANDBOX_BOX_TYPE', 'ssh'), use_host_network=True, ), ) @@ -80,8 +79,7 @@ def validate_final_state(final_state: State | None, test_name: str): ( os.getenv('DEFAULT_AGENT') == 'CodeActAgent' or os.getenv('DEFAULT_AGENT') == 'CodeActSWEAgent' - ) - and os.getenv('SANDBOX_BOX_TYPE', '').lower() != 'ssh', + ), reason='CodeActAgent/CodeActSWEAgent only supports ssh sandbox which is stateful', ) @pytest.mark.skipif( @@ -118,18 +116,13 @@ def test_write_simple_script(current_test_name: str) -> None: ( os.getenv('DEFAULT_AGENT') == 'CodeActAgent' or os.getenv('DEFAULT_AGENT') == 'CodeActSWEAgent' - ) - and os.getenv('SANDBOX_BOX_TYPE', '').lower() != 'ssh', + ), reason='CodeActAgent/CodeActSWEAgent only supports ssh sandbox which is stateful', ) @pytest.mark.skipif( os.getenv('DEFAULT_AGENT') == 'PlannerAgent', reason='We only keep basic tests for PlannerAgent', ) -@pytest.mark.skipif( - os.getenv('SANDBOX_BOX_TYPE') == 'local', - reason='local sandbox shows environment-dependent absolute path for pwd command', -) def test_edits(current_test_name: str): # Copy workspace artifacts to workspace_base location source_dir = os.path.join(os.path.dirname(__file__), 'workspace/test_edits/') @@ -163,10 +156,6 @@ def test_edits(current_test_name: str): and os.getenv('DEFAULT_AGENT') != 'CodeActSWEAgent', reason='currently only CodeActAgent and CodeActSWEAgent have IPython (Jupyter) execution by default', ) -@pytest.mark.skipif( - os.getenv('SANDBOX_BOX_TYPE') != 'ssh', - reason='Currently, only ssh sandbox supports stateful tasks', -) def test_ipython(current_test_name: str): # Execute the task task = "Use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'. Do not ask me for confirmation at any point." @@ -191,10 +180,6 @@ def test_ipython(current_test_name: str): os.getenv('DEFAULT_AGENT') != 'ManagerAgent', reason='Currently, only ManagerAgent supports task rejection', ) -@pytest.mark.skipif( - os.getenv('SANDBOX_BOX_TYPE') == 'local', - reason='FIXME: local sandbox does not capture stderr', -) def test_simple_task_rejection(current_test_name: str): # Give an impossible task to do: cannot write a commit message because # the workspace is not a git repo @@ -211,10 +196,6 @@ def test_simple_task_rejection(current_test_name: str): and os.getenv('DEFAULT_AGENT') != 'CodeActSWEAgent', reason='currently only CodeActAgent and CodeActSWEAgent have IPython (Jupyter) execution by default', ) -@pytest.mark.skipif( - os.getenv('SANDBOX_BOX_TYPE') != 'ssh', - reason='Currently, only ssh sandbox supports stateful tasks', -) def test_ipython_module(current_test_name: str): # Execute the task task = "Install and import pymsgbox==1.0.9 and print it's version in /workspace/test.txt. Do not ask me for confirmation at any point." @@ -245,8 +226,7 @@ def test_ipython_module(current_test_name: str): ( os.getenv('DEFAULT_AGENT') == 'CodeActAgent' or os.getenv('DEFAULT_AGENT') == 'CodeActSWEAgent' - ) - and os.getenv('SANDBOX_BOX_TYPE', '').lower() != 'ssh', + ), reason='CodeActAgent/CodeActSWEAgent only supports ssh sandbox which is stateful', ) def test_browse_internet(http_server, current_test_name: str): diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index d40e6bf163dd..acff8be7cc5e 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -52,7 +52,6 @@ def test_compat_env_to_config(monkeypatch, setup_env): monkeypatch.setenv('AGENT_MEMORY_MAX_THREADS', '4') monkeypatch.setenv('AGENT_MEMORY_ENABLED', 'True') monkeypatch.setenv('DEFAULT_AGENT', 'CodeActAgent') - monkeypatch.setenv('SANDBOX_TYPE', 'local') monkeypatch.setenv('SANDBOX_TIMEOUT', '10') config = AppConfig() @@ -67,7 +66,6 @@ def test_compat_env_to_config(monkeypatch, setup_env): assert config.get_agent_config().memory_max_threads == 4 assert config.get_agent_config().memory_enabled is True assert config.default_agent == 'CodeActAgent' - assert config.sandbox.box_type == 'local' assert config.sandbox.timeout == 10 @@ -120,7 +118,6 @@ def test_load_from_new_style_toml(default_config, temp_toml_file): [core] workspace_base = "/opt/files2/workspace" default_agent = "TestAgent" -sandbox_type = "local" """ ) @@ -150,12 +147,8 @@ def test_load_from_new_style_toml(default_config, temp_toml_file): assert default_config.get_agent_config('BrowsingAgent').memory_enabled is False assert default_config.workspace_base == '/opt/files2/workspace' - assert default_config.sandbox.box_type == 'local' assert default_config.sandbox.timeout == 1 - # default config doesn't have a field sandbox_type - assert not hasattr(default_config, 'sandbox_type') - # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set assert default_config.workspace_mount_path is UndefinedString.UNDEFINED assert ( @@ -170,7 +163,7 @@ def test_load_from_new_style_toml(default_config, temp_toml_file): assert default_config.workspace_mount_path == '/opt/files2/workspace' -def test_compat_load_sandbox_from_toml(default_config, temp_toml_file): +def test_compat_load_sandbox_from_toml(default_config: AppConfig, temp_toml_file: str): # test loading configuration from a new-style TOML file # uses a toml file with sandbox_vars instead of a sandbox section with open(temp_toml_file, 'w', encoding='utf-8') as toml_file: @@ -184,7 +177,6 @@ def test_compat_load_sandbox_from_toml(default_config, temp_toml_file): [core] workspace_base = "/opt/files2/workspace" -sandbox_type = "local" sandbox_timeout = 500 sandbox_container_image = "node:14" sandbox_user_id = 1001 @@ -199,7 +191,6 @@ def test_compat_load_sandbox_from_toml(default_config, temp_toml_file): assert default_config.default_agent == 'TestAgent' assert default_config.get_agent_config().memory_enabled is True assert default_config.workspace_base == '/opt/files2/workspace' - assert default_config.sandbox.box_type == 'local' assert default_config.sandbox.timeout == 500 assert default_config.sandbox.container_image == 'node:14' assert default_config.sandbox.user_id == 1001 @@ -208,7 +199,6 @@ def test_compat_load_sandbox_from_toml(default_config, temp_toml_file): finalize_config(default_config) # app config doesn't have fields sandbox_* - assert not hasattr(default_config, 'sandbox_type') assert not hasattr(default_config, 'sandbox_timeout') assert not hasattr(default_config, 'sandbox_container_image') assert not hasattr(default_config, 'sandbox_user_id') @@ -229,7 +219,6 @@ def test_env_overrides_compat_toml(monkeypatch, default_config, temp_toml_file): [core] workspace_base = "/opt/files3/workspace" -sandbox_type = "local" disable_color = true sandbox_timeout = 500 sandbox_user_id = 1001 @@ -237,7 +226,6 @@ def test_env_overrides_compat_toml(monkeypatch, default_config, temp_toml_file): monkeypatch.setenv('LLM_API_KEY', 'env-api-key') monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED') - monkeypatch.setenv('SANDBOX_TYPE', 'e2b') monkeypatch.setenv('SANDBOX_TIMEOUT', '1000') monkeypatch.setenv('SANDBOX_USER_ID', '1002') @@ -262,7 +250,6 @@ def test_env_overrides_compat_toml(monkeypatch, default_config, temp_toml_file): assert default_config.workspace_mount_path is UndefinedString.UNDEFINED assert default_config.workspace_mount_path == 'UNDEFINED' - assert default_config.sandbox.box_type == 'e2b' assert default_config.disable_color is True assert default_config.sandbox.timeout == 1000 assert default_config.sandbox.user_id == 1002 @@ -285,14 +272,12 @@ def test_env_overrides_sandbox_toml(monkeypatch, default_config, temp_toml_file) workspace_base = "/opt/files3/workspace" [sandbox] -box_type = "e2b" timeout = 500 user_id = 1001 """) monkeypatch.setenv('LLM_API_KEY', 'env-api-key') monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED') - monkeypatch.setenv('SANDBOX_TYPE', 'local') monkeypatch.setenv('SANDBOX_TIMEOUT', '1000') monkeypatch.setenv('SANDBOX_USER_ID', '1002') @@ -303,7 +288,6 @@ def test_env_overrides_sandbox_toml(monkeypatch, default_config, temp_toml_file) # before load_from_env, values are set to the values from the toml file assert default_config.get_llm_config().api_key == 'toml-api-key' - assert default_config.sandbox.box_type == 'e2b' assert default_config.sandbox.timeout == 500 assert default_config.sandbox.user_id == 1001 @@ -314,7 +298,6 @@ def test_env_overrides_sandbox_toml(monkeypatch, default_config, temp_toml_file) assert default_config.get_llm_config().model == 'test-model' assert default_config.get_llm_config().api_key == 'env-api-key' - assert default_config.sandbox.box_type == 'local' assert default_config.sandbox.timeout == 1000 assert default_config.sandbox.user_id == 1002 @@ -335,7 +318,6 @@ def test_sandbox_config_from_toml(default_config, temp_toml_file): model = "test-model" [sandbox] -box_type = "local" timeout = 1 container_image = "custom_image" user_id = 1001 @@ -347,7 +329,6 @@ def test_sandbox_config_from_toml(default_config, temp_toml_file): finalize_config(default_config) assert default_config.get_llm_config().model == 'test-model' - assert default_config.sandbox.box_type == 'local' assert default_config.sandbox.timeout == 1 assert default_config.sandbox.container_image == 'custom_image' assert default_config.sandbox.user_id == 1001 @@ -374,7 +355,6 @@ def test_defaults_dict_after_updates(default_config): defaults_after_updates['workspace_mount_path']['default'] is UndefinedString.UNDEFINED ) - assert defaults_after_updates['sandbox']['box_type']['default'] == 'ssh' assert defaults_after_updates['sandbox']['timeout']['default'] == 120 assert ( defaults_after_updates['sandbox']['container_image']['default'] @@ -393,7 +373,6 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config): load_from_toml(default_config) load_from_env(default_config, os.environ) - default_config.ssh_password = None # prevent leak default_config.jwt_secret = None # prevent leak for llm in default_config.llms.values(): llm.api_key = None # prevent leak @@ -405,13 +384,8 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config): def test_finalize_config(default_config): # Test finalize config assert default_config.workspace_mount_path is UndefinedString.UNDEFINED - default_config.sandbox.box_type = 'local' finalize_config(default_config) - assert ( - default_config.workspace_mount_path_in_sandbox - == default_config.workspace_mount_path - ) assert default_config.workspace_mount_path == os.path.abspath( default_config.workspace_base ) @@ -426,16 +400,6 @@ def test_workspace_mount_path_default(default_config): ) -def test_workspace_mount_path_in_sandbox_local(default_config): - assert default_config.workspace_mount_path_in_sandbox == '/workspace' - default_config.sandbox.box_type = 'local' - finalize_config(default_config) - assert ( - default_config.workspace_mount_path_in_sandbox - == default_config.workspace_mount_path - ) - - def test_workspace_mount_rewrite(default_config, monkeypatch): default_config.workspace_base = '/home/user/project' default_config.workspace_mount_rewrite = '/home/user:/sandbox' @@ -512,14 +476,11 @@ def test_api_keys_repr_str(): agents={'agent': agent_config}, e2b_api_key='my_e2b_api_key', jwt_secret='my_jwt_secret', - ssh_password='my_ssh_password', ) assert "e2b_api_key='******'" in repr(app_config) assert "e2b_api_key='******'" in str(app_config) assert "jwt_secret='******'" in repr(app_config) assert "jwt_secret='******'" in str(app_config) - assert "ssh_password='******'" in repr(app_config) - assert "ssh_password='******'" in str(app_config) # Check that no other attrs in AppConfig have 'key' or 'token' in their name # This will fail when new attrs are added, and attract attention From 512f56ea80eb186c982201e410b456379d037970 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:25:24 -0400 Subject: [PATCH 04/46] chore(deps-dev): bump tailwindcss from 3.4.7 to 3.4.9 in /frontend (#3297) Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.7 to 3.4.9. - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.9/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.7...v3.4.9) --- updated-dependencies: - dependency-name: tailwindcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2ef4a59f221d..126781d37957 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -63,7 +63,7 @@ "lint-staged": "^15.2.8", "postcss": "^8.4.41", "prettier": "^3.3.3", - "tailwindcss": "^3.4.7", + "tailwindcss": "^3.4.9", "typescript": "^5.5.4", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" @@ -12147,9 +12147,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.7.tgz", - "integrity": "sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ==", + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz", + "integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", diff --git a/frontend/package.json b/frontend/package.json index d775c380c0fd..3317d011b093 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -86,7 +86,7 @@ "lint-staged": "^15.2.8", "postcss": "^8.4.41", "prettier": "^3.3.3", - "tailwindcss": "^3.4.7", + "tailwindcss": "^3.4.9", "typescript": "^5.5.4", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" From 81db5aefc7ac11afae7257d1c45bb418d31b3fb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:25:36 -0400 Subject: [PATCH 05/46] chore(deps): bump vite from 5.3.5 to 5.4.0 in /frontend (#3295) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.5 to 5.4.0. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/create-vite@5.4.0/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 14 +++++++++----- frontend/package.json | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 126781d37957..147651479db0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,7 +32,7 @@ "react-redux": "^9.1.2", "react-syntax-highlighter": "^15.5.0", "tailwind-merge": "^2.4.0", - "vite": "^5.3.5", + "vite": "^5.4.0", "web-vitals": "^3.5.2" }, "devDependencies": { @@ -12833,12 +12833,12 @@ } }, "node_modules/vite": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", - "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", + "integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.39", + "postcss": "^8.4.40", "rollup": "^4.13.0" }, "bin": { @@ -12858,6 +12858,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -12875,6 +12876,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, diff --git a/frontend/package.json b/frontend/package.json index 3317d011b093..dc9641ffd76a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ "react-redux": "^9.1.2", "react-syntax-highlighter": "^15.5.0", "tailwind-merge": "^2.4.0", - "vite": "^5.3.5", + "vite": "^5.4.0", "web-vitals": "^3.5.2" }, "scripts": { From ddd2565035f3494645104cd1d46dbef708324dbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:25:52 -0400 Subject: [PATCH 06/46] chore(deps-dev): bump @tailwindcss/typography in /frontend (#3294) Bumps [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) from 0.5.13 to 0.5.14. - [Release notes](https://github.com/tailwindlabs/tailwindcss-typography/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/master/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.13...v0.5.14) --- updated-dependencies: - dependency-name: "@tailwindcss/typography" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 147651479db0..a931aefb12e9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,7 +36,7 @@ "web-vitals": "^3.5.2" }, "devDependencies": { - "@tailwindcss/typography": "^0.5.13", + "@tailwindcss/typography": "^0.5.14", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", @@ -4637,9 +4637,9 @@ } }, "node_modules/@tailwindcss/typography": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", - "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", + "version": "0.5.14", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.14.tgz", + "integrity": "sha512-ZvOCjUbsJBjL9CxQBn+VEnFpouzuKhxh2dH8xMIWHILL+HfOYtlAkWcyoon8LlzE53d2Yo6YO6pahKKNW3q1YQ==", "dev": true, "dependencies": { "lodash.castarray": "^4.4.0", diff --git a/frontend/package.json b/frontend/package.json index dc9641ffd76a..62e3778eeca6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,7 +59,7 @@ ] }, "devDependencies": { - "@tailwindcss/typography": "^0.5.13", + "@tailwindcss/typography": "^0.5.14", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", From 2e6b08db4fd12eadb993e32b418b62c289c34ffa Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Fri, 9 Aug 2024 07:28:34 +0800 Subject: [PATCH 07/46] fix: workspace folder permission & app container cannot access client API (#3300) * also copy over pyproject and poetry lock * add missing readme * remove extra git config init since it is already done in client.py * only chown if the /workspace dir does not exists * Revert "remove extra git config init since it is already done in client.py" This reverts commit e8556cd76dcb1720b33f5e06904c56efda2e7d9f. * remove extra git config init since it is already done in client.py * fix test runtime * print container log while reconnecting * print log in more readable format * print log in more readable format * increase lines * clean up sandbox and ssh related stuff * remove ssh hostname * remove ssh hostname * fix docker app cannot access runtime API issue * remove ssh password * API HOSTNAME should be pre-fixed with SANDBOX * update config * fix typo that breaks the test --- containers/app/Dockerfile | 1 + opendevin/core/config.py | 2 ++ opendevin/runtime/client/client.py | 23 ++++++++++++++--------- opendevin/runtime/client/runtime.py | 29 +++++++++++++++-------------- tests/unit/test_runtime.py | 7 +++++-- 5 files changed, 37 insertions(+), 25 deletions(-) diff --git a/containers/app/Dockerfile b/containers/app/Dockerfile index efd36fd0363c..36ed2fbf062f 100644 --- a/containers/app/Dockerfile +++ b/containers/app/Dockerfile @@ -37,6 +37,7 @@ ARG OPEN_DEVIN_BUILD_VERSION #re-declare for this section ENV RUN_AS_DEVIN=true # A random number--we need this to be different from the user's UID on the host machine ENV OPENDEVIN_USER_ID=42420 +ENV SANDBOX_API_HOSTNAME=host.docker.internal ENV USE_HOST_NETWORK=false ENV WORKSPACE_BASE=/opt/workspace_base ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION diff --git a/opendevin/core/config.py b/opendevin/core/config.py index 6d5d55637f00..46217d3784fb 100644 --- a/opendevin/core/config.py +++ b/opendevin/core/config.py @@ -145,6 +145,7 @@ class SandboxConfig(metaclass=Singleton): """Configuration for the sandbox. Attributes: + api_hostname: The hostname for the EventStream Runtime API. container_image: The container image to use for the sandbox. user_id: The user ID for the sandbox. timeout: The timeout for the sandbox. @@ -164,6 +165,7 @@ class SandboxConfig(metaclass=Singleton): Default is None for general purpose browsing. Check evaluation/miniwob and evaluation/webarena for examples. """ + api_hostname: str = 'localhost' container_image: str = ( 'ubuntu:22.04' # default to ubuntu:22.04 for eventstream runtime ) diff --git a/opendevin/runtime/client/client.py b/opendevin/runtime/client/client.py index 572ba447eb96..a1883bb829ff 100644 --- a/opendevin/runtime/client/client.py +++ b/opendevin/runtime/client/client.py @@ -128,14 +128,19 @@ def _init_user(self, username: str, user_id: int) -> None: raise RuntimeError(f'Failed to add sudoer: {output.stderr.decode()}') logger.debug(f'Added sudoer successfully. Output: [{output.stdout.decode()}]') - # Add user and change ownership of the initial working directory + # Add user and change ownership of the initial working directory if it doesn't exist + command = ( + f'useradd -rm -d /home/{username} -s /bin/bash ' + f'-g root -G sudo -u {user_id} {username}' + ) + + if not os.path.exists(self.initial_pwd): + command += f' && mkdir -p {self.initial_pwd}' + command += f' && chown -R {username}:root {self.initial_pwd}' + command += f' && chmod g+s {self.initial_pwd}' + output = subprocess.run( - ( - f'useradd -rm -d /home/{username} -s /bin/bash ' - f'-g root -G sudo -u {user_id} {username} &&' - f'chown -R {username}:root {self.initial_pwd} && ' - f'chmod g+s {self.initial_pwd}' - ), + command, shell=True, capture_output=True, ) @@ -381,11 +386,11 @@ async def write(self, action: FileWriteAction) -> Observation: assert file_stat is not None # restore the original file permissions if the file already exists os.chmod(filepath, file_stat.st_mode) - os.chown(filepath, file_stat.st_uid, ROOT_GID) + os.chown(filepath, file_stat.st_uid, file_stat.st_gid) else: # set the new file permissions if the file is new os.chmod(filepath, 0o644) - os.chown(filepath, self.user_id, ROOT_GID) + os.chown(filepath, self.user_id, self.user_id) except FileNotFoundError: return ErrorObservation(f'File not found: {filepath}') diff --git a/opendevin/runtime/client/runtime.py b/opendevin/runtime/client/runtime.py index d646b76fcc93..e8733a713494 100644 --- a/opendevin/runtime/client/runtime.py +++ b/opendevin/runtime/client/runtime.py @@ -22,7 +22,6 @@ ) from opendevin.events.action.action import Action from opendevin.events.observation import ( - CmdOutputObservation, ErrorObservation, NullObservation, Observation, @@ -54,7 +53,7 @@ def __init__( config, event_stream, sid, plugins ) # will initialize the event stream self._port = find_available_tcp_port() - self.api_url = f'http://localhost:{self._port}' + self.api_url = f'http://{self.config.sandbox.api_hostname}:{self._port}' self.session: Optional[aiohttp.ClientSession] = None self.instance_id = ( @@ -98,8 +97,6 @@ async def ainit(self, env_vars: dict[str, str] | None = None): ) logger.info(f'Container initialized with env vars: {env_vars}') - await self._init_git_config() - @staticmethod def _init_docker_client() -> docker.DockerClient: try: @@ -183,16 +180,6 @@ async def _init_container( await self.close(close_client=False) raise e - async def _init_git_config(self): - action = CmdRunAction( - 'git config --global user.name "opendevin" && ' - 'git config --global user.email "opendevin@all-hands.dev"' - ) - logger.info(f'Setting git config: {action}') - obs: Observation = await self.run_action(action) - assert isinstance(obs, CmdOutputObservation) - assert obs.exit_code == 0, f'Failed to set git config: {obs}' - async def _ensure_session(self): await asyncio.sleep(1) if self.session is None or self.session.closed: @@ -205,6 +192,20 @@ async def _ensure_session(self): ) async def _wait_until_alive(self): logger.info('Reconnecting session') + container = self.docker_client.containers.get(self.container_name) + # print logs + _logs = container.logs(tail=10).decode('utf-8').split('\n') + # add indent + _logs = '\n'.join([f' |{log}' for log in _logs]) + logger.info( + '\n' + + '-' * 30 + + 'Container logs (last 10 lines):' + + '-' * 30 + + f'\n{_logs}' + + '\n' + + '-' * 90 + ) async with aiohttp.ClientSession() as session: async with session.get(f'{self.api_url}/alive') as response: if response.status == 200: diff --git a/tests/unit/test_runtime.py b/tests/unit/test_runtime.py index 0e1a402caf33..9614f5adbf23 100644 --- a/tests/unit/test_runtime.py +++ b/tests/unit/test_runtime.py @@ -1265,9 +1265,12 @@ async def test_keep_prompt(temp_dir): @pytest.mark.asyncio -async def test_git_operation(temp_dir, box_class): +async def test_git_operation(box_class): + # do not mount workspace, since workspace mount by tests will be owned by root + # while the user_id we get via os.getuid() is different from root + # which causes permission issues runtime = await _load_runtime( - temp_dir, + temp_dir=None, box_class=box_class, # Need to use non-root user to expose issues run_as_devin=True, From ac7badc2364ed2edb5c692de732a027f2959b7a0 Mon Sep 17 00:00:00 2001 From: tobitege Date: Fri, 9 Aug 2024 11:06:39 +0200 Subject: [PATCH 08/46] (fix) revert #3240 ignore_paths in ghcr.yml (#3308) --- .github/workflows/ghcr.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 1846e8742f8c..dd04e2532d69 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -12,11 +12,6 @@ on: tags: - '*' pull_request: - paths-ignore: - - '**.md' - - 'docs/**' - - 'frontend/**' - - 'evaluation/**' workflow_dispatch: inputs: reason: From 5e6fd58f56840f20fc619349428092b7ddae45fc Mon Sep 17 00:00:00 2001 From: tobitege Date: Fri, 9 Aug 2024 14:02:43 +0200 Subject: [PATCH 09/46] try to fix DOCKER_IMAGE_TAG in ghcr_push_runtime (od_runtime, arm64) (#3311) --- .github/workflows/ghcr.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index dd04e2532d69..4477d0bf6a62 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -305,7 +305,7 @@ jobs: needs: [ghcr_build_runtime, test_runtime, runtime_integration_tests_on_linux] if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') env: - tags: ${{ needs.ghcr_build_runtime.outputs.tags }} + RUNTIME_TAGS: ${{ needs.ghcr_build_runtime.outputs.tags }} permissions: contents: read packages: write @@ -349,16 +349,15 @@ jobs: exit 1 fi echo "loaded image = $loaded_image" - tags=$(echo ${tags} | tr ' ' '\n') image_name=$(echo "ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}" | tr '[:upper:]' '[:lower:]') echo "image name = $image_name" - for tag in $tags; do + echo "$RUNTIME_TAGS" | tr ' ' '\n' | while read -r tag; do echo "tag = $tag" - if [ -n "$image_name" ]; then + if [ -n "$image_name" ] && [ -n "$tag" ]; then docker tag $loaded_image $image_name:${tag}_${{ matrix.platform }} docker push $image_name:${tag}_${{ matrix.platform }} else - echo "Skipping tag and push due to empty image_name" + echo "Skipping tag and push due to empty image_name or tag" fi done From b4db5b9cae7eb398f10ddc0d1e609d7db9c97731 Mon Sep 17 00:00:00 2001 From: Graham Neubig Date: Fri, 9 Aug 2024 10:06:55 -0400 Subject: [PATCH 10/46] Remove some obsolete examples of persist_sandbox in the doc (#3312) --- .../current/usage/custom_sandbox_guide.md | 2 -- .../current/usage/custom_sandbox_guide.md | 2 -- docs/modules/usage/custom_sandbox_guide.md | 2 -- 3 files changed, 6 deletions(-) diff --git a/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md b/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md index 92fefc90ff22..d7812087ff5e 100644 --- a/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md +++ b/docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md @@ -41,7 +41,6 @@ Créez un fichier ```config.toml``` dans le répertoire OpenDevin et entrez ces ```toml [core] workspace_base="./workspace" -persist_sandbox=false run_as_devin=true sandbox_container_image="image_personnalisée" ``` @@ -92,7 +91,6 @@ Si vous voyez cette erreur dans la sortie de la console, il s'agit du fait que O ```toml [core] workspace_base="./workspace" -persist_sandbox=false run_as_devin=true sandbox_container_image="image_personnalisée" sandbox_user_id="1001" diff --git a/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md b/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md index c21374abbd74..8bae2b75dc80 100644 --- a/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md +++ b/docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/custom_sandbox_guide.md @@ -40,7 +40,6 @@ docker build -t custom_image . ``` [core] workspace_base="./workspace" -persist_sandbox=false run_as_devin=true sandbox_container_image="custom_image" ``` @@ -92,7 +91,6 @@ dockerfile_content = ( ``` [core] workspace_base="./workspace" -persist_sandbox=false run_as_devin=true sandbox_container_image="custom_image" sandbox_user_id="1001" diff --git a/docs/modules/usage/custom_sandbox_guide.md b/docs/modules/usage/custom_sandbox_guide.md index eb5f1dc0780b..8be127f44b4a 100644 --- a/docs/modules/usage/custom_sandbox_guide.md +++ b/docs/modules/usage/custom_sandbox_guide.md @@ -70,7 +70,6 @@ Create a `config.toml` file in the OpenDevin directory and enter these contents: ```toml [core] workspace_base="./workspace" -persist_sandbox=false run_as_devin=true sandbox_container_image="custom_image" ``` @@ -129,7 +128,6 @@ If you see this error in the console output it is because OpenDevin is trying to ```toml [core] workspace_base="./workspace" -persist_sandbox=false run_as_devin=true sandbox_container_image="custom_image" sandbox_user_id="1001" From d2a8ff0918e16e9f6489dc53ce05ed3b2d75e9bc Mon Sep 17 00:00:00 2001 From: tobitege Date: Fri, 9 Aug 2024 16:22:10 +0200 Subject: [PATCH 11/46] fix double quotes around env vars typo in ghcr.yml (#3313) --- .github/workflows/ghcr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 4477d0bf6a62..770c181a7f9e 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -125,7 +125,7 @@ jobs: else echo 'No Dockerfile detected which means an exact image is already built. Pulling the image and saving it to a tar file...' source containers/runtime/config.sh - echo '$DOCKER_IMAGE_TAG $DOCKER_IMAGE_HASH_TAG' >> tags.txt + echo "$DOCKER_IMAGE_TAG $DOCKER_IMAGE_HASH_TAG" >> tags.txt echo "Pulling image $DOCKER_IMAGE/$DOCKER_IMAGE_HASH_TAG to /tmp/${{ matrix.image }}_image_${{ matrix.platform }}.tar" docker pull $DOCKER_IMAGE:$DOCKER_IMAGE_HASH_TAG docker save $DOCKER_IMAGE:$DOCKER_IMAGE_HASH_TAG -o /tmp/${{ matrix.image }}_image_${{ matrix.platform }}.tar From f8c815279d354d56cafb1f70479a1085364f5b55 Mon Sep 17 00:00:00 2001 From: tofarr Date: Fri, 9 Aug 2024 09:31:29 -0600 Subject: [PATCH 12/46] Bug fix drag resize (#3307) * Fix issue where mouse drag fails on first attempt * Detach event correctly * Use old variable names --------- Co-authored-by: Tim O'Farrell Co-authored-by: tobitege --- frontend/src/components/Resizable.tsx | 81 +++++++++++++++------------ 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/Resizable.tsx b/frontend/src/components/Resizable.tsx index 4d7663e45963..5bdc98afb2d8 100644 --- a/frontend/src/components/Resizable.tsx +++ b/frontend/src/components/Resizable.tsx @@ -25,49 +25,58 @@ export function Container({ orientation, initialSize, }: ContainerProps): JSX.Element { - const [firstSize, setFirstSize] = useState(initialSize); - const [dividerPosition, setDividerPosition] = useState( - undefined, - ); + const [firstSize, setFirstSize] = useState(initialSize); + const [dividerPosition, setDividerPosition] = useState(null); const firstRef = useRef(null); useEffect(() => { - if (firstRef.current !== null) { - if (orientation === Orientation.HORIZONTAL) { - firstRef.current.style.width = `${firstSize}px`; - } else { - firstRef.current.style.height = `${firstSize}px`; - } + if (dividerPosition == null || !firstRef.current) { + return undefined; } - }, [firstSize, orientation]); - - const onMouseMove = (e: MouseEvent) => { - e.preventDefault(); - if (firstSize && dividerPosition) { - if (orientation === Orientation.HORIZONTAL) { - const newLeftWidth = firstSize + e.clientX - dividerPosition; - setDividerPosition(e.clientX); - setFirstSize(newLeftWidth); - } else { - const newTopHeight = firstSize + e.clientY - dividerPosition; - setDividerPosition(e.clientY); - setFirstSize(newTopHeight); + const getFirstSizeFromEvent = (e: MouseEvent) => { + const position = + orientation === Orientation.HORIZONTAL ? e.clientX : e.clientY; + return firstSize + position - dividerPosition; + }; + const onMouseMove = (e: MouseEvent) => { + e.preventDefault(); + const newFirstSize = getFirstSizeFromEvent(e); + const { current } = firstRef; + if (current) { + if (orientation === Orientation.HORIZONTAL) { + current.style.width = `${newFirstSize}px`; + } else { + current.style.height = `${newFirstSize}px`; + } } - } - }; - - const onMouseUp = () => { - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - }; + }; + const onMouseUp = (e: MouseEvent) => { + e.preventDefault(); + setFirstSize(getFirstSizeFromEvent(e)); + setDividerPosition(null); + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + return () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + }, [dividerPosition, firstSize, orientation]); const onMouseDown = (e: React.MouseEvent) => { e.preventDefault(); - setDividerPosition( - orientation === Orientation.HORIZONTAL ? e.clientX : e.clientY, - ); - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); + const position = + orientation === Orientation.HORIZONTAL ? e.clientX : e.clientY; + setDividerPosition(position); + }; + + const getStyleForFirst = () => { + if (orientation === Orientation.HORIZONTAL) { + return { width: `${firstSize}px` }; + } + return { height: `${firstSize}px` }; }; return ( @@ -77,7 +86,7 @@ export function Container({ className, )} > -
+
{firstChild}
Date: Fri, 9 Aug 2024 11:35:28 -0400 Subject: [PATCH 13/46] chore(deps-dev): bump ruff from 0.5.6 to 0.5.7 (#3322) Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.6 to 0.5.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.5.6...0.5.7) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 40 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index bbef5ce8f7dd..d52debb1a7f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7025,29 +7025,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.5.6" +version = "0.5.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"}, - {file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"}, - {file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"}, - {file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"}, - {file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"}, - {file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"}, - {file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"}, - {file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"}, - {file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"}, + {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"}, + {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"}, + {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"}, + {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"}, + {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"}, + {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"}, + {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"}, + {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"}, + {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"}, ] [[package]] @@ -9223,4 +9223,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "202d5f9ba3bad3e13568393524ef4f6d8957cb932b208bdcbbf666437926f6d4" +content-hash = "613a56e7dc5551be660388fb8603f6139dbc5d440ea39f3ba931870dc3234bb4" diff --git a/pyproject.toml b/pyproject.toml index 063a002fc97f..7e7d7276b6d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ llama-index-embeddings-azure-openai = "*" llama-index-embeddings-ollama = "*" [tool.poetry.group.dev.dependencies] -ruff = "0.5.6" +ruff = "0.5.7" mypy = "1.11.1" pre-commit = "3.8.0" build = "*" From e9ee8f1cb9b83a37e5fd84a00949f1a7212ca02a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:35:52 -0400 Subject: [PATCH 14/46] chore(deps): bump json-repair from 0.26.0 to 0.27.0 (#3321) Bumps [json-repair](https://github.com/mangiucugna/json_repair) from 0.26.0 to 0.27.0. - [Release notes](https://github.com/mangiucugna/json_repair/releases) - [Commits](https://github.com/mangiucugna/json_repair/compare/0.26.0...0.27.0) --- updated-dependencies: - dependency-name: json-repair dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index d52debb1a7f5..fba6933fd89d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3084,13 +3084,13 @@ files = [ [[package]] name = "json-repair" -version = "0.26.0" +version = "0.27.0" description = "A package to repair broken json strings" optional = false python-versions = ">=3.8" files = [ - {file = "json_repair-0.26.0-py3-none-any.whl", hash = "sha256:01a9dfd3f8611a0207eaf93989cfaf678dabdd62c6e128125aaf142cd136ec58"}, - {file = "json_repair-0.26.0.tar.gz", hash = "sha256:dccdcfcbe910a5b3f93ea234cf41e79c900b42e16746400d7a9d8823229c7f61"}, + {file = "json_repair-0.27.0-py3-none-any.whl", hash = "sha256:20763c8cf1c3096e33ce7c09c2b8e6c471a4acdce468688c96052fc7cccbad7f"}, + {file = "json_repair-0.27.0.tar.gz", hash = "sha256:f4e14c5ad2b3f17290a361c3c90915536b462c36f69989e915867e81663dd467"}, ] [[package]] From e57d14bfc8f3716d24823b455ba3a7e83ec77af5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:36:04 -0400 Subject: [PATCH 15/46] chore(deps): bump litellm from 1.43.3 to 1.43.4 (#3320) Bumps [litellm](https://github.com/BerriAI/litellm) from 1.43.3 to 1.43.4. - [Release notes](https://github.com/BerriAI/litellm/releases) - [Commits](https://github.com/BerriAI/litellm/compare/v1.43.3...v1.43.4) --- updated-dependencies: - dependency-name: litellm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index fba6933fd89d..d7c78985085a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3566,13 +3566,13 @@ types-tqdm = "*" [[package]] name = "litellm" -version = "1.43.3" +version = "1.43.4" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.43.3-py3-none-any.whl", hash = "sha256:b626df5da48b794be4d1888eaecfe941d68a081bfeab50531e5395e32d0e7894"}, - {file = "litellm-1.43.3.tar.gz", hash = "sha256:b139fd3965cc1fdf98746dac67ce1f57f72c247083174ee8f1f46ed86fafdffc"}, + {file = "litellm-1.43.4-py3-none-any.whl", hash = "sha256:619cfaab189f921f66ff50c2b7a0e965e562c2a95b17c2ee24649826ba35da11"}, + {file = "litellm-1.43.4.tar.gz", hash = "sha256:949c51ad494b935d80da1cd18c3567e4ed181f8eb531ef4706e3be72afb0c43c"}, ] [package.dependencies] From 6854d38707d59db3504efbea2a2ac70bebec5ffe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:36:46 -0400 Subject: [PATCH 16/46] chore(deps): bump boto3 from 1.34.156 to 1.34.157 (#3317) Bumps [boto3](https://github.com/boto/boto3) from 1.34.156 to 1.34.157. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.34.156...1.34.157) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index d7c78985085a..6d36383b8661 100644 --- a/poetry.lock +++ b/poetry.lock @@ -519,17 +519,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.156" +version = "1.34.157" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.156-py3-none-any.whl", hash = "sha256:cbbd453270b8ce94ef9da60dfbb6f9ceeb3eeee226b635aa9ec44b1def98cc96"}, - {file = "boto3-1.34.156.tar.gz", hash = "sha256:b33e9a8f8be80d3053b8418836a7c1900410b23a30c7cb040927d601a1082e68"}, + {file = "boto3-1.34.157-py3-none-any.whl", hash = "sha256:3cc357156df5482154a016f138d1953061a181b4c594f8b6302c9d6c024bd950"}, + {file = "boto3-1.34.157.tar.gz", hash = "sha256:7ef19ed38cba9863b58430fb4a66a72a5c250304f234bd1c16b860f9bf25677b"}, ] [package.dependencies] -botocore = ">=1.34.156,<1.35.0" +botocore = ">=1.34.157,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -538,13 +538,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.156" +version = "1.34.157" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.156-py3-none-any.whl", hash = "sha256:c48f8c8996216dfdeeb0aa6d3c0f2c7ae25234766434a2ea3e57bdc08494bdda"}, - {file = "botocore-1.34.156.tar.gz", hash = "sha256:5d1478c41ab9681e660b3322432fe09c4055759c317984b7b8d3af9557ff769a"}, + {file = "botocore-1.34.157-py3-none-any.whl", hash = "sha256:c6cba6de8eb86ca4d2f934e009b37adbe1e7fdcfa52fbab74783f4c30676e07d"}, + {file = "botocore-1.34.157.tar.gz", hash = "sha256:5628a36cec123cdc8c1158d05a7b06aa5e53649ad73796c50ef3fb51199785fb"}, ] [package.dependencies] From 8bb9e37c4620d79a7e938a0990ed7009fef71ac1 Mon Sep 17 00:00:00 2001 From: Kavinkumar <33546454+mkavinkumar1@users.noreply.github.com> Date: Fri, 9 Aug 2024 21:08:23 +0530 Subject: [PATCH 17/46] =?UTF-8?q?Clear=20history=20at=20the=20beginning=20?= =?UTF-8?q?of=20a=20new=20task=F0=9F=A7=B9=20(#3285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tobitege --- frontend/src/components/AgentControlBar.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/components/AgentControlBar.tsx b/frontend/src/components/AgentControlBar.tsx index 04f1775b2323..7c4437086d0a 100644 --- a/frontend/src/components/AgentControlBar.tsx +++ b/frontend/src/components/AgentControlBar.tsx @@ -8,6 +8,7 @@ import { changeAgentState } from "#/services/agentStateService"; import store, { RootState } from "#/store"; import AgentState from "#/types/AgentState"; import { clearMessages } from "#/state/chatSlice"; +import Session from "#/services/session"; const IgnoreTaskStateMap: { [k: string]: AgentState[] } = { [AgentState.PAUSED]: [ @@ -83,6 +84,7 @@ function AgentControlBar() { } if (action === AgentState.STOPPED) { + Session._history = []; store.dispatch(clearMessages()); } else { setIsLoading(true); From 2b96911758dfe1220bb0f71d2007e33b0d6ef4ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:38:39 -0400 Subject: [PATCH 18/46] chore(deps): bump google-cloud-aiplatform from 1.60.0 to 1.61.0 (#3318) Bumps [google-cloud-aiplatform](https://github.com/googleapis/python-aiplatform) from 1.60.0 to 1.61.0. - [Release notes](https://github.com/googleapis/python-aiplatform/releases) - [Changelog](https://github.com/googleapis/python-aiplatform/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/python-aiplatform/compare/v1.60.0...v1.61.0) --- updated-dependencies: - dependency-name: google-cloud-aiplatform dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6d36383b8661..425d067186b3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2127,13 +2127,13 @@ httplib2 = ">=0.19.0" [[package]] name = "google-cloud-aiplatform" -version = "1.60.0" +version = "1.61.0" description = "Vertex AI API client library" optional = false python-versions = ">=3.8" files = [ - {file = "google-cloud-aiplatform-1.60.0.tar.gz", hash = "sha256:782c7f1ec0e77a7c7daabef3b65bfd506ed2b4b1dc2186753c43cd6faf8dd04e"}, - {file = "google_cloud_aiplatform-1.60.0-py2.py3-none-any.whl", hash = "sha256:5f14159c9575f4b46335027e3ceb8fa57bd5eaa76a07f858105b8c6c034ec0d6"}, + {file = "google-cloud-aiplatform-1.61.0.tar.gz", hash = "sha256:648e3cd7bb75be706d3c31d852a3d4d8a2e616ad4db4cf520ef4430615cf8ad9"}, + {file = "google_cloud_aiplatform-1.61.0-py2.py3-none-any.whl", hash = "sha256:57b36d5fa085e68197e9fc576c43263a7cad320483aa3b166bcd1fdc7e8f49e7"}, ] [package.dependencies] @@ -2145,7 +2145,7 @@ google-cloud-resource-manager = ">=1.3.3,<3.0.0dev" google-cloud-storage = ">=1.32.0,<3.0.0dev" packaging = ">=14.3" proto-plus = ">=1.22.3,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" pydantic = "<3" shapely = "<3.0.0dev" From 00bc68642f8b79e5539940b1a8602b7708cb81b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:38:49 -0400 Subject: [PATCH 19/46] chore(deps-dev): bump openai from 1.40.1 to 1.40.2 (#3319) Bumps [openai](https://github.com/openai/openai-python) from 1.40.1 to 1.40.2. - [Release notes](https://github.com/openai/openai-python/releases) - [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/openai/openai-python/compare/v1.40.1...v1.40.2) --- updated-dependencies: - dependency-name: openai dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 425d067186b3..65a38908108f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5126,13 +5126,13 @@ sympy = "*" [[package]] name = "openai" -version = "1.40.1" +version = "1.40.2" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.40.1-py3-none-any.whl", hash = "sha256:cf5929076c6ca31c26f1ed207e9fd19eb05404cc9104f64c9d29bb0ac0c5bcd4"}, - {file = "openai-1.40.1.tar.gz", hash = "sha256:cb1294ac1f8c6a1acbb07e090698eb5ad74a7a88484e77126612a4f22579673d"}, + {file = "openai-1.40.2-py3-none-any.whl", hash = "sha256:38068f858f310b4fd4b0ea8734c3efcfde3c15a2978311e1453bd84817231b96"}, + {file = "openai-1.40.2.tar.gz", hash = "sha256:2180e9070bd36084328248b3ce668964e8ddd2e9019e1d426e31dc54cc117bb5"}, ] [package.dependencies] From bdf6df12c34bb944161c06f41eaef5864bf757d3 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Sat, 10 Aug 2024 03:04:43 +0800 Subject: [PATCH 20/46] fix: pip not available in runtime (#3306) * try to fix pip unavailable * update test case for pip * force rebuild in CI * remove extra symlink * fix newline * added semi-colon to line 31 * Dockerfile.j2: activate env at the end * Revert "Dockerfile.j2: activate env at the end" This reverts commit cf2f5651021fe80d4ab69a35a85f0a35b29dc3d7. * cleanup Dockerfile * switch default python image * remove image agnostic (no longer used) * fix tests * switch to nikolaik/python-nodejs:python3.11-nodejs22 * fix test * fix test * revert docker * update template --------- Co-authored-by: tobitege Co-authored-by: Graham Neubig --- .github/workflows/ghcr.yml | 4 +- config.template.toml | 2 +- evaluation/EDA/run_infer.py | 2 +- evaluation/agent_bench/run_infer.py | 2 +- evaluation/bird/run_infer.py | 2 +- evaluation/browsing_delegation/run_infer.py | 2 +- evaluation/gaia/run_infer.py | 2 +- evaluation/gorilla/run_infer.py | 2 +- evaluation/gpqa/run_infer.py | 2 +- evaluation/humanevalfix/run_infer.py | 2 +- evaluation/toolqa/run_infer.py | 2 +- evaluation/webarena/run_infer.py | 2 +- opendevin/core/config.py | 4 +- opendevin/runtime/utils/image_agnostic.py | 113 ------------------ opendevin/runtime/utils/runtime_build.py | 16 ++- .../utils/runtime_templates/Dockerfile.j2 | 7 -- tests/integration/regenerate.sh | 2 +- tests/unit/test_config.py | 2 +- tests/unit/test_image_agnostic_util.py | 51 -------- tests/unit/test_runtime.py | 13 +- tests/unit/test_runtime_build.py | 17 +-- 21 files changed, 50 insertions(+), 201 deletions(-) delete mode 100644 opendevin/runtime/utils/image_agnostic.py delete mode 100644 tests/unit/test_image_agnostic_util.py diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml index 770c181a7f9e..66270d3a0e98 100644 --- a/.github/workflows/ghcr.yml +++ b/.github/workflows/ghcr.yml @@ -81,7 +81,7 @@ jobs: strategy: matrix: image: ['od_runtime'] - base_image: ['ubuntu:22.04'] + base_image: ['nikolaik/python-nodejs:python3.11-nodejs22'] platform: ['amd64', 'arm64'] steps: - name: Checkout @@ -115,7 +115,7 @@ jobs: - name: Install Python dependencies using Poetry run: make install-python-dependencies - name: Create source distribution and Dockerfile - run: poetry run python3 opendevin/runtime/utils/runtime_build.py --base_image ${{ matrix.base_image }} --build_folder containers/runtime + run: poetry run python3 opendevin/runtime/utils/runtime_build.py --base_image ${{ matrix.base_image }} --build_folder containers/runtime --force_rebuild - name: Build and export image id: build run: | diff --git a/config.template.toml b/config.template.toml index 1307852d3d1e..644e1d603472 100644 --- a/config.template.toml +++ b/config.template.toml @@ -174,7 +174,7 @@ llm_config = 'gpt3' #user_id = 1000 # Container image to use for the sandbox -#container_image = "ghcr.io/opendevin/sandbox:main" +#container_image = "nikolaik/python-nodejs:python3.11-nodejs22" # Use host network #use_host_network = false diff --git a/evaluation/EDA/run_infer.py b/evaluation/EDA/run_infer.py index 6d489c3053b4..29ce4241c66b 100644 --- a/evaluation/EDA/run_infer.py +++ b/evaluation/EDA/run_infer.py @@ -62,7 +62,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=False, use_host_network=False, ), diff --git a/evaluation/agent_bench/run_infer.py b/evaluation/agent_bench/run_infer.py index 23514634b688..77b7a1e0026d 100644 --- a/evaluation/agent_bench/run_infer.py +++ b/evaluation/agent_bench/run_infer.py @@ -44,7 +44,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/bird/run_infer.py b/evaluation/bird/run_infer.py index 70402c6a2943..86db5eb9eb20 100644 --- a/evaluation/bird/run_infer.py +++ b/evaluation/bird/run_infer.py @@ -75,7 +75,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/browsing_delegation/run_infer.py b/evaluation/browsing_delegation/run_infer.py index 983d753c8e33..c4d5ecd4d6ea 100644 --- a/evaluation/browsing_delegation/run_infer.py +++ b/evaluation/browsing_delegation/run_infer.py @@ -40,7 +40,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=False, use_host_network=False, ), diff --git a/evaluation/gaia/run_infer.py b/evaluation/gaia/run_infer.py index 7a791c4aa7e3..0ff4b1a4340e 100644 --- a/evaluation/gaia/run_infer.py +++ b/evaluation/gaia/run_infer.py @@ -51,7 +51,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/gorilla/run_infer.py b/evaluation/gorilla/run_infer.py index 8aad9d9da93d..d84432c3b0aa 100644 --- a/evaluation/gorilla/run_infer.py +++ b/evaluation/gorilla/run_infer.py @@ -42,7 +42,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/gpqa/run_infer.py b/evaluation/gpqa/run_infer.py index 32cee5fb7ede..8f2df78e5e16 100644 --- a/evaluation/gpqa/run_infer.py +++ b/evaluation/gpqa/run_infer.py @@ -65,7 +65,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/humanevalfix/run_infer.py b/evaluation/humanevalfix/run_infer.py index 94d23f931373..8fc895042820 100644 --- a/evaluation/humanevalfix/run_infer.py +++ b/evaluation/humanevalfix/run_infer.py @@ -86,7 +86,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/toolqa/run_infer.py b/evaluation/toolqa/run_infer.py index 2eef9d238070..930f1199e672 100644 --- a/evaluation/toolqa/run_infer.py +++ b/evaluation/toolqa/run_infer.py @@ -45,7 +45,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, ), diff --git a/evaluation/webarena/run_infer.py b/evaluation/webarena/run_infer.py index c0a3182f7795..f661a147c93b 100644 --- a/evaluation/webarena/run_infer.py +++ b/evaluation/webarena/run_infer.py @@ -54,7 +54,7 @@ def get_config( runtime='eventstream', max_iterations=metadata.max_iterations, sandbox=SandboxConfig( - container_image='ubuntu:22.04', + container_image='python:3.11-bookworm', enable_auto_lint=True, use_host_network=False, browsergym_eval_env=env_id, diff --git a/opendevin/core/config.py b/opendevin/core/config.py index 46217d3784fb..9f918b02484a 100644 --- a/opendevin/core/config.py +++ b/opendevin/core/config.py @@ -166,9 +166,7 @@ class SandboxConfig(metaclass=Singleton): """ api_hostname: str = 'localhost' - container_image: str = ( - 'ubuntu:22.04' # default to ubuntu:22.04 for eventstream runtime - ) + container_image: str = 'nikolaik/python-nodejs:python3.11-nodejs22' # default to nikolaik/python-nodejs:python3.11-nodejs22 for eventstream runtime user_id: int = os.getuid() if hasattr(os, 'getuid') else 1000 timeout: int = 120 enable_auto_lint: bool = ( diff --git a/opendevin/runtime/utils/image_agnostic.py b/opendevin/runtime/utils/image_agnostic.py deleted file mode 100644 index d44c9c4ebb39..000000000000 --- a/opendevin/runtime/utils/image_agnostic.py +++ /dev/null @@ -1,113 +0,0 @@ -"""This module contains functions for building and managing the agnostic sandbox image. - -This WILL BE DEPRECATED when EventStreamRuntime is fully implemented and adopted. -""" - -import tempfile - -import docker - -from opendevin.core.logger import opendevin_logger as logger - - -def generate_dockerfile(base_image: str) -> str: - """Generate the Dockerfile content for the agnostic sandbox image based on user-provided base image. - - NOTE: This is only tested on debian yet. - """ - # FIXME: Remove the requirement of ssh in future version - dockerfile_content = ( - f'FROM {base_image}\n' - 'RUN apt update && apt install -y openssh-server wget sudo\n' - 'RUN mkdir -p -m0755 /var/run/sshd\n' - 'RUN mkdir -p /opendevin && mkdir -p /opendevin/logs && chmod 777 /opendevin/logs\n' - 'RUN echo "" > /opendevin/bash.bashrc\n' - 'RUN if [ ! -d /opendevin/miniforge3 ]; then \\\n' - ' wget --progress=bar:force -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" && \\\n' - ' bash Miniforge3.sh -b -p /opendevin/miniforge3 && \\\n' - ' rm Miniforge3.sh && \\\n' - ' chmod -R g+w /opendevin/miniforge3 && \\\n' - ' bash -c ". /opendevin/miniforge3/etc/profile.d/conda.sh && conda config --set changeps1 False && conda config --append channels conda-forge"; \\\n' - ' fi\n' - 'RUN /opendevin/miniforge3/bin/pip install --upgrade pip\n' - 'RUN /opendevin/miniforge3/bin/pip install jupyterlab notebook jupyter_kernel_gateway flake8\n' - 'RUN /opendevin/miniforge3/bin/pip install python-docx PyPDF2 python-pptx pylatexenc openai\n' - ).strip() - return dockerfile_content - - -def _build_sandbox_image( - base_image: str, target_image_name: str, docker_client: docker.DockerClient -): - try: - with tempfile.TemporaryDirectory() as temp_dir: - dockerfile_content = generate_dockerfile(base_image) - - logger.info(f'Building agnostic sandbox image: {target_image_name}') - logger.info( - ( - f'===== Dockerfile content =====\n' - f'{dockerfile_content}\n' - f'===============================' - ) - ) - with open(f'{temp_dir}/Dockerfile', 'w') as file: - file.write(dockerfile_content) - - api_client = docker_client.api - build_logs = api_client.build( - path=temp_dir, tag=target_image_name, rm=True, decode=True - ) - - for log in build_logs: - if 'stream' in log: - print(log['stream'].strip()) - elif 'error' in log: - logger.error(log['error'].strip()) - else: - logger.info(str(log)) - - logger.info(f'Image {target_image_name} built successfully') - except docker.errors.BuildError as e: - logger.error(f'Sandbox image build failed: {e}') - raise e - except Exception as e: - logger.error(f'An error occurred during sandbox image build: {e}') - raise e - - -def _get_new_image_name(base_image: str) -> str: - prefix = 'od_sandbox' - if ':' not in base_image: - base_image = base_image + ':latest' - - [repo, tag] = base_image.split(':') - repo = repo.replace('/', '___') - return f'{prefix}:{repo}__{tag}' - - -def get_od_sandbox_image(base_image: str, docker_client: docker.DockerClient) -> str: - """Return the sandbox image name based on user-provided base image. - - The returned sandbox image is assumed to contains all the required dependencies for OpenDevin. - If the sandbox image is not found, it will be built. - """ - # OpenDevin's offcial sandbox already contains the required dependencies for OpenDevin. - if 'ghcr.io/opendevin/sandbox' in base_image: - return base_image - - new_image_name = _get_new_image_name(base_image) - - # Detect if the sandbox image is built - images = docker_client.images.list() - for image in images: - if new_image_name in image.tags: - logger.info('Found existing od_sandbox image, reuse:' + new_image_name) - return new_image_name - - # If the sandbox image is not found, build it - logger.info( - f'od_sandbox image is not found for {base_image}, will build: {new_image_name}' - ) - _build_sandbox_image(base_image, new_image_name, docker_client) - return new_image_name diff --git a/opendevin/runtime/utils/runtime_build.py b/opendevin/runtime/utils/runtime_build.py index b04e99d15e48..3f51a7f7ca8d 100644 --- a/opendevin/runtime/utils/runtime_build.py +++ b/opendevin/runtime/utils/runtime_build.py @@ -239,6 +239,7 @@ def build_runtime_image( extra_deps: str | None = None, docker_build_folder: str | None = None, dry_run: bool = False, + force_rebuild: bool = False, ) -> str: """Build the runtime image for the OpenDevin runtime. @@ -277,7 +278,10 @@ def build_runtime_image( # 2. If the exact hash is not found, we will FIRST try to re-build it # by leveraging the non-hash `generic_runtime_image_name` to save some time # from re-building the dependencies (e.g., poetry install, apt install) - elif _check_image_exists(generic_runtime_image_name, docker_client): + elif ( + _check_image_exists(generic_runtime_image_name, docker_client) + and not force_rebuild + ): logger.info( f'Cannot find matched hash for image [{hash_runtime_image_name}]\n' f'Will try to re-build it from latest [{generic_runtime_image_name}] image to potentially save ' @@ -319,6 +323,10 @@ def build_runtime_image( # 3. If the image is not found AND we cannot re-use the non-hash latest relavant image, # we will build it completely from scratch else: + if force_rebuild: + logger.info( + f'Force re-build: Will try to re-build image [{generic_runtime_image_name}] from scratch.\n' + ) cur_docker_build_folder = docker_build_folder or tempfile.mkdtemp() _new_from_scratch_hash = prep_docker_build_folder( cur_docker_build_folder, @@ -352,8 +360,11 @@ def build_runtime_image( if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--base_image', type=str, default='ubuntu:22.04') + parser.add_argument( + '--base_image', type=str, default='nikolaik/python-nodejs:python3.11-nodejs22' + ) parser.add_argument('--build_folder', type=str, default=None) + parser.add_argument('--force_rebuild', action='store_true', default=False) args = parser.parse_args() if args.build_folder is not None: @@ -373,6 +384,7 @@ def build_runtime_image( docker_client=docker.from_env(), docker_build_folder=temp_dir, dry_run=True, + force_rebuild=args.force_rebuild, ) _runtime_image_repo, runtime_image_hash_tag = runtime_image_hash_name.split( ':' diff --git a/opendevin/runtime/utils/runtime_templates/Dockerfile.j2 b/opendevin/runtime/utils/runtime_templates/Dockerfile.j2 index 6121e0e356e2..b7d04d246529 100644 --- a/opendevin/runtime/utils/runtime_templates/Dockerfile.j2 +++ b/opendevin/runtime/utils/runtime_templates/Dockerfile.j2 @@ -18,13 +18,6 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -# Install Python if not already installed -RUN if [ ! -e /usr/bin/python ]; then \ - apt-get update && \ - apt-get install -y python3 && \ - ln -s /usr/bin/python3 /usr/bin/python; \ -fi - # Create necessary directories RUN mkdir -p /opendevin && \ mkdir -p /opendevin/logs && \ diff --git a/tests/integration/regenerate.sh b/tests/integration/regenerate.sh index 016be2ca89fa..56253c5b96fd 100755 --- a/tests/integration/regenerate.sh +++ b/tests/integration/regenerate.sh @@ -59,7 +59,7 @@ mkdir -p $WORKSPACE_BASE TEST_RUNTIME="${TEST_RUNTIME:-eventstream}" # can be server or eventstream # TODO: set this as default after ServerRuntime is deprecated if [ "$TEST_RUNTIME" == "eventstream" ] && [ -z "$SANDBOX_CONTAINER_IMAGE" ]; then - SANDBOX_CONTAINER_IMAGE="ubuntu:22.04" + SANDBOX_CONTAINER_IMAGE="nikolaik/python-nodejs:python3.11-nodejs22" fi MAX_ITERATIONS=15 diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index acff8be7cc5e..301c1579071f 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -358,7 +358,7 @@ def test_defaults_dict_after_updates(default_config): assert defaults_after_updates['sandbox']['timeout']['default'] == 120 assert ( defaults_after_updates['sandbox']['container_image']['default'] - == 'ubuntu:22.04' + == 'nikolaik/python-nodejs:python3.11-nodejs22' ) assert defaults_after_updates == initial_defaults diff --git a/tests/unit/test_image_agnostic_util.py b/tests/unit/test_image_agnostic_util.py deleted file mode 100644 index d5a94bfe24ed..000000000000 --- a/tests/unit/test_image_agnostic_util.py +++ /dev/null @@ -1,51 +0,0 @@ -from unittest.mock import MagicMock, patch - -from opendevin.runtime.utils.image_agnostic import ( - _get_new_image_name, - generate_dockerfile, - get_od_sandbox_image, -) - - -def test_generate_dockerfile(): - base_image = 'debian:11' - dockerfile_content = generate_dockerfile(base_image) - assert base_image in dockerfile_content - assert ( - 'RUN apt update && apt install -y openssh-server wget sudo' - in dockerfile_content - ) - - -def test_get_new_image_name_legacy(): - # test non-eventstream runtime (sandbox-based) - base_image = 'debian:11' - new_image_name = _get_new_image_name(base_image) - assert new_image_name == 'od_sandbox:debian__11' - - base_image = 'ubuntu:22.04' - new_image_name = _get_new_image_name(base_image) - assert new_image_name == 'od_sandbox:ubuntu__22.04' - - base_image = 'ubuntu' - new_image_name = _get_new_image_name(base_image) - assert new_image_name == 'od_sandbox:ubuntu__latest' - - -@patch('opendevin.runtime.utils.image_agnostic._build_sandbox_image') -@patch('opendevin.runtime.utils.image_agnostic.docker.DockerClient') -def test_get_od_sandbox_image(mock_docker_client, mock_build_sandbox_image): - base_image = 'debian:11' - mock_docker_client.images.list.return_value = [ - MagicMock(tags=['od_sandbox:debian__11']) - ] - - image_name = get_od_sandbox_image(base_image, mock_docker_client) - assert image_name == 'od_sandbox:debian__11' - - mock_docker_client.images.list.return_value = [] - image_name = get_od_sandbox_image(base_image, mock_docker_client) - assert image_name == 'od_sandbox:debian__11' - mock_build_sandbox_image.assert_called_once_with( - base_image, 'od_sandbox:debian__11', mock_docker_client - ) diff --git a/tests/unit/test_runtime.py b/tests/unit/test_runtime.py index 9614f5adbf23..190998436c4d 100644 --- a/tests/unit/test_runtime.py +++ b/tests/unit/test_runtime.py @@ -83,7 +83,9 @@ def enable_auto_lint(request): return request.param -@pytest.fixture(scope='module', params=['ubuntu:22.04', 'debian:11']) +@pytest.fixture( + scope='module', params=['nikolaik/python-nodejs:python3.11-nodejs22', 'debian:11'] +) def container_image(request): time.sleep(1) return request.param @@ -127,7 +129,7 @@ async def _load_runtime( if 'od_runtime' not in cur_container_image and cur_container_image not in { 'xingyaoww/od-eval-miniwob:v1.0' }: # a special exception list - cur_container_image = 'ubuntu:22.04' + cur_container_image = 'nikolaik/python-nodejs:python3.11-nodejs22' logger.warning( f'`{config.sandbox.container_image}` is not an od_runtime image. Will use `{cur_container_image}` as the container image for testing.' ) @@ -1033,6 +1035,13 @@ async def test_bash_python_version(temp_dir, box_class): assert obs.exit_code == 0 # Should not error out + action = CmdRunAction(command='pip --version') + logger.info(action, extra={'msg_type': 'ACTION'}) + obs = await runtime.run_action(action) + logger.info(obs, extra={'msg_type': 'OBSERVATION'}) + assert obs.exit_code == 0 + # Should not error out + await runtime.close() await asyncio.sleep(1) diff --git a/tests/unit/test_runtime_build.py b/tests/unit/test_runtime_build.py index 027b257b7398..438355401ea9 100644 --- a/tests/unit/test_runtime_build.py +++ b/tests/unit/test_runtime_build.py @@ -62,7 +62,7 @@ def test_put_source_code_to_dir(temp_dir): def test_docker_build_folder(temp_dir): prep_docker_build_folder( temp_dir, - base_image='ubuntu:22.04', + base_image='nikolaik/python-nodejs:python3.11-nodejs22', skip_init=False, ) @@ -81,14 +81,14 @@ def test_docker_build_folder(temp_dir): def test_hash_folder_same(temp_dir): dir_hash_1 = prep_docker_build_folder( temp_dir, - base_image='ubuntu:22.04', + base_image='nikolaik/python-nodejs:python3.11-nodejs22', skip_init=False, ) with tempfile.TemporaryDirectory() as temp_dir_2: dir_hash_2 = prep_docker_build_folder( temp_dir_2, - base_image='ubuntu:22.04', + base_image='nikolaik/python-nodejs:python3.11-nodejs22', skip_init=False, ) assert dir_hash_1 == dir_hash_2 @@ -97,14 +97,14 @@ def test_hash_folder_same(temp_dir): def test_hash_folder_diff_init(temp_dir): dir_hash_1 = prep_docker_build_folder( temp_dir, - base_image='ubuntu:22.04', + base_image='nikolaik/python-nodejs:python3.11-nodejs22', skip_init=False, ) with tempfile.TemporaryDirectory() as temp_dir_2: dir_hash_2 = prep_docker_build_folder( temp_dir_2, - base_image='ubuntu:22.04', + base_image='nikolaik/python-nodejs:python3.11-nodejs22', skip_init=True, ) assert dir_hash_1 != dir_hash_2 @@ -113,7 +113,7 @@ def test_hash_folder_diff_init(temp_dir): def test_hash_folder_diff_image(temp_dir): dir_hash_1 = prep_docker_build_folder( temp_dir, - base_image='ubuntu:22.04', + base_image='nikolaik/python-nodejs:python3.11-nodejs22', skip_init=False, ) @@ -178,11 +178,12 @@ def test_get_runtime_image_repo_and_tag_eventstream(): and img_tag == f'{OD_VERSION}_image_debian_tag_11' ) - base_image = 'ubuntu:22.04' + base_image = 'nikolaik/python-nodejs:python3.11-nodejs22' img_repo, img_tag = get_runtime_image_repo_and_tag(base_image) assert ( img_repo == f'{RUNTIME_IMAGE_REPO}' - and img_tag == f'{OD_VERSION}_image_ubuntu_tag_22.04' + and img_tag + == f'{OD_VERSION}_image_nikolaik___python-nodejs_tag_python3.11-nodejs22' ) base_image = 'ubuntu' From 76a27e72bd93aa86e6328920a998c6e9b5c78c13 Mon Sep 17 00:00:00 2001 From: tobitege <10787084+tobitege@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:13:27 +0200 Subject: [PATCH 21/46] remove test_sandbox references from run-unit-tests.yml (#3326) --- .github/workflows/run-unit-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 0badedf9579c..fd3b9c23e0b6 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -113,7 +113,7 @@ jobs: - name: Build Environment run: make build - name: Run Tests - run: poetry run pytest --forked --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit -k "not test_sandbox.py and not test_runtime.py" + run: poetry run pytest --forked --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit -k "not test_runtime.py" - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 env: @@ -142,7 +142,7 @@ jobs: - name: Build Environment run: make build - name: Run Tests - run: poetry run pytest --forked --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit -k "not test_sandbox.py and not test_runtime.py" + run: poetry run pytest --forked --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit -k "not test_runtime.py" - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 env: From b4a7e27bfde36c0a4ee5e3ffb4004af3edfdf323 Mon Sep 17 00:00:00 2001 From: tofarr Date: Fri, 9 Aug 2024 14:48:31 -0600 Subject: [PATCH 22/46] chore Fix architecture diagram (#3141) * Fix architecture diagram * Updated Readme with permanent img * Fix path --------- Co-authored-by: Tim O'Farrell Co-authored-by: Tim O'Farrell Co-authored-by: tobitege <10787084+tobitege@users.noreply.github.com> --- .../img/system_architecture_overview.png | Bin 0 -> 175252 bytes opendevin/README.md | 6 +----- 2 files changed, 1 insertion(+), 5 deletions(-) create mode 100644 docs/static/img/system_architecture_overview.png diff --git a/docs/static/img/system_architecture_overview.png b/docs/static/img/system_architecture_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..a9174fc6ba725ef52f433988b5527c0622c30abc GIT binary patch literal 175252 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 B8u9=D literal 0 HcmV?d00001 diff --git a/opendevin/README.md b/opendevin/README.md index f977abbdeb10..11c52dcab2e8 100644 --- a/opendevin/README.md +++ b/opendevin/README.md @@ -3,11 +3,7 @@ This directory contains the core components of OpenDevin. This diagram provides an overview of the roles of each component and how they communicate and collaborate. - -
- OpenDevin System Architecture Diagram Jul 4 2024 -

OpenDevin System Architecture Diagram (July 4, 2024)

-
+![OpenDevin System Architecture Diagram (July 4, 2024)](../docs/static/img/system_architecture_overview.png) ## Classes The key classes in OpenDevin are: From e059407c7436da5cbfde10b85af634286c130db4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:39:06 -0700 Subject: [PATCH 23/46] chore(deps-dev): bump vite-tsconfig-paths in /frontend (#3303) Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.3.2 to 5.0.1. - [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases) - [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.3.2...v5.0.1) --- updated-dependencies: - dependency-name: vite-tsconfig-paths dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: tofarr Co-authored-by: tobitege <10787084+tobitege@users.noreply.github.com> Co-authored-by: Yufan Song <33971064+yufansong@users.noreply.github.com> --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a931aefb12e9..8e9382e4d607 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -65,7 +65,7 @@ "prettier": "^3.3.3", "tailwindcss": "^3.4.9", "typescript": "^5.5.4", - "vite-tsconfig-paths": "^4.3.2", + "vite-tsconfig-paths": "^5.0.1", "vitest": "^1.6.0" }, "engines": { @@ -12913,9 +12913,9 @@ } }, "node_modules/vite-tsconfig-paths": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", - "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.0.1.tgz", + "integrity": "sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==", "dev": true, "dependencies": { "debug": "^4.1.1", diff --git a/frontend/package.json b/frontend/package.json index 62e3778eeca6..f5134a233119 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -88,7 +88,7 @@ "prettier": "^3.3.3", "tailwindcss": "^3.4.9", "typescript": "^5.5.4", - "vite-tsconfig-paths": "^4.3.2", + "vite-tsconfig-paths": "^5.0.1", "vitest": "^1.6.0" }, "packageManager": "npm@10.5.0", From ace733cb1f499f23957f4ed42b721355833cd948 Mon Sep 17 00:00:00 2001 From: tofarr Date: Fri, 9 Aug 2024 17:33:35 -0600 Subject: [PATCH 24/46] Minor UI fix to explorer tree (#3328) Co-authored-by: Tim O'Farrell Co-authored-by: Graham Neubig --- frontend/src/components/file-explorer/ExplorerTree.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/file-explorer/ExplorerTree.tsx b/frontend/src/components/file-explorer/ExplorerTree.tsx index 8ab18bb2676e..32b014d9a613 100644 --- a/frontend/src/components/file-explorer/ExplorerTree.tsx +++ b/frontend/src/components/file-explorer/ExplorerTree.tsx @@ -18,7 +18,7 @@ function ExplorerTree({ files, defaultOpen = false }: ExplorerTreeProps) { ); } return ( -
+
{files.map((file) => ( ))} From e6fa5b5df057079ce6c23f505b17be3d65f368ca Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Sat, 10 Aug 2024 08:50:49 +0800 Subject: [PATCH 25/46] chore: remove unused plugin mixin (#3332) * remove unused plugin mixin * remove unused field * remove unused comments --- opendevin/runtime/plugins/__init__.py | 4 - .../runtime/plugins/agent_skills/__init__.py | 6 - .../runtime/plugins/agent_skills/setup.sh | 21 -- opendevin/runtime/plugins/jupyter/__init__.py | 11 - opendevin/runtime/plugins/jupyter/execute_cli | 4 - .../runtime/plugins/jupyter/execute_cli.py | 45 --- opendevin/runtime/plugins/jupyter/setup.sh | 87 ----- opendevin/runtime/plugins/mixin.py | 101 ------ opendevin/runtime/plugins/requirement.py | 5 - .../plugins/swe_agent_commands/__init__.py | 71 ---- .../_setup_cursor_mode_env.sh | 24 -- .../swe_agent_commands/_setup_default_env.sh | 18 -- .../plugins/swe_agent_commands/_split_string | 17 - .../swe_agent_commands/cursors_defaults.sh | 306 ------------------ .../cursors_edit_linting.sh | 100 ------ .../plugins/swe_agent_commands/defaults.sh | 195 ----------- .../swe_agent_commands/edit_linting.sh | 129 -------- .../swe_agent_commands/parse_commands.py | 62 ---- .../plugins/swe_agent_commands/search.sh | 155 --------- .../swe_agent_commands/setup_cursor_mode.sh | 19 -- .../swe_agent_commands/setup_default.sh | 19 -- opendevin/runtime/sandbox.py | 3 +- 22 files changed, 1 insertion(+), 1401 deletions(-) delete mode 100755 opendevin/runtime/plugins/agent_skills/setup.sh delete mode 100755 opendevin/runtime/plugins/jupyter/execute_cli delete mode 100755 opendevin/runtime/plugins/jupyter/execute_cli.py delete mode 100755 opendevin/runtime/plugins/jupyter/setup.sh delete mode 100644 opendevin/runtime/plugins/mixin.py delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/__init__.py delete mode 100755 opendevin/runtime/plugins/swe_agent_commands/_setup_cursor_mode_env.sh delete mode 100755 opendevin/runtime/plugins/swe_agent_commands/_setup_default_env.sh delete mode 100755 opendevin/runtime/plugins/swe_agent_commands/_split_string delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/cursors_defaults.sh delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/cursors_edit_linting.sh delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/defaults.sh delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/edit_linting.sh delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/parse_commands.py delete mode 100644 opendevin/runtime/plugins/swe_agent_commands/search.sh delete mode 100755 opendevin/runtime/plugins/swe_agent_commands/setup_cursor_mode.sh delete mode 100755 opendevin/runtime/plugins/swe_agent_commands/setup_default.sh diff --git a/opendevin/runtime/plugins/__init__.py b/opendevin/runtime/plugins/__init__.py index 9c4e01cc8a33..fac44a362544 100644 --- a/opendevin/runtime/plugins/__init__.py +++ b/opendevin/runtime/plugins/__init__.py @@ -1,19 +1,15 @@ # Requirements from .agent_skills import AgentSkillsPlugin, AgentSkillsRequirement from .jupyter import JupyterPlugin, JupyterRequirement -from .mixin import PluginMixin from .requirement import Plugin, PluginRequirement -from .swe_agent_commands import SWEAgentCommandsRequirement __all__ = [ 'Plugin', - 'PluginMixin', 'PluginRequirement', 'AgentSkillsRequirement', 'AgentSkillsPlugin', 'JupyterRequirement', 'JupyterPlugin', - 'SWEAgentCommandsRequirement', ] ALL_PLUGINS = { diff --git a/opendevin/runtime/plugins/agent_skills/__init__.py b/opendevin/runtime/plugins/agent_skills/__init__.py index 8fed5bdb05ba..e331e16096ae 100644 --- a/opendevin/runtime/plugins/agent_skills/__init__.py +++ b/opendevin/runtime/plugins/agent_skills/__init__.py @@ -1,4 +1,3 @@ -import os from dataclasses import dataclass from opendevin.runtime.plugins.agent_skills.agentskills import DOCUMENTATION @@ -8,11 +7,6 @@ @dataclass class AgentSkillsRequirement(PluginRequirement): name: str = 'agent_skills' - host_src: str = os.path.dirname( - os.path.abspath(__file__) - ) # The directory of this file (opendevin/runtime/plugins/agent_skills) - sandbox_dest: str = '/opendevin/plugins/' - bash_script_path: str = 'setup.sh' documentation: str = DOCUMENTATION diff --git a/opendevin/runtime/plugins/agent_skills/setup.sh b/opendevin/runtime/plugins/agent_skills/setup.sh deleted file mode 100755 index 53e5cfe7df65..000000000000 --- a/opendevin/runtime/plugins/agent_skills/setup.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -e - -OPENDEVIN_PYTHON_INTERPRETER=/opendevin/miniforge3/bin/python -# check if OPENDEVIN_PYTHON_INTERPRETER exists and it is usable -if [ -z "$OPENDEVIN_PYTHON_INTERPRETER" ] || [ ! -x "$OPENDEVIN_PYTHON_INTERPRETER" ]; then - echo "OPENDEVIN_PYTHON_INTERPRETER is not usable. Please pull the latest Docker image!" - exit 1 -fi - -# add agent_skills to PATH -echo 'export PATH=/opendevin/plugins/agent_skills:$PATH' >> ~/.bashrc - -# add agent_skills to PYTHONPATH -echo 'export PYTHONPATH=/opendevin/plugins/agent_skills:$PYTHONPATH' >> ~/.bashrc - -source ~/.bashrc - -$OPENDEVIN_PYTHON_INTERPRETER -m pip install flake8 python-docx PyPDF2 python-pptx pylatexenc openai opencv-python -$OPENDEVIN_PYTHON_INTERPRETER -m pip install diskcache==5.6.3 grep-ast==0.3.2 tree-sitter==0.21.3 tree-sitter-languages==1.10.2 diff --git a/opendevin/runtime/plugins/jupyter/__init__.py b/opendevin/runtime/plugins/jupyter/__init__.py index 74239efbee4d..f9c33bfa19cc 100644 --- a/opendevin/runtime/plugins/jupyter/__init__.py +++ b/opendevin/runtime/plugins/jupyter/__init__.py @@ -1,4 +1,3 @@ -import os import subprocess import time from dataclasses import dataclass @@ -15,16 +14,6 @@ @dataclass class JupyterRequirement(PluginRequirement): name: str = 'jupyter' - host_src: str = os.path.dirname( - os.path.abspath(__file__) - ) # The directory of this file (opendevin/runtime/plugins/jupyter) - sandbox_dest: str = '/opendevin/plugins/' - bash_script_path: str = 'setup.sh' - - # ================================================================ - # Plugin methods, which will ONLY be used in the runtime client - # running inside docker - # ================================================================ class JupyterPlugin(Plugin): diff --git a/opendevin/runtime/plugins/jupyter/execute_cli b/opendevin/runtime/plugins/jupyter/execute_cli deleted file mode 100755 index 9637290e3887..000000000000 --- a/opendevin/runtime/plugins/jupyter/execute_cli +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -# Run the Python script with the specified interpreter -export JUPYTER_PWD=$(pwd) -$OPENDEVIN_PYTHON_INTERPRETER /opendevin/plugins/jupyter/execute_cli.py diff --git a/opendevin/runtime/plugins/jupyter/execute_cli.py b/opendevin/runtime/plugins/jupyter/execute_cli.py deleted file mode 100755 index 0b77653e12b5..000000000000 --- a/opendevin/runtime/plugins/jupyter/execute_cli.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import sys -import time -import traceback - -import requests - -# Read the Python code from STDIN -code = sys.stdin.read() - - -def execute_code(code, print_output=True): - PORT = os.environ.get('JUPYTER_EXEC_SERVER_PORT') - POST_URL = f'http://localhost:{PORT}/execute' - - # Set the default kernel ID - kernel_id = 'default' - output = '' - for i in range(3): - try: - response = requests.post( - POST_URL, json={'kernel_id': kernel_id, 'code': code} - ) - output = response.text - if '500: Internal Server Error' not in output: - if print_output: - print(output) - break - except requests.exceptions.ConnectionError: - if i == 2: - traceback.print_exc() - time.sleep(2) - else: - if not output: - with open('/opendevin/logs/jupyter_execute_server.log', 'r') as f: - output = f.read() - print('Failed to connect to the Jupyter server', output) - - -if jupyter_pwd := os.environ.get('JUPYTER_PWD'): - execute_code( - f'import os\nos.environ["JUPYTER_PWD"] = "{jupyter_pwd}"\n', print_output=False - ) - -execute_code(code) diff --git a/opendevin/runtime/plugins/jupyter/setup.sh b/opendevin/runtime/plugins/jupyter/setup.sh deleted file mode 100755 index 765351880f11..000000000000 --- a/opendevin/runtime/plugins/jupyter/setup.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/bash - -set -e - -# Hardcoded to use the Python interpreter from the OpenDevin runtime client -OPENDEVIN_PYTHON_INTERPRETER=/opendevin/miniforge3/bin/python -# check if OPENDEVIN_PYTHON_INTERPRETER exists and it is usable -if [ -z "$OPENDEVIN_PYTHON_INTERPRETER" ] || [ ! -x "$OPENDEVIN_PYTHON_INTERPRETER" ]; then - echo "OPENDEVIN_PYTHON_INTERPRETER is not usable. Please pull the latest Docker image!" - exit 1 -fi - -# Install dependencies -$OPENDEVIN_PYTHON_INTERPRETER -m pip install jupyterlab notebook jupyter_kernel_gateway - -source ~/.bashrc -# ADD /opendevin/plugins to PATH to make `jupyter_cli` available -echo 'export PATH=$PATH:/opendevin/plugins/jupyter' >> ~/.bashrc -export PATH=/opendevin/plugins/jupyter:$PATH - -# Temporary add /opendevin/miniforge3/bin to PATH -# will not persist after the end of the script -# This fixes https://github.com/OpenDevin/OpenDevin/pull/2489#issuecomment-2223088169 -export PATH=/opendevin/miniforge3/bin:$PATH - -# if user name is `opendevin`, add '/home/opendevin/.local/bin' to PATH -if [ "$USER" = "opendevin" ]; then - echo 'export PATH=$PATH:/home/opendevin/.local/bin' >> ~/.bashrc - echo "export OPENDEVIN_PYTHON_INTERPRETER=$OPENDEVIN_PYTHON_INTERPRETER" >> ~/.bashrc - export PATH=$PATH:/home/opendevin/.local/bin - export PIP_CACHE_DIR=$HOME/.cache/pip -fi -# if user name is `root`, add '/root/.local/bin' to PATH -if [ "$USER" = "root" ]; then - echo 'export PATH=$PATH:/root/.local/bin' >> ~/.bashrc - echo "export OPENDEVIN_PYTHON_INTERPRETER=$OPENDEVIN_PYTHON_INTERPRETER" >> ~/.bashrc - export PATH=$PATH:/root/.local/bin - export PIP_CACHE_DIR=$HOME/.cache/pip - -fi - -# Run background process to start jupyter kernel gateway -# write a bash function that finds a free port -find_free_port() { - local start_port="${1:-20000}" - local end_port="${2:-65535}" - - for port in $(seq $start_port $end_port); do - if ! ss -tuln | awk '{print $5}' | grep -q ":$port$"; then - echo $port - return - fi - done - - echo "No free ports found in the range $start_port to $end_port" >&2 - return 1 -} - -export JUPYTER_GATEWAY_PORT=$(find_free_port 20000 30000) -$OPENDEVIN_PYTHON_INTERPRETER -m \ - jupyter kernelgateway --KernelGatewayApp.ip=0.0.0.0 --KernelGatewayApp.port=$JUPYTER_GATEWAY_PORT > /opendevin/logs/jupyter_kernel_gateway.log 2>&1 & - -export JUPYTER_GATEWAY_PID=$! -echo "export JUPYTER_GATEWAY_PID=$JUPYTER_GATEWAY_PID" >> ~/.bashrc -export JUPYTER_GATEWAY_KERNEL_ID="default" -echo "export JUPYTER_GATEWAY_KERNEL_ID=$JUPYTER_GATEWAY_KERNEL_ID" >> ~/.bashrc -echo "JupyterKernelGateway started with PID: $JUPYTER_GATEWAY_PID" - -# Start the jupyter_server -export JUPYTER_EXEC_SERVER_PORT=$(find_free_port 30000 40000) -echo "export JUPYTER_EXEC_SERVER_PORT=$JUPYTER_EXEC_SERVER_PORT" >> ~/.bashrc -$OPENDEVIN_PYTHON_INTERPRETER /opendevin/plugins/jupyter/execute_server.py > /opendevin/logs/jupyter_execute_server.log 2>&1 & -export JUPYTER_EXEC_SERVER_PID=$! -echo "export JUPYTER_EXEC_SERVER_PID=$JUPYTER_EXEC_SERVER_PID" >> ~/.bashrc -echo "Execution server started with PID: $JUPYTER_EXEC_SERVER_PID" - -# Wait until /opendevin/logs/jupyter_kernel_gateway.log contains "is available" -while ! grep -q "at" /opendevin/logs/jupyter_kernel_gateway.log; do - echo "Waiting for Jupyter kernel gateway to be available..." - sleep 1 -done -# Wait until /opendevin/logs/jupyter_execute_server.log contains "Jupyter kernel created for conversation" -while ! grep -q "kernel created" /opendevin/logs/jupyter_execute_server.log; do - echo "Waiting for Jupyter kernel to be created..." - sleep 1 -done -echo "Jupyter kernel ready." diff --git a/opendevin/runtime/plugins/mixin.py b/opendevin/runtime/plugins/mixin.py deleted file mode 100644 index d193d3303d93..000000000000 --- a/opendevin/runtime/plugins/mixin.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -from typing import Protocol - -from opendevin.core.logger import opendevin_logger as logger -from opendevin.core.schema import CancellableStream -from opendevin.runtime.plugins.requirement import PluginRequirement - - -class SandboxProtocol(Protocol): - # https://stackoverflow.com/questions/51930339/how-do-i-correctly-add-type-hints-to-mixin-classes - - @property - def initialize_plugins(self) -> bool: ... - - def execute( - self, cmd: str, stream: bool = False - ) -> tuple[int, str | CancellableStream]: ... - - def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): ... - - -def _source_bashrc(sandbox: SandboxProtocol): - exit_code, output = sandbox.execute( - 'source /opendevin/bash.bashrc && source ~/.bashrc' - ) - if exit_code != 0: - raise RuntimeError( - f'Failed to source /opendevin/bash.bashrc and ~/.bashrc with exit code {exit_code} and output: {output}' - ) - logger.info('Sourced /opendevin/bash.bashrc and ~/.bashrc successfully') - - -class PluginMixin: - """Mixin for Sandbox to support plugins.""" - - def init_plugins(self: SandboxProtocol, requirements: list[PluginRequirement]): - """Load a plugin into the sandbox.""" - if hasattr(self, 'plugin_initialized') and self.plugin_initialized: - return - - if self.initialize_plugins: - logger.info('Initializing plugins in the sandbox') - - # clean-up ~/.bashrc and touch ~/.bashrc - exit_code, output = self.execute('rm -f ~/.bashrc && touch ~/.bashrc') - if exit_code != 0: - logger.warning( - f'Failed to clean-up ~/.bashrc with exit code {exit_code} and output: {output}' - ) - - for requirement in requirements: - # source bashrc file when plugin loads - _source_bashrc(self) - - # copy over the files - self.copy_to( - requirement.host_src, requirement.sandbox_dest, recursive=True - ) - logger.info( - f'Copied files from [{requirement.host_src}] to [{requirement.sandbox_dest}] inside sandbox.' - ) - - # Execute the bash script - abs_path_to_bash_script = os.path.join( - requirement.sandbox_dest, - requirement.name, - requirement.bash_script_path, - ) - logger.info( - f'Initializing plugin [{requirement.name}] by executing [{abs_path_to_bash_script}] in the sandbox.' - ) - exit_code, output = self.execute(abs_path_to_bash_script, stream=True) - if isinstance(output, CancellableStream): - total_output = '' - for line in output: - # Removes any trailing whitespace, including \n and \r\n - line = line.rstrip() - # logger.debug(line) - # Avoid text from lines running into each other - total_output += line + ' ' - _exit_code = output.exit_code() - output.close() - if _exit_code != 0: - raise RuntimeError( - f'Failed to initialize plugin {requirement.name} with exit code {_exit_code} and output: {total_output.strip()}' - ) - logger.debug(f'Output: {total_output.strip()}') - else: - if exit_code != 0: - raise RuntimeError( - f'Failed to initialize plugin {requirement.name} with exit code {exit_code} and output: {output}' - ) - logger.debug(f'Output: {output}') - logger.info(f'Plugin {requirement.name} initialized successfully') - else: - logger.info('Skipping plugin initialization in the sandbox') - - if len(requirements) > 0: - _source_bashrc(self) - - self.plugin_initialized = True diff --git a/opendevin/runtime/plugins/requirement.py b/opendevin/runtime/plugins/requirement.py index 14399061e61a..b6713fe2a3a5 100644 --- a/opendevin/runtime/plugins/requirement.py +++ b/opendevin/runtime/plugins/requirement.py @@ -29,8 +29,3 @@ class PluginRequirement: """Requirement for a plugin.""" name: str - # FOLDER/FILES to be copied to the sandbox - host_src: str - sandbox_dest: str - # NOTE: bash_script_path should be relative to the `sandbox_dest` path - bash_script_path: str diff --git a/opendevin/runtime/plugins/swe_agent_commands/__init__.py b/opendevin/runtime/plugins/swe_agent_commands/__init__.py deleted file mode 100644 index d904190aa250..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/__init__.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -from dataclasses import dataclass, field - -from opendevin.runtime.plugins.requirement import PluginRequirement -from opendevin.runtime.plugins.swe_agent_commands.parse_commands import ( - parse_command_file, -) - - -def _resolve_to_cur_dir(filename): - return os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) - - -def check_and_parse_command_file(filepath) -> str: - if filepath is None: - raise FileNotFoundError(f'File not found: {filepath}') - return parse_command_file(filepath) - - -DEFAULT_SCRIPT_FILEPATHS = [ - _resolve_to_cur_dir('defaults.sh'), - _resolve_to_cur_dir('search.sh'), - _resolve_to_cur_dir('edit_linting.sh'), -] -DEFAULT_DOCUMENTATION = ''.join( - [ - check_and_parse_command_file(filepath) - for filepath in DEFAULT_SCRIPT_FILEPATHS - if filepath is not None - ] -) - - -@dataclass -class SWEAgentCommandsRequirement(PluginRequirement): - name: str = 'swe_agent_commands' - host_src: str = os.path.dirname(os.path.abspath(__file__)) - sandbox_dest: str = '/opendevin/plugins/' - bash_script_path: str = 'setup_default.sh' - - scripts_filepaths: list[str | None] = field( - default_factory=lambda: DEFAULT_SCRIPT_FILEPATHS - ) - documentation: str = DEFAULT_DOCUMENTATION - - -CURSOR_SCRIPT_FILEPATHS = [ - _resolve_to_cur_dir('cursors_defaults.sh'), - _resolve_to_cur_dir('cursors_edit_linting.sh'), - _resolve_to_cur_dir('search.sh'), -] -CURSOR_DOCUMENTATION = ''.join( - [ - check_and_parse_command_file(filepath) - for filepath in CURSOR_SCRIPT_FILEPATHS - if filepath is not None - ] -) - - -@dataclass -class SWEAgentCursorCommandsRequirement(PluginRequirement): - name: str = 'swe_agent_commands' - host_src: str = os.path.dirname(os.path.abspath(__file__)) - sandbox_dest: str = '/opendevin/plugins/swe_agent_commands' - bash_script_path: str = 'setup_cursor_mode.sh' - - scripts_filepaths: list[str | None] = field( - default_factory=lambda: CURSOR_SCRIPT_FILEPATHS - ) - documentation: str = CURSOR_DOCUMENTATION diff --git a/opendevin/runtime/plugins/swe_agent_commands/_setup_cursor_mode_env.sh b/opendevin/runtime/plugins/swe_agent_commands/_setup_cursor_mode_env.sh deleted file mode 100755 index 3f4e8bf9e020..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/_setup_cursor_mode_env.sh +++ /dev/null @@ -1,24 +0,0 @@ -# Cursor Mode from SWE-Bench -# https://github.com/princeton-nlp/SWE-agent/blob/ca54d5556b9db4f4f2be21f09530ce69a72c0305/config/configs/default_sys-env_cursors_window100-detailed_cmd_format-last_5_history-1_demos.yaml -export WINDOW=200; -export OVERLAP=2; -export CURRENT_LINE=0; -export CURRENT_FILE=''; -export SEARCH_RESULTS=(); -export SEARCH_FILES=(); -export SEARCH_INDEX=0; -export START_INDEX=0; -export END_INDEX=0; -export START_CURSOR=0; -export END_CURSOR=0; -export START_CURSOR_MARK='"<<<<< START CURSOR >>>>>"'; # these have to use double quotes -export END_CURSOR_MARK='"<<<<< END CURSOR >>>>>"'; # these have to use double quotes - -state() { - local working_dir="$PWD"; - if [ -z $CURRENT_FILE ]; then - echo '{"open_file": "n/a", "working_dir": "'$working_dir'"}'; - else - echo '{"open_file": "'$(realpath $CURRENT_FILE)'", "working_dir": "'$working_dir'"}'; - fi -}; diff --git a/opendevin/runtime/plugins/swe_agent_commands/_setup_default_env.sh b/opendevin/runtime/plugins/swe_agent_commands/_setup_default_env.sh deleted file mode 100755 index fc0dbad7b870..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/_setup_default_env.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Default Mode from SWE-Bench -# https://github.com/princeton-nlp/SWE-agent/blob/ca54d5556b9db4f4f2be21f09530ce69a72c0305/config/configs/default_sys-env_window100-detailed_cmd_format-last_5_history-1_demos.yaml -export WINDOW=100; -export OVERLAP=2; -export CURRENT_LINE=0; -export CURRENT_FILE=''; -export SEARCH_RESULTS=(); -export SEARCH_FILES=(); -export SEARCH_INDEX=0; - -state() { - local working_dir="$PWD"; - if [ -z $CURRENT_FILE ]; then - echo '{"open_file": "n/a", "working_dir": "'$working_dir'"}'; - else - echo '{"open_file": "'$(realpath $CURRENT_FILE)'", "working_dir": "'$working_dir'"}'; - fi -}; diff --git a/opendevin/runtime/plugins/swe_agent_commands/_split_string b/opendevin/runtime/plugins/swe_agent_commands/_split_string deleted file mode 100755 index 7525fdd2419e..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/_split_string +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python3 -import sys - - -def print_flake8_output(input_string, show_line_numbers=False): - for value in input_string.split('\n'): - parts = value.split() - if not show_line_numbers: - print(f"- {' '.join(parts[1:])}") - else: - line_nums = ':'.join(parts[0].split(':')[1:]) - print(f"- {line_nums} {' '.join(parts[1:])}") - - -if __name__ == '__main__': - lint_output = sys.argv[1] - print_flake8_output(lint_output) diff --git a/opendevin/runtime/plugins/swe_agent_commands/cursors_defaults.sh b/opendevin/runtime/plugins/swe_agent_commands/cursors_defaults.sh deleted file mode 100644 index 561900371b1e..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/cursors_defaults.sh +++ /dev/null @@ -1,306 +0,0 @@ -_reset_cursors() { - export START_CURSOR=1 - export END_CURSOR=1 -} - -_constrain_cursors() { - # constrain the cursors to be within the bounds of the file [0, total_lines+1] - local total_lines=$(awk 'END {print NR}' "$CURRENT_FILE") - total_lines=$((total_lines < 1 ? 1 : total_lines)) # if the file is empty, set total_lines to 1 - local start_line=$((CURRENT_LINE - WINDOW / 2)) - local end_line=$((CURRENT_LINE + WINDOW / 2)) - start_line=$((start_line < 1 ? 1 : start_line)) - end_line=$((end_line > total_lines ? total_lines : end_line)) - local warning_string="" - if [ "$START_CURSOR" -lt "$start_line" ]; then - warning_string+="START_CURSOR moved to $start_line\n" - START_CURSOR=$start_line - elif [ "$START_CURSOR" -gt "$end_line" ]; then - START_CURSOR=$end_line - warning_string+="START_CURSOR moved to $end_line\n" - fi - if [ "$END_CURSOR" -lt "$start_line" ]; then - warning_string+="END_CURSOR moved to $start_line\n" - END_CURSOR=$start_line - elif [ "$END_CURSOR" -gt "$end_line" ]; then - warning_string+="END_CURSOR moved to $end_line\n" - END_CURSOR=$end_line - fi - export START_CURSOR END_CURSOR - echo "$warning_string" - echo $START_CURSOR $END_CURSOR -} - -_print() { - local cursor_warning=$(_constrain_cursors) - local cursor_values=$(echo "$cursor_warning" | tail -n 1) - cursor_warning=$(echo "$cursor_warning" | head -n -1) - export START_CURSOR=$(echo "$cursor_values" | awk '{print $1}') - export END_CURSOR=$(echo "$cursor_values" | awk '{print $2}') - local total_lines=$(awk 'END {print NR}' $CURRENT_FILE) - echo "[File: $(realpath "$CURRENT_FILE") ($total_lines lines total)]" - local start_line=$((CURRENT_LINE - WINDOW / 2)) - local end_line=$((CURRENT_LINE + WINDOW / 2)) - start_line=$((start_line < 1 ? 1 : start_line)) - end_line=$((end_line > total_lines ? total_lines : end_line)) - local lines=() - local i=0 - while IFS= read -r line; do - lines[i++]="$line" - done < <(awk -v start="$start_line" -v end="$end_line" 'NR>=start && NR<=end {print}' "$CURRENT_FILE") - local num_lines=${#lines[@]} - if [ $start_line -gt 1 ]; then - echo "($((start_line - 1)) more lines above)" - fi - for ((i=0; i -# docstring: sets the start and end cursors to the given line numbers -# arguments: -# start_line: -# type: integer -# description: the line number to set the start cursor to -# required: true -# end_line: -# type: integer -# description: the line number to set the end cursor to -# required: true -set_cursors() { - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - if [ $# -lt 2 ] - then - echo "Usage: set_cursors " - return - fi - local start_line=$1 - local end_line=$2 - local re='^[0-9]+$' - if ! [[ $start_line =~ $re ]] - then - echo "Usage: set_cursors " - echo "Error: start_line must be a number" - return - fi - if ! [[ $end_line =~ $re ]] - then - echo "Usage: set_cursors " - echo "Error: end_line must be a number" - return - fi - if [ $start_line -gt $end_line ] - then - echo "Usage: set_cursors " - echo "Error: start_line must be less than or equal to end_line" - return - fi - export START_CURSOR=$start_line - export END_CURSOR=$end_line - _print -} - -# @yaml -# signature: open [] -# docstring: opens the file at the given path in the editor. If line_number is provided, the window will be centered on that line -# arguments: -# path: -# type: string -# description: the path to the file to open -# required: true -# line_number: -# type: integer -# description: the line number to move the window to (if not provided, the window will start at the top of the file) -# required: false -open() { - if [ -z "$1" ] - then - echo "Usage: open " - return - fi - # Check if the second argument is provided - if [ -n "$2" ]; then - # Check if the provided argument is a valid number - if ! [[ $2 =~ ^[0-9]+$ ]]; then - echo "Usage: open []" - echo "Error: must be a number" - return # Exit if the line number is not valid - fi - local max_line=$(awk 'END {print NR}' $1) - if [ $2 -gt $max_line ]; then - echo "Warning: ($2) is greater than the number of lines in the file ($max_line)" - echo "Warning: Setting to $max_line" - local line_number=$(jq -n "$max_line") # Set line number to max if greater than max - elif [ $2 -lt 1 ]; then - echo "Warning: ($2) is less than 1" - echo "Warning: Setting to 1" - local line_number=$(jq -n "1") # Set line number to 1 if less than 1 - else - local line_number=$(jq -n "$2") # Set line number if valid - fi - else - local line_number=$(jq -n "$WINDOW/2") # Set default line number if not provided - fi - - if [ -f "$1" ]; then - export CURRENT_FILE=$(realpath $1) - export CURRENT_LINE=$line_number - _constrain_line - _print - else - echo "File $1 not found" - fi -} - -# @yaml -# signature: scroll_down -# docstring: moves the window down {WINDOW} lines -scroll_down() { - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - export CURRENT_LINE=$(jq -n "$CURRENT_LINE + $WINDOW - $OVERLAP") - _constrain_line - _print -} - -# @yaml -# signature: scroll_up -# docstring: moves the window up {WINDOW} lines -scroll_up() { - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - export CURRENT_LINE=$(jq -n "$CURRENT_LINE - $WINDOW + $OVERLAP") - _constrain_line - _print -} - -# @yaml -# signature: goto -# docstring: moves the window to show -# arguments: -# line_number: -# type: integer -# description: the line number to move the window to -# required: true -goto() { - if [ $# -gt 1 ]; then - echo "goto allows only one line number at a time." - return - fi - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - if [ -z "$1" ] - then - echo "Usage: goto " - return - fi - if ! [[ $1 =~ ^[0-9]+$ ]] - then - echo "Usage: goto " - echo "Error: must be a number" - return - fi - local max_line=$(awk 'END {print NR}' $CURRENT_FILE) - if [ $1 -gt $max_line ] - then - echo "Error: must be less than or equal to $max_line" - return - fi - local OFFSET=$(jq -n "$WINDOW/6" | jq 'floor') - export CURRENT_LINE=$(jq -n "[$1 + $WINDOW/2 - $OFFSET, 1] | max | floor") - _constrain_line - _print -} - -# @yaml -# signature: create -# docstring: creates and opens a new file with the given name -# arguments: -# filename: -# type: string -# description: the name of the file to create -# required: true -create() { - if [ -z "$1" ]; then - echo "Usage: create " - return - fi - - # Check if the file already exists - if [ -e "$1" ]; then - echo "Error: File '$1' already exists." - open "$1" - return - fi - - # Create the file an empty new line - printf "\n" > "$1" - # Use the existing open command to open the created file - open "$1" -} - -# @yaml -# signature: submit -# docstring: submits your current code and terminates the session -submit() { - cd $ROOT - - # Check if the patch file exists and is non-empty - if [ -s "$SWE_CMD_WORK_DIR/test.patch" ]; then - # Apply the patch in reverse - git apply -R < "$SWE_CMD_WORK_DIR/test.patch" - fi - - git add -A - git diff --cached > model.patch - echo "<>" -} diff --git a/opendevin/runtime/plugins/swe_agent_commands/cursors_edit_linting.sh b/opendevin/runtime/plugins/swe_agent_commands/cursors_edit_linting.sh deleted file mode 100644 index a11f82f5aadd..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/cursors_edit_linting.sh +++ /dev/null @@ -1,100 +0,0 @@ -# @yaml -# signature: |- -# edit < -# EOF -# docstring: replaces *all* of the text between the START CURSOR and the END CURSOR with the replacement_text. The replacement text is delineated using heredoc syntax. All of the will be entered, so make sure your indentation is formatted properly. To enter text at the beginning of the file, set START CURSOR and END CURSOR to 0. Use set_cursors to move the cursors around. Python files will be checked for syntax errors after the edit. -# arguments: -# replacement_text: -# type: string -# description: the text to replace the current selection with -# required: true -edit() { - if [ -z "$CURRENT_FILE" ] - then - echo 'No file is opened. Use the `open` command first.' - return - fi - local start_line=$((START_CURSOR - 1)) - start_line=$((start_line < 0 ? 0 : start_line)) - local end_line=$((END_CURSOR)) - end_line=$((end_line < 0 ? 0 : end_line)) - - local replacement=() - while IFS= read -r line - do - replacement+=("$line") - done - - local num_lines=${#replacement[@]} - # Create a backup of the current file - cp "$CURRENT_FILE" "$SWE_CMD_WORK_DIR/$(basename "$CURRENT_FILE")_backup" - # Read the file line by line into an array - mapfile -t lines < "$CURRENT_FILE" - local new_lines=("${lines[@]:0:$start_line}" "${replacement[@]}" "${lines[@]:$((end_line))}") - # Write the new stuff directly back into the original file - printf "%s\n" "${new_lines[@]}" >| "$CURRENT_FILE" - # Run linter if enabled - if [[ $CURRENT_FILE == *.py && -n "$ENABLE_AUTO_LINT" ]]; then - lint_output=$(flake8 --isolated --select=F821,F822,F831,E111,E112,E113,E999,E902 "$CURRENT_FILE" 2>&1) - else - # do nothing - lint_output="" - fi - # if there is no output, then the file is good - if [ -z "$lint_output" ]; then - _constrain_line - # set to START + num_lines - 1, unless num_lines is 0, then set to START - export END_CURSOR=$((num_lines == 0 ? START_CURSOR : START_CURSOR + num_lines - 1)) - export START_CURSOR=$START_CURSOR - _print - echo "File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary." - else - echo "Your proposed edit has introduced new syntax error(s). Please understand the fixes and retry your edit command." - echo "" - echo "ERRORS:" - _split_string "$lint_output" - echo "" - - # Save original values - original_current_line=$CURRENT_LINE - original_window=$WINDOW - original_end_cursor=$END_CURSOR - - # Update values - export CURRENT_LINE=$(( (num_lines / 2) + start_line )) # Set to "center" of edit - export WINDOW=$((num_lines + 10)) # Show +/- 5 lines around edit - export END_CURSOR=$((num_lines == 0 ? START_CURSOR : START_CURSOR + num_lines - 1)) - - echo "This is how your edit would have looked if applied" - echo "-------------------------------------------------" - _constrain_line - _print - echo "-------------------------------------------------" - echo "" - - # Restoring CURRENT_FILE to original contents. - cp "$SWE_CMD_WORK_DIR/$(basename "$CURRENT_FILE")_backup" "$CURRENT_FILE" - - export CURRENT_LINE=$(( ((end_line - start_line) / 2) + start_line )) # Set to "center" of edit - export WINDOW=$((end_line - start_line + 10)) - export END_CURSOR=$original_end_cursor - - echo "This is the original code before your edit" - echo "-------------------------------------------------" - _constrain_line - _print - echo "-------------------------------------------------" - - # Restore original values - export CURRENT_LINE=$original_current_line - export WINDOW=$original_window - export END_CURSOR=$original_end_cursor - - echo "Your changes have NOT been applied. Please fix your edit command and try again." - echo "You either need to 1) Specify the correct start/end line arguments or 2) Correct your edit code." - echo "DO NOT re-run the same failed edit command. Running it again will lead to the same error." - fi - # Remove backup file - rm -f "$SWE_CMD_WORK_DIR/$(basename "$CURRENT_FILE")_backup" -} diff --git a/opendevin/runtime/plugins/swe_agent_commands/defaults.sh b/opendevin/runtime/plugins/swe_agent_commands/defaults.sh deleted file mode 100644 index 706368d72c5d..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/defaults.sh +++ /dev/null @@ -1,195 +0,0 @@ -_print() { - local total_lines=$(awk 'END {print NR}' $CURRENT_FILE) - echo "[File: $(realpath $CURRENT_FILE) ($total_lines lines total)]" - lines_above=$(jq -n "$CURRENT_LINE - $WINDOW/2" | jq '[0, .] | max | floor') - lines_below=$(jq -n "$total_lines - $CURRENT_LINE - $WINDOW/2" | jq '[0, .] | max | round') - if [ $lines_above -gt 0 ]; then - echo "($lines_above more lines above)" - fi - cat $CURRENT_FILE | grep -n $ | head -n $(jq -n "[$CURRENT_LINE + $WINDOW/2, $WINDOW/2] | max | floor") | tail -n $(jq -n "$WINDOW") - if [ $lines_below -gt 0 ]; then - echo "($lines_below more lines below)" - fi -} - -_constrain_line() { - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - local max_line=$(awk 'END {print NR}' $CURRENT_FILE) - local half_window=$(jq -n "$WINDOW/2" | jq 'floor') - export CURRENT_LINE=$(jq -n "[$CURRENT_LINE, $max_line - $half_window] | min") - export CURRENT_LINE=$(jq -n "[$CURRENT_LINE, $half_window] | max") -} - -# @yaml -# signature: open [] -# docstring: opens the file at the given path in the editor. If line_number is provided, the window will be move to include that line -# arguments: -# path: -# type: string -# description: the path to the file to open -# required: true -# line_number: -# type: integer -# description: the line number to move the window to (if not provided, the window will start at the top of the file) -# required: false -open() { - if [ -z "$1" ] - then - echo "Usage: open " - return - fi - # Check if the second argument is provided - if [ -n "$2" ]; then - # Check if the provided argument is a valid number - if ! [[ $2 =~ ^[0-9]+$ ]]; then - echo "Usage: open []" - echo "Error: must be a number" - return # Exit if the line number is not valid - fi - local max_line=$(awk 'END {print NR}' $1) - if [ $2 -gt $max_line ]; then - echo "Warning: ($2) is greater than the number of lines in the file ($max_line)" - echo "Warning: Setting to $max_line" - local line_number=$(jq -n "$max_line") # Set line number to max if greater than max - elif [ $2 -lt 1 ]; then - echo "Warning: ($2) is less than 1" - echo "Warning: Setting to 1" - local line_number=$(jq -n "1") # Set line number to 1 if less than 1 - else - local OFFSET=$(jq -n "$WINDOW/6" | jq 'floor') - local line_number=$(jq -n "[$2 + $WINDOW/2 - $OFFSET, 1] | max | floor") - fi - else - local line_number=$(jq -n "$WINDOW/2") # Set default line number if not provided - fi - - if [ -f "$1" ]; then - export CURRENT_FILE=$(realpath $1) - export CURRENT_LINE=$line_number - _constrain_line - _print - elif [ -d "$1" ]; then - echo "Error: $1 is a directory. You can only open files. Use cd or ls to navigate directories." - else - echo "File $1 not found" - fi -} - -# @yaml -# signature: goto -# docstring: moves the window to show -# arguments: -# line_number: -# type: integer -# description: the line number to move the window to -# required: true -goto() { - if [ $# -gt 1 ]; then - echo "goto allows only one line number at a time." - return - fi - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - if [ -z "$1" ] - then - echo "Usage: goto " - return - fi - if ! [[ $1 =~ ^[0-9]+$ ]] - then - echo "Usage: goto " - echo "Error: must be a number" - return - fi - local max_line=$(awk 'END {print NR}' $CURRENT_FILE) - if [ $1 -gt $max_line ] - then - echo "Error: must be less than or equal to $max_line" - return - fi - local OFFSET=$(jq -n "$WINDOW/6" | jq 'floor') - export CURRENT_LINE=$(jq -n "[$1 + $WINDOW/2 - $OFFSET, 1] | max | floor") - _constrain_line - _print -} - -# @yaml -# signature: scroll_down -# docstring: moves the window down {WINDOW} lines -scroll_down() { - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - export CURRENT_LINE=$(jq -n "$CURRENT_LINE + $WINDOW - $OVERLAP") - _constrain_line - _print -} - -# @yaml -# signature: scroll_up -# docstring: moves the window down {WINDOW} lines -scroll_up() { - if [ -z "$CURRENT_FILE" ] - then - echo "No file open. Use the open command first." - return - fi - export CURRENT_LINE=$(jq -n "$CURRENT_LINE - $WINDOW + $OVERLAP") - _constrain_line - _print -} - -# @yaml -# signature: create -# docstring: creates and opens a new file with the given name -# arguments: -# filename: -# type: string -# description: the name of the file to create -# required: true -create() { - if [ -z "$1" ]; then - echo "Usage: create " - return - fi - - # Check if the file already exists - if [ -e "$1" ]; then - echo "Error: File '$1' already exists." - open "$1" - return - fi - - # Create the file an empty new line - printf "\n" > "$1" - # Use the existing open command to open the created file - open "$1" -} - -# @yaml -# signature: submit -# docstring: submits your current code and terminates the session -submit() { - cd $ROOT - - # Check if the patch file exists and is non-empty - if [ -s "$SWE_CMD_WORK_DIR/test.patch" ]; then - # Apply the patch in reverse - git apply -R < "$SWE_CMD_WORK_DIR/test.patch" - fi - - git add -A - git diff --cached > model.patch - echo "<>" -} diff --git a/opendevin/runtime/plugins/swe_agent_commands/edit_linting.sh b/opendevin/runtime/plugins/swe_agent_commands/edit_linting.sh deleted file mode 100644 index 8341c3cd8c8e..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/edit_linting.sh +++ /dev/null @@ -1,129 +0,0 @@ -# @yaml -# signature: |- -# edit : < -# EOF -# docstring: replaces lines through (inclusive) with the given text in the open file. The replacement text is delineated using heredoc syntax. All of the 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. Remember, the file must be open before editing. -# arguments: -# start_line: -# type: integer -# description: the line number to start the edit at -# required: true -# end_line: -# type: integer -# description: the line number to end the edit at (inclusive) -# required: true -# replacement_text: -# type: string -# description: the text to replace the current selection with -# required: true -edit() { - if [ -z "$CURRENT_FILE" ] - then - echo 'No file open. Use the `open` command first.' - return - fi - - local start_line="$(echo $1: | cut -d: -f1)" - local end_line="$(echo $1: | cut -d: -f2)" - - if [ -z "$start_line" ] || [ -z "$end_line" ] - then - echo "Usage: edit :" - return - fi - - local re='^[0-9]+$' - if ! [[ $start_line =~ $re ]]; then - echo "Usage: edit :" - echo "Error: start_line must be a number" - return - fi - if ! [[ $end_line =~ $re ]]; then - echo "Usage: edit :" - echo "Error: end_line must be a number" - return - fi - - # Bash array starts at 0, so let's adjust - local start_line=$((start_line - 1)) - local end_line=$((end_line)) - - local line_count=0 - local replacement=() - while IFS= read -r line - do - replacement+=("$line") - ((line_count++)) - done - - # Create a backup of the current file - cp "$CURRENT_FILE" "$SWE_CMD_WORK_DIR/$(basename "$CURRENT_FILE")_backup" - - # Read the file line by line into an array - mapfile -t lines < "$CURRENT_FILE" - local new_lines=("${lines[@]:0:$start_line}" "${replacement[@]}" "${lines[@]:$((end_line))}") - # Write the new stuff directly back into the original file - printf "%s\n" "${new_lines[@]}" >| "$CURRENT_FILE" - - # Run linter if enabled - if [[ $CURRENT_FILE == *.py && -n "$ENABLE_AUTO_LINT" ]]; then - lint_output=$(flake8 --isolated --select=F821,F822,F831,E111,E112,E113,E999,E902 "$CURRENT_FILE" 2>&1) - else - # do nothing - lint_output="" - fi - - # if there is no output, then the file is good - if [ -z "$lint_output" ]; then - export CURRENT_LINE=$start_line - _constrain_line - _print - - echo "File updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary." - else - echo "Your proposed edit has introduced new syntax error(s). Please understand the fixes and retry your edit command." - echo "" - echo "ERRORS:" - _split_string "$lint_output" - echo "" - - # Save original values - original_current_line=$CURRENT_LINE - original_window=$WINDOW - - # Update values - export CURRENT_LINE=$(( (line_count / 2) + start_line )) # Set to "center" of edit - export WINDOW=$((line_count + 10)) # Show +/- 5 lines around edit - - echo "This is how your edit would have looked if applied" - echo "-------------------------------------------------" - _constrain_line - _print - echo "-------------------------------------------------" - echo "" - - # Restoring CURRENT_FILE to original contents. - cp "$SWE_CMD_WORK_DIR/$(basename "$CURRENT_FILE")_backup" "$CURRENT_FILE" - - export CURRENT_LINE=$(( ((end_line - start_line + 1) / 2) + start_line )) - export WINDOW=$((end_line - start_line + 10)) - - echo "This is the original code before your edit" - echo "-------------------------------------------------" - _constrain_line - _print - echo "-------------------------------------------------" - - # Restore original values - export CURRENT_LINE=$original_current_line - export WINDOW=$original_window - - echo "Your changes have NOT been applied. Please fix your edit command and try again." - echo "You either need to 1) Specify the correct start/end line arguments or 2) Correct your edit code." - echo "DO NOT re-run the same failed edit command. Running it again will lead to the same error." - fi - - # Remove backup file - rm -f "$SWE_CMD_WORK_DIR/$(basename "$CURRENT_FILE")_backup" -} diff --git a/opendevin/runtime/plugins/swe_agent_commands/parse_commands.py b/opendevin/runtime/plugins/swe_agent_commands/parse_commands.py deleted file mode 100644 index 49aa9489646c..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/parse_commands.py +++ /dev/null @@ -1,62 +0,0 @@ -from dataclasses import dataclass - -import yaml - - -@dataclass() -class Command: - name: str - docstring: str | None = None - signature: str | None = None - - -def parse_command_file(filepath: str) -> str: - content = open(filepath, 'r').read() - lines = content.split('\n') - commands: list[Command] = [] - idx = 0 - docs: list[str] = [] - while idx < len(lines): - line = lines[idx] - idx += 1 - if line.startswith('# '): - docs.append(line[2:]) - elif line.strip().endswith('() {'): - name = line.split()[0][:-2] - while lines[idx].strip() != '}': - idx += 1 - docstring, signature = None, name - docs_dict = yaml.safe_load('\n'.join(docs).replace('@yaml', '')) - if docs_dict is not None: - docstring = docs_dict.get('docstring') - arguments = docs_dict.get('arguments', None) - if 'signature' in docs_dict: - signature = docs_dict['signature'] - else: - if arguments is not None: - for param, settings in arguments.items(): - if 'required' in settings: - signature += f' <{param}>' - else: - signature += f' [<{param}>]' - command = Command(name, docstring, signature) - commands.append(command) - docs = [] - function_docs = '' - for cmd in commands: - if cmd.docstring is not None: - function_docs += f'{cmd.signature or cmd.name} - {cmd.docstring}\n' - return function_docs - - -if __name__ == '__main__': - import sys - - if len(sys.argv) < 2: - print('Usage: python parse_commands.py ') - sys.exit(1) - filepath = sys.argv[1] - filepaths = filepath.split(',') - for filepath in filepaths: - docs = parse_command_file(filepath) - print(docs) diff --git a/opendevin/runtime/plugins/swe_agent_commands/search.sh b/opendevin/runtime/plugins/swe_agent_commands/search.sh deleted file mode 100644 index c09566bdcb1c..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/search.sh +++ /dev/null @@ -1,155 +0,0 @@ -# @yaml -# signature: search_dir [] -# docstring: searches for search_term in all files in dir. If dir is not provided, searches in the current directory -# arguments: -# search_term: -# type: string -# description: the term to search for -# required: true -# dir: -# type: string -# description: the directory to search in (if not provided, searches in the current directory) -# required: false -search_dir() { - if [ $# -eq 1 ]; then - local search_term="$1" - local dir="./" - elif [ $# -eq 2 ]; then - local search_term="$1" - if [ -d "$2" ]; then - local dir="$2" - else - echo "Directory $2 not found" - return - fi - else - echo "Usage: search_dir []" - return - fi - dir=$(realpath "$dir") - local matches=$(find "$dir" -type f ! -path '*/.*' -exec grep -nIH -- "$search_term" {} + | cut -d: -f1 | sort | uniq -c) - # if no matches, return - if [ -z "$matches" ]; then - echo "No matches found for \"$search_term\" in $dir" - return - fi - # Calculate total number of matches - local num_matches=$(echo "$matches" | awk '{sum+=$1} END {print sum}') - # calculate total number of files matched - local num_files=$(echo "$matches" | wc -l | awk '{$1=$1; print $0}') - # if num_files is > 100, print an error - if [ $num_files -gt 100 ]; then - echo "More than $num_files files matched for \"$search_term\" in $dir. Please narrow your search." - return - fi - - echo "Found $num_matches matches for \"$search_term\" in $dir:" - echo "$matches" | awk '{$2=$2; gsub(/^\.+\/+/, "./", $2); print $2 " ("$1" matches)"}' - echo "End of matches for \"$search_term\" in $dir" -} - -# @yaml -# signature: search_file [] -# docstring: searches for search_term in file. If file is not provided, searches in the current open file -# arguments: -# search_term: -# type: string -# description: the term to search for -# required: true -# file: -# type: string -# description: the file to search in (if not provided, searches in the current open file) -# required: false -search_file() { - # Check if the first argument is provided - if [ -z "$1" ]; then - echo "Usage: search_file []" - return - fi - # Check if the second argument is provided - if [ -n "$2" ]; then - # Check if the provided argument is a valid file - if [ -f "$2" ]; then - local file="$2" # Set file if valid - else - echo "Usage: search_file []" - echo "Error: File name $2 not found. Please provide a valid file name." - return # Exit if the file is not valid - fi - else - # Check if a file is open - if [ -z "$CURRENT_FILE" ]; then - echo "No file open. Use the open command first." - return # Exit if no file is open - fi - local file="$CURRENT_FILE" # Set file to the current open file - fi - local search_term="$1" - file=$(realpath "$file") - # Use grep to directly get the desired formatted output - local matches=$(grep -nH -- "$search_term" "$file") - # Check if no matches were found - if [ -z "$matches" ]; then - echo "No matches found for \"$search_term\" in $file" - return - fi - # Calculate total number of matches - local num_matches=$(echo "$matches" | wc -l | awk '{$1=$1; print $0}') - - # calculate total number of lines matched - local num_lines=$(echo "$matches" | cut -d: -f1 | sort | uniq | wc -l | awk '{$1=$1; print $0}') - # if num_lines is > 100, print an error - if [ $num_lines -gt 100 ]; then - echo "More than $num_lines lines matched for \"$search_term\" in $file. Please narrow your search." - return - fi - - # Print the total number of matches and the matches themselves - echo "Found $num_matches matches for \"$search_term\" in $file:" - echo "$matches" | cut -d: -f1-2 | sort -u -t: -k2,2n | while IFS=: read -r filename line_number; do - echo "Line $line_number:$(sed -n "${line_number}p" "$file")" - done - echo "End of matches for \"$search_term\" in $file" -} - -# @yaml -# signature: find_file [] -# docstring: finds all files with the given name in dir. If dir is not provided, searches in the current directory -# arguments: -# file_name: -# type: string -# description: the name of the file to search for -# required: true -# dir: -# type: string -# description: the directory to search in (if not provided, searches in the current directory) -# required: false -find_file() { - if [ $# -eq 1 ]; then - local file_name="$1" - local dir="./" - elif [ $# -eq 2 ]; then - local file_name="$1" - if [ -d "$2" ]; then - local dir="$2" - else - echo "Directory $2 not found" - return - fi - else - echo "Usage: find_file []" - return - fi - - dir=$(realpath "$dir") - local matches=$(find "$dir" -type f -name "$file_name") - # if no matches, return - if [ -z "$matches" ]; then - echo "No matches found for \"$file_name\" in $dir" - return - fi - # Calculate total number of matches - local num_matches=$(echo "$matches" | wc -l | awk '{$1=$1; print $0}') - echo "Found $num_matches matches for \"$file_name\" in $dir:" - echo "$matches" | awk '{print $0}' -} diff --git a/opendevin/runtime/plugins/swe_agent_commands/setup_cursor_mode.sh b/opendevin/runtime/plugins/swe_agent_commands/setup_cursor_mode.sh deleted file mode 100755 index 8788e3947d73..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/setup_cursor_mode.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -export PIP_CACHE_DIR=$HOME/.cache/pip -pip install flake8 - -# Cursor Mode from SWE-Bench -# https://github.com/princeton-nlp/SWE-agent/blob/ca54d5556b9db4f4f2be21f09530ce69a72c0305/config/configs/default_sys-env_cursors_window100-detailed_cmd_format-last_5_history-1_demos.yaml#L108-L111 -echo 'source /opendevin/plugins/swe_agent_commands/_setup_cursor_mode_env.sh' >> ~/.bashrc - -# make _split_string (py) available -echo 'export PATH=$PATH:/opendevin/plugins/swe_agent_commands' >> ~/.bashrc - -echo 'source /opendevin/plugins/swe_agent_commands/cursors_defaults.sh' >> ~/.bashrc -echo 'source /opendevin/plugins/swe_agent_commands/cursors_edit_linting.sh' >> ~/.bashrc -echo 'source /opendevin/plugins/swe_agent_commands/search.sh' >> ~/.bashrc - -echo 'export SWE_CMD_WORK_DIR="/opendevin/plugins/swe_agent_commands/workdir"' >> ~/.bashrc -sudo mkdir -p /opendevin/plugins/swe_agent_commands/workdir -sudo chmod 777 /opendevin/plugins/swe_agent_commands/workdir diff --git a/opendevin/runtime/plugins/swe_agent_commands/setup_default.sh b/opendevin/runtime/plugins/swe_agent_commands/setup_default.sh deleted file mode 100755 index 465a83e12f9a..000000000000 --- a/opendevin/runtime/plugins/swe_agent_commands/setup_default.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -export PIP_CACHE_DIR=$HOME/.cache/pip -pip install flake8 - -# Default Mode from SWE-Bench -# https://github.com/princeton-nlp/SWE-agent/blob/ca54d5556b9db4f4f2be21f09530ce69a72c0305/config/configs/default_sys-env_window100-detailed_cmd_format-last_5_history-1_demos.yaml#L103-L106 -echo 'source /opendevin/plugins/swe_agent_commands/_setup_default_env.sh' >> ~/.bashrc - -# make _split_string (py) available -echo 'export PATH=$PATH:/opendevin/plugins/swe_agent_commands' >> ~/.bashrc - -echo 'source /opendevin/plugins/swe_agent_commands/defaults.sh' >> ~/.bashrc -echo 'source /opendevin/plugins/swe_agent_commands/search.sh' >> ~/.bashrc -echo 'source /opendevin/plugins/swe_agent_commands/edit_linting.sh' >> ~/.bashrc - -echo 'export SWE_CMD_WORK_DIR="/opendevin/plugins/swe_agent_commands/workdir"' >> ~/.bashrc -sudo mkdir -p /opendevin/plugins/swe_agent_commands/workdir -sudo chmod 777 /opendevin/plugins/swe_agent_commands/workdir diff --git a/opendevin/runtime/sandbox.py b/opendevin/runtime/sandbox.py index 5009308a9276..c8ebd87d3783 100644 --- a/opendevin/runtime/sandbox.py +++ b/opendevin/runtime/sandbox.py @@ -3,10 +3,9 @@ from opendevin.core.config import SandboxConfig from opendevin.core.schema import CancellableStream -from opendevin.runtime.plugins.mixin import PluginMixin -class Sandbox(ABC, PluginMixin): +class Sandbox(ABC): _env: dict[str, str] = {} is_initial_session: bool = True From da5bf6c1bfe79f38f7682c1ad5788bb8741cf13a Mon Sep 17 00:00:00 2001 From: Yufan Song <33971064+yufansong@users.noreply.github.com> Date: Sun, 11 Aug 2024 00:33:58 -0700 Subject: [PATCH 26/46] remove useless mock test code (#3335) --- opendevin/runtime/client/mock_test/client.py | 62 ------------------- .../runtime/client/mock_test/echo_server.py | 31 ---------- .../client/mock_test/execute_server.py | 33 ---------- 3 files changed, 126 deletions(-) delete mode 100644 opendevin/runtime/client/mock_test/client.py delete mode 100644 opendevin/runtime/client/mock_test/echo_server.py delete mode 100644 opendevin/runtime/client/mock_test/execute_server.py diff --git a/opendevin/runtime/client/mock_test/client.py b/opendevin/runtime/client/mock_test/client.py deleted file mode 100644 index 19a5a60f19ef..000000000000 --- a/opendevin/runtime/client/mock_test/client.py +++ /dev/null @@ -1,62 +0,0 @@ -# client.py, this file is used in development to test the runtime client. It is not used in production. -import asyncio -import websockets - -# Function for sending commands to the server in EventStreamRuntime -class EventStreamRuntime: - uri = 'ws://localhost:8080' - - def __init__(self): - self.websocket = None - - def _init_websocket(self): - self.websocket = None - # TODO: need to initialization globally only once - # self.loop = asyncio.new_event_loop() - # asyncio.set_event_loop(self.loop) - # self.loop.run_until_complete(self._init_websocket_connect()) - - async def execute(self, command): - self.websocket = await websockets.connect(self.uri) - - print(f"Sending command: {command}") - await self.websocket.send(command) - print("Command sent, waiting for response...") - try: - output = await asyncio.wait_for(self.websocket.recv(), timeout=10) - print("Received output") - print(output) - except asyncio.TimeoutError: - print("No response received within the timeout period.") - - await self.websocket.close() - -# Function for testing sending commands to the server -async def send_command(): - uri = "ws://localhost:8080" - while True: - try: - async with websockets.connect(uri) as websocket: - while True: - command = input("Enter the command to execute in the Docker container (type 'exit' to quit): ") - if command.lower() == 'exit': - return - await websocket.send(command) - response = await websocket.recv() - exit_code = response[-1].strip()\ - # command_output = '\n'.join(response[1:-1]).strip() - # print("Yufan:", command_output) - print("Exit Code:", exit_code) - print(response) - except (websockets.exceptions.ConnectionClosed, OSError) as e: - print(f"Connection closed, retrying... ({str(e)})") - await asyncio.sleep(1) - -if __name__ == "__main__": - asyncio.run(send_command()) - - -# if __name__ == "__main__": -# runtime = EventStreamRuntime() -# asyncio.run(runtime.execute('ls -l')) -# asyncio.run(runtime.execute('pwd')) \ No newline at end of file diff --git a/opendevin/runtime/client/mock_test/echo_server.py b/opendevin/runtime/client/mock_test/echo_server.py deleted file mode 100644 index 5108f5709323..000000000000 --- a/opendevin/runtime/client/mock_test/echo_server.py +++ /dev/null @@ -1,31 +0,0 @@ -# echo_server.py, this file is used in development to test the runtime client. It is not used in production. -import asyncio -import websockets -import pexpect -from websockets.exceptions import ConnectionClosed -import json - -def is_valid_json(s): - try: - json.loads(s) - except json.JSONDecodeError: - return False - return True - -# Function for testing websocket echo -async def echo(websocket, path): - async for message in websocket: - if is_valid_json(message): - event = json.loads(message) - print("Received:", event) - response = json.dumps(event) - await websocket.send(response) - else: - print("Received:", message) - response = f"Echo: {message}" - await websocket.send(response) - -start_server = websockets.serve(echo, "0.0.0.0", 8080) - -asyncio.get_event_loop().run_until_complete(start_server) -asyncio.get_event_loop().run_forever() diff --git a/opendevin/runtime/client/mock_test/execute_server.py b/opendevin/runtime/client/mock_test/execute_server.py deleted file mode 100644 index e7bfbab11095..000000000000 --- a/opendevin/runtime/client/mock_test/execute_server.py +++ /dev/null @@ -1,33 +0,0 @@ -# execute_server.py, this file is used in development to test the runtime client. It is not used in production. -import asyncio -import websockets -import pexpect -from websockets.exceptions import ConnectionClosed -import json - -# Function for testing execution of shell commands -async def execute_command(websocket, path): - shell = pexpect.spawn('/bin/bash', encoding='utf-8') - shell.expect(r'[$#] ') - - try: - async for message in websocket: - try: - print(f"Received command: {message}") - shell.sendline(message) - shell.expect(r'[$#] ') - output = shell.before.strip().split('\r\n', 1)[1].strip() - print("Yufan:",output) - await websocket.send(output) - except Exception as e: - await websocket.send(f"Error: {str(e)}") - except ConnectionClosed: - print("Connection closed") - finally: - shell.close() - - -start_server = websockets.serve(execute_command, "0.0.0.0", 8080) - -asyncio.get_event_loop().run_until_complete(start_server) -asyncio.get_event_loop().run_forever() From 9757f362bffabe09eb15c584e24936f8245c6a36 Mon Sep 17 00:00:00 2001 From: yueqis <141804823+yueqis@users.noreply.github.com> Date: Sun, 11 Aug 2024 03:40:27 -0400 Subject: [PATCH 27/46] Update run_infer.py of gorilla to download my-languages.so (#3286) * Update run_infer.py of gorilla to download my-languages.so * add exist check, change file path, lint code --------- Co-authored-by: Graham Neubig Co-authored-by: yufansong --- evaluation/gorilla/run_infer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/evaluation/gorilla/run_infer.py b/evaluation/gorilla/run_infer.py index d84432c3b0aa..44fb73c2348d 100644 --- a/evaluation/gorilla/run_infer.py +++ b/evaluation/gorilla/run_infer.py @@ -3,6 +3,7 @@ import os import pandas as pd +import requests from evaluation.gorilla.utils import encode_question, get_data_for_hub from evaluation.utils.shared import ( @@ -168,6 +169,16 @@ async def process_instance( dataset_df, output_file=output_file, eval_n_limit=args.eval_n_limit ) + file_path = os.path.join(os.path.dirname(__file__), 'my-languages.so') + # Check if the file exists + if not os.path.exists(file_path): + url = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/eval/eval-scripts/codebleu/parser/my-languages.so' + response = requests.get(url) + with open(file_path, 'wb') as f: + f.write(response.content) + else: + print('File already exists, skipping download.') + asyncio.run( run_evaluation( dataset=dataset, From 99ac91f6ad22d908447583efb814935a69a3fa9c Mon Sep 17 00:00:00 2001 From: Yufan Song <33971064+yufansong@users.noreply.github.com> Date: Sun, 11 Aug 2024 07:47:28 -0700 Subject: [PATCH 28/46] remove sandbox abstract class (#3337) * remove sandbox abstract class * remove cancellable stream --- opendevin/core/schema/__init__.py | 3 --- opendevin/core/schema/stream.py | 27 -------------------------- opendevin/runtime/__init__.py | 1 - opendevin/runtime/e2b/runtime.py | 3 +-- opendevin/runtime/e2b/sandbox.py | 15 ++++++++------- opendevin/runtime/sandbox.py | 32 ------------------------------- 6 files changed, 9 insertions(+), 72 deletions(-) delete mode 100644 opendevin/core/schema/stream.py delete mode 100644 opendevin/runtime/sandbox.py diff --git a/opendevin/core/schema/__init__.py b/opendevin/core/schema/__init__.py index 416aa0736b46..2fa4e1aa3e77 100644 --- a/opendevin/core/schema/__init__.py +++ b/opendevin/core/schema/__init__.py @@ -2,13 +2,10 @@ from .agent import AgentState from .config import ConfigType from .observation import ObservationType -from .stream import CancellableStream, StreamMixin __all__ = [ 'ActionType', 'ObservationType', 'ConfigType', 'AgentState', - 'CancellableStream', - 'StreamMixin', ] diff --git a/opendevin/core/schema/stream.py b/opendevin/core/schema/stream.py deleted file mode 100644 index 29c9c7e463af..000000000000 --- a/opendevin/core/schema/stream.py +++ /dev/null @@ -1,27 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Union - - -class StreamMixin: - def __init__(self, generator): - self.generator = generator - self.closed = False - - def __iter__(self): - return self - - def __next__(self): - if self.closed: - raise StopIteration - else: - return next(self.generator) - - -class CancellableStream(StreamMixin, ABC): - @abstractmethod - def close(self): - pass - - @abstractmethod - def exit_code(self) -> Union[int, None]: - pass diff --git a/opendevin/runtime/__init__.py b/opendevin/runtime/__init__.py index 26d690826f2b..ea17ebb442fe 100644 --- a/opendevin/runtime/__init__.py +++ b/opendevin/runtime/__init__.py @@ -1,5 +1,4 @@ from .e2b.sandbox import E2BBox -from .sandbox import Sandbox def get_runtime_cls(name: str): diff --git a/opendevin/runtime/e2b/runtime.py b/opendevin/runtime/e2b/runtime.py index 5d90408b5545..0a185d5c97d3 100644 --- a/opendevin/runtime/e2b/runtime.py +++ b/opendevin/runtime/e2b/runtime.py @@ -10,7 +10,6 @@ Observation, ) from opendevin.events.stream import EventStream -from opendevin.runtime import Sandbox from opendevin.runtime.plugins import PluginRequirement from opendevin.runtime.runtime import Runtime @@ -26,7 +25,7 @@ def __init__( event_stream: EventStream, sid: str = 'default', plugins: list[PluginRequirement] | None = None, - sandbox: Sandbox | None = None, + sandbox: E2BSandbox | None = None, ): super().__init__(config, event_stream, sid, plugins) if sandbox is None: diff --git a/opendevin/runtime/e2b/sandbox.py b/opendevin/runtime/e2b/sandbox.py index 361a0df15c7b..08af9eb02b8f 100644 --- a/opendevin/runtime/e2b/sandbox.py +++ b/opendevin/runtime/e2b/sandbox.py @@ -1,5 +1,6 @@ import os import tarfile +import copy from glob import glob from e2b import Sandbox as E2BSandbox @@ -9,13 +10,12 @@ from opendevin.core.config import SandboxConfig from opendevin.core.logger import opendevin_logger as logger -from opendevin.core.schema import CancellableStream -from opendevin.runtime.sandbox import Sandbox - -class E2BBox(Sandbox): +class E2BBox: closed = False _cwd: str = '/home/user' + _env: dict[str, str] = {} + is_initial_session: bool = True def __init__( self, @@ -23,7 +23,8 @@ def __init__( e2b_api_key: str, template: str = 'open-devin', ): - super().__init__(config) + self.config = copy.deepcopy(config) + self.initialize_plugins: bool = config.initialize_plugins self.sandbox = E2BSandbox( api_key=e2b_api_key, template=template, @@ -62,8 +63,8 @@ def _archive(self, host_src: str, recursive: bool = False): return tar_filename def execute( - self, cmd: str, stream: bool = False, timeout: int | None = None - ) -> tuple[int, str | CancellableStream]: + self, cmd: str, timeout: int | None = None + ) -> tuple[int, str]: timeout = timeout if timeout is not None else self.config.timeout process = self.sandbox.process.start(cmd, env_vars=self._env) try: diff --git a/opendevin/runtime/sandbox.py b/opendevin/runtime/sandbox.py deleted file mode 100644 index c8ebd87d3783..000000000000 --- a/opendevin/runtime/sandbox.py +++ /dev/null @@ -1,32 +0,0 @@ -import copy -from abc import ABC, abstractmethod - -from opendevin.core.config import SandboxConfig -from opendevin.core.schema import CancellableStream - - -class Sandbox(ABC): - _env: dict[str, str] = {} - is_initial_session: bool = True - - def __init__(self, config: SandboxConfig): - self.config = copy.deepcopy(config) - self.initialize_plugins: bool = config.initialize_plugins - - @abstractmethod - def execute( - self, cmd: str, stream: bool = False, timeout: int | None = None - ) -> tuple[int, str | CancellableStream]: - pass - - @abstractmethod - def close(self): - pass - - @abstractmethod - def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): - pass - - @abstractmethod - def get_working_directory(self): - pass From 28dd882f98915538acf24f2bfa28f5e3a8838f06 Mon Sep 17 00:00:00 2001 From: Yufan Song <33971064+yufansong@users.noreply.github.com> Date: Sun, 11 Aug 2024 12:08:30 -0700 Subject: [PATCH 29/46] remove useless sanbox (#3336) --- opendevin/runtime/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/opendevin/runtime/__init__.py b/opendevin/runtime/__init__.py index ea17ebb442fe..04fb9789f9fb 100644 --- a/opendevin/runtime/__init__.py +++ b/opendevin/runtime/__init__.py @@ -16,9 +16,7 @@ def get_runtime_cls(name: str): __all__ = [ - 'DockerSSHBox', 'E2BBox', - 'LocalBox', 'Sandbox', 'get_runtime_cls', ] From 1424db8309e78528db0ede6bed08e47e541b2026 Mon Sep 17 00:00:00 2001 From: Yufan Song <33971064+yufansong@users.noreply.github.com> Date: Sun, 11 Aug 2024 18:53:55 -0700 Subject: [PATCH 30/46] remove uesless process (#3338) --- opendevin/runtime/process.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 opendevin/runtime/process.py diff --git a/opendevin/runtime/process.py b/opendevin/runtime/process.py deleted file mode 100644 index aed4cbbd4c00..000000000000 --- a/opendevin/runtime/process.py +++ /dev/null @@ -1,17 +0,0 @@ -from abc import ABC, abstractmethod - - -class Process(ABC): - @property - @abstractmethod - def pid(self) -> int: - pass - - @property - @abstractmethod - def command(self) -> str: - pass - - @abstractmethod - def read_logs(self) -> str: - pass From 4a62f9c4ec62423a6f0b5cee541b8217675a7e46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 23:53:08 +0800 Subject: [PATCH 31/46] chore(deps-dev): bump @types/node from 22.1.0 to 22.2.0 in /frontend (#3340) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.1.0 to 22.2.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8e9382e4d607..2fa25e07455d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -40,7 +40,7 @@ "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", - "@types/node": "^22.1.0", + "@types/node": "^22.2.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-highlight": "^0.12.8", @@ -4857,9 +4857,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", - "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", + "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", "devOptional": true, "dependencies": { "undici-types": "~6.13.0" diff --git a/frontend/package.json b/frontend/package.json index f5134a233119..783532f10b65 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,7 +63,7 @@ "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", - "@types/node": "^22.1.0", + "@types/node": "^22.2.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-highlight": "^0.12.8", From 34510c36059ceaab4f250dfb870d571d8b7274ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 23:53:33 +0800 Subject: [PATCH 32/46] chore(deps): bump tailwind-merge from 2.4.0 to 2.5.1 in /frontend (#3341) Bumps [tailwind-merge](https://github.com/dcastil/tailwind-merge) from 2.4.0 to 2.5.1. - [Release notes](https://github.com/dcastil/tailwind-merge/releases) - [Commits](https://github.com/dcastil/tailwind-merge/compare/v2.4.0...v2.5.1) --- updated-dependencies: - dependency-name: tailwind-merge dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2fa25e07455d..3e6ceb1ffa45 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,7 +31,7 @@ "react-markdown": "^9.0.1", "react-redux": "^9.1.2", "react-syntax-highlighter": "^15.5.0", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.1", "vite": "^5.4.0", "web-vitals": "^3.5.2" }, @@ -12114,9 +12114,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", - "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.1.tgz", + "integrity": "sha512-1zKDdExKvNltulO+J0x/Rqv40xQn78FHsEQVn3rxt8e4HdebRIT6o6zGeLYlGuxd3Efue9Y69qsp8vKwEhuEeg==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/frontend/package.json b/frontend/package.json index 783532f10b65..515d050a135d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "react-markdown": "^9.0.1", "react-redux": "^9.1.2", "react-syntax-highlighter": "^15.5.0", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.1", "vite": "^5.4.0", "web-vitals": "^3.5.2" }, From 9653f04367675ec81f181a1d1ecc9cb1cf324d04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:47:47 -0700 Subject: [PATCH 33/46] chore(deps-dev): bump openai from 1.40.2 to 1.40.3 (#3354) Bumps [openai](https://github.com/openai/openai-python) from 1.40.2 to 1.40.3. - [Release notes](https://github.com/openai/openai-python/releases) - [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md) - [Commits](https://github.com/openai/openai-python/compare/v1.40.2...v1.40.3) --- updated-dependencies: - dependency-name: openai dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: yufansong --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 65a38908108f..52cc6b29018c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5126,13 +5126,13 @@ sympy = "*" [[package]] name = "openai" -version = "1.40.2" +version = "1.40.3" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.40.2-py3-none-any.whl", hash = "sha256:38068f858f310b4fd4b0ea8734c3efcfde3c15a2978311e1453bd84817231b96"}, - {file = "openai-1.40.2.tar.gz", hash = "sha256:2180e9070bd36084328248b3ce668964e8ddd2e9019e1d426e31dc54cc117bb5"}, + {file = "openai-1.40.3-py3-none-any.whl", hash = "sha256:09396cb6e2e15c921a5d872bf92841a60a9425da10dcd962b45fe7c4f48f8395"}, + {file = "openai-1.40.3.tar.gz", hash = "sha256:f2ffe907618240938c59d7ccc67dd01dc8c50be203c0077240db6758d2f02480"}, ] [package.dependencies] From 602bf40bd45d98b5ea9ba1f40b1a0df4c7b657a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:48:11 -0700 Subject: [PATCH 34/46] chore(deps-dev): bump llama-index-embeddings-huggingface (#3352) Bumps llama-index-embeddings-huggingface from 0.2.2 to 0.2.3. --- updated-dependencies: - dependency-name: llama-index-embeddings-huggingface dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: yufansong --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 52cc6b29018c..8b2b7162f6fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3718,13 +3718,13 @@ llama-index-llms-azure-openai = ">=0.1.3,<0.2.0" [[package]] name = "llama-index-embeddings-huggingface" -version = "0.2.2" +version = "0.2.3" description = "llama-index embeddings huggingface integration" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "llama_index_embeddings_huggingface-0.2.2-py3-none-any.whl", hash = "sha256:3445b1c7823cdb45622f90e79f2540db870ea55b226ec7538be963d340f43240"}, - {file = "llama_index_embeddings_huggingface-0.2.2.tar.gz", hash = "sha256:43b2978740d29291ae4c7566922d2b1c7543dc979e268794b578e1a2adfb4319"}, + {file = "llama_index_embeddings_huggingface-0.2.3-py3-none-any.whl", hash = "sha256:7dee842f938d5fa8992e7803eda8a14f6bea72ec0bc0a546f4c6aa455166cde5"}, + {file = "llama_index_embeddings_huggingface-0.2.3.tar.gz", hash = "sha256:6fe54366eeb87ff81b50624d6b8ccca4230f8035fcc19a0b0b3f31c6d8a82f8b"}, ] [package.dependencies] From b4795ffbca9cf7c3f50e579e7ecaf29db85c2c67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:48:28 -0700 Subject: [PATCH 35/46] chore(deps): bump json-repair from 0.27.0 to 0.27.2 (#3350) Bumps [json-repair](https://github.com/mangiucugna/json_repair) from 0.27.0 to 0.27.2. - [Release notes](https://github.com/mangiucugna/json_repair/releases) - [Commits](https://github.com/mangiucugna/json_repair/compare/0.27.0...0.27.2) --- updated-dependencies: - dependency-name: json-repair dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8b2b7162f6fb..380fc50cf991 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3084,13 +3084,13 @@ files = [ [[package]] name = "json-repair" -version = "0.27.0" +version = "0.27.2" description = "A package to repair broken json strings" optional = false python-versions = ">=3.8" files = [ - {file = "json_repair-0.27.0-py3-none-any.whl", hash = "sha256:20763c8cf1c3096e33ce7c09c2b8e6c471a4acdce468688c96052fc7cccbad7f"}, - {file = "json_repair-0.27.0.tar.gz", hash = "sha256:f4e14c5ad2b3f17290a361c3c90915536b462c36f69989e915867e81663dd467"}, + {file = "json_repair-0.27.2-py3-none-any.whl", hash = "sha256:493ad22d9918ebc60661cf5f590d2c727a554f2ddddcc68953aeca83bde7ce68"}, + {file = "json_repair-0.27.2.tar.gz", hash = "sha256:8a22455b58a098c73c18c81c4efe3d77e27763b4e643715ae20dce31eb4a703e"}, ] [[package]] From 6c61f55962e39d2ab1b1486312bfdff7aaf19ebe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:48:54 -0700 Subject: [PATCH 36/46] chore(deps): bump litellm from 1.43.4 to 1.43.7 (#3349) Bumps [litellm](https://github.com/BerriAI/litellm) from 1.43.4 to 1.43.7. - [Release notes](https://github.com/BerriAI/litellm/releases) - [Commits](https://github.com/BerriAI/litellm/compare/v1.43.4...v1.43.7) --- updated-dependencies: - dependency-name: litellm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: yufansong --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 380fc50cf991..d8b1bb8e7770 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3566,13 +3566,13 @@ types-tqdm = "*" [[package]] name = "litellm" -version = "1.43.4" +version = "1.43.7" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.43.4-py3-none-any.whl", hash = "sha256:619cfaab189f921f66ff50c2b7a0e965e562c2a95b17c2ee24649826ba35da11"}, - {file = "litellm-1.43.4.tar.gz", hash = "sha256:949c51ad494b935d80da1cd18c3567e4ed181f8eb531ef4706e3be72afb0c43c"}, + {file = "litellm-1.43.7-py3-none-any.whl", hash = "sha256:88d9d8dcb4579839106941f1ce59143ab926af986a2206cce4bcda1ae153a78c"}, + {file = "litellm-1.43.7.tar.gz", hash = "sha256:b6ef8db0c7555d590957c37b228584efc5e9154b925ab0fffb112be26f1ab5ab"}, ] [package.dependencies] From 390a660aabf4990d266c468695edf1290d006f24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:49:22 -0700 Subject: [PATCH 37/46] chore(deps-dev): bump sympy from 1.13.1 to 1.13.2 (#3353) Bumps [sympy](https://github.com/sympy/sympy) from 1.13.1 to 1.13.2. - [Release notes](https://github.com/sympy/sympy/releases) - [Commits](https://github.com/sympy/sympy/compare/sympy-1.13.1...sympy-1.13.2) --- updated-dependencies: - dependency-name: sympy dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: yufansong --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index d8b1bb8e7770..bca41ccae644 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7683,13 +7683,13 @@ resolved_reference = "c2b3cefd4a5af0b248966a773650a39046072975" [[package]] name = "sympy" -version = "1.13.1" +version = "1.13.2" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" files = [ - {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, - {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, + {file = "sympy-1.13.2-py3-none-any.whl", hash = "sha256:c51d75517712f1aed280d4ce58506a4a88d635d6b5dd48b39102a7ae1f3fcfe9"}, + {file = "sympy-1.13.2.tar.gz", hash = "sha256:401449d84d07be9d0c7a46a64bd54fe097667d5e7181bfe67ec777be9e01cb13"}, ] [package.dependencies] From cbfa8a872b708452584b1694a4be878bff9d8112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:49:45 -0700 Subject: [PATCH 38/46] chore(deps): bump boto3 from 1.34.157 to 1.34.158 (#3351) Bumps [boto3](https://github.com/boto/boto3) from 1.34.157 to 1.34.158. - [Release notes](https://github.com/boto/boto3/releases) - [Commits](https://github.com/boto/boto3/compare/1.34.157...1.34.158) --- updated-dependencies: - dependency-name: boto3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index bca41ccae644..a1b19bea9200 100644 --- a/poetry.lock +++ b/poetry.lock @@ -519,17 +519,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.157" +version = "1.34.158" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.157-py3-none-any.whl", hash = "sha256:3cc357156df5482154a016f138d1953061a181b4c594f8b6302c9d6c024bd950"}, - {file = "boto3-1.34.157.tar.gz", hash = "sha256:7ef19ed38cba9863b58430fb4a66a72a5c250304f234bd1c16b860f9bf25677b"}, + {file = "boto3-1.34.158-py3-none-any.whl", hash = "sha256:c29e9b7e1034e8734ccaffb9f2b3f3df2268022fd8a93d836604019f8759ce27"}, + {file = "boto3-1.34.158.tar.gz", hash = "sha256:5b7b2ce0ec1e498933f600d29f3e1c641f8c44dd7e468c26795359d23d81fa39"}, ] [package.dependencies] -botocore = ">=1.34.157,<1.35.0" +botocore = ">=1.34.158,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -538,13 +538,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.157" +version = "1.34.158" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.157-py3-none-any.whl", hash = "sha256:c6cba6de8eb86ca4d2f934e009b37adbe1e7fdcfa52fbab74783f4c30676e07d"}, - {file = "botocore-1.34.157.tar.gz", hash = "sha256:5628a36cec123cdc8c1158d05a7b06aa5e53649ad73796c50ef3fb51199785fb"}, + {file = "botocore-1.34.158-py3-none-any.whl", hash = "sha256:0e6fceba1e39bfa8feeba70ba3ac2af958b3387df4bd3b5f2db3f64c1754c756"}, + {file = "botocore-1.34.158.tar.gz", hash = "sha256:5934082e25ad726673afbf466092fb1223dafa250e6e756c819430ba6b1b3da5"}, ] [package.dependencies] From 38c1ab189af479bc41968ea37570d17d31b389b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:50:09 -0700 Subject: [PATCH 39/46] chore(deps-dev): bump @docusaurus/module-type-aliases in /docs (#3348) Bumps [@docusaurus/module-type-aliases](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-module-type-aliases) from 3.4.0 to 3.5.1. - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.5.1/packages/docusaurus-module-type-aliases) --- updated-dependencies: - dependency-name: "@docusaurus/module-type-aliases" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 104 +++++++++++++++++++++++++++++++++++++++-- docs/package.json | 2 +- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 935c0614a685..d85eaf65f710 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -21,7 +21,7 @@ "react-use": "^17.5.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.4.0", + "@docusaurus/module-type-aliases": "^3.5.1", "@docusaurus/tsconfig": "^3.4.0", "@docusaurus/types": "^3.4.0", "typescript": "~5.5.4" @@ -2292,11 +2292,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", - "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.5.1.tgz", + "integrity": "sha512-SKKdA5RnvZr3pvFXkxtfsBVNgflRGa/bN1HbNi+1s0HNVYPuhB9DFC/CrKe2OoOfUXx7F7k2gg0Jg9gJYDy4rA==", + "dev": true, "dependencies": { - "@docusaurus/types": "3.4.0", + "@docusaurus/types": "3.5.1", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2309,6 +2310,27 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/module-type-aliases/node_modules/@docusaurus/types": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.1.tgz", + "integrity": "sha512-IXTGQBoXAGFliGF5Cn3F+gSGskgzAL8+4y6dDY1gcePA0r8WngHj8oovS1YPv+b9JOff32nv8YGGZITHOMXJsA==", + "dev": true, + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-content-blog": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", @@ -2370,6 +2392,24 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-content-docs/node_modules/@docusaurus/module-type-aliases": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", + "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "dependencies": { + "@docusaurus/types": "3.4.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/@docusaurus/plugin-content-pages": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", @@ -2556,6 +2596,24 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/module-type-aliases": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", + "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "dependencies": { + "@docusaurus/types": "3.4.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/@docusaurus/theme-common": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", @@ -2585,6 +2643,24 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/module-type-aliases": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", + "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "dependencies": { + "@docusaurus/types": "3.4.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/@docusaurus/theme-mermaid": { "version": "3.4.0", "resolved": "https://registry.npmmirror.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz", @@ -2606,6 +2682,24 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/module-type-aliases": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", + "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "dependencies": { + "@docusaurus/types": "3.4.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/@docusaurus/theme-search-algolia": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", diff --git a/docs/package.json b/docs/package.json index a529476c4786..8200b6e0fceb 100644 --- a/docs/package.json +++ b/docs/package.json @@ -28,7 +28,7 @@ "react-use": "^17.5.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.4.0", + "@docusaurus/module-type-aliases": "^3.5.1", "@docusaurus/tsconfig": "^3.4.0", "@docusaurus/types": "^3.4.0", "typescript": "~5.5.4" From 5226d8d684459c81aa83ab7ab849c37f0e436c10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:44:37 +0000 Subject: [PATCH 40/46] chore(deps): bump @docusaurus/plugin-content-pages in /docs (#3346) Bumps [@docusaurus/plugin-content-pages](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-plugin-content-pages) from 3.4.0 to 3.5.1. - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.5.1/packages/docusaurus-plugin-content-pages) --- updated-dependencies: - dependency-name: "@docusaurus/plugin-content-pages" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 328 +++++++++++++++++++++++++++++++++++++++-- docs/package.json | 2 +- 2 files changed, 320 insertions(+), 10 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index d85eaf65f710..1e8a6ae54927 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@docusaurus/core": "^3.4.0", - "@docusaurus/plugin-content-pages": "^3.4.0", + "@docusaurus/plugin-content-pages": "^3.5.1", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-mermaid": "^3.4.0", "@mdx-js/react": "^3.0.0", @@ -2411,15 +2411,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", - "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.1.tgz", + "integrity": "sha512-V2PDVrO2vHYJ7uhrEHpfzg3TTuwfrgNC0pGhM5gXaMfCbdhKm7iwV0huGLcyIX5Peyh7EMP2e8GFccUzWFMYOg==", "dependencies": { - "@docusaurus/core": "3.4.0", - "@docusaurus/mdx-loader": "3.4.0", - "@docusaurus/types": "3.4.0", - "@docusaurus/utils": "3.4.0", - "@docusaurus/utils-validation": "3.4.0", + "@docusaurus/core": "3.5.1", + "@docusaurus/mdx-loader": "3.5.1", + "@docusaurus/types": "3.5.1", + "@docusaurus/utils": "3.5.1", + "@docusaurus/utils-validation": "3.5.1", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2432,6 +2432,250 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/core": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.5.1.tgz", + "integrity": "sha512-N3+9IbGI2jbkiRc6ZbEnU9dC02nHQXi8ivM1VJldkPQyP7WlyHXS+NDhmL3rwaYOMbGH96X2LcKigCKg7pEEqg==", + "dependencies": { + "@babel/core": "^7.23.3", + "@babel/generator": "^7.23.3", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.22.9", + "@babel/preset-env": "^7.22.9", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@babel/runtime": "^7.22.6", + "@babel/runtime-corejs3": "^7.22.6", + "@babel/traverse": "^7.22.8", + "@docusaurus/cssnano-preset": "3.5.1", + "@docusaurus/logger": "3.5.1", + "@docusaurus/mdx-loader": "3.5.1", + "@docusaurus/utils": "3.5.1", + "@docusaurus/utils-common": "3.5.1", + "@docusaurus/utils-validation": "3.5.1", + "autoprefixer": "^10.4.14", + "babel-loader": "^9.1.3", + "babel-plugin-dynamic-import-node": "^2.3.3", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "clean-css": "^5.3.2", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "copy-webpack-plugin": "^11.0.0", + "core-js": "^3.31.1", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "del": "^6.1.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "html-minifier-terser": "^7.2.0", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.5.3", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.7.6", + "p-map": "^4.0.0", + "postcss": "^8.4.26", + "postcss-loader": "^7.3.3", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.1", + "react-helmet-async": "^1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "rtl-detect": "^1.0.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.5", + "shelljs": "^0.8.5", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "url-loader": "^4.1.1", + "webpack": "^5.88.1", + "webpack-bundle-analyzer": "^4.9.0", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.9.0", + "webpackbar": "^5.0.2" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/cssnano-preset": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.5.1.tgz", + "integrity": "sha512-mvtWPLWePlm+4doepxMUT5ynsJQ3CgPtDdbaQh9wm3iAE/7OATBpSgLlfz5N+YtxI5bjIErjbkH8yzISP+S65g==", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.4.38", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/logger": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.5.1.tgz", + "integrity": "sha512-B36a88CEHCtxIylAV1HNuiiISpoKBqm0UxA6a/JwtHX++Dxb7LNDSGs8ELBlQsZN0OG2tX3tBsCWyaLPwYorkQ==", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/mdx-loader": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.5.1.tgz", + "integrity": "sha512-D6Ea2dt32xhoqH+1EuHLGDVSX2HLFiR4QpI0GTU46qOu2hb2ChpQENIUZ2inOsdGFunNa0fCnDG3qn7Kdbzq1A==", + "dependencies": { + "@docusaurus/logger": "3.5.1", + "@docusaurus/utils": "3.5.1", + "@docusaurus/utils-validation": "3.5.1", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^1.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/types": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.1.tgz", + "integrity": "sha512-IXTGQBoXAGFliGF5Cn3F+gSGskgzAL8+4y6dDY1gcePA0r8WngHj8oovS1YPv+b9JOff32nv8YGGZITHOMXJsA==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/utils": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.1.tgz", + "integrity": "sha512-/4QAvXyiQviz2FQ4ct5l1ckvDihIdjS8FsOExC0T+Y1UD38jgPbjTwRJXsDaRsDRCCrDAtXvlonxXw2kixcnXw==", + "dependencies": { + "@docusaurus/logger": "3.5.1", + "@docusaurus/utils-common": "3.5.1", + "@svgr/webpack": "^8.1.0", + "escape-string-regexp": "^4.0.0", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", + "shelljs": "^0.8.5", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/types": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/types": { + "optional": true + } + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/utils-common": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.5.1.tgz", + "integrity": "sha512-374n6/IW34gHR65JMMN33XLFogTCsrGVPQDVbv2vG96EYHvYzE/plfcGV7xSbXB8yS1YHsxVfvNgVUGi973bfQ==", + "dependencies": { + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/types": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/types": { + "optional": true + } + } + }, + "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/utils-validation": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.5.1.tgz", + "integrity": "sha512-LZdQnqVVLStgTCn0rfvf4wuOQkjPbGtLXJIQ449em1wJeSFO7lfmn5VGUNLt+xKHvIPfN272EHG8BuvijCI0+A==", + "dependencies": { + "@docusaurus/logger": "3.5.1", + "@docusaurus/utils": "3.5.1", + "@docusaurus/utils-common": "3.5.1", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, "node_modules/@docusaurus/plugin-debug": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz", @@ -2557,6 +2801,28 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/plugin-content-pages": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", + "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-classic": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", @@ -2614,6 +2880,28 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/plugin-content-pages": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", + "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-common": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", @@ -2661,6 +2949,28 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/plugin-content-pages": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", + "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-mermaid": { "version": "3.4.0", "resolved": "https://registry.npmmirror.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz", diff --git a/docs/package.json b/docs/package.json index 8200b6e0fceb..aa2094db1ed2 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@docusaurus/core": "^3.4.0", - "@docusaurus/plugin-content-pages": "^3.4.0", + "@docusaurus/plugin-content-pages": "^3.5.1", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-mermaid": "^3.4.0", "@mdx-js/react": "^3.0.0", From e2b2f74737dc61ac07ff21b21b2942bdf991f00c Mon Sep 17 00:00:00 2001 From: mamoodi Date: Mon, 12 Aug 2024 15:17:12 -0400 Subject: [PATCH 41/46] Add comments to the runtime_build script (#3333) --- containers/runtime/config.sh | 3 +- opendevin/runtime/utils/runtime_build.py | 187 ++++++++++++++++------- 2 files changed, 134 insertions(+), 56 deletions(-) diff --git a/containers/runtime/config.sh b/containers/runtime/config.sh index 1a4b47f10d71..e0de71f4fe02 100644 --- a/containers/runtime/config.sh +++ b/containers/runtime/config.sh @@ -1,6 +1,7 @@ DOCKER_REGISTRY=ghcr.io DOCKER_ORG=opendevin DOCKER_BASE_DIR="./containers/runtime" -# These two variables will be appended by the runtime_build.py script +# These variables will be appended by the runtime_build.py script # DOCKER_IMAGE= # DOCKER_IMAGE_TAG= +# DOCKER_IMAGE_HASH_TAG= diff --git a/opendevin/runtime/utils/runtime_build.py b/opendevin/runtime/utils/runtime_build.py index 3f51a7f7ca8d..eda59b6382a6 100644 --- a/opendevin/runtime/utils/runtime_build.py +++ b/opendevin/runtime/utils/runtime_build.py @@ -18,7 +18,11 @@ def _get_package_version(): - """Read the version from pyproject.toml as the other one may be outdated.""" + """Read the version from pyproject.toml. + + Returns: + - The version specified in pyproject.toml under [tool.poetry] + """ project_root = os.path.dirname(os.path.dirname(os.path.abspath(opendevin.__file__))) pyproject_path = os.path.join(project_root, 'pyproject.toml') with open(pyproject_path, 'r') as f: @@ -27,13 +31,15 @@ def _get_package_version(): def _create_project_source_dist(): - """Create a source distribution of the project. Return the path to the tarball.""" - # Copy the project directory to the container - # get the location of "opendevin" package + """Create a source distribution of the project. + + Returns: + - str: The path to the project tarball + """ project_root = os.path.dirname(os.path.dirname(os.path.abspath(opendevin.__file__))) logger.info(f'Using project root: {project_root}') - # run "python -m build -s" on project_root + # run "python -m build -s" on project_root to create project tarball result = subprocess.run(['python', '-m', 'build', '-s', project_root]) if result.returncode != 0: logger.error(f'Build failed: {result}') @@ -53,12 +59,18 @@ def _create_project_source_dist(): def _put_source_code_to_dir(temp_dir: str): - """Put the source code of OpenDevin to the temp_dir/code.""" + """Builds the project source tarball. Copies it to temp_dir and unpacks it. + The OpenDevin source code ends up in the temp_dir/code directory + + Parameters: + - temp_dir (str): The directory to put the source code in + """ + # Build the project source tarball tarball_path = _create_project_source_dist() filename = os.path.basename(tarball_path) filename = filename.removesuffix('.tar.gz') - # move the tarball to temp_dir + # Move the project tarball to temp_dir _res = shutil.copy(tarball_path, os.path.join(temp_dir, 'project.tar.gz')) if _res: os.remove(tarball_path) @@ -66,11 +78,11 @@ def _put_source_code_to_dir(temp_dir: str): f'Source distribution moved to {os.path.join(temp_dir, "project.tar.gz")}' ) - # unzip the tarball + # Unzip the tarball shutil.unpack_archive(os.path.join(temp_dir, 'project.tar.gz'), temp_dir) - # remove the tarball + # Remove the tarball os.remove(os.path.join(temp_dir, 'project.tar.gz')) - # rename the directory to the 'code' + # Rename the directory containing the code to 'code' os.rename(os.path.join(temp_dir, filename), os.path.join(temp_dir, 'code')) logger.info(f'Unpacked source code directory: {os.path.join(temp_dir, "code")}') @@ -80,13 +92,23 @@ def _generate_dockerfile( skip_init: bool = False, extra_deps: str | None = None, ) -> str: - """Generate the Dockerfile content for the eventstream runtime image based on user-provided base image.""" + """Generate the Dockerfile content for the runtime image based on the base image. + + Parameters: + - base_image (str): The base image provided for the runtime image + - skip_init (boolean): + - extra_deps (str): + + Returns: + - str: The resulting Dockerfile content + """ env = Environment( loader=FileSystemLoader( searchpath=os.path.join(os.path.dirname(__file__), 'runtime_templates') ) ) template = env.get_template('Dockerfile.j2') + dockerfile_content = template.render( base_image=base_image, skip_init=skip_init, @@ -101,11 +123,21 @@ def prep_docker_build_folder( skip_init: bool = False, extra_deps: str | None = None, ) -> str: - """Prepares the docker build folder by copying the source code and generating the Dockerfile. + """Prepares a docker build folder by copying the source code and generating the Dockerfile - Return the MD5 hash of the directory. + Parameters: + - dir_path (str): The build folder to place the source code and Dockerfile + - base_image (str): The base Docker image to use for the Dockerfile + - skip_init (str): + - extra_deps (str): + + Returns: + - str: The MD5 hash of the build folder directory (dir_path) """ + # Copy the source code to directory. It will end up in dir_path/code _put_source_code_to_dir(dir_path) + + # Create a Dockerfile and write it to dir_path dockerfile_content = _generate_dockerfile( base_image, skip_init=skip_init, @@ -113,14 +145,15 @@ def prep_docker_build_folder( ) logger.info( ( - f'===== Dockerfile content =====\n' + f'===== Dockerfile content start =====\n' f'{dockerfile_content}\n' - f'===============================' + f'===== Dockerfile content end =====' ) ) with open(os.path.join(dir_path, 'Dockerfile'), 'w') as file: file.write(dockerfile_content) + # Get the MD5 hash of the dir_path directory hash = dirhash(dir_path, 'md5') logger.info( f'Input base image: {base_image}\n' @@ -138,23 +171,22 @@ def _build_sandbox_image( target_image_hash_tag: str, target_image_tag: str, ) -> str: - """Build the sandbox image. - + """Build and tag the sandbox image. The image will be tagged as both: - - target_image_repo:target_image_hash_tag - - target_image_repo:target_image_tag - - Args: - docker_folder: str: the path to the docker build folder - docker_client: docker.DockerClient: the docker client - target_image_repo: str: the repository name for the target image - target_image_hash_tag: str: the *hash* tag for the target image that is calculated based - on the contents of the docker build folder (source code and Dockerfile) - e.g., ubuntu:latest -> od_runtime:1234567890abcdef - target_image_tag: str: the tag for the target image that's generic and based on the base image name - e.g., ubuntu:latest -> od_runtime:ubuntu_tag_latest + - target_image_hash_tag + - target_image_tag + + Parameters: + - docker_folder (str): the path to the docker build folder + - docker_client (docker.DockerClient): the docker client + - target_image_repo (str): the repository name for the target image + - target_image_hash_tag (str): the *hash* tag for the target image that is calculated based + on the contents of the docker build folder (source code and Dockerfile) + e.g. 1234567890abcdef + -target_image_tag (str): the tag for the target image that's generic and based on the base image name + e.g. od_v0.8.3_image_ubuntu_tag_22.04 """ - # 1. Always directly build and tag using the dir_hash + # Build the Docker image and tag it with the hash (target_image_hash_tag) target_image_hash_name = f'{target_image_repo}:{target_image_hash_tag}' try: build_logs = docker_client.api.build( @@ -175,7 +207,7 @@ def _build_sandbox_image( else: logger.info(str(log)) - # 2. Re-tag the image with a more generic tag (as somewhat of "latest" tag) + # Re-tag the image with the target_image_tag logger.info(f'Image [{target_image_hash_name}] build finished.') image = docker_client.images.get(target_image_hash_name) image.tag(target_image_repo, target_image_tag) @@ -183,24 +215,32 @@ def _build_sandbox_image( f'Re-tagged image [{target_image_hash_name}] with more generic tag [{target_image_tag}]' ) - # check if the image is built successfully + # Check if the image is built successfully image = docker_client.images.get(target_image_hash_name) if image is None: - raise RuntimeError( - f'Build failed: Image [{target_image_repo}:{target_image_hash_tag}] not found' - ) + raise RuntimeError(f'Build failed: Image {target_image_hash_name} not found') logger.info( - f'Image [{target_image_repo}:{target_image_hash_tag}] (hash: [{target_image_tag}]) built successfully' + f'Image {target_image_repo} with tags [{target_image_hash_tag}, {target_image_tag}] built successfully' ) return target_image_hash_name def get_runtime_image_repo_and_tag(base_image: str) -> tuple[str, str]: + """Retrieves the Docker repo and tag associated with the Docker image. + + Parameters: + - base_image (str): The name of the base Docker image + + Returns: + - tuple[str, str]: The Docker repo and tag of the Docker image + """ + if RUNTIME_IMAGE_REPO in base_image: logger.info( f'The provided image [{base_image}] is a already a valid od_runtime image.\n' f'Will try to reuse it as is.' ) + if ':' not in base_image: base_image = base_image + ':latest' repo, tag = base_image.split(':') @@ -217,9 +257,14 @@ def get_runtime_image_repo_and_tag(base_image: str) -> tuple[str, str]: def _check_image_exists(image_name: str, docker_client: docker.DockerClient) -> bool: """Check if the image exists in the registry (try to pull it first) AND in the local store. - image_name is f'{repo}:{tag}' + Parameters: + - image_name (str): The Docker image to check (:) + - docker_client (docker.DockerClient): The Docker client + + Returns: + - bool: Whether the Docker image exists in the registry and in the local store """ - # Try to pull the new image from the registry + # Try to pull the Docker image from the registry try: docker_client.images.pull(image_name) except Exception: @@ -241,12 +286,22 @@ def build_runtime_image( dry_run: bool = False, force_rebuild: bool = False, ) -> str: - """Build the runtime image for the OpenDevin runtime. + """Prepares the final docker build folder. + If dry_run is False, it will also build the OpenDevin runtime Docker image using the docker build folder. + + Parameters: + - base_image (str): The name of the base Docker image to use + - docker_client (docker.DockerClient): The Docker client + - extra_deps (str): + - docker_build_folder (str): The directory to use for the build. If not provided a temporary directory will be used + - dry_run (bool): if True, it will only ready the build folder. It will not actually build the Docker image + - force_rebuild (bool): if True, it will create the Dockerfile which uses the base_image + + Returns: + - str: :. Where MD5 hash is the hash of the docker build folder See https://docs.all-hands.dev/modules/usage/runtime for more details. """ - runtime_image_repo, runtime_image_tag = get_runtime_image_repo_and_tag(base_image) - # Calculate the hash for the docker build folder (source code and Dockerfile) with tempfile.TemporaryDirectory() as temp_dir: from_scratch_hash = prep_docker_build_folder( @@ -256,34 +311,35 @@ def build_runtime_image( extra_deps=extra_deps, ) - # hash image name, if the hash matches, it means the image is already - # built from scratch with the *exact SAME source code* on the exact Dockerfile + runtime_image_repo, runtime_image_tag = get_runtime_image_repo_and_tag(base_image) + + # The image name in the format : hash_runtime_image_name = f'{runtime_image_repo}:{from_scratch_hash}' - # non-hash generic image name, it could contains *similar* dependencies + # non-hash generic image name, it could contain *similar* dependencies # but *might* not exactly match the state of the source code. # It resembles the "latest" tag in the docker image naming convention for # a particular {repo}:{tag} pair (e.g., ubuntu:latest -> od_runtime:ubuntu_tag_latest) # we will build from IT to save time if the `from_scratch_hash` is not found generic_runtime_image_name = f'{runtime_image_repo}:{runtime_image_tag}' - # 1. If the image exists with the same hash, we will reuse it as is + # Scenario 1: If we already have an image with the exact same hash, then it means the image is already built + # with the exact same source code and Dockerfile, so we will reuse it. Building it is not required. if _check_image_exists(hash_runtime_image_name, docker_client): logger.info( - f'Image [{hash_runtime_image_name}] exists with matched hash for Docker build folder.\n' - 'Will reuse it as is.' + f'Image [{hash_runtime_image_name}] already exists so we will reuse it.' ) return hash_runtime_image_name - # 2. If the exact hash is not found, we will FIRST try to re-build it - # by leveraging the non-hash `generic_runtime_image_name` to save some time + # Scenario 2: If a Docker image with the exact hash is not found, we will FIRST try to re-build it + # by leveraging the `generic_runtime_image_name` to save some time # from re-building the dependencies (e.g., poetry install, apt install) elif ( _check_image_exists(generic_runtime_image_name, docker_client) and not force_rebuild ): logger.info( - f'Cannot find matched hash for image [{hash_runtime_image_name}]\n' + f'Cannot find docker Image [{hash_runtime_image_name}]\n' f'Will try to re-build it from latest [{generic_runtime_image_name}] image to potentially save ' f'time for dependencies installation.\n' ) @@ -297,6 +353,7 @@ def build_runtime_image( skip_init=True, # skip init since we are re-using the existing image extra_deps=extra_deps, ) + assert ( _skip_init_hash != from_scratch_hash ), f'The skip_init hash [{_skip_init_hash}] should not match the existing hash [{from_scratch_hash}]' @@ -317,16 +374,18 @@ def build_runtime_image( logger.info( f'Dry run: Skipping image build for [{generic_runtime_image_name}]' ) + if docker_build_folder is None: shutil.rmtree(cur_docker_build_folder) - # 3. If the image is not found AND we cannot re-use the non-hash latest relavant image, - # we will build it completely from scratch + # Scenario 3: If the Docker image with the required hash is not found AND we cannot re-use the latest + # relevant image, we will build it completely from scratch else: if force_rebuild: logger.info( f'Force re-build: Will try to re-build image [{generic_runtime_image_name}] from scratch.\n' ) + cur_docker_build_folder = docker_build_folder or tempfile.mkdtemp() _new_from_scratch_hash = prep_docker_build_folder( cur_docker_build_folder, @@ -368,17 +427,27 @@ def build_runtime_image( args = parser.parse_args() if args.build_folder is not None: + # If a build_folder is provided, we do not actually build the Docker image. We copy the necessary source code + # and create a Dockerfile dynamically and place it in the build_folder only. This allows the Docker image to + # then be created using the Dockerfile (most likely using the containers/build.sh script) build_folder = args.build_folder assert os.path.exists( build_folder ), f'Build folder {build_folder} does not exist' logger.info( - f'Will prepare a build folder by copying the source code and generating the Dockerfile: {build_folder}' + f'Copying the source code and generating the Dockerfile in the build folder: {build_folder}' ) + runtime_image_repo, runtime_image_tag = get_runtime_image_repo_and_tag( args.base_image ) + logger.info( + f'Runtime image repo: {runtime_image_repo} and runtime image tag: {runtime_image_tag}' + ) + with tempfile.TemporaryDirectory() as temp_dir: + # dry_run is true so we only prepare a temp_dir containing the required source code and the Dockerfile. We + # then obtain the MD5 hash of the folder and return : runtime_image_hash_name = build_runtime_image( args.base_image, docker_client=docker.from_env(), @@ -386,15 +455,19 @@ def build_runtime_image( dry_run=True, force_rebuild=args.force_rebuild, ) + _runtime_image_repo, runtime_image_hash_tag = runtime_image_hash_name.split( ':' ) + # Move contents of temp_dir to build_folder shutil.copytree(temp_dir, build_folder, dirs_exist_ok=True) logger.info( f'Build folder [{build_folder}] is ready: {os.listdir(build_folder)}' ) + # We now update the config.sh in the build_folder to contain the required values. This is used in the + # containers/build.sh script which is called to actually build the Docker image with open(os.path.join(build_folder, 'config.sh'), 'a') as file: file.write( ( @@ -405,10 +478,14 @@ def build_runtime_image( ) ) logger.info( - f'`config.sh` is updated with the new image name [{runtime_image_repo}] and tag [{runtime_image_tag}, {runtime_image_hash_tag}]' + f'`config.sh` is updated with the image repo[{runtime_image_repo}] and tags [{runtime_image_tag}, {runtime_image_hash_tag}]' + ) + logger.info( + f'Dockerfile, source code and config.sh are ready in {build_folder}' ) - logger.info(f'Dockerfile and source distribution are ready in {build_folder}') else: + # If a build_folder is not provided, after copying the required source code and dynamically creating the + # Dockerfile, we actually build the Docker image logger.info('Building image in a temporary folder') client = docker.from_env() image_name = build_runtime_image(args.base_image, client) From 930ee2703709b8e812195e2566f85d330a5a983e Mon Sep 17 00:00:00 2001 From: tofarr Date: Mon, 12 Aug 2024 15:05:59 -0600 Subject: [PATCH 42/46] Collapsible resizers (#3330) * Collapsible resizable divs Co-authored-by: Tim O'Farrell Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com> --- frontend/src/App.tsx | 10 +- frontend/src/components/Resizable.tsx | 138 +++++++++++++++++++++----- 2 files changed, 120 insertions(+), 28 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 08491c805e7c..5cd81ecc815b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -83,19 +83,19 @@ function App(): JSX.Element { className="grow h-full min-h-0 min-w-0 px-3 pt-3" initialSize={500} firstChild={} - firstClassName="min-w-[500px] rounded-xl overflow-hidden border border-neutral-600" + firstClassName="rounded-xl overflow-hidden border border-neutral-600" secondChild={ } - firstClassName="min-h-72 rounded-xl border border-neutral-600 bg-neutral-800 flex flex-col overflow-hidden" + firstClassName="rounded-xl border border-neutral-600 bg-neutral-800 flex flex-col overflow-hidden" secondChild={} - secondClassName="min-h-72 rounded-xl border border-neutral-600 bg-neutral-800" + secondClassName="rounded-xl border border-neutral-600 bg-neutral-800" /> } - secondClassName="flex flex-col overflow-hidden grow min-w-[500px]" + secondClassName="flex flex-col overflow-hidden" />
diff --git a/frontend/src/components/Resizable.tsx b/frontend/src/components/Resizable.tsx index 5bdc98afb2d8..fd82a3b9c53f 100644 --- a/frontend/src/components/Resizable.tsx +++ b/frontend/src/components/Resizable.tsx @@ -1,11 +1,24 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { CSSProperties, useEffect, useRef, useState } from "react"; +import { + VscChevronDown, + VscChevronLeft, + VscChevronRight, + VscChevronUp, +} from "react-icons/vsc"; import { twMerge } from "tailwind-merge"; +import IconButton from "./IconButton"; export enum Orientation { HORIZONTAL = "horizontal", VERTICAL = "vertical", } +enum Collapse { + COLLAPSED = "collapsed", + SPLIT = "split", + FILLED = "filled", +} + type ContainerProps = { firstChild: React.ReactNode; firstClassName: string | undefined; @@ -28,30 +41,40 @@ export function Container({ const [firstSize, setFirstSize] = useState(initialSize); const [dividerPosition, setDividerPosition] = useState(null); const firstRef = useRef(null); + const secondRef = useRef(null); + const [collapse, setCollapse] = useState(Collapse.SPLIT); + const isHorizontal = orientation === Orientation.HORIZONTAL; useEffect(() => { if (dividerPosition == null || !firstRef.current) { return undefined; } const getFirstSizeFromEvent = (e: MouseEvent) => { - const position = - orientation === Orientation.HORIZONTAL ? e.clientX : e.clientY; + const position = isHorizontal ? e.clientX : e.clientY; return firstSize + position - dividerPosition; }; const onMouseMove = (e: MouseEvent) => { e.preventDefault(); - const newFirstSize = getFirstSizeFromEvent(e); + const newFirstSize = `${getFirstSizeFromEvent(e)}px`; const { current } = firstRef; if (current) { - if (orientation === Orientation.HORIZONTAL) { - current.style.width = `${newFirstSize}px`; + if (isHorizontal) { + current.style.width = newFirstSize; + current.style.minWidth = newFirstSize; } else { - current.style.height = `${newFirstSize}px`; + current.style.height = newFirstSize; + current.style.minHeight = newFirstSize; } } }; const onMouseUp = (e: MouseEvent) => { e.preventDefault(); + if (firstRef.current) { + firstRef.current.style.transition = ""; + } + if (secondRef.current) { + secondRef.current.style.transition = ""; + } setFirstSize(getFirstSizeFromEvent(e)); setDividerPosition(null); document.removeEventListener("mousemove", onMouseMove); @@ -67,33 +90,102 @@ export function Container({ const onMouseDown = (e: React.MouseEvent) => { e.preventDefault(); - const position = - orientation === Orientation.HORIZONTAL ? e.clientX : e.clientY; + if (firstRef.current) { + firstRef.current.style.transition = "none"; + } + if (secondRef.current) { + secondRef.current.style.transition = "none"; + } + const position = isHorizontal ? e.clientX : e.clientY; setDividerPosition(position); }; const getStyleForFirst = () => { - if (orientation === Orientation.HORIZONTAL) { - return { width: `${firstSize}px` }; + const style: CSSProperties = { overflow: "hidden" }; + if (collapse === Collapse.COLLAPSED) { + style.opacity = 0; + style.width = 0; + style.minWidth = 0; + style.height = 0; + style.minHeight = 0; + } else if (collapse === Collapse.SPLIT) { + const firstSizePx = `${firstSize}px`; + if (isHorizontal) { + style.width = firstSizePx; + style.minWidth = firstSizePx; + } else { + style.height = firstSizePx; + style.minHeight = firstSizePx; + } + } else { + style.flexGrow = 1; + } + return style; + }; + + const getStyleForSecond = () => { + const style: CSSProperties = { overflow: "hidden" }; + if (collapse === Collapse.FILLED) { + style.opacity = 0; + style.width = 0; + style.minWidth = 0; + style.height = 0; + style.minHeight = 0; + } else if (collapse === Collapse.SPLIT) { + style.flexGrow = 1; + } else { + style.flexGrow = 1; + } + return style; + }; + + const onCollapse = () => { + if (collapse === Collapse.SPLIT) { + setCollapse(Collapse.COLLAPSED); + } else { + setCollapse(Collapse.SPLIT); + } + }; + + const onExpand = () => { + if (collapse === Collapse.SPLIT) { + setCollapse(Collapse.FILLED); + } else { + setCollapse(Collapse.SPLIT); } - return { height: `${firstSize}px` }; }; return ( -
-
+
+
{firstChild}
-
{secondChild}
+ className={`${isHorizontal ? "cursor-ew-resize w-3 flex-col" : "cursor-ns-resize h-3 flex-row"} shrink-0 flex justify-center items-center`} + onMouseDown={collapse === Collapse.SPLIT ? onMouseDown : undefined} + > + : } + ariaLabel="Collapse" + onClick={onCollapse} + /> + : } + ariaLabel="Expand" + onClick={onExpand} + /> +
+
+ {secondChild} +
); } From 86d933f1b02227a28a38ccfd7a31c49ebbe1ec73 Mon Sep 17 00:00:00 2001 From: Yufan Song <33971064+yufansong@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:34:17 -0700 Subject: [PATCH 43/46] remove useless code (#3355) --- opendevin/runtime/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/opendevin/runtime/__init__.py b/opendevin/runtime/__init__.py index 04fb9789f9fb..a189cb13ea91 100644 --- a/opendevin/runtime/__init__.py +++ b/opendevin/runtime/__init__.py @@ -17,6 +17,5 @@ def get_runtime_cls(name: str): __all__ = [ 'E2BBox', - 'Sandbox', 'get_runtime_cls', ] From c73016c551df836120bdd52387a6597a7522b58a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:42:57 -0400 Subject: [PATCH 44/46] chore(deps-dev): bump @docusaurus/types from 3.4.0 to 3.5.1 in /docs (#3344) Bumps [@docusaurus/types](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-types) from 3.4.0 to 3.5.1. - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.5.1/packages/docusaurus-types) --- updated-dependencies: - dependency-name: "@docusaurus/types" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 269 ++++++++++++++++++++++++++++++++++------- docs/package.json | 2 +- 2 files changed, 225 insertions(+), 46 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 1e8a6ae54927..03405741b682 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -23,7 +23,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.5.1", "@docusaurus/tsconfig": "^3.4.0", - "@docusaurus/types": "^3.4.0", + "@docusaurus/types": "^3.5.1", "typescript": "~5.5.4" }, "engines": { @@ -2310,27 +2310,6 @@ "react-dom": "*" } }, - "node_modules/@docusaurus/module-type-aliases/node_modules/@docusaurus/types": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.1.tgz", - "integrity": "sha512-IXTGQBoXAGFliGF5Cn3F+gSGskgzAL8+4y6dDY1gcePA0r8WngHj8oovS1YPv+b9JOff32nv8YGGZITHOMXJsA==", - "dev": true, - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "^1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/@docusaurus/plugin-content-blog": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", @@ -2362,6 +2341,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-content-blog/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-content-docs": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz", @@ -2410,6 +2409,26 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/plugin-content-docs/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-content-pages": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.5.1.tgz", @@ -2581,26 +2600,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/types": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.1.tgz", - "integrity": "sha512-IXTGQBoXAGFliGF5Cn3F+gSGskgzAL8+4y6dDY1gcePA0r8WngHj8oovS1YPv+b9JOff32nv8YGGZITHOMXJsA==", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "^1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/utils": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.5.1.tgz", @@ -2696,6 +2695,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-debug/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-google-analytics": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz", @@ -2714,6 +2733,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-google-gtag": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz", @@ -2733,6 +2772,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-google-gtag/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-google-tag-manager": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz", @@ -2751,6 +2810,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-google-tag-manager/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/plugin-sitemap": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz", @@ -2774,6 +2853,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/plugin-sitemap/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/preset-classic": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz", @@ -2823,6 +2922,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-classic": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", @@ -2902,6 +3021,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-common": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", @@ -2971,6 +3110,26 @@ "react-dom": "^18.0.0" } }, + "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-mermaid": { "version": "3.4.0", "resolved": "https://registry.npmmirror.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz", @@ -3010,6 +3169,26 @@ "react-dom": "*" } }, + "node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@docusaurus/theme-search-algolia": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", @@ -3059,9 +3238,9 @@ "dev": true }, "node_modules/@docusaurus/types": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", - "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.5.1.tgz", + "integrity": "sha512-IXTGQBoXAGFliGF5Cn3F+gSGskgzAL8+4y6dDY1gcePA0r8WngHj8oovS1YPv+b9JOff32nv8YGGZITHOMXJsA==", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", diff --git a/docs/package.json b/docs/package.json index aa2094db1ed2..38ef389bfc26 100644 --- a/docs/package.json +++ b/docs/package.json @@ -30,7 +30,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.5.1", "@docusaurus/tsconfig": "^3.4.0", - "@docusaurus/types": "^3.4.0", + "@docusaurus/types": "^3.5.1", "typescript": "~5.5.4" }, "browserslist": { From ea8397ff89d279e57b943f6d592f3e62d85c5852 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 02:30:42 +0000 Subject: [PATCH 45/46] chore(deps-dev): bump @docusaurus/tsconfig from 3.4.0 to 3.5.1 in /docs (#3342) Bumps [@docusaurus/tsconfig](https://github.com/facebook/docusaurus/tree/HEAD/packages/docusaurus-tsconfig) from 3.4.0 to 3.5.1. - [Release notes](https://github.com/facebook/docusaurus/releases) - [Changelog](https://github.com/facebook/docusaurus/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/docusaurus/commits/v3.5.1/packages/docusaurus-tsconfig) --- updated-dependencies: - dependency-name: "@docusaurus/tsconfig" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/package-lock.json | 8 ++++---- docs/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/package-lock.json b/docs/package-lock.json index 03405741b682..d216ddd9396f 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.5.1", - "@docusaurus/tsconfig": "^3.4.0", + "@docusaurus/tsconfig": "^3.5.1", "@docusaurus/types": "^3.5.1", "typescript": "~5.5.4" }, @@ -3232,9 +3232,9 @@ } }, "node_modules/@docusaurus/tsconfig": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.4.0.tgz", - "integrity": "sha512-0qENiJ+TRaeTzcg4olrnh0BQ7eCxTgbYWBnWUeQDc84UYkt/T3pDNnm3SiQkqPb+YQ1qtYFlC0RriAElclo8Dg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.5.1.tgz", + "integrity": "sha512-6OO63/xQ11Tu4reCRuB4zfjqdZYmQwkOTVI8zxxEHCLma4pplsx4HTCB2lVgztEL+Qr6hcHY952ZrpmoAt5rUA==", "dev": true }, "node_modules/@docusaurus/types": { diff --git a/docs/package.json b/docs/package.json index 38ef389bfc26..9d658d88ba51 100644 --- a/docs/package.json +++ b/docs/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.5.1", - "@docusaurus/tsconfig": "^3.4.0", + "@docusaurus/tsconfig": "^3.5.1", "@docusaurus/types": "^3.5.1", "typescript": "~5.5.4" }, From 20a35c59f630d6d3251053c053c4f5f25855007d Mon Sep 17 00:00:00 2001 From: Graham Neubig Date: Tue, 13 Aug 2024 01:38:41 -0400 Subject: [PATCH 46/46] Group docusaurus dependabot updates (#3359) --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2669165c607b..6a565838e580 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,3 +20,8 @@ updates: schedule: interval: "daily" open-pull-requests-limit: 20 + +groups: + docusaurus: + patterns: + - "*docusaurus*"