Compare commits

..

2 Commits

Author SHA1 Message Date
Graham Neubig
4483c7ba83 Remove dead code 2024-08-12 22:45:46 -04:00
Graham Neubig
dfc36eb861 Add tests for agent controller 2024-08-12 21:26:36 -04:00
1102 changed files with 41296 additions and 59034 deletions

View File

@@ -1 +1 @@
The files in this directory configure a development container for GitHub Codespaces.
The files in this directory configure a development container for GitHub Codespaces.

View File

@@ -1,5 +1,5 @@
{
"name": "OpenHands Codespaces",
"name": "OpenDevin Codespaces",
"image": "mcr.microsoft.com/devcontainers/universal",
"customizations":{
"vscode":{

View File

@@ -2,5 +2,7 @@
sudo apt update
sudo apt install -y netcat
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt install -y python3.12
curl -sSL https://install.python-poetry.org | python3.12 -
sudo apt install -y python3.11
curl -sSL https://install.python-poetry.org | python3.11 -
# chromadb requires SQLite > 3.35 but SQLite in Python3.11.9 comes with 3.31.1
sudo cp /opt/conda/lib/libsqlite3.so.0 /lib/x86_64-linux-gnu/libsqlite3.so.0

View File

@@ -1,59 +1,75 @@
name: Bug
description: Report a problem with OpenHands
description: Report a problem with OpenDevin
title: '[Bug]: '
labels: ['bug']
body:
- type: markdown
attributes:
value: Thank you for taking the time to fill out this bug report. Please provide as much information as possible to help us understand and address the issue effectively.
value: Thank you for taking the time to fill out this bug report. We greatly appreciate your effort to complete this template fully. Please provide as much information as possible to help us understand and address the issue effectively.
- type: checkboxes
attributes:
label: Is there an existing issue for the same bug?
description: Please check if an issue already exists for the bug you encountered.
options:
- label: I have checked the troubleshooting document at https://docs.all-hands.dev/modules/usage/troubleshooting
required: true
- label: I have checked the existing issues.
required: true
- type: textarea
id: bug-description
attributes:
label: Describe the bug and reproduction steps
description: Provide a description of the issue along with any reproduction steps.
label: Describe the bug
description: Provide a short description of the problem.
validations:
required: true
- type: dropdown
id: installation
- type: textarea
id: current-version
attributes:
label: OpenHands Installation
description: How are you running OpenHands?
options:
- Docker command in README
- Development workflow
default: 0
label: Current OpenDevin version
description: What version of OpenDevin are you using? If you're running in docker, tell us the tag you're using (e.g. ghcr.io/opendevin/opendevin:0.3.1).
render: bash
validations:
required: true
- type: input
id: openhands-version
- type: textarea
id: config
attributes:
label: OpenHands Version
description: What version of OpenHands are you using?
placeholder: ex. 0.9.8, main, etc.
label: Installation and Configuration
description: Please provide any commands you ran and any configuration (redacting API keys)
render: bash
validations:
required: true
- type: dropdown
id: os
- type: textarea
id: model-agent
attributes:
label: Model and Agent
description: What model and agent are you using? You can see these settings in the UI by clicking the settings wheel.
placeholder: |
- Model:
- Agent:
- type: textarea
id: os-version
attributes:
label: Operating System
options:
- MacOS
- Linux
- WSL on Windows
description: What Operating System are you using? Linux, Mac OS, WSL on Windows
- type: textarea
id: repro-steps
attributes:
label: Reproduction Steps
description: Please list the steps to reproduce the issue.
placeholder: |
1.
2.
3.
- type: textarea
id: additional-context
attributes:
label: Logs, Errors, Screenshots, and Additional Context
description: Please provide any additional information you think might help. If you want to share the chat history
you can click the thumbs-down (👎) button above the input field and you will get a shareable link
(you can also click thumbs up when things are going well of course!). LLM logs will be stored in the
`logs/llm/default` folder. Please add any additional context about the problem here.
description: If you want to share the chat history you can click the thumbs-down (👎) button above the input field and you will get a shareable link (you can also click thumbs up when things are going well of course!). LLM logs will be stored in the `logs/llm/default` folder. Please add any additional context about the problem here.

View File

@@ -1,6 +1,6 @@
---
name: Feature Request
about: Suggest an idea for OpenHands features
about: Suggest an idea for OpenDevin features
title: ''
labels: 'enhancement'
assignees: ''

View File

@@ -5,34 +5,18 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
open-pull-requests-limit: 20
- package-ecosystem: "npm"
directory: "/frontend"
- package-ecosystem: "npm" # See documentation for possible values
directory: "/frontend" # Location of package manifests
schedule:
interval: "daily"
open-pull-requests-limit: 20
groups:
docusaurus:
patterns:
- "*docusaurus*"
eslint:
patterns:
- "*eslint*"
- package-ecosystem: "npm"
directory: "/docs"
- package-ecosystem: "npm" # See documentation for possible values
directory: "/docs" # Location of package manifests
schedule:
interval: "daily"
open-pull-requests-limit: 20
groups:
docusaurus:
patterns:
- "*docusaurus*"
eslint:
patterns:
- "*eslint*"

View File

@@ -1,6 +1,6 @@
**End-user friendly description of the problem this fixes or functionality that this introduces**
**What is the problem that this fixes or functionality that this introduces? Does it fix any open issues?**
- [ ] Include this change in the Release Notes. If checked, you must provide an **end-user friendly** description for your change below
---
**Give a summary of what the PR does, explaining any non-trivial design decisions**
@@ -8,4 +8,4 @@
---
**Link of any specific issues this addresses**
**Other references**

View File

@@ -1,7 +1,6 @@
# Workflow that cleans up outdated and old workflows to prevent out of disk issues
name: Delete old workflow runs
# This workflow is currently only triggered manually
on:
workflow_dispatch:
inputs:

View File

@@ -1,8 +1,6 @@
# Workflow that builds and deploys the documentation website
name: Deploy Docs to GitHub Pages
# * Always run on "main"
# * Run on PRs that target the "main" branch and have changes in the "docs" folder or this workflow
on:
push:
branches:
@@ -10,21 +8,15 @@ on:
pull_request:
paths:
- 'docs/**'
- '.github/workflows/deploy-docs.yml'
branches:
- main
# If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
concurrency:
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
cancel-in-progress: true
jobs:
# Build the documentation website
build:
if: github.repository == 'All-Hands-AI/OpenHands'
name: Build Docusaurus
runs-on: ubuntu-latest
if: github.repository == 'OpenDevin/OpenDevin'
steps:
- uses: actions/checkout@v4
with:
@@ -37,7 +29,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.11'
- name: Generate Python Docs
run: rm -rf docs/modules/python && pip install pydoc-markdown && pydoc-markdown
- name: Install dependencies
@@ -52,14 +44,10 @@ jobs:
# Deploy the documentation website
deploy:
if: github.ref == 'refs/heads/main' && github.repository == 'All-Hands-AI/OpenHands'
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
# This job only runs on "main" so only run one of these jobs at a time
# otherwise it will fail if one is already running
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
needs: build
if: github.ref == 'refs/heads/main' && github.repository == 'OpenDevin/OpenDevin'
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
pages: write # to deploy to Pages

View File

@@ -1,56 +1,35 @@
# Workflow that uses the DummyAgent to run a simple task
name: Run E2E test with dummy agent
# Always run on "main"
# Always run on PRs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
on:
push:
branches:
- main
pull_request:
# If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
concurrency:
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'poetry'
- name: Install Python dependencies using Poetry
run: poetry install --without evaluation,llama-index
- name: Build Environment
run: make build
python-version: '3.11'
- name: Set up environment
run: |
curl -sSL https://install.python-poetry.org | python3 -
poetry install --without evaluation,llama-index
poetry run playwright install --with-deps chromium
wget https://huggingface.co/BAAI/bge-small-en-v1.5/raw/main/1_Pooling/config.json -P /tmp/llama_index/models--BAAI--bge-small-en-v1.5/snapshots/5c38ec7c405ec4b44b94cc5a9bb96e735b38267a/1_Pooling/
- name: Run tests
run: |
set -e
SANDBOX_FORCE_REBUILD_RUNTIME=True poetry run python3 openhands/core/main.py -t "do a flip" -d ./workspace/ -c DummyAgent
poetry run python opendevin/core/main.py -t "do a flip" -d ./workspace/ -c DummyAgent
- name: Check exit code
run: |
if [ $? -ne 0 ]; then

View File

@@ -1,44 +0,0 @@
# Workflow that runs frontend unit tests
name: Run Frontend Unit Tests
# * Always run on "main"
# * Run on PRs that have changes in the "frontend" folder or this workflow
on:
push:
branches:
- main
pull_request:
paths:
- 'frontend/**'
- '.github/workflows/fe-unit-tests.yml'
# If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
concurrency:
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
cancel-in-progress: true
jobs:
# Run frontend unit tests
fe-test:
name: FE Unit Tests
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
working-directory: ./frontend
run: npm ci
- name: Run tests and collect coverage
working-directory: ./frontend
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,481 +0,0 @@
# Workflow that builds, tests and then pushes the OpenHands and runtime docker images to the ghcr.io repository
name: Build, Test and Publish RT Image
# Always run on "main"
# Always run on tags
# Always run on PRs
# Can also be triggered manually
on:
push:
branches:
- main
tags:
- '*'
pull_request:
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual trigger'
required: true
default: ''
# If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
concurrency:
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
cancel-in-progress: true
env:
BASE_IMAGE_FOR_HASH_EQUIVALENCE_TEST: nikolaik/python-nodejs:python3.12-nodejs22
RELEVANT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
jobs:
# Builds the OpenHands Docker images
ghcr_build_app:
name: Build App Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
hash_from_app_image: ${{ steps.get_hash_in_app_image.outputs.hash_from_app_image }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
with:
image: tonistiigi/binfmt:latest
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Build and push app image
if: "!github.event.pull_request.head.repo.fork"
run: |
./containers/build.sh -i openhands -o ${{ github.repository_owner }} --push
- name: Build app image
if: "github.event.pull_request.head.repo.fork"
run: |
./containers/build.sh -i openhands -o ${{ github.repository_owner }} --load
- name: Get hash in App Image
id: get_hash_in_app_image
run: |
# Lowercase the repository owner
export REPO_OWNER=${{ github.repository_owner }}
REPO_OWNER=$(echo $REPO_OWNER | tr '[:upper:]' '[:lower:]')
# Run the build script in the app image
docker run -e SANDBOX_USER_ID=0 -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/${REPO_OWNER}/openhands:${{ env.RELEVANT_SHA }} /bin/bash -c "mkdir -p containers/runtime; python3 openhands/runtime/utils/runtime_build.py --base_image ${{ env.BASE_IMAGE_FOR_HASH_EQUIVALENCE_TEST }} --build_folder containers/runtime --force_rebuild" 2>&1 | tee docker-outputs.txt
# Get the hash from the build script
hash_from_app_image=$(cat docker-outputs.txt | grep "Hash for docker build directory" | awk -F "): " '{print $2}' | uniq | head -n1)
echo "hash_from_app_image=$hash_from_app_image" >> $GITHUB_OUTPUT
echo "Hash from app image: $hash_from_app_image"
# This test should move when we have a test suite for the app image
- name: Test docker in App Image
run: |
# Lowercase the repository owner
export REPO_OWNER=${{ github.repository_owner }}
REPO_OWNER=$(echo $REPO_OWNER | tr '[:upper:]' '[:lower:]')
docker run -e SANDBOX_USER_ID=0 -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/${REPO_OWNER}/openhands:${{ env.RELEVANT_SHA }} /bin/bash -c "docker run hello-world"
# Builds the runtime Docker images
ghcr_build_runtime:
name: Build Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
matrix:
base_image:
- image: 'nikolaik/python-nodejs:python3.12-nodejs22'
tag: nikolaik
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
with:
image: tonistiigi/binfmt:latest
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Create source distribution and Dockerfile
run: poetry run python3 openhands/runtime/utils/runtime_build.py --base_image ${{ matrix.base_image.image }} --build_folder containers/runtime --force_rebuild
- name: Build and push runtime image ${{ matrix.base_image.image }}
if: github.event.pull_request.head.repo.fork != true
run: |
./containers/build.sh -i runtime -o ${{ github.repository_owner }} --push -t ${{ matrix.base_image.tag }}
# Forked repos can't push to GHCR, so we need to upload the image as an artifact
- name: Build runtime image ${{ matrix.base_image.image }} for fork
if: github.event.pull_request.head.repo.fork
uses: docker/build-push-action@v6
with:
tags: ghcr.io/all-hands-ai/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image.tag }}
outputs: type=docker,dest=/tmp/runtime-${{ matrix.base_image.tag }}.tar
context: containers/runtime
- name: Upload runtime image for fork
if: github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@v4
with:
name: runtime-${{ matrix.base_image.tag }}
path: /tmp/runtime-${{ matrix.base_image.tag }}.tar
verify_hash_equivalence_in_runtime_and_app:
name: Verify Hash Equivalence in Runtime and Docker images
runs-on: ubuntu-latest
needs: [ghcr_build_runtime, ghcr_build_app]
strategy:
fail-fast: false
matrix:
base_image: ['nikolaik']
steps:
- uses: actions/checkout@v4
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Get hash in App Image
run: |
echo "Hash from app image: ${{ needs.ghcr_build_app.outputs.hash_from_app_image }}"
echo "hash_from_app_image=${{ needs.ghcr_build_app.outputs.hash_from_app_image }}" >> $GITHUB_ENV
- name: Get hash using code (development mode)
run: |
mkdir -p containers/runtime
poetry run python3 openhands/runtime/utils/runtime_build.py --base_image ${{ env.BASE_IMAGE_FOR_HASH_EQUIVALENCE_TEST }} --build_folder containers/runtime --force_rebuild > output.txt 2>&1
hash_from_code=$(cat output.txt | grep "Hash for docker build directory" | awk -F "): " '{print $2}' | uniq | head -n1)
echo "hash_from_code=$hash_from_code" >> $GITHUB_ENV
- name: Compare hashes
run: |
echo "Hash from App Image: ${{ env.hash_from_app_image }}"
echo "Hash from Code: ${{ env.hash_from_code }}"
if [ "${{ env.hash_from_app_image }}" = "${{ env.hash_from_code }}" ]; then
echo "Hashes match!"
else
echo "Hashes do not match!"
exit 1
fi
# Run unit tests with the EventStream runtime Docker images as root
test_runtime_root:
name: RT Unit Tests (Root)
needs: [ghcr_build_runtime]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
base_image: ['nikolaik']
steps:
- uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
# Forked repos can't push to GHCR, so we need to download the image as an artifact
- name: Download runtime image for fork
if: github.event.pull_request.head.repo.fork
uses: actions/download-artifact@v4
with:
name: runtime-${{ matrix.base_image }}
path: /tmp
- name: Load runtime image for fork
if: github.event.pull_request.head.repo.fork
run: |
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Run runtime tests
run: |
# We install pytest-xdist in order to run tests across CPUs
poetry run pip install pytest-xdist
# Install to be able to retry on failures for flaky tests
poetry run pip install pytest-rerunfailures
image_name=ghcr.io/${{ github.repository_owner }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
image_name=$(echo $image_name | tr '[:upper:]' '[:lower:]')
SKIP_CONTAINER_LOGS=true \
TEST_RUNTIME=eventstream \
SANDBOX_USER_ID=$(id -u) \
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
RUN_AS_OPENHANDS=false \
poetry run pytest -n 3 -raRs --reruns 2 --reruns-delay 5 --cov=openhands --cov-report=xml -s ./tests/runtime
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Run unit tests with the EventStream runtime Docker images as openhands user
test_runtime_oh:
name: RT Unit Tests (openhands)
runs-on: ubuntu-latest
needs: [ghcr_build_runtime]
strategy:
matrix:
base_image: ['nikolaik']
steps:
- uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
# Forked repos can't push to GHCR, so we need to download the image as an artifact
- name: Download runtime image for fork
if: github.event.pull_request.head.repo.fork
uses: actions/download-artifact@v4
with:
name: runtime-${{ matrix.base_image }}
path: /tmp
- name: Load runtime image for fork
if: github.event.pull_request.head.repo.fork
run: |
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Run runtime tests
run: |
# We install pytest-xdist in order to run tests across CPUs
poetry run pip install pytest-xdist
# Install to be able to retry on failures for flaky tests
poetry run pip install pytest-rerunfailures
image_name=ghcr.io/${{ github.repository_owner }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
image_name=$(echo $image_name | tr '[:upper:]' '[:lower:]')
SKIP_CONTAINER_LOGS=true \
TEST_RUNTIME=eventstream \
SANDBOX_USER_ID=$(id -u) \
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
RUN_AS_OPENHANDS=true \
poetry run pytest -n 3 -raRs --reruns 2 --reruns-delay 5 --cov=openhands --cov-report=xml -s ./tests/runtime
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Run integration tests with the eventstream runtime Docker image
runtime_integration_tests_on_linux:
name: RT Integration Tests (Linux)
runs-on: ubuntu-latest
needs: [ghcr_build_runtime]
strategy:
fail-fast: false
matrix:
base_image: ['nikolaik']
steps:
- uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
# Forked repos can't push to GHCR, so we need to download the image as an artifact
- name: Download runtime image for fork
if: github.event.pull_request.head.repo.fork
uses: actions/download-artifact@v4
with:
name: runtime-${{ matrix.base_image }}
path: /tmp
- name: Load runtime image for fork
if: github.event.pull_request.head.repo.fork
run: |
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Run integration tests
run: |
image_name=ghcr.io/${{ github.repository_owner }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
image_name=$(echo $image_name | tr '[:upper:]' '[:lower:]')
TEST_RUNTIME=eventstream \
SANDBOX_USER_ID=$(id -u) \
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
TEST_ONLY=true \
./tests/integration/regenerate.sh
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# The two following jobs (named identically) are to check whether all the runtime tests have passed as the
# "All Runtime Tests Passed" is a required job for PRs to merge
# Due to this bug: https://github.com/actions/runner/issues/2566, we want to create a job that runs when the
# prerequisites have been cancelled or failed so merging is disallowed, otherwise Github considers "skipped" as "success"
runtime_tests_check_success:
name: All Runtime Tests Passed
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
runs-on: ubuntu-latest
needs: [test_runtime_root, test_runtime_oh, runtime_integration_tests_on_linux, verify_hash_equivalence_in_runtime_and_app]
steps:
- name: All tests passed
run: echo "All runtime tests have passed successfully!"
runtime_tests_check_fail:
name: All Runtime Tests Passed
if: ${{ cancelled() || contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
runs-on: ubuntu-latest
needs: [test_runtime_root, test_runtime_oh, runtime_integration_tests_on_linux, verify_hash_equivalence_in_runtime_and_app]
steps:
- name: Some tests failed
run: |
echo "Some runtime tests failed or were cancelled"
exit 1

430
.github/workflows/ghcr.yml vendored Normal file
View File

@@ -0,0 +1,430 @@
# Workflow that builds, tests and then pushes the docker images to the ghcr.io repository
name: Build Publish and Test Runtime Image
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
on:
push:
branches:
- main
tags:
- '*'
pull_request:
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual trigger'
required: true
default: ''
jobs:
# Builds the OpenDevin Docker images
ghcr_build:
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.capture-tags.outputs.tags }}
permissions:
contents: read
packages: write
strategy:
matrix:
image: ['opendevin']
platform: ['amd64', 'arm64']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Build and export image
id: build
run: ./containers/build.sh ${{ matrix.image }} ${{ github.repository_owner }} ${{ matrix.platform }}
- name: Capture tags
id: capture-tags
run: |
tags=$(cat tags.txt)
echo "tags=$tags"
echo "tags=$tags" >> $GITHUB_OUTPUT
- name: Upload Docker image as artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.image }}-docker-image-${{ matrix.platform }}
path: /tmp/${{ matrix.image }}_image_${{ matrix.platform }}.tar
retention-days: 14
# Builds the runtime Docker images
ghcr_build_runtime:
runs-on: ubuntu-latest
outputs:
tags: ${{ steps.capture-tags.outputs.tags }}
permissions:
contents: read
packages: write
strategy:
matrix:
image: ['od_runtime']
base_image: ['nikolaik/python-nodejs:python3.11-nodejs22']
platform: ['amd64', 'arm64']
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'poetry'
- 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 --force_rebuild
- name: Build and export image
id: build
run: |
if [ -f 'containers/runtime/Dockerfile' ]; then
echo 'Dockerfile detected, building runtime image...'
./containers/build.sh ${{ matrix.image }} ${{ github.repository_owner }} ${{ matrix.platform }}
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 "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
fi
- name: Capture tags
id: capture-tags
run: |
tags=$(cat tags.txt)
echo "tags=$tags"
echo "tags=$tags" >> $GITHUB_OUTPUT
- name: Upload Docker image as artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.image }}-docker-image-${{ matrix.platform }}
path: /tmp/${{ matrix.image }}_image_${{ matrix.platform }}.tar
retention-days: 14
# Run unit tests with the EventStream and Server runtime Docker images
test_runtime:
name: Test Runtime
runs-on: ubuntu-latest
needs: [ghcr_build_runtime, ghcr_build]
strategy:
matrix:
runtime_type: ['eventstream']
steps:
- uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# when set to "true" but frees about 6 GB
tool-cache: true
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'poetry'
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Download Runtime Docker image
if: matrix.runtime_type == 'eventstream'
uses: actions/download-artifact@v4
with:
name: od_runtime-docker-image-amd64
path: /tmp/
- name: Download Sandbox Docker image
if: matrix.runtime_type == 'server'
uses: actions/download-artifact@v4
with:
name: sandbox-docker-image-amd64
path: /tmp/
- name: Load Runtime image and run runtime tests
run: |
# Load the Docker image and capture the output
if [ "${{ matrix.runtime_type }}" == "eventstream" ]; then
output=$(docker load -i /tmp/od_runtime_image_amd64.tar)
else
output=$(docker load -i /tmp/sandbox_image_amd64.tar)
fi
# Extract the first image name from the output
image_name=$(echo "$output" | grep -oP 'Loaded image: \K.*' | head -n 1)
# Print the full name of the image
echo "Loaded Docker image: $image_name"
TEST_RUNTIME=${{ matrix.runtime_type }} SANDBOX_USER_ID=$(id -u) SANDBOX_CONTAINER_IMAGE=$image_name TEST_IN_CI=true poetry run pytest --cov=agenthub --cov=opendevin --cov-report=xml -s ./tests/unit/test_runtime.py
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Run integration tests with the eventstream runtime Docker image
runtime_integration_tests_on_linux:
name: Runtime Integration Tests on Linux
runs-on: ubuntu-latest
needs: [ghcr_build_runtime]
strategy:
fail-fast: false
matrix:
python-version: ['3.11']
# server is tested in a separate workflow
runtime_type: ['eventstream']
steps:
- uses: actions/checkout@v4
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Download Runtime Docker image
uses: actions/download-artifact@v4
with:
name: od_runtime-docker-image-amd64
path: /tmp/
- name: Load runtime image and run integration tests
run: |
# Load the Docker image and capture the output
if [ "${{ matrix.runtime_type }}" == "eventstream" ]; then
output=$(docker load -i /tmp/od_runtime_image_amd64.tar)
else
echo "No Runtime Docker image to load"
exit 1
fi
# Extract the first image name from the output
image_name=$(echo "$output" | grep -oP 'Loaded image: \K.*' | head -n 1)
# Print the full name of the image
echo "Loaded Docker image: $image_name"
TEST_RUNTIME=${{ matrix.runtime_type }} SANDBOX_USER_ID=$(id -u) SANDBOX_CONTAINER_IMAGE=$image_name TEST_IN_CI=true TEST_ONLY=true ./tests/integration/regenerate.sh
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Push the OpenDevin and sandbox Docker images to the ghcr.io repository
ghcr_push:
runs-on: ubuntu-latest
needs: [ghcr_build]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
env:
tags: ${{ needs.ghcr_build.outputs.tags }}
permissions:
contents: read
packages: write
strategy:
matrix:
image: ['opendevin']
platform: ['amd64', 'arm64']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Download Docker images
uses: actions/download-artifact@v4
with:
name: ${{ matrix.image }}-docker-image-${{ matrix.platform }}
path: /tmp/${{ matrix.platform }}
- name: Load images and push to registry
run: |
mv /tmp/${{ matrix.platform }}/${{ matrix.image }}_image_${{ matrix.platform }}.tar .
loaded_image=$(docker load -i ${{ matrix.image }}_image_${{ matrix.platform }}.tar | grep "Loaded image:" | head -n 1 | awk '{print $3}')
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 "tag = $tag"
docker tag $loaded_image $image_name:${tag}_${{ matrix.platform }}
docker push $image_name:${tag}_${{ matrix.platform }}
done
# Push the runtime Docker images to the ghcr.io repository
ghcr_push_runtime:
runs-on: ubuntu-latest
needs: [ghcr_build_runtime, test_runtime, runtime_integration_tests_on_linux]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
env:
RUNTIME_TAGS: ${{ needs.ghcr_build_runtime.outputs.tags }}
permissions:
contents: read
packages: write
strategy:
matrix:
image: ['od_runtime']
platform: ['amd64', 'arm64']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
tool-cache: true
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: false
swap-storage: true
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Download Docker images
uses: actions/download-artifact@v4
with:
name: ${{ matrix.image }}-docker-image-${{ matrix.platform }}
path: /tmp/${{ matrix.platform }}
- name: List downloaded files
run: |
ls -la /tmp/${{ matrix.platform }}
file /tmp/${{ matrix.platform }}/*
- name: Load images and push to registry
run: |
mv /tmp/${{ matrix.platform }}/${{ matrix.image }}_image_${{ matrix.platform }}.tar ./${{ matrix.image }}_image_${{ matrix.platform }}.tar
if ! loaded_image=$(docker load -i ${{ matrix.image }}_image_${{ matrix.platform }}.tar | grep "Loaded image:" | head -n 1 | awk '{print $3}'); then
echo "Failed to load Docker image"
exit 1
fi
echo "loaded image = $loaded_image"
image_name=$(echo "ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}" | tr '[:upper:]' '[:lower:]')
echo "image name = $image_name"
echo "$RUNTIME_TAGS" | tr ' ' '\n' | while read -r tag; do
echo "tag = $tag"
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 or tag"
fi
done
# Creates and pushes the OpenDevin and sandbox Docker image manifests
create_manifest:
runs-on: ubuntu-latest
needs: [ghcr_build, ghcr_push]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
env:
tags: ${{ needs.ghcr_build.outputs.tags }}
strategy:
matrix:
image: ['opendevin']
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push multi-platform manifest
run: |
image_name=$(echo "ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}" | tr '[:upper:]' '[:lower:]')
echo "image name = $image_name"
tags=$(echo ${tags} | tr ' ' '\n')
for tag in $tags; do
echo 'tag = $tag'
docker buildx imagetools create --tag $image_name:$tag \
$image_name:${tag}_amd64 \
$image_name:${tag}_arm64
done
# Creates and pushes the runtime Docker image manifest
create_manifest_runtime:
runs-on: ubuntu-latest
needs: [ghcr_build_runtime, ghcr_push_runtime]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
env:
tags: ${{ needs.ghcr_build_runtime.outputs.tags }}
strategy:
matrix:
image: ['od_runtime']
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push multi-platform manifest
run: |
image_name=$(echo "ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}" | tr '[:upper:]' '[:lower:]')
echo "image name = $image_name"
tags=$(echo ${tags} | tr ' ' '\n')
for tag in $tags; do
echo 'tag = $tag'
docker buildx imagetools create --tag $image_name:$tag \
$image_name:${tag}_amd64 \
$image_name:${tag}_arm64
done

View File

@@ -1,20 +1,16 @@
# Workflow that runs lint on the frontend and python code
name: Lint
# The jobs in this workflow are required, so they must run at all times
# Always run on "main"
# Always run on PRs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
on:
push:
branches:
- main
pull_request:
# If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
concurrency:
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
cancel-in-progress: true
jobs:
# Run lint on the frontend code
lint-frontend:
@@ -46,9 +42,9 @@ jobs:
- name: Set up python
uses: actions/setup-python@v5
with:
python-version: 3.12
python-version: 3.11
cache: 'pip'
- name: Install pre-commit
run: pip install pre-commit==3.7.0
- name: Run pre-commit hooks
run: pre-commit run --files openhands/**/* evaluation/**/* tests/**/* --show-diff-on-failure --config ./dev_config/python/.pre-commit-config.yaml
run: pre-commit run --files opendevin/**/* agenthub/**/* evaluation/**/* tests/**/* --show-diff-on-failure --config ./dev_config/python/.pre-commit-config.yaml

View File

@@ -1,13 +0,0 @@
name: Resolve Issues with OpenHands
on:
issues:
types: [labeled]
jobs:
call-openhands-resolver:
uses: All-Hands-AI/openhands-resolver/.github/workflows/openhands-resolver.yml@main
if: github.event.label.name == 'fix-me'
with:
issue_number: ${{ github.event.issue.number }}
secrets: inherit

View File

@@ -1,31 +0,0 @@
# Publishes the OpenHands PyPi package
name: Publish PyPi Package
# Triggered manually
on:
workflow_dispatch:
inputs:
reason:
description: 'Reason for manual trigger'
required: true
default: ''
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.12
- name: Install Poetry
uses: snok/install-poetry@v1.4.1
with:
virtualenvs-in-project: true
virtualenvs-path: ~/.virtualenvs
- name: Install Poetry Dependencies
run: poetry install --no-interaction --no-root
- name: Build poetry project
run: ./build.sh
- name: publish
run: poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }}

