mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f866b4e3a | ||
|
|
36548606d9 | ||
|
|
ec087993f1 | ||
|
|
f12e9e94f7 | ||
|
|
9f720a9d69 | ||
|
|
bbf40c6576 | ||
|
|
1ffee80dcb | ||
|
|
43bc944ac4 | ||
|
|
4be33a079b | ||
|
|
c76a659cde | ||
|
|
0f68a18cbb | ||
|
|
c9ebabd82d | ||
|
|
ad932e45e8 | ||
|
|
3278caf3c2 | ||
|
|
896d7b8b96 | ||
|
|
cb61282c39 | ||
|
|
7a235ce6ff | ||
|
|
5ffb1ef704 | ||
|
|
4e4f4d64f8 | ||
|
|
3d38a105cf | ||
|
|
b1ab4d342e | ||
|
|
3e91899720 | ||
|
|
959fa3ed64 | ||
|
|
c51f07bd1f | ||
|
|
b8ef68dc60 | ||
|
|
d21bd49f08 | ||
|
|
4c265515d2 | ||
|
|
e4acfa68ec | ||
|
|
d395b5e11f | ||
|
|
6d75647c40 | ||
|
|
285010b48f | ||
|
|
395c1ea9e3 | ||
|
|
91ad59dc24 | ||
|
|
62750c07e5 | ||
|
|
cf439fa89c | ||
|
|
85c0864802 | ||
|
|
ff5d8094de | ||
|
|
660d1d1e64 | ||
|
|
78cc27a18f | ||
|
|
2db7a50e43 | ||
|
|
f3702cec35 | ||
|
|
d0cde6baf2 | ||
|
|
2e4911dc59 | ||
|
|
5378932017 | ||
|
|
4502b009c2 | ||
|
|
d3ef98bb4e | ||
|
|
06cc1ef297 | ||
|
|
17644fedd7 | ||
|
|
de4cf07d4d | ||
|
|
2eec4cea6f | ||
|
|
32ee6a5a64 | ||
|
|
2b3c38d061 | ||
|
|
7810d8c4a0 | ||
|
|
a8f1feee5d | ||
|
|
0f07805a1b | ||
|
|
8a58e724c6 | ||
|
|
996757f4f5 | ||
|
|
02bc7de36d | ||
|
|
42332294a1 | ||
|
|
6e3e9e397e | ||
|
|
fb8de1f98c | ||
|
|
9e5f767571 | ||
|
|
ce30d63510 | ||
|
|
616ff49787 | ||
|
|
0aa508c382 | ||
|
|
f32f683f92 | ||
|
|
fd5e1e0b0b | ||
|
|
f02d55ae8b | ||
|
|
be73792230 | ||
|
|
f437d06e81 |
41
.github/workflows/ghcr-build.yml
vendored
41
.github/workflows/ghcr-build.yml
vendored
@@ -41,8 +41,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.4.0
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
- name: Login to GHCR
|
||||
@@ -54,22 +56,22 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Lowercase Repository Owner
|
||||
run: |
|
||||
echo REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
|
||||
- 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
|
||||
./containers/build.sh -i openhands -o ${{ env.REPO_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
|
||||
./containers/build.sh -i openhands -o ${{ env.REPO_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
|
||||
docker run -e SANDBOX_USER_ID=0 -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/${{ env.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
|
||||
@@ -90,8 +92,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.4.0
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
- name: Login to GHCR
|
||||
@@ -122,16 +126,19 @@ jobs:
|
||||
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: Lowercase Repository Owner
|
||||
run: |
|
||||
echo REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
|
||||
- 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 }}
|
||||
./containers/build.sh -i runtime -o ${{ env.REPO_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 }}
|
||||
tags: ghcr.io/${{ env.REPO_OWNER }}/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
|
||||
@@ -151,6 +158,8 @@ jobs:
|
||||
base_image: ['nikolaik']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -233,6 +242,9 @@ jobs:
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: make install-python-dependencies
|
||||
- name: Lowercase Repository Owner
|
||||
run: |
|
||||
echo REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
|
||||
- name: Run docker runtime tests
|
||||
run: |
|
||||
# We install pytest-xdist in order to run tests across CPUs
|
||||
@@ -241,8 +253,7 @@ jobs:
|
||||
# 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:]')
|
||||
image_name=ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
|
||||
|
||||
TEST_RUNTIME=docker \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
@@ -296,6 +307,9 @@ jobs:
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: make install-python-dependencies
|
||||
- name: Lowercase Repository Owner
|
||||
run: |
|
||||
echo REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
|
||||
- name: Run runtime tests
|
||||
run: |
|
||||
# We install pytest-xdist in order to run tests across CPUs
|
||||
@@ -304,8 +318,7 @@ jobs:
|
||||
# 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:]')
|
||||
image_name=ghcr.io/${{ env.REPO_OWNER }}/runtime:${{ env.RELEVANT_SHA }}-${{ matrix.base_image }}
|
||||
|
||||
TEST_RUNTIME=docker \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
|
||||
55
CITATION.cff
Normal file
55
CITATION.cff
Normal file
@@ -0,0 +1,55 @@
|
||||
cff-version: 1.2.0
|
||||
message: "If you use this software, please cite it using the following metadata."
|
||||
title: "OpenHands: An Open Platform for AI Software Developers as Generalist Agents"
|
||||
authors:
|
||||
- family-names: Wang
|
||||
given-names: Xingyao
|
||||
- family-names: Li
|
||||
given-names: Boxuan
|
||||
- family-names: Song
|
||||
given-names: Yufan
|
||||
- family-names: Xu
|
||||
given-names: Frank F.
|
||||
- family-names: Tang
|
||||
given-names: Xiangru
|
||||
- family-names: Zhuge
|
||||
given-names: Mingchen
|
||||
- family-names: Pan
|
||||
given-names: Jiayi
|
||||
- family-names: Song
|
||||
given-names: Yueqi
|
||||
- family-names: Li
|
||||
given-names: Bowen
|
||||
- family-names: Singh
|
||||
given-names: Jaskirat
|
||||
- family-names: Tran
|
||||
given-names: Hoang H.
|
||||
- family-names: Li
|
||||
given-names: Fuqiang
|
||||
- family-names: Ma
|
||||
given-names: Ren
|
||||
- family-names: Zheng
|
||||
given-names: Mingzhang
|
||||
- family-names: Qian
|
||||
given-names: Bill
|
||||
- family-names: Shao
|
||||
given-names: Yanjun
|
||||
- family-names: Muennighoff
|
||||
given-names: Niklas
|
||||
- family-names: Zhang
|
||||
given-names: Yizhe
|
||||
- family-names: Hui
|
||||
given-names: Binyuan
|
||||
- family-names: Lin
|
||||
given-names: Junyang
|
||||
- family-names: Brennan
|
||||
given-names: Robert
|
||||
- family-names: Peng
|
||||
given-names: Hao
|
||||
- family-names: Ji
|
||||
given-names: Heng
|
||||
- family-names: Neubig
|
||||
given-names: Graham
|
||||
year: 2024
|
||||
doi: "10.48550/arXiv.2407.16741"
|
||||
url: "https://arxiv.org/abs/2407.16741"
|
||||
@@ -100,7 +100,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker container image by
|
||||
setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.26-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.28-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
These are the procedures and guidelines on how issues are triaged in this repo by the maintainers.
|
||||
|
||||
## General
|
||||
* Most issues must be tagged with **enhancement** or **bug**.
|
||||
* Issues may be tagged with what it relates to (**backend**, **frontend**, **agent quality**, etc.).
|
||||
* All issues must be tagged with **enhancement**, **bug** or **troubleshooting/help**.
|
||||
* Issues may be tagged with what it relates to (**agent quality**, **frontend**, **resolver**, etc.).
|
||||
|
||||
## Severity
|
||||
* **Low**: Minor issues or affecting single user.
|
||||
* **Medium**: Affecting multiple users.
|
||||
* **High**: High visibility issues or affecting many users.
|
||||
* **Critical**: Affecting all users or potential security issues.
|
||||
|
||||
## Effort
|
||||
@@ -18,8 +19,14 @@ These are the procedures and guidelines on how issues are triaged in this repo b
|
||||
|
||||
## Not Enough Information
|
||||
* User is asked to provide more information (logs, how to reproduce, etc.) when the issue is not clear.
|
||||
* If an issue is unclear and the author does not provide more information or respond to a request, the issue may be closed as **not planned** (Usually after a week).
|
||||
* If an issue is unclear and the author does not provide more information or respond to a request,
|
||||
the issue may be closed as **not planned** (Usually after a week).
|
||||
|
||||
## Multiple Requests/Fixes in One Issue
|
||||
* These issues will be narrowed down to one request/fix so the issue is more easily tracked and fixed.
|
||||
* Issues may be broken down into multiple issues if required.
|
||||
|
||||
## Stale and Auto Closures
|
||||
* In order to keep a maintainable backlog, issues that have no activity within 30 days are automatically marked as **Stale**.
|
||||
* If issues marked as **Stale** continue to have no activity for 7 more days, they will automatically be closed as not planned.
|
||||
* Issues may be reopened by maintainers if deemed important.
|
||||
|
||||
@@ -43,17 +43,17 @@ See the [Running OpenHands](https://docs.all-hands.dev/modules/usage/installatio
|
||||
system requirements and more information.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28
|
||||
```
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||
|
||||
@@ -95,6 +95,11 @@ workspace_base = "./workspace"
|
||||
# List of allowed file extensions for uploads
|
||||
#file_uploads_allowed_extensions = [".*"]
|
||||
|
||||
# Whether to enable the default LLM summarizing condenser when no condenser is specified in config
|
||||
# When true, a LLMSummarizingCondenserConfig will be used as the default condenser
|
||||
# When false, a NoOpCondenserConfig (no summarization) will be used
|
||||
#enable_default_condenser = true
|
||||
|
||||
#################################### LLM #####################################
|
||||
# Configuration for LLM models (group name starts with 'llm')
|
||||
# use 'llm' for the default LLM config
|
||||
@@ -294,6 +299,69 @@ llm_config = 'gpt3'
|
||||
# The security analyzer to use (For Headless / CLI only - In Web this is overridden by Session Init)
|
||||
#security_analyzer = ""
|
||||
|
||||
#################################### Condenser #################################
|
||||
# Condensers control how conversation history is managed and compressed when
|
||||
# the context grows too large. Each agent uses one condenser configuration.
|
||||
##############################################################################
|
||||
[condenser]
|
||||
# The type of condenser to use. Available options:
|
||||
# - "noop": No condensing, keeps full history (default)
|
||||
# - "observation_masking": Keeps full event structure but masks older observations
|
||||
# - "recent": Keeps only recent events and discards older ones
|
||||
# - "llm": Uses an LLM to summarize conversation history
|
||||
# - "amortized": Intelligently forgets older events while preserving important context
|
||||
# - "llm_attention": Uses an LLM to prioritize most relevant context
|
||||
type = "noop"
|
||||
|
||||
# Examples for each condenser type (uncomment and modify as needed):
|
||||
|
||||
# 1. NoOp Condenser - No additional settings needed
|
||||
#type = "noop"
|
||||
|
||||
# 2. Observation Masking Condenser
|
||||
#type = "observation_masking"
|
||||
# Number of most-recent events where observations will not be masked
|
||||
#attention_window = 100
|
||||
|
||||
# 3. Recent Events Condenser
|
||||
#type = "recent"
|
||||
# Number of initial events to always keep (typically includes task description)
|
||||
#keep_first = 1
|
||||
# Maximum number of events to keep in history
|
||||
#max_events = 100
|
||||
|
||||
# 4. LLM Summarizing Condenser
|
||||
#type = "llm"
|
||||
# Reference to an LLM config to use for summarization
|
||||
#llm_config = "condenser"
|
||||
# Number of initial events to always keep (typically includes task description)
|
||||
#keep_first = 1
|
||||
# Maximum size of history before triggering summarization
|
||||
#max_size = 100
|
||||
|
||||
# 5. Amortized Forgetting Condenser
|
||||
#type = "amortized"
|
||||
# Number of initial events to always keep (typically includes task description)
|
||||
#keep_first = 1
|
||||
# Maximum size of history before triggering forgetting
|
||||
#max_size = 100
|
||||
|
||||
# 6. LLM Attention Condenser
|
||||
#type = "llm_attention"
|
||||
# Reference to an LLM config to use for attention scoring
|
||||
#llm_config = "condenser"
|
||||
# Number of initial events to always keep (typically includes task description)
|
||||
#keep_first = 1
|
||||
# Maximum size of history before triggering attention mechanism
|
||||
#max_size = 100
|
||||
|
||||
# Example of a custom LLM configuration for condensers that require an LLM
|
||||
# If not provided, it falls back to the default LLM
|
||||
#[llm.condenser]
|
||||
#model = "gpt-4o"
|
||||
#temperature = 0.1
|
||||
#max_tokens = 1024
|
||||
|
||||
#################################### Eval ####################################
|
||||
# Configuration for the evaluation, please refer to the specific evaluation
|
||||
# plugin for the available options
|
||||
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
- 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.26-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.28-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of openhands-state for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -52,7 +52,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -61,7 +61,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -56,6 +56,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
```
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
La façon la plus simple d'exécuter OpenHands est avec Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter OpenHands en mode [headless scriptable](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), en tant que [CLI interactive](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), ou en utilisant l'[Action GitHub OpenHands](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
|
||||
@@ -13,7 +13,7 @@ C'est le Runtime par défaut qui est utilisé lorsque vous démarrez OpenHands.
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -50,7 +50,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -59,7 +59,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -57,6 +57,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
```
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
在 Docker 中运行 OpenHands 是最简单的方式。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28
|
||||
```
|
||||
|
||||
你也可以在可脚本化的[无头模式](https://docs.all-hands.dev/modules/usage/how-to/headless-mode)下运行 OpenHands,作为[交互式 CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode),或使用 [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action)。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -385,6 +385,11 @@ To use these with the docker command, pass in `-e SANDBOX_<option>`. Example: `-
|
||||
- Default: `false`
|
||||
- Description: Use host network
|
||||
|
||||
- `runtime_binding_address`
|
||||
- Type: `str`
|
||||
- Default: `127.0.0.1`
|
||||
- Description: The binding address for the runtime ports. It specifies which network interface on the host machine Docker should bind the runtime ports to.
|
||||
|
||||
### Linting and Plugins
|
||||
- `enable_auto_lint`
|
||||
- Type: `bool`
|
||||
|
||||
@@ -35,7 +35,7 @@ To run OpenHands in CLI mode with Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -45,7 +45,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ To run OpenHands in Headless mode with Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -43,7 +43,7 @@ docker run -it \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -58,17 +58,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
The easiest way to run OpenHands is in Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.26
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.28
|
||||
```
|
||||
|
||||
You'll find OpenHands running at http://localhost:3000!
|
||||
|
||||
@@ -16,7 +16,7 @@ some flags being passed to `docker run` that make this possible:
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.26-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.28-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
@@ -84,3 +84,36 @@ docker run # ...
|
||||
-e MODAL_API_TOKEN_ID="your-id" \
|
||||
-e MODAL_API_TOKEN_SECRET="your-secret" \
|
||||
```
|
||||
|
||||
## Daytona Runtime
|
||||
|
||||
Another option is using [Daytona](https://www.daytona.io/) as a runtime provider:
|
||||
|
||||
### Step 1: Retrieve Your Daytona API Key
|
||||
1. Visit the [Daytona Dashboard](https://app.daytona.io/dashboard/keys).
|
||||
2. Click **"Create Key"**.
|
||||
3. Enter a name for your key and confirm the creation.
|
||||
4. Once the key is generated, copy it.
|
||||
|
||||
### Step 2: Set Your API Key as an Environment Variable
|
||||
Run the following command in your terminal, replacing `<your-api-key>` with the actual key you copied:
|
||||
```bash
|
||||
export DAYTONA_API_KEY="<your-api-key>"
|
||||
```
|
||||
|
||||
This step ensures that OpenHands can authenticate with the Daytona platform when it runs.
|
||||
|
||||
### Step 3: Run OpenHands Locally Using Docker
|
||||
To start the latest version of OpenHands on your machine, execute the following command in your terminal:
|
||||
```bash
|
||||
bash -i <(curl -sL https://get.daytona.io/openhands)
|
||||
```
|
||||
|
||||
#### What This Command Does:
|
||||
- Downloads the latest OpenHands release script.
|
||||
- Runs the script in an interactive Bash session.
|
||||
- Automatically pulls and runs the OpenHands container using Docker.
|
||||
|
||||
Once executed, OpenHands should be running locally and ready for use.
|
||||
|
||||
For more details and manual initialization, view the entire [README.md](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/impl/daytona/README.md)
|
||||
|
||||
8
docs/package-lock.json
generated
8
docs/package-lock.json
generated
@@ -24,7 +24,7 @@
|
||||
"@docusaurus/module-type-aliases": "^3.5.1",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.5.1",
|
||||
"typescript": "~5.7.3"
|
||||
"typescript": "~5.8.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
@@ -17638,9 +17638,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@docusaurus/module-type-aliases": "^3.5.1",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.5.1",
|
||||
"typescript": "~5.7.3"
|
||||
"typescript": "~5.8.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -5,9 +5,6 @@ export function Demo() {
|
||||
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ paddingBottom: "10px", paddingTop: "10px", textAlign: "center" }}
|
||||
>
|
||||
<video
|
||||
playsInline
|
||||
autoPlay={true}
|
||||
@@ -20,6 +17,5 @@ export function Demo() {
|
||||
>
|
||||
<source src="img/teaser.mp4" type="video/mp4"></source>
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.demo {
|
||||
width: 100%;
|
||||
padding: 30px;
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
border-radius: 40px;
|
||||
|
||||
@@ -17,6 +17,29 @@ export function HomepageHeader() {
|
||||
|
||||
<p className="header-subtitle">{siteConfig.tagline}</p>
|
||||
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
fontSize: '1.2rem',
|
||||
maxWidth: '800px',
|
||||
margin: '0 auto',
|
||||
padding: '0rem 0rem 1rem'
|
||||
}}>
|
||||
<p style={{ margin: '0' }}>
|
||||
Use AI to tackle the toil in your backlog. Our agents have all the same tools as a human developer: they can modify code, run commands, browse the web,
|
||||
call APIs, and yes-even copy code snippets from StackOverflow.
|
||||
<br/>
|
||||
<Link to="https://docs.all-hands.dev/modules/usage/installation"
|
||||
style={{
|
||||
textDecoration: 'underline',
|
||||
display: 'inline-block',
|
||||
marginTop: '0.5rem'
|
||||
}}
|
||||
>
|
||||
Get started with OpenHands.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div align="center" className="header-links">
|
||||
<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>
|
||||
@@ -27,12 +50,9 @@ export function HomepageHeader() {
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<Demo />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* homepageHeader.css */
|
||||
|
||||
.homepage-header {
|
||||
height: 800px;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
padding: 1rem;
|
||||
font-weight: 300;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
.header-subtitle {
|
||||
font-size: 1.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.header-links {
|
||||
|
||||
@@ -2,15 +2,7 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import Layout from '@theme/Layout';
|
||||
import { HomepageHeader } from '../components/HomepageHeader/HomepageHeader';
|
||||
import { translate } from '@docusaurus/Translate';
|
||||
|
||||
export function Header({ title, summary }): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>{title}</h1>
|
||||
<h2 style={{ fontSize: '3rem' }}>{summary}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { Demo } from "../components/Demo/Demo";
|
||||
|
||||
export default function Home(): JSX.Element {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
@@ -23,11 +15,14 @@ export default function Home(): JSX.Element {
|
||||
})}
|
||||
>
|
||||
<HomepageHeader />
|
||||
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
||||
<br />
|
||||
|
||||
<div style={{ textAlign: 'center', padding: '1rem 0' }}>
|
||||
<Demo />
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', padding: '0.5rem 2rem 1.5rem' }}>
|
||||
<h2>Most Popular Links</h2>
|
||||
<ul style={{ listStyleType: 'none'}}>
|
||||
<li><a href="/modules/usage/Installation">How to Run OpenHands</a></li>
|
||||
<li><a href="/modules/usage/prompting/microagents-repo">Customizing OpenHands to a repository</a></li>
|
||||
<li><a href="/modules/usage/how-to/github-action">Integrating OpenHands with Github</a></li>
|
||||
<li><a href="/modules/usage/llms#model-recommendations">Recommended models to use</a></li>
|
||||
|
||||
@@ -24,6 +24,7 @@ from openhands.core.config import (
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
game = None
|
||||
|
||||
@@ -121,6 +122,7 @@ def process_instance(
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
|
||||
@@ -34,6 +34,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import AgentFinishAction, CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
|
||||
def get_config(
|
||||
@@ -210,6 +211,7 @@ def process_instance(
|
||||
# =============================================
|
||||
|
||||
runtime: Runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
initialize_runtime(runtime, instance=instance)
|
||||
|
||||
|
||||
@@ -56,9 +56,10 @@ You can update the arguments in the script
|
||||
./evaluation/benchmarks/aider_bench/scripts/run_infer.sh eval_gpt35_turbo HEAD CodeActAgent 100 1 "1,3,10"
|
||||
```
|
||||
|
||||
### Run Inference on `RemoteRuntime` (experimental)
|
||||
### Run Inference on `RemoteRuntime`
|
||||
|
||||
This is in beta. Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
|
||||
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/aider_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [eval-num-workers] [eval_ids]
|
||||
|
||||
@@ -34,6 +34,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
# Configure visibility of unit tests to the Agent.
|
||||
USE_UNIT_TESTS = os.environ.get('USE_UNIT_TESTS', 'false').lower() == 'true'
|
||||
@@ -203,7 +204,7 @@ def process_instance(
|
||||
# =============================================
|
||||
|
||||
runtime: Runtime = create_runtime(config)
|
||||
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance=instance)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -31,6 +31,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': functools.partial(
|
||||
@@ -274,6 +275,7 @@ def process_instance(
|
||||
instruction += AGENT_CLS_TO_INST_SUFFIX[metadata.agent_class]
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -34,6 +34,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
|
||||
def codeact_user_response(state: State) -> str:
|
||||
@@ -399,6 +400,7 @@ def process_instance(
|
||||
instruction += AGENT_CLS_TO_INST_SUFFIX[metadata.agent_class]
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -25,6 +25,7 @@ from openhands.core.config import (
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
# Only CodeActAgent can delegate to BrowsingAgent
|
||||
SUPPORTED_AGENT_CLS = {'CodeActAgent'}
|
||||
@@ -74,6 +75,7 @@ def process_instance(
|
||||
)
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
|
||||
@@ -23,10 +23,10 @@ Make sure your Docker daemon is running, and you have ample disk space (at least
|
||||
When the `run_infer.sh` script is started, it will automatically pull the `lite` split in Commit0. For example, for instance ID `commit-0/minitorch`, it will try to pull our pre-build docker image `wentingzhao/minitorch` from DockerHub. This image will be used create an OpenHands runtime image where the agent will operate on.
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/commit0_bench/scripts/run_infer.sh [repo_split] [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
./evaluation/benchmarks/commit0/scripts/run_infer.sh [repo_split] [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
|
||||
# Example
|
||||
./evaluation/benchmarks/commit0_bench/scripts/run_infer.sh lite llm.eval_sonnet HEAD CodeActAgent 16 100 8 wentingzhao/commit0_combined test
|
||||
./evaluation/benchmarks/commit0/scripts/run_infer.sh lite llm.eval_sonnet HEAD CodeActAgent 16 100 8 wentingzhao/commit0_combined test
|
||||
```
|
||||
|
||||
where `model_config` is mandatory, and the rest are optional.
|
||||
@@ -48,26 +48,25 @@ default, it is set to 1.
|
||||
- `dataset`, a huggingface dataset name. e.g. `wentingzhao/commit0_combined`, specifies which dataset to evaluate on.
|
||||
- `dataset_split`, split for the huggingface dataset. Notice only `test` is supported for Commit0.
|
||||
|
||||
Note that the `USE_INSTANCE_IMAGE` environment variable is always set to `true` for Commit0.
|
||||
|
||||
Let's say you'd like to run 10 instances using `llm.eval_sonnet` and CodeActAgent,
|
||||
|
||||
then your command would be:
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/commit0_bench/scripts/run_infer.sh lite llm.eval_sonnet HEAD CodeActAgent 10 30 1 wentingzhao/commit0_combined test
|
||||
./evaluation/benchmarks/commit0/scripts/run_infer.sh lite llm.eval_sonnet HEAD CodeActAgent 10 30 1 wentingzhao/commit0_combined test
|
||||
```
|
||||
|
||||
### Run Inference on `RemoteRuntime` (experimental)
|
||||
### Run Inference on `RemoteRuntime`
|
||||
|
||||
This is in beta. Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
|
||||
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/commit0_bench/scripts/run_infer.sh [repo_split] [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
./evaluation/benchmarks/commit0/scripts/run_infer.sh [repo_split] [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
|
||||
# Example - This runs evaluation on CodeActAgent for 10 instances on "wentingzhao/commit0_combined"'s test set, with max 30 iteration per instances, with 1 number of workers running in parallel
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" RUNTIME=remote SANDBOX_REMOTE_RUNTIME_API_URL="https://runtime.eval.all-hands.dev" EVAL_DOCKER_IMAGE_PREFIX="docker.io/wentingzhao" \
|
||||
./evaluation/benchmarks/commit0_bench/scripts/run_infer.sh lite llm.eval_sonnet HEAD CodeActAgent 10 30 1 wentingzhao/commit0_combined test
|
||||
./evaluation/benchmarks/commit0/scripts/run_infer.sh lite llm.eval_sonnet HEAD CodeActAgent 10 30 1 wentingzhao/commit0_combined test
|
||||
```
|
||||
|
||||
To clean-up all existing runtime you've already started, run:
|
||||
@@ -35,10 +35,10 @@ from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation, ErrorObservation
|
||||
from openhands.events.serialization.event import event_to_dict
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
from openhands.utils.shutdown_listener import sleep_if_should_continue
|
||||
|
||||
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'
|
||||
USE_INSTANCE_IMAGE = os.environ.get('USE_INSTANCE_IMAGE', 'false').lower() == 'true'
|
||||
RUN_WITH_BROWSING = os.environ.get('RUN_WITH_BROWSING', 'false').lower() == 'true'
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
@@ -104,7 +104,6 @@ def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
assert USE_INSTANCE_IMAGE
|
||||
repo_name = instance['repo'].split('/')[1]
|
||||
base_container_image = get_instance_docker_image(repo_name)
|
||||
logger.info(
|
||||
@@ -302,16 +301,6 @@ def complete_runtime(
|
||||
pytest_exit_code = obs.content.strip()
|
||||
# logger.info(f'Pytest exit code: {pytest_exit_code}')
|
||||
|
||||
# Read the test report
|
||||
action = CmdRunAction(command='cat report.json')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
isinstance(obs, CmdOutputObservation),
|
||||
f'Failed to read test report: {str(obs)}',
|
||||
)
|
||||
# Get test IDs from instance
|
||||
repo_name = instance['repo'].split('/')[1]
|
||||
repo_name = repo_name.replace('.', '-')
|
||||
@@ -322,8 +311,20 @@ def complete_runtime(
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
test_ids = obs.content.strip().split('\n')
|
||||
|
||||
# Read the test report
|
||||
action = CmdRunAction(command='cat report.json')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
isinstance(obs, CmdOutputObservation),
|
||||
f'Failed to read test report: {str(obs)}',
|
||||
)
|
||||
json_report = obs.content.strip()
|
||||
|
||||
try:
|
||||
report = json.loads(obs.content)
|
||||
report = json.loads(json_report)
|
||||
tests = {x['nodeid']: x['call'] for x in report['tests'] if 'call' in x}
|
||||
|
||||
# Calculate test statistics
|
||||
@@ -394,6 +395,7 @@ def process_instance(
|
||||
logger.info(f'Starting evaluation for instance {instance.instance_id}.')
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
try:
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
@@ -30,11 +30,6 @@ if [ -z "$MAX_ITER" ]; then
|
||||
MAX_ITER=100
|
||||
fi
|
||||
|
||||
if [ -z "$USE_INSTANCE_IMAGE" ]; then
|
||||
echo "USE_INSTANCE_IMAGE not specified, use default true"
|
||||
USE_INSTANCE_IMAGE=true
|
||||
fi
|
||||
|
||||
if [ -z "$RUN_WITH_BROWSING" ]; then
|
||||
echo "RUN_WITH_BROWSING not specified, use default false"
|
||||
RUN_WITH_BROWSING=false
|
||||
@@ -56,8 +51,6 @@ if [ -z "$SPLIT" ]; then
|
||||
SPLIT="test"
|
||||
fi
|
||||
|
||||
export USE_INSTANCE_IMAGE=$USE_INSTANCE_IMAGE
|
||||
echo "USE_INSTANCE_IMAGE: $USE_INSTANCE_IMAGE"
|
||||
export RUN_WITH_BROWSING=$RUN_WITH_BROWSING
|
||||
echo "RUN_WITH_BROWSING: $RUN_WITH_BROWSING"
|
||||
|
||||
@@ -91,7 +84,7 @@ fi
|
||||
|
||||
function run_eval() {
|
||||
local eval_note=$1
|
||||
COMMAND="poetry run python evaluation/benchmarks/commit0_bench/run_infer.py \
|
||||
COMMAND="poetry run python evaluation/benchmarks/commit0/run_infer.py \
|
||||
--agent-cls $AGENT \
|
||||
--llm-config $MODEL_CONFIG \
|
||||
--max-iterations $MAX_ITER \
|
||||
@@ -34,6 +34,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import AgentFinishAction, CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
EVALUATION_LLM = 'gpt-4-1106-preview'
|
||||
|
||||
@@ -281,6 +282,7 @@ def process_instance(
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance.data_files)
|
||||
|
||||
state: State | None = asyncio.run(
|
||||
|
||||
@@ -31,6 +31,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import AgentFinishAction, CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
DATASET_CACHE_DIR = os.path.join(os.path.dirname(__file__), 'data')
|
||||
|
||||
@@ -148,6 +149,7 @@ def process_instance(
|
||||
logger.info(f'Instruction:\n{instruction}', extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
# This file is modified from https://github.com/ShishirPatil/gorilla/blob/main/eval/eval-scripts/ast_eval_hf.py
|
||||
|
||||
import tree_sitter_python as tspython
|
||||
from tree_sitter import Language, Parser
|
||||
|
||||
|
||||
@@ -39,10 +40,9 @@ def get_all_sub_trees(root_node):
|
||||
|
||||
|
||||
# Parse the program into AST trees
|
||||
def ast_parse(candidate, lang='python'):
|
||||
LANGUAGE = Language('evaluation/gorilla/my-languages.so', lang)
|
||||
parser = Parser()
|
||||
parser.set_language(LANGUAGE)
|
||||
def ast_parse(candidate):
|
||||
LANGUAGE = Language(tspython.language())
|
||||
parser = Parser(LANGUAGE)
|
||||
|
||||
candidate_tree = parser.parse(bytes(candidate, 'utf8')).root_node
|
||||
return candidate_tree
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
# This file is modified from https://github.com/ShishirPatil/gorilla/blob/main/eval/eval-scripts/ast_eval_tf.py
|
||||
|
||||
import tree_sitter_python as tspython
|
||||
from tree_sitter import Language, Parser
|
||||
|
||||
|
||||
@@ -39,10 +40,9 @@ def get_all_sub_trees(root_node):
|
||||
|
||||
|
||||
# Parse the program into AST trees
|
||||
def ast_parse(candidate, lang='python'):
|
||||
LANGUAGE = Language('evaluation/gorilla/my-languages.so', lang)
|
||||
parser = Parser()
|
||||
parser.set_language(LANGUAGE)
|
||||
def ast_parse(candidate):
|
||||
LANGUAGE = Language(tspython.language())
|
||||
parser = Parser(LANGUAGE)
|
||||
|
||||
candidate_tree = parser.parse(bytes(candidate, 'utf8')).root_node
|
||||
return candidate_tree
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
# This file is modified from https://github.com/ShishirPatil/gorilla/blob/main/eval/eval-scripts/ast_eval_th.py
|
||||
|
||||
import tree_sitter_python as tspython
|
||||
from tree_sitter import Language, Parser
|
||||
|
||||
|
||||
@@ -39,10 +40,9 @@ def get_all_sub_trees(root_node):
|
||||
|
||||
|
||||
# Parse the program into AST trees
|
||||
def ast_parse(candidate, lang='python'):
|
||||
LANGUAGE = Language('evaluation/gorilla/my-languages.so', lang)
|
||||
parser = Parser()
|
||||
parser.set_language(LANGUAGE)
|
||||
def ast_parse(candidate):
|
||||
LANGUAGE = Language(tspython.language())
|
||||
parser = Parser(LANGUAGE)
|
||||
|
||||
candidate_tree = parser.parse(bytes(candidate, 'utf8')).root_node
|
||||
return candidate_tree
|
||||
|
||||
@@ -26,6 +26,7 @@ from openhands.core.config import (
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': codeact_user_response,
|
||||
@@ -82,6 +83,7 @@ def process_instance(
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
config=config,
|
||||
|
||||
@@ -71,19 +71,19 @@ def fetch_data(url, filename):
|
||||
|
||||
def get_data_for_hub(hub: str):
|
||||
if hub == 'hf':
|
||||
question_data = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/eval/eval-data/questions/huggingface/questions_huggingface_0_shot.jsonl'
|
||||
api_dataset = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/data/api/huggingface_api.jsonl'
|
||||
apibench = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/data/apibench/huggingface_eval.json'
|
||||
question_data = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/eval/eval-data/questions/huggingface/questions_huggingface_0_shot.jsonl'
|
||||
api_dataset = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/data/api/huggingface_api.jsonl'
|
||||
apibench = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/data/apibench/huggingface_eval.json'
|
||||
ast_eval = ast_eval_hf
|
||||
elif hub == 'torch':
|
||||
question_data = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/eval/eval-data/questions/torchhub/questions_torchhub_0_shot.jsonl'
|
||||
api_dataset = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/data/api/torchhub_api.jsonl'
|
||||
apibench = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/data/apibench/torchhub_eval.json'
|
||||
question_data = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/eval/eval-data/questions/torchhub/questions_torchhub_0_shot.jsonl'
|
||||
api_dataset = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/data/api/torchhub_api.jsonl'
|
||||
apibench = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/data/apibench/torchhub_eval.json'
|
||||
ast_eval = ast_eval_th
|
||||
elif hub == 'tf':
|
||||
question_data = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/eval/eval-data/questions/tensorflowhub/questions_tensorflowhub_0_shot.jsonl'
|
||||
api_dataset = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/data/api/tensorflowhub_api.jsonl'
|
||||
apibench = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/main/data/apibench/tensorflow_eval.json'
|
||||
question_data = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/eval/eval-data/questions/tensorflowhub/questions_tensorflowhub_0_shot.jsonl'
|
||||
api_dataset = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/data/api/tensorflowhub_api.jsonl'
|
||||
apibench = 'https://raw.githubusercontent.com/ShishirPatil/gorilla/refs/tags/v1.2/data/apibench/tensorflow_eval.json'
|
||||
ast_eval = ast_eval_tf
|
||||
|
||||
question_data = fetch_data(question_data, 'question_data.jsonl')
|
||||
|
||||
@@ -49,6 +49,7 @@ from openhands.events.action import (
|
||||
MessageAction,
|
||||
)
|
||||
from openhands.events.observation import Observation
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
ACTION_FORMAT = """
|
||||
<<FINAL_ANSWER||
|
||||
@@ -214,6 +215,7 @@ Ok now its time to start solving the question. Good luck!
|
||||
"""
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
config=config,
|
||||
|
||||
@@ -39,6 +39,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
IMPORT_HELPER = {
|
||||
'python': [
|
||||
@@ -232,6 +233,7 @@ def process_instance(
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
|
||||
@@ -31,6 +31,7 @@ from openhands.events.action import (
|
||||
)
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': codeact_user_response,
|
||||
@@ -206,6 +207,7 @@ def process_instance(
|
||||
instruction += AGENT_CLS_TO_INST_SUFFIX[metadata.agent_class]
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -19,9 +19,10 @@ Access with browser the above MiniWoB URLs and see if they load correctly.
|
||||
./evaluation/benchmarks/miniwob/scripts/run_infer.sh llm.claude-35-sonnet-eval
|
||||
```
|
||||
|
||||
### Run Inference on `RemoteRuntime` (experimental)
|
||||
### Run Inference on `RemoteRuntime`
|
||||
|
||||
This is in beta. Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
|
||||
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/miniwob/scripts/run_infer.sh [model_config] [git-version] [agent] [note] [eval_limit] [num_workers]
|
||||
|
||||
@@ -41,6 +41,7 @@ from openhands.runtime.browser.browser_env import (
|
||||
BROWSER_EVAL_GET_GOAL_ACTION,
|
||||
BROWSER_EVAL_GET_REWARDS_ACTION,
|
||||
)
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
SUPPORTED_AGENT_CLS = {'BrowsingAgent', 'CodeActAgent'}
|
||||
|
||||
@@ -145,6 +146,7 @@ def process_instance(
|
||||
logger.info(f'Starting evaluation for instance {env_id}.')
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
task_str, obs = initialize_runtime(runtime)
|
||||
|
||||
task_str += (
|
||||
|
||||
@@ -35,6 +35,7 @@ from openhands.events.action import (
|
||||
)
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
|
||||
def codeact_user_response_mint(state: State, task: Task, task_config: dict[str, int]):
|
||||
@@ -184,6 +185,7 @@ def process_instance(
|
||||
)
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime)
|
||||
|
||||
state: State | None = asyncio.run(
|
||||
|
||||
@@ -43,6 +43,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
config = load_app_config()
|
||||
|
||||
@@ -234,6 +235,7 @@ def process_instance(instance: Any, metadata: EvalMetadata, reset_logger: bool =
|
||||
instruction += AGENT_CLS_TO_INST_SUFFIX[metadata.agent_class]
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
# Run the agent
|
||||
|
||||
@@ -29,6 +29,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': codeact_user_response,
|
||||
@@ -195,6 +196,7 @@ If the program uses some packages that are incompatible, please figure out alter
|
||||
"""
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime, instance)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
This folder contains the evaluation harness that we built on top of the original [SWE-Bench benchmark](https://www.swebench.com/) ([paper](https://arxiv.org/abs/2310.06770)).
|
||||
|
||||
**UPDATE (2/18/2025): We now support running SWE-Gym using the same evaluation harness here. For more details, checkout [this README](./SWE-Gym.md).
|
||||
|
||||
**UPDATE (7/1/2024): We now support the official SWE-Bench dockerized evaluation as announced [here](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md).**
|
||||
|
||||
The evaluation consists of three steps:
|
||||
|
||||
1. Environment setup: [install python environment](../../README.md#development-environment), [configure LLM config](../../README.md#configure-openhands-and-your-llm), and [pull docker](#openhands-swe-bench-instance-level-docker-support).
|
||||
1. Environment setup: [install python environment](../../README.md#development-environment) and [configure LLM config](../../README.md#configure-openhands-and-your-llm).
|
||||
2. [Run inference](#run-inference-on-swe-bench-instances): Generate a edit patch for each Github issue
|
||||
3. [Evaluate patches using SWE-Bench docker](#evaluate-generated-patches)
|
||||
|
||||
@@ -14,22 +16,21 @@ The evaluation consists of three steps:
|
||||
|
||||
Please follow instruction [here](../../README.md#setup) to setup your local development environment and LLM.
|
||||
|
||||
## OpenHands SWE-Bench Instance-level Docker Support
|
||||
## Run Inference (Rollout) on SWE-Bench Instances: Generate Patch from Problem Statement
|
||||
|
||||
OpenHands now support using the [official evaluation docker](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md) for both **[inference](#run-inference-on-swe-bench-instances) and [evaluation](#evaluate-generated-patches)**.
|
||||
This is now the default behavior.
|
||||
### Running Locally with Docker
|
||||
|
||||
## Run Inference on SWE-Bench Instances
|
||||
Make sure your Docker daemon is running, and you have ample disk space (at least 200-500GB, depends on the SWE-Bench set you are running on) for the instance-level docker image.
|
||||
|
||||
Make sure your Docker daemon is running, and you have ample disk space (at least 200-500GB, depends on the SWE-Bench set you are running on) for the [instance-level docker image](#openhands-swe-bench-instance-level-docker-support).
|
||||
|
||||
When the `run_infer.sh` script is started, it will automatically pull the relevant SWE-Bench images. For example, for instance ID `django_django-11011`, it will try to pull our pre-build docker image `sweb.eval.x86_64.django_s_django-11011` from DockerHub. This image will be used create an OpenHands runtime image where the agent will operate on.
|
||||
When the `run_infer.sh` script is started, it will automatically pull the relevant SWE-Bench images.
|
||||
For example, for instance ID `django_django-11011`, it will try to pull our pre-build docker image `sweb.eval.x86_64.django_s_django-11011` from DockerHub.
|
||||
This image will be used create an OpenHands runtime image where the agent will operate on.
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
|
||||
# Example
|
||||
./evaluation/benchmarks/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 300 30 1 princeton-nlp/SWE-bench_Lite test
|
||||
./evaluation/benchmarks/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 500 100 1 princeton-nlp/SWE-bench_Verified test
|
||||
```
|
||||
|
||||
where `model_config` is mandatory, and the rest are optional.
|
||||
@@ -47,14 +48,16 @@ in order to use `eval_limit`, you must also set `agent`.
|
||||
default, it is set to 30.
|
||||
- `num_workers`, e.g. `3`, is the number of parallel workers to run the evaluation. By
|
||||
default, it is set to 1.
|
||||
- `dataset`, a huggingface dataset name. e.g. `princeton-nlp/SWE-bench` or `princeton-nlp/SWE-bench_Lite`, specifies which dataset to evaluate on.
|
||||
- `dataset`, a huggingface dataset name. e.g. `princeton-nlp/SWE-bench`, `princeton-nlp/SWE-bench_Lite`, or `princeton-nlp/SWE-bench_Verified`, specifies which dataset to evaluate on.
|
||||
- `dataset_split`, split for the huggingface dataset. e.g., `test`, `dev`. Default to `test`.
|
||||
|
||||
There are also two optional environment variables you can set.
|
||||
> [!CAUTION]
|
||||
> Setting `num_workers` larger than 1 is not officially tested, YMMV.
|
||||
|
||||
There is also one optional environment variable you can set.
|
||||
|
||||
```bash
|
||||
export USE_HINT_TEXT=true # if you want to use hint text in the evaluation. Default to false. Ignore this if you are not sure.
|
||||
export USE_INSTANCE_IMAGE=true # if you want to use instance-level docker images. Default to true
|
||||
```
|
||||
|
||||
Let's say you'd like to run 10 instances using `llm.eval_gpt4_1106_preview` and CodeActAgent,
|
||||
@@ -65,9 +68,11 @@ then your command would be:
|
||||
./evaluation/benchmarks/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 10
|
||||
```
|
||||
|
||||
### Run Inference on `RemoteRuntime` (experimental)
|
||||
### Running in parallel with RemoteRuntime
|
||||
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
OpenHands Remote Runtime is currently in beta (read [here](https://runtime.all-hands.dev/) for more details), it allows you to run rollout in parallel in the cloud, so you don't need a powerful machine to run evaluation.
|
||||
|
||||
Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
@@ -100,41 +105,14 @@ After running the inference, you will obtain a `output.jsonl` (by default it wil
|
||||
|
||||
## Evaluate Generated Patches
|
||||
|
||||
### Download Docker Images
|
||||
|
||||
**(Recommended for reproducibility)** If you have extra local space (e.g., 200GB), you can try pull the [instance-level docker images](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md#choosing-the-right-cache_level) we've prepared by running:
|
||||
|
||||
```bash
|
||||
evaluation/benchmarks/swe_bench/scripts/docker/pull_all_eval_docker.sh instance
|
||||
```
|
||||
|
||||
If you want to save disk space a bit (e.g., with ~50GB free disk space), while speeding up the image pre-build process, you can pull the environment-level docker images:
|
||||
|
||||
```bash
|
||||
evaluation/benchmarks/swe_bench/scripts/docker/pull_all_eval_docker.sh env
|
||||
```
|
||||
|
||||
If you want to evaluate on the full SWE-Bench test set:
|
||||
|
||||
```bash
|
||||
evaluation/benchmarks/swe_bench/scripts/docker/pull_all_eval_docker.sh instance full
|
||||
```
|
||||
|
||||
### Run evaluation
|
||||
### Run evaluation with official SWE-Bench harness (Recommend if you have local disk space)
|
||||
|
||||
With `output.jsonl` file, you can run `eval_infer.sh` to evaluate generated patches, and produce a fine-grained report.
|
||||
|
||||
**This evaluation is performed using the official dockerized evaluation announced [here](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md).**
|
||||
|
||||
> If you want to evaluate existing results, you should first run this to clone existing outputs
|
||||
>
|
||||
>```bash
|
||||
>git clone https://huggingface.co/spaces/OpenHands/evaluation evaluation/evaluation_outputs
|
||||
>```
|
||||
> [!NOTE]
|
||||
> This process will automatically download docker images from SWE-Bench official docker hub, please make sure you have enough disk space!
|
||||
|
||||
NOTE, you should have already pulled the instance-level OR env-level docker images following [this section](#openhands-swe-bench-instance-level-docker-support).
|
||||
|
||||
Then you can run the following:
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/eval_infer.sh $YOUR_OUTPUT_JSONL [instance_id] [dataset_name] [split]
|
||||
@@ -163,9 +141,10 @@ The final results will be saved to `evaluation/evaluation_outputs/outputs/swe_be
|
||||
- `report.json`: a JSON file that contains keys like `"resolved_ids"` pointing to instance IDs that are resolved by the agent.
|
||||
- `logs/`: a directory of test logs
|
||||
|
||||
### Run evaluation with `RemoteRuntime` (experimental)
|
||||
### Run evaluation with `RemoteRuntime`
|
||||
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
OpenHands Remote Runtime is currently in beta (read [here](https://runtime.all-hands.dev/) for more details), it allows you to run rollout in parallel in the cloud, so you don't need a powerful machine to run evaluation.
|
||||
Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/eval_infer_remote.sh [output.jsonl filepath] [num_workers]
|
||||
@@ -180,35 +159,3 @@ To clean-up all existing runtimes that you've already started, run:
|
||||
```bash
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" ./evaluation/utils/scripts/cleanup_remote_runtime.sh
|
||||
```
|
||||
|
||||
## Visualize Results
|
||||
|
||||
First you need to clone `https://huggingface.co/spaces/OpenHands/evaluation` and add your own running results from openhands into the `outputs` of the cloned repo.
|
||||
|
||||
```bash
|
||||
git clone https://huggingface.co/spaces/OpenHands/evaluation
|
||||
```
|
||||
|
||||
**(optional) setup streamlit environment with conda**:
|
||||
|
||||
```bash
|
||||
cd evaluation
|
||||
conda create -n streamlit python=3.10
|
||||
conda activate streamlit
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**run the visualizer**:
|
||||
Then, in a separate Python environment with `streamlit` library, you can run the following:
|
||||
|
||||
```bash
|
||||
# Make sure you are inside the cloned `evaluation` repo
|
||||
conda activate streamlit # if you follow the optional conda env setup above
|
||||
streamlit run app.py --server.port 8501 --server.address 0.0.0.0
|
||||
```
|
||||
|
||||
Then you can access the SWE-Bench trajectory visualizer at `localhost:8501`.
|
||||
|
||||
## Submit your evaluation results
|
||||
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
|
||||
128
evaluation/benchmarks/swe_bench/SWE-Gym.md
Normal file
128
evaluation/benchmarks/swe_bench/SWE-Gym.md
Normal file
@@ -0,0 +1,128 @@
|
||||
<h1 align="center"> Training Software Engineering Agents and Verifiers with SWE-Gym </h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.jiayipan.com/" style="text-decoration: none;">Jiayi Pan<sup>*,1</sup></a>,
|
||||
<a href="https://xwang.dev/" style="text-decoration: none;">Xingyao Wang<sup>*,2</sup></a>,
|
||||
<a href="https://www.phontron.com/" style="text-decoration: none;">Graham Neubig<sup>3</sup></a>,
|
||||
<a href="https://www.cs.toronto.edu/~ndjaitly/" style="text-decoration: none;">Navdeep Jaitly<sup>4</sup></a>,
|
||||
<a href="https://blender.cs.illinois.edu/hengji.html" style="text-decoration: none;">Heng Ji<sup>2</sup></a>,
|
||||
<a href="https://www.alanesuhr.com/" style="text-decoration: none;">Alane Suhr<sup>^,1</sup></a>,
|
||||
<a href="https://dreasysnail.github.io/" style="text-decoration: none;">Yizhe Zhang<sup>^,4</sup></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sup>1</sup>UC Berkeley, <sup>2</sup>UIUC, <sup>3</sup>CMU, <sup>4</sup>Apple </br>
|
||||
<sub><sup>*</sup>Equal contribution, <sup>^</sup>Equal supervision</sub>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://arxiv.org/abs/2412.21139">📃 Paper</a>
|
||||
•
|
||||
<a href="https://huggingface.co/SWE-Gym" >🤗 Data & Models</a>
|
||||
</p>
|
||||
|
||||
We present **SWE-Gym**, the first environment for training real-world software engineering agents.
|
||||
We use it to train strong LM agents that achieve state-of-the-art open results on SWE-Bench, with early, promising scaling characteristics as we increase training and inference-time compute.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/SWE-Gym/SWE-Gym/blob/main/assets/images/teaser.jpg?raw=true" width="100%" alt="teaser">
|
||||
</p>
|
||||
|
||||
---
|
||||
# Run SWE-Gym with OpenHands
|
||||
|
||||
The process of running SWE-Gym is very similar to how you'd run SWE-Bench evaluation.
|
||||
|
||||
|
||||
1. First, clone OpenHands repo `git clone https://github.com/All-Hands-AI/OpenHands.git`
|
||||
2. Then setup the repo following [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md)
|
||||
3. Then you can simply serve your own model as an OpenAI compatible endpoint, put those info in config.toml. You can do this by following instruction [here](../../README.md#setup).
|
||||
4. And then simply do the following to sample for 16x parallelism:
|
||||
|
||||
```bash
|
||||
export ALLHANDS_API_KEY=ah-yourkey # You don't need to set this when running these in local docker container
|
||||
./evaluation/benchmarks/swe_bench/scripts/rollout_swegym.sh llm.mymodel-temp05 'train-t05' 16
|
||||
```
|
||||
|
||||
NOTE: SWE-Gym sampling with parallelism is currently only tested with AllHands RemoteRuntime (limited beta). Fill [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply for access.
|
||||
|
||||
|
||||
5. When `rollout_swegym.sh` finishes, you will get a file called `output.with_completions.jsonl.gz`. Then you can use [`./scripts/swegym/convert_data.ipynb`](./scripts/swegym/convert_data.ipynb) to convert them into SFT data format.
|
||||
|
||||
---
|
||||
# More info about SWE-Gym
|
||||
|
||||
Progress in agents for software engineering has been limited by the lack of training environments that both include rigorous verification for reinforcement learning and cover the expansive tasks encountered in real-world repository-level engineering.
|
||||
|
||||
We introduce SWE-Gym: An Open Environment for Training Software Engineering Agents & Verifiers.
|
||||
Our baselines achieve new open SOTA - 32%/26% on SWE-Bench Verified/Lite, with promising scaling trends.
|
||||
|
||||

|
||||
*SWE-Gym enables scalable improvements for software engineering agents at both training and inference time. Our current results is primarily bottlenecked by training and inference compute, rather than the size of our environment.*
|
||||
|
||||
## SWE-Gym Environment
|
||||
|
||||
We create SWE-Gym, the first environment for training SWE agents, with **2.4K real tasks from 11 Python repos** & a Lite split of 234 instances. SWE-Gym combines real-world Python tasks, repository context, executable environments, and test verification to train agents for solving software engineering problems.
|
||||
|
||||

|
||||
|
||||
|
||||
## SWE-Gym trains LMs as agents
|
||||
|
||||
When fine-tuned on less than 500 agent-environment interaction trajectories sampled from it from GPT-4o and Claude 3.5 Sonnet, we achieve **+14%** absolute gains on SWE-Bench Verified with an 32B LM-powered OpenHands agent.
|
||||
|
||||

|
||||
|
||||
|
||||
## SWE-Gym enables self-improvement
|
||||
|
||||
SWE-Gym is also effective across agent scaffolds. With rejection sampling fine-tuning and MoatlessTools scaffold, our 32B and 7B models achieve 20% and 10% respectively on SWE-Bench Lite through self-improvement.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/SWE-Gym/SWE-Gym/blob/main/assets/images/ml-agent.jpg?raw=true" width="80%" alt="Moatless self-improvement">
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## SWE-Gym enables inference-time scaling
|
||||
|
||||
SWE-Gym enables inference-time scaling through verifiers trained on agent trajectories.
|
||||
These verifiers identify most promising solutions via best-of-n selection, together with our learned agents, they achieve 32%/26% on SWE-Bench Verified/Lite, a new open SoTA.
|
||||
|
||||
|
||||

|
||||
*Inference Time Scaling for Moatless Agent*
|
||||
|
||||

|
||||
*Inference Time Scaling for OpenHands Agent*
|
||||
|
||||
|
||||
## Our baselines on SWE-Gym shows strong scaling trends
|
||||
|
||||
Lastly, our ablations reveal strong scaling trends - performance is now bottlenecked by train and inference compute, rather than the size of our dataset. Pushing and improving these scaling trends further is an exciting direction for future work.
|
||||
|
||||

|
||||
|
||||
## Reproducing Results
|
||||
**The Dataset**
|
||||
|
||||
To access SWE-Gym dataset, checkout our huggingface hub page [SWE-Gym](https://huggingface.co/SWE-Gym)
|
||||
|
||||
The environment constants are currently saved at [SWE-Bench-Fork](https://github.com/SWE-Gym/SWE-Bench-Fork)
|
||||
|
||||
We also have pre-built docker images for each instance under [xingyaoww/sweb.eval.x86_64](https://hub.docker.com/search?q=xingyaoww%2Fsweb.eval.x86_64.) prefix at docker hub.
|
||||
|
||||
|
||||
## 📚 Citation
|
||||
|
||||
```bibtex
|
||||
@misc{pan2024trainingsoftwareengineeringagents,
|
||||
title={Training Software Engineering Agents and Verifiers with SWE-Gym},
|
||||
author={Jiayi Pan and Xingyao Wang and Graham Neubig and Navdeep Jaitly and Heng Ji and Alane Suhr and Yizhe Zhang},
|
||||
year={2024},
|
||||
eprint={2412.21139},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.SE},
|
||||
url={https://arxiv.org/abs/2412.21139},
|
||||
}
|
||||
```
|
||||
@@ -6,17 +6,6 @@ import time
|
||||
from functools import partial
|
||||
|
||||
import pandas as pd
|
||||
from swebench.harness.grading import get_eval_report
|
||||
from swebench.harness.run_evaluation import (
|
||||
APPLY_PATCH_FAIL,
|
||||
APPLY_PATCH_PASS,
|
||||
)
|
||||
from swebench.harness.test_spec.test_spec import (
|
||||
SWEbenchInstance,
|
||||
TestSpec,
|
||||
make_test_spec,
|
||||
)
|
||||
from swebench.harness.utils import load_swebench_dataset
|
||||
from tqdm import tqdm
|
||||
|
||||
from evaluation.benchmarks.swe_bench.resource.mapping import (
|
||||
@@ -288,7 +277,7 @@ def process_instance(
|
||||
'model_patch': model_patch,
|
||||
'instance_id': instance_id,
|
||||
},
|
||||
log_path=test_output_path,
|
||||
test_log_path=test_output_path,
|
||||
include_tests_status=True,
|
||||
)
|
||||
report = _report[instance_id]
|
||||
@@ -348,6 +337,31 @@ if __name__ == '__main__':
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
if 'SWE-Gym' in args.dataset:
|
||||
from swegym.harness.grading import get_eval_report
|
||||
from swegym.harness.run_evaluation import (
|
||||
APPLY_PATCH_FAIL,
|
||||
APPLY_PATCH_PASS,
|
||||
)
|
||||
from swegym.harness.test_spec import (
|
||||
SWEbenchInstance,
|
||||
TestSpec,
|
||||
make_test_spec,
|
||||
)
|
||||
from swegym.harness.utils import load_swebench_dataset
|
||||
else: # Newer version of SWE-Bench have different import paths
|
||||
from swebench.harness.grading import get_eval_report
|
||||
from swebench.harness.run_evaluation import (
|
||||
APPLY_PATCH_FAIL,
|
||||
APPLY_PATCH_PASS,
|
||||
)
|
||||
from swebench.harness.test_spec.test_spec import (
|
||||
SWEbenchInstance,
|
||||
TestSpec,
|
||||
make_test_spec,
|
||||
)
|
||||
from swebench.harness.utils import load_swebench_dataset
|
||||
|
||||
# Load SWE-Bench dataset
|
||||
full_dataset: list[SWEbenchInstance] = load_swebench_dataset(
|
||||
args.dataset, args.split
|
||||
|
||||
@@ -40,10 +40,10 @@ from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation, ErrorObservation
|
||||
from openhands.events.serialization.event import event_to_dict
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
from openhands.utils.shutdown_listener import sleep_if_should_continue
|
||||
|
||||
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'
|
||||
USE_INSTANCE_IMAGE = os.environ.get('USE_INSTANCE_IMAGE', 'true').lower() == 'true'
|
||||
RUN_WITH_BROWSING = os.environ.get('RUN_WITH_BROWSING', 'false').lower() == 'true'
|
||||
|
||||
|
||||
@@ -120,23 +120,18 @@ def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
SWE_BENCH_CONTAINER_IMAGE = 'ghcr.io/opendevin/eval-swe-bench:full-v1.2.1'
|
||||
if USE_INSTANCE_IMAGE:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
use_official_image = bool(
|
||||
'verified' in metadata.dataset.lower() or 'lite' in metadata.dataset.lower()
|
||||
)
|
||||
base_container_image = get_instance_docker_image(
|
||||
instance['instance_id'], use_official_image
|
||||
)
|
||||
logger.info(
|
||||
f'Using instance container image: {base_container_image}. '
|
||||
f'Please make sure this image exists. '
|
||||
f'Submit an issue on https://github.com/All-Hands-AI/OpenHands if you run into any issues.'
|
||||
)
|
||||
else:
|
||||
base_container_image = SWE_BENCH_CONTAINER_IMAGE
|
||||
logger.info(f'Using swe-bench container image: {base_container_image}')
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
use_official_image = bool(
|
||||
'verified' in metadata.dataset.lower() or 'lite' in metadata.dataset.lower()
|
||||
)
|
||||
base_container_image = get_instance_docker_image(
|
||||
instance['instance_id'], use_official_image
|
||||
)
|
||||
logger.info(
|
||||
f'Using instance container image: {base_container_image}. '
|
||||
f'Please make sure this image exists. '
|
||||
f'Submit an issue on https://github.com/All-Hands-AI/OpenHands if you run into any issues.'
|
||||
)
|
||||
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = base_container_image
|
||||
@@ -208,75 +203,65 @@ def initialize_runtime(
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to export USER: {str(obs)}')
|
||||
|
||||
if USE_INSTANCE_IMAGE:
|
||||
# inject the init script
|
||||
script_dir = os.path.dirname(__file__)
|
||||
# inject the init script
|
||||
script_dir = os.path.dirname(__file__)
|
||||
|
||||
# inject the instance info
|
||||
action = CmdRunAction(command='mkdir -p /swe_util/eval_data/instances')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to create /swe_util/eval_data/instances: {str(obs)}',
|
||||
)
|
||||
# inject the instance info
|
||||
action = CmdRunAction(command='mkdir -p /swe_util/eval_data/instances')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to create /swe_util/eval_data/instances: {str(obs)}',
|
||||
)
|
||||
|
||||
swe_instance_json_name = 'swe-bench-instance.json'
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Construct the full path for the desired file name within the temporary directory
|
||||
temp_file_path = os.path.join(temp_dir, swe_instance_json_name)
|
||||
# Write to the file with the desired name within the temporary directory
|
||||
with open(temp_file_path, 'w') as f:
|
||||
if not isinstance(instance, dict):
|
||||
json.dump([instance.to_dict()], f)
|
||||
else:
|
||||
json.dump([instance], f)
|
||||
swe_instance_json_name = 'swe-bench-instance.json'
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Construct the full path for the desired file name within the temporary directory
|
||||
temp_file_path = os.path.join(temp_dir, swe_instance_json_name)
|
||||
# Write to the file with the desired name within the temporary directory
|
||||
with open(temp_file_path, 'w') as f:
|
||||
if not isinstance(instance, dict):
|
||||
json.dump([instance.to_dict()], f)
|
||||
else:
|
||||
json.dump([instance], f)
|
||||
|
||||
# Copy the file to the desired location
|
||||
runtime.copy_to(temp_file_path, '/swe_util/eval_data/instances/')
|
||||
# Copy the file to the desired location
|
||||
runtime.copy_to(temp_file_path, '/swe_util/eval_data/instances/')
|
||||
|
||||
# inject the instance swe entry
|
||||
runtime.copy_to(
|
||||
str(os.path.join(script_dir, 'scripts/setup/instance_swe_entry.sh')),
|
||||
'/swe_util/',
|
||||
)
|
||||
action = CmdRunAction(command='cat ~/.bashrc')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to cat ~/.bashrc: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='source ~/.bashrc')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
if isinstance(obs, ErrorObservation):
|
||||
logger.error(f'Failed to source ~/.bashrc: {str(obs)}')
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to source ~/.bashrc: {str(obs)}')
|
||||
action = CmdRunAction(command='cat ~/.bashrc')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to cat ~/.bashrc: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='source /swe_util/instance_swe_entry.sh')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to source /swe_util/instance_swe_entry.sh: {str(obs)}',
|
||||
)
|
||||
else:
|
||||
action = CmdRunAction(command='source /swe_util/swe_entry.sh')
|
||||
action.set_hard_timeout(1800)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to source /swe_util/swe_entry.sh: {str(obs)}',
|
||||
)
|
||||
action = CmdRunAction(command='source ~/.bashrc')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
if isinstance(obs, ErrorObservation):
|
||||
logger.error(f'Failed to source ~/.bashrc: {str(obs)}')
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to source ~/.bashrc: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='source /swe_util/instance_swe_entry.sh')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to source /swe_util/instance_swe_entry.sh: {str(obs)}',
|
||||
)
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.set_hard_timeout(600)
|
||||
@@ -371,6 +356,32 @@ def complete_runtime(
|
||||
f'Failed to git config --global core.pager "": {str(obs)}',
|
||||
)
|
||||
|
||||
# First check for any git repositories in subdirectories
|
||||
action = CmdRunAction(command='find . -type d -name .git -not -path "./.git"')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
isinstance(obs, CmdOutputObservation) and obs.exit_code == 0,
|
||||
f'Failed to find git repositories: {str(obs)}',
|
||||
)
|
||||
|
||||
git_dirs = [p for p in obs.content.strip().split('\n') if p]
|
||||
if git_dirs:
|
||||
# Remove all .git directories in subdirectories
|
||||
for git_dir in git_dirs:
|
||||
action = CmdRunAction(command=f'rm -rf "{git_dir}"')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
isinstance(obs, CmdOutputObservation) and obs.exit_code == 0,
|
||||
f'Failed to remove git directory {git_dir}: {str(obs)}',
|
||||
)
|
||||
|
||||
# add all files
|
||||
action = CmdRunAction(command='git add -A')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
@@ -438,6 +449,7 @@ def process_instance(
|
||||
f'This is the {runtime_failure_count + 1}th attempt for instance {instance.instance_id}, setting resource factor to {config.sandbox.remote_runtime_resource_factor}'
|
||||
)
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
try:
|
||||
initialize_runtime(runtime, instance)
|
||||
@@ -521,6 +533,20 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame:
|
||||
return dataset
|
||||
|
||||
|
||||
# A list of instances that are known to be tricky to infer
|
||||
# (will cause runtime failure even with resource factor = 8)
|
||||
SWEGYM_EXCLUDE_IDS = [
|
||||
'dask__dask-10422',
|
||||
'pandas-dev__pandas-50548',
|
||||
'pandas-dev__pandas-53672',
|
||||
'pandas-dev__pandas-54174',
|
||||
'pandas-dev__pandas-55518',
|
||||
'pandas-dev__pandas-58383',
|
||||
'pydata__xarray-6721',
|
||||
'pytest-dev__pytest-10081',
|
||||
'pytest-dev__pytest-7236',
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser.add_argument(
|
||||
@@ -544,6 +570,13 @@ if __name__ == '__main__':
|
||||
logger.info(
|
||||
f'Loaded dataset {args.dataset} with split {args.split}: {len(swe_bench_tests)} tasks'
|
||||
)
|
||||
if 'SWE-Gym' in args.dataset:
|
||||
swe_bench_tests = swe_bench_tests[
|
||||
~swe_bench_tests['instance_id'].isin(SWEGYM_EXCLUDE_IDS)
|
||||
]
|
||||
logger.info(
|
||||
f'{len(swe_bench_tests)} tasks left after excluding SWE-Gym excluded tasks'
|
||||
)
|
||||
|
||||
llm_config = None
|
||||
if args.llm_config:
|
||||
@@ -587,6 +620,6 @@ if __name__ == '__main__':
|
||||
output_file,
|
||||
args.eval_num_workers,
|
||||
process_instance,
|
||||
timeout_seconds=120 * 60, # 2 hour PER instance should be more than enough
|
||||
timeout_seconds=8 * 60 * 60, # 8 hour PER instance should be more than enough
|
||||
max_retries=5,
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ def load_completions(output_dir: str, instance_id: str):
|
||||
# create messages
|
||||
messages = result['messages']
|
||||
messages.append(result['response']['choices'][0]['message'])
|
||||
tools = result['kwargs']['tools']
|
||||
tools = result['kwargs'].get('tools', [])
|
||||
return {
|
||||
'messages': messages,
|
||||
'tools': tools,
|
||||
|
||||
@@ -75,7 +75,7 @@ def load_completions(instance_id: str):
|
||||
# create messages
|
||||
messages = result['messages']
|
||||
messages.append(result['response']['choices'][0]['message'])
|
||||
tools = result['kwargs']['tools']
|
||||
tools = result['kwargs'].get('tools', None)
|
||||
return {
|
||||
'messages': messages,
|
||||
'tools': tools,
|
||||
|
||||
132
evaluation/benchmarks/swe_bench/scripts/rollout_swegym.sh
Executable file
132
evaluation/benchmarks/swe_bench/scripts/rollout_swegym.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NOTE: this script is for rolling out the SWE-Gym dataset for **TRAINING**
|
||||
# For more information, please refer to
|
||||
# 1. the Github Repo: https://github.com/SWE-Gym/SWE-Gym
|
||||
# 2. the paper: https://arxiv.org/abs/2412.21139
|
||||
|
||||
MODEL=$1 # eg your llm config name in config.toml (eg: "llm.claude-3-5-sonnet-20241022-t05")
|
||||
EXP_NAME=$2 # "train-t05"
|
||||
N_WORKERS=${3:-64}
|
||||
N_RUNS=${4:-1}
|
||||
|
||||
export EXP_NAME=$EXP_NAME
|
||||
# use 2x resources for rollout since some codebases are pretty resource-intensive
|
||||
export DEFAULT_RUNTIME_RESOURCE_FACTOR=2
|
||||
echo "MODEL: $MODEL"
|
||||
echo "EXP_NAME: $EXP_NAME"
|
||||
DATASET="SWE-Gym/SWE-Gym" # change this to the "/SWE-Gym-Lite" if you want to rollout the lite subset
|
||||
SPLIT="train"
|
||||
|
||||
if [ -z "$ALLHANDS_API_KEY" ]; then
|
||||
echo "ALLHANDS_API_KEY is not set. Will rollout and evaluate locally using Docker. WARNING: A large value of N_WORKERS will result in a large number of Docker containers being spun up and may crash your machine."
|
||||
export RUNTIME=docker
|
||||
else
|
||||
echo "ALLHANDS_API_KEY is set. Continuing rollout and evaluation with remote runtime..."
|
||||
export RUNTIME=remote
|
||||
export SANDBOX_REMOTE_RUNTIME_API_URL="https://runtime.eval.all-hands.dev"
|
||||
export EVAL_DOCKER_IMAGE_PREFIX="us-central1-docker.pkg.dev/evaluation-092424/swe-bench-images"
|
||||
fi
|
||||
|
||||
EVAL_LIMIT=3000
|
||||
MAX_ITER=100
|
||||
|
||||
|
||||
# ===== Run inference =====
|
||||
source "evaluation/utils/version_control.sh"
|
||||
get_openhands_version
|
||||
|
||||
echo "OPENHANDS_VERSION: $OPENHANDS_VERSION"
|
||||
echo "MODEL_CONFIG: $MODEL_CONFIG"
|
||||
echo "DATASET: $DATASET"
|
||||
echo "SPLIT: $SPLIT"
|
||||
|
||||
# Default to NOT use Hint
|
||||
export USE_INSTANCE_IMAGE=true
|
||||
export USE_HINT_TEXT=false
|
||||
export RUN_WITH_BROWSING=false
|
||||
echo "USE_HINT_TEXT: $USE_HINT_TEXT"
|
||||
EVAL_NOTE="$OPENHANDS_VERSION-no-hint-$EXP_NAME"
|
||||
|
||||
function run_eval() {
|
||||
local eval_note=$1
|
||||
COMMAND="poetry run python evaluation/benchmarks/swe_bench/run_infer.py \
|
||||
--agent-cls CodeActAgent \
|
||||
--llm-config $MODEL \
|
||||
--max-iterations $MAX_ITER \
|
||||
--eval-num-workers $N_WORKERS \
|
||||
--eval-note $eval_note \
|
||||
--dataset $DATASET \
|
||||
--split $SPLIT"
|
||||
|
||||
if [ -n "$EVAL_LIMIT" ]; then
|
||||
echo "EVAL_LIMIT: $EVAL_LIMIT"
|
||||
COMMAND="$COMMAND --eval-n-limit $EVAL_LIMIT"
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
eval $COMMAND
|
||||
}
|
||||
|
||||
for run_idx in $(seq 1 $N_RUNS); do
|
||||
|
||||
while true; do
|
||||
echo "### Running inference... ###"
|
||||
unset SANDBOX_ENV_GITHUB_TOKEN # prevent the agent from using the github token to push
|
||||
current_eval_note="$EVAL_NOTE-run_$run_idx"
|
||||
echo "EVAL_NOTE: $current_eval_note"
|
||||
INFER_OUTPUT=$(run_eval $current_eval_note)
|
||||
INFER_STATUS=$? # Capture the exit status of run_infer.sh
|
||||
echo "INFER_STATUS: $INFER_STATUS"
|
||||
|
||||
echo "### Cleaning up remote runtime... ###"
|
||||
./evaluation/utils/scripts/cleanup_remote_runtime.sh
|
||||
|
||||
if [ $INFER_STATUS -eq 0 ]; then
|
||||
echo "### Inference completed successfully. ###"
|
||||
break
|
||||
else
|
||||
echo "### Inference failed with exit code $INFER_STATUS. Retrying... ###"
|
||||
fi
|
||||
done
|
||||
|
||||
# Extract the output directory using the special delimiters
|
||||
OUTPUT_FILE=$(echo "$INFER_OUTPUT" | grep -o '### OUTPUT FILE:.* ###' | sed 's/### OUTPUT FILE: \(.*\) ###/\1/')
|
||||
echo "Got OUTPUT_FILE: $OUTPUT_FILE"
|
||||
|
||||
while true; do
|
||||
echo "### Evaluating on $OUTPUT_FILE ... ###"
|
||||
COMMAND="poetry run python evaluation/benchmarks/swe_bench/eval_infer.py \
|
||||
--eval-num-workers $((N_WORKERS * 2)) \
|
||||
--input-file $OUTPUT_FILE \
|
||||
--dataset $DATASET \
|
||||
--split $SPLIT"
|
||||
|
||||
if [ -n "$EVAL_LIMIT" ]; then
|
||||
echo "EVAL_LIMIT: $EVAL_LIMIT"
|
||||
COMMAND="$COMMAND --eval-n-limit $EVAL_LIMIT"
|
||||
fi
|
||||
echo "Running command: $COMMAND"
|
||||
# Run the command
|
||||
eval $COMMAND
|
||||
EVAL_STATUS=$?
|
||||
if [ $EVAL_STATUS -eq 0 ]; then
|
||||
echo "### Evaluation completed successfully. ###"
|
||||
break
|
||||
else
|
||||
echo "### Evaluation failed with exit code $EVAL_STATUS. Retrying... ###"
|
||||
fi
|
||||
|
||||
./evaluation/utils/scripts/cleanup_remote_runtime.sh
|
||||
done
|
||||
|
||||
# update the output with evaluation results
|
||||
echo "### Updating the output with evaluation results... ###"
|
||||
poetry run python evaluation/benchmarks/swe_bench/scripts/eval/update_output_with_eval.py $OUTPUT_FILE
|
||||
|
||||
echo "### Combining the final completions... ###"
|
||||
poetry run python evaluation/benchmarks/swe_bench/scripts/eval/combine_final_completions.py $OUTPUT_FILE
|
||||
|
||||
echo "### DONE for run $run_idx! ###"
|
||||
echo "You can find the final output at $(dirname $OUTPUT_FILE)/$FINAL_OUTPUT_FILE"
|
||||
done
|
||||
@@ -29,11 +29,6 @@ if [ -z "$MAX_ITER" ]; then
|
||||
MAX_ITER=100
|
||||
fi
|
||||
|
||||
if [ -z "$USE_INSTANCE_IMAGE" ]; then
|
||||
echo "USE_INSTANCE_IMAGE not specified, use default true"
|
||||
USE_INSTANCE_IMAGE=true
|
||||
fi
|
||||
|
||||
if [ -z "$RUN_WITH_BROWSING" ]; then
|
||||
echo "RUN_WITH_BROWSING not specified, use default false"
|
||||
RUN_WITH_BROWSING=false
|
||||
@@ -50,8 +45,6 @@ if [ -z "$SPLIT" ]; then
|
||||
SPLIT="test"
|
||||
fi
|
||||
|
||||
export USE_INSTANCE_IMAGE=$USE_INSTANCE_IMAGE
|
||||
echo "USE_INSTANCE_IMAGE: $USE_INSTANCE_IMAGE"
|
||||
export RUN_WITH_BROWSING=$RUN_WITH_BROWSING
|
||||
echo "RUN_WITH_BROWSING: $RUN_WITH_BROWSING"
|
||||
|
||||
|
||||
287
evaluation/benchmarks/swe_bench/scripts/swegym/convert_data.ipynb
vendored
Normal file
287
evaluation/benchmarks/swe_bench/scripts/swegym/convert_data.ipynb
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"import pandas as pd\n",
|
||||
"from tqdm import tqdm\n",
|
||||
"\n",
|
||||
"tqdm.pandas()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 1. Load raw data and convert to training data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import gzip\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"from tqdm import tqdm\n",
|
||||
"\n",
|
||||
"FILE_PATHS = [\n",
|
||||
" 'YOURPATH-no-hint-train-t05-run_1/output.with_completions.jsonl.gz',\n",
|
||||
" 'YOURPATH-no-hint-train-t05-run_2/output.with_completions.jsonl.gz',\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"# More memory efficient for large files\n",
|
||||
"# Initialize lists to store the data\n",
|
||||
"data = []\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Read file line by line\n",
|
||||
"for FILE_PATH in FILE_PATHS:\n",
|
||||
" with gzip.open(FILE_PATH, 'rb') as f: # Use 'rb' for gzipped files\n",
|
||||
" for i, line in tqdm(\n",
|
||||
" enumerate(f), desc=f\"Processing {FILE_PATH.split('/')[-1]}\"\n",
|
||||
" ):\n",
|
||||
" # Parse only the fields we need\n",
|
||||
" raw_data = json.loads(line)\n",
|
||||
" data.append(\n",
|
||||
" {\n",
|
||||
" 'resolved': raw_data['report']['resolved'],\n",
|
||||
" 'messages': raw_data['raw_completions']['messages']\n",
|
||||
" if raw_data['raw_completions'] is not None\n",
|
||||
" else None,\n",
|
||||
" 'git_patch': raw_data['test_result'].get('git_patch', ''),\n",
|
||||
" 'tools': raw_data['raw_completions']['tools']\n",
|
||||
" if raw_data['raw_completions'] is not None\n",
|
||||
" and 'tools' in raw_data['raw_completions']\n",
|
||||
" else None,\n",
|
||||
" }\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"# Convert to DataFrame after collecting all data\n",
|
||||
"df = pd.DataFrame(data)\n",
|
||||
"print(f'#total amount of data={len(df)}')\n",
|
||||
"df = df[~df['messages'].isna()]\n",
|
||||
"print(f'#total amount of data after removing nan={len(df)}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Filter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def _contains_multiple_tool_calls(messages: list[dict]) -> bool:\n",
|
||||
" return any(\n",
|
||||
" message.get('tool_calls') and len(message['tool_calls']) > 1\n",
|
||||
" for message in messages\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df['contains_multiple_tool_calls'] = df['messages'].apply(_contains_multiple_tool_calls)\n",
|
||||
"display(df.groupby(['contains_multiple_tool_calls'])['resolved'].sum())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import copy\n",
|
||||
"\n",
|
||||
"# Convert function calling messages to non-function calling messages\n",
|
||||
"from openhands.llm.fn_call_converter import (\n",
|
||||
" FunctionCallConversionError,\n",
|
||||
" convert_fncall_messages_to_non_fncall_messages,\n",
|
||||
" convert_from_multiple_tool_calls_to_single_tool_call_messages,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"total_failed = 0\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _convert_messages(messages: list[dict], tools: list[dict]) -> list[dict]:\n",
|
||||
" global total_failed\n",
|
||||
" message_copy = copy.deepcopy(messages)\n",
|
||||
" for message in message_copy:\n",
|
||||
" if message['content'] is None:\n",
|
||||
" message['content'] = ''\n",
|
||||
" try:\n",
|
||||
" return convert_fncall_messages_to_non_fncall_messages(\n",
|
||||
" message_copy, tools, add_in_context_learning_example=False\n",
|
||||
" )\n",
|
||||
" except FunctionCallConversionError:\n",
|
||||
" total_failed += 1\n",
|
||||
" # print(f'Failed to convert messages: {messages}\\nTools: {tools}')\n",
|
||||
" # traceback.print_exc()\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"df['converted_messages'] = df.apply(\n",
|
||||
" lambda row: convert_from_multiple_tool_calls_to_single_tool_call_messages(\n",
|
||||
" row['messages'], ignore_final_tool_result=True\n",
|
||||
" ),\n",
|
||||
" axis=1,\n",
|
||||
")\n",
|
||||
"df['nonfncall_messages'] = df.apply(\n",
|
||||
" lambda row: _convert_messages(row['converted_messages'], row['tools']), axis=1\n",
|
||||
")\n",
|
||||
"print('total nan', df['nonfncall_messages'].isna().sum())\n",
|
||||
"df = df[~df['nonfncall_messages'].isna()]\n",
|
||||
"print(f'Total failed: {total_failed}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Tokenization"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pandarallel import pandarallel\n",
|
||||
"from transformers import AutoTokenizer\n",
|
||||
"\n",
|
||||
"os.environ['TOKENIZERS_PARALLELISM'] = 'false'\n",
|
||||
"pandarallel.initialize(progress_bar=True, verbose=1, nb_workers=16)\n",
|
||||
"tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen2.5-7B-Instruct')\n",
|
||||
"df['n_tokens'] = df['rm_conv'].parallel_apply(\n",
|
||||
" lambda x: len(tokenizer.apply_chat_template(x))\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(f'BEFORE: #total={len(df)}')\n",
|
||||
"df_selected = df[df['n_tokens'] < 131072]\n",
|
||||
"print(f'AFTER(truncated to 128k): #total={len(df_selected)}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_selected['n_tokens'].describe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# ecdf of n_tokens\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import seaborn as sns\n",
|
||||
"\n",
|
||||
"display(df.groupby(['resolved'])['n_tokens'].describe())\n",
|
||||
"sns.ecdfplot(x='n_tokens', data=df, hue='resolved')\n",
|
||||
"plt.show()\n",
|
||||
"\n",
|
||||
"print(f'#total={len(df)}')\n",
|
||||
"df_selected = df[df['n_tokens'] < 131072]\n",
|
||||
"print(f'#selected={len(df_selected)}')\n",
|
||||
"display(df_selected.groupby(['resolved'])['n_tokens'].describe())\n",
|
||||
"sns.ecdfplot(x='n_tokens', data=df_selected, hue='resolved')\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_selected[~df_selected['resolved']]['n_tokens'].describe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_selected['resolved'].value_counts()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_selected.groupby(['resolved'])['n_tokens'].describe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Save Resolved Messages for SFT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df_selected[df_selected['resolved']][['nonfncall_messages']].rename(\n",
|
||||
" columns={'nonfncall_messages': 'messages'}\n",
|
||||
").to_json(\n",
|
||||
" os.path.join(\n",
|
||||
" 'YOUR_OUTPUT_FOLDER',\n",
|
||||
" f'policy_traj_128k_swegym_{df_selected[\"resolved\"].value_counts()[True]}i.jsonl',\n",
|
||||
" ),\n",
|
||||
" lines=True,\n",
|
||||
" orient='records',\n",
|
||||
")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "openhands-ai-CPy6G0pU-py3.12",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.8"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events.action import BrowseInteractiveAction
|
||||
@@ -133,7 +133,7 @@ def parse_content_to_elements(content: str) -> Dict[str, str]:
|
||||
return elements
|
||||
|
||||
|
||||
def find_matching_anchor(content: str, selector: str) -> Optional[str]:
|
||||
def find_matching_anchor(content: str, selector: str) -> str | None:
|
||||
"""Find the anchor ID that matches the given selector description"""
|
||||
elements = parse_content_to_elements(content)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import BrowserOutputObservation, CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
|
||||
def get_config(
|
||||
@@ -275,7 +276,7 @@ if __name__ == '__main__':
|
||||
args.task_image_name, task_short_name, temp_dir, agent_llm_config, agent_config
|
||||
)
|
||||
runtime: Runtime = create_runtime(config)
|
||||
|
||||
call_async_from_sync(runtime.connect)
|
||||
init_task_env(runtime, args.server_hostname, env_llm_config)
|
||||
|
||||
dependencies = load_dependencies(runtime)
|
||||
|
||||
@@ -27,6 +27,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': codeact_user_response,
|
||||
@@ -104,6 +105,7 @@ def process_instance(instance: Any, metadata: EvalMetadata, reset_logger: bool =
|
||||
logger.info(f'Instruction:\n{instruction}', extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
initialize_runtime(runtime)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
|
||||
@@ -37,6 +37,7 @@ from openhands.runtime.browser.browser_env import (
|
||||
BROWSER_EVAL_GET_GOAL_ACTION,
|
||||
BROWSER_EVAL_GET_REWARDS_ACTION,
|
||||
)
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
SUPPORTED_AGENT_CLS = {'VisualBrowsingAgent'}
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
@@ -159,6 +160,8 @@ def process_instance(
|
||||
logger.info(f'Starting evaluation for instance {env_id}.')
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
task_str, goal_image_urls = initialize_runtime(runtime)
|
||||
initial_user_action = MessageAction(content=task_str, image_urls=goal_image_urls)
|
||||
state: State | None = asyncio.run(
|
||||
|
||||
@@ -36,6 +36,7 @@ from openhands.runtime.browser.browser_env import (
|
||||
BROWSER_EVAL_GET_GOAL_ACTION,
|
||||
BROWSER_EVAL_GET_REWARDS_ACTION,
|
||||
)
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
SUPPORTED_AGENT_CLS = {'BrowsingAgent'}
|
||||
|
||||
@@ -144,6 +145,7 @@ def process_instance(
|
||||
logger.info(f'Starting evaluation for instance {env_id}.')
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
task_str = initialize_runtime(runtime)
|
||||
|
||||
state: State | None = asyncio.run(
|
||||
|
||||
@@ -30,6 +30,7 @@ from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.events.serialization.event import event_to_dict
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
FAKE_RESPONSES = {
|
||||
'CodeActAgent': fake_user_response,
|
||||
@@ -108,6 +109,7 @@ def process_instance(
|
||||
# create sandbox and run the agent
|
||||
# =============================================
|
||||
runtime: Runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
try:
|
||||
test_class.initialize_runtime(runtime)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render, screen, within } from "@testing-library/react";
|
||||
import { screen, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { TrajectoryActions } from "#/components/features/trajectory/trajectory-actions";
|
||||
|
||||
describe("TrajectoryActions", () => {
|
||||
@@ -14,7 +15,7 @@ describe("TrajectoryActions", () => {
|
||||
});
|
||||
|
||||
it("should render correctly", () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
@@ -25,10 +26,11 @@ describe("TrajectoryActions", () => {
|
||||
const actions = screen.getByTestId("feedback-actions");
|
||||
within(actions).getByTestId("positive-feedback");
|
||||
within(actions).getByTestId("negative-feedback");
|
||||
within(actions).getByTestId("export-trajectory");
|
||||
});
|
||||
|
||||
it("should call onPositiveFeedback when positive feedback is clicked", async () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
@@ -43,7 +45,7 @@ describe("TrajectoryActions", () => {
|
||||
});
|
||||
|
||||
it("should call onNegativeFeedback when negative feedback is clicked", async () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
@@ -58,7 +60,7 @@ describe("TrajectoryActions", () => {
|
||||
});
|
||||
|
||||
it("should call onExportTrajectory when negative feedback is clicked", async () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<TrajectoryActions
|
||||
onPositiveFeedback={onPositiveFeedback}
|
||||
onNegativeFeedback={onNegativeFeedback}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Messages } from "#/components/features/chat/messages";
|
||||
import type { Message } from "#/message";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
|
||||
describe("File Operations Messages", () => {
|
||||
it("should show success indicator for successful file read operation", () => {
|
||||
@@ -16,7 +17,7 @@ describe("File Operations Messages", () => {
|
||||
},
|
||||
];
|
||||
|
||||
render(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
renderWithProviders(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
|
||||
const statusIcon = screen.getByTestId("status-icon");
|
||||
expect(statusIcon).toBeInTheDocument();
|
||||
@@ -35,7 +36,7 @@ describe("File Operations Messages", () => {
|
||||
},
|
||||
];
|
||||
|
||||
render(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
renderWithProviders(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
|
||||
const statusIcon = screen.getByTestId("status-icon");
|
||||
expect(statusIcon).toBeInTheDocument();
|
||||
@@ -54,7 +55,7 @@ describe("File Operations Messages", () => {
|
||||
},
|
||||
];
|
||||
|
||||
render(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
renderWithProviders(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
|
||||
const statusIcon = screen.getByTestId("status-icon");
|
||||
expect(statusIcon).toBeInTheDocument();
|
||||
@@ -73,7 +74,7 @@ describe("File Operations Messages", () => {
|
||||
},
|
||||
];
|
||||
|
||||
render(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
renderWithProviders(<Messages messages={messages} isAwaitingUserConfirmation={false} />);
|
||||
|
||||
const statusIcon = screen.getByTestId("status-icon");
|
||||
expect(statusIcon).toBeInTheDocument();
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { RuntimeSizeSelector } from "#/components/shared/modals/settings/runtime-size-selector";
|
||||
|
||||
const renderRuntimeSizeSelector = () =>
|
||||
renderWithProviders(<RuntimeSizeSelector isDisabled={false} />);
|
||||
|
||||
describe("RuntimeSizeSelector", () => {
|
||||
it("should show both runtime size options", () => {
|
||||
renderRuntimeSizeSelector();
|
||||
// The options are in the hidden select element
|
||||
const select = screen.getByRole("combobox", { hidden: true });
|
||||
expect(select).toHaveValue("1");
|
||||
expect(select).toHaveDisplayValue("1x (2 core, 8G)");
|
||||
expect(select.children).toHaveLength(3); // Empty option + 2 size options
|
||||
});
|
||||
|
||||
it("should show the full description text for disabled options", async () => {
|
||||
renderRuntimeSizeSelector();
|
||||
|
||||
// Click the button to open the dropdown
|
||||
const button = screen.getByRole("button", {
|
||||
name: "1x (2 core, 8G) SETTINGS_FORM$RUNTIME_SIZE_LABEL",
|
||||
});
|
||||
button.click();
|
||||
|
||||
// Wait for the dropdown to open and find the description text
|
||||
const description = await screen.findByText(
|
||||
"Runtime sizes over 1 are disabled by default, please contact contact@all-hands.dev to get access to larger runtimes.",
|
||||
);
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(description).toHaveClass("whitespace-normal", "break-words");
|
||||
});
|
||||
});
|
||||
4521
frontend/package-lock.json
generated
4521
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,53 +1,53 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.26.0",
|
||||
"version": "0.28.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/react": "2.6.14",
|
||||
"@heroui/react": "2.7.4",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.1.5",
|
||||
"@react-router/serve": "^7.1.5",
|
||||
"@react-router/node": "^7.2.0",
|
||||
"@react-router/serve": "^7.2.0",
|
||||
"@react-types/shared": "^3.27.0",
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"@reduxjs/toolkit": "^2.6.0",
|
||||
"@stripe/react-stripe-js": "^3.1.1",
|
||||
"@stripe/stripe-js": "^5.5.0",
|
||||
"@tanstack/react-query": "^5.66.7",
|
||||
"@stripe/stripe-js": "^5.7.0",
|
||||
"@tanstack/react-query": "^5.66.11",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.1",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"framer-motion": "^12.4.4",
|
||||
"framer-motion": "^12.4.7",
|
||||
"i18next": "^24.2.2",
|
||||
"i18next-browser-languagedetector": "^8.0.3",
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"isbot": "^5.1.22",
|
||||
"jose": "^5.10.0",
|
||||
"isbot": "^5.1.23",
|
||||
"jose": "^6.0.8",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.219.3",
|
||||
"posthog-js": "^1.225.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.5.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-markdown": "^9.0.3",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.0.1",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^7.1.5",
|
||||
"react-router": "^7.2.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-textarea-autosize": "^8.5.7",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sirv-cli": "^3.0.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"vite": "^6.1.0",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"vite": "^6.2.0",
|
||||
"web-vitals": "^3.5.2",
|
||||
"ws": "^8.18.0"
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev",
|
||||
@@ -81,14 +81,14 @@
|
||||
"devDependencies": {
|
||||
"@mswjs/socket.io-binding": "^0.1.1",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@react-router/dev": "^7.1.5",
|
||||
"@react-router/dev": "^7.2.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.66.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.1",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/node": "^22.13.8",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
@@ -96,13 +96,13 @@
|
||||
"@types/ws": "^8.5.14",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vitest/coverage-v8": "^3.0.6",
|
||||
"@vitest/coverage-v8": "^3.0.7",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-config-prettier": "^10.0.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
@@ -113,10 +113,10 @@
|
||||
"lint-staged": "^15.4.3",
|
||||
"msw": "^2.6.6",
|
||||
"postcss": "^8.5.2",
|
||||
"prettier": "^3.5.1",
|
||||
"stripe": "^17.5.0",
|
||||
"prettier": "^3.5.3",
|
||||
"stripe": "^17.7.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript": "^5.8.2",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.0.2"
|
||||
|
||||
@@ -174,7 +174,7 @@ class OpenHands {
|
||||
code: string,
|
||||
): Promise<GitHubAccessTokenResponse> {
|
||||
const { data } = await openHands.post<GitHubAccessTokenResponse>(
|
||||
"/api/github/callback",
|
||||
"/api/keycloak/callback",
|
||||
{
|
||||
code,
|
||||
},
|
||||
|
||||
BIN
frontend/src/assets/notification.mp3
Normal file
BIN
frontend/src/assets/notification.mp3
Normal file
Binary file not shown.
@@ -57,18 +57,19 @@ export function ChatMessage({
|
||||
onClick={handleCopyToClipboard}
|
||||
mode={isCopy ? "copied" : "copy"}
|
||||
/>
|
||||
<Markdown
|
||||
className="text-sm overflow-auto break-words"
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
a: anchor,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{message}
|
||||
</Markdown>
|
||||
<div className="text-sm overflow-auto break-words">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
a: anchor,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{message}
|
||||
</Markdown>
|
||||
</div>
|
||||
{children}
|
||||
</article>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { Link } from "react-router";
|
||||
import { code } from "../markdown/code";
|
||||
import { ol, ul } from "../markdown/list";
|
||||
import ArrowUp from "#/icons/angle-up-solid.svg?react";
|
||||
@@ -9,6 +10,8 @@ import ArrowDown from "#/icons/angle-down-solid.svg?react";
|
||||
import CheckCircle from "#/icons/check-circle-solid.svg?react";
|
||||
import XCircle from "#/icons/x-circle-solid.svg?react";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { BILLING_SETTINGS } from "#/utils/feature-flags";
|
||||
|
||||
interface ExpandableMessageProps {
|
||||
id?: string;
|
||||
@@ -23,6 +26,7 @@ export function ExpandableMessage({
|
||||
type,
|
||||
success,
|
||||
}: ExpandableMessageProps) {
|
||||
const { data: config } = useConfig();
|
||||
const { t, i18n } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(true);
|
||||
const [headline, setHeadline] = useState("");
|
||||
@@ -38,6 +42,28 @@ export function ExpandableMessage({
|
||||
|
||||
const statusIconClasses = "h-4 w-4 ml-2 inline";
|
||||
|
||||
if (
|
||||
BILLING_SETTINGS() &&
|
||||
config?.APP_MODE === "saas" &&
|
||||
id === "STATUS$ERROR_LLM_OUT_OF_CREDITS"
|
||||
) {
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-start border-l-2 pl-2 my-2 py-2 border-danger">
|
||||
<div className="text-sm w-full">
|
||||
<div className="font-bold text-danger">
|
||||
{t("STATUS$ERROR_LLM_OUT_OF_CREDITS")}
|
||||
</div>
|
||||
<Link
|
||||
className="mt-2 mb-2 w-full h-10 rounded flex items-center justify-center gap-2 bg-primary text-[#0D0F11]"
|
||||
to="/settings/billing"
|
||||
>
|
||||
{t("BILLING$CLICK_TO_TOP_UP")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -97,17 +123,18 @@ export function ExpandableMessage({
|
||||
)}
|
||||
</div>
|
||||
{(!headline || showDetails) && (
|
||||
<Markdown
|
||||
className="text-sm overflow-auto"
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
<div className="text-sm overflow-auto">
|
||||
<Markdown
|
||||
components={{
|
||||
code,
|
||||
ul,
|
||||
ol,
|
||||
}}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
>
|
||||
{details}
|
||||
</Markdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,12 +9,21 @@ import {
|
||||
useWsClient,
|
||||
WsClientProviderStatus,
|
||||
} from "#/context/ws-client-provider";
|
||||
import { useNotification } from "#/hooks/useNotification";
|
||||
import { browserTab } from "#/utils/browser-tab";
|
||||
|
||||
const notificationStates = [
|
||||
AgentState.AWAITING_USER_INPUT,
|
||||
AgentState.FINISHED,
|
||||
AgentState.AWAITING_USER_CONFIRMATION,
|
||||
];
|
||||
|
||||
export function AgentStatusBar() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curStatusMessage } = useSelector((state: RootState) => state.status);
|
||||
const { status } = useWsClient();
|
||||
const { notify } = useNotification();
|
||||
|
||||
const [statusMessage, setStatusMessage] = React.useState<string>("");
|
||||
|
||||
@@ -45,13 +54,40 @@ export function AgentStatusBar() {
|
||||
updateStatusMessage();
|
||||
}, [curStatusMessage.id]);
|
||||
|
||||
// Handle window focus/blur
|
||||
React.useEffect(() => {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
|
||||
const handleFocus = () => {
|
||||
browserTab.stopNotification();
|
||||
};
|
||||
|
||||
window.addEventListener("focus", handleFocus);
|
||||
return () => {
|
||||
window.removeEventListener("focus", handleFocus);
|
||||
browserTab.stopNotification();
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (status === WsClientProviderStatus.DISCONNECTED) {
|
||||
setStatusMessage("Connecting...");
|
||||
} else {
|
||||
setStatusMessage(AGENT_STATUS_MAP[curAgentState].message);
|
||||
if (notificationStates.includes(curAgentState)) {
|
||||
const message = t(AGENT_STATUS_MAP[curAgentState].message);
|
||||
notify(t(AGENT_STATUS_MAP[curAgentState].message), {
|
||||
body: t(`Agent state changed to ${curAgentState}`),
|
||||
playSound: true,
|
||||
});
|
||||
|
||||
// Update browser tab if window exists and is not focused
|
||||
if (typeof document !== "undefined" && !document.hasFocus()) {
|
||||
browserTab.startNotification(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [curAgentState]);
|
||||
}, [curAgentState, notify, t]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
|
||||
@@ -101,9 +101,11 @@ export function ConversationCard({
|
||||
"h-auto w-fit rounded-xl border border-[#525252]",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
{isActive && <span className="w-2 h-2 bg-blue-500 rounded-full" />}
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0 overflow-hidden mr-2">
|
||||
{isActive && (
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0" />
|
||||
)}
|
||||
{titleMode === "edit" && (
|
||||
<input
|
||||
ref={inputRef}
|
||||
@@ -119,7 +121,8 @@ export function ConversationCard({
|
||||
{titleMode === "view" && (
|
||||
<p
|
||||
data-testid="conversation-card-title"
|
||||
className="text-sm leading-6 font-semibold bg-transparent w-full"
|
||||
className="text-sm leading-6 font-semibold bg-transparent truncate overflow-hidden"
|
||||
title={title}
|
||||
>
|
||||
{title}
|
||||
</p>
|
||||
|
||||
@@ -99,7 +99,6 @@ export function GitHubRepositorySelector({
|
||||
<AutocompleteItem
|
||||
data-testid="github-repo-item"
|
||||
key={repo.id}
|
||||
value={repo.id}
|
||||
className="data-[selected=true]:bg-default-100"
|
||||
textValue={repo.full_name}
|
||||
>
|
||||
@@ -114,7 +113,6 @@ export function GitHubRepositorySelector({
|
||||
<AutocompleteItem
|
||||
data-testid="github-repo-item"
|
||||
key={repo.id}
|
||||
value={repo.id}
|
||||
className="data-[selected=true]:bg-default-100"
|
||||
textValue={repo.full_name}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Autocomplete, AutocompleteItem } from "@heroui/react";
|
||||
import { ReactNode } from "react";
|
||||
import { OptionalTag } from "./optional-tag";
|
||||
|
||||
interface SettingsDropdownInputProps {
|
||||
testId: string;
|
||||
label: string;
|
||||
label: ReactNode;
|
||||
name: string;
|
||||
items: { key: React.Key; label: string }[];
|
||||
showOptionalTag?: boolean;
|
||||
@@ -29,7 +30,7 @@ export function SettingsDropdownInput({
|
||||
{showOptionalTag && <OptionalTag />}
|
||||
</div>
|
||||
<Autocomplete
|
||||
aria-label={label}
|
||||
aria-label={typeof label === "string" ? label : name}
|
||||
data-testid={testId}
|
||||
name={name}
|
||||
defaultItems={items}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ThumbsUpIcon from "#/icons/thumbs-up.svg?react";
|
||||
import ThumbDownIcon from "#/icons/thumbs-down.svg?react";
|
||||
import ExportIcon from "#/icons/export.svg?react";
|
||||
@@ -14,22 +15,27 @@ export function TrajectoryActions({
|
||||
onNegativeFeedback,
|
||||
onExportTrajectory,
|
||||
}: TrajectoryActionsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div data-testid="feedback-actions" className="flex gap-1">
|
||||
<TrajectoryActionButton
|
||||
testId="positive-feedback"
|
||||
onClick={onPositiveFeedback}
|
||||
icon={<ThumbsUpIcon width={15} height={15} />}
|
||||
tooltip={t("BUTTON$MARK_HELPFUL")}
|
||||
/>
|
||||
<TrajectoryActionButton
|
||||
testId="negative-feedback"
|
||||
onClick={onNegativeFeedback}
|
||||
icon={<ThumbDownIcon width={15} height={15} />}
|
||||
tooltip={t("BUTTON$MARK_NOT_HELPFUL")}
|
||||
/>
|
||||
<TrajectoryActionButton
|
||||
testId="export-trajectory"
|
||||
onClick={onExportTrajectory}
|
||||
icon={<ExportIcon width={15} height={15} />}
|
||||
tooltip={t("BUTTON$EXPORT_CONVERSATION")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Tooltip } from "@heroui/react";
|
||||
|
||||
interface TrajectoryActionButtonProps {
|
||||
testId?: string;
|
||||
onClick: () => void;
|
||||
icon: React.ReactNode;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export function TrajectoryActionButton({
|
||||
testId,
|
||||
onClick,
|
||||
icon,
|
||||
tooltip,
|
||||
}: TrajectoryActionButtonProps) {
|
||||
return (
|
||||
const button = (
|
||||
<button
|
||||
type="button"
|
||||
data-testid={testId}
|
||||
@@ -19,4 +23,14 @@ export function TrajectoryActionButton({
|
||||
{icon}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (tooltip) {
|
||||
return (
|
||||
<Tooltip content={tooltip} closeDelay={100}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Autocomplete, AutocompleteItem } from "@heroui/react";
|
||||
|
||||
interface FormFieldsetProps {
|
||||
id: string;
|
||||
label: string;
|
||||
items: { key: string; value: string }[];
|
||||
defaultSelectedKey?: string;
|
||||
isClearable?: boolean;
|
||||
}
|
||||
|
||||
export function FormFieldset({
|
||||
id,
|
||||
label,
|
||||
items,
|
||||
defaultSelectedKey,
|
||||
isClearable,
|
||||
}: FormFieldsetProps) {
|
||||
return (
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<label htmlFor={id} className="font-[500] text-[#A3A3A3] text-xs">
|
||||
{label}
|
||||
</label>
|
||||
<Autocomplete
|
||||
id={id}
|
||||
name={id}
|
||||
aria-label={label}
|
||||
defaultSelectedKey={defaultSelectedKey}
|
||||
isClearable={isClearable}
|
||||
inputProps={{
|
||||
classNames: {
|
||||
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<AutocompleteItem key={item.key} value={item.key}>
|
||||
{item.value}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</Autocomplete>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Autocomplete, AutocompleteItem } from "@heroui/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface AgentInputProps {
|
||||
isDisabled: boolean;
|
||||
defaultValue: string;
|
||||
agents: string[];
|
||||
}
|
||||
|
||||
export function AgentInput({
|
||||
isDisabled,
|
||||
defaultValue,
|
||||
agents,
|
||||
}: AgentInputProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<fieldset data-testid="agent-selector" className="flex flex-col gap-2">
|
||||
<label htmlFor="agent" className="font-[500] text-[#A3A3A3] text-xs">
|
||||
{t(I18nKey.SETTINGS_FORM$AGENT_LABEL)}
|
||||
</label>
|
||||
<Autocomplete
|
||||
isDisabled={isDisabled}
|
||||
isRequired
|
||||
id="agent"
|
||||
aria-label="Agent"
|
||||
data-testid="agent-input"
|
||||
name="agent"
|
||||
defaultSelectedKey={defaultValue}
|
||||
isClearable={false}
|
||||
inputProps={{
|
||||
classNames: {
|
||||
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{agents.map((agent) => (
|
||||
<AutocompleteItem key={agent} value={agent}>
|
||||
{agent}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</Autocomplete>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Autocomplete, AutocompleteItem } from "@heroui/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface SecurityAnalyzerInputProps {
|
||||
isDisabled: boolean;
|
||||
defaultValue: string;
|
||||
securityAnalyzers: string[];
|
||||
}
|
||||
|
||||
export function SecurityAnalyzerInput({
|
||||
isDisabled,
|
||||
defaultValue,
|
||||
securityAnalyzers,
|
||||
}: SecurityAnalyzerInputProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="security-analyzer"
|
||||
className="font-[500] text-[#A3A3A3] text-xs"
|
||||
>
|
||||
{t(I18nKey.SETTINGS_FORM$SECURITY_ANALYZER_LABEL)}
|
||||
</label>
|
||||
<Autocomplete
|
||||
isDisabled={isDisabled}
|
||||
id="security-analyzer"
|
||||
name="security-analyzer"
|
||||
aria-label="Security Analyzer"
|
||||
defaultSelectedKey={defaultValue}
|
||||
inputProps={{
|
||||
classNames: {
|
||||
inputWrapper: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{securityAnalyzers.map((analyzer) => (
|
||||
<AutocompleteItem key={analyzer} value={analyzer}>
|
||||
{analyzer}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
</Autocomplete>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
@@ -100,7 +100,6 @@ export function ModelSelector({
|
||||
<AutocompleteItem
|
||||
data-testid={`provider-item-${provider}`}
|
||||
key={provider}
|
||||
value={provider}
|
||||
>
|
||||
{mapProvider(provider)}
|
||||
</AutocompleteItem>
|
||||
@@ -110,7 +109,7 @@ export function ModelSelector({
|
||||
{Object.keys(models)
|
||||
.filter((provider) => !VERIFIED_PROVIDERS.includes(provider))
|
||||
.map((provider) => (
|
||||
<AutocompleteItem key={provider} value={provider}>
|
||||
<AutocompleteItem key={provider}>
|
||||
{mapProvider(provider)}
|
||||
</AutocompleteItem>
|
||||
))}
|
||||
@@ -148,9 +147,7 @@ export function ModelSelector({
|
||||
{models[selectedProvider || ""]?.models
|
||||
.filter((model) => VERIFIED_MODELS.includes(model))
|
||||
.map((model) => (
|
||||
<AutocompleteItem key={model} value={model}>
|
||||
{model}
|
||||
</AutocompleteItem>
|
||||
<AutocompleteItem key={model}>{model}</AutocompleteItem>
|
||||
))}
|
||||
</AutocompleteSection>
|
||||
<AutocompleteSection title="Others">
|
||||
@@ -160,7 +157,6 @@ export function ModelSelector({
|
||||
<AutocompleteItem
|
||||
data-testid={`model-item-${model}`}
|
||||
key={model}
|
||||
value={model}
|
||||
>
|
||||
{model}
|
||||
</AutocompleteItem>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Select, SelectItem } from "@heroui/react";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface RuntimeSizeSelectorProps {
|
||||
isDisabled: boolean;
|
||||
defaultValue?: number;
|
||||
}
|
||||
|
||||
export function RuntimeSizeSelector({
|
||||
isDisabled,
|
||||
defaultValue,
|
||||
}: RuntimeSizeSelectorProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<fieldset className="flex flex-col gap-2">
|
||||
<label
|
||||
htmlFor="runtime-size"
|
||||
className="font-[500] text-[#A3A3A3] text-xs"
|
||||
>
|
||||
{t(I18nKey.SETTINGS_FORM$RUNTIME_SIZE_LABEL)}
|
||||
</label>
|
||||
<Select
|
||||
data-testid="runtime-size"
|
||||
id="runtime-size"
|
||||
name="runtime-size"
|
||||
defaultSelectedKeys={[String(defaultValue || 1)]}
|
||||
selectedKeys={[String(defaultValue || 1)]}
|
||||
isDisabled={isDisabled}
|
||||
selectionMode="single"
|
||||
disallowEmptySelection
|
||||
aria-label={t(I18nKey.SETTINGS_FORM$RUNTIME_SIZE_LABEL)}
|
||||
classNames={{
|
||||
trigger: "bg-[#27272A] rounded-md text-sm px-3 py-[10px]",
|
||||
}}
|
||||
>
|
||||
<SelectItem key="1" value={1}>
|
||||
1x (2 core, 8G)
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
key="2"
|
||||
value={2}
|
||||
isDisabled
|
||||
classNames={{
|
||||
description:
|
||||
"whitespace-normal break-words min-w-[300px] max-w-[300px]",
|
||||
base: "min-w-[300px] max-w-[300px]",
|
||||
}}
|
||||
description="Runtime sizes over 1 are disabled by default, please contact contact@all-hands.dev to get access to larger runtimes."
|
||||
>
|
||||
2x (4 core, 16G)
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
@@ -43,15 +43,18 @@ export function SettingsForm({ settings, models, onClose }: SettingsFormProps) {
|
||||
const handleFormSubmission = async (formData: FormData) => {
|
||||
const newSettings = extractSettings(formData);
|
||||
|
||||
await saveUserSettings(newSettings);
|
||||
onClose();
|
||||
resetOngoingSession();
|
||||
await saveUserSettings(newSettings, {
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
resetOngoingSession();
|
||||
|
||||
posthog.capture("settings_saved", {
|
||||
LLM_MODEL: newSettings.LLM_MODEL,
|
||||
LLM_API_KEY: newSettings.LLM_API_KEY ? "SET" : "UNSET",
|
||||
REMOTE_RUNTIME_RESOURCE_FACTOR:
|
||||
newSettings.REMOTE_RUNTIME_RESOURCE_FACTOR,
|
||||
posthog.capture("settings_saved", {
|
||||
LLM_MODEL: newSettings.LLM_MODEL,
|
||||
LLM_API_KEY: newSettings.LLM_API_KEY ? "SET" : "UNSET",
|
||||
REMOTE_RUNTIME_RESOURCE_FACTOR:
|
||||
newSettings.REMOTE_RUNTIME_RESOURCE_FACTOR,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const saveSettingsMutationFn = async (settings: Partial<PostSettings>) => {
|
||||
github_token: settings.github_token,
|
||||
unset_github_token: settings.unset_github_token,
|
||||
enable_default_condenser: settings.ENABLE_DEFAULT_CONDENSER,
|
||||
enable_sound_notifications: settings.ENABLE_SOUND_NOTIFICATIONS,
|
||||
user_consents_to_analytics: settings.user_consents_to_analytics,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from "react";
|
||||
import posthog from "posthog-js";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useAuth } from "#/context/auth-context";
|
||||
import { useConfig } from "#/hooks/query/use-config";
|
||||
import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||
|
||||
const getSettingsQueryFn = async () => {
|
||||
@@ -20,18 +19,17 @@ const getSettingsQueryFn = async () => {
|
||||
REMOTE_RUNTIME_RESOURCE_FACTOR: apiSettings.remote_runtime_resource_factor,
|
||||
GITHUB_TOKEN_IS_SET: apiSettings.github_token_is_set,
|
||||
ENABLE_DEFAULT_CONDENSER: apiSettings.enable_default_condenser,
|
||||
ENABLE_SOUND_NOTIFICATIONS: apiSettings.enable_sound_notifications,
|
||||
USER_CONSENTS_TO_ANALYTICS: apiSettings.user_consents_to_analytics,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSettings = () => {
|
||||
const { setGitHubTokenIsSet, githubTokenIsSet } = useAuth();
|
||||
const { data: config } = useConfig();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["settings", githubTokenIsSet],
|
||||
queryFn: getSettingsQueryFn,
|
||||
enabled: config?.APP_MODE !== "saas" || githubTokenIsSet,
|
||||
// Only retry if the error is not a 404 because we
|
||||
// would want to show the modal immediately if the
|
||||
// settings are not found
|
||||
|
||||
56
frontend/src/hooks/useNotification.ts
Normal file
56
frontend/src/hooks/useNotification.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import notificationSound from "../assets/notification.mp3";
|
||||
import { useCurrentSettings } from "../context/settings-context";
|
||||
|
||||
export const useNotification = () => {
|
||||
const { settings } = useCurrentSettings();
|
||||
const audioRef = useRef<HTMLAudioElement | undefined>(undefined);
|
||||
|
||||
// Initialize audio only in browser environment
|
||||
if (typeof window !== "undefined" && !audioRef.current) {
|
||||
audioRef.current = new Audio(notificationSound);
|
||||
audioRef.current.volume = 0.5;
|
||||
}
|
||||
|
||||
const notify = useCallback(
|
||||
async (
|
||||
title: string,
|
||||
options?: NotificationOptions & { playSound?: boolean },
|
||||
): Promise<Notification | undefined> => {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
|
||||
// Only play sound if:
|
||||
// 1. Explicitly requested via playSound option
|
||||
// 2. Sound notifications are enabled in settings
|
||||
// 3. Audio is available
|
||||
// 4. Not a settings-related notification
|
||||
if (
|
||||
options?.playSound === true && // Must be explicitly true
|
||||
settings?.ENABLE_SOUND_NOTIFICATIONS &&
|
||||
audioRef.current &&
|
||||
!title.includes("BUTTON$") // Don't play for button/settings actions
|
||||
) {
|
||||
// Reset and play sound
|
||||
audioRef.current.currentTime = 0;
|
||||
audioRef.current.play().catch(() => {
|
||||
// Ignore autoplay errors
|
||||
});
|
||||
}
|
||||
|
||||
if (Notification.permission === "default") {
|
||||
await Notification.requestPermission();
|
||||
}
|
||||
|
||||
if (Notification.permission === "granted") {
|
||||
// Remove playSound from options before passing to Notification
|
||||
const { playSound, ...notificationOptions } = options || {};
|
||||
return new Notification(title, notificationOptions);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
[settings?.ENABLE_SOUND_NOTIFICATIONS],
|
||||
);
|
||||
|
||||
return { notify };
|
||||
};
|
||||
315
frontend/src/i18n/declaration.ts
Normal file
315
frontend/src/i18n/declaration.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
// this file generate by script, don't modify it manually!!!
|
||||
export enum I18nKey {
|
||||
APP$TITLE = "APP$TITLE",
|
||||
BROWSER$TITLE = "BROWSER$TITLE",
|
||||
BROWSER$EMPTY_MESSAGE = "BROWSER$EMPTY_MESSAGE",
|
||||
SETTINGS$TITLE = "SETTINGS$TITLE",
|
||||
PROJECT$START_NEW = "PROJECT$START_NEW",
|
||||
PROJECT$NEW = "PROJECT$NEW",
|
||||
ACCOUNT_SETTINGS$TITLE = "ACCOUNT_SETTINGS$TITLE",
|
||||
WORKSPACE$TITLE = "WORKSPACE$TITLE",
|
||||
WORKSPACE$TERMINAL_TAB_LABEL = "WORKSPACE$TERMINAL_TAB_LABEL",
|
||||
TERMINAL$WAITING_FOR_CLIENT = "TERMINAL$WAITING_FOR_CLIENT",
|
||||
CODE_EDITOR$FILE_SAVED_SUCCESSFULLY = "CODE_EDITOR$FILE_SAVED_SUCCESSFULLY",
|
||||
CODE_EDITOR$SAVING_LABEL = "CODE_EDITOR$SAVING_LABEL",
|
||||
CODE_EDITOR$SAVE_LABEL = "CODE_EDITOR$SAVE_LABEL",
|
||||
CODE_EDITOR$OPTIONS = "CODE_EDITOR$OPTIONS",
|
||||
CODE_EDITOR$FILE_SAVE_ERROR = "CODE_EDITOR$FILE_SAVE_ERROR",
|
||||
CODE_EDITOR$EMPTY_MESSAGE = "CODE_EDITOR$EMPTY_MESSAGE",
|
||||
FILE_SERVICE$SELECT_FILE_ERROR = "FILE_SERVICE$SELECT_FILE_ERROR",
|
||||
FILE_SERVICE$UPLOAD_FILES_ERROR = "FILE_SERVICE$UPLOAD_FILES_ERROR",
|
||||
FILE_SERVICE$LIST_FILES_ERROR = "FILE_SERVICE$LIST_FILES_ERROR",
|
||||
FILE_SERVICE$SAVE_FILE_ERROR = "FILE_SERVICE$SAVE_FILE_ERROR",
|
||||
SUGGESTIONS$INCREASE_TEST_COVERAGE = "SUGGESTIONS$INCREASE_TEST_COVERAGE",
|
||||
SUGGESTIONS$AUTO_MERGE_PRS = "SUGGESTIONS$AUTO_MERGE_PRS",
|
||||
SUGGESTIONS$FIX_README = "SUGGESTIONS$FIX_README",
|
||||
SUGGESTIONS$CLEAN_DEPENDENCIES = "SUGGESTIONS$CLEAN_DEPENDENCIES",
|
||||
FILE_SERVICE$INVALID_FILE_PATH = "FILE_SERVICE$INVALID_FILE_PATH",
|
||||
WORKSPACE$PLANNER_TAB_LABEL = "WORKSPACE$PLANNER_TAB_LABEL",
|
||||
WORKSPACE$JUPYTER_TAB_LABEL = "WORKSPACE$JUPYTER_TAB_LABEL",
|
||||
WORKSPACE$CODE_EDITOR_TAB_LABEL = "WORKSPACE$CODE_EDITOR_TAB_LABEL",
|
||||
WORKSPACE$BROWSER_TAB_LABEL = "WORKSPACE$BROWSER_TAB_LABEL",
|
||||
VSCODE$OPEN = "VSCODE$OPEN",
|
||||
INCREASE_TEST_COVERAGE = "INCREASE_TEST_COVERAGE",
|
||||
AUTO_MERGE_PRS = "AUTO_MERGE_PRS",
|
||||
FIX_README = "FIX_README",
|
||||
CLEAN_DEPENDENCIES = "CLEAN_DEPENDENCIES",
|
||||
CONFIGURATION$OPENHANDS_WORKSPACE_DIRECTORY_INPUT_LABEL = "CONFIGURATION$OPENHANDS_WORKSPACE_DIRECTORY_INPUT_LABEL",
|
||||
LLM$PROVIDER = "LLM$PROVIDER",
|
||||
LLM$SELECT_PROVIDER_PLACEHOLDER = "LLM$SELECT_PROVIDER_PLACEHOLDER",
|
||||
API$KEY = "API$KEY",
|
||||
API$DONT_KNOW_KEY = "API$DONT_KNOW_KEY",
|
||||
BUTTON$SAVE = "BUTTON$SAVE",
|
||||
BUTTON$CLOSE = "BUTTON$CLOSE",
|
||||
BUTTON$RESET_TO_DEFAULTS = "BUTTON$RESET_TO_DEFAULTS",
|
||||
MODAL$CONFIRM_RESET_TITLE = "MODAL$CONFIRM_RESET_TITLE",
|
||||
MODAL$CONFIRM_RESET_MESSAGE = "MODAL$CONFIRM_RESET_MESSAGE",
|
||||
MODAL$END_SESSION_TITLE = "MODAL$END_SESSION_TITLE",
|
||||
MODAL$END_SESSION_MESSAGE = "MODAL$END_SESSION_MESSAGE",
|
||||
BUTTON$END_SESSION = "BUTTON$END_SESSION",
|
||||
BUTTON$CANCEL = "BUTTON$CANCEL",
|
||||
LANGUAGE$LABEL = "LANGUAGE$LABEL",
|
||||
GITHUB$TOKEN_LABEL = "GITHUB$TOKEN_LABEL",
|
||||
GITHUB$TOKEN_OPTIONAL = "GITHUB$TOKEN_OPTIONAL",
|
||||
GITHUB$GET_TOKEN = "GITHUB$GET_TOKEN",
|
||||
COMMON$HERE = "COMMON$HERE",
|
||||
ANALYTICS$ENABLE = "ANALYTICS$ENABLE",
|
||||
GITHUB$TOKEN_INVALID = "GITHUB$TOKEN_INVALID",
|
||||
BUTTON$DISCONNECT = "BUTTON$DISCONNECT",
|
||||
GITHUB$CONFIGURE_REPOS = "GITHUB$CONFIGURE_REPOS",
|
||||
COMMON$CLICK_FOR_INSTRUCTIONS = "COMMON$CLICK_FOR_INSTRUCTIONS",
|
||||
LLM$SELECT_MODEL_PLACEHOLDER = "LLM$SELECT_MODEL_PLACEHOLDER",
|
||||
LLM$MODEL = "LLM$MODEL",
|
||||
CONFIGURATION$OPENHANDS_WORKSPACE_DIRECTORY_INPUT_PLACEHOLDER = "CONFIGURATION$OPENHANDS_WORKSPACE_DIRECTORY_INPUT_PLACEHOLDER",
|
||||
CONFIGURATION$MODAL_TITLE = "CONFIGURATION$MODAL_TITLE",
|
||||
CONFIGURATION$MODEL_SELECT_LABEL = "CONFIGURATION$MODEL_SELECT_LABEL",
|
||||
CONFIGURATION$MODEL_SELECT_PLACEHOLDER = "CONFIGURATION$MODEL_SELECT_PLACEHOLDER",
|
||||
CONFIGURATION$AGENT_SELECT_LABEL = "CONFIGURATION$AGENT_SELECT_LABEL",
|
||||
CONFIGURATION$AGENT_SELECT_PLACEHOLDER = "CONFIGURATION$AGENT_SELECT_PLACEHOLDER",
|
||||
CONFIGURATION$LANGUAGE_SELECT_LABEL = "CONFIGURATION$LANGUAGE_SELECT_LABEL",
|
||||
CONFIGURATION$LANGUAGE_SELECT_PLACEHOLDER = "CONFIGURATION$LANGUAGE_SELECT_PLACEHOLDER",
|
||||
CONFIGURATION$SECURITY_SELECT_LABEL = "CONFIGURATION$SECURITY_SELECT_LABEL",
|
||||
CONFIGURATION$SECURITY_SELECT_PLACEHOLDER = "CONFIGURATION$SECURITY_SELECT_PLACEHOLDER",
|
||||
CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL = "CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL",
|
||||
CONFIGURATION$MODAL_SAVE_BUTTON_LABEL = "CONFIGURATION$MODAL_SAVE_BUTTON_LABEL",
|
||||
CONFIGURATION$MODAL_RESET_BUTTON_LABEL = "CONFIGURATION$MODAL_RESET_BUTTON_LABEL",
|
||||
STATUS$CONNECTED_TO_SERVER = "STATUS$CONNECTED_TO_SERVER",
|
||||
PROJECT$NEW_PROJECT = "PROJECT$NEW_PROJECT",
|
||||
BROWSER$SCREENSHOT = "BROWSER$SCREENSHOT",
|
||||
TIME$MINUTES_AGO = "TIME$MINUTES_AGO",
|
||||
TIME$HOURS_AGO = "TIME$HOURS_AGO",
|
||||
TIME$DAYS_AGO = "TIME$DAYS_AGO",
|
||||
SETTINGS_FORM$RUNTIME_SIZE_LABEL = "SETTINGS_FORM$RUNTIME_SIZE_LABEL",
|
||||
CONFIGURATION$SETTINGS_NEED_UPDATE_MESSAGE = "CONFIGURATION$SETTINGS_NEED_UPDATE_MESSAGE",
|
||||
CONFIGURATION$AGENT_LOADING = "CONFIGURATION$AGENT_LOADING",
|
||||
CONFIGURATION$AGENT_RUNNING = "CONFIGURATION$AGENT_RUNNING",
|
||||
CONFIGURATION$ERROR_FETCH_MODELS = "CONFIGURATION$ERROR_FETCH_MODELS",
|
||||
CONFIGURATION$SETTINGS_NOT_FOUND = "CONFIGURATION$SETTINGS_NOT_FOUND",
|
||||
CONNECT_TO_GITHUB_BY_TOKEN_MODAL$TERMS_OF_SERVICE = "CONNECT_TO_GITHUB_BY_TOKEN_MODAL$TERMS_OF_SERVICE",
|
||||
SESSION$SERVER_CONNECTED_MESSAGE = "SESSION$SERVER_CONNECTED_MESSAGE",
|
||||
SESSION$SESSION_HANDLING_ERROR_MESSAGE = "SESSION$SESSION_HANDLING_ERROR_MESSAGE",
|
||||
SESSION$SESSION_CONNECTION_ERROR_MESSAGE = "SESSION$SESSION_CONNECTION_ERROR_MESSAGE",
|
||||
SESSION$SOCKET_NOT_INITIALIZED_ERROR_MESSAGE = "SESSION$SOCKET_NOT_INITIALIZED_ERROR_MESSAGE",
|
||||
EXPLORER$UPLOAD_ERROR_MESSAGE = "EXPLORER$UPLOAD_ERROR_MESSAGE",
|
||||
EXPLORER$LABEL_DROP_FILES = "EXPLORER$LABEL_DROP_FILES",
|
||||
EXPLORER$LABEL_WORKSPACE = "EXPLORER$LABEL_WORKSPACE",
|
||||
EXPLORER$EMPTY_WORKSPACE_MESSAGE = "EXPLORER$EMPTY_WORKSPACE_MESSAGE",
|
||||
EXPLORER$LOADING_WORKSPACE_MESSAGE = "EXPLORER$LOADING_WORKSPACE_MESSAGE",
|
||||
EXPLORER$REFRESH_ERROR_MESSAGE = "EXPLORER$REFRESH_ERROR_MESSAGE",
|
||||
EXPLORER$UPLOAD_SUCCESS_MESSAGE = "EXPLORER$UPLOAD_SUCCESS_MESSAGE",
|
||||
EXPLORER$NO_FILES_UPLOADED_MESSAGE = "EXPLORER$NO_FILES_UPLOADED_MESSAGE",
|
||||
EXPLORER$UPLOAD_PARTIAL_SUCCESS_MESSAGE = "EXPLORER$UPLOAD_PARTIAL_SUCCESS_MESSAGE",
|
||||
EXPLORER$UPLOAD_UNEXPECTED_RESPONSE_MESSAGE = "EXPLORER$UPLOAD_UNEXPECTED_RESPONSE_MESSAGE",
|
||||
EXPLORER$VSCODE_SWITCHING_MESSAGE = "EXPLORER$VSCODE_SWITCHING_MESSAGE",
|
||||
EXPLORER$VSCODE_SWITCHING_ERROR_MESSAGE = "EXPLORER$VSCODE_SWITCHING_ERROR_MESSAGE",
|
||||
LOAD_SESSION$MODAL_TITLE = "LOAD_SESSION$MODAL_TITLE",
|
||||
LOAD_SESSION$MODAL_CONTENT = "LOAD_SESSION$MODAL_CONTENT",
|
||||
LOAD_SESSION$RESUME_SESSION_MODAL_ACTION_LABEL = "LOAD_SESSION$RESUME_SESSION_MODAL_ACTION_LABEL",
|
||||
LOAD_SESSION$START_NEW_SESSION_MODAL_ACTION_LABEL = "LOAD_SESSION$START_NEW_SESSION_MODAL_ACTION_LABEL",
|
||||
FEEDBACK$MODAL_TITLE = "FEEDBACK$MODAL_TITLE",
|
||||
FEEDBACK$MODAL_CONTENT = "FEEDBACK$MODAL_CONTENT",
|
||||
FEEDBACK$EMAIL_LABEL = "FEEDBACK$EMAIL_LABEL",
|
||||
FEEDBACK$CONTRIBUTE_LABEL = "FEEDBACK$CONTRIBUTE_LABEL",
|
||||
FEEDBACK$SHARE_LABEL = "FEEDBACK$SHARE_LABEL",
|
||||
FEEDBACK$CANCEL_LABEL = "FEEDBACK$CANCEL_LABEL",
|
||||
FEEDBACK$EMAIL_PLACEHOLDER = "FEEDBACK$EMAIL_PLACEHOLDER",
|
||||
FEEDBACK$PASSWORD_COPIED_MESSAGE = "FEEDBACK$PASSWORD_COPIED_MESSAGE",
|
||||
FEEDBACK$GO_TO_FEEDBACK = "FEEDBACK$GO_TO_FEEDBACK",
|
||||
FEEDBACK$PASSWORD = "FEEDBACK$PASSWORD",
|
||||
FEEDBACK$INVALID_EMAIL_FORMAT = "FEEDBACK$INVALID_EMAIL_FORMAT",
|
||||
FEEDBACK$FAILED_TO_SHARE = "FEEDBACK$FAILED_TO_SHARE",
|
||||
FEEDBACK$COPY_LABEL = "FEEDBACK$COPY_LABEL",
|
||||
FEEDBACK$SHARING_SETTINGS_LABEL = "FEEDBACK$SHARING_SETTINGS_LABEL",
|
||||
SECURITY$UNKNOWN_ANALYZER_LABEL = "SECURITY$UNKNOWN_ANALYZER_LABEL",
|
||||
INVARIANT$UPDATE_POLICY_LABEL = "INVARIANT$UPDATE_POLICY_LABEL",
|
||||
INVARIANT$UPDATE_SETTINGS_LABEL = "INVARIANT$UPDATE_SETTINGS_LABEL",
|
||||
INVARIANT$SETTINGS_LABEL = "INVARIANT$SETTINGS_LABEL",
|
||||
INVARIANT$ASK_CONFIRMATION_RISK_SEVERITY_LABEL = "INVARIANT$ASK_CONFIRMATION_RISK_SEVERITY_LABEL",
|
||||
INVARIANT$DONT_ASK_FOR_CONFIRMATION_LABEL = "INVARIANT$DONT_ASK_FOR_CONFIRMATION_LABEL",
|
||||
INVARIANT$INVARIANT_ANALYZER_LABEL = "INVARIANT$INVARIANT_ANALYZER_LABEL",
|
||||
INVARIANT$INVARIANT_ANALYZER_MESSAGE = "INVARIANT$INVARIANT_ANALYZER_MESSAGE",
|
||||
INVARIANT$CLICK_TO_LEARN_MORE_LABEL = "INVARIANT$CLICK_TO_LEARN_MORE_LABEL",
|
||||
INVARIANT$POLICY_LABEL = "INVARIANT$POLICY_LABEL",
|
||||
INVARIANT$LOG_LABEL = "INVARIANT$LOG_LABEL",
|
||||
INVARIANT$EXPORT_TRACE_LABEL = "INVARIANT$EXPORT_TRACE_LABEL",
|
||||
INVARIANT$TRACE_EXPORTED_MESSAGE = "INVARIANT$TRACE_EXPORTED_MESSAGE",
|
||||
INVARIANT$POLICY_UPDATED_MESSAGE = "INVARIANT$POLICY_UPDATED_MESSAGE",
|
||||
INVARIANT$SETTINGS_UPDATED_MESSAGE = "INVARIANT$SETTINGS_UPDATED_MESSAGE",
|
||||
CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE = "CHAT_INTERFACE$INITIALIZING_AGENT_LOADING_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_INIT_MESSAGE = "CHAT_INTERFACE$AGENT_INIT_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_RUNNING_MESSAGE = "CHAT_INTERFACE$AGENT_RUNNING_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE = "CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE = "CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_PAUSED_MESSAGE = "CHAT_INTERFACE$AGENT_PAUSED_MESSAGE",
|
||||
LANDING$TITLE = "LANDING$TITLE",
|
||||
LANDING$SUBTITLE = "LANDING$SUBTITLE",
|
||||
LANDING$START_HELP = "LANDING$START_HELP",
|
||||
LANDING$START_HELP_LINK = "LANDING$START_HELP_LINK",
|
||||
SUGGESTIONS$HELLO_WORLD = "SUGGESTIONS$HELLO_WORLD",
|
||||
SUGGESTIONS$TODO_APP = "SUGGESTIONS$TODO_APP",
|
||||
SUGGESTIONS$HACKER_NEWS = "SUGGESTIONS$HACKER_NEWS",
|
||||
LANDING$CHANGE_PROMPT = "LANDING$CHANGE_PROMPT",
|
||||
GITHUB$CONNECT = "GITHUB$CONNECT",
|
||||
GITHUB$NO_RESULTS = "GITHUB$NO_RESULTS",
|
||||
GITHUB$ADD_MORE_REPOS = "GITHUB$ADD_MORE_REPOS",
|
||||
GITHUB$YOUR_REPOS = "GITHUB$YOUR_REPOS",
|
||||
GITHUB$PUBLIC_REPOS = "GITHUB$PUBLIC_REPOS",
|
||||
DOWNLOAD$PREPARING = "DOWNLOAD$PREPARING",
|
||||
DOWNLOAD$DOWNLOADING = "DOWNLOAD$DOWNLOADING",
|
||||
DOWNLOAD$FOUND_FILES = "DOWNLOAD$FOUND_FILES",
|
||||
DOWNLOAD$SCANNING = "DOWNLOAD$SCANNING",
|
||||
DOWNLOAD$FILES_PROGRESS = "DOWNLOAD$FILES_PROGRESS",
|
||||
DOWNLOAD$CANCEL = "DOWNLOAD$CANCEL",
|
||||
ACTION$CONFIRM = "ACTION$CONFIRM",
|
||||
ACTION$REJECT = "ACTION$REJECT",
|
||||
BADGE$BETA = "BADGE$BETA",
|
||||
AGENT$RESUME_TASK = "AGENT$RESUME_TASK",
|
||||
AGENT$PAUSE_TASK = "AGENT$PAUSE_TASK",
|
||||
TOS$ACCEPT = "TOS$ACCEPT",
|
||||
TOS$TERMS = "TOS$TERMS",
|
||||
USER$ACCOUNT_SETTINGS = "USER$ACCOUNT_SETTINGS",
|
||||
JUPYTER$OUTPUT_LABEL = "JUPYTER$OUTPUT_LABEL",
|
||||
BUTTON$STOP = "BUTTON$STOP",
|
||||
LANDING$ATTACH_IMAGES = "LANDING$ATTACH_IMAGES",
|
||||
LANDING$OPEN_REPO = "LANDING$OPEN_REPO",
|
||||
LANDING$IMPORT_PROJECT = "LANDING$IMPORT_PROJECT",
|
||||
LANDING$UPLOAD_ZIP = "LANDING$UPLOAD_ZIP",
|
||||
LANDING$RECENT_CONVERSATION = "LANDING$RECENT_CONVERSATION",
|
||||
LANDING$OR = "LANDING$OR",
|
||||
SUGGESTIONS$TEST_COVERAGE = "SUGGESTIONS$TEST_COVERAGE",
|
||||
SUGGESTIONS$AUTO_MERGE = "SUGGESTIONS$AUTO_MERGE",
|
||||
CHAT_INTERFACE$AGENT_STOPPED_MESSAGE = "CHAT_INTERFACE$AGENT_STOPPED_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_FINISHED_MESSAGE = "CHAT_INTERFACE$AGENT_FINISHED_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_REJECTED_MESSAGE = "CHAT_INTERFACE$AGENT_REJECTED_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_ERROR_MESSAGE = "CHAT_INTERFACE$AGENT_ERROR_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE = "CHAT_INTERFACE$AGENT_AWAITING_USER_CONFIRMATION_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE = "CHAT_INTERFACE$AGENT_ACTION_USER_CONFIRMED_MESSAGE",
|
||||
CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE = "CHAT_INTERFACE$AGENT_ACTION_USER_REJECTED_MESSAGE",
|
||||
CHAT_INTERFACE$INPUT_PLACEHOLDER = "CHAT_INTERFACE$INPUT_PLACEHOLDER",
|
||||
CHAT_INTERFACE$INPUT_CONTINUE_MESSAGE = "CHAT_INTERFACE$INPUT_CONTINUE_MESSAGE",
|
||||
CHAT_INTERFACE$USER_ASK_CONFIRMATION = "CHAT_INTERFACE$USER_ASK_CONFIRMATION",
|
||||
CHAT_INTERFACE$USER_CONFIRMED = "CHAT_INTERFACE$USER_CONFIRMED",
|
||||
CHAT_INTERFACE$USER_REJECTED = "CHAT_INTERFACE$USER_REJECTED",
|
||||
CHAT_INTERFACE$INPUT_SEND_MESSAGE_BUTTON_CONTENT = "CHAT_INTERFACE$INPUT_SEND_MESSAGE_BUTTON_CONTENT",
|
||||
CHAT_INTERFACE$CHAT_MESSAGE_COPIED = "CHAT_INTERFACE$CHAT_MESSAGE_COPIED",
|
||||
CHAT_INTERFACE$CHAT_MESSAGE_COPY_FAILED = "CHAT_INTERFACE$CHAT_MESSAGE_COPY_FAILED",
|
||||
CHAT_INTERFACE$TOOLTIP_COPY_MESSAGE = "CHAT_INTERFACE$TOOLTIP_COPY_MESSAGE",
|
||||
CHAT_INTERFACE$TOOLTIP_SEND_MESSAGE = "CHAT_INTERFACE$TOOLTIP_SEND_MESSAGE",
|
||||
CHAT_INTERFACE$TOOLTIP_UPLOAD_IMAGE = "CHAT_INTERFACE$TOOLTIP_UPLOAD_IMAGE",
|
||||
CHAT_INTERFACE$INITIAL_MESSAGE = "CHAT_INTERFACE$INITIAL_MESSAGE",
|
||||
CHAT_INTERFACE$ASSISTANT = "CHAT_INTERFACE$ASSISTANT",
|
||||
CHAT_INTERFACE$TO_BOTTOM = "CHAT_INTERFACE$TO_BOTTOM",
|
||||
CHAT_INTERFACE$MESSAGE_ARIA_LABEL = "CHAT_INTERFACE$MESSAGE_ARIA_LABEL",
|
||||
CHAT_INTERFACE$CHAT_CONVERSATION = "CHAT_INTERFACE$CHAT_CONVERSATION",
|
||||
CHAT_INTERFACE$UNKNOWN_SENDER = "CHAT_INTERFACE$UNKNOWN_SENDER",
|
||||
SECURITY_ANALYZER$UNKNOWN_RISK = "SECURITY_ANALYZER$UNKNOWN_RISK",
|
||||
SECURITY_ANALYZER$LOW_RISK = "SECURITY_ANALYZER$LOW_RISK",
|
||||
SECURITY_ANALYZER$MEDIUM_RISK = "SECURITY_ANALYZER$MEDIUM_RISK",
|
||||
SECURITY_ANALYZER$HIGH_RISK = "SECURITY_ANALYZER$HIGH_RISK",
|
||||
SETTINGS$MODEL_TOOLTIP = "SETTINGS$MODEL_TOOLTIP",
|
||||
SETTINGS$AGENT_TOOLTIP = "SETTINGS$AGENT_TOOLTIP",
|
||||
SETTINGS$LANGUAGE_TOOLTIP = "SETTINGS$LANGUAGE_TOOLTIP",
|
||||
SETTINGS$DISABLED_RUNNING = "SETTINGS$DISABLED_RUNNING",
|
||||
SETTINGS$API_KEY_PLACEHOLDER = "SETTINGS$API_KEY_PLACEHOLDER",
|
||||
SETTINGS$CONFIRMATION_MODE = "SETTINGS$CONFIRMATION_MODE",
|
||||
SETTINGS$CONFIRMATION_MODE_TOOLTIP = "SETTINGS$CONFIRMATION_MODE_TOOLTIP",
|
||||
SETTINGS$AGENT_SELECT_ENABLED = "SETTINGS$AGENT_SELECT_ENABLED",
|
||||
SETTINGS$SECURITY_ANALYZER = "SETTINGS$SECURITY_ANALYZER",
|
||||
PLANNER$EMPTY_MESSAGE = "PLANNER$EMPTY_MESSAGE",
|
||||
FEEDBACK$PUBLIC_LABEL = "FEEDBACK$PUBLIC_LABEL",
|
||||
FEEDBACK$PRIVATE_LABEL = "FEEDBACK$PRIVATE_LABEL",
|
||||
STATUS$STARTING_RUNTIME = "STATUS$STARTING_RUNTIME",
|
||||
STATUS$STARTING_CONTAINER = "STATUS$STARTING_CONTAINER",
|
||||
STATUS$PREPARING_CONTAINER = "STATUS$PREPARING_CONTAINER",
|
||||
STATUS$CONTAINER_STARTED = "STATUS$CONTAINER_STARTED",
|
||||
ACCOUNT_SETTINGS_MODAL$DISCONNECT = "ACCOUNT_SETTINGS_MODAL$DISCONNECT",
|
||||
ACCOUNT_SETTINGS_MODAL$SAVE = "ACCOUNT_SETTINGS_MODAL$SAVE",
|
||||
ACCOUNT_SETTINGS_MODAL$CLOSE = "ACCOUNT_SETTINGS_MODAL$CLOSE",
|
||||
ACCOUNT_SETTINGS_MODAL$GITHUB_TOKEN_INVALID = "ACCOUNT_SETTINGS_MODAL$GITHUB_TOKEN_INVALID",
|
||||
CONNECT_TO_GITHUB_MODAL$GET_YOUR_TOKEN = "CONNECT_TO_GITHUB_MODAL$GET_YOUR_TOKEN",
|
||||
CONNECT_TO_GITHUB_MODAL$HERE = "CONNECT_TO_GITHUB_MODAL$HERE",
|
||||
CONNECT_TO_GITHUB_MODAL$CONNECT = "CONNECT_TO_GITHUB_MODAL$CONNECT",
|
||||
CONNECT_TO_GITHUB_MODAL$CLOSE = "CONNECT_TO_GITHUB_MODAL$CLOSE",
|
||||
CONNECT_TO_GITHUB_BY_TOKEN_MODAL$BY_CONNECTING_YOU_AGREE = "CONNECT_TO_GITHUB_BY_TOKEN_MODAL$BY_CONNECTING_YOU_AGREE",
|
||||
CONNECT_TO_GITHUB_BY_TOKEN_MODAL$CONTINUE = "CONNECT_TO_GITHUB_BY_TOKEN_MODAL$CONTINUE",
|
||||
LOADING_PROJECT$LOADING = "LOADING_PROJECT$LOADING",
|
||||
CUSTOM_INPUT$OPTIONAL_LABEL = "CUSTOM_INPUT$OPTIONAL_LABEL",
|
||||
SETTINGS_FORM$CUSTOM_MODEL_LABEL = "SETTINGS_FORM$CUSTOM_MODEL_LABEL",
|
||||
SETTINGS_FORM$BASE_URL_LABEL = "SETTINGS_FORM$BASE_URL_LABEL",
|
||||
SETTINGS_FORM$API_KEY_LABEL = "SETTINGS_FORM$API_KEY_LABEL",
|
||||
SETTINGS_FORM$DONT_KNOW_API_KEY_LABEL = "SETTINGS_FORM$DONT_KNOW_API_KEY_LABEL",
|
||||
SETTINGS_FORM$CLICK_HERE_FOR_INSTRUCTIONS_LABEL = "SETTINGS_FORM$CLICK_HERE_FOR_INSTRUCTIONS_LABEL",
|
||||
SETTINGS_FORM$AGENT_LABEL = "SETTINGS_FORM$AGENT_LABEL",
|
||||
SETTINGS_FORM$SECURITY_ANALYZER_LABEL = "SETTINGS_FORM$SECURITY_ANALYZER_LABEL",
|
||||
SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL = "SETTINGS_FORM$ENABLE_CONFIRMATION_MODE_LABEL",
|
||||
SETTINGS_FORM$SAVE_LABEL = "SETTINGS_FORM$SAVE_LABEL",
|
||||
SETTINGS_FORM$CLOSE_LABEL = "SETTINGS_FORM$CLOSE_LABEL",
|
||||
SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL = "SETTINGS_FORM$RESET_TO_DEFAULTS_LABEL",
|
||||
SETTINGS_FORM$CANCEL_LABEL = "SETTINGS_FORM$CANCEL_LABEL",
|
||||
SETTINGS_FORM$END_SESSION_LABEL = "SETTINGS_FORM$END_SESSION_LABEL",
|
||||
SETTINGS_FORM$CHANGING_WORKSPACE_WARNING_MESSAGE = "SETTINGS_FORM$CHANGING_WORKSPACE_WARNING_MESSAGE",
|
||||
SETTINGS_FORM$ARE_YOU_SURE_LABEL = "SETTINGS_FORM$ARE_YOU_SURE_LABEL",
|
||||
SETTINGS_FORM$ALL_INFORMATION_WILL_BE_DELETED_MESSAGE = "SETTINGS_FORM$ALL_INFORMATION_WILL_BE_DELETED_MESSAGE",
|
||||
PROJECT_MENU_DETAILS_PLACEHOLDER$NEW_PROJECT_LABEL = "PROJECT_MENU_DETAILS_PLACEHOLDER$NEW_PROJECT_LABEL",
|
||||
PROJECT_MENU_DETAILS_PLACEHOLDER$CONNECT_TO_GITHUB = "PROJECT_MENU_DETAILS_PLACEHOLDER$CONNECT_TO_GITHUB",
|
||||
PROJECT_MENU_DETAILS_PLACEHOLDER$CONNECTED = "PROJECT_MENU_DETAILS_PLACEHOLDER$CONNECTED",
|
||||
PROJECT_MENU_DETAILS$AGO_LABEL = "PROJECT_MENU_DETAILS$AGO_LABEL",
|
||||
STATUS$ERROR_LLM_AUTHENTICATION = "STATUS$ERROR_LLM_AUTHENTICATION",
|
||||
STATUS$ERROR_LLM_SERVICE_UNAVAILABLE = "STATUS$ERROR_LLM_SERVICE_UNAVAILABLE",
|
||||
STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR = "STATUS$ERROR_LLM_INTERNAL_SERVER_ERROR",
|
||||
STATUS$ERROR_LLM_OUT_OF_CREDITS = "STATUS$ERROR_LLM_OUT_OF_CREDITS",
|
||||
STATUS$ERROR_RUNTIME_DISCONNECTED = "STATUS$ERROR_RUNTIME_DISCONNECTED",
|
||||
STATUS$LLM_RETRY = "STATUS$LLM_RETRY",
|
||||
AGENT_ERROR$BAD_ACTION = "AGENT_ERROR$BAD_ACTION",
|
||||
AGENT_ERROR$ACTION_TIMEOUT = "AGENT_ERROR$ACTION_TIMEOUT",
|
||||
PROJECT_MENU_CARD_CONTEXT_MENU$CONNECT_TO_GITHUB_LABEL = "PROJECT_MENU_CARD_CONTEXT_MENU$CONNECT_TO_GITHUB_LABEL",
|
||||
PROJECT_MENU_CARD_CONTEXT_MENU$PUSH_TO_GITHUB_LABEL = "PROJECT_MENU_CARD_CONTEXT_MENU$PUSH_TO_GITHUB_LABEL",
|
||||
PROJECT_MENU_CARD_CONTEXT_MENU$DOWNLOAD_FILES_LABEL = "PROJECT_MENU_CARD_CONTEXT_MENU$DOWNLOAD_FILES_LABEL",
|
||||
PROJECT_MENU_CARD$OPEN = "PROJECT_MENU_CARD$OPEN",
|
||||
ACTION_BUTTON$RESUME = "ACTION_BUTTON$RESUME",
|
||||
ACTION_BUTTON$PAUSE = "ACTION_BUTTON$PAUSE",
|
||||
BROWSER$SCREENSHOT_ALT = "BROWSER$SCREENSHOT_ALT",
|
||||
ERROR_TOAST$CLOSE_BUTTON_LABEL = "ERROR_TOAST$CLOSE_BUTTON_LABEL",
|
||||
FILE_EXPLORER$UPLOAD = "FILE_EXPLORER$UPLOAD",
|
||||
FILE_EXPLORER$REFRESH_WORKSPACE = "FILE_EXPLORER$REFRESH_WORKSPACE",
|
||||
FILE_EXPLORER$OPEN_WORKSPACE = "FILE_EXPLORER$OPEN_WORKSPACE",
|
||||
FILE_EXPLORER$CLOSE_WORKSPACE = "FILE_EXPLORER$CLOSE_WORKSPACE",
|
||||
ACTION_MESSAGE$RUN = "ACTION_MESSAGE$RUN",
|
||||
ACTION_MESSAGE$RUN_IPYTHON = "ACTION_MESSAGE$RUN_IPYTHON",
|
||||
ACTION_MESSAGE$READ = "ACTION_MESSAGE$READ",
|
||||
ACTION_MESSAGE$EDIT = "ACTION_MESSAGE$EDIT",
|
||||
ACTION_MESSAGE$WRITE = "ACTION_MESSAGE$WRITE",
|
||||
ACTION_MESSAGE$BROWSE = "ACTION_MESSAGE$BROWSE",
|
||||
ACTION_MESSAGE$THINK = "ACTION_MESSAGE$THINK",
|
||||
OBSERVATION_MESSAGE$RUN = "OBSERVATION_MESSAGE$RUN",
|
||||
OBSERVATION_MESSAGE$RUN_IPYTHON = "OBSERVATION_MESSAGE$RUN_IPYTHON",
|
||||
OBSERVATION_MESSAGE$READ = "OBSERVATION_MESSAGE$READ",
|
||||
OBSERVATION_MESSAGE$EDIT = "OBSERVATION_MESSAGE$EDIT",
|
||||
OBSERVATION_MESSAGE$WRITE = "OBSERVATION_MESSAGE$WRITE",
|
||||
OBSERVATION_MESSAGE$BROWSE = "OBSERVATION_MESSAGE$BROWSE",
|
||||
EXPANDABLE_MESSAGE$SHOW_DETAILS = "EXPANDABLE_MESSAGE$SHOW_DETAILS",
|
||||
EXPANDABLE_MESSAGE$HIDE_DETAILS = "EXPANDABLE_MESSAGE$HIDE_DETAILS",
|
||||
AI_SETTINGS$TITLE = "AI_SETTINGS$TITLE",
|
||||
SETTINGS$DESCRIPTION = "SETTINGS$DESCRIPTION",
|
||||
SETTINGS$WARNING = "SETTINGS$WARNING",
|
||||
SIDEBAR$SETTINGS = "SIDEBAR$SETTINGS",
|
||||
SIDEBAR$DOCS = "SIDEBAR$DOCS",
|
||||
SUGGESTIONS$ADD_DOCS = "SUGGESTIONS$ADD_DOCS",
|
||||
SUGGESTIONS$ADD_DOCKERFILE = "SUGGESTIONS$ADD_DOCKERFILE",
|
||||
STATUS$CONNECTED = "STATUS$CONNECTED",
|
||||
BROWSER$NO_PAGE_LOADED = "BROWSER$NO_PAGE_LOADED",
|
||||
USER$AVATAR_PLACEHOLDER = "USER$AVATAR_PLACEHOLDER",
|
||||
ACCOUNT_SETTINGS$SETTINGS = "ACCOUNT_SETTINGS$SETTINGS",
|
||||
ACCOUNT_SETTINGS$LOGOUT = "ACCOUNT_SETTINGS$LOGOUT",
|
||||
SETTINGS_FORM$ADVANCED_OPTIONS_LABEL = "SETTINGS_FORM$ADVANCED_OPTIONS_LABEL",
|
||||
CONVERSATION$NO_CONVERSATIONS = "CONVERSATION$NO_CONVERSATIONS",
|
||||
LANDING$SELECT_REPO = "LANDING$SELECT_REPO",
|
||||
BUTTON$SEND = "BUTTON$SEND",
|
||||
STATUS$WAITING_FOR_CLIENT = "STATUS$WAITING_FOR_CLIENT",
|
||||
SUGGESTIONS$WHAT_TO_BUILD = "SUGGESTIONS$WHAT_TO_BUILD",
|
||||
SETTINGS_FORM$ENABLE_DEFAULT_CONDENSER_SWITCH_LABEL = "SETTINGS_FORM$ENABLE_DEFAULT_CONDENSER_SWITCH_LABEL",
|
||||
BUTTON$MARK_HELPFUL = "BUTTON$MARK_HELPFUL",
|
||||
BUTTON$MARK_NOT_HELPFUL = "BUTTON$MARK_NOT_HELPFUL",
|
||||
BUTTON$EXPORT_CONVERSATION = "BUTTON$EXPORT_CONVERSATION",
|
||||
BILLING$CLICK_TO_TOP_UP = "BILLING$CLICK_TO_TOP_UP",
|
||||
}
|
||||
@@ -3833,7 +3833,21 @@
|
||||
"pt": "Ocorreu um erro ao conectar ao ambiente de execução. Por favor, atualize a página.",
|
||||
"tr": "Çalışma zamanına bağlanırken bir hata oluştu. Lütfen sayfayı yenileyin."
|
||||
},
|
||||
|
||||
"STATUS$ERROR_LLM_OUT_OF_CREDITS": {
|
||||
"en": "You're out of OpenHands Credits",
|
||||
"ja": "OpenHandsクレジットが不足しています",
|
||||
"zh-CN": "您的OpenHands点数已用完",
|
||||
"zh-TW": "您的OpenHands點數已用完",
|
||||
"ko-KR": "OpenHands 크레딧이 소진되었습니다",
|
||||
"no": "Du er tom for OpenHands-kreditter",
|
||||
"it": "Hai esaurito i crediti OpenHands",
|
||||
"pt": "Você está sem créditos OpenHands",
|
||||
"es": "Te has quedado sin créditos de OpenHands",
|
||||
"ar": "لقد نفدت رصيدك من OpenHands",
|
||||
"fr": "Vous n'avez plus de crédits OpenHands",
|
||||
"tr": "OpenHands kredileriniz tükendi",
|
||||
"de": "Ihre OpenHands-Guthaben sind aufgebraucht"
|
||||
},
|
||||
"STATUS$ERROR_RUNTIME_DISCONNECTED": {
|
||||
"en": "There was an error while connecting to the runtime. Please refresh the page.",
|
||||
"zh-CN": "运行时已断开连接",
|
||||
@@ -4164,6 +4178,21 @@
|
||||
"es": "Navegando en la web",
|
||||
"tr": "Web'de geziniyor"
|
||||
},
|
||||
"ACTION_MESSAGE$THINK": {
|
||||
"en": "Thinking",
|
||||
"zh-CN": "思考",
|
||||
"zh-TW": "思考",
|
||||
"ko-KR": "생각",
|
||||
"ja": "考える",
|
||||
"no": "Tenker",
|
||||
"ar": "يفكر",
|
||||
"de": "Denkt",
|
||||
"fr": "Pensant",
|
||||
"it": "Pensando",
|
||||
"pt": "Pensando",
|
||||
"es": "Pensando",
|
||||
"tr": "Düşünüyor"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$RUN": {
|
||||
"en": "Ran a bash command",
|
||||
"zh-CN": "运行",
|
||||
@@ -4558,5 +4587,65 @@
|
||||
"SETTINGS_FORM$ENABLE_DEFAULT_CONDENSER_SWITCH_LABEL": {
|
||||
"en": "Enable Memory Condenser",
|
||||
"zh-TW": "啟用記憶體壓縮器"
|
||||
},
|
||||
"BUTTON$MARK_HELPFUL": {
|
||||
"en": "Mark this solution as helpful",
|
||||
"zh-CN": "标记此解决方案有帮助",
|
||||
"zh-TW": "標記此解決方案有幫助",
|
||||
"de": "Diese Lösung als hilfreich markieren",
|
||||
"ko-KR": "이 해결책이 도움이 되었다고 표시",
|
||||
"no": "Marker denne løsningen som nyttig",
|
||||
"it": "Segna questa soluzione come utile",
|
||||
"pt": "Marcar esta solução como útil",
|
||||
"es": "Marcar esta solución como útil",
|
||||
"ar": "وضع علامة على هذا الحل كمفيد",
|
||||
"fr": "Marquer cette solution comme utile",
|
||||
"tr": "Bu çözümü yararlı olarak işaretle",
|
||||
"ja": "このソリューションが役立つと評価"
|
||||
},
|
||||
"BUTTON$MARK_NOT_HELPFUL": {
|
||||
"en": "Mark this solution as not helpful",
|
||||
"zh-CN": "标记此解决方案没有帮助",
|
||||
"zh-TW": "標記此解決方案沒有幫助",
|
||||
"de": "Diese Lösung als nicht hilfreich markieren",
|
||||
"ko-KR": "이 해결책이 도움이 되지 않았다고 표시",
|
||||
"no": "Marker denne løsningen som ikke nyttig",
|
||||
"it": "Segna questa soluzione come non utile",
|
||||
"pt": "Marcar esta solução como não útil",
|
||||
"es": "Marcar esta solución como no útil",
|
||||
"ar": "وضع علامة على هذا الحل كغير مفيد",
|
||||
"fr": "Marquer cette solution comme non utile",
|
||||
"tr": "Bu çözümü yararlı değil olarak işaretle",
|
||||
"ja": "このソリューションが役立たないと評価"
|
||||
},
|
||||
"BUTTON$EXPORT_CONVERSATION": {
|
||||
"en": "Export conversation",
|
||||
"zh-CN": "导出对话",
|
||||
"zh-TW": "導出對話",
|
||||
"de": "Konversation exportieren",
|
||||
"ko-KR": "대화 내보내기",
|
||||
"no": "Eksporter samtale",
|
||||
"it": "Esporta conversazione",
|
||||
"pt": "Exportar conversa",
|
||||
"es": "Exportar conversación",
|
||||
"ar": "تصدير المحادثة",
|
||||
"fr": "Exporter la conversation",
|
||||
"tr": "Konuşmayı dışa aktar",
|
||||
"ja": "会話をエクスポート"
|
||||
},
|
||||
"BILLING$CLICK_TO_TOP_UP": {
|
||||
"en": "Add funds to Your Account",
|
||||
"ja": "アカウントに資金を追加",
|
||||
"zh-CN": "为您的账户充值",
|
||||
"zh-TW": "為您的帳戶充值",
|
||||
"ko-KR": "계정에 자금 추가",
|
||||
"no": "Legg til midler på kontoen din",
|
||||
"it": "Aggiungi fondi al tuo account",
|
||||
"pt": "Adicionar fundos à sua conta",
|
||||
"es": "Añadir fondos a tu cuenta",
|
||||
"ar": "إضافة رصيد إلى حسابك",
|
||||
"fr": "Ajouter des fonds à votre compte",
|
||||
"tr": "Hesabınıza bakiye ekleyin",
|
||||
"de": "Guthaben zu Ihrem Konto hinzufügen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = {
|
||||
DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR,
|
||||
github_token_is_set: DEFAULT_SETTINGS.GITHUB_TOKEN_IS_SET,
|
||||
enable_default_condenser: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER,
|
||||
enable_sound_notifications: DEFAULT_SETTINGS.ENABLE_SOUND_NOTIFICATIONS,
|
||||
user_consents_to_analytics: DEFAULT_SETTINGS.USER_CONSENTS_TO_ANALYTICS,
|
||||
};
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ function AccountSettings() {
|
||||
formData.get("enable-analytics-switch")?.toString() === "on";
|
||||
const enableMemoryCondenser =
|
||||
formData.get("enable-memory-condenser-switch")?.toString() === "on";
|
||||
const enableSoundNotifications =
|
||||
formData.get("enable-sound-notifications-switch")?.toString() === "on";
|
||||
|
||||
saveSettings(
|
||||
{
|
||||
@@ -112,6 +114,7 @@ function AccountSettings() {
|
||||
LANGUAGE: languageValue,
|
||||
user_consents_to_analytics: userConsentsToAnalytics,
|
||||
ENABLE_DEFAULT_CONDENSER: enableMemoryCondenser,
|
||||
ENABLE_SOUND_NOTIFICATIONS: enableSoundNotifications,
|
||||
LLM_MODEL: customLlmModel || fullLlmModel,
|
||||
LLM_BASE_URL: formData.get("base-url-input")?.toString() || "",
|
||||
LLM_API_KEY:
|
||||
@@ -275,7 +278,15 @@ function AccountSettings() {
|
||||
<SettingsDropdownInput
|
||||
testId="runtime-settings-input"
|
||||
name="runtime-settings-input"
|
||||
label="Runtime Settings"
|
||||
label={
|
||||
<>
|
||||
Runtime Settings (
|
||||
<a href="mailto:contact@all-hands.dev">
|
||||
get in touch for access
|
||||
</a>
|
||||
)
|
||||
</>
|
||||
}
|
||||
items={REMOTE_RUNTIME_OPTIONS}
|
||||
defaultSelectedKey={settings.REMOTE_RUNTIME_RESOURCE_FACTOR?.toString()}
|
||||
isDisabled
|
||||
@@ -398,6 +409,14 @@ function AccountSettings() {
|
||||
>
|
||||
Enable analytics
|
||||
</SettingsSwitch>
|
||||
|
||||
<SettingsSwitch
|
||||
testId="enable-sound-notifications-switch"
|
||||
name="enable-sound-notifications-switch"
|
||||
defaultIsToggled={!!settings.ENABLE_SOUND_NOTIFICATIONS}
|
||||
>
|
||||
Enable sound notifications
|
||||
</SettingsSwitch>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -57,6 +57,28 @@ const messageActions = {
|
||||
store.dispatch(appendJupyterInput(message.args.code));
|
||||
}
|
||||
},
|
||||
[ActionType.FINISH]: (message: ActionMessage) => {
|
||||
store.dispatch(addAssistantMessage(message.args.final_thought));
|
||||
let successPrediction = "";
|
||||
if (message.args.task_completed === "partial") {
|
||||
successPrediction =
|
||||
"The agent thinks that the task was **completed partially**.";
|
||||
} else if (message.args.task_completed === "false") {
|
||||
successPrediction =
|
||||
"The agent thinks that the task was **not completed**.";
|
||||
} else if (message.args.task_completed === "true") {
|
||||
successPrediction =
|
||||
"The agent thinks that the task was **completed successfully**.";
|
||||
}
|
||||
if (successPrediction) {
|
||||
// if final_thought is not empty, add a new line before the success prediction
|
||||
if (message.args.final_thought) {
|
||||
store.dispatch(addAssistantMessage(`\n${successPrediction}`));
|
||||
} else {
|
||||
store.dispatch(addAssistantMessage(successPrediction));
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export function handleActionMessage(message: ActionMessage) {
|
||||
|
||||
@@ -48,6 +48,8 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
break;
|
||||
case ObservationType.READ:
|
||||
case ObservationType.EDIT:
|
||||
case ObservationType.THINK:
|
||||
case ObservationType.NULL:
|
||||
break; // We don't display the default message for these observations
|
||||
default:
|
||||
store.dispatch(addAssistantMessage(message.message));
|
||||
@@ -87,6 +89,17 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
);
|
||||
break;
|
||||
case "read":
|
||||
store.dispatch(
|
||||
addAssistantObservation({
|
||||
...baseObservation,
|
||||
observation,
|
||||
extras: {
|
||||
path: String(message.extras.path || ""),
|
||||
impl_source: String(message.extras.impl_source || ""),
|
||||
},
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case "edit":
|
||||
store.dispatch(
|
||||
addAssistantObservation({
|
||||
@@ -94,6 +107,8 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
observation,
|
||||
extras: {
|
||||
path: String(message.extras.path || ""),
|
||||
diff: String(message.extras.diff || ""),
|
||||
impl_source: String(message.extras.impl_source || ""),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
REMOTE_RUNTIME_RESOURCE_FACTOR: 1,
|
||||
GITHUB_TOKEN_IS_SET: false,
|
||||
ENABLE_DEFAULT_CONDENSER: true,
|
||||
ENABLE_SOUND_NOTIFICATIONS: false,
|
||||
USER_CONSENTS_TO_ANALYTICS: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -115,6 +115,8 @@ export const chatSlice = createSlice({
|
||||
) {
|
||||
text += `\n\n${getRiskText(action.payload.args.security_risk as unknown as ActionSecurityRisk)}`;
|
||||
}
|
||||
} else if (actionID === "think") {
|
||||
text = action.payload.args.thought;
|
||||
}
|
||||
const message: Message = {
|
||||
type: "action",
|
||||
@@ -157,9 +159,16 @@ export const chatSlice = createSlice({
|
||||
.includes("error:");
|
||||
} else if (observationID === "read" || observationID === "edit") {
|
||||
// For read/edit operations, we consider it successful if there's content and no error
|
||||
causeMessage.success =
|
||||
observation.payload.content.length > 0 &&
|
||||
!observation.payload.content.toLowerCase().includes("error:");
|
||||
|
||||
if (observation.payload.extras.impl_source === "oh_aci") {
|
||||
causeMessage.success =
|
||||
observation.payload.content.length > 0 &&
|
||||
!observation.payload.content.startsWith("ERROR:\n");
|
||||
} else {
|
||||
causeMessage.success =
|
||||
observation.payload.content.length > 0 &&
|
||||
!observation.payload.content.toLowerCase().includes("error:");
|
||||
}
|
||||
}
|
||||
|
||||
if (observationID === "run" || observationID === "run_ipython") {
|
||||
@@ -171,9 +180,14 @@ export const chatSlice = createSlice({
|
||||
causeMessage.content
|
||||
}\n\nOutput:\n\`\`\`\n${content.trim() || "[Command finished execution with no output]"}\n\`\`\``;
|
||||
causeMessage.content = content; // Observation content includes the action
|
||||
} else if (observationID === "read" || observationID === "edit") {
|
||||
const { content } = observation.payload;
|
||||
causeMessage.content = `\`\`\`${observationID === "edit" ? "diff" : "python"}\n${content}\n\`\`\``; // Content is already truncated by the ACI
|
||||
} else if (observationID === "read") {
|
||||
causeMessage.content = `\`\`\`\n${observation.payload.content}\n\`\`\``; // Content is already truncated by the ACI
|
||||
} else if (observationID === "edit") {
|
||||
if (causeMessage.success) {
|
||||
causeMessage.content = `\`\`\`diff\n${observation.payload.extras.diff}\n\`\`\``; // Content is already truncated by the ACI
|
||||
} else {
|
||||
causeMessage.content = observation.payload.content;
|
||||
}
|
||||
} else if (observationID === "browse") {
|
||||
let content = `**URL:** ${observation.payload.extras.url}\n`;
|
||||
if (observation.payload.extras.error) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user