mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
2 Commits
feat/basic
...
neubig/err
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4483c7ba83 | ||
|
|
dfc36eb861 |
@@ -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.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "OpenHands Codespaces",
|
||||
"name": "OpenDevin Codespaces",
|
||||
"image": "mcr.microsoft.com/devcontainers/universal",
|
||||
"customizations":{
|
||||
"vscode":{
|
||||
|
||||
@@ -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
|
||||
|
||||
70
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
70
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
@@ -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.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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: ''
|
||||
|
||||
28
.github/dependabot.yml
vendored
28
.github/dependabot.yml
vendored
@@ -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*"
|
||||
|
||||
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@@ -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**
|
||||
|
||||
1
.github/workflows/clean-up.yml
vendored
1
.github/workflows/clean-up.yml
vendored
@@ -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:
|
||||
|
||||
18
.github/workflows/deploy-docs.yml
vendored
18
.github/workflows/deploy-docs.yml
vendored
@@ -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
|
||||
|
||||
45
.github/workflows/dummy-agent-test.yml
vendored
45
.github/workflows/dummy-agent-test.yml
vendored
@@ -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
|
||||
|
||||
44
.github/workflows/fe-unit-tests.yml
vendored
44
.github/workflows/fe-unit-tests.yml
vendored
@@ -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 }}
|
||||
481
.github/workflows/ghcr-build.yml
vendored
481
.github/workflows/ghcr-build.yml
vendored
@@ -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
430
.github/workflows/ghcr.yml
vendored
Normal 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
|
||||
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@@ -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
|
||||
|
||||
13
.github/workflows/openhands-resolver.yml
vendored
13
.github/workflows/openhands-resolver.yml
vendored
@@ -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
|
||||
31
.github/workflows/pypi-release.yml
vendored
31
.github/workflows/pypi-release.yml
vendored
@@ -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 }}
|
||||
@@ -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
|
||||
13
.github/workflows/review-pr.yml
vendored
13
.github/workflows/review-pr.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
113
.github/workflows/solve-issue.yml
vendored
Normal 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."
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -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
6
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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`
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Exclude all Python bytecode files
|
||||
global-exclude *.pyc
|
||||
|
||||
# Exclude Python cache directories
|
||||
global-exclude __pycache__
|
||||
57
Makefile
57
Makefile
@@ -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
138
README.md
@@ -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.
|
||||
|
||||

|
||||
|
||||
## ⚡ 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
72
agenthub/README.md
Normal 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`.
|
||||
@@ -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,
|
||||
@@ -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 \
|
||||
5
agenthub/browsing_agent/__init__.py
Normal file
5
agenthub/browsing_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.controller.agent import Agent
|
||||
|
||||
from .browsing_agent import BrowsingAgent
|
||||
|
||||
Agent.register('BrowsingAgent', BrowsingAgent)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
]
|
||||
)
|
||||
88
agenthub/browsing_agent/response_parser.py
Normal file
88
agenthub/browsing_agent/response_parser.py
Normal 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,
|
||||
)
|
||||
29
agenthub/codeact_agent/README.md
Normal file
29
agenthub/codeact_agent/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
5
agenthub/codeact_agent/__init__.py
Normal file
5
agenthub/codeact_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.controller.agent import Agent
|
||||
|
||||
from .codeact_agent import CodeActAgent
|
||||
|
||||
Agent.register('CodeActAgent', CodeActAgent)
|
||||
@@ -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):
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
### 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
|
||||
@@ -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'
|
||||
)
|
||||
@@ -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**.
|
||||
|
||||
5
agenthub/codeact_swe_agent/__init__.py
Normal file
5
agenthub/codeact_swe_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.controller.agent import Agent
|
||||
|
||||
from .codeact_swe_agent import CodeActSWEAgent
|
||||
|
||||
Agent.register('CodeActSWEAgent', CodeActSWEAgent)
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
@@ -1,4 +1,4 @@
|
||||
from openhands.runtime.plugins import AgentSkillsRequirement
|
||||
from opendevin.runtime.plugins import AgentSkillsRequirement
|
||||
|
||||
_AGENT_SKILLS_DOCS = AgentSkillsRequirement.documentation
|
||||
|
||||
@@ -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):
|
||||
5
agenthub/delegator_agent/__init__.py
Normal file
5
agenthub/delegator_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.controller.agent import Agent
|
||||
|
||||
from .agent import DelegatorAgent
|
||||
|
||||
Agent.register('DelegatorAgent', DelegatorAgent)
|
||||
@@ -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.
|
||||
5
agenthub/dummy_agent/__init__.py
Normal file
5
agenthub/dummy_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.controller.agent import Agent
|
||||
|
||||
from .agent import DummyAgent
|
||||
|
||||
Agent.register('DummyAgent', DummyAgent)
|
||||
@@ -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(
|
||||
@@ -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
|
||||
@@ -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,
|
||||
5
agenthub/planner_agent/__init__.py
Normal file
5
agenthub/planner_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.controller.agent import Agent
|
||||
|
||||
from .agent import PlannerAgent
|
||||
|
||||
Agent.register('PlannerAgent', PlannerAgent)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
5
build.sh
5
build.sh
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cp pyproject.toml poetry.lock openhands
|
||||
poetry build -v
|
||||
22
compose.yml
22
compose.yml
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 .
|
||||
```
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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="."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -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
Reference in New Issue
Block a user