View File

@@ -1,73 +0,0 @@
name: Regenerate Integration Tests
on:
workflow_dispatch:
inputs:
debug:
description: 'Enable debug mode'
type: boolean
default: true
log_to_file:
description: 'Enable logging to file'
type: boolean
default: true
force_regenerate_tests:
description: 'Force regeneration of tests'
type: boolean
default: false
force_use_llm:
description: 'Force use of LLM'
type: boolean
default: false
jobs:
regenerate_integration_tests:
if: github.ref != 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Build Environment
run: make build
- name: Regenerate integration tests
run: |
DEBUG=${{ inputs.debug }} \
LOG_TO_FILE=${{ inputs.log_to_file }} \
FORCE_REGENERATE=${{ inputs.force_regenerate_tests }} \
FORCE_USE_LLM=${{ inputs.force_use_llm }} \
./tests/integration/regenerate.sh
- name: Commit changes
run: |
if git diff --quiet --exit-code; then
echo "No changes to commit"
exit 0
fi
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add .
# run it twice in case pre-commit makes changes
git commit -am "Regenerate integration tests" || git commit -am "Regenerate integration tests"
git push

View File

@@ -1,5 +1,5 @@
# Workflow that uses OpenHands to review a pull request. PR must be labeled 'review-this'
name: Use OpenHands to Review Pull Request
# Workflow that uses OpenDevin to review a pull request. PR must be labeled 'review-this'
name: Use OpenDevin to Review Pull Request
on:
pull_request:
@@ -15,13 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.11'
- name: install git, github cli
run: |
sudo apt-get install -y git gh
@@ -52,7 +49,7 @@ jobs:
export PATH="/github/home/.local/bin:$PATH"
poetry install --without evaluation,llama-index
poetry run playwright install --with-deps chromium
- name: Run OpenHands
- name: Run OpenDevin
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_MODEL: ${{ vars.LLM_MODEL }}
@@ -63,7 +60,7 @@ jobs:
export PYTHONPATH=$(pwd):$PYTHONPATH
export WORKSPACE_MOUNT_PATH=$GITHUB_WORKSPACE
export WORKSPACE_BASE=$GITHUB_WORKSPACE
echo -e "/exit\n" | poetry run python openhands/core/main.py -i 50 -f task.txt
echo -e "/exit\n" | poetry run python opendevin/core/main.py -i 50 -f task.txt
rm task.txt
- name: Check if review file is non-empty
id: check_file

View File

@@ -1,47 +1,65 @@
# Workflow that runs python unit tests
name: Run Python Unit Tests
# Workflow that runs frontend and python unit tests
name: Run Unit Tests
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
# The jobs in this workflow are required, so they must run at all times
# * Always run on "main"
# * Always run on PRs
on:
push:
branches:
- main
paths-ignore:
- '**/*.md'
- 'frontend/**'
- 'docs/**'
- 'evaluation/**'
pull_request:
# If triggered by a PR, it will be in the same group. However, each commit on main will be in its own unique group
concurrency:
group: ${{ github.workflow }}-${{ (github.head_ref && github.ref) || github.run_id }}
cancel-in-progress: true
jobs:
# Run frontend unit tests
fe-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
working-directory: ./frontend
run: npm ci
- name: Run tests and collect coverage
working-directory: ./frontend
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Run python unit tests on macOS
test-on-macos:
name: Python Unit Tests on macOS
name: Test on macOS
runs-on: macos-12
env:
INSTALL_DOCKER: '1' # Set to '0' to skip Docker installation
strategy:
matrix:
python-version: ['3.12']
python-version: ['3.11']
steps:
- uses: actions/checkout@v4
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install poetry via pipx
run: pipx install poetry
cache: 'poetry'
- name: Install Python dependencies using Poetry
run: poetry install --without evaluation,llama-index
- name: Install & Start Docker
@@ -94,11 +112,8 @@ jobs:
sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock
- name: Build Environment
run: make build
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Run Tests
run: poetry run pytest --forked --cov=openhands --cov-report=xml ./tests/unit --ignore=tests/unit/test_memory.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:
@@ -106,18 +121,15 @@ jobs:
# Run python unit tests on Linux
test-on-linux:
name: Python Unit Tests on Linux
name: Test on Linux
runs-on: ubuntu-latest
env:
INSTALL_DOCKER: '0' # Set to '0' to skip Docker installation
strategy:
matrix:
python-version: ['3.12']
python-version: ['3.11']
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
@@ -130,7 +142,7 @@ jobs:
- name: Build Environment
run: make build
- name: Run Tests
run: poetry run pytest --forked --cov=openhands --cov-report=xml -svv ./tests/unit --ignore=tests/unit/test_memory.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:

113
.github/workflows/solve-issue.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
# Workflow that uses OpenDevin to resolve a GitHub issue. Issue must be labeled 'solve-this'
name: Use OpenDevin to Resolve GitHub Issue
on:
issues:
types: [labeled]
permissions:
contents: write
pull-requests: write
issues: write
jobs:
dogfood:
if: github.event.label.name == 'solve-this'
runs-on: ubuntu-latest
container:
image: ghcr.io/opendevin/opendevin
volumes:
- /var/run/docker.sock:/var/run/docker.sock
steps:
- name: install git, github cli
run: apt-get install -y git gh
- name: Checkout Repository
uses: actions/checkout@v4
- name: Write Task File
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
echo "TITLE:" > task.txt
echo "${ISSUE_TITLE}" >> task.txt
echo "" >> task.txt
echo "BODY:" >> task.txt
echo "${ISSUE_BODY}" >> task.txt
- name: Set up environment
run: |
curl -sSL https://install.python-poetry.org | python3 -
export PATH="/github/home/.local/bin:$PATH"
poetry install --without evaluation,llama-index
poetry run playwright install --with-deps chromium
- name: Run OpenDevin
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
# Append path to launch poetry
export PATH="/github/home/.local/bin:$PATH"
# Append path to correctly import package, note: must set pwd at first
export PYTHONPATH=$(pwd):$PYTHONPATH
WORKSPACE_MOUNT_PATH=$GITHUB_WORKSPACE poetry run python ./opendevin/core/main.py -i 50 -f task.txt -d $GITHUB_WORKSPACE
rm task.txt
- name: Setup Git, Create Branch, and Commit Changes
run: |
# Setup Git configuration
git config --global --add safe.directory $PWD
git config --global user.name 'OpenDevin'
git config --global user.email 'OpenDevin@users.noreply.github.com'
# Create a unique branch name with a timestamp
BRANCH_NAME="fix/${{ github.event.issue.number }}-$(date +%Y%m%d%H%M%S)"
# Checkout new branch
git checkout -b $BRANCH_NAME
# Add all changes to staging, except task.txt
git add --all -- ':!task.txt'
# Commit the changes, if any
git commit -m "OpenDevin: Resolve Issue #${{ github.event.issue.number }}"
if [ $? -ne 0 ]; then
echo "No changes to commit."
exit 0
fi
# Push changes
git push --set-upstream origin $BRANCH_NAME
- name: Fetch Default Branch
env:
GH_TOKEN: ${{ github.token }}
run: |
# Fetch the default branch using gh cli
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
echo "Default branch is $DEFAULT_BRANCH"
echo "DEFAULT_BRANCH=$DEFAULT_BRANCH" >> $GITHUB_ENV
- name: Generate PR
env:
GH_TOKEN: ${{ github.token }}
run: |
# Create PR and capture URL
PR_URL=$(gh pr create \
--title "OpenDevin: Resolve Issue #2" \
--body "This PR was generated by OpenDevin to resolve issue #2" \
--repo "foragerr/OpenDevin" \
--head "${{ github.head_ref }}" \
--base "${{ env.DEFAULT_BRANCH }}" \
| grep -o 'https://github.com/[^ ]*')
# Extract PR number from URL
PR_NUMBER=$(echo "$PR_URL" | grep -o '[0-9]\+$')
# Set environment vars
echo "PR_URL=$PR_URL" >> $GITHUB_ENV
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
- name: Post Comment
env:
GH_TOKEN: ${{ github.token }}
run: |
gh issue comment ${{ github.event.issue.number }} \
-b "OpenDevin raised [PR #${{ env.PR_NUMBER }}](${{ env.PR_URL }}) to resolve this issue."

View File

@@ -1,7 +1,6 @@
# Workflow that marks issues and PRs with no activity for 30 days with "Stale" and closes them after 7 more days of no activity
name: 'Close stale issues'
# Runs every day at 01:30
on:
schedule:
- cron: '30 1 * * *'
@@ -15,7 +14,6 @@ jobs:
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'This PR is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
days-before-stale: 30
exempt-issue-labels: 'tracked'
close-issue-message: 'This issue was closed because it has been stalled for over 30 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for over 30 days with no activity.'
days-before-close: 7

6
.gitignore vendored
View File

@@ -121,7 +121,6 @@ celerybeat.pid
# Environments
.env
frontend/.env
.venv
env/
venv/
@@ -218,13 +217,14 @@ config.toml
config.toml_
config.toml.bak
containers/agnostic_sandbox
# swe-bench-eval
image_build_logs
run_instance_logs
runtime_*.tar
od_runtime_*.tar
# docker build
containers/runtime/Dockerfile
containers/runtime/project.tar.gz
containers/runtime/code

View File

@@ -1,28 +0,0 @@
OpenHands is an automated AI software engineer. It is a repo with a Python backend
(in the `openhands` directory) and TypeScript frontend (in the `frontend` directory).
General Setup:
- To set up the entire repo, including frontend and backend, run `make build`
- To run linting and type-checking before finishing the job, run `poetry run pre-commit run --all-files --config ./dev_config/python/.pre-commit-config.yaml`
Backend:
- Located in the `openhands` directory
- Testing:
- All tests are in `tests/unit/test_*.py`
- To test new code, run `poetry run pytest tests/unit/test_xxx.py` where `xxx` is the appropriate file for the current functionality
- Write all tests with pytest
Frontend:
- Located in the `frontend` directory
- Prerequisites: A recent version of NodeJS / NPM
- Setup: Run `npm install` in the frontend directory
- Testing:
- Run tests: `npm run test`
- To run specific tests: `npm run test -- -t "TestName"`
- Building:
- Build for production: `npm run build`
- Environment Variables:
- Set in `frontend/.env` or as environment variables
- Available variables: VITE_BACKEND_HOST, VITE_USE_TLS, VITE_INSECURE_SKIP_VERIFY, VITE_FRONTEND_PORT
- Internationalization:
- Generate i18n declaration file: `npm run make-i18n`

View File

@@ -1,36 +1,35 @@
# Contributing
Thanks for your interest in contributing to OpenHands! We welcome and appreciate contributions.
Thanks for your interest in contributing to OpenDevin! We welcome and appreciate contributions.
## How Can I Contribute?
There are many ways that you can contribute:
1. **Download and use** OpenHands, and send [issues](https://github.com/All-Hands-AI/OpenHands/issues) when you encounter something that isn't working or a feature that you'd like to see.
1. **Download and use** OpenDevin, and send [issues](https://github.com/OpenDevin/OpenDevin/issues) when you encounter something that isn't working or a feature that you'd like to see.
2. **Send feedback** after each session by [clicking the thumbs-up thumbs-down buttons](https://docs.all-hands.dev/modules/usage/feedback), so we can see where things are working and failing, and also build an open dataset for training code agents.
3. **Improve the Codebase** by sending PRs (see details below). In particular, we have some [good first issues](https://github.com/All-Hands-AI/OpenHands/labels/good%20first%20issue) that may be ones to start on.
3. **Improve the Codebase** by sending PRs (see details below). In particular, we have some [good first issue](https://github.com/OpenDevin/OpenDevin/labels/good%20first%20issue) issues that may be ones to start on.
## Understanding OpenHands's CodeBase
## Understanding OpenDevin's CodeBase
To understand the codebase, please refer to the README in each module:
- [frontend](./frontend/README.md)
- [agenthub](./agenthub/README.md)
- [evaluation](./evaluation/README.md)
- [openhands](./openhands/README.md)
- [agenthub](./openhands/agenthub/README.md)
- [server](./openhands/server/README.md)
- [opendevin](./opendevin/README.md)
- [server](./opendevin/server/README.md)
When you write code, it is also good to write tests. Please navigate to the `tests` folder to see existing test suites.
At the moment, we have two kinds of tests: `unit` and `integration`. Please refer to the README for each test suite. These tests also run on GitHub's continuous integration to ensure quality of the project.
## Sending Pull Requests to OpenHands
## Sending Pull Requests to OpenDevin
### 1. Fork the Official Repository
Fork the [OpenHands repository](https://github.com/All-Hands-AI/OpenHands) into your own account.
Fork the [OpenDevin repository](https://github.com/OpenDevin/OpenDevin) into your own account.
Clone your own forked repository into your local environment:
```shell
git clone git@github.com:<YOUR-USERNAME>/OpenHands.git
git clone git@github.com:<YOUR-USERNAME>/OpenDevin.git
```
### 2. Configure Git
@@ -39,8 +38,8 @@ Set the official repository as your [upstream](https://www.atlassian.com/git/tut
Add the original repository as upstream:
```shell
cd OpenHands
git remote add upstream git@github.com:All-Hands-AI/OpenHands.git
cd OpenDevin
git remote add upstream git@github.com:OpenDevin/OpenDevin.git
```
Verify that the remote is set:
@@ -63,7 +62,7 @@ git push origin main
### 4. Set up the Development Environment
We have a separate doc [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) that tells you how to set up a development workflow.
We have a separate doc [Development.md](https://github.com/OpenDevin/OpenDevin/blob/main/Development.md) that tells you how to set up a development workflow.
### 5. Write Code and Commit It
@@ -81,13 +80,13 @@ git push origin my_branch
* On GitHub, go to the page of your forked repository, and create a Pull Request:
- Click on `Branches`
- Click on the `...` beside your branch and click on `New pull request`
- Set `base repository` to `All-Hands-AI/OpenHands`
- Set `base repository` to `OpenDevin/OpenDevin`
- Set `base` to `main`
- Click `Create pull request`
The PR should appear in [OpenHands PRs](https://github.com/All-Hands-AI/OpenHands/pulls).
The PR should appear in [OpenDevin PRs](https://github.com/OpenDevin/OpenDevin/pulls).
Then the OpenHands team will review your code.
Then the OpenDevin team will review your code.
## PR Rules
@@ -110,7 +109,7 @@ For example, a PR title could be:
- `refactor: modify package path`
- `feat(frontend): xxxx`, where `(frontend)` means that this PR mainly focuses on the frontend component.
You may also check out previous PRs in the [PR list](https://github.com/All-Hands-AI/OpenHands/pulls).
You may also check out previous PRs in the [PR list](https://github.com/OpenDevin/OpenDevin/pulls).
### 2. Pull Request description
- If your PR is small (such as a typo fix), you can go brief.

View File

@@ -1,13 +1,13 @@
# Development Guide
This guide is for people working on OpenHands and editing the source code.
If you wish to contribute your changes, check out the [CONTRIBUTING.md](https://github.com/All-Hands-AI/OpenHands/blob/main/CONTRIBUTING.md) on how to clone and setup the project initially before moving on.
Otherwise, you can clone the OpenHands project directly.
This guide is for people working on OpenDevin and editing the source code.
If you wish to contribute your changes, check out the [CONTRIBUTING.md](https://github.com/OpenDevin/OpenDevin/blob/main/CONTRIBUTING.md) on how to clone and setup the project initially before moving on.
Otherwise, you can clone the OpenDevin project directly.
## Start the server for development
### 1. Requirements
* Linux, Mac OS, or [WSL on Windows](https://learn.microsoft.com/en-us/windows/wsl/install) [ Ubuntu <= 22.04]
* [Docker](https://docs.docker.com/engine/install/) (For those on MacOS, make sure to allow the default Docker socket to be used from advanced settings!)
* [Python](https://www.python.org/downloads/) = 3.12
* [Python](https://www.python.org/downloads/) = 3.11
* [NodeJS](https://nodejs.org/en/download/package-manager) >= 18.17.1
* [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) >= 1.8
* netcat => sudo apt-get install netcat
@@ -22,21 +22,21 @@ If you want to develop without system admin/sudo access to upgrade/install `Pyth
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
bash Miniforge3-$(uname)-$(uname -m).sh
# Install Python 3.12, nodejs, and poetry
mamba install python=3.12
# Install Python 3.11, nodejs, and poetry
mamba install python=3.11
mamba install conda-forge::nodejs
mamba install conda-forge::poetry
```
### 2. Build and Setup The Environment
Begin by building the project which includes setting up the environment and installing dependencies. This step ensures that OpenHands is ready to run on your system:
Begin by building the project which includes setting up the environment and installing dependencies. This step ensures that OpenDevin is ready to run on your system:
```bash
make build
```
### 3. Configuring the Language Model
OpenHands supports a diverse array of Language Models (LMs) through the powerful [litellm](https://docs.litellm.ai) library. By default, we've chosen the mighty GPT-4 from OpenAI as our go-to model, but the world is your oyster! You can unleash the potential of Anthropic's suave Claude, the enigmatic Llama, or any other LM that piques your interest.
OpenDevin supports a diverse array of Language Models (LMs) through the powerful [litellm](https://docs.litellm.ai) library. By default, we've chosen the mighty GPT-4 from OpenAI as our go-to model, but the world is your oyster! You can unleash the potential of Anthropic's suave Claude, the enigmatic Llama, or any other LM that piques your interest.
To configure the LM of your choice, run:
@@ -44,20 +44,20 @@ To configure the LM of your choice, run:
make setup-config
```
This command will prompt you to enter the LLM API key, model name, and other variables ensuring that OpenHands is tailored to your specific needs. Note that the model name will apply only when you run headless. If you use the UI, please set the model in the UI.
This command will prompt you to enter the LLM API key, model name, and other variables ensuring that OpenDevin is tailored to your specific needs. Note that the model name will apply only when you run headless. If you use the UI, please set the model in the UI.
Note: If you have previously run OpenHands using the docker command, you may have already set some environmental variables in your terminal. The final configurations are set from highest to lowest priority:
Note: If you have previously run OpenDevin using the docker command, you may have already set some environmental variables in your terminal. The final configurations are set from highest to lowest priority:
Environment variables > config.toml variables > default variables
**Note on Alternative Models:**
Some alternative models may prove more challenging to tame than others. Fear not, brave adventurer! We shall soon unveil LLM-specific documentation to guide you on your quest.
And if you've already mastered the art of wielding a model other than OpenAI's GPT, we encourage you to share your setup instructions with us by creating instructions and adding it [to our documentation](https://github.com/All-Hands-AI/OpenHands/tree/main/docs/modules/usage/llms).
And if you've already mastered the art of wielding a model other than OpenAI's GPT, we encourage you to share your setup instructions with us by creating instructions and adding it [to our documentation](https://github.com/OpenDevin/OpenDevin/tree/main/docs/modules/usage/llms).
For a full list of the LM providers and models available, please consult the [litellm documentation](https://docs.litellm.ai/docs/providers).
### 4. Running the application
#### Option A: Run the Full Application
Once the setup is complete, launching OpenHands is as simple as running a single command. This command starts both the backend and frontend servers seamlessly, allowing you to interact with OpenHands:
Once the setup is complete, launching OpenDevin is as simple as running a single command. This command starts both the backend and frontend servers seamlessly, allowing you to interact with OpenDevin:
```bash
make run
```
@@ -75,10 +75,10 @@ make run
### 6. LLM Debugging
If you encounter any issues with the Language Model (LM) or you're simply curious, you can inspect the actual LLM prompts and responses. To do so, export DEBUG=1 in the environment and restart the backend.
OpenHands will then log the prompts and responses in the logs/llm/CURRENT_DATE directory, allowing you to identify the causes.
OpenDevin will then log the prompts and responses in the logs/llm/CURRENT_DATE directory, allowing you to identify the causes.
### 7. Help
Need assistance or information on available targets and commands? The help command provides all the necessary guidance to ensure a smooth experience with OpenHands.
Need assistance or information on available targets and commands? The help command provides all the necessary guidance to ensure a smooth experience with OpenDevin.
```bash
make help
```
@@ -97,33 +97,3 @@ Please refer to [this README](./tests/integration/README.md) for details.
### 9. Add or update dependency
1. Add your dependency in `pyproject.toml` or use `poetry add xxx`
2. Update the poetry.lock file via `poetry lock --no-update`
### 9. Use existing Docker image
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker container image. Follow these steps:
1. Set the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
2. Example: export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik
## Develop inside Docker container
TL;DR
```bash
make docker-dev
```
See more details [here](./containers/dev/README.md)
If you are just interested in running `OpenHands` without installing all the required tools on your host.
```bash
make docker-run
```
If you do not have `make` on your host, run:
```bash
cd ./containers/dev
./dev.sh
```
You do need [Docker](https://docs.docker.com/engine/install/) installed on your host though.

View File

@@ -1,5 +0,0 @@
# Exclude all Python bytecode files
global-exclude *.pyc
# Exclude Python cache directories
global-exclude __pycache__

View File

@@ -1,16 +1,16 @@
SHELL=/bin/bash
# Makefile for OpenHands project
# Makefile for OpenDevin project
# Variables
BACKEND_HOST ?= "127.0.0.1"
DOCKER_IMAGE = ghcr.io/opendevin/sandbox:main
BACKEND_PORT = 3000
BACKEND_HOST_PORT = "$(BACKEND_HOST):$(BACKEND_PORT)"
BACKEND_HOST = "127.0.0.1:$(BACKEND_PORT)"
FRONTEND_PORT = 3001
DEFAULT_WORKSPACE_DIR = "./workspace"
DEFAULT_MODEL = "gpt-4o"
CONFIG_FILE = config.toml
PRE_COMMIT_CONFIG_PATH = "./dev_config/python/.pre-commit-config.yaml"
PYTHON_VERSION = 3.12
PYTHON_VERSION = 3.11
# ANSI color codes
GREEN=$(shell tput -Txterm setaf 2)
@@ -166,7 +166,7 @@ install-pre-commit-hooks:
lint-backend:
@echo "$(YELLOW)Running linters...$(RESET)"
@poetry run pre-commit run --files openhands/**/* agenthub/**/* evaluation/**/* --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
@poetry run pre-commit run --files opendevin/**/* agenthub/**/* evaluation/**/* --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
lint-frontend:
@echo "$(YELLOW)Running linters for frontend...$(RESET)"
@@ -190,12 +190,12 @@ build-frontend:
# Start backend
start-backend:
@echo "$(YELLOW)Starting backend...$(RESET)"
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "$(shell pwd)/workspace"
@poetry run uvicorn opendevin.server.listen:app --port $(BACKEND_PORT) --reload --reload-exclude "workspace/*"
# Start frontend
start-frontend:
@echo "$(YELLOW)Starting frontend...$(RESET)"
@cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST_PORT) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start -- --port $(FRONTEND_PORT)
@cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start
# Common setup for running the app (non-callable)
_run_setup:
@@ -205,7 +205,7 @@ _run_setup:
fi
@mkdir -p logs
@echo "$(YELLOW)Starting backend server...$(RESET)"
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
@poetry run uvicorn opendevin.server.listen:app --port $(BACKEND_PORT) &
@echo "$(YELLOW)Waiting for the backend to start...$(RESET)"
@until nc -z localhost $(BACKEND_PORT); do sleep 0.1; done
@echo "$(GREEN)Backend started successfully.$(RESET)"
@@ -217,20 +217,6 @@ run:
@cd frontend && echo "$(BLUE)Starting frontend with npm...$(RESET)" && npm run start -- --port $(FRONTEND_PORT)
@echo "$(GREEN)Application started successfully.$(RESET)"
# Run the app (in docker)
docker-run: WORKSPACE_BASE ?= $(PWD)/workspace
docker-run:
@if [ -f /.dockerenv ]; then \
echo "Running inside a Docker container. Exiting..."; \
exit 0; \
else \
echo "$(YELLOW)Running the app in Docker $(OPTIONS)...$(RESET)"; \
export WORKSPACE_BASE=${WORKSPACE_BASE}; \
export SANDBOX_USER_ID=$(shell id -u); \
export DATE=$(shell date +%Y%m%d%H%M%S); \
docker compose up $(OPTIONS); \
fi
# Run the app (WSL mode)
run-wsl:
@echo "$(YELLOW)Running the app in WSL mode...$(RESET)"
@@ -275,10 +261,6 @@ setup-config-prompts:
echo " - nomic-embed-text"; \
echo " - all-minilm"; \
echo " - stable-code"; \
echo " - bge-m3"; \
echo " - bge-large"; \
echo " - paraphrase-multilingual"; \
echo " - snowflake-arctic-embed"; \
echo " - Leave blank to default to 'BAAI/bge-small-en-v1.5' via huggingface"; \
read -p "> " llm_embedding_model; \
echo "embedding_model=\"$$llm_embedding_model\"" >> $(CONFIG_FILE).tmp; \
@@ -295,20 +277,10 @@ setup-config-prompts:
fi
# Develop in container
docker-dev:
@if [ -f /.dockerenv ]; then \
echo "Running inside a Docker container. Exiting..."; \
exit 0; \
else \
echo "$(YELLOW)Build and run in Docker $(OPTIONS)...$(RESET)"; \
./containers/dev/dev.sh $(OPTIONS); \
fi
# Clean up all caches
clean:
@echo "$(YELLOW)Cleaning up caches...$(RESET)"
@rm -rf openhands/.cache
@rm -rf opendevin/.cache
@echo "$(GREEN)Caches cleaned up successfully.$(RESET)"
# Help
@@ -317,16 +289,13 @@ help:
@echo "Targets:"
@echo " $(GREEN)build$(RESET) - Build project, including environment setup and dependencies."
@echo " $(GREEN)lint$(RESET) - Run linters on the project."
@echo " $(GREEN)setup-config$(RESET) - Setup the configuration for OpenHands by providing LLM API key,"
@echo " $(GREEN)setup-config$(RESET) - Setup the configuration for OpenDevin by providing LLM API key,"
@echo " LLM Model name, and workspace directory."
@echo " $(GREEN)start-backend$(RESET) - Start the backend server for the OpenHands project."
@echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenHands project."
@echo " $(GREEN)run$(RESET) - Run the OpenHands application, starting both backend and frontend servers."
@echo " $(GREEN)start-backend$(RESET) - Start the backend server for the OpenDevin project."
@echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenDevin project."
@echo " $(GREEN)run$(RESET) - Run the OpenDevin application, starting both backend and frontend servers."
@echo " Backend Log file will be stored in the 'logs' directory."
@echo " $(GREEN)docker-dev$(RESET) - Build and run the OpenHands application in Docker."
@echo " $(GREEN)docker-run$(RESET) - Run the OpenHands application, starting both backend and frontend servers in Docker."
@echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets."
# Phony targets
.PHONY: build check-dependencies check-python check-npm check-docker check-poetry install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint start-backend start-frontend run run-wsl setup-config setup-config-prompts help
.PHONY: docker-dev docker-run

138
README.md
View File

@@ -1,109 +1,124 @@
<a name="readme-top"></a>
<div align="center">
<img src="./docs/static/img/logo.png" alt="Logo" width="200">
<h1 align="center">OpenHands: Code Less, Make More</h1>
</div>
<!--
*** Thanks for checking out the Best-README-Template. If you have a suggestion
*** that would make this better, please fork the repo and create a pull request
*** or simply open an issue with the tag "enhancement".
*** Don't forget to give the project a star!
*** Thanks again! Now go create something AMAZING! :D
-->
<!-- PROJECT SHIELDS -->
<!--
*** I'm using markdown "reference style" links for readability.
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
*** See the bottom of this document for the declaration of the reference variables
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
-->
<div align="center">
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors"><img src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Contributors"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers"></a>
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge&color=blue"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
<a href="https://github.com/OpenDevin/OpenDevin/graphs/contributors"><img src="https://img.shields.io/github/contributors/opendevin/opendevin?style=for-the-badge&color=blue" alt="Contributors"></a>
<a href="https://github.com/OpenDevin/OpenDevin/network/members"><img src="https://img.shields.io/github/forks/opendevin/opendevin?style=for-the-badge&color=blue" alt="Forks"></a>
<a href="https://github.com/OpenDevin/OpenDevin/stargazers"><img src="https://img.shields.io/github/stars/opendevin/opendevin?style=for-the-badge&color=blue" alt="Stargazers"></a>
<a href="https://github.com/OpenDevin/OpenDevin/issues"><img src="https://img.shields.io/github/issues/opendevin/opendevin?style=for-the-badge&color=blue" alt="Issues"></a>
<a href="https://github.com/OpenDevin/OpenDevin/blob/main/LICENSE"><img src="https://img.shields.io/github/license/opendevin/opendevin?style=for-the-badge&color=blue" alt="MIT License"></a>
<br/>
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2ngejmfw6-9gW4APWOC9XUp1n~SiQ6iw"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="Credits"></a>
<br/>
<a href="https://docs.all-hands.dev/modules/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="Check out the documentation"></a>
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
<a href="https://huggingface.co/spaces/OpenHands/evaluation"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark Score"></a>
<hr>
<a href="https://codecov.io/github/opendevin/opendevin?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/opendevin/opendevin?style=for-the-badge"></a>
</div>
Welcome to OpenHands (formerly OpenDevin), a platform for software development agents powered by AI.
<!-- PROJECT LOGO -->
<div align="center">
<img src="./docs/static/img/logo.png" alt="Logo" width="200" height="200">
<h1 align="center">OpenDevin: Code Less, Make More</h1>
<a href="https://docs.all-hands.dev/modules/usage/intro"><img src="https://img.shields.io/badge/Documentation-OpenDevin-blue?logo=googledocs&logoColor=white&style=for-the-badge" alt="Check out the documentation"></a>
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper-%20on%20Arxiv-red?logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
<br/>
<a href="https://huggingface.co/spaces/OpenDevin/evaluation"><img src="https://img.shields.io/badge/Evaluation-Benchmark%20on%20HF%20Space-green?logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark"></a>
</div>
<hr>
OpenHands agents can do anything a human developer can: modify code, run commands, browse the web,
call APIs, and yes—even copy code snippets from StackOverflow.
Welcome to OpenDevin, a platform for autonomous software engineers, powered by AI and LLMs.
Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or jump to the [Quick Start](#-quick-start).
OpenDevin agents collaborate with human developers to write code, fix bugs, and ship features.
![App screenshot](./docs/static/img/screenshot.png)
## ⚡ Quick Start
## ⚡ Getting Started
OpenDevin works best with Docker version 26.0.0+ (Docker Desktop 4.31.0+).
You must be using Linux, Mac OS, or WSL on Windows.
The easiest way to run OpenHands is in Docker. You can change `WORKSPACE_BASE` below to
point OpenHands to existing code that you'd like to modify.
To start OpenDevin in a docker container, run the following commands in your terminal:
See the [Installation](https://docs.all-hands.dev/modules/usage/installation) guide for
system requirements and more information.
> [!WARNING]
> When you run the following command, files in `./workspace` may be modified or deleted.
```bash
export WORKSPACE_BASE=$(pwd)/workspace
docker pull ghcr.io/all-hands-ai/runtime:0.9-nikolaik
docker run -it --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik \
WORKSPACE_BASE=$(pwd)/workspace
docker run -it \
--pull=always \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-v $WORKSPACE_BASE:/opt/workspace_base \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/all-hands-ai/openhands:0.9
--name opendevin-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/opendevin/opendevin:0.8
```
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
> [!NOTE]
> By default, this command pulls the `latest` tag, which represents the most recent release of OpenDevin. You have other options as well:
> - For a specific release version, use `ghcr.io/opendevin/opendevin:<OpenDevin_version>` (replace <OpenDevin_version> with the desired version number).
> - For the most up-to-date development version, use `ghcr.io/opendevin/opendevin:main`. This version may be **(unstable!)** and is recommended for testing or development purposes only.
>
> Choose the tag that best suits your needs based on stability requirements and desired features.
You'll need a model provider and API key. One option that works well: [Claude 3.5 Sonnet](https://www.anthropic.com/api), but you have [many options](https://docs.all-hands.dev/modules/usage/llms).
You'll find OpenDevin running at [http://localhost:3000](http://localhost:3000) with access to `./workspace`. To have OpenDevin operate on your code, place it in `./workspace`.
OpenDevin will only have access to this workspace folder. The rest of your system will not be affected as it runs in a secured docker sandbox.
---
Upon opening OpenDevin, you must select the appropriate `Model` and enter the `API Key` within the settings that should pop up automatically. These can be set at any time by selecting
the `Settings` button (gear icon) in the UI. If the required `Model` does not exist in the list, you can manually enter it in the text box.
You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
or as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
For the development workflow, see [Development.md](https://github.com/OpenDevin/OpenDevin/blob/main/Development.md).
Visit [Installation](https://docs.all-hands.dev/modules/usage/installation) for more information and setup instructions.
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
If you want to modify the OpenHands source code, check out [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
## 🚀 Documentation
Having issues? The [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting) can help.
To learn more about the project, and for tips on using OpenDevin,
**check out our [documentation](https://docs.all-hands.dev/modules/usage/intro)**.
## 📖 Documentation
To learn more about the project, and for tips on using OpenHands,
**check out our [documentation](https://docs.all-hands.dev/modules/usage/getting-started)**.
There you'll find resources on how to use different LLM providers,
There you'll find resources on how to use different LLM providers (like ollama and Anthropic's Claude),
troubleshooting resources, and advanced configuration options.
## 🤝 How to Contribute
OpenHands is a community-driven project, and we welcome contributions from everyone.
OpenDevin is a community-driven project, and we welcome contributions from everyone.
Whether you're a developer, a researcher, or simply enthusiastic about advancing the field of
software engineering with AI, there are many ways to get involved:
- **Code Contributions:** Help us develop new agents, core functionality, the frontend and other interfaces, or sandboxing solutions.
- **Research and Evaluation:** Contribute to our understanding of LLMs in software engineering, participate in evaluating the models, or suggest improvements.
- **Feedback and Testing:** Use the OpenHands toolset, report bugs, suggest features, or provide feedback on usability.
- **Feedback and Testing:** Use the OpenDevin toolset, report bugs, suggest features, or provide feedback on usability.
For details, please check [CONTRIBUTING.md](./CONTRIBUTING.md).
## 🤖 Join Our Community
Whether you're a developer, a researcher, or simply enthusiastic about OpenHands, we'd love to have you in our community.
Whether you're a developer, a researcher, or simply enthusiastic about OpenDevin, we'd love to have you in our community.
Let's make software engineering better together!
- [Slack workspace](https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA) - Here we talk about research, architecture, and future development.
- [Slack workspace](https://join.slack.com/t/opendevin/shared_invite/zt-2ngejmfw6-9gW4APWOC9XUp1n~SiQ6iw) - Here we talk about research, architecture, and future development.
- [Discord server](https://discord.gg/ESHStjSjD4) - This is a community-run server for general discussion, questions, and feedback.
## 📈 Progress
<p align="center">
<a href="https://star-history.com/#All-Hands-AI/OpenHands&Date">
<img src="https://api.star-history.com/svg?repos=All-Hands-AI/OpenHands&type=Date" width="500" alt="Star History Chart">
<a href="https://star-history.com/#OpenDevin/OpenDevin&Date">
<img src="https://api.star-history.com/svg?repos=OpenDevin/OpenDevin&type=Date" width="500" alt="Star History Chart">
</a>
</p>
@@ -111,17 +126,22 @@ Let's make software engineering better together!
Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
## 🙏 Acknowledgements
OpenHands is built by a large number of contributors, and every contribution is greatly appreciated! We also build upon other open source projects, and we are deeply thankful for their work.
For a list of open source projects and licenses used in OpenHands, please see our [CREDITS.md](./CREDITS.md) file.
[contributors-shield]: https://img.shields.io/github/contributors/opendevin/opendevin?style=for-the-badge
[contributors-url]: https://github.com/OpenDevin/OpenDevin/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/opendevin/opendevin?style=for-the-badge
[forks-url]: https://github.com/OpenDevin/OpenDevin/network/members
[stars-shield]: https://img.shields.io/github/stars/opendevin/opendevin?style=for-the-badge
[stars-url]: https://github.com/OpenDevin/OpenDevin/stargazers
[issues-shield]: https://img.shields.io/github/issues/opendevin/opendevin?style=for-the-badge
[issues-url]: https://github.com/OpenDevin/OpenDevin/issues
[license-shield]: https://img.shields.io/github/license/opendevin/opendevin?style=for-the-badge
[license-url]: https://github.com/OpenDevin/OpenDevin/blob/main/LICENSE
## 📚 Cite
```
@misc{openhands,
title={{OpenHands: An Open Platform for AI Software Developers as Generalist Agents}},
@misc{opendevin,
title={{OpenDevin: An Open Platform for AI Software Developers as Generalist Agents}},
author={Xingyao Wang and Boxuan Li and Yufan Song and Frank F. Xu and Xiangru Tang and Mingchen Zhuge and Jiayi Pan and Yueqi Song and Bowen Li and Jaskirat Singh and Hoang H. Tran and Fuqiang Li and Ren Ma and Mingzhang Zheng and Bill Qian and Yanjun Shao and Niklas Muennighoff and Yizhe Zhang and Binyuan Hui and Junyang Lin and Robert Brennan and Hao Peng and Heng Ji and Graham Neubig},
year={2024},
eprint={2407.16741},

72
agenthub/README.md Normal file
View File

@@ -0,0 +1,72 @@
# Agent Hub
In this folder, there may exist multiple implementations of `Agent` that will be used by the framework.
For example, `agenthub/codeact_agent`, etc.
Contributors from different backgrounds and interests can choose to contribute to any (or all!) of these directions.
## Constructing an Agent
The abstraction for an agent can be found [here](../opendevin/controller/agent.py).
Agents are run inside of a loop. At each iteration, `agent.step()` is called with a
[State](../opendevin/controller/state/state.py) input, and the agent must output an [Action](../opendevin/events/action).
Every agent also has a `self.llm` which it can use to interact with the LLM configured by the user.
See the [LiteLLM docs for `self.llm.completion`](https://docs.litellm.ai/docs/completion).
## State
The `state` contains:
- A history of actions taken by the agent, as well as any observations (e.g. file content, command output) from those actions
- A list of actions/observations that have happened since the most recent step
- A [`root_task`](https://github.com/OpenDevin/OpenDevin/blob/main/opendevin/controller/state/task.py), which contains a plan of action
- The agent can add and modify subtasks through the `AddTaskAction` and `ModifyTaskAction`
## Actions
Here is a list of available Actions, which can be returned by `agent.step()`:
- [`CmdRunAction`](../opendevin/events/action/commands.py) - Runs a command inside a sandboxed terminal
- [`IPythonRunCellAction`](../opendevin/events/action/commands.py) - Execute a block of Python code interactively (in Jupyter notebook) and receives `CmdOutputObservation`. Requires setting up `jupyter` [plugin](../opendevin/runtime/plugins) as a requirement.
- [`FileReadAction`](../opendevin/events/action/files.py) - Reads the content of a file
- [`FileWriteAction`](../opendevin/events/action/files.py) - Writes new content to a file
- [`BrowseURLAction`](../opendevin/events/action/browse.py) - Gets the content of a URL
- [`AddTaskAction`](../opendevin/events/action/tasks.py) - Adds a subtask to the plan
- [`ModifyTaskAction`](../opendevin/events/action/tasks.py) - Changes the state of a subtask.
- [`AgentFinishAction`](../opendevin/events/action/agent.py) - Stops the control loop, allowing the user/delegator agent to enter a new task
- [`AgentRejectAction`](../opendevin/events/action/agent.py) - Stops the control loop, allowing the user/delegator agent to enter a new task
- [`AgentFinishAction`](../opendevin/events/action/agent.py) - Stops the control loop, allowing the user to enter a new task
- [`MessageAction`](../opendevin/events/action/message.py) - Represents a message from an agent or the user
You can use `action.to_dict()` and `action_from_dict` to serialize and deserialize actions.
## Observations
There are also several types of Observations. These are typically available in the step following the corresponding Action.
But they may also appear as a result of asynchronous events (e.g. a message from the user).
Here is a list of available Observations:
- [`CmdOutputObservation`](../opendevin/events/observation/commands.py)
- [`BrowserOutputObservation`](../opendevin/events/observation/browse.py)
- [`FileReadObservation`](../opendevin/events/observation/files.py)
- [`FileWriteObservation`](../opendevin/events/observation/files.py)
- [`ErrorObservation`](../opendevin/events/observation/error.py)
- [`SuccessObservation`](../opendevin/events/observation/success.py)
You can use `observation.to_dict()` and `observation_from_dict` to serialize and deserialize observations.
## Interface
Every agent must implement the following methods:
### `step`
```
def step(self, state: "State") -> "Action"
```
`step` moves the agent forward one step towards its goal. This probably means
sending a prompt to the LLM, then parsing the response into an `Action`.

View File

@@ -1,13 +1,14 @@
from dotenv import load_dotenv
from openhands.agenthub.micro.agent import MicroAgent
from openhands.agenthub.micro.registry import all_microagents
from openhands.controller.agent import Agent
from opendevin.controller.agent import Agent
from .micro.agent import MicroAgent
from .micro.registry import all_microagents
load_dotenv()
from openhands.agenthub import ( # noqa: E402
from . import ( # noqa: E402
browsing_agent,
codeact_agent,
codeact_swe_agent,

View File

@@ -8,7 +8,7 @@ This folder implements the basic BrowserGym [demo agent](https://github.com/Serv
Note that for browsing tasks, GPT-4 is usually a requirement to get reasonable results, due to the complexity of the web page structures.
```
poetry run python ./openhands/core/main.py \
poetry run python ./opendevin/core/main.py \
-i 10 \
-t "tell me the usa's president using google search" \
-c BrowsingAgent \

View File

@@ -0,0 +1,5 @@
from opendevin.controller.agent import Agent
from .browsing_agent import BrowsingAgent
Agent.register('BrowsingAgent', BrowsingAgent)

View File

@@ -3,25 +3,25 @@ import os
from browsergym.core.action.highlevel import HighLevelActionSet
from browsergym.utils.obs import flatten_axtree_to_str
from openhands.agenthub.browsing_agent.response_parser import BrowsingResponseParser
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.logger import openhands_logger as logger
from openhands.core.message import Message, TextContent
from openhands.events.action import (
from agenthub.browsing_agent.response_parser import BrowsingResponseParser
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.logger import opendevin_logger as logger
from opendevin.core.message import Message, TextContent
from opendevin.events.action import (
Action,
AgentFinishAction,
BrowseInteractiveAction,
MessageAction,
)
from openhands.events.event import EventSource
from openhands.events.observation import BrowserOutputObservation
from openhands.events.observation.observation import Observation
from openhands.llm.llm import LLM
from openhands.runtime.plugins import (
from opendevin.events.event import EventSource
from opendevin.events.observation import BrowserOutputObservation
from opendevin.events.observation.observation import Observation
from opendevin.llm.llm import LLM
from opendevin.runtime.plugins import (
PluginRequirement,
)
from opendevin.runtime.tools import RuntimeTool
USE_NAV = (
os.environ.get('USE_NAV', 'true') == 'true'
@@ -65,15 +65,10 @@ In order to accomplish my goal I need to send the information asked back to the
"""
def get_prompt(
error_prefix: str, cur_url: str, cur_axtree_txt: str, prev_action_str: str
) -> str:
def get_prompt(error_prefix: str, cur_axtree_txt: str, prev_action_str: str) -> str:
prompt = f"""\
{error_prefix}
# Current Page URL:
{cur_url}
# Current Accessibility Tree:
{cur_axtree_txt}
@@ -98,19 +93,19 @@ class BrowsingAgent(Agent):
"""
sandbox_plugins: list[PluginRequirement] = []
runtime_tools: list[RuntimeTool] = [RuntimeTool.BROWSER]
response_parser = BrowsingResponseParser()
def __init__(
self,
llm: LLM,
config: AgentConfig,
) -> None:
"""Initializes a new instance of the BrowsingAgent class.
Parameters:
- llm (LLM): The llm to be used by this agent
"""
super().__init__(llm, config)
super().__init__(llm)
# define a configurable action space, with chat functionality, web navigation, and webpage grounding using accessibility tree and HTML.
# see https://github.com/ServiceNow/BrowserGym/blob/main/core/src/browsergym/core/action/highlevel.py for more details
action_subsets = ['chat', 'bid']
@@ -144,7 +139,6 @@ class BrowsingAgent(Agent):
"""
messages: list[Message] = []
prev_actions = []
cur_url = ''
cur_axtree_txt = ''
error_prefix = ''
last_obs = None
@@ -171,7 +165,7 @@ class BrowsingAgent(Agent):
prev_action_str = '\n'.join(prev_actions)
# if the final BrowserInteractiveAction exec BrowserGym's send_msg_to_user,
# we should also send a message back to the user in OpenHands and call it a day
# we should also send a message back to the user in OpenDevin and call it a day
if (
isinstance(last_action, BrowseInteractiveAction)
and last_action.browsergym_send_msg_to_user
@@ -185,9 +179,6 @@ class BrowsingAgent(Agent):
self.error_accumulator += 1
if self.error_accumulator > 5:
return MessageAction('Too many errors encountered. Task failed.')
cur_url = last_obs.url
try:
cur_axtree_txt = flatten_axtree_to_str(
last_obs.axtree_object,
@@ -213,11 +204,12 @@ class BrowsingAgent(Agent):
messages.append(Message(role='system', content=[TextContent(text=system_msg)]))
prompt = get_prompt(error_prefix, cur_url, cur_axtree_txt, prev_action_str)
prompt = get_prompt(error_prefix, cur_axtree_txt, prev_action_str)
messages.append(Message(role='user', content=[TextContent(text=prompt)]))
logger.debug(prompt)
response = self.llm.completion(
messages=self.llm.format_messages_for_llm(messages),
messages=[message.model_dump() for message in messages],
temperature=0.0,
stop=[')```', ')\n```'],
)
return self.response_parser.parse(response)

View File

@@ -12,11 +12,12 @@ from browsergym.core.action.base import AbstractActionSet
from browsergym.core.action.highlevel import HighLevelActionSet
from browsergym.core.action.python import PythonActionSet
from openhands.agenthub.browsing_agent.utils import (
from opendevin.runtime.browser.browser_env import BrowserEnv
from .utils import (
ParseError,
parse_html_tags_raise,
)
from openhands.runtime.browser.browser_env import BrowserEnv
@dataclass
@@ -57,7 +58,7 @@ class Flags:
@classmethod
def from_dict(self, flags_dict):
"""Helper for JSON serializable requirement."""
"""Helper for JSON serializble requirement."""
if isinstance(flags_dict, Flags):
return flags_dict
@@ -354,7 +355,7 @@ and executed by a program, make sure to follow the formatting instructions.
self._prompt += '\n'.join(
[
f"""\
- [{msg['role']}], {msg['message']}"""
- [{msg['role']}] {msg['message']}"""
for msg in chat_messages
]
)

View File

@@ -0,0 +1,88 @@
import ast
from opendevin.controller.action_parser import ActionParser, ResponseParser
from opendevin.core.logger import opendevin_logger as logger
from opendevin.events.action import (
Action,
BrowseInteractiveAction,
)
class BrowsingResponseParser(ResponseParser):
def __init__(self):
# Need to pay attention to the item order in self.action_parsers
super().__init__()
self.action_parsers = [BrowsingActionParserMessage()]
self.default_parser = BrowsingActionParserBrowseInteractive()
def parse(self, response: str) -> Action:
action_str = self.parse_response(response)
return self.parse_action(action_str)
def parse_response(self, response) -> str:
action_str = response['choices'][0]['message']['content']
if action_str is None:
return ''
action_str = action_str.strip()
if not action_str.endswith('```'):
action_str = action_str + ')```'
logger.info(action_str)
return action_str
def parse_action(self, action_str: str) -> Action:
for action_parser in self.action_parsers:
if action_parser.check_condition(action_str):
return action_parser.parse(action_str)
return self.default_parser.parse(action_str)
class BrowsingActionParserMessage(ActionParser):
"""Parser action:
- BrowseInteractiveAction(browser_actions) - unexpected response format, message back to user
"""
def __init__(
self,
):
pass
def check_condition(self, action_str: str) -> bool:
return '```' not in action_str
def parse(self, action_str: str) -> Action:
msg = f'send_msg_to_user("""{action_str}""")'
return BrowseInteractiveAction(
browser_actions=msg,
thought=action_str,
browsergym_send_msg_to_user=action_str,
)
class BrowsingActionParserBrowseInteractive(ActionParser):
"""Parser action:
- BrowseInteractiveAction(browser_actions) - handle send message to user function call in BrowserGym
"""
def __init__(
self,
):
pass
def check_condition(self, action_str: str) -> bool:
return True
def parse(self, action_str: str) -> Action:
thought = action_str.split('```')[0].strip()
action_str = action_str.split('```')[1].strip()
msg_content = ''
for sub_action in action_str.split('\n'):
if 'send_msg_to_user(' in sub_action:
tree = ast.parse(sub_action)
args = tree.body[0].value.args # type: ignore
msg_content = args[0].value
return BrowseInteractiveAction(
browser_actions=action_str,
thought=thought,
browsergym_send_msg_to_user=msg_content,
)

View File

@@ -0,0 +1,29 @@
# CodeAct Agent Framework
This folder implements the CodeAct idea ([paper](https://arxiv.org/abs/2402.01030), [tweet](https://twitter.com/xingyaow_/status/1754556835703751087)) that consolidates LLM agents **act**ions into a unified **code** action space for both *simplicity* and *performance* (see paper for more details).
The conceptual idea is illustrated below. At each turn, the agent can:
1. **Converse**: Communicate with humans in natural language to ask for clarification, confirmation, etc.
2. **CodeAct**: Choose to perform the task by executing code
- Execute any valid Linux `bash` command
- Execute any valid `Python` code with [an interactive Python interpreter](https://ipython.org/). This is simulated through `bash` command, see plugin system below for more details.
![image](https://github.com/OpenDevin/OpenDevin/assets/38853559/92b622e3-72ad-4a61-8f41-8c040b6d5fb3)
## Plugin System
To make the CodeAct agent more powerful with only access to `bash` action space, CodeAct agent leverages OpenDevin's plugin system:
- [Jupyter plugin](https://github.com/OpenDevin/OpenDevin/tree/main/opendevin/runtime/plugins/jupyter): for IPython execution via bash command
- [SWE-agent tool plugin](https://github.com/OpenDevin/OpenDevin/tree/main/opendevin/runtime/plugins/swe_agent_commands): Powerful bash command line tools for software development tasks introduced by [swe-agent](https://github.com/princeton-nlp/swe-agent).
## Demo
https://github.com/OpenDevin/OpenDevin/assets/38853559/f592a192-e86c-4f48-ad31-d69282d5f6ac
*Example of CodeActAgent with `gpt-4-turbo-2024-04-09` performing a data science task (linear regression)*
## Work-in-progress & Next step
[] Support web-browsing
[] Complete the workflow for CodeAct agent to submit Github PRs

View File

@@ -0,0 +1,5 @@
from opendevin.controller.agent import Agent
from .codeact_agent import CodeActAgent
Agent.register('CodeActAgent', CodeActAgent)

View File

@@ -1,7 +1,7 @@
import re
from openhands.controller.action_parser import ActionParser, ResponseParser
from openhands.events.action import (
from opendevin.controller.action_parser import ActionParser, ResponseParser
from opendevin.events.action import (
Action,
AgentDelegateAction,
AgentFinishAction,
@@ -40,10 +40,6 @@ class CodeActResponseParser(ResponseParser):
if action is None:
return ''
for lang in ['bash', 'ipython', 'browse']:
# special handling for DeepSeek: it has stop-word bug and returns </execute_ipython instead of </execute_ipython>
if f'</execute_{lang}' in action and f'</execute_{lang}>' not in action:
action = action.replace(f'</execute_{lang}', f'</execute_{lang}>')
if f'<execute_{lang}>' in action and f'</execute_{lang}>' not in action:
action += f'</execute_{lang}>'
return action
@@ -158,15 +154,8 @@ class CodeActActionParserAgentDelegate(ActionParser):
), 'self.agent_delegate should not be None when parse is called'
thought = action_str.replace(self.agent_delegate.group(0), '').strip()
browse_actions = self.agent_delegate.group(1).strip()
thought = (
f'{thought}\nI should start with: {browse_actions}'
if thought
else f'I should start with: {browse_actions}'
)
return AgentDelegateAction(
agent='BrowsingAgent', thought=thought, inputs={'task': browse_actions}
)
task = f'{thought}. I should start with: {browse_actions}'
return AgentDelegateAction(agent='BrowsingAgent', inputs={'task': task})
class CodeActActionParserMessage(ActionParser):

View File

@@ -1,12 +1,15 @@
import os
from itertools import islice
from openhands.agenthub.codeact_agent.action_parser import CodeActResponseParser
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.message import ImageContent, Message, TextContent
from openhands.events.action import (
from agenthub.codeact_agent.action_parser import CodeActResponseParser
from agenthub.codeact_agent.prompt import (
COMMAND_DOCS,
EXAMPLES,
GITHUB_MESSAGE,
SYSTEM_PREFIX,
SYSTEM_SUFFIX,
)
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.message import ImageContent, Message, TextContent
from opendevin.events.action import (
Action,
AgentDelegateAction,
AgentFinishAction,
@@ -14,27 +17,38 @@ from openhands.events.action import (
IPythonRunCellAction,
MessageAction,
)
from openhands.events.observation import (
from opendevin.events.observation import (
AgentDelegateObservation,
CmdOutputObservation,
IPythonRunCellObservation,
UserRejectObservation,
)
from openhands.events.observation.error import ErrorObservation
from openhands.events.observation.observation import Observation
from openhands.events.serialization.event import truncate_content
from openhands.llm.llm import LLM
from openhands.runtime.plugins import (
from opendevin.events.observation.observation import Observation
from opendevin.events.serialization.event import truncate_content
from opendevin.llm.llm import LLM
from opendevin.runtime.plugins import (
AgentSkillsRequirement,
JupyterRequirement,
PluginRequirement,
)
from openhands.utils.microagent import MicroAgent
from openhands.utils.prompt import PromptManager
from opendevin.runtime.tools import RuntimeTool
ENABLE_GITHUB = True
# FIXME: We can tweak these two settings to create MicroAgents specialized toward different area
def get_system_message() -> str:
if ENABLE_GITHUB:
return f'{SYSTEM_PREFIX}\n{GITHUB_MESSAGE}\n\n{COMMAND_DOCS}\n\n{SYSTEM_SUFFIX}'
else:
return f'{SYSTEM_PREFIX}\n\n{COMMAND_DOCS}\n\n{SYSTEM_SUFFIX}'
def get_in_context_example() -> str:
return EXAMPLES
class CodeActAgent(Agent):
VERSION = '1.9'
VERSION = '1.8'
"""
The Code Act Agent is a minimalist agent.
The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
@@ -50,7 +64,24 @@ class CodeActAgent(Agent):
- Execute any valid Linux `bash` command
- Execute any valid `Python` code with [an interactive Python interpreter](https://ipython.org/). This is simulated through `bash` command, see plugin system below for more details.
![image](https://github.com/All-Hands-AI/OpenHands/assets/38853559/92b622e3-72ad-4a61-8f41-8c040b6d5fb3)
![image](https://github.com/OpenDevin/OpenDevin/assets/38853559/92b622e3-72ad-4a61-8f41-8c040b6d5fb3)
### Plugin System
To make the CodeAct agent more powerful with only access to `bash` action space, CodeAct agent leverages OpenDevin's plugin system:
- [Jupyter plugin](https://github.com/OpenDevin/OpenDevin/tree/main/opendevin/runtime/plugins/jupyter): for IPython execution via bash command
- [SWE-agent tool plugin](https://github.com/OpenDevin/OpenDevin/tree/main/opendevin/runtime/plugins/swe_agent_commands): Powerful bash command line tools for software development tasks introduced by [swe-agent](https://github.com/princeton-nlp/swe-agent).
### Demo
https://github.com/OpenDevin/OpenDevin/assets/38853559/f592a192-e86c-4f48-ad31-d69282d5f6ac
*Example of CodeActAgent with `gpt-4-turbo-2024-04-09` performing a data science task (linear regression)*
### Work-in-progress & Next step
[] Support web-browsing
[] Complete the workflow for CodeAct agent to submit Github PRs
"""
@@ -61,38 +92,25 @@ class CodeActAgent(Agent):
AgentSkillsRequirement(),
JupyterRequirement(),
]
runtime_tools: list[RuntimeTool] = [RuntimeTool.BROWSER]
system_message: str = get_system_message()
in_context_example: str = f"Here is an example of how you can interact with the environment for task solving:\n{get_in_context_example()}\n\nNOW, LET'S START!"
action_parser = CodeActResponseParser()
def __init__(
self,
llm: LLM,
config: AgentConfig,
) -> None:
"""Initializes a new instance of the CodeActAgent class.
Parameters:
- llm (LLM): The llm to be used by this agent
"""
super().__init__(llm, config)
super().__init__(llm)
self.reset()
self.micro_agent = (
MicroAgent(
os.path.join(
os.path.dirname(__file__), 'micro', f'{config.micro_agent_name}.md'
)
)
if config.micro_agent_name
else None
)
self.prompt_manager = PromptManager(
prompt_dir=os.path.join(os.path.dirname(__file__)),
agent_skills_docs=AgentSkillsRequirement.documentation,
micro_agent=self.micro_agent,
)
def action_to_str(self, action: Action) -> str:
if isinstance(action, CmdRunAction):
return (
@@ -118,11 +136,7 @@ class CodeActAgent(Agent):
):
content = [TextContent(text=self.action_to_str(action))]
if (
self.llm.vision_is_active()
and isinstance(action, MessageAction)
and action.images_urls
):
if isinstance(action, MessageAction) and action.images_urls:
content.append(ImageContent(image_urls=action.images_urls))
return Message(
@@ -132,15 +146,14 @@ class CodeActAgent(Agent):
def get_observation_message(self, obs: Observation) -> Message | None:
max_message_chars = self.llm.config.max_message_chars
obs_prefix = 'OBSERVATION:\n'
if isinstance(obs, CmdOutputObservation):
text = obs_prefix + truncate_content(obs.content, max_message_chars)
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text += (
f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]'
)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, IPythonRunCellObservation):
text = obs_prefix + obs.content
text = 'OBSERVATION:\n' + obs.content
# replace base64 images with a placeholder
splitted = text.split('\n')
for i, line in enumerate(splitted):
@@ -152,23 +165,11 @@ class CodeActAgent(Agent):
text = truncate_content(text, max_message_chars)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, AgentDelegateObservation):
text = obs_prefix + truncate_content(
obs.outputs['content'] if 'content' in obs.outputs else '',
max_message_chars,
text = 'OBSERVATION:\n' + truncate_content(
str(obs.outputs), max_message_chars
)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, ErrorObservation):
text = obs_prefix + truncate_content(obs.content, max_message_chars)
text += '\n[Error occurred in processing last action]'
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, UserRejectObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text += '\n[Last action has been rejected by the user]'
return Message(role='user', content=[TextContent(text=text)])
else:
# If an observation message is not returned, it will cause an error
# when the LLM tries to return the next message
raise ValueError(f'Unknown observation type: {type(obs)}')
return None
def reset(self) -> None:
"""Resets the CodeAct Agent."""
@@ -195,39 +196,22 @@ class CodeActAgent(Agent):
# prepare what we want to send to the LLM
messages = self._get_messages(state)
params = {
'messages': self.llm.format_messages_for_llm(messages),
'stop': [
response = self.llm.completion(
messages=[message.model_dump() for message in messages],
stop=[
'</execute_ipython>',
'</execute_bash>',
'</execute_browse>',
],
}
response = self.llm.completion(**params)
temperature=0.0,
)
return self.action_parser.parse(response)
def _get_messages(self, state: State) -> list[Message]:
messages: list[Message] = [
Message(
role='system',
content=[
TextContent(
text=self.prompt_manager.system_message,
cache_prompt=self.llm.is_caching_prompt_active(), # Cache system prompt
)
],
),
Message(
role='user',
content=[
TextContent(
text=self.prompt_manager.initial_user_message,
cache_prompt=self.llm.is_caching_prompt_active(), # if the user asks the same query,
)
],
),
Message(role='system', content=[TextContent(text=self.system_message)]),
Message(role='user', content=[TextContent(text=self.in_context_example)]),
]
for event in state.history.get_events():
@@ -243,38 +227,42 @@ class CodeActAgent(Agent):
if message:
# handle error if the message is the SAME role as the previous message
# litellm.exceptions.BadRequestError: litellm.BadRequestError: OpenAIException - Error code: 400 - {'detail': 'Only supports u/a/u/a/u...'}
# there shouldn't be two consecutive messages from the same role
# there should not have two consecutive messages from the same role
if messages and messages[-1].role == message.role:
messages[-1].content.extend(message.content)
else:
messages.append(message)
# Add caching to the last 2 user messages
if self.llm.is_caching_prompt_active():
user_turns_processed = 0
for message in reversed(messages):
if message.role == 'user' and user_turns_processed < 2:
message.content[
-1
].cache_prompt = True # Last item inside the message content
user_turns_processed += 1
# The latest user message is important:
# the latest user message is important:
# we want to remind the agent of the environment constraints
latest_user_message = next(
islice(
(
m
for m in reversed(messages)
if m.role == 'user'
and any(isinstance(c, TextContent) for c in m.content)
),
1,
(
m
for m in reversed(messages)
if m.role == 'user'
and any(isinstance(c, TextContent) for c in m.content)
),
None,
)
# Get the last user text inside content
if latest_user_message:
latest_user_message_text = next(
(
t
for t in reversed(latest_user_message.content)
if isinstance(t, TextContent)
)
)
# add a reminder to the prompt
reminder_text = f'\n\nENVIRONMENT REMINDER: You have {state.max_iterations - state.iteration} turns left to complete the task. When finished reply with <finish></finish>.'
latest_user_message.content.append(TextContent(text=reminder_text))
if latest_user_message_text:
latest_user_message_text.text = (
latest_user_message_text.text + reminder_text
)
else:
latest_user_message_text = TextContent(text=reminder_text)
latest_user_message.content.append(latest_user_message_text)
return messages

View File

@@ -1,4 +1,52 @@
{% set DEFAULT_EXAMPLE %}
from opendevin.runtime.plugins import AgentSkillsRequirement
_AGENT_SKILLS_DOCS = AgentSkillsRequirement.documentation
COMMAND_DOCS = (
'\nApart from the standard Python library, the assistant can also use the following functions (already imported) in <execute_ipython> environment:\n'
f'{_AGENT_SKILLS_DOCS}'
"Please note that THE `edit_file_by_replace`, `append_file` and `insert_content_at_line` FUNCTIONS REQUIRE PROPER INDENTATION. If the assistant would like to add the line ' print(x)', it must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run."
)
# ======= SYSTEM MESSAGE =======
MINIMAL_SYSTEM_PREFIX = """A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
The assistant can use an interactive Python (Jupyter Notebook) environment, executing code with <execute_ipython>.
<execute_ipython>
print("Hello World!")
</execute_ipython>
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
Important, however: do not run interactive commands. You do not have access to stdin.
Also, you need to handle commands that may run indefinitely and not return a result. For such cases, you should redirect the output to a file and run the command in the background to avoid blocking the execution.
For example, to run a Python script that might run indefinitely without returning immediately, you can use the following format: <execute_bash> python3 app.py > server.log 2>&1 & </execute_bash>
Also, if a command execution result saying like: Command: "npm start" timed out. Sending SIGINT to the process, you should also retry with running the command in the background.
"""
BROWSING_PREFIX = """The assistant can browse the Internet with <execute_browse> and </execute_browse>.
For example, <execute_browse> Tell me the usa's president using google search </execute_browse>.
Or <execute_browse> Tell me what is in http://example.com </execute_browse>.
"""
PIP_INSTALL_PREFIX = """The assistant can install Python packages using the %pip magic command in an IPython environment by using the following syntax: <execute_ipython> %pip install [package needed] </execute_ipython> and should always import packages and define variables before starting to use them."""
SYSTEM_PREFIX = MINIMAL_SYSTEM_PREFIX + BROWSING_PREFIX + PIP_INSTALL_PREFIX
GITHUB_MESSAGE = """To interact with GitHub, use the $GITHUB_TOKEN environment variable.
For example, to push a branch `my_branch` to the GitHub repo `owner/repo`:
<execute_bash> git push https://$GITHUB_TOKEN@github.com/owner/repo.git my_branch </execute_bash>
If $GITHUB_TOKEN is not set, ask the user to set it."""
SYSTEM_SUFFIX = """Responses should be concise.
The assistant should attempt fewer things at a time instead of putting too many commands OR too much code in one "execute" block.
Include ONLY ONE <execute_ipython>, <execute_bash>, or <execute_browse> per response, unless the assistant is finished with the task or needs more input or action from the user in order to proceed.
If the assistant is finished with the task you MUST include <finish></finish> in your response.
IMPORTANT: Execute code using <execute_ipython>, <execute_bash>, or <execute_browse> whenever possible.
When handling files, try to use full paths and pwd to avoid errors.
"""
# ======= EXAMPLE MESSAGE =======
EXAMPLES = """
--- START OF EXAMPLE ---
USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.
@@ -20,7 +68,7 @@ OBSERVATION:
ASSISTANT:
Now I will write the Python code for starting a web server and save it to the file `app.py`:
<execute_ipython>
EDITED_CODE="""from flask import Flask
EDITED_CODE=\"\"\"from flask import Flask
app = Flask(__name__)
@app.route('/')
@@ -29,7 +77,7 @@ def index():
return str(numbers)
if __name__ == '__main__':
app.run(port=5000)"""
app.run(port=5000)\"\"\"
insert_content_at_line(
'app.py',
@@ -95,8 +143,8 @@ Collecting click>=8.1.3
Using cached click-8.1.7-py3-none-any.whl (97 kB)
Collecting itsdangerous>=2.1.2
Using cached itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Requirement already satisfied: Jinja2>=3.1.2 in /home/openhands/.local/lib/python3.10/site-packages (from flask) (3.1.3)
Requirement already satisfied: MarkupSafe>=2.0 in /home/openhands/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
Requirement already satisfied: Jinja2>=3.1.2 in /home/opendevin/.local/lib/python3.10/site-packages (from flask) (3.1.3)
Requirement already satisfied: MarkupSafe>=2.0 in /home/opendevin/.local/lib/python3.10/site-packages (from Jinja2>=3.1.2->flask) (2.1.5)
Installing collected packages: Werkzeug, itsdangerous, click, blinker, flask
Successfully installed Werkzeug-3.0.2 blinker-1.7.0 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
@@ -217,15 +265,11 @@ The server is running on port 5000 with PID 126. You can access the list of numb
<finish></finish>
--- END OF EXAMPLE ---
{% endset %}
Here is an example of how you can interact with the environment for task solving:
{{ DEFAULT_EXAMPLE }}
{% if micro_agent %}
--- BEGIN OF GUIDELINE ---
The following information may assist you in completing your task:
"""
{{ micro_agent }}
--- END OF GUIDELINE ---
{% endif %}
NOW, LET'S START!
INVALID_INPUT_MESSAGE = (
"I don't understand your input. \n"
'For bash commands, use <execute_bash> YOUR_COMMAND </execute_bash>.\n'
'For Python code, use <execute_ipython> YOUR_CODE </execute_ipython>.\n'
'For browsing, use <execute_browse> YOUR_COMMAND </execute_browse>.\n'
)

View File

@@ -1,6 +1,6 @@
# CodeAct (SWE Edit Specialized)
This agent is an adaptation of the original [SWE Agent](https://swe-agent.com/) based on CodeAct using the `agentskills` library of OpenHands.
This agent is an adaptation of the original [SWE Agent](https://swe-agent.com/) based on CodeAct using the `agentskills` library of OpenDevin.
Its intended use is **solving GitHub issues**.

View File

@@ -0,0 +1,5 @@
from opendevin.controller.agent import Agent
from .codeact_swe_agent import CodeActSWEAgent
Agent.register('CodeActSWEAgent', CodeActSWEAgent)

View File

@@ -1,7 +1,7 @@
import re
from openhands.controller.action_parser import ActionParser
from openhands.events.action import (
from opendevin.controller.action_parser import ActionParser
from opendevin.events.action import (
Action,
AgentFinishAction,
CmdRunAction,

View File

@@ -1,36 +1,33 @@
from openhands.agenthub.codeact_swe_agent.prompt import (
from agenthub.codeact_swe_agent.prompt import (
COMMAND_DOCS,
SWE_EXAMPLE,
SYSTEM_PREFIX,
SYSTEM_SUFFIX,
)
from openhands.agenthub.codeact_swe_agent.response_parser import (
CodeActSWEResponseParser,
)
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.message import ImageContent, Message, TextContent
from openhands.events.action import (
from agenthub.codeact_swe_agent.response_parser import CodeActSWEResponseParser
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.message import ImageContent, Message, TextContent
from opendevin.events.action import (
Action,
AgentFinishAction,
CmdRunAction,
IPythonRunCellAction,
MessageAction,
)
from openhands.events.observation import (
from opendevin.events.observation import (
CmdOutputObservation,
IPythonRunCellObservation,
)
from openhands.events.observation.error import ErrorObservation
from openhands.events.observation.observation import Observation
from openhands.events.serialization.event import truncate_content
from openhands.llm.llm import LLM
from openhands.runtime.plugins import (
from opendevin.events.observation.observation import Observation
from opendevin.events.serialization.event import truncate_content
from opendevin.llm.llm import LLM
from opendevin.runtime.plugins import (
AgentSkillsRequirement,
JupyterRequirement,
PluginRequirement,
)
from opendevin.runtime.tools import RuntimeTool
def get_system_message() -> str:
@@ -44,7 +41,7 @@ def get_in_context_example() -> str:
class CodeActSWEAgent(Agent):
VERSION = '1.6'
"""
This agent is an adaptation of the original [SWE Agent](https://swe-agent.com/) based on CodeAct 1.5 using the `agentskills` library of OpenHands.
This agent is an adaptation of the original [SWE Agent](https://swe-agent.com/) based on CodeAct 1.5 using the `agentskills` library of OpenDevin.
It is intended use is **solving Github issues**.
@@ -58,6 +55,7 @@ class CodeActSWEAgent(Agent):
AgentSkillsRequirement(),
JupyterRequirement(),
]
runtime_tools: list[RuntimeTool] = []
system_message: str = get_system_message()
in_context_example: str = f"Here is an example of how you can interact with the environment for task solving:\n{get_in_context_example()}\n\nNOW, LET'S START!"
@@ -67,14 +65,13 @@ class CodeActSWEAgent(Agent):
def __init__(
self,
llm: LLM,
config: AgentConfig,
) -> None:
"""Initializes a new instance of the CodeActSWEAgent class.
"""Initializes a new instance of the CodeActAgent class.
Parameters:
- llm (LLM): The llm to be used by this agent
"""
super().__init__(llm, config)
super().__init__(llm)
self.reset()
def action_to_str(self, action: Action) -> str:
@@ -96,11 +93,7 @@ class CodeActSWEAgent(Agent):
):
content = [TextContent(text=self.action_to_str(action))]
if (
self.llm.vision_is_active()
and isinstance(action, MessageAction)
and action.images_urls
):
if isinstance(action, MessageAction) and action.images_urls:
content.append(ImageContent(image_urls=action.images_urls))
return Message(
@@ -129,14 +122,7 @@ class CodeActSWEAgent(Agent):
text = '\n'.join(splitted)
text = truncate_content(text, max_message_chars)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, ErrorObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text += '\n[Error occurred in processing last action]'
return Message(role='user', content=[TextContent(text=text)])
else:
# If an observation message is not returned, it will cause an error
# when the LLM tries to return the next message
raise ValueError(f'Unknown observation type: {type(obs)}')
return None
def reset(self) -> None:
"""Resets the CodeAct Agent."""
@@ -162,12 +148,14 @@ class CodeActSWEAgent(Agent):
# prepare what we want to send to the LLM
messages: list[Message] = self._get_messages(state)
response = self.llm.completion(
messages=self.llm.format_messages_for_llm(messages),
messages=[message.model_dump() for message in messages],
stop=[
'</execute_ipython>',
'</execute_bash>',
],
temperature=0.0,
)
return self.response_parser.parse(response)

View File

@@ -1,4 +1,4 @@
from openhands.runtime.plugins import AgentSkillsRequirement
from opendevin.runtime.plugins import AgentSkillsRequirement
_AGENT_SKILLS_DOCS = AgentSkillsRequirement.documentation

View File

@@ -1,11 +1,11 @@
from openhands.agenthub.codeact_swe_agent.action_parser import (
from agenthub.codeact_swe_agent.action_parser import (
CodeActSWEActionParserCmdRun,
CodeActSWEActionParserFinish,
CodeActSWEActionParserIPythonRunCell,
CodeActSWEActionParserMessage,
)
from openhands.controller.action_parser import ResponseParser
from openhands.events.action import Action
from opendevin.controller.action_parser import ResponseParser
from opendevin.events.action import Action
class CodeActSWEResponseParser(ResponseParser):

View File

@@ -0,0 +1,5 @@
from opendevin.controller.agent import Agent
from .agent import DelegatorAgent
Agent.register('DelegatorAgent', DelegatorAgent)

View File

@@ -1,9 +1,8 @@
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.events.action import Action, AgentDelegateAction, AgentFinishAction
from openhands.events.observation import AgentDelegateObservation
from openhands.llm.llm import LLM
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.events.action import Action, AgentDelegateAction, AgentFinishAction
from opendevin.events.observation import AgentDelegateObservation
from opendevin.llm.llm import LLM
class DelegatorAgent(Agent):
@@ -14,13 +13,13 @@ class DelegatorAgent(Agent):
current_delegate: str = ''
def __init__(self, llm: LLM, config: AgentConfig):
def __init__(self, llm: LLM):
"""Initialize the Delegator Agent with an LLM
Parameters:
- llm (LLM): The llm to be used by this agent
"""
super().__init__(llm, config)
super().__init__(llm)
def step(self, state: State) -> Action:
"""Checks to see if current step is completed, returns AgentFinishAction if True.

View File

@@ -0,0 +1,5 @@
from opendevin.controller.agent import Agent
from .agent import DummyAgent
Agent.register('DummyAgent', DummyAgent)

View File

@@ -1,10 +1,9 @@
from typing import TypedDict, Union
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.schema import AgentState
from openhands.events.action import (
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.schema import AgentState
from opendevin.events.action import (
Action,
AddTaskAction,
AgentFinishAction,
@@ -17,7 +16,7 @@ from openhands.events.action import (
MessageAction,
ModifyTaskAction,
)
from openhands.events.observation import (
from opendevin.events.observation import (
AgentStateChangedObservation,
CmdOutputObservation,
FileReadObservation,
@@ -25,8 +24,8 @@ from openhands.events.observation import (
NullObservation,
Observation,
)
from openhands.events.serialization.event import event_to_dict
from openhands.llm.llm import LLM
from opendevin.events.serialization.event import event_to_dict
from opendevin.llm.llm import LLM
"""
FIXME: There are a few problems this surfaced
@@ -46,8 +45,8 @@ class DummyAgent(Agent):
without making any LLM calls.
"""
def __init__(self, llm: LLM, config: AgentConfig):
super().__init__(llm, config)
def __init__(self, llm: LLM):
super().__init__(llm)
self.steps: list[ActionObs] = [
{
'action': AddTaskAction(

View File

@@ -1,17 +1,17 @@
from jinja2 import BaseLoader, Environment
from openhands.agenthub.micro.instructions import instructions
from openhands.agenthub.micro.registry import all_microagents
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.message import ImageContent, Message, TextContent
from openhands.core.utils import json
from openhands.events.action import Action
from openhands.events.serialization.action import action_from_dict
from openhands.events.serialization.event import event_to_memory
from openhands.llm.llm import LLM
from openhands.memory.history import ShortTermHistory
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.message import ImageContent, Message, TextContent
from opendevin.core.utils import json
from opendevin.events.action import Action
from opendevin.events.serialization.action import action_from_dict
from opendevin.events.serialization.event import event_to_memory
from opendevin.llm.llm import LLM
from opendevin.memory.history import ShortTermHistory
from .instructions import instructions
from .registry import all_microagents
def parse_response(orig_response: str) -> Action:
@@ -54,8 +54,8 @@ class MicroAgent(Agent):
return json.dumps(processed_history, **kwargs)
def __init__(self, llm: LLM, config: AgentConfig):
super().__init__(llm, config)
def __init__(self, llm: LLM):
super().__init__(llm)
if 'name' not in self.agent_definition:
raise ValueError('Agent definition must contain a name')
self.prompt_template = Environment(loader=BaseLoader).from_string(self.prompt)
@@ -73,12 +73,10 @@ class MicroAgent(Agent):
latest_user_message=last_user_message,
)
content = [TextContent(text=prompt)]
if self.llm.vision_is_active() and last_image_urls:
if last_image_urls:
content.append(ImageContent(image_urls=last_image_urls))
message = Message(role='user', content=content)
resp = self.llm.completion(
messages=self.llm.format_messages_for_llm(message),
)
resp = self.llm.completion(messages=[message.model_dump()])
action_resp = resp['choices'][0]['message']['content']
action = parse_response(action_resp)
return action

View File

@@ -4,7 +4,7 @@ CommitWriterAgent can help write git commit message. Example:
```bash
WORKSPACE_MOUNT_PATH="`PWD`" \
poetry run python openhands/core/main.py -t "dummy task" -c CommitWriterAgent -d ./
poetry run python opendevin/core/main.py -t "dummy task" -c CommitWriterAgent -d ./
```
This agent is special in the sense that it doesn't need a task. Once called,

View File

@@ -0,0 +1,5 @@
from opendevin.controller.agent import Agent
from .agent import PlannerAgent
Agent.register('PlannerAgent', PlannerAgent)

View File

@@ -1,11 +1,12 @@
from openhands.agenthub.planner_agent.prompt import get_prompt_and_images
from openhands.agenthub.planner_agent.response_parser import PlannerResponseParser
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.message import ImageContent, Message, TextContent
from openhands.events.action import Action, AgentFinishAction
from openhands.llm.llm import LLM
from agenthub.planner_agent.response_parser import PlannerResponseParser
from opendevin.controller.agent import Agent
from opendevin.controller.state.state import State
from opendevin.core.message import ImageContent, Message, TextContent
from opendevin.events.action import Action, AgentFinishAction
from opendevin.llm.llm import LLM
from opendevin.runtime.tools import RuntimeTool
from .prompt import get_prompt_and_images
class PlannerAgent(Agent):
@@ -14,15 +15,16 @@ class PlannerAgent(Agent):
The planner agent utilizes a special prompting strategy to create long term plans for solving problems.
The agent is given its previous action-observation pairs, current task, and hint based on last action taken at every step.
"""
runtime_tools: list[RuntimeTool] = [RuntimeTool.BROWSER]
response_parser = PlannerResponseParser()
def __init__(self, llm: LLM, config: AgentConfig):
def __init__(self, llm: LLM):
"""Initialize the Planner Agent with an LLM
Parameters:
- llm (LLM): The llm to be used by this agent
"""
super().__init__(llm, config)
super().__init__(llm)
def step(self, state: State) -> Action:
"""Checks to see if current step is completed, returns AgentFinishAction if True.
@@ -46,8 +48,8 @@ class PlannerAgent(Agent):
state, self.llm.config.max_message_chars
)
content = [TextContent(text=prompt)]
if self.llm.vision_is_active() and image_urls:
if image_urls:
content.append(ImageContent(image_urls=image_urls))
message = Message(role='user', content=content)
resp = self.llm.completion(messages=self.llm.format_messages_for_llm(message))
resp = self.llm.completion(messages=[message.model_dump()])
return self.response_parser.parse(resp)

View File

@@ -1,13 +1,13 @@
from openhands.controller.state.state import State
from openhands.core.logger import openhands_logger as logger
from openhands.core.schema import ActionType
from openhands.core.utils import json
from openhands.events.action import (
from opendevin.controller.state.state import State
from opendevin.core.logger import opendevin_logger as logger
from opendevin.core.schema import ActionType
from opendevin.core.utils import json
from opendevin.events.action import (
Action,
NullAction,
)
from openhands.events.serialization.action import action_from_dict
from openhands.events.serialization.event import event_to_memory
from opendevin.events.serialization.action import action_from_dict
from opendevin.events.serialization.event import event_to_memory
HISTORY_SIZE = 20

View File

@@ -1,9 +1,9 @@
from openhands.controller.action_parser import ResponseParser
from openhands.core.utils import json
from openhands.events.action import (
from opendevin.controller.action_parser import ResponseParser
from opendevin.core.utils import json
from opendevin.events.action import (
Action,
)
from openhands.events.serialization.action import action_from_dict
from opendevin.events.serialization.action import action_from_dict
class PlannerResponseParser(ResponseParser):

View File

@@ -1,5 +0,0 @@
#!/bin/bash
set -e
cp pyproject.toml poetry.lock openhands
poetry build -v

View File

@@ -1,22 +0,0 @@
#
services:
openhands:
build:
context: ./
dockerfile: ./containers/app/Dockerfile
image: openhands:latest
container_name: openhands-app-${DATE:-}
environment:
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.9-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:
- "3000:3000"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${WORKSPACE_BASE:-$PWD/workspace}:/opt/workspace_base
pull_policy: build
stdin_open: true
tty: true

View File

@@ -1,4 +1,4 @@
###################### OpenHands Configuration Example ######################
###################### OpenDevin Configuration Example ######################
#
# All settings have default values, so you only need to uncomment and
# modify what you want to change
@@ -13,10 +13,6 @@
# API key for E2B
#e2b_api_key = ""
# API key for Modal
#modal_api_token_id = ""
#modal_api_token_secret = ""
# Base path for the workspace
workspace_base = "./workspace"
@@ -32,9 +28,6 @@ workspace_base = "./workspace"
# Enable saving and restoring the session when run from CLI
#enable_cli_session = false
# Path to store trajectories
#trajectories_path="./trajectories"
# File store path
#file_store_path = "/tmp/file_store"
@@ -62,8 +55,8 @@ workspace_base = "./workspace"
# Path to rewrite the workspace mount path to
#workspace_mount_rewrite = ""
# Run as openhands
#run_as_openhands = true
# Run as devin
#run_as_devin = true
# Runtime environment
#runtime = "eventstream"
@@ -71,15 +64,6 @@ workspace_base = "./workspace"
# Name of the default agent
#default_agent = "CodeActAgent"
# JWT secret for authentication
#jwt_secret = ""
# Restrict file types for file uploads
#file_uploads_restrict_file_types = false
# List of allowed file extensions for uploads
#file_uploads_allowed_extensions = [".*"]
#################################### LLM #####################################
# Configuration for LLM models (group name starts with 'llm')
# use 'llm' for the default LLM config
@@ -119,7 +103,7 @@ api_key = "your-api-key"
#embedding_deployment_name = ""
# Embedding model to use
embedding_model = "local"
embedding_model = ""
# Maximum number of characters in an observation's content
#max_message_chars = 10000
@@ -133,31 +117,14 @@ embedding_model = "local"
# Model to use
model = "gpt-4o"
# Number of retries to attempt when an operation fails with the LLM.
# Increase this value to allow more attempts before giving up
#num_retries = 8
# Number of retries to attempt
#num_retries = 5
# Maximum wait time (in seconds) between retry attempts
# This caps the exponential backoff to prevent excessively long
#retry_max_wait = 120
# Retry maximum wait time
#retry_max_wait = 60
# Minimum wait time (in seconds) between retry attempts
# This sets the initial delay before the first retry
#retry_min_wait = 15
# Multiplier for exponential backoff calculation
# The wait time increases by this factor after each failed attempt
# A value of 2.0 means each retry waits twice as long as the previous one
#retry_multiplier = 2.0
# Drop any unmapped (unsupported) params without causing an exception
#drop_params = false
# Using the prompt caching feature if provided by the LLM and supported
#caching_prompt = true
# Base URL for the OLLAMA API
#ollama_base_url = ""
# Retry minimum wait time
#retry_min_wait = 3
# Temperature for the API
#temperature = 0.0
@@ -166,17 +133,14 @@ model = "gpt-4o"
#timeout = 0
# Top p for the API
#top_p = 1.0
#top_p = 0.5
# If model is vision capable, this option allows to disable image processing (useful for cost reduction).
#disable_vision = true
[llm.gpt4o-mini]
[llm.gpt3]
# API key to use
api_key = "your-api-key"
# Model to use
model = "gpt-4o-mini"
model = "gpt-3.5"
#################################### Agent ###################################
# Configuration for agents (group name starts with 'agent')
@@ -185,17 +149,14 @@ model = "gpt-4o-mini"
# agent.CodeActAgent
##############################################################################
[agent]
# Name of the micro agent to use for this agent
#micro_agent_name = ""
# Memory enabled
#memory_enabled = false
# Memory maximum threads
#memory_max_threads = 3
#memory_max_threads = 2
# LLM config group to use
#llm_config = 'your-llm-config-group'
#llm_config = 'llm'
[agent.RepoExplorerAgent]
# Example: use a cheaper model for RepoExplorerAgent to reduce cost, especially
@@ -213,7 +174,7 @@ llm_config = 'gpt3'
#user_id = 1000
# Container image to use for the sandbox
#base_container_image = "nikolaik/python-nodejs:python3.12-nodejs22"
#container_image = "nikolaik/python-nodejs:python3.11-nodejs22"
# Use host network
#use_host_network = false
@@ -221,29 +182,6 @@ llm_config = 'gpt3'
# Enable auto linting after editing
#enable_auto_lint = false
# Whether to initialize plugins
#initialize_plugins = true
# Extra dependencies to install in the runtime image
#runtime_extra_deps = ""
# Environment variables to set at the launch of the runtime
#runtime_startup_env_vars = {}
# BrowserGym environment to use for evaluation
#browsergym_eval_env = ""
#################################### Security ###################################
# Configuration for security features
##############################################################################
[security]
# Enable confirmation mode
#confirmation_mode = false
# The security analyzer to use
#security_analyzer = ""
#################################### Eval ####################################
# Configuration for the evaluation, please refer to the specific evaluation
# plugin for the available options

View File

@@ -7,6 +7,6 @@ by the `ghcr.yml` workflow.
## Building Manually
```bash
docker build -f containers/app/Dockerfile -t openhands .
docker build -f containers/app/Dockerfile -t opendevin .
docker build -f containers/sandbox/Dockerfile -t sandbox .
```

View File

@@ -1,4 +1,4 @@
ARG OPENHANDS_BUILD_VERSION=dev
ARG OPEN_DEVIN_BUILD_VERSION=dev
FROM node:21.7.2-bookworm-slim AS frontend-builder
WORKDIR /app
@@ -28,74 +28,62 @@ COPY ./pyproject.toml ./poetry.lock ./
RUN touch README.md
RUN export POETRY_CACHE_DIR && poetry install --without evaluation,llama-index --no-root && rm -rf $POETRY_CACHE_DIR
FROM python:3.12.3-slim AS openhands-app
FROM python:3.12.3-slim AS runtime
WORKDIR /app
ARG OPENHANDS_BUILD_VERSION #re-declare for this section
ARG OPEN_DEVIN_BUILD_VERSION #re-declare for this section
ENV RUN_AS_OPENHANDS=true
ENV RUN_AS_DEVIN=true
# A random number--we need this to be different from the user's UID on the host machine
ENV OPENHANDS_USER_ID=42420
ENV SANDBOX_LOCAL_RUNTIME_URL=http://host.docker.internal
ENV OPENDEVIN_USER_ID=42420
ENV SANDBOX_API_HOSTNAME=host.docker.internal
ENV USE_HOST_NETWORK=false
ENV WORKSPACE_BASE=/opt/workspace_base
ENV OPENHANDS_BUILD_VERSION=$OPENHANDS_BUILD_VERSION
ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION
RUN mkdir -p $WORKSPACE_BASE
RUN apt-get update -y \
&& apt-get install -y curl ssh sudo
# Install Docker - https://docs.docker.com/engine/install/debian/
RUN apt-get install ca-certificates curl \
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
&& chmod a+r /etc/apt/keyrings/docker.asc \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt install -y docker-ce
# 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 $OPENHANDS_USER_ID -s /bin/bash openhands && \
usermod -aG app openhands && \
usermod -aG sudo openhands && \
RUN useradd -l -m -u $OPENDEVIN_USER_ID -s /bin/bash opendevin && \
usermod -aG app opendevin && \
usermod -aG sudo opendevin && \
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN chown -R openhands:app /app && chmod -R 770 /app
RUN sudo chown -R openhands:app $WORKSPACE_BASE && sudo chmod -R 770 $WORKSPACE_BASE
USER openhands
RUN chown -R opendevin:app /app && chmod -R 770 /app
RUN sudo chown -R opendevin:app $WORKSPACE_BASE && sudo chmod -R 770 $WORKSPACE_BASE
USER opendevin
ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH" \
PYTHONPATH='/app'
COPY --chown=openhands:app --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY --chown=opendevin:app --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
RUN playwright install --with-deps chromium
COPY --chown=openhands:app --chmod=770 ./openhands ./openhands
COPY --chown=openhands:app --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
COPY --chown=openhands:app --chmod=770 ./openhands/agenthub ./openhands/agenthub
COPY --chown=openhands:app ./pyproject.toml ./pyproject.toml
COPY --chown=openhands:app ./poetry.lock ./poetry.lock
COPY --chown=openhands:app ./README.md ./README.md
COPY --chown=openhands:app ./MANIFEST.in ./MANIFEST.in
COPY --chown=openhands:app ./LICENSE ./LICENSE
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
# This is run as "openhands" user, and will create __pycache__ with openhands:openhands ownership
RUN python openhands/core/download.py # No-op to download assets
# Add this line to set group ownership of all files/directories not already in "app" group
# openhands:openhands -> openhands:app
RUN find /app \! -group app -exec chgrp app {} +
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
COPY --chown=openhands:app --chmod=770 --from=frontend-builder /app/build/client ./frontend/build
COPY --chown=openhands:app --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh
COPY --chown=opendevin:app --chmod=770 --from=frontend-builder /app/dist ./frontend/dist
COPY --chown=opendevin:app --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh
USER root
WORKDIR /app
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["uvicorn", "openhands.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]
CMD ["uvicorn", "opendevin.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]

View File

@@ -1,4 +1,4 @@
DOCKER_REGISTRY=ghcr.io
DOCKER_ORG=all-hands-ai
DOCKER_IMAGE=openhands
DOCKER_ORG=opendevin
DOCKER_IMAGE=opendevin
DOCKER_BASE_DIR="."

View File

@@ -1,7 +1,7 @@
#!/bin/bash
set -eo pipefail
echo "Starting OpenHands..."
echo "Starting OpenDevin..."
if [[ $NO_SETUP == "true" ]]; then
echo "Skipping setup, running as $(whoami)"
"$@"
@@ -9,7 +9,7 @@ if [[ $NO_SETUP == "true" ]]; then
fi
if [ "$(id -u)" -ne 0 ]; then
echo "The OpenHands entrypoint.sh must run as root"
echo "The OpenDevin entrypoint.sh must run as root"
exit 1
fi
@@ -19,11 +19,11 @@ if [ -z "$SANDBOX_USER_ID" ]; then
fi
if [[ "$SANDBOX_USER_ID" -eq 0 ]]; then
echo "Running OpenHands as root"
export RUN_AS_OPENHANDS=false
echo "Running OpenDevin as root"
export RUN_AS_DEVIN=false
mkdir -p /root/.cache/ms-playwright/
if [ -d "/home/openhands/.cache/ms-playwright/" ]; then
mv /home/openhands/.cache/ms-playwright/ /root/.cache/
if [ -d "/home/opendevin/.cache/ms-playwright/" ]; then
mv /home/opendevin/.cache/ms-playwright/ /root/.cache/
fi
"$@"
else
@@ -32,9 +32,9 @@ else
echo "User enduser already exists. Skipping creation."
else
if ! useradd -l -m -u $SANDBOX_USER_ID -s /bin/bash enduser; then
echo "Failed to create user enduser with id $SANDBOX_USER_ID. Moving openhands user."
echo "Failed to create user enduser with id $SANDBOX_USER_ID. Moving opendevin user."
incremented_id=$(($SANDBOX_USER_ID + 1))
usermod -u $incremented_id openhands
usermod -u $incremented_id opendevin
if ! useradd -l -m -u $SANDBOX_USER_ID -s /bin/bash enduser; then
echo "Failed to create user enduser with id $SANDBOX_USER_ID for a second time. Exiting."
exit 1
@@ -42,7 +42,7 @@ else
fi
fi
usermod -aG app enduser
# get the user group of /var/run/docker.sock and set openhands to that group
# get the user group of /var/run/docker.sock and set opendevin to that group
DOCKER_SOCKET_GID=$(stat -c '%g' /var/run/docker.sock)
echo "Docker socket group id: $DOCKER_SOCKET_GID"
if getent group $DOCKER_SOCKET_GID; then
@@ -54,11 +54,11 @@ else
mkdir -p /home/enduser/.cache/huggingface/hub/
mkdir -p /home/enduser/.cache/ms-playwright/
if [ -d "/home/openhands/.cache/ms-playwright/" ]; then
mv /home/openhands/.cache/ms-playwright/ /home/enduser/.cache/
if [ -d "/home/opendevin/.cache/ms-playwright/" ]; then
mv /home/opendevin/.cache/ms-playwright/ /home/enduser/.cache/
fi
usermod -aG $DOCKER_SOCKET_GID enduser
echo "Running as enduser"
su enduser /bin/bash -c "${*@Q}" # This magically runs any arguments passed to the script as a command
su enduser /bin/bash -c "$*"
fi

View File

@@ -1,54 +1,14 @@
#!/bin/bash
set -eo pipefail
# Initialize variables with default values
image_name=""
org_name=""
push=0
load=0
tag_suffix=""
image_name=$1
org_name=$2
platform=$3
# Function to display usage information
usage() {
echo "Usage: $0 -i <image_name> [-o <org_name>] [--push] [--load] [-t <tag_suffix>]"
echo " -i: Image name (required)"
echo " -o: Organization name"
echo " --push: Push the image"
echo " --load: Load the image"
echo " -t: Tag suffix"
exit 1
}
# Parse command-line options
while [[ $# -gt 0 ]]; do
case $1 in
-i) image_name="$2"; shift 2 ;;
-o) org_name="$2"; shift 2 ;;
--push) push=1; shift ;;
--load) load=1; shift ;;
-t) tag_suffix="$2"; shift 2 ;;
*) usage ;;
esac
done
# Check if required arguments are provided
if [[ -z "$image_name" ]]; then
echo "Error: Image name is required."
usage
fi
echo "Building: $image_name"
echo "Building: $image_name for platform: $platform"
tags=()
OPENHANDS_BUILD_VERSION="dev"
cache_tag_base="buildcache"
cache_tag="$cache_tag_base"
if [[ -n $RELEVANT_SHA ]]; then
git_hash=$(git rev-parse --short "$RELEVANT_SHA")
tags+=("$git_hash")
tags+=("$RELEVANT_SHA")
fi
OPEN_DEVIN_BUILD_VERSION="dev"
if [[ -n $GITHUB_REF_NAME ]]; then
# check if ref name is a version number
@@ -58,31 +18,22 @@ if [[ -n $GITHUB_REF_NAME ]]; then
tags+=("$major_version" "$minor_version")
tags+=("latest")
fi
sanitized_ref_name=$(echo "$GITHUB_REF_NAME" | sed 's/[^a-zA-Z0-9.-]\+/-/g')
OPENHANDS_BUILD_VERSION=$sanitized_ref_name
sanitized_ref_name=$(echo "$sanitized_ref_name" | tr '[:upper:]' '[:lower:]') # lower case is required in tagging
tags+=("$sanitized_ref_name")
cache_tag+="-${sanitized_ref_name}"
sanitized=$(echo "$GITHUB_REF_NAME" | sed 's/[^a-zA-Z0-9.-]\+/-/g')
OPEN_DEVIN_BUILD_VERSION=$sanitized
tag=$(echo "$sanitized" | tr '[:upper:]' '[:lower:]') # lower case is required in tagging
tags+=("$tag")
fi
if [[ -n $tag_suffix ]]; then
cache_tag+="-${tag_suffix}"
for i in "${!tags[@]}"; do
tags[$i]="${tags[$i]}-$tag_suffix"
done
fi
echo "Tags: ${tags[@]}"
if [[ "$image_name" == "openhands" ]]; then
if [[ "$image_name" == "opendevin" ]]; then
dir="./containers/app"
elif [[ "$image_name" == "runtime" ]]; then
elif [[ "$image_name" == "od_runtime" ]]; then
dir="./containers/runtime"
else
dir="./containers/$image_name"
fi
if [[ (! -f "$dir/Dockerfile") && "$image_name" != "runtime" ]]; then
if [[ (! -f "$dir/Dockerfile") && "$image_name" != "od_runtime" ]]; then
# Allow runtime to be built without a Dockerfile
echo "No Dockerfile found"
exit 1
@@ -98,14 +49,15 @@ if [[ -n "$org_name" ]]; then
DOCKER_ORG="$org_name"
fi
# If $DOCKER_IMAGE_HASH_TAG is set, add it to the tags
if [[ -n "$DOCKER_IMAGE_HASH_TAG" ]]; then
tags+=("$DOCKER_IMAGE_HASH_TAG")
fi
# If $DOCKER_IMAGE_TAG is set, add it to the tags
if [[ -n "$DOCKER_IMAGE_TAG" ]]; then
tags+=("$DOCKER_IMAGE_TAG")
fi
# If $DOCKER_IMAGE_HASH_TAG is set, add it to the tags
if [[ -n "$DOCKER_IMAGE_HASH_TAG" ]]; then
tags+=("$DOCKER_IMAGE_HASH_TAG")
fi
DOCKER_REPOSITORY="$DOCKER_REGISTRY/$DOCKER_ORG/$DOCKER_IMAGE"
DOCKER_REPOSITORY=${DOCKER_REPOSITORY,,} # lowercase
@@ -117,40 +69,15 @@ for tag in "${tags[@]}"; do
args+=" -t $DOCKER_REPOSITORY:$tag"
done
if [[ $push -eq 1 ]]; then
args+=" --push"
args+=" --cache-to=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag,mode=max"
fi
if [[ $load -eq 1 ]]; then
args+=" --load"
fi
echo "Args: $args"
# Modify the platform selection based on --load flag
if [[ $load -eq 1 ]]; then
# When loading, build only for the current platform
platform=$(docker version -f '{{.Server.Os}}/{{.Server.Arch}}')
else
# For push or without load, build for multiple platforms
platform="linux/amd64,linux/arm64"
fi
echo "Building for platform(s): $platform"
output_image="/tmp/${image_name}_image_${platform}.tar"
docker buildx build \
$args \
--build-arg OPENHANDS_BUILD_VERSION="$OPENHANDS_BUILD_VERSION" \
--cache-from=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag \
--cache-from=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag_base-main \
--platform $platform \
--build-arg OPEN_DEVIN_BUILD_VERSION="$OPEN_DEVIN_BUILD_VERSION" \
--platform linux/$platform \
--provenance=false \
-f "$dir/Dockerfile" \
--output type=docker,dest="$output_image" \
"$DOCKER_BASE_DIR"
# If load was requested, print the loaded images
if [[ $load -eq 1 ]]; then
echo "Local images built:"
docker images "$DOCKER_REPOSITORY" --format "{{.Repository}}:{{.Tag}}"
fi
echo "${tags[*]}" > tags.txt

View File

@@ -1,124 +0,0 @@
# syntax=docker/dockerfile:1
###
FROM ubuntu:22.04 AS dind
# https://docs.docker.com/engine/install/ubuntu/
RUN apt-get update && apt-get install -y \
ca-certificates \
curl \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \
&& chmod a+r /etc/apt/keyrings/docker.asc \
&& echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
RUN apt-get update && apt-get install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& apt-get autoremove -y
###
FROM dind AS openhands
ENV DEBIAN_FRONTEND=noninteractive
#
RUN apt-get update && apt-get install -y \
bash \
build-essential \
curl \
git \
git-lfs \
software-properties-common \
make \
netcat \
sudo \
wget \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& apt-get autoremove -y
# https://github.com/cli/cli/blob/trunk/docs/install_linux.md
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
&& apt-get update && apt-get -y install \
gh \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& apt-get autoremove -y
# Python 3.12
RUN add-apt-repository ppa:deadsnakes/ppa \
&& apt-get update \
&& apt-get install -y python3.12 python3.12-venv python3.12-dev python3-pip \
&& ln -s /usr/bin/python3.12 /usr/bin/python
# NodeJS >= 18.17.1
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs
# Poetry >= 1.8
RUN curl -fsSL https://install.python-poetry.org | python3.12 - \
&& ln -s ~/.local/bin/poetry /usr/local/bin/poetry
#
RUN <<EOF
#!/bin/bash
printf "#!/bin/bash
set +x
uname -a
docker --version
gh --version | head -n 1
git --version
#
python --version
echo node `node --version`
echo npm `npm --version`
poetry --version
netcat -h 2>&1 | head -n 1
" > /version.sh
chmod a+x /version.sh
EOF
###
FROM openhands AS dev
RUN apt-get update && apt-get install -y \
dnsutils \
file \
iproute2 \
jq \
lsof \
ripgrep \
silversearcher-ag \
vim \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& apt-get autoremove -y
WORKDIR /app
# cache build dependencies
RUN \
--mount=type=bind,source=./,target=/app/ \
<<EOF
#!/bin/bash
make -s clean
make -s check-dependencies
make -s install-python-dependencies
# NOTE
# node_modules are .dockerignore-d therefore not mountable
# make -s install-frontend-dependencies
EOF
#
CMD ["bash"]

View File

@@ -1,54 +0,0 @@
# Develop in Docker
Install [Docker](https://docs.docker.com/engine/install/) on your host machine and run:
```bash
make docker-dev
# same as:
cd ./containers/dev
./dev.sh
```
It could take some time if you are running for the first time as Docker will pull all the tools required for building OpenHands. The next time you run again, it should be instant.
## Build and run
If everything goes well, you should be inside a container after Docker finishes building the `openhands:dev` image similar to the following:
```bash
Build and run in Docker ...
root@93fc0005fcd2:/app#
```
You may now proceed with the normal [build and run](../../Development.md) workflow as if you were on the host.
## Make changes
The source code on the host is mounted as `/app` inside docker. You may edit the files as usual either inside the Docker container or on your host with your favorite IDE/editors.
The following are also mapped as readonly from your host:
```yaml
# host credentials
- $HOME/.git-credentials:/root/.git-credentials:ro
- $HOME/.gitconfig:/root/.gitconfig:ro
- $HOME/.npmrc:/root/.npmrc:ro
```
## VSCode
Alternatively, if you use VSCode, you could also [attach to the running container](https://code.visualstudio.com/docs/devcontainers/attach-container).
See details for [developing in docker](https://code.visualstudio.com/docs/devcontainers/containers) or simply ask `OpenHands` ;-)
## Rebuild dev image
You could optionally pass additional options to the build script.
```bash
make docker-dev OPTIONS="--build"
# or
./containers/dev/dev.sh --build
```
See [docker compose run](https://docs.docker.com/reference/cli/docker/compose/run/) for more options.

View File

@@ -1,38 +0,0 @@
#
services:
dev:
privileged: true
build:
context: ${OPENHANDS_WORKSPACE:-../../}
dockerfile: ./containers/dev/Dockerfile
image: openhands:dev
container_name: openhands-dev
environment:
- BACKEND_HOST=${BACKEND_HOST:-"0.0.0.0"}
- SANDBOX_API_HOSTNAME=host.docker.internal
#
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.9-nikolaik}
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
ports:
- "3000:3000"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${WORKSPACE_BASE:-$PWD/workspace}:/opt/workspace_base
# source code
- ${OPENHANDS_WORKSPACE:-../../}:/app
# host credentials
- $HOME/.git-credentials:/root/.git-credentials:ro
- $HOME/.gitconfig:/root/.gitconfig:ro
- $HOME/.npmrc:/root/.npmrc:ro
# cache
- cache-data:/root/.cache
pull_policy: never
stdin_open: true
tty: true
##
volumes:
cache-data:

Some files were not shown because too many files have changed in this diff Show More