Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3587bdc61e |
@@ -1,223 +0,0 @@
|
|||||||
name: End-to-End Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, labeled]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- develop
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
e2e-tests:
|
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'end-to-end') || github.event_name == 'workflow_dispatch'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
|
|
||||||
env:
|
|
||||||
GITHUB_REPO_NAME: ${{ github.repository }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install poetry via pipx
|
|
||||||
uses: abatilo/actions-poetry@v3
|
|
||||||
with:
|
|
||||||
poetry-version: 2.1.3
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.12'
|
|
||||||
cache: 'poetry'
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xauth xvfb libgbm1 libasound2t64 netcat-openbsd
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '22'
|
|
||||||
cache: 'npm'
|
|
||||||
cache-dependency-path: 'frontend/package-lock.json'
|
|
||||||
|
|
||||||
- name: Setup environment for end-to-end tests
|
|
||||||
run: |
|
|
||||||
# Create test results directory
|
|
||||||
mkdir -p test-results
|
|
||||||
|
|
||||||
# Create downloads directory for OpenHands (use a directory in the home folder)
|
|
||||||
mkdir -p $HOME/downloads
|
|
||||||
sudo chown -R $USER:$USER $HOME/downloads
|
|
||||||
sudo chmod -R 755 $HOME/downloads
|
|
||||||
|
|
||||||
- name: Build OpenHands
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
LLM_MODEL: ${{ secrets.LLM_MODEL || 'gpt-4o' }}
|
|
||||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY || 'test-key' }}
|
|
||||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
|
||||||
INSTALL_DOCKER: 1
|
|
||||||
RUNTIME: docker
|
|
||||||
FRONTEND_PORT: 12000
|
|
||||||
FRONTEND_HOST: 0.0.0.0
|
|
||||||
BACKEND_HOST: 0.0.0.0
|
|
||||||
BACKEND_PORT: 3000
|
|
||||||
ENABLE_BROWSER: true
|
|
||||||
INSTALL_PLAYWRIGHT: 1
|
|
||||||
run: |
|
|
||||||
# Fix poetry.lock file if needed
|
|
||||||
echo "Fixing poetry.lock file if needed..."
|
|
||||||
poetry lock
|
|
||||||
|
|
||||||
# Build OpenHands using make build
|
|
||||||
echo "Running make build..."
|
|
||||||
make build
|
|
||||||
|
|
||||||
# Install Chromium Headless Shell for Playwright (needed for pytest-playwright)
|
|
||||||
echo "Installing Chromium Headless Shell for Playwright..."
|
|
||||||
poetry run playwright install chromium-headless-shell
|
|
||||||
|
|
||||||
# Verify Playwright browsers are installed (for e2e tests only)
|
|
||||||
echo "Verifying Playwright browsers installation for e2e tests..."
|
|
||||||
BROWSER_CHECK=$(poetry run python tests/e2e/check_playwright.py 2>/dev/null)
|
|
||||||
|
|
||||||
if [ "$BROWSER_CHECK" != "chromium_found" ]; then
|
|
||||||
echo "ERROR: Chromium browser not found or not working for e2e tests"
|
|
||||||
echo "$BROWSER_CHECK"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Playwright browsers are properly installed for e2e tests."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Docker runtime will handle workspace directory creation
|
|
||||||
|
|
||||||
# Start the application using make run with custom parameters and reduced logging
|
|
||||||
echo "Starting OpenHands using make run..."
|
|
||||||
# Set environment variables to reduce logging verbosity
|
|
||||||
export PYTHONUNBUFFERED=1
|
|
||||||
export LOG_LEVEL=WARNING
|
|
||||||
export UVICORN_LOG_LEVEL=warning
|
|
||||||
export OPENHANDS_LOG_LEVEL=WARNING
|
|
||||||
FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0 make run > /tmp/openhands-e2e-test.log 2>&1 &
|
|
||||||
|
|
||||||
# Store the PID of the make run process
|
|
||||||
MAKE_PID=$!
|
|
||||||
echo "OpenHands started with PID: $MAKE_PID"
|
|
||||||
|
|
||||||
# Wait for the application to start
|
|
||||||
echo "Waiting for OpenHands to start..."
|
|
||||||
max_attempts=15
|
|
||||||
attempt=1
|
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
|
||||||
echo "Checking if OpenHands is running (attempt $attempt of $max_attempts)..."
|
|
||||||
|
|
||||||
# Check if the process is still running
|
|
||||||
if ! ps -p $MAKE_PID > /dev/null; then
|
|
||||||
echo "ERROR: OpenHands process has terminated unexpectedly"
|
|
||||||
echo "Last 50 lines of the log:"
|
|
||||||
tail -n 50 /tmp/openhands-e2e-test.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if frontend port is open
|
|
||||||
if nc -z localhost 12000; then
|
|
||||||
# Verify we can get HTML content
|
|
||||||
if curl -s http://localhost:12000 | grep -q "<html"; then
|
|
||||||
echo "SUCCESS: OpenHands is running and serving HTML content on port 12000"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
echo "Port 12000 is open but not serving HTML content yet"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Frontend port 12000 is not open yet"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Show log output on each attempt
|
|
||||||
echo "Recent log output:"
|
|
||||||
tail -n 20 /tmp/openhands-e2e-test.log
|
|
||||||
|
|
||||||
# Wait before next attempt
|
|
||||||
echo "Waiting 10 seconds before next check..."
|
|
||||||
sleep 10
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
|
|
||||||
# Exit if we've reached the maximum number of attempts
|
|
||||||
if [ $attempt -gt $max_attempts ]; then
|
|
||||||
echo "ERROR: OpenHands failed to start after $max_attempts attempts"
|
|
||||||
echo "Last 50 lines of the log:"
|
|
||||||
tail -n 50 /tmp/openhands-e2e-test.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Final verification that the app is running
|
|
||||||
if ! nc -z localhost 12000 || ! curl -s http://localhost:12000 | grep -q "<html"; then
|
|
||||||
echo "ERROR: OpenHands is not running properly on port 12000"
|
|
||||||
echo "Last 50 lines of the log:"
|
|
||||||
tail -n 50 /tmp/openhands-e2e-test.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Print success message
|
|
||||||
echo "OpenHands is running successfully on port 12000"
|
|
||||||
|
|
||||||
- name: Run end-to-end tests
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN }}
|
|
||||||
LLM_MODEL: ${{ secrets.LLM_MODEL || 'gpt-4o' }}
|
|
||||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY || 'test-key' }}
|
|
||||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
|
||||||
run: |
|
|
||||||
# Check if the application is running
|
|
||||||
if ! nc -z localhost 12000; then
|
|
||||||
echo "ERROR: OpenHands is not running on port 12000"
|
|
||||||
echo "Last 50 lines of the log:"
|
|
||||||
tail -n 50 /tmp/openhands-e2e-test.log
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run the tests with detailed output
|
|
||||||
cd tests/e2e
|
|
||||||
poetry run python -m pytest test_e2e_workflow.py::test_github_token_configuration test_e2e_workflow.py::test_conversation_start -v --no-header --capture=no --timeout=600
|
|
||||||
|
|
||||||
- name: Upload test results
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: playwright-report
|
|
||||||
path: tests/e2e/test-results/
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: Upload OpenHands logs
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: openhands-logs
|
|
||||||
path: |
|
|
||||||
/tmp/openhands-e2e-test.log
|
|
||||||
/tmp/openhands-e2e-build.log
|
|
||||||
/tmp/openhands-backend.log
|
|
||||||
/tmp/openhands-frontend.log
|
|
||||||
/tmp/backend-health-check.log
|
|
||||||
/tmp/frontend-check.log
|
|
||||||
/tmp/vite-config.log
|
|
||||||
/tmp/makefile-contents.log
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: Cleanup
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
# Stop OpenHands processes
|
|
||||||
echo "Stopping OpenHands processes..."
|
|
||||||
pkill -f "python -m openhands.server" || true
|
|
||||||
pkill -f "npm run dev" || true
|
|
||||||
pkill -f "make run" || true
|
|
||||||
|
|
||||||
# Print process status for debugging
|
|
||||||
echo "Checking if any OpenHands processes are still running:"
|
|
||||||
ps aux | grep -E "openhands|npm run dev" || true
|
|
||||||
@@ -51,6 +51,8 @@ jobs:
|
|||||||
run: PYTHONPATH=".:$PYTHONPATH" poetry run pytest --forked -n auto -svv ./tests/unit
|
run: PYTHONPATH=".:$PYTHONPATH" poetry run pytest --forked -n auto -svv ./tests/unit
|
||||||
- name: Run Runtime Tests with CLIRuntime
|
- name: Run Runtime Tests with CLIRuntime
|
||||||
run: PYTHONPATH=".:$PYTHONPATH" TEST_RUNTIME=cli poetry run pytest -svv tests/runtime/test_bash.py
|
run: PYTHONPATH=".:$PYTHONPATH" TEST_RUNTIME=cli poetry run pytest -svv tests/runtime/test_bash.py
|
||||||
|
- name: Run E2E Tests
|
||||||
|
run: PYTHONPATH=".:$PYTHONPATH" poetry run pytest -svv tests/e2e
|
||||||
|
|
||||||
# Run specific Windows python tests
|
# Run specific Windows python tests
|
||||||
test-on-windows:
|
test-on-windows:
|
||||||
|
|||||||
@@ -254,11 +254,3 @@ containers/runtime/Dockerfile
|
|||||||
containers/runtime/project.tar.gz
|
containers/runtime/project.tar.gz
|
||||||
containers/runtime/code
|
containers/runtime/code
|
||||||
**/node_modules/
|
**/node_modules/
|
||||||
|
|
||||||
# VSCode extension test files
|
|
||||||
openhands/integrations/vscode/.vscode-test/
|
|
||||||
openhands/integrations/vscode/out/
|
|
||||||
openhands/integrations/vscode/node_modules/
|
|
||||||
|
|
||||||
# test results
|
|
||||||
test-results
|
|
||||||
|
|||||||
@@ -3,9 +3,4 @@
|
|||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"python.testing.pytestArgs": [
|
|
||||||
"tests"
|
|
||||||
],
|
|
||||||
"python.testing.unittestEnabled": false,
|
|
||||||
"python.testing.pytestEnabled": true,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,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
|
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.
|
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.53-nikolaik`
|
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.51-nikolaik`
|
||||||
|
|
||||||
## Develop inside Docker container
|
## Develop inside Docker container
|
||||||
|
|
||||||
|
|||||||
@@ -52,63 +52,37 @@ which comes with $20 in free credits for new users.
|
|||||||
|
|
||||||
## 💻 Running OpenHands Locally
|
## 💻 Running OpenHands Locally
|
||||||
|
|
||||||
### Option 1: CLI Launcher (Recommended)
|
OpenHands can also run on your local system using Docker.
|
||||||
|
See the [Running OpenHands](https://docs.all-hands.dev/usage/installation) guide for
|
||||||
|
system requirements and more information.
|
||||||
|
|
||||||
The easiest way to run OpenHands locally is using the CLI launcher with [uv](https://docs.astral.sh/uv/). This provides better isolation from your current project's virtual environment and is required for OpenHands' default MCP servers.
|
> [!WARNING]
|
||||||
|
> On a public network? See our [Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)
|
||||||
|
> to secure your deployment by restricting network binding and implementing additional security measures.
|
||||||
|
|
||||||
**Install uv** (if you haven't already):
|
|
||||||
|
|
||||||
See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for the latest installation instructions for your platform.
|
|
||||||
|
|
||||||
**Launch OpenHands**:
|
|
||||||
```bash
|
|
||||||
# Launch the GUI server
|
|
||||||
uvx --python 3.12 --from openhands-ai openhands serve
|
|
||||||
|
|
||||||
# Or launch the CLI
|
|
||||||
uvx --python 3.12 --from openhands-ai openhands
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000) (for GUI mode)!
|
|
||||||
|
|
||||||
### Option 2: Docker
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click to expand Docker command</summary>
|
|
||||||
|
|
||||||
You can also run OpenHands directly with Docker:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||||
|
|
||||||
docker run -it --rm --pull=always \
|
docker run -it --rm --pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e LOG_ALL_EVENTS=true \
|
-e LOG_ALL_EVENTS=true \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
-p 3000:3000 \
|
-p 3000:3000 \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app \
|
--name openhands-app \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||||
|
|
||||||
> [!WARNING]
|
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||||
> On a public network? See our [Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)
|
|
||||||
> to secure your deployment by restricting network binding and implementing additional security measures.
|
|
||||||
|
|
||||||
### Getting Started
|
|
||||||
|
|
||||||
When you open the application, you'll be asked to choose an LLM provider and add an API key.
|
When you open the application, you'll be asked to choose an LLM provider and add an API key.
|
||||||
[Anthropic's Claude Sonnet 4](https://www.anthropic.com/api) (`anthropic/claude-sonnet-4-20250514`)
|
[Anthropic's Claude Sonnet 4](https://www.anthropic.com/api) (`anthropic/claude-sonnet-4-20250514`)
|
||||||
works best, but you have [many options](https://docs.all-hands.dev/usage/llms).
|
works best, but you have [many options](https://docs.all-hands.dev/usage/llms).
|
||||||
|
|
||||||
See the [Running OpenHands](https://docs.all-hands.dev/usage/installation) guide for
|
|
||||||
system requirements and more information.
|
|
||||||
|
|
||||||
## 💡 Other ways to run OpenHands
|
## 💡 Other ways to run OpenHands
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
@@ -119,8 +93,8 @@ system requirements and more information.
|
|||||||
> [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud)
|
> [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud)
|
||||||
|
|
||||||
You can [connect OpenHands to your local filesystem](https://docs.all-hands.dev/usage/runtimes/docker#connecting-to-your-filesystem),
|
You can [connect OpenHands to your local filesystem](https://docs.all-hands.dev/usage/runtimes/docker#connecting-to-your-filesystem),
|
||||||
interact with it via a [friendly CLI](https://docs.all-hands.dev/usage/how-to/cli-mode),
|
|
||||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/usage/how-to/headless-mode),
|
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/usage/how-to/headless-mode),
|
||||||
|
interact with it via a [friendly CLI](https://docs.all-hands.dev/usage/how-to/cli-mode),
|
||||||
or run it on tagged issues with [a github action](https://docs.all-hands.dev/usage/how-to/github-action).
|
or run it on tagged issues with [a github action](https://docs.all-hands.dev/usage/how-to/github-action).
|
||||||
|
|
||||||
Visit [Running OpenHands](https://docs.all-hands.dev/usage/installation) for more information and setup instructions.
|
Visit [Running OpenHands](https://docs.all-hands.dev/usage/installation) for more information and setup instructions.
|
||||||
|
|||||||
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
|||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||||
|
|
||||||
docker run -it --rm --pull=always \
|
docker run -it --rm --pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e LOG_ALL_EVENTS=true \
|
-e LOG_ALL_EVENTS=true \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
-p 3000:3000 \
|
-p 3000:3000 \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app \
|
--name openhands-app \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||||
```
|
```
|
||||||
|
|
||||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||||
|
|||||||
@@ -42,17 +42,17 @@ OpenHandsはDockerを利用してローカル環境でも実行できます。
|
|||||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||||
|
|
||||||
docker run -it --rm --pull=always \
|
docker run -it --rm --pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e LOG_ALL_EVENTS=true \
|
-e LOG_ALL_EVENTS=true \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
-p 3000:3000 \
|
-p 3000:3000 \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app \
|
--name openhands-app \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||||
```
|
```
|
||||||
|
|
||||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ def build_vscode_extension():
|
|||||||
|
|
||||||
|
|
||||||
def build(setup_kwargs):
|
def build(setup_kwargs):
|
||||||
"""This function is called by Poetry during the build process.
|
"""
|
||||||
|
This function is called by Poetry during the build process.
|
||||||
`setup_kwargs` is a dictionary that will be passed to `setuptools.setup()`.
|
`setup_kwargs` is a dictionary that will be passed to `setuptools.setup()`.
|
||||||
"""
|
"""
|
||||||
print('--- Running custom Poetry build script (build_vscode.py) ---')
|
print('--- Running custom Poetry build script (build_vscode.py) ---')
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||||
- DOCKER_HOST_ADDR=host.docker.internal
|
- DOCKER_HOST_ADDR=host.docker.internal
|
||||||
#
|
#
|
||||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.53-nikolaik}
|
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.51-nikolaik}
|
||||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
image: openhands:latest
|
image: openhands:latest
|
||||||
container_name: openhands-app-${DATE:-}
|
container_name: openhands-app-${DATE:-}
|
||||||
environment:
|
environment:
|
||||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik}
|
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.51-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 for this user
|
#- 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 for this user
|
||||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
@@ -78,14 +78,6 @@ description: Complete guide for setting up Jira Data Center integration with Ope
|
|||||||
- **Service Account API Key**: The personal access token from Step 2 above
|
- **Service Account API Key**: The personal access token from Step 2 above
|
||||||
- Ensure **Active** toggle is enabled
|
- Ensure **Active** toggle is enabled
|
||||||
|
|
||||||
<Note>
|
|
||||||
Workspace name is the host name of your Jira Data Center instance.
|
|
||||||
|
|
||||||
Eg: http://jira.all-hands.dev/projects/OH/issues/OH-77
|
|
||||||
|
|
||||||
Here the workspace name is **jira.all-hands.dev**.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
3. **Complete OAuth Flow**
|
3. **Complete OAuth Flow**
|
||||||
- You'll be redirected to Jira Data Center to complete OAuth verification
|
- You'll be redirected to Jira Data Center to complete OAuth verification
|
||||||
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
||||||
@@ -109,18 +101,18 @@ Here the workspace name is **jira.all-hands.dev**.
|
|||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="Workspace link flow">
|
<Accordion title="Workspace link flow">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Workspace Configure flow">
|
<Accordion title="Workspace Configure flow">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Edit view as a user">
|
<Accordion title="Edit view as a user">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Edit view as the workspace creator">
|
<Accordion title="Edit view as the workspace creator">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
|
|||||||
@@ -15,27 +15,28 @@ description: Complete guide for setting up Jira Cloud integration with OpenHands
|
|||||||
- Go to **Directory** > **Users**
|
- Go to **Directory** > **Users**
|
||||||
|
|
||||||
2. **Create OpenHands Service Account**
|
2. **Create OpenHands Service Account**
|
||||||
- Click **Service accounts**
|
- Click **Add user**
|
||||||
- Click **Create a service account**
|
- Email: `openhands@yourcompany.com` (replace with your preferred service account email)
|
||||||
- Name: `OpenHands Agent`
|
- Display name: `OpenHands Agent`
|
||||||
- Click **Next**
|
- Send invitation: **No** (you'll set password manually)
|
||||||
- Select **User** role for Jira app
|
- Click **Add user**
|
||||||
- Click **Create**
|
|
||||||
|
3. **Configure Account**
|
||||||
|
- Locate the created user and click on it
|
||||||
|
- Set a secure password
|
||||||
|
- Add to relevant Jira projects with appropriate permissions
|
||||||
|
|
||||||
### Step 2: Generate API Token
|
### Step 2: Generate API Token
|
||||||
|
|
||||||
1. **Access Service Account Configuration**
|
1. **Access API Token Management**
|
||||||
- Locate the created service account from above step and click on it
|
- Log in as the OpenHands service account
|
||||||
|
- Go to [API Tokens](https://id.atlassian.com/manage-profile/security/api-tokens)
|
||||||
|
|
||||||
|
2. **Create API Token**
|
||||||
- Click **Create API token**
|
- Click **Create API token**
|
||||||
- Set the expiry to 365 days (maximum allowed value)
|
- Label: `OpenHands Cloud Integration`
|
||||||
- Click **Next**
|
- Expiry: Set appropriate expiration (recommend 1 year)
|
||||||
- In **Select token scopes** screen, filter by following values
|
- Click **Create**
|
||||||
- App: Jira
|
|
||||||
- Scope type: Classic
|
|
||||||
- Scope actions: Write, Read
|
|
||||||
- Select `read:jira-work` and `write:jira-work` scopes
|
|
||||||
- Click **Next**
|
|
||||||
- Review and create API token
|
|
||||||
- **Important**: Copy and securely store the token immediately
|
- **Important**: Copy and securely store the token immediately
|
||||||
|
|
||||||
### Step 3: Configure Webhook
|
### Step 3: Configure Webhook
|
||||||
@@ -82,14 +83,6 @@ description: Complete guide for setting up Jira Cloud integration with OpenHands
|
|||||||
- **Service Account API Key**: The API token from Step 2 above
|
- **Service Account API Key**: The API token from Step 2 above
|
||||||
- Ensure **Active** toggle is enabled
|
- Ensure **Active** toggle is enabled
|
||||||
|
|
||||||
<Note>
|
|
||||||
Workspace name is the host name when accessing a resource in Jira Cloud.
|
|
||||||
|
|
||||||
Eg: https://all-hands.atlassian.net/browse/OH-55
|
|
||||||
|
|
||||||
Here the workspace name is **all-hands**.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
3. **Complete OAuth Flow**
|
3. **Complete OAuth Flow**
|
||||||
- You'll be redirected to Jira Cloud to complete OAuth verification
|
- You'll be redirected to Jira Cloud to complete OAuth verification
|
||||||
- Grant the necessary permissions to verify your workspace access.
|
- Grant the necessary permissions to verify your workspace access.
|
||||||
@@ -113,18 +106,18 @@ Here the workspace name is **all-hands**.
|
|||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="Workspace link flow">
|
<Accordion title="Workspace link flow">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Workspace Configure flow">
|
<Accordion title="Workspace Configure flow">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Edit view as a user">
|
<Accordion title="Edit view as a user">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Edit view as the workspace creator">
|
<Accordion title="Edit view as the workspace creator">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ description: Complete guide for setting up Linear integration with OpenHands Clo
|
|||||||
|
|
||||||
1. **Access API Settings**
|
1. **Access API Settings**
|
||||||
- Log in as the service account
|
- Log in as the service account
|
||||||
- Go to **Settings** > **Security & access**
|
- Go to **Settings** > **API**
|
||||||
|
|
||||||
2. **Create Personal API Key**
|
2. **Create Personal API Key**
|
||||||
- Click **Create new key**
|
- Click **Create new key**
|
||||||
@@ -82,14 +82,6 @@ description: Complete guide for setting up Linear integration with OpenHands Clo
|
|||||||
- **Service Account API Key**: The API key from Step 2 above
|
- **Service Account API Key**: The API key from Step 2 above
|
||||||
- Ensure **Active** toggle is enabled
|
- Ensure **Active** toggle is enabled
|
||||||
|
|
||||||
<Note>
|
|
||||||
Workspace name is the identifier after the host name when accessing a resource in Linear.
|
|
||||||
|
|
||||||
Eg: https://linear.app/allhands/issue/OH-37
|
|
||||||
|
|
||||||
Here the workspace name is **allhands**.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||
3. **Complete OAuth Flow**
|
3. **Complete OAuth Flow**
|
||||||
- You'll be redirected to Linear to complete OAuth verification
|
- You'll be redirected to Linear to complete OAuth verification
|
||||||
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
||||||
@@ -113,15 +105,15 @@ Here the workspace name is **allhands**.
|
|||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="Workspace link flow">
|
<Accordion title="Workspace link flow">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Workspace Configure flow">
|
<Accordion title="Workspace Configure flow">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Edit view as a user">
|
<Accordion title="Edit view as a user">
|
||||||

|

|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Edit view as the workspace creator">
|
<Accordion title="Edit view as the workspace creator">
|
||||||
|
|||||||
@@ -58,18 +58,17 @@ The OpenHands agent needs to identify which Git repository to work with when pro
|
|||||||
|
|
||||||
### Platform Configuration Issues
|
### Platform Configuration Issues
|
||||||
- **Webhook not triggering**: Verify the webhook URL is correct and the proper event types are selected (Comment, Issue updated)
|
- **Webhook not triggering**: Verify the webhook URL is correct and the proper event types are selected (Comment, Issue updated)
|
||||||
- **API authentication failing**: Check API key/token validity and ensure required scopes are granted. If your current API token is expired, make sure to update it in the respective integration settings
|
- **API authentication failing**: Check API key/token validity and ensure required scopes are granted
|
||||||
- **Permission errors**: Ensure the service account has access to relevant projects/teams and appropriate permissions
|
- **Permission errors**: Ensure the service account has access to relevant projects/teams and appropriate permissions
|
||||||
|
|
||||||
### Workspace Integration Issues
|
### Workspace Integration Issues
|
||||||
- **Workspace linking requests credentials**: If there are no active workspace integrations for the workspace you specified, you need to configure it first. Contact your platform administrator that you want to integrate with (eg: Jira, Linear)
|
- **Workspace linking requests credentials**: If there are no active workspace integrations for the workspace you specified, you need to configure it first. Contact your platform administrator that you want to integrate with (eg: Jira, Linear)
|
||||||
|
- **OAuth flow fails**: Ensure you're signing in with the same Git provider account that contains the repositories you want OpenHands to work on
|
||||||
- **Integration not found**: Verify the workspace name matches exactly and that platform configuration was completed first
|
- **Integration not found**: Verify the workspace name matches exactly and that platform configuration was completed first
|
||||||
- **OAuth flow fails**: Make sure that you're authorizing with the correct account with proper workspace access
|
|
||||||
|
|
||||||
### General Issues
|
### General Issues
|
||||||
- **Agent not responding**: Check webhook logs in your platform settings and verify service account status
|
- **Agent not responding**: Check webhook logs in your platform settings and verify service account status
|
||||||
- **Authentication errors**: Verify Git provider permissions and OpenHands Cloud access
|
- **Authentication errors**: Verify Git provider permissions and OpenHands Cloud access
|
||||||
- **Agent fails to identify git repo**: Ensure you're signing in with the same Git provider account that contains the repositories you want OpenHands to work on
|
|
||||||
- **Partial functionality**: Ensure both platform configuration and workspace integration are properly completed
|
- **Partial functionality**: Ensure both platform configuration and workspace integration are properly completed
|
||||||
|
|
||||||
### Getting Help
|
### Getting Help
|
||||||
|
|||||||
@@ -20,42 +20,27 @@ for scripting.
|
|||||||
|
|
||||||
### Running with Python
|
### Running with Python
|
||||||
|
|
||||||
**Note** - OpenHands requires Python version 3.12 or higher (Python 3.14 is not currently supported) and `uv` for the default `fetch` MCP server (more details below).
|
**Note** - OpenHands requires Python version 3.12 or higher (Python 3.14 is not currently supported) and `uvx` for the default `fetch` MCP server (more details below).
|
||||||
|
|
||||||
#### Recommended: Using uv
|
1. Install OpenHands using pip:
|
||||||
|
```bash
|
||||||
|
pip install openhands-ai
|
||||||
|
```
|
||||||
|
|
||||||
We recommend using [uv](https://docs.astral.sh/uv/) for the best OpenHands experience. uv provides better isolation from your current project's virtual environment and is required for OpenHands' default MCP servers.
|
Or if you prefer not to manage your own Python environment, you can use `uvx`:
|
||||||
|
|
||||||
1. **Install uv** (if you haven't already):
|
|
||||||
|
|
||||||
See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for the latest installation instructions for your platform.
|
|
||||||
|
|
||||||
2. **Launch OpenHands CLI**:
|
|
||||||
```bash
|
```bash
|
||||||
uvx --python 3.12 --from openhands-ai openhands
|
uvx --python 3.12 --from openhands-ai openhands
|
||||||
```
|
```
|
||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
|
|
||||||
<Accordion title="Alternative: Traditional pip installation">
|
|
||||||
|
|
||||||
If you prefer to use pip:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install OpenHands
|
|
||||||
pip install openhands-ai
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that you'll still need `uv` installed for the default MCP servers to work properly.
|
|
||||||
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="Create shell aliases for easy access across environments">
|
<Accordion title="Create shell aliases for easy access across environments">
|
||||||
|
|
||||||
Add the following to your shell configuration file (`.bashrc`, `.zshrc`, etc.):
|
Add the following to your shell configuration file (`.bashrc`, `.zshrc`, etc.):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add OpenHands aliases (recommended)
|
# Add OpenHands aliases
|
||||||
alias openhands="uvx --python 3.12 --from openhands-ai openhands"
|
alias openhands="uvx --python 3.12 --from openhands-ai openhands"
|
||||||
alias oh="uvx --python 3.12 --from openhands-ai openhands"
|
alias oh="uvx --python 3.12 --from openhands-ai openhands"
|
||||||
```
|
```
|
||||||
@@ -87,19 +72,18 @@ source ~/.bashrc # or source ~/.zshrc
|
|||||||
|
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
|
|
||||||
3. Launch an interactive OpenHands conversation from the command line:
|
2. Launch an interactive OpenHands conversation from the command line:
|
||||||
```bash
|
```bash
|
||||||
# If using uvx (recommended)
|
openhands
|
||||||
uvx --python 3.12 --from openhands-ai openhands
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
If you have cloned the repository, you can also run the CLI directly using Poetry:
|
If you have cloned the repository, you can also run the CLI directly using Poetry:
|
||||||
|
|
||||||
poetry run openhands
|
poetry run python -m openhands.cli.main
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
4. Set your model, API key, and other preferences using the UI (or alternatively environment variables, below).
|
3. Set your model, API key, and other preferences using the UI (or alternatively environment variables, below).
|
||||||
|
|
||||||
This command opens an interactive prompt where you can type tasks or commands and get responses from OpenHands.
|
This command opens an interactive prompt where you can type tasks or commands and get responses from OpenHands.
|
||||||
The first time you run the CLI, it will take you through configuring the required LLM
|
The first time you run the CLI, it will take you through configuring the required LLM
|
||||||
@@ -119,7 +103,7 @@ The conversation history will be saved in `~/.openhands/sessions`.
|
|||||||
```bash
|
```bash
|
||||||
docker run -it \
|
docker run -it \
|
||||||
--pull=always \
|
--pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e SANDBOX_USER_ID=$(id -u) \
|
-e SANDBOX_USER_ID=$(id -u) \
|
||||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||||
-e LLM_API_KEY=$LLM_API_KEY \
|
-e LLM_API_KEY=$LLM_API_KEY \
|
||||||
@@ -128,7 +112,7 @@ docker run -it \
|
|||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53 \
|
docker.all-hands.dev/all-hands-ai/openhands:0.51 \
|
||||||
python -m openhands.cli.main --override-cli-mode true
|
python -m openhands.cli.main --override-cli-mode true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export GITHUB_TOKEN="your-token" # Required for repository operations
|
|||||||
# Run OpenHands
|
# Run OpenHands
|
||||||
docker run -it \
|
docker run -it \
|
||||||
--pull=always \
|
--pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e SANDBOX_USER_ID=$(id -u) \
|
-e SANDBOX_USER_ID=$(id -u) \
|
||||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||||
-e LLM_API_KEY=$LLM_API_KEY \
|
-e LLM_API_KEY=$LLM_API_KEY \
|
||||||
@@ -73,7 +73,7 @@ docker run -it \
|
|||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53 \
|
docker.all-hands.dev/all-hands-ai/openhands:0.51 \
|
||||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -68,23 +68,23 @@ Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstud
|
|||||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||||
|
|
||||||
docker run -it --rm --pull=always \
|
docker run -it --rm --pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e LOG_ALL_EVENTS=true \
|
-e LOG_ALL_EVENTS=true \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
-p 3000:3000 \
|
-p 3000:3000 \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app \
|
--name openhands-app \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Wait until the server is running (see log below):
|
2. Wait until the server is running (see log below):
|
||||||
```
|
```
|
||||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.53
|
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||||
Starting OpenHands...
|
Starting OpenHands...
|
||||||
Running OpenHands as root
|
Running OpenHands as root
|
||||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||||
|
|||||||
@@ -66,31 +66,9 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
|||||||
|
|
||||||
### Start the App
|
### Start the App
|
||||||
|
|
||||||
#### Option 1: Using the CLI Launcher with uv (Recommended)
|
#### Option 1: Using the CLI Launcher (Recommended)
|
||||||
|
|
||||||
We recommend using [uv](https://docs.astral.sh/uv/) for the best OpenHands experience. uv provides better isolation from your current project's virtual environment and is required for OpenHands' default MCP servers (like the [fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)).
|
If you have Python 3.12+ installed, you can use the CLI launcher for a simpler experience:
|
||||||
|
|
||||||
**Install uv** (if you haven't already):
|
|
||||||
|
|
||||||
See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for the latest installation instructions for your platform.
|
|
||||||
|
|
||||||
**Launch OpenHands**:
|
|
||||||
```bash
|
|
||||||
# Launch the GUI server
|
|
||||||
uvx --python 3.12 --from openhands-ai openhands serve
|
|
||||||
|
|
||||||
# Or with GPU support (requires nvidia-docker)
|
|
||||||
uvx --python 3.12 --from openhands-ai openhands serve --gpu
|
|
||||||
|
|
||||||
# Or with current directory mounted
|
|
||||||
uvx --python 3.12 --from openhands-ai openhands serve --mount-cwd
|
|
||||||
```
|
|
||||||
|
|
||||||
This will automatically handle Docker requirements checking, image pulling, and launching the GUI server. The `--gpu` flag enables GPU support via nvidia-docker, and `--mount-cwd` mounts your current directory into the container.
|
|
||||||
|
|
||||||
<Accordion title="Alternative: Traditional pip installation">
|
|
||||||
|
|
||||||
If you prefer to use pip and have Python 3.12+ installed:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install OpenHands
|
# Install OpenHands
|
||||||
@@ -98,32 +76,34 @@ pip install openhands-ai
|
|||||||
|
|
||||||
# Launch the GUI server
|
# Launch the GUI server
|
||||||
openhands serve
|
openhands serve
|
||||||
|
|
||||||
|
# Or with GPU support (requires nvidia-docker)
|
||||||
|
openhands serve --gpu
|
||||||
|
|
||||||
|
# Or with current directory mounted
|
||||||
|
openhands serve --mount-cwd
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that you'll still need `uv` installed for the default MCP servers to work properly.
|
Or using `uvx --python 3.12 --from openhands-ai openhands serve` if you have [uv](https://docs.astral.sh/uv/) installed.
|
||||||
|
|
||||||
</Accordion>
|
This will automatically handle Docker requirements checking, image pulling, and launching the GUI server. The `--gpu` flag enables GPU support via nvidia-docker, and `--mount-cwd` mounts your current directory into the container.
|
||||||
|
|
||||||
#### Option 2: Using Docker Directly
|
#### Option 2: Using Docker Directly
|
||||||
|
|
||||||
<Accordion title="Docker Command (Click to expand)">
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik
|
||||||
|
|
||||||
docker run -it --rm --pull=always \
|
docker run -it --rm --pull=always \
|
||||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.51-nikolaik \
|
||||||
-e LOG_ALL_EVENTS=true \
|
-e LOG_ALL_EVENTS=true \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
-v ~/.openhands:/.openhands \
|
-v ~/.openhands:/.openhands \
|
||||||
-p 3000:3000 \
|
-p 3000:3000 \
|
||||||
--add-host host.docker.internal:host-gateway \
|
--add-host host.docker.internal:host-gateway \
|
||||||
--name openhands-app \
|
--name openhands-app \
|
||||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
docker.all-hands.dev/all-hands-ai/openhands:0.51
|
||||||
```
|
```
|
||||||
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||||
|
|
||||||
You'll find OpenHands running at http://localhost:3000!
|
You'll find OpenHands running at http://localhost:3000!
|
||||||
|
|||||||
@@ -506,6 +506,7 @@ def commit0_setup(dataset: pd.DataFrame, repo_split: str) -> pd.DataFrame:
|
|||||||
Returns:
|
Returns:
|
||||||
Filtered dataset based on split type
|
Filtered dataset based on split type
|
||||||
"""
|
"""
|
||||||
|
|
||||||
filtered_dataset = pd.concat(
|
filtered_dataset = pd.concat(
|
||||||
[
|
[
|
||||||
dataset[dataset['repo'].str.split('/').str[1] == repo]
|
dataset[dataset['repo'].str.split('/').str[1] == repo]
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ def get_config(
|
|||||||
def get_dv_query_for_real(
|
def get_dv_query_for_real(
|
||||||
datasets, question, domain_knowledge=None, workflow_tags=None
|
datasets, question, domain_knowledge=None, workflow_tags=None
|
||||||
):
|
):
|
||||||
"""Prepare a structured query for the agent to execute on the specified datasets.
|
"""
|
||||||
|
Prepare a structured query for the agent to execute on the specified datasets.
|
||||||
|
|
||||||
This function constructs a query by compiling metadata from the provided datasets, along with any relevant domain knowledge and workflow tags.
|
This function constructs a query by compiling metadata from the provided datasets, along with any relevant domain knowledge and workflow tags.
|
||||||
|
|
||||||
@@ -103,6 +104,7 @@ def get_dv_query_for_real(
|
|||||||
query_to_dv: Query to be run on the dataset
|
query_to_dv: Query to be run on the dataset
|
||||||
dataset_meta: Metadata of the dataset
|
dataset_meta: Metadata of the dataset
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dataset_meta = ''
|
dataset_meta = ''
|
||||||
for dataset_metadata in datasets:
|
for dataset_metadata in datasets:
|
||||||
dataset_meta += 'Dataset name: ' + dataset_metadata['name']
|
dataset_meta += 'Dataset name: ' + dataset_metadata['name']
|
||||||
@@ -138,7 +140,8 @@ def get_dv_query_for_real(
|
|||||||
|
|
||||||
|
|
||||||
def initialize_runtime(runtime: Runtime, data_files: list[str]):
|
def initialize_runtime(runtime: Runtime, data_files: list[str]):
|
||||||
"""Initialize the runtime for the agent.
|
"""
|
||||||
|
Initialize the runtime for the agent.
|
||||||
|
|
||||||
This function is called before the runtime is used to run the agent.
|
This function is called before the runtime is used to run the agent.
|
||||||
"""
|
"""
|
||||||
@@ -228,7 +231,8 @@ def process_instance(
|
|||||||
metadata: EvalMetadata,
|
metadata: EvalMetadata,
|
||||||
reset_logger: bool = True,
|
reset_logger: bool = True,
|
||||||
):
|
):
|
||||||
"""Process and evaluate a single instance of the dataset.
|
"""
|
||||||
|
Process and evaluate a single instance of the dataset.
|
||||||
|
|
||||||
This function executes the OpenHands agent
|
This function executes the OpenHands agent
|
||||||
for a specific instance of the dataset. It retrieves
|
for a specific instance of the dataset. It retrieves
|
||||||
@@ -243,6 +247,7 @@ def process_instance(
|
|||||||
Returns:
|
Returns:
|
||||||
output: EvalOutput object
|
output: EvalOutput object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config = get_config(metadata)
|
config = get_config(metadata)
|
||||||
|
|
||||||
# Setup the logger properly, so you can run
|
# Setup the logger properly, so you can run
|
||||||
@@ -351,7 +356,8 @@ def list_csv_files(list_of_datasets):
|
|||||||
|
|
||||||
|
|
||||||
def create_dataset(repo_location: str, split: str = 'test'):
|
def create_dataset(repo_location: str, split: str = 'test'):
|
||||||
"""Create a dataset from the discoverybench repository
|
"""
|
||||||
|
Create a dataset from the discoverybench repository
|
||||||
by walking through the repository and extracting metadata
|
by walking through the repository and extracting metadata
|
||||||
from the metadata_{}.json files
|
from the metadata_{}.json files
|
||||||
|
|
||||||
@@ -362,6 +368,7 @@ def create_dataset(repo_location: str, split: str = 'test'):
|
|||||||
Returns:
|
Returns:
|
||||||
df: DataFrame containing the dataset instances
|
df: DataFrame containing the dataset instances
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data_dict = {}
|
data_dict = {}
|
||||||
|
|
||||||
data_location = os.path.join(repo_location, 'discoverybench', 'real', split)
|
data_location = os.path.join(repo_location, 'discoverybench', 'real', split)
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ def process_instance(
|
|||||||
log_dir: str | None = None,
|
log_dir: str | None = None,
|
||||||
runtime_failure_count: int = 0,
|
runtime_failure_count: int = 0,
|
||||||
) -> EvalOutput:
|
) -> EvalOutput:
|
||||||
"""Evaluate agent performance on a SWE-bench problem instance.
|
"""
|
||||||
|
Evaluate agent performance on a SWE-bench problem instance.
|
||||||
|
|
||||||
Note that this signature differs from the expected input to `run_evaluation`. Use
|
Note that this signature differs from the expected input to `run_evaluation`. Use
|
||||||
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
"""Utilities for handling binary files and patch generation in SWE-bench evaluation."""
|
"""
|
||||||
|
Utilities for handling binary files and patch generation in SWE-bench evaluation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def remove_binary_diffs(patch_text):
|
def remove_binary_diffs(patch_text):
|
||||||
"""Remove binary file diffs from a git patch.
|
"""
|
||||||
|
Remove binary file diffs from a git patch.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
patch_text (str): The git patch text
|
patch_text (str): The git patch text
|
||||||
@@ -33,7 +36,8 @@ def remove_binary_diffs(patch_text):
|
|||||||
|
|
||||||
|
|
||||||
def remove_binary_files_from_git():
|
def remove_binary_files_from_git():
|
||||||
"""Generate a bash command to remove binary files from git staging.
|
"""
|
||||||
|
Generate a bash command to remove binary files from git staging.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: A bash command that removes binary files from git staging
|
str: A bash command that removes binary files from git staging
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ def process_instance(
|
|||||||
runtime_failure_count: int = 0,
|
runtime_failure_count: int = 0,
|
||||||
conditional_imports: ConditionalImports | None = None,
|
conditional_imports: ConditionalImports | None = None,
|
||||||
) -> EvalOutput:
|
) -> EvalOutput:
|
||||||
"""Evaluate agent performance on a SWE-bench problem instance.
|
"""
|
||||||
|
Evaluate agent performance on a SWE-bench problem instance.
|
||||||
|
|
||||||
Note that this signature differs from the expected input to `run_evaluation`. Use
|
Note that this signature differs from the expected input to `run_evaluation`. Use
|
||||||
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ from openhands.core.logger import openhands_logger as logger
|
|||||||
|
|
||||||
class LocEvaluator:
|
class LocEvaluator:
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Localization evaluation.
|
"""
|
||||||
|
Localization evaluation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: all main arguments
|
args: all main arguments
|
||||||
@@ -75,7 +76,8 @@ class LocEvaluator:
|
|||||||
self.task_resolved = False
|
self.task_resolved = False
|
||||||
|
|
||||||
def _init_dir(self, directory_path):
|
def _init_dir(self, directory_path):
|
||||||
"""Check if a directory exists and create it if it doesn't.
|
"""
|
||||||
|
Check if a directory exists and create it if it doesn't.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
directory_path (str): Path to the directory to check/create
|
directory_path (str): Path to the directory to check/create
|
||||||
@@ -205,7 +207,8 @@ class LocEvaluator:
|
|||||||
self._compute_avg_over_all()
|
self._compute_avg_over_all()
|
||||||
|
|
||||||
def _write_to_json(self, data, file_name):
|
def _write_to_json(self, data, file_name):
|
||||||
"""Writes the current object data to a JSON file.
|
"""
|
||||||
|
Writes the current object data to a JSON file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if writing was successful, False otherwise.
|
bool: True if writing was successful, False otherwise.
|
||||||
@@ -222,7 +225,8 @@ class LocEvaluator:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def read_from_json(self, file_path):
|
def read_from_json(self, file_path):
|
||||||
"""Reads data from a JSON file and loads it into the current object.
|
"""
|
||||||
|
Reads data from a JSON file and loads it into the current object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The loaded JSON data, or an empty dict if the file doesn't exist
|
dict: The loaded JSON data, or an empty dict if the file doesn't exist
|
||||||
@@ -249,7 +253,8 @@ class LocEvaluator:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def read_from_jsonl(self, file_path):
|
def read_from_jsonl(self, file_path):
|
||||||
"""Reads data from a JSON file and loads it into the current object.
|
"""
|
||||||
|
Reads data from a JSON file and loads it into the current object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The loaded JSON data, or an empty dict if the file doesn't exist
|
dict: The loaded JSON data, or an empty dict if the file doesn't exist
|
||||||
@@ -289,7 +294,8 @@ class LocEvaluator:
|
|||||||
history_idx += 1
|
history_idx += 1
|
||||||
|
|
||||||
def _parse_string_to_dict(self, dict_string) -> dict:
|
def _parse_string_to_dict(self, dict_string) -> dict:
|
||||||
"""Convert a string representation of a dictionary to an actual dictionary.
|
"""
|
||||||
|
Convert a string representation of a dictionary to an actual dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dict_string (str): String representation of a dictionary
|
dict_string (str): String representation of a dictionary
|
||||||
@@ -322,7 +328,8 @@ class LocEvaluator:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _parse_value_from_args(self, argument_str: str, key: str) -> str:
|
def _parse_value_from_args(self, argument_str: str, key: str) -> str:
|
||||||
"""Parse a specific key's value from argument string.
|
"""
|
||||||
|
Parse a specific key's value from argument string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
argument_str (str): The argument string containing key-value pairs
|
argument_str (str): The argument string containing key-value pairs
|
||||||
@@ -400,7 +407,8 @@ class LocEvaluator:
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
def _parse_path_from_args(self, argument_str: str) -> str:
|
def _parse_path_from_args(self, argument_str: str) -> str:
|
||||||
"""Parse path from argument string.
|
"""
|
||||||
|
Parse path from argument string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
argument_str (str): The argument string containing path information
|
argument_str (str): The argument string containing path information
|
||||||
@@ -411,7 +419,8 @@ class LocEvaluator:
|
|||||||
return self._parse_value_from_args(argument_str, 'path')
|
return self._parse_value_from_args(argument_str, 'path')
|
||||||
|
|
||||||
def _parse_func_names_from_str(self, code_patch) -> list:
|
def _parse_func_names_from_str(self, code_patch) -> list:
|
||||||
"""Parse function names from the new_str code patch.
|
"""
|
||||||
|
Parse function names from the new_str code patch.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code_patch: Either a string (argument string) or already extracted new_str code
|
code_patch: Either a string (argument string) or already extracted new_str code
|
||||||
@@ -792,7 +801,8 @@ class LocEvaluator:
|
|||||||
|
|
||||||
|
|
||||||
def swe_data_loader(args):
|
def swe_data_loader(args):
|
||||||
"""Loading SWE-Bench data.
|
"""
|
||||||
|
Loading SWE-Bench data.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: Main arguments.
|
args: Main arguments.
|
||||||
@@ -824,7 +834,8 @@ def swe_data_loader(args):
|
|||||||
|
|
||||||
|
|
||||||
def infer_data_loader(args):
|
def infer_data_loader(args):
|
||||||
"""Load instance IDs.
|
"""
|
||||||
|
Load instance IDs.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: Main arguments.
|
args: Main arguments.
|
||||||
@@ -857,7 +868,8 @@ def infer_data_loader(args):
|
|||||||
|
|
||||||
|
|
||||||
def infer_cost_calculator(args):
|
def infer_cost_calculator(args):
|
||||||
"""Calculate total and average costs from metric JSON files with detailed output.
|
"""
|
||||||
|
Calculate total and average costs from metric JSON files with detailed output.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: Main arguments.
|
args: Main arguments.
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ class LocalizationInfo:
|
|||||||
hunks_per_file: dict[str, int] # File -> number of hunks
|
hunks_per_file: dict[str, int] # File -> number of hunks
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, Any]:
|
def to_dict(self) -> dict[str, Any]:
|
||||||
"""Convert LocalizationInfo to a dictionary for JSON serialization.
|
"""
|
||||||
|
Convert LocalizationInfo to a dictionary for JSON serialization.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary representation of the localization information
|
Dictionary representation of the localization information
|
||||||
@@ -57,7 +58,8 @@ class LocalizationInfo:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict[str, Any]) -> 'LocalizationInfo':
|
def from_dict(cls, data: dict[str, Any]) -> 'LocalizationInfo':
|
||||||
"""Create LocalizationInfo from a dictionary (for loading from JSON).
|
"""
|
||||||
|
Create LocalizationInfo from a dictionary (for loading from JSON).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: Dictionary containing localization information
|
data: Dictionary containing localization information
|
||||||
@@ -89,7 +91,8 @@ class LocalizationInfo:
|
|||||||
|
|
||||||
|
|
||||||
class LocMeta:
|
class LocMeta:
|
||||||
"""SWE-Bench dataset loader and ground-truth localization parser.
|
"""
|
||||||
|
SWE-Bench dataset loader and ground-truth localization parser.
|
||||||
|
|
||||||
This class handles loading SWE-Bench datasets and extracting ground-truth
|
This class handles loading SWE-Bench datasets and extracting ground-truth
|
||||||
localization information from patches for code localization evaluation.
|
localization information from patches for code localization evaluation.
|
||||||
@@ -101,7 +104,8 @@ class LocMeta:
|
|||||||
dataset_name: str = 'princeton-nlp/SWE-bench_Verified',
|
dataset_name: str = 'princeton-nlp/SWE-bench_Verified',
|
||||||
split: str = 'test',
|
split: str = 'test',
|
||||||
):
|
):
|
||||||
"""Initialize LocMeta with a SWE-Bench dataset.
|
"""
|
||||||
|
Initialize LocMeta with a SWE-Bench dataset.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dataset_name: HuggingFace dataset name (e.g., "princeton-nlp/SWE-bench_Verified")
|
dataset_name: HuggingFace dataset name (e.g., "princeton-nlp/SWE-bench_Verified")
|
||||||
@@ -120,7 +124,8 @@ class LocMeta:
|
|||||||
self._init_swe_dataset()
|
self._init_swe_dataset()
|
||||||
|
|
||||||
def _init_swe_dataset(self) -> None:
|
def _init_swe_dataset(self) -> None:
|
||||||
"""Load and initialize the SWE-Bench dataset from HuggingFace.
|
"""
|
||||||
|
Load and initialize the SWE-Bench dataset from HuggingFace.
|
||||||
Converts to pandas DataFrame for easy manipulation.
|
Converts to pandas DataFrame for easy manipulation.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@@ -145,7 +150,8 @@ class LocMeta:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def get_instance_by_id(self, instance_id: str) -> pd.Series:
|
def get_instance_by_id(self, instance_id: str) -> pd.Series:
|
||||||
"""Retrieve a specific instance by its ID.
|
"""
|
||||||
|
Retrieve a specific instance by its ID.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
instance_id: The instance identifier
|
instance_id: The instance identifier
|
||||||
@@ -163,7 +169,8 @@ class LocMeta:
|
|||||||
return self.df.iloc[idx]
|
return self.df.iloc[idx]
|
||||||
|
|
||||||
def parse_instance_loc(self, instance: Union[pd.Series, str]) -> LocalizationInfo:
|
def parse_instance_loc(self, instance: Union[pd.Series, str]) -> LocalizationInfo:
|
||||||
"""Parse ground-truth localization information from a SWE-Bench instance.
|
"""
|
||||||
|
Parse ground-truth localization information from a SWE-Bench instance.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
instance: Either a pandas Series with instance data or an instance_id string
|
instance: Either a pandas Series with instance data or an instance_id string
|
||||||
@@ -211,7 +218,8 @@ class LocMeta:
|
|||||||
def _parse_file_patch_lines(
|
def _parse_file_patch_lines(
|
||||||
self, file_patch: str
|
self, file_patch: str
|
||||||
) -> tuple[list[tuple[int, int]], int, int]:
|
) -> tuple[list[tuple[int, int]], int, int]:
|
||||||
"""Parse line ranges and count changes from a single file patch.
|
"""
|
||||||
|
Parse line ranges and count changes from a single file patch.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_patch: Patch content for a single file
|
file_patch: Patch content for a single file
|
||||||
@@ -245,7 +253,8 @@ class LocMeta:
|
|||||||
def _parse_code_structures_from_patch(
|
def _parse_code_structures_from_patch(
|
||||||
self, file_patch: str, file_path: str
|
self, file_patch: str, file_path: str
|
||||||
) -> tuple[list[str], list[str]]:
|
) -> tuple[list[str], list[str]]:
|
||||||
"""Extract function and class names from patch context (fallback method).
|
"""
|
||||||
|
Extract function and class names from patch context (fallback method).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_patch: Patch content for a single file
|
file_patch: Patch content for a single file
|
||||||
@@ -302,7 +311,8 @@ class LocMeta:
|
|||||||
def _parse_patch_localization(
|
def _parse_patch_localization(
|
||||||
self, patch_content: str, instance_id: str
|
self, patch_content: str, instance_id: str
|
||||||
) -> LocalizationInfo:
|
) -> LocalizationInfo:
|
||||||
"""Parse localization information from a git patch (improved method).
|
"""
|
||||||
|
Parse localization information from a git patch (improved method).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
patch_content: The git patch content
|
patch_content: The git patch content
|
||||||
@@ -380,7 +390,8 @@ class LocMeta:
|
|||||||
def _extract_code_structures_from_patch(
|
def _extract_code_structures_from_patch(
|
||||||
self, file_patch: str, file_path: str
|
self, file_patch: str, file_path: str
|
||||||
) -> tuple[list[str], list[str]]:
|
) -> tuple[list[str], list[str]]:
|
||||||
"""Extract function and class names from patch context and content.
|
"""
|
||||||
|
Extract function and class names from patch context and content.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_patch: Patch content for a single file
|
file_patch: Patch content for a single file
|
||||||
@@ -508,7 +519,8 @@ class LocMeta:
|
|||||||
def _parse_patch_localization_with_runtime(
|
def _parse_patch_localization_with_runtime(
|
||||||
self, patch_content: str, instance_id: str, runtime: Runtime
|
self, patch_content: str, instance_id: str, runtime: Runtime
|
||||||
) -> LocalizationInfo:
|
) -> LocalizationInfo:
|
||||||
"""Parse localization information from a git patch using OpenHands runtime.
|
"""
|
||||||
|
Parse localization information from a git patch using OpenHands runtime.
|
||||||
This is the superior method when runtime is available.
|
This is the superior method when runtime is available.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -584,7 +596,8 @@ class LocMeta:
|
|||||||
def parse_instance_loc_with_runtime(
|
def parse_instance_loc_with_runtime(
|
||||||
self, instance: Union[pd.Series, str], runtime: Runtime = None
|
self, instance: Union[pd.Series, str], runtime: Runtime = None
|
||||||
) -> LocalizationInfo:
|
) -> LocalizationInfo:
|
||||||
"""Parse ground-truth localization information using OpenHands runtime.
|
"""
|
||||||
|
Parse ground-truth localization information using OpenHands runtime.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
instance: Either a pandas Series with instance data or an instance_id string
|
instance: Either a pandas Series with instance data or an instance_id string
|
||||||
@@ -621,7 +634,8 @@ class LocMeta:
|
|||||||
def _analyze_source_code_with_runtime(
|
def _analyze_source_code_with_runtime(
|
||||||
self, runtime: Runtime, file_path: str, affected_lines: list[int]
|
self, runtime: Runtime, file_path: str, affected_lines: list[int]
|
||||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||||
"""Analyze source code using OpenHands runtime to find functions and classes.
|
"""
|
||||||
|
Analyze source code using OpenHands runtime to find functions and classes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
runtime: OpenHands runtime object
|
runtime: OpenHands runtime object
|
||||||
@@ -681,7 +695,8 @@ class LocMeta:
|
|||||||
def _parse_cython_content_with_line_mapping(
|
def _parse_cython_content_with_line_mapping(
|
||||||
self, content: str, affected_lines: list[int]
|
self, content: str, affected_lines: list[int]
|
||||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||||
"""Parse Cython content to extract functions and classes with line mapping.
|
"""
|
||||||
|
Parse Cython content to extract functions and classes with line mapping.
|
||||||
Since Cython files can't be parsed with Python's AST, we use regex-based parsing.
|
Since Cython files can't be parsed with Python's AST, we use regex-based parsing.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -813,7 +828,8 @@ class LocMeta:
|
|||||||
def _parse_python_content_with_line_mapping(
|
def _parse_python_content_with_line_mapping(
|
||||||
self, content: str, affected_lines: list[int]
|
self, content: str, affected_lines: list[int]
|
||||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||||
"""Parse Python content to extract functions and classes with accurate line mapping.
|
"""
|
||||||
|
Parse Python content to extract functions and classes with accurate line mapping.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
content: Python source code content
|
content: Python source code content
|
||||||
@@ -898,7 +914,8 @@ class LocMeta:
|
|||||||
def _parse_python_content(
|
def _parse_python_content(
|
||||||
self, content: str, affected_lines: list[int]
|
self, content: str, affected_lines: list[int]
|
||||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||||
"""Parse Python content to extract functions and classes.
|
"""
|
||||||
|
Parse Python content to extract functions and classes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
content: Python source code content
|
content: Python source code content
|
||||||
@@ -972,7 +989,8 @@ class LocMeta:
|
|||||||
return [], [], {}, {}
|
return [], [], {}, {}
|
||||||
|
|
||||||
def _split_patch_by_files(self, patch_content: str) -> dict[str, str]:
|
def _split_patch_by_files(self, patch_content: str) -> dict[str, str]:
|
||||||
"""Split a multi-file patch into individual file patches.
|
"""
|
||||||
|
Split a multi-file patch into individual file patches.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
patch_content: Complete patch content
|
patch_content: Complete patch content
|
||||||
@@ -1031,7 +1049,8 @@ class LocMeta:
|
|||||||
def _empty_localization_info(
|
def _empty_localization_info(
|
||||||
self, instance_id: str = 'unknown'
|
self, instance_id: str = 'unknown'
|
||||||
) -> LocalizationInfo:
|
) -> LocalizationInfo:
|
||||||
"""Return an empty LocalizationInfo object.
|
"""
|
||||||
|
Return an empty LocalizationInfo object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
instance_id: Instance identifier
|
instance_id: Instance identifier
|
||||||
@@ -1053,7 +1072,8 @@ class LocMeta:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_dataset_statistics(self) -> dict[str, Any]:
|
def get_dataset_statistics(self) -> dict[str, Any]:
|
||||||
"""Get statistics about the loaded dataset.
|
"""
|
||||||
|
Get statistics about the loaded dataset.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing dataset statistics
|
Dictionary containing dataset statistics
|
||||||
@@ -1075,7 +1095,8 @@ class LocMeta:
|
|||||||
return stats
|
return stats
|
||||||
|
|
||||||
def get_instances_by_repo(self, repo_name: str) -> pd.DataFrame:
|
def get_instances_by_repo(self, repo_name: str) -> pd.DataFrame:
|
||||||
"""Get all instances for a specific repository.
|
"""
|
||||||
|
Get all instances for a specific repository.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
repo_name: Repository name (e.g., "django/django")
|
repo_name: Repository name (e.g., "django/django")
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from openhands.core.logger import openhands_logger as logger
|
|||||||
|
|
||||||
|
|
||||||
def verify_instance_costs(row: pd.Series) -> float:
|
def verify_instance_costs(row: pd.Series) -> float:
|
||||||
"""Verifies that the accumulated_cost matches the sum of individual costs in metrics.
|
"""
|
||||||
|
Verifies that the accumulated_cost matches the sum of individual costs in metrics.
|
||||||
Also checks for duplicate consecutive costs which might indicate buggy counting.
|
Also checks for duplicate consecutive costs which might indicate buggy counting.
|
||||||
If the consecutive costs are identical, the file is affected by this bug:
|
If the consecutive costs are identical, the file is affected by this bug:
|
||||||
https://github.com/All-Hands-AI/OpenHands/issues/5383
|
https://github.com/All-Hands-AI/OpenHands/issues/5383
|
||||||
|
|||||||
@@ -181,7 +181,9 @@ def distinct_methods_stats(tree, num_lines):
|
|||||||
|
|
||||||
|
|
||||||
def loops_stats(tree, num_lines):
|
def loops_stats(tree, num_lines):
|
||||||
"""Calculate the average number of loops."""
|
"""
|
||||||
|
Calculate the average number of loops.
|
||||||
|
"""
|
||||||
total_loops = 0
|
total_loops = 0
|
||||||
|
|
||||||
def traverse(node):
|
def traverse(node):
|
||||||
@@ -197,7 +199,9 @@ def loops_stats(tree, num_lines):
|
|||||||
|
|
||||||
|
|
||||||
def branches_stats(tree, num_lines):
|
def branches_stats(tree, num_lines):
|
||||||
"""Calculate the average number of branches (conditional statements)."""
|
"""
|
||||||
|
Calculate the average number of branches (conditional statements).
|
||||||
|
"""
|
||||||
total_branches = 0
|
total_branches = 0
|
||||||
|
|
||||||
def traverse(node):
|
def traverse(node):
|
||||||
|
|||||||
@@ -192,7 +192,8 @@ def run_mutation_testing(
|
|||||||
def grade_test_output(
|
def grade_test_output(
|
||||||
test_suite: str, instance: pd.Series, test_output: str, test_spec: TestSpec, runtime
|
test_suite: str, instance: pd.Series, test_output: str, test_spec: TestSpec, runtime
|
||||||
):
|
):
|
||||||
"""Two-pass test grading with short-circuiting:
|
"""
|
||||||
|
Two-pass test grading with short-circuiting:
|
||||||
1. Run all tests to identify passing/failing tests
|
1. Run all tests to identify passing/failing tests
|
||||||
2. If no failing tests, evaluate coverage immediately
|
2. If no failing tests, evaluate coverage immediately
|
||||||
3. Otherwise, run only passing tests for coverage analysis
|
3. Otherwise, run only passing tests for coverage analysis
|
||||||
@@ -279,7 +280,8 @@ def process_instance(
|
|||||||
reset_logger: bool = True,
|
reset_logger: bool = True,
|
||||||
log_dir: str | None = None,
|
log_dir: str | None = None,
|
||||||
) -> EvalOutput:
|
) -> EvalOutput:
|
||||||
"""Evaluate agent performance on a TestGenEval problem instance.
|
"""
|
||||||
|
Evaluate agent performance on a TestGenEval problem instance.
|
||||||
|
|
||||||
Note that this signature differs from the expected input to `run_evaluation`. Use
|
Note that this signature differs from the expected input to `run_evaluation`. Use
|
||||||
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
||||||
@@ -451,7 +453,8 @@ def process_instance(
|
|||||||
|
|
||||||
|
|
||||||
def count_and_log_fields(evaluated_predictions, fields, key):
|
def count_and_log_fields(evaluated_predictions, fields, key):
|
||||||
"""Count and log the sum of specified fields in the evaluated predictions,
|
"""
|
||||||
|
Count and log the sum of specified fields in the evaluated predictions,
|
||||||
ignoring fields with a value of -1. If all values for a field are -1,
|
ignoring fields with a value of -1. If all values for a field are -1,
|
||||||
return -1.
|
return -1.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from evaluation.benchmarks.testgeneval.constants import TestStatus
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_pytest(log: str) -> dict[str, str]:
|
def parse_log_pytest(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with PyTest framework
|
"""
|
||||||
|
Parser for test logs generated with PyTest framework
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
@@ -25,7 +26,8 @@ def parse_log_pytest(log: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_pytest_options(log: str) -> dict[str, str]:
|
def parse_log_pytest_options(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with PyTest framework with options
|
"""
|
||||||
|
Parser for test logs generated with PyTest framework with options
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
@@ -59,7 +61,8 @@ def parse_log_pytest_options(log: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_django(log: str) -> dict[str, str]:
|
def parse_log_django(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with Django tester framework
|
"""
|
||||||
|
Parser for test logs generated with Django tester framework
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
@@ -138,7 +141,8 @@ def parse_log_django(log: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_pytest_v2(log: str) -> dict[str, str]:
|
def parse_log_pytest_v2(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with PyTest framework (Later Version)
|
"""
|
||||||
|
Parser for test logs generated with PyTest framework (Later Version)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
@@ -166,7 +170,8 @@ def parse_log_pytest_v2(log: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_seaborn(log: str) -> dict[str, str]:
|
def parse_log_seaborn(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with seaborn testing framework
|
"""
|
||||||
|
Parser for test logs generated with seaborn testing framework
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
@@ -191,7 +196,8 @@ def parse_log_seaborn(log: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_sympy(log: str) -> dict[str, str]:
|
def parse_log_sympy(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with Sympy framework
|
"""
|
||||||
|
Parser for test logs generated with Sympy framework
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
@@ -223,7 +229,8 @@ def parse_log_sympy(log: str) -> dict[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def parse_log_matplotlib(log: str) -> dict[str, str]:
|
def parse_log_matplotlib(log: str) -> dict[str, str]:
|
||||||
"""Parser for test logs generated with PyTest framework
|
"""
|
||||||
|
Parser for test logs generated with PyTest framework
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log (str): log content
|
log (str): log content
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ if sys.getrecursionlimit() < 10_000:
|
|||||||
|
|
||||||
|
|
||||||
def bleu(gold: list[str], pred: list[str]) -> float:
|
def bleu(gold: list[str], pred: list[str]) -> float:
|
||||||
"""Calculate BLEU score, using smoothing method 2 with auto reweighting, in the range of 0~100.
|
"""
|
||||||
|
Calculate BLEU score, using smoothing method 2 with auto reweighting, in the range of 0~100.
|
||||||
|
|
||||||
:param gold: list of gold tokens
|
:param gold: list of gold tokens
|
||||||
:param pred: list of predicted tokens
|
:param pred: list of predicted tokens
|
||||||
@@ -29,7 +30,8 @@ def bleu(gold: list[str], pred: list[str]) -> float:
|
|||||||
|
|
||||||
|
|
||||||
def batch_bleu(golds: list[list[str]], preds: list[list[str]]) -> list[float]:
|
def batch_bleu(golds: list[list[str]], preds: list[list[str]]) -> list[float]:
|
||||||
"""Calculate BLEU score for a batch of sentences.
|
"""
|
||||||
|
Calculate BLEU score for a batch of sentences.
|
||||||
|
|
||||||
:param golds: list of gold sentences
|
:param golds: list of gold sentences
|
||||||
:param preds: list of predicted sentences
|
:param preds: list of predicted sentences
|
||||||
@@ -41,7 +43,8 @@ def batch_bleu(golds: list[list[str]], preds: list[list[str]]) -> list[float]:
|
|||||||
|
|
||||||
|
|
||||||
def corpus_bleu(golds: list[list[str]], preds: list[list[str]]) -> float:
|
def corpus_bleu(golds: list[list[str]], preds: list[list[str]]) -> float:
|
||||||
"""Calculate corpus-level BLEU score for a batch of sentences.
|
"""
|
||||||
|
Calculate corpus-level BLEU score for a batch of sentences.
|
||||||
|
|
||||||
:param golds: list of gold sentences
|
:param golds: list of gold sentences
|
||||||
:param preds: list of predicted sentences
|
:param preds: list of predicted sentences
|
||||||
@@ -60,7 +63,8 @@ def corpus_bleu(golds: list[list[str]], preds: list[list[str]]) -> float:
|
|||||||
def edit_sim(
|
def edit_sim(
|
||||||
gold: Union[str, list[str]], pred: Union[str, list[str]], sep: str = ' '
|
gold: Union[str, list[str]], pred: Union[str, list[str]], sep: str = ' '
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Calculate char-level edit similarity, in the range of 0~100.
|
"""
|
||||||
|
Calculate char-level edit similarity, in the range of 0~100.
|
||||||
|
|
||||||
:param gold: gold sentence or list of gold tokens
|
:param gold: gold sentence or list of gold tokens
|
||||||
:param pred: predicted sentence or list of predicted tokens
|
:param pred: predicted sentence or list of predicted tokens
|
||||||
@@ -81,7 +85,8 @@ def batch_edit_sim(
|
|||||||
preds: list[Union[str, list[str]]],
|
preds: list[Union[str, list[str]]],
|
||||||
sep: str = ' ',
|
sep: str = ' ',
|
||||||
) -> list[float]:
|
) -> list[float]:
|
||||||
"""Calculate char-level edit similarity for a batch of sentences.
|
"""
|
||||||
|
Calculate char-level edit similarity for a batch of sentences.
|
||||||
|
|
||||||
:param golds: list of gold sentences
|
:param golds: list of gold sentences
|
||||||
:param preds: list of predicted sentences
|
:param preds: list of predicted sentences
|
||||||
@@ -97,7 +102,8 @@ T = TypeVar('T')
|
|||||||
|
|
||||||
|
|
||||||
def exact_match(gold: T, pred: T) -> float:
|
def exact_match(gold: T, pred: T) -> float:
|
||||||
"""Calculate exact match accuracy, in the range of {0, 100}.
|
"""
|
||||||
|
Calculate exact match accuracy, in the range of {0, 100}.
|
||||||
|
|
||||||
:param gold: gold sentence or list of gold tokens
|
:param gold: gold sentence or list of gold tokens
|
||||||
:param pred: predicted sentence or list of predicted tokens
|
:param pred: predicted sentence or list of predicted tokens
|
||||||
@@ -109,7 +115,8 @@ def exact_match(gold: T, pred: T) -> float:
|
|||||||
|
|
||||||
|
|
||||||
def batch_exact_match(golds: list[T], preds: list[T]) -> list[float]:
|
def batch_exact_match(golds: list[T], preds: list[T]) -> list[float]:
|
||||||
"""Calculate exact match accuracy for a batch of sentences.
|
"""
|
||||||
|
Calculate exact match accuracy for a batch of sentences.
|
||||||
|
|
||||||
:param golds: list of gold sentences
|
:param golds: list of gold sentences
|
||||||
:param preds: list of predicted sentences
|
:param preds: list of predicted sentences
|
||||||
@@ -123,7 +130,8 @@ def batch_exact_match(golds: list[T], preds: list[T]) -> list[float]:
|
|||||||
def rouge_l(
|
def rouge_l(
|
||||||
gold: Union[str, list[str]], pred: Union[str, list[str]], sep: str = ' '
|
gold: Union[str, list[str]], pred: Union[str, list[str]], sep: str = ' '
|
||||||
) -> dict[str, float]:
|
) -> dict[str, float]:
|
||||||
"""Calculate ROUGE-L F1, precision, and recall scores, in the range of 0~100.
|
"""
|
||||||
|
Calculate ROUGE-L F1, precision, and recall scores, in the range of 0~100.
|
||||||
|
|
||||||
:param gold: gold sentence or list of gold tokens
|
:param gold: gold sentence or list of gold tokens
|
||||||
:param pred: predicted sentence or list of predicted tokens
|
:param pred: predicted sentence or list of predicted tokens
|
||||||
@@ -148,7 +156,8 @@ def batch_rouge_l(
|
|||||||
preds: list[Union[str, list[str]]],
|
preds: list[Union[str, list[str]]],
|
||||||
sep: str = ' ',
|
sep: str = ' ',
|
||||||
) -> dict[str, list[float]]:
|
) -> dict[str, list[float]]:
|
||||||
"""Calculate ROUGE-L F1, precision, and recall scores for a batch of sentences.
|
"""
|
||||||
|
Calculate ROUGE-L F1, precision, and recall scores for a batch of sentences.
|
||||||
|
|
||||||
:param golds: list of gold sentences
|
:param golds: list of gold sentences
|
||||||
:param preds: list of predicted sentences
|
:param preds: list of predicted sentences
|
||||||
@@ -166,7 +175,8 @@ def accuracy(
|
|||||||
pred: list[str],
|
pred: list[str],
|
||||||
ignore: Optional[Sequence[str]] = None,
|
ignore: Optional[Sequence[str]] = None,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Calculate token-level accuracy, in the range of 0~100.
|
"""
|
||||||
|
Calculate token-level accuracy, in the range of 0~100.
|
||||||
If gold and pred are not the same length, the longer one would be truncated.
|
If gold and pred are not the same length, the longer one would be truncated.
|
||||||
|
|
||||||
:param gold: list of gold tokens
|
:param gold: list of gold tokens
|
||||||
@@ -200,7 +210,8 @@ def batch_accuracy(
|
|||||||
preds: list[list[str]],
|
preds: list[list[str]],
|
||||||
ignore: Optional[Sequence[str]] = None,
|
ignore: Optional[Sequence[str]] = None,
|
||||||
) -> list[float]:
|
) -> list[float]:
|
||||||
"""Calculate token-level accuracy for a batch of sentences.
|
"""
|
||||||
|
Calculate token-level accuracy for a batch of sentences.
|
||||||
|
|
||||||
:param golds: list of gold sentences
|
:param golds: list of gold sentences
|
||||||
:param preds: list of predicted sentences
|
:param preds: list of predicted sentences
|
||||||
@@ -215,7 +226,8 @@ def batch_accuracy(
|
|||||||
def first_match_to_topk(
|
def first_match_to_topk(
|
||||||
first_match_list: list[int], k_values: list[int]
|
first_match_list: list[int], k_values: list[int]
|
||||||
) -> dict[int, list[float]]:
|
) -> dict[int, list[float]]:
|
||||||
"""Calculate top-k accuracy with the first match ranks (1-indexed).
|
"""
|
||||||
|
Calculate top-k accuracy with the first match ranks (1-indexed).
|
||||||
|
|
||||||
:param first_match: first match ranks (1-indexed)
|
:param first_match: first match ranks (1-indexed)
|
||||||
:param k_values: k values to consider
|
:param k_values: k values to consider
|
||||||
@@ -225,7 +237,8 @@ def first_match_to_topk(
|
|||||||
|
|
||||||
|
|
||||||
def pass_at_k(n: int, c: int, k: int) -> float:
|
def pass_at_k(n: int, c: int, k: int) -> float:
|
||||||
"""Sample pass@k metric according to the Codex paper, but in the scale of 0~100.
|
"""
|
||||||
|
Sample pass@k metric according to the Codex paper, but in the scale of 0~100.
|
||||||
:param n: total number of samples
|
:param n: total number of samples
|
||||||
:param c: number of correct samples
|
:param c: number of correct samples
|
||||||
:param k: k in pass@$k$
|
:param k: k in pass@$k$
|
||||||
@@ -238,7 +251,8 @@ def pass_at_k(n: int, c: int, k: int) -> float:
|
|||||||
|
|
||||||
|
|
||||||
def self_bleu(samples: list[list[str]]) -> float:
|
def self_bleu(samples: list[list[str]]) -> float:
|
||||||
"""Calculate self-BLEU among the samples.
|
"""
|
||||||
|
Calculate self-BLEU among the samples.
|
||||||
:param samples: the chosen m samples
|
:param samples: the chosen m samples
|
||||||
:return: self-BLEU
|
:return: self-BLEU
|
||||||
"""
|
"""
|
||||||
@@ -260,7 +274,8 @@ def self_bleu(samples: list[list[str]]) -> float:
|
|||||||
|
|
||||||
|
|
||||||
def self_edit_distance(samples: list[Union[str, list[str]]], sep=' ') -> float:
|
def self_edit_distance(samples: list[Union[str, list[str]]], sep=' ') -> float:
|
||||||
"""Calculate self-edit-distance among the samples.
|
"""
|
||||||
|
Calculate self-edit-distance among the samples.
|
||||||
:param samples: the chosen m samples
|
:param samples: the chosen m samples
|
||||||
:param sep: the separator between tokens
|
:param sep: the separator between tokens
|
||||||
:return: self-edit-distance
|
:return: self-edit-distance
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ def check_mutation(mutation_output):
|
|||||||
|
|
||||||
|
|
||||||
def count_methods(code_str):
|
def count_methods(code_str):
|
||||||
"""Counts the number of methods/functions in a given string of code.
|
"""
|
||||||
|
Counts the number of methods/functions in a given string of code.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code_str (str): A string containing code.
|
code_str (str): A string containing code.
|
||||||
@@ -45,7 +46,8 @@ def count_methods(code_str):
|
|||||||
|
|
||||||
|
|
||||||
def get_lines_of_code(code_str):
|
def get_lines_of_code(code_str):
|
||||||
"""Extracts lines of code from a given string.
|
"""
|
||||||
|
Extracts lines of code from a given string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code_str (str): A string containing code.
|
code_str (str): A string containing code.
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import traceback
|
|||||||
|
|
||||||
|
|
||||||
def insert_line_in_string(input_string, new_str, insert_line):
|
def insert_line_in_string(input_string, new_str, insert_line):
|
||||||
"""Inserts a new line into a string at the specified line number.
|
"""
|
||||||
|
Inserts a new line into a string at the specified line number.
|
||||||
|
|
||||||
:param input_string: The original string.
|
:param input_string: The original string.
|
||||||
:param new_str: The string to insert.
|
:param new_str: The string to insert.
|
||||||
@@ -28,7 +29,8 @@ def insert_line_in_string(input_string, new_str, insert_line):
|
|||||||
|
|
||||||
|
|
||||||
def print_string_diff(original, modified):
|
def print_string_diff(original, modified):
|
||||||
"""Prints the differences between two strings line by line.
|
"""
|
||||||
|
Prints the differences between two strings line by line.
|
||||||
|
|
||||||
:param original: The original string.
|
:param original: The original string.
|
||||||
:param modified: The modified string.
|
:param modified: The modified string.
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ def extract_preamble_classes_and_functions(code):
|
|||||||
current_position = 0
|
current_position = 0
|
||||||
|
|
||||||
def extract_class_body(code: str, start_index: int) -> tuple[str, int]:
|
def extract_class_body(code: str, start_index: int) -> tuple[str, int]:
|
||||||
"""Extracts the body of a class from the given code starting from the specified index.
|
"""
|
||||||
|
Extracts the body of a class from the given code starting from the specified index.
|
||||||
Returns the class body and the end index of the class body.
|
Returns the class body and the end index of the class body.
|
||||||
"""
|
"""
|
||||||
if not code or start_index < 0 or start_index >= len(code):
|
if not code or start_index < 0 or start_index >= len(code):
|
||||||
@@ -167,8 +168,8 @@ def extract_preamble_classes_and_functions(code):
|
|||||||
def filter_passing_tests(
|
def filter_passing_tests(
|
||||||
test_content: str, test_output: str, repo: str
|
test_content: str, test_output: str, repo: str
|
||||||
) -> tuple[str, list[str], list[str]]:
|
) -> tuple[str, list[str], list[str]]:
|
||||||
"""Filter tests based on their execution results.
|
"""
|
||||||
|
Filter tests based on their execution results.
|
||||||
Returns:
|
Returns:
|
||||||
Tuple containing:
|
Tuple containing:
|
||||||
- Modified test content with only passing tests
|
- Modified test content with only passing tests
|
||||||
@@ -245,7 +246,8 @@ def filter_passing_tests(
|
|||||||
def filter_tests(
|
def filter_tests(
|
||||||
test_content: str, test_output: str, repo: str
|
test_content: str, test_output: str, repo: str
|
||||||
) -> tuple[str, list[str], list[str]]:
|
) -> tuple[str, list[str], list[str]]:
|
||||||
"""Filter tests using AST parsing to remove failing test functions from the test file.
|
"""
|
||||||
|
Filter tests using AST parsing to remove failing test functions from the test file.
|
||||||
Non-test functions (e.g. setup or helper methods) and classes (even if all test methods are failing)
|
Non-test functions (e.g. setup or helper methods) and classes (even if all test methods are failing)
|
||||||
are preserved.
|
are preserved.
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ DIFF_MODIFIED_FILE_REGEX = r'--- a/(.*)'
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TestSpec:
|
class TestSpec:
|
||||||
"""A dataclass that represents a test specification for a single instance of SWE-bench."""
|
"""
|
||||||
|
A dataclass that represents a test specification for a single instance of SWE-bench.
|
||||||
|
"""
|
||||||
|
|
||||||
instance_id: str
|
instance_id: str
|
||||||
id: str
|
id: str
|
||||||
@@ -84,7 +86,10 @@ def make_test_setup(specs, env_name, repo_directory, includes_tox=False):
|
|||||||
|
|
||||||
|
|
||||||
def make_test_script_list(test_cmd, specs, env_name, repo_directory):
|
def make_test_script_list(test_cmd, specs, env_name, repo_directory):
|
||||||
"""Runs the tests."""
|
"""
|
||||||
|
Runs the tests.
|
||||||
|
"""
|
||||||
|
|
||||||
includes_tox = 'tox' in test_cmd
|
includes_tox = 'tox' in test_cmd
|
||||||
eval_commands = make_test_setup(specs, env_name, repo_directory, includes_tox)
|
eval_commands = make_test_setup(specs, env_name, repo_directory, includes_tox)
|
||||||
eval_commands += [
|
eval_commands += [
|
||||||
@@ -99,7 +104,10 @@ def make_test_script_list(test_cmd, specs, env_name, repo_directory):
|
|||||||
|
|
||||||
|
|
||||||
def make_mutation_script_list(specs, env_name, repo_directory, mutation_timeout):
|
def make_mutation_script_list(specs, env_name, repo_directory, mutation_timeout):
|
||||||
"""Runs the tests."""
|
"""
|
||||||
|
Runs the tests.
|
||||||
|
"""
|
||||||
|
|
||||||
eval_commands = make_test_setup(specs, env_name, repo_directory)
|
eval_commands = make_test_setup(specs, env_name, repo_directory)
|
||||||
eval_commands += [
|
eval_commands += [
|
||||||
'cosmic-ray init mutation.toml mutation.sqlite',
|
'cosmic-ray init mutation.toml mutation.sqlite',
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from evaluation.benchmarks.testgeneval.constants import (
|
|||||||
|
|
||||||
|
|
||||||
def get_test_directives(instance: TestGenEvalInstance) -> list:
|
def get_test_directives(instance: TestGenEvalInstance) -> list:
|
||||||
"""Get test directives from the test_patch of a task instance
|
"""
|
||||||
|
Get test directives from the test_patch of a task instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
instance (dict): task instance
|
instance (dict): task instance
|
||||||
@@ -42,7 +43,9 @@ def get_test_directives(instance: TestGenEvalInstance) -> list:
|
|||||||
def load_testgeneval_dataset(
|
def load_testgeneval_dataset(
|
||||||
name='kjain14/testgeneval', split='test', ids=None
|
name='kjain14/testgeneval', split='test', ids=None
|
||||||
) -> list[TestGenEvalInstance]:
|
) -> list[TestGenEvalInstance]:
|
||||||
"""Load SWE-bench dataset from Hugging Face Datasets or local .json/.jsonl file"""
|
"""
|
||||||
|
Load SWE-bench dataset from Hugging Face Datasets or local .json/.jsonl file
|
||||||
|
"""
|
||||||
# check that all instance IDs are in the dataset
|
# check that all instance IDs are in the dataset
|
||||||
if ids:
|
if ids:
|
||||||
ids = set(ids)
|
ids = set(ids)
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ class ActionType(Enum):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Selector:
|
class Selector:
|
||||||
"""Represents either a direct anchor ID or a descriptive selector"""
|
"""
|
||||||
|
Represents either a direct anchor ID or a descriptive selector
|
||||||
|
"""
|
||||||
|
|
||||||
value: str
|
value: str
|
||||||
is_anchor: bool = False
|
is_anchor: bool = False
|
||||||
@@ -147,7 +149,8 @@ def find_matching_anchor(content: str, selector: str) -> str | None:
|
|||||||
|
|
||||||
|
|
||||||
def resolve_action(action: BrowserAction, content: str) -> BrowserAction:
|
def resolve_action(action: BrowserAction, content: str) -> BrowserAction:
|
||||||
"""Resolve any descriptive selectors in the action to anchor IDs based on the content.
|
"""
|
||||||
|
Resolve any descriptive selectors in the action to anchor IDs based on the content.
|
||||||
Returns a new action with resolved selectors.
|
Returns a new action with resolved selectors.
|
||||||
"""
|
"""
|
||||||
if isinstance(action, (InputAction, ClickAction)):
|
if isinstance(action, (InputAction, ClickAction)):
|
||||||
@@ -171,7 +174,8 @@ def pre_login(
|
|||||||
save_screenshots=True,
|
save_screenshots=True,
|
||||||
screenshots_dir='screenshots',
|
screenshots_dir='screenshots',
|
||||||
):
|
):
|
||||||
"""Logs in to all the websites that are needed for the evaluation.
|
"""
|
||||||
|
Logs in to all the websites that are needed for the evaluation.
|
||||||
Once logged in, the sessions would be cached in the browser, so OpenHands
|
Once logged in, the sessions would be cached in the browser, so OpenHands
|
||||||
agent doesn't need to log in to these websites again.
|
agent doesn't need to log in to these websites again.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ def get_config(
|
|||||||
|
|
||||||
|
|
||||||
def load_dependencies(runtime: Runtime) -> list[str]:
|
def load_dependencies(runtime: Runtime) -> list[str]:
|
||||||
"""Every task has a dependencies.yml file, which lists all the services that the
|
"""
|
||||||
|
Every task has a dependencies.yml file, which lists all the services that the
|
||||||
task depends on. This function loads the file and returns all dependent service names.
|
task depends on. This function loads the file and returns all dependent service names.
|
||||||
"""
|
"""
|
||||||
command = 'cat /utils/dependencies.yml'
|
command = 'cat /utils/dependencies.yml'
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
def calculate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float:
|
def calculate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float:
|
||||||
"""Calculate the cost of the model call."""
|
"""
|
||||||
|
Calculate the cost of the model call.
|
||||||
|
"""
|
||||||
if 'claude-3-5-sonnet' in model.lower():
|
if 'claude-3-5-sonnet' in model.lower():
|
||||||
# https://www.anthropic.com/pricing#anthropic-api, accessed 12/11/2024
|
# https://www.anthropic.com/pricing#anthropic-api, accessed 12/11/2024
|
||||||
return 0.000003 * prompt_tokens + 0.000015 * completion_tokens
|
return 0.000003 * prompt_tokens + 0.000015 * completion_tokens
|
||||||
@@ -58,7 +60,8 @@ def calculate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> fl
|
|||||||
|
|
||||||
|
|
||||||
def analyze_eval_json_file(filepath: str) -> tuple[int, int]:
|
def analyze_eval_json_file(filepath: str) -> tuple[int, int]:
|
||||||
"""Analyze a single eval JSON file and extract the total and result from final_score.
|
"""
|
||||||
|
Analyze a single eval JSON file and extract the total and result from final_score.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filepath: Path to the JSON file
|
filepath: Path to the JSON file
|
||||||
@@ -81,7 +84,8 @@ def analyze_eval_json_file(filepath: str) -> tuple[int, int]:
|
|||||||
|
|
||||||
|
|
||||||
def analyze_traj_json_file(filepath: str) -> tuple[int, float]:
|
def analyze_traj_json_file(filepath: str) -> tuple[int, float]:
|
||||||
"""Analyze a single trajectory JSON file and extract the steps and tokens
|
"""
|
||||||
|
Analyze a single trajectory JSON file and extract the steps and tokens
|
||||||
for each step. Then estimate the cost based on the tokens and the model type.
|
for each step. Then estimate the cost based on the tokens and the model type.
|
||||||
Note: this is assuming there's no prompt caching at all.
|
Note: this is assuming there's no prompt caching at all.
|
||||||
"""
|
"""
|
||||||
@@ -111,7 +115,8 @@ def analyze_traj_json_file(filepath: str) -> tuple[int, float]:
|
|||||||
def analyze_folder(
|
def analyze_folder(
|
||||||
folder_path: str,
|
folder_path: str,
|
||||||
) -> tuple[dict[str, tuple[int, int]], dict[str, tuple[int, float]]]:
|
) -> tuple[dict[str, tuple[int, int]], dict[str, tuple[int, float]]]:
|
||||||
"""Analyze all eval_*.json & traj_*.json files in the specified folder.
|
"""
|
||||||
|
Analyze all eval_*.json & traj_*.json files in the specified folder.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
folder_path: Path to the folder containing JSON files
|
folder_path: Path to the folder containing JSON files
|
||||||
@@ -143,7 +148,9 @@ def analyze_folder(
|
|||||||
|
|
||||||
|
|
||||||
def get_task_nature_category(task_name: str) -> str:
|
def get_task_nature_category(task_name: str) -> str:
|
||||||
"""Get the nature category of the task."""
|
"""
|
||||||
|
Get the nature category of the task.
|
||||||
|
"""
|
||||||
task_nature = task_name.split('-')[0]
|
task_nature = task_name.split('-')[0]
|
||||||
if task_nature.lower() in ['sde', 'pm', 'ds', 'admin', 'hr', 'finance']:
|
if task_nature.lower() in ['sde', 'pm', 'ds', 'admin', 'hr', 'finance']:
|
||||||
return task_nature
|
return task_nature
|
||||||
@@ -152,7 +159,8 @@ def get_task_nature_category(task_name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def calculate_score(total: int, result: int) -> float:
|
def calculate_score(total: int, result: int) -> float:
|
||||||
"""Calculate the score as a number between 0 and 1.
|
"""
|
||||||
|
Calculate the score as a number between 0 and 1.
|
||||||
|
|
||||||
Formula: score = (result / total) * 0.5 + (result // total) * 0.5
|
Formula: score = (result / total) * 0.5 + (result // total) * 0.5
|
||||||
Explanation:
|
Explanation:
|
||||||
@@ -170,7 +178,8 @@ def calculate_score(total: int, result: int) -> float:
|
|||||||
|
|
||||||
|
|
||||||
def is_perfect_completion(total: int, result: int) -> bool:
|
def is_perfect_completion(total: int, result: int) -> bool:
|
||||||
"""Check if the task achieved perfect completion.
|
"""
|
||||||
|
Check if the task achieved perfect completion.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
total: Total possible points
|
total: Total possible points
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""GPT performs line level generation prediction and truncates overly long tokens"""
|
"""
|
||||||
|
GPT performs line level generation prediction and truncates overly long tokens
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -54,7 +56,8 @@ def predict(content, model_name):
|
|||||||
|
|
||||||
|
|
||||||
def bulid_prompt(description, old_version, old_code, new_version) -> str:
|
def bulid_prompt(description, old_version, old_code, new_version) -> str:
|
||||||
"""Build prompt
|
"""
|
||||||
|
build prompt
|
||||||
:param version:
|
:param version:
|
||||||
:param description:
|
:param description:
|
||||||
:param masked_code:
|
:param masked_code:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""GPT performs line level generation prediction and truncates overly long tokens"""
|
"""
|
||||||
|
GPT performs line level generation prediction and truncates overly long tokens
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -54,7 +56,8 @@ def predict(content, model_name):
|
|||||||
|
|
||||||
|
|
||||||
def bulid_prompt(version, description) -> str:
|
def bulid_prompt(version, description) -> str:
|
||||||
"""Build prompt
|
"""
|
||||||
|
build prompt
|
||||||
:param version:
|
:param version:
|
||||||
:param description:
|
:param description:
|
||||||
:param masked_code:
|
:param masked_code:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""block completion"""
|
"""
|
||||||
|
block completion
|
||||||
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import gc
|
import gc
|
||||||
@@ -77,7 +79,8 @@ def run_inference(model_name, origin_data_list):
|
|||||||
|
|
||||||
|
|
||||||
def bulid_prompt(version, description) -> str:
|
def bulid_prompt(version, description) -> str:
|
||||||
"""Build prompt
|
"""
|
||||||
|
build prompt
|
||||||
:param version:
|
:param version:
|
||||||
:param description:
|
:param description:
|
||||||
:param masked_code:
|
:param masked_code:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""code migration"""
|
"""
|
||||||
|
code migration
|
||||||
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import gc
|
import gc
|
||||||
@@ -79,7 +81,8 @@ def run_inference(model_name, origin_data_list):
|
|||||||
|
|
||||||
|
|
||||||
def bulid_prompt(description, old_version, old_code, new_version) -> str:
|
def bulid_prompt(description, old_version, old_code, new_version) -> str:
|
||||||
"""Build prompt
|
"""
|
||||||
|
build prompt
|
||||||
:param version:
|
:param version:
|
||||||
:param description:
|
:param description:
|
||||||
:param masked_code:
|
:param masked_code:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""评测block的预测能力
|
"""
|
||||||
|
评测block的预测能力
|
||||||
1、判断是否包含正确的函数名
|
1、判断是否包含正确的函数名
|
||||||
2、判断是否合法
|
2、判断是否合法
|
||||||
3、计算ISM,和PM
|
3、计算ISM,和PM
|
||||||
@@ -21,7 +22,8 @@ def is_code_valid(code):
|
|||||||
|
|
||||||
|
|
||||||
def longest_common_prefix_between_lists_with_elements(list1, list2):
|
def longest_common_prefix_between_lists_with_elements(list1, list2):
|
||||||
"""计算两个字符串列表中元素的最长前缀匹配长度
|
"""
|
||||||
|
计算两个字符串列表中元素的最长前缀匹配长度
|
||||||
:param list1:
|
:param list1:
|
||||||
:param list2:
|
:param list2:
|
||||||
:return:
|
:return:
|
||||||
@@ -44,7 +46,8 @@ def longest_common_prefix_between_lists_with_elements(list1, list2):
|
|||||||
|
|
||||||
|
|
||||||
def get_token(ans_code: str, output_code: str):
|
def get_token(ans_code: str, output_code: str):
|
||||||
"""对代码进行词法分析,分解成标识符,返回两个标识符列表
|
"""
|
||||||
|
对代码进行词法分析,分解成标识符,返回两个标识符列表
|
||||||
:param ans_code:
|
:param ans_code:
|
||||||
:param output_code:
|
:param output_code:
|
||||||
:return:
|
:return:
|
||||||
@@ -91,7 +94,8 @@ def get_token(ans_code: str, output_code: str):
|
|||||||
|
|
||||||
|
|
||||||
def get_token_per_line(code: str):
|
def get_token_per_line(code: str):
|
||||||
"""对每一行代码进行词法分析,记录每一行的标识符
|
"""
|
||||||
|
对每一行代码进行词法分析,记录每一行的标识符
|
||||||
:param code: 代码字符串
|
:param code: 代码字符串
|
||||||
:return: 每一行的标识符列表组成的列表
|
:return: 每一行的标识符列表组成的列表
|
||||||
"""
|
"""
|
||||||
@@ -113,7 +117,8 @@ def get_token_per_line(code: str):
|
|||||||
|
|
||||||
|
|
||||||
def get_ISM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
def get_ISM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
||||||
"""计算ISM,返回一个有序的得分列表
|
"""
|
||||||
|
计算ISM,返回一个有序的得分列表
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
score_list = []
|
score_list = []
|
||||||
@@ -152,7 +157,8 @@ def get_ISM(answer_code: str, model_output_list: list, answer_name: str) -> list
|
|||||||
def get_ISM_without_verification(
|
def get_ISM_without_verification(
|
||||||
answer_code: str, model_output_list: list, answer_name: str
|
answer_code: str, model_output_list: list, answer_name: str
|
||||||
) -> list:
|
) -> list:
|
||||||
"""计算ISM,返回一个有序的得分列表
|
"""
|
||||||
|
计算ISM,返回一个有序的得分列表
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
score_list = []
|
score_list = []
|
||||||
@@ -184,7 +190,8 @@ def get_ISM_without_verification(
|
|||||||
|
|
||||||
|
|
||||||
def longest_common_prefix_with_lengths(list1, list2):
|
def longest_common_prefix_with_lengths(list1, list2):
|
||||||
"""计算两个二维列表中每个子列表的最长前缀匹配长度,并记录拥有最长前缀匹配长度的两个子列表的长度
|
"""
|
||||||
|
计算两个二维列表中每个子列表的最长前缀匹配长度,并记录拥有最长前缀匹配长度的两个子列表的长度
|
||||||
:param list1: 第一个二维列表
|
:param list1: 第一个二维列表
|
||||||
:param list2: 第二个二维列表
|
:param list2: 第二个二维列表
|
||||||
:return: 最长前缀匹配长度以及拥有最长前缀匹配长度的两个子列表的长度
|
:return: 最长前缀匹配长度以及拥有最长前缀匹配长度的两个子列表的长度
|
||||||
@@ -209,7 +216,8 @@ def longest_common_prefix_with_lengths(list1, list2):
|
|||||||
|
|
||||||
|
|
||||||
def get_PM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
def get_PM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
||||||
"""计算PM,返回一个有序的得分列表
|
"""
|
||||||
|
计算PM,返回一个有序的得分列表
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
score_list = []
|
score_list = []
|
||||||
@@ -246,7 +254,8 @@ def get_PM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
|||||||
|
|
||||||
|
|
||||||
def get_score(score_list: list, k):
|
def get_score(score_list: list, k):
|
||||||
"""计算score@n,k
|
"""
|
||||||
|
计算score@n,k
|
||||||
:param score_list:
|
:param score_list:
|
||||||
:param k:
|
:param k:
|
||||||
:return:
|
:return:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Calculate the cdc score for migration"""
|
"""
|
||||||
|
Calculate the cdc score for migration
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
@@ -9,7 +11,8 @@ import re
|
|||||||
|
|
||||||
|
|
||||||
def is_correct_parameter_count(function_name, correct_code, test_code):
|
def is_correct_parameter_count(function_name, correct_code, test_code):
|
||||||
"""判断参数数量是否一致
|
"""
|
||||||
|
判断参数数量是否一致
|
||||||
:param function_name:
|
:param function_name:
|
||||||
:param correct_code:
|
:param correct_code:
|
||||||
:param test_code:
|
:param test_code:
|
||||||
@@ -40,7 +43,8 @@ def is_correct_parameter_count(function_name, correct_code, test_code):
|
|||||||
|
|
||||||
|
|
||||||
def check_keyword_parameters(function_name, correct_code, test_code):
|
def check_keyword_parameters(function_name, correct_code, test_code):
|
||||||
"""判断关键词参数赋值是否正确使用
|
"""
|
||||||
|
判断关键词参数赋值是否正确使用
|
||||||
:param function_name:
|
:param function_name:
|
||||||
:param correct_code:
|
:param correct_code:
|
||||||
:param test_code:
|
:param test_code:
|
||||||
@@ -78,7 +82,8 @@ def check_keyword_parameters(function_name, correct_code, test_code):
|
|||||||
|
|
||||||
|
|
||||||
def with_correct(answer_code: str, model_output: str) -> bool:
|
def with_correct(answer_code: str, model_output: str) -> bool:
|
||||||
"""当answer是with结构时,判断模型生成的是不是with结构
|
"""
|
||||||
|
当answer是with结构时,判断模型生成的是不是with结构
|
||||||
:param answer_code:
|
:param answer_code:
|
||||||
:param model_output:
|
:param model_output:
|
||||||
:return:
|
:return:
|
||||||
@@ -100,7 +105,9 @@ def compute_block_score_k(
|
|||||||
core_line_in_core_block,
|
core_line_in_core_block,
|
||||||
core_line_in_output_clear,
|
core_line_in_output_clear,
|
||||||
):
|
):
|
||||||
"""cdc需要满足五个条件,em只需要满足第一个条件"""
|
"""
|
||||||
|
cdc需要满足五个条件,em只需要满足第一个条件
|
||||||
|
"""
|
||||||
c = 0
|
c = 0
|
||||||
n = len(model_output)
|
n = len(model_output)
|
||||||
for index, code in enumerate(model_output):
|
for index, code in enumerate(model_output):
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Calculate the cdc score for line and block"""
|
"""
|
||||||
|
Calculate the cdc score for line and block
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
@@ -17,7 +19,8 @@ def is_code_valid(code):
|
|||||||
|
|
||||||
|
|
||||||
def is_correct_parameter_count(function_name, correct_code, test_code):
|
def is_correct_parameter_count(function_name, correct_code, test_code):
|
||||||
"""判断参数数量是否一致
|
"""
|
||||||
|
判断参数数量是否一致
|
||||||
:param function_name:
|
:param function_name:
|
||||||
:param correct_code:
|
:param correct_code:
|
||||||
:param test_code:
|
:param test_code:
|
||||||
@@ -48,7 +51,8 @@ def is_correct_parameter_count(function_name, correct_code, test_code):
|
|||||||
|
|
||||||
|
|
||||||
def check_keyword_parameters(function_name, correct_code, test_code):
|
def check_keyword_parameters(function_name, correct_code, test_code):
|
||||||
"""判断关键词参数赋值是否正确使用
|
"""
|
||||||
|
判断关键词参数赋值是否正确使用
|
||||||
:param function_name:
|
:param function_name:
|
||||||
:param correct_code:
|
:param correct_code:
|
||||||
:param test_code:
|
:param test_code:
|
||||||
@@ -86,7 +90,8 @@ def check_keyword_parameters(function_name, correct_code, test_code):
|
|||||||
|
|
||||||
|
|
||||||
def with_correct(answer_code: str, model_output: str) -> bool:
|
def with_correct(answer_code: str, model_output: str) -> bool:
|
||||||
"""当answer是with结构时,判断模型生成的是不是with结构
|
"""
|
||||||
|
当answer是with结构时,判断模型生成的是不是with结构
|
||||||
:param answer_code:
|
:param answer_code:
|
||||||
:param model_output:
|
:param model_output:
|
||||||
:return:
|
:return:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Calculate the cdc score for line and block"""
|
"""
|
||||||
|
Calculate the cdc score for line and block
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
@@ -17,7 +19,8 @@ def is_code_valid(code):
|
|||||||
|
|
||||||
|
|
||||||
def is_correct_parameter_count(function_name, correct_code, test_code):
|
def is_correct_parameter_count(function_name, correct_code, test_code):
|
||||||
"""判断参数数量是否一致
|
"""
|
||||||
|
判断参数数量是否一致
|
||||||
:param function_name:
|
:param function_name:
|
||||||
:param correct_code:
|
:param correct_code:
|
||||||
:param test_code:
|
:param test_code:
|
||||||
@@ -48,7 +51,8 @@ def is_correct_parameter_count(function_name, correct_code, test_code):
|
|||||||
|
|
||||||
|
|
||||||
def check_keyword_parameters(function_name, correct_code, test_code):
|
def check_keyword_parameters(function_name, correct_code, test_code):
|
||||||
"""判断关键词参数赋值是否正确使用
|
"""
|
||||||
|
判断关键词参数赋值是否正确使用
|
||||||
:param function_name:
|
:param function_name:
|
||||||
:param correct_code:
|
:param correct_code:
|
||||||
:param test_code:
|
:param test_code:
|
||||||
@@ -86,7 +90,8 @@ def check_keyword_parameters(function_name, correct_code, test_code):
|
|||||||
|
|
||||||
|
|
||||||
def with_correct(answer_code: str, model_output: str) -> bool:
|
def with_correct(answer_code: str, model_output: str) -> bool:
|
||||||
"""当answer是with结构时,判断模型生成的是不是with结构
|
"""
|
||||||
|
当answer是with结构时,判断模型生成的是不是with结构
|
||||||
:param answer_code:
|
:param answer_code:
|
||||||
:param model_output:
|
:param model_output:
|
||||||
:return:
|
:return:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Find the line of code generated by the model using the block in the version code"""
|
"""
|
||||||
|
Find the line of code generated by the model using the block in the version code
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Find the line of code generated by the model using the block in the version code"""
|
"""
|
||||||
|
Find the line of code generated by the model using the block in the version code
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Clear the<start>and<end>generated by the model in inference"""
|
"""
|
||||||
|
Clear the<start>and<end>generated by the model in inference
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|||||||
@@ -622,7 +622,8 @@ def compatibility_for_eval_history_pairs(
|
|||||||
|
|
||||||
|
|
||||||
def is_fatal_evaluation_error(error: str | None) -> bool:
|
def is_fatal_evaluation_error(error: str | None) -> bool:
|
||||||
"""The AgentController class overrides last error for certain exceptions
|
"""
|
||||||
|
The AgentController class overrides last error for certain exceptions
|
||||||
We want to ensure those exeption do not overlap with fatal exceptions defined here
|
We want to ensure those exeption do not overlap with fatal exceptions defined here
|
||||||
This is because we do a comparisino against the stringified error
|
This is because we do a comparisino against the stringified error
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
||||||
import { renderHook } from "@testing-library/react";
|
|
||||||
import { useDocumentTitleFromState } from "#/hooks/use-document-title-from-state";
|
|
||||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
|
||||||
|
|
||||||
// Mock the useActiveConversation hook
|
|
||||||
vi.mock("#/hooks/query/use-active-conversation");
|
|
||||||
|
|
||||||
const mockUseActiveConversation = vi.mocked(useActiveConversation);
|
|
||||||
|
|
||||||
describe("useDocumentTitleFromState", () => {
|
|
||||||
const originalTitle = document.title;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
document.title = "Test";
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
document.title = originalTitle;
|
|
||||||
vi.resetAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set document title to default suffix when no conversation", () => {
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: null,
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
renderHook(() => useDocumentTitleFromState());
|
|
||||||
|
|
||||||
expect(document.title).toBe("OpenHands");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set document title to custom suffix when no conversation", () => {
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: null,
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
renderHook(() => useDocumentTitleFromState("Custom App"));
|
|
||||||
|
|
||||||
expect(document.title).toBe("Custom App");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set document title with conversation title", () => {
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: {
|
|
||||||
conversation_id: "123",
|
|
||||||
title: "My Conversation",
|
|
||||||
status: "RUNNING",
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
renderHook(() => useDocumentTitleFromState());
|
|
||||||
|
|
||||||
expect(document.title).toBe("My Conversation | OpenHands");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should update document title when conversation title changes", () => {
|
|
||||||
// Initial state - no conversation
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: null,
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const { rerender } = renderHook(() => useDocumentTitleFromState());
|
|
||||||
expect(document.title).toBe("OpenHands");
|
|
||||||
|
|
||||||
// Conversation with initial title
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: {
|
|
||||||
conversation_id: "123",
|
|
||||||
title: "Conversation 65e29",
|
|
||||||
status: "RUNNING",
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
rerender();
|
|
||||||
expect(document.title).toBe("Conversation 65e29 | OpenHands");
|
|
||||||
|
|
||||||
// Conversation title updated to human-readable title
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: {
|
|
||||||
conversation_id: "123",
|
|
||||||
title: "Help me build a React app",
|
|
||||||
status: "RUNNING",
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
rerender();
|
|
||||||
expect(document.title).toBe("Help me build a React app | OpenHands");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle conversation without title", () => {
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: {
|
|
||||||
conversation_id: "123",
|
|
||||||
title: undefined,
|
|
||||||
status: "RUNNING",
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
renderHook(() => useDocumentTitleFromState());
|
|
||||||
|
|
||||||
expect(document.title).toBe("OpenHands");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty conversation title", () => {
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: {
|
|
||||||
conversation_id: "123",
|
|
||||||
title: "",
|
|
||||||
status: "RUNNING",
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
renderHook(() => useDocumentTitleFromState());
|
|
||||||
|
|
||||||
expect(document.title).toBe("OpenHands");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reset document title on cleanup", () => {
|
|
||||||
mockUseActiveConversation.mockReturnValue({
|
|
||||||
data: {
|
|
||||||
conversation_id: "123",
|
|
||||||
title: "My Conversation",
|
|
||||||
status: "RUNNING",
|
|
||||||
},
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const { unmount } = renderHook(() => useDocumentTitleFromState());
|
|
||||||
|
|
||||||
expect(document.title).toBe("My Conversation | OpenHands");
|
|
||||||
|
|
||||||
unmount();
|
|
||||||
|
|
||||||
expect(document.title).toBe("OpenHands");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { screen } from "@testing-library/react";
|
import { screen } from "@testing-library/react";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import i18n from "../../src/i18n";
|
import i18n from "../../src/i18n";
|
||||||
import { AccountSettingsContextMenu } from "../../src/components/features/context-menu/account-settings-context-menu";
|
import { AccountSettingsContextMenu } from "../../src/components/features/context-menu/account-settings-context-menu";
|
||||||
import { renderWithProviders } from "../../test-utils";
|
import { renderWithProviders } from "../../test-utils";
|
||||||
@@ -17,63 +17,4 @@ describe("Translations", () => {
|
|||||||
screen.getByTestId("account-settings-context-menu"),
|
screen.getByTestId("account-settings-context-menu"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not attempt to load unsupported language codes", async () => {
|
|
||||||
// Test that the configuration prevents 404 errors by not attempting to load
|
|
||||||
// unsupported language codes like 'en-US@posix'
|
|
||||||
const originalLanguage = i18n.language;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// With nonExplicitSupportedLngs: false, i18next will not attempt to load
|
|
||||||
// unsupported language codes, preventing 404 errors
|
|
||||||
|
|
||||||
// Test with a language code that includes region but is not in supportedLngs
|
|
||||||
await i18n.changeLanguage("en-US@posix");
|
|
||||||
|
|
||||||
// Since "en-US@posix" is not in supportedLngs and nonExplicitSupportedLngs is false,
|
|
||||||
// i18next should fall back to the fallbackLng ("en")
|
|
||||||
expect(i18n.language).toBe("en");
|
|
||||||
|
|
||||||
// Test another unsupported region code
|
|
||||||
await i18n.changeLanguage("ja-JP");
|
|
||||||
|
|
||||||
// Even with nonExplicitSupportedLngs: false, i18next still falls back to base language
|
|
||||||
// if it exists in supportedLngs, but importantly, it won't make a 404 request first
|
|
||||||
expect(i18n.language).toBe("ja");
|
|
||||||
|
|
||||||
// Test that supported languages still work
|
|
||||||
await i18n.changeLanguage("ja");
|
|
||||||
expect(i18n.language).toBe("ja");
|
|
||||||
|
|
||||||
await i18n.changeLanguage("zh-CN");
|
|
||||||
expect(i18n.language).toBe("zh-CN");
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// Restore the original language
|
|
||||||
await i18n.changeLanguage(originalLanguage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have proper i18n configuration", () => {
|
|
||||||
// Test that the i18n instance has the expected configuration
|
|
||||||
expect(i18n.options.supportedLngs).toBeDefined();
|
|
||||||
|
|
||||||
// nonExplicitSupportedLngs should be false to prevent 404 errors
|
|
||||||
expect(i18n.options.nonExplicitSupportedLngs).toBe(false);
|
|
||||||
|
|
||||||
// fallbackLng can be a string or array, check if it includes "en"
|
|
||||||
const fallbackLng = i18n.options.fallbackLng;
|
|
||||||
if (Array.isArray(fallbackLng)) {
|
|
||||||
expect(fallbackLng).toContain("en");
|
|
||||||
} else {
|
|
||||||
expect(fallbackLng).toBe("en");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that supported languages include both base and region-specific codes
|
|
||||||
const supportedLngs = i18n.options.supportedLngs as string[];
|
|
||||||
expect(supportedLngs).toContain("en");
|
|
||||||
expect(supportedLngs).toContain("zh-CN");
|
|
||||||
expect(supportedLngs).toContain("zh-TW");
|
|
||||||
expect(supportedLngs).toContain("ko-KR");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "openhands-frontend",
|
"name": "openhands-frontend",
|
||||||
"version": "0.53.0",
|
"version": "0.51.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "openhands-frontend",
|
"name": "openhands-frontend",
|
||||||
"version": "0.53.0",
|
"version": "0.51.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "^2.8.2",
|
"@heroui/react": "^2.8.2",
|
||||||
"@heroui/use-infinite-scroll": "^2.2.10",
|
"@heroui/use-infinite-scroll": "^2.2.10",
|
||||||
@@ -6114,60 +6114,6 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.0.2",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
|
||||||
"version": "1.4.3",
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
|
||||||
"version": "0.2.11",
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/core": "^1.4.3",
|
|
||||||
"@emnapi/runtime": "^1.4.3",
|
|
||||||
"@tybys/wasm-util": "^0.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
|
||||||
"version": "0.9.0",
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
|
||||||
"version": "2.8.0",
|
|
||||||
"inBundle": true,
|
|
||||||
"license": "0BSD",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openhands-frontend",
|
"name": "openhands-frontend",
|
||||||
"version": "0.53.0",
|
"version": "0.51.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function ChatMessage({
|
|||||||
onMouseEnter={() => setIsHovering(true)}
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
onMouseLeave={() => setIsHovering(false)}
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-xl relative w-fit max-w-full",
|
"rounded-xl relative w-fit",
|
||||||
"flex flex-col gap-2",
|
"flex flex-col gap-2",
|
||||||
type === "user" && " p-4 bg-tertiary self-end",
|
type === "user" && " p-4 bg-tertiary self-end",
|
||||||
type === "agent" && "mt-6 max-w-full bg-transparent",
|
type === "agent" && "mt-6 max-w-full bg-transparent",
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { Lock } from "lucide-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ContextMenu } from "./context-menu";
|
import { ContextMenu } from "./context-menu";
|
||||||
import { ContextMenuListItem } from "./context-menu-list-item";
|
import { ContextMenuListItem } from "./context-menu-list-item";
|
||||||
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
|
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
import { ContextMenuIconText } from "./context-menu-icon-text";
|
|
||||||
|
|
||||||
interface AccountSettingsContextMenuProps {
|
interface AccountSettingsContextMenuProps {
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
@@ -25,10 +23,7 @@ export function AccountSettingsContextMenu({
|
|||||||
className="absolute right-full md:left-full -top-1 z-10 w-fit"
|
className="absolute right-full md:left-full -top-1 z-10 w-fit"
|
||||||
>
|
>
|
||||||
<ContextMenuListItem onClick={onLogout} data-testid="logout-button">
|
<ContextMenuListItem onClick={onLogout} data-testid="logout-button">
|
||||||
<ContextMenuIconText
|
{t(I18nKey.ACCOUNT_SETTINGS$LOGOUT)}
|
||||||
icon={Lock}
|
|
||||||
text={t(I18nKey.ACCOUNT_SETTINGS$LOGOUT)}
|
|
||||||
/>
|
|
||||||
</ContextMenuListItem>
|
</ContextMenuListItem>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export function ConnectToProviderMessage() {
|
|||||||
<Link
|
<Link
|
||||||
data-testid="navigate-to-settings-button"
|
data-testid="navigate-to-settings-button"
|
||||||
to="/settings/integrations"
|
to="/settings/integrations"
|
||||||
className="self-start"
|
|
||||||
>
|
>
|
||||||
<BrandButton type="button" variant="primary" isDisabled={isLoading}>
|
<BrandButton type="button" variant="primary" isDisabled={isLoading}>
|
||||||
{!isLoading && t("SETTINGS$TITLE")}
|
{!isLoading && t("SETTINGS$TITLE")}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation, Trans } from "react-i18next";
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
import { MCPConfig } from "#/types/settings";
|
import { MCPConfig } from "#/types/settings";
|
||||||
import { I18nKey } from "#/i18n/declaration";
|
import { I18nKey } from "#/i18n/declaration";
|
||||||
@@ -11,11 +11,6 @@ interface MCPJsonEditorProps {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MCP_DEFAULT_CONFIG: MCPConfig = {
|
|
||||||
sse_servers: [],
|
|
||||||
stdio_servers: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function MCPJsonEditor({
|
export function MCPJsonEditor({
|
||||||
mcpConfig,
|
mcpConfig,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -25,17 +20,10 @@ export function MCPJsonEditor({
|
|||||||
const [configText, setConfigText] = useState(() =>
|
const [configText, setConfigText] = useState(() =>
|
||||||
mcpConfig
|
mcpConfig
|
||||||
? JSON.stringify(mcpConfig, null, 2)
|
? JSON.stringify(mcpConfig, null, 2)
|
||||||
: JSON.stringify(MCP_DEFAULT_CONFIG, null, 2),
|
: t(I18nKey.SETTINGS$MCP_DEFAULT_CONFIG),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
textareaRef.current?.focus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setConfigText(e.target.value);
|
setConfigText(e.target.value);
|
||||||
};
|
};
|
||||||
@@ -101,7 +89,6 @@ export function MCPJsonEditor({
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full h-64 resize-y p-2 rounded-sm text-sm font-mono",
|
"w-full h-64 resize-y p-2 rounded-sm text-sm font-mono",
|
||||||
"bg-tertiary border border-[#717888]",
|
"bg-tertiary border border-[#717888]",
|
||||||
@@ -131,7 +118,7 @@ export function MCPJsonEditor({
|
|||||||
{t(I18nKey.BUTTON$CANCEL)}
|
{t(I18nKey.BUTTON$CANCEL)}
|
||||||
</BrandButton>
|
</BrandButton>
|
||||||
<BrandButton type="button" variant="primary" onClick={handleSave}>
|
<BrandButton type="button" variant="primary" onClick={handleSave}>
|
||||||
{t(I18nKey.SETTINGS$MCP_PREVIEW_CHANGES)}
|
{t(I18nKey.SETTINGS$MCP_CONFIRM_CHANGES)}
|
||||||
</BrandButton>
|
</BrandButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -100,17 +100,6 @@ export function ConfigureModal({
|
|||||||
}
|
}
|
||||||
}, [isOpen, existingWorkspace, isWorkspaceEditable]);
|
}, [isOpen, existingWorkspace, isWorkspaceEditable]);
|
||||||
|
|
||||||
// Helper function to get platform-specific placeholder
|
|
||||||
const getWorkspacePlaceholder = () => {
|
|
||||||
if (platform === "jira") {
|
|
||||||
return I18nKey.PROJECT_MANAGEMENT$JIRA_WORKSPACE_NAME_PLACEHOLDER;
|
|
||||||
}
|
|
||||||
if (platform === "jira-dc") {
|
|
||||||
return I18nKey.PROJECT_MANAGEMENT$JIRA_DC_WORKSPACE_NAME_PLACEHOLDER;
|
|
||||||
}
|
|
||||||
return I18nKey.PROJECT_MANAGEMENT$LINEAR_WORKSPACE_NAME_PLACEHOLDER;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validation states
|
// Validation states
|
||||||
const [workspaceError, setWorkspaceError] = useState<string | null>(null);
|
const [workspaceError, setWorkspaceError] = useState<string | null>(null);
|
||||||
const [webhookSecretError, setWebhookSecretError] = useState<string | null>(
|
const [webhookSecretError, setWebhookSecretError] = useState<string | null>(
|
||||||
@@ -279,11 +268,8 @@ export function ConfigureModal({
|
|||||||
<BaseModalDescription>
|
<BaseModalDescription>
|
||||||
{showConfigurationFields ? (
|
{showConfigurationFields ? (
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey={
|
i18nKey={I18nKey.PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION}
|
||||||
I18nKey.PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_2
|
|
||||||
}
|
|
||||||
components={{
|
components={{
|
||||||
b: <b />,
|
|
||||||
a: (
|
a: (
|
||||||
<a
|
<a
|
||||||
href="https://docs.all-hands.dev/usage/cloud/openhands-cloud"
|
href="https://docs.all-hands.dev/usage/cloud/openhands-cloud"
|
||||||
@@ -294,41 +280,41 @@ export function ConfigureModal({
|
|||||||
Check the document for more information
|
Check the document for more information
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
|
b: <b />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Trans
|
<p className="mt-4">
|
||||||
i18nKey={
|
<Trans
|
||||||
I18nKey.PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_1
|
i18nKey={
|
||||||
}
|
I18nKey.PROJECT_MANAGEMENT$IMPORTANT_WORKSPACE_INTEGRATION
|
||||||
components={{
|
}
|
||||||
b: <b />,
|
components={{
|
||||||
a: (
|
b: <b />,
|
||||||
<a
|
a: (
|
||||||
href="https://docs.all-hands.dev/usage/cloud/openhands-cloud"
|
<a
|
||||||
target="_blank"
|
href="https://docs.all-hands.dev/usage/cloud/openhands-cloud"
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
className="text-blue-500 underline"
|
rel="noopener noreferrer"
|
||||||
>
|
className="text-blue-500 underline"
|
||||||
Check the document for more information
|
>
|
||||||
</a>
|
Check the document for more information
|
||||||
),
|
</a>
|
||||||
}}
|
),
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="mt-4">
|
|
||||||
{t(I18nKey.PROJECT_MANAGEMENT$WORKSPACE_NAME_HINT, {
|
|
||||||
platform: platformName,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</BaseModalDescription>
|
</BaseModalDescription>
|
||||||
<div className="w-full flex flex-col gap-4 mt-1">
|
<div className="w-full flex flex-col gap-4 mt-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex gap-2 items-end">
|
<div className="flex gap-2 items-end">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<SettingsInput
|
<SettingsInput
|
||||||
label={t(I18nKey.PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL)}
|
label={t(I18nKey.PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL)}
|
||||||
placeholder={t(getWorkspacePlaceholder())}
|
placeholder={t(
|
||||||
|
I18nKey.PROJECT_MANAGEMENT$WORKSPACE_NAME_PLACEHOLDER,
|
||||||
|
)}
|
||||||
value={workspace}
|
value={workspace}
|
||||||
onChange={handleWorkspaceChange}
|
onChange={handleWorkspaceChange}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -432,7 +418,7 @@ export function ConfigureModal({
|
|||||||
>
|
>
|
||||||
{(() => {
|
{(() => {
|
||||||
if (existingWorkspace && showConfigurationFields) {
|
if (existingWorkspace && showConfigurationFields) {
|
||||||
return t(I18nKey.PROJECT_MANAGEMENT$UPDATE_BUTTON_LABEL);
|
return t(I18nKey.PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL);
|
||||||
}
|
}
|
||||||
return t(I18nKey.PROJECT_MANAGEMENT$CONNECT_BUTTON_LABEL);
|
return t(I18nKey.PROJECT_MANAGEMENT$CONNECT_BUTTON_LABEL);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
@@ -22,5 +22,5 @@ export function useDocumentTitleFromState(suffix = "OpenHands") {
|
|||||||
return () => {
|
return () => {
|
||||||
document.title = suffix;
|
document.title = suffix;
|
||||||
};
|
};
|
||||||
}, [conversation?.title, suffix]);
|
}, [conversation, suffix]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export enum I18nKey {
|
|||||||
SETTINGS$NAV_MCP = "SETTINGS$NAV_MCP",
|
SETTINGS$NAV_MCP = "SETTINGS$NAV_MCP",
|
||||||
SETTINGS$MCP_CONFIGURATION = "SETTINGS$MCP_CONFIGURATION",
|
SETTINGS$MCP_CONFIGURATION = "SETTINGS$MCP_CONFIGURATION",
|
||||||
SETTINGS$MCP_EDIT_CONFIGURATION = "SETTINGS$MCP_EDIT_CONFIGURATION",
|
SETTINGS$MCP_EDIT_CONFIGURATION = "SETTINGS$MCP_EDIT_CONFIGURATION",
|
||||||
SETTINGS$MCP_PREVIEW_CHANGES = "SETTINGS$MCP_PREVIEW_CHANGES",
|
SETTINGS$MCP_CONFIRM_CHANGES = "SETTINGS$MCP_CONFIRM_CHANGES",
|
||||||
SETTINGS$MCP_CONFIG_DESCRIPTION = "SETTINGS$MCP_CONFIG_DESCRIPTION",
|
SETTINGS$MCP_CONFIG_DESCRIPTION = "SETTINGS$MCP_CONFIG_DESCRIPTION",
|
||||||
SETTINGS$MCP_CONFIG_ERROR = "SETTINGS$MCP_CONFIG_ERROR",
|
SETTINGS$MCP_CONFIG_ERROR = "SETTINGS$MCP_CONFIG_ERROR",
|
||||||
SETTINGS$MCP_CONFIG_EXAMPLE = "SETTINGS$MCP_CONFIG_EXAMPLE",
|
SETTINGS$MCP_CONFIG_EXAMPLE = "SETTINGS$MCP_CONFIG_EXAMPLE",
|
||||||
@@ -71,6 +71,7 @@ export enum I18nKey {
|
|||||||
SETTINGS$MCP_ERROR_SSE_URL = "SETTINGS$MCP_ERROR_SSE_URL",
|
SETTINGS$MCP_ERROR_SSE_URL = "SETTINGS$MCP_ERROR_SSE_URL",
|
||||||
SETTINGS$MCP_ERROR_STDIO_PROPS = "SETTINGS$MCP_ERROR_STDIO_PROPS",
|
SETTINGS$MCP_ERROR_STDIO_PROPS = "SETTINGS$MCP_ERROR_STDIO_PROPS",
|
||||||
SETTINGS$MCP_ERROR_INVALID_JSON = "SETTINGS$MCP_ERROR_INVALID_JSON",
|
SETTINGS$MCP_ERROR_INVALID_JSON = "SETTINGS$MCP_ERROR_INVALID_JSON",
|
||||||
|
SETTINGS$MCP_DEFAULT_CONFIG = "SETTINGS$MCP_DEFAULT_CONFIG",
|
||||||
HOME$CONNECT_PROVIDER_MESSAGE = "HOME$CONNECT_PROVIDER_MESSAGE",
|
HOME$CONNECT_PROVIDER_MESSAGE = "HOME$CONNECT_PROVIDER_MESSAGE",
|
||||||
HOME$LETS_START_BUILDING = "HOME$LETS_START_BUILDING",
|
HOME$LETS_START_BUILDING = "HOME$LETS_START_BUILDING",
|
||||||
HOME$OPENHANDS_DESCRIPTION = "HOME$OPENHANDS_DESCRIPTION",
|
HOME$OPENHANDS_DESCRIPTION = "HOME$OPENHANDS_DESCRIPTION",
|
||||||
@@ -749,15 +750,8 @@ export enum I18nKey {
|
|||||||
PROJECT_MANAGEMENT$LINK_CONFIRMATION_TITLE = "PROJECT_MANAGEMENT$LINK_CONFIRMATION_TITLE",
|
PROJECT_MANAGEMENT$LINK_CONFIRMATION_TITLE = "PROJECT_MANAGEMENT$LINK_CONFIRMATION_TITLE",
|
||||||
PROJECT_MANAGEMENT$CONFIGURE_BUTTON_LABEL = "PROJECT_MANAGEMENT$CONFIGURE_BUTTON_LABEL",
|
PROJECT_MANAGEMENT$CONFIGURE_BUTTON_LABEL = "PROJECT_MANAGEMENT$CONFIGURE_BUTTON_LABEL",
|
||||||
PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL = "PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL",
|
PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL = "PROJECT_MANAGEMENT$EDIT_BUTTON_LABEL",
|
||||||
PROJECT_MANAGEMENT$UPDATE_BUTTON_LABEL = "PROJECT_MANAGEMENT$UPDATE_BUTTON_LABEL",
|
|
||||||
PROJECT_MANAGEMENT$CONFIGURE_MODAL_TITLE = "PROJECT_MANAGEMENT$CONFIGURE_MODAL_TITLE",
|
|
||||||
PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_1 = "PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_1",
|
|
||||||
PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_2 = "PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_2",
|
|
||||||
PROJECT_MANAGEMENT$WORKSPACE_NAME_HINT = "PROJECT_MANAGEMENT$WORKSPACE_NAME_HINT",
|
|
||||||
PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL = "PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL",
|
PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL = "PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL",
|
||||||
PROJECT_MANAGEMENT$JIRA_WORKSPACE_NAME_PLACEHOLDER = "PROJECT_MANAGEMENT$JIRA_WORKSPACE_NAME_PLACEHOLDER",
|
PROJECT_MANAGEMENT$WORKSPACE_NAME_PLACEHOLDER = "PROJECT_MANAGEMENT$WORKSPACE_NAME_PLACEHOLDER",
|
||||||
PROJECT_MANAGEMENT$JIRA_DC_WORKSPACE_NAME_PLACEHOLDER = "PROJECT_MANAGEMENT$JIRA_DC_WORKSPACE_NAME_PLACEHOLDER",
|
|
||||||
PROJECT_MANAGEMENT$LINEAR_WORKSPACE_NAME_PLACEHOLDER = "PROJECT_MANAGEMENT$LINEAR_WORKSPACE_NAME_PLACEHOLDER",
|
|
||||||
PROJECT_MANAGEMENT$WEBHOOK_SECRET_LABEL = "PROJECT_MANAGEMENT$WEBHOOK_SECRET_LABEL",
|
PROJECT_MANAGEMENT$WEBHOOK_SECRET_LABEL = "PROJECT_MANAGEMENT$WEBHOOK_SECRET_LABEL",
|
||||||
PROJECT_MANAGEMENT$WEBHOOK_SECRET_PLACEHOLDER = "PROJECT_MANAGEMENT$WEBHOOK_SECRET_PLACEHOLDER",
|
PROJECT_MANAGEMENT$WEBHOOK_SECRET_PLACEHOLDER = "PROJECT_MANAGEMENT$WEBHOOK_SECRET_PLACEHOLDER",
|
||||||
PROJECT_MANAGEMENT$SERVICE_ACCOUNT_EMAIL_LABEL = "PROJECT_MANAGEMENT$SERVICE_ACCOUNT_EMAIL_LABEL",
|
PROJECT_MANAGEMENT$SERVICE_ACCOUNT_EMAIL_LABEL = "PROJECT_MANAGEMENT$SERVICE_ACCOUNT_EMAIL_LABEL",
|
||||||
@@ -766,6 +760,9 @@ export enum I18nKey {
|
|||||||
PROJECT_MANAGEMENT$SERVICE_ACCOUNT_API_PLACEHOLDER = "PROJECT_MANAGEMENT$SERVICE_ACCOUNT_API_PLACEHOLDER",
|
PROJECT_MANAGEMENT$SERVICE_ACCOUNT_API_PLACEHOLDER = "PROJECT_MANAGEMENT$SERVICE_ACCOUNT_API_PLACEHOLDER",
|
||||||
PROJECT_MANAGEMENT$CONNECT_BUTTON_LABEL = "PROJECT_MANAGEMENT$CONNECT_BUTTON_LABEL",
|
PROJECT_MANAGEMENT$CONNECT_BUTTON_LABEL = "PROJECT_MANAGEMENT$CONNECT_BUTTON_LABEL",
|
||||||
PROJECT_MANAGEMENT$ACTIVE_TOGGLE_LABEL = "PROJECT_MANAGEMENT$ACTIVE_TOGGLE_LABEL",
|
PROJECT_MANAGEMENT$ACTIVE_TOGGLE_LABEL = "PROJECT_MANAGEMENT$ACTIVE_TOGGLE_LABEL",
|
||||||
|
PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION = "PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION",
|
||||||
|
PROJECT_MANAGEMENT$CONFIGURE_MODAL_TITLE = "PROJECT_MANAGEMENT$CONFIGURE_MODAL_TITLE",
|
||||||
|
PROJECT_MANAGEMENT$IMPORTANT_WORKSPACE_INTEGRATION = "PROJECT_MANAGEMENT$IMPORTANT_WORKSPACE_INTEGRATION",
|
||||||
PROJECT_MANAGEMENT$WORKSPACE_NAME_VALIDATION_ERROR = "PROJECT_MANAGEMENT$WORKSPACE_NAME_VALIDATION_ERROR",
|
PROJECT_MANAGEMENT$WORKSPACE_NAME_VALIDATION_ERROR = "PROJECT_MANAGEMENT$WORKSPACE_NAME_VALIDATION_ERROR",
|
||||||
PROJECT_MANAGEMENT$WEBHOOK_SECRET_NAME_VALIDATION_ERROR = "PROJECT_MANAGEMENT$WEBHOOK_SECRET_NAME_VALIDATION_ERROR",
|
PROJECT_MANAGEMENT$WEBHOOK_SECRET_NAME_VALIDATION_ERROR = "PROJECT_MANAGEMENT$WEBHOOK_SECRET_NAME_VALIDATION_ERROR",
|
||||||
PROJECT_MANAGEMENT$SVC_ACC_EMAIL_VALIDATION_ERROR = "PROJECT_MANAGEMENT$SVC_ACC_EMAIL_VALIDATION_ERROR",
|
PROJECT_MANAGEMENT$SVC_ACC_EMAIL_VALIDATION_ERROR = "PROJECT_MANAGEMENT$SVC_ACC_EMAIL_VALIDATION_ERROR",
|
||||||
|
|||||||
@@ -27,15 +27,7 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
debug: import.meta.env.NODE_ENV === "development",
|
debug: import.meta.env.NODE_ENV === "development",
|
||||||
|
load: "currentOnly",
|
||||||
// Define supported languages explicitly to prevent 404 errors
|
|
||||||
// According to i18next documentation, this is the recommended way to prevent
|
|
||||||
// 404 requests for unsupported language codes like 'en-US@posix'
|
|
||||||
supportedLngs: AvailableLanguages.map((lang) => lang.value),
|
|
||||||
|
|
||||||
// Do NOT set nonExplicitSupportedLngs: true as it causes 404 errors
|
|
||||||
// for region-specific codes not in supportedLngs (per i18next developer)
|
|
||||||
nonExplicitSupportedLngs: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|||||||
@@ -815,21 +815,21 @@
|
|||||||
"de": "Konfiguration bearbeiten",
|
"de": "Konfiguration bearbeiten",
|
||||||
"uk": "Редагувати налаштування"
|
"uk": "Редагувати налаштування"
|
||||||
},
|
},
|
||||||
"SETTINGS$MCP_PREVIEW_CHANGES": {
|
"SETTINGS$MCP_CONFIRM_CHANGES": {
|
||||||
"en": "Preview Changes",
|
"en": "Confirm Changes",
|
||||||
"ja": "変更内容をプレビュー",
|
"ja": "変更を確定",
|
||||||
"zh-CN": "预览更改",
|
"zh-CN": "确认更改",
|
||||||
"zh-TW": "預覽變更",
|
"zh-TW": "確認變更",
|
||||||
"ko-KR": "변경 사항 미리보기",
|
"ko-KR": "변경 사항 확인",
|
||||||
"no": "Forhåndsvis endringer",
|
"no": "Bekreft endringer",
|
||||||
"it": "Anteprima modifiche",
|
"it": "Conferma modifiche",
|
||||||
"pt": "Visualizar alterações",
|
"pt": "Confirmar alterações",
|
||||||
"es": "Vista previa de los cambios",
|
"es": "Confirmar cambios",
|
||||||
"ar": "معاينة التغييرات",
|
"ar": "تأكيد التغييرات",
|
||||||
"fr": "Aperçu des modifications",
|
"fr": "Confirmer les modifications",
|
||||||
"tr": "Değişiklikleri Önizle",
|
"tr": "Değişiklikleri Onayla",
|
||||||
"de": "Änderungen anzeigen",
|
"de": "Änderungen bestätigen",
|
||||||
"uk": "Переглянути зміни"
|
"uk": "Підтвердити зміни"
|
||||||
},
|
},
|
||||||
"SETTINGS$MCP_CONFIG_DESCRIPTION": {
|
"SETTINGS$MCP_CONFIG_DESCRIPTION": {
|
||||||
"en": "Edit the JSON configuration for MCP servers below. The configuration must include both sse_servers and stdio_servers arrays. For full configuration details and integration examples, see the <a>documentation</a>.",
|
"en": "Edit the JSON configuration for MCP servers below. The configuration must include both sse_servers and stdio_servers arrays. For full configuration details and integration examples, see the <a>documentation</a>.",
|
||||||
@@ -1135,6 +1135,22 @@
|
|||||||
"de": "Ungültiges JSON",
|
"de": "Ungültiges JSON",
|
||||||
"uk": "Невірний JSON"
|
"uk": "Невірний JSON"
|
||||||
},
|
},
|
||||||
|
"SETTINGS$MCP_DEFAULT_CONFIG": {
|
||||||
|
"en": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"ja": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"zh-CN": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"zh-TW": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"ko-KR": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"no": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"it": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"pt": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"es": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"ar": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"fr": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"tr": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"de": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||||
|
"uk": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}"
|
||||||
|
},
|
||||||
"HOME$CONNECT_PROVIDER_MESSAGE": {
|
"HOME$CONNECT_PROVIDER_MESSAGE": {
|
||||||
"en": "To get started with suggested tasks, please connect your GitHub, GitLab, or Bitbucket account.",
|
"en": "To get started with suggested tasks, please connect your GitHub, GitLab, or Bitbucket account.",
|
||||||
"ja": "提案されたタスクを始めるには、GitHub、GitLab、またはBitbucketアカウントを接続してください。",
|
"ja": "提案されたタスクを始めるには、GitHub、GitLab、またはBitbucketアカウントを接続してください。",
|
||||||
@@ -11983,86 +11999,6 @@
|
|||||||
"de": "Bearbeiten",
|
"de": "Bearbeiten",
|
||||||
"uk": "Редагувати"
|
"uk": "Редагувати"
|
||||||
},
|
},
|
||||||
"PROJECT_MANAGEMENT$UPDATE_BUTTON_LABEL": {
|
|
||||||
"en": "Update",
|
|
||||||
"ja": "更新",
|
|
||||||
"zh-CN": "更新",
|
|
||||||
"zh-TW": "更新",
|
|
||||||
"ko-KR": "업데이트",
|
|
||||||
"no": "Oppdater",
|
|
||||||
"it": "Aggiorna",
|
|
||||||
"pt": "Atualizar",
|
|
||||||
"es": "Actualizar",
|
|
||||||
"ar": "تحديث",
|
|
||||||
"fr": "Mettre à jour",
|
|
||||||
"tr": "Güncelle",
|
|
||||||
"de": "Aktualisieren",
|
|
||||||
"uk": "Оновити"
|
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$CONFIGURE_MODAL_TITLE": {
|
|
||||||
"en": "Configure {{platform}} Integration",
|
|
||||||
"ja": "{{platform}}統合を設定",
|
|
||||||
"zh-CN": "配置{{platform}}集成",
|
|
||||||
"zh-TW": "配置{{platform}}集成",
|
|
||||||
"ko-KR": "{{platform}} 통합 구성",
|
|
||||||
"no": "Konfigurer {{platform}}-integrasjon",
|
|
||||||
"it": "Configura integrazione {{platform}}",
|
|
||||||
"pt": "Configurar integração {{platform}}",
|
|
||||||
"es": "Configurar integración de {{platform}}",
|
|
||||||
"ar": "تكوين تكامل {{platform}}",
|
|
||||||
"fr": "Configurer l'intégration {{platform}}",
|
|
||||||
"tr": "{{platform}} Entegrasyonunu Yapılandır",
|
|
||||||
"de": "{{platform}}-Integration konfigurieren",
|
|
||||||
"uk": "Налаштувати інтеграцію {{platform}}"
|
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_1": {
|
|
||||||
"en": "<b>Important: </b>Make sure the workspace integration for your target workspace is already configured. Check the <a>documentation</a> for more information.",
|
|
||||||
"ja": "<b>重要: </b>対象のワークスペースのワークスペース統合がすでに設定されていることを確認してください。詳しくは<a>ドキュメント</a>をご覧ください。",
|
|
||||||
"zh-CN": "<b>重要提示:</b>请确保目标工作区的工作区集成已配置完毕。查看<a>文档</a>了解更多信息。",
|
|
||||||
"zh-TW": "<b>重要提示:</b>請確保目標工作區的工作區整合已設定完成。查看<a>文件</a>以了解更多資訊。",
|
|
||||||
"ko-KR": "<b>중요:</b>대상 작업 공간에 대한 작업 공간 통합이 이미 구성되어 있는지 확인하세요. 자세한 내용은 <a>설명서</a>를 참조하세요.",
|
|
||||||
"no": "<b>Viktig:</b>Sørg for at arbeidsområdeintegrasjonen for målarbeidsområdet ditt allerede er konfigurert. Se <a>dokumentasjonen</a> for mer informasjon.",
|
|
||||||
"it": "<b>Importante: </b>Assicurati che l'integrazione dell'area di lavoro per l'area di lavoro di destinazione sia già configurata. Consulta la <a>documentazione</a> per ulteriori informazioni.",
|
|
||||||
"pt": "<b>Importante:</b>Certifique-se de que a integração do workspace de destino já esteja configurada. Consulte a <a>documentação</a> para obter mais informações.",
|
|
||||||
"es": "Importante: Asegúrate de que la integración del espacio de trabajo de destino ya esté configurada. Consulta la documentación para obtener más información.",
|
|
||||||
"ar": "<b>هام: </b>تأكد من إعداد تكامل مساحة العمل لمساحة العمل المستهدفة. <a>راجع المستند لمزيد من المعلومات</a>.",
|
|
||||||
"fr": "<b>Important :</b>Assurez-vous que l'intégration de l'espace de travail cible est déjà configurée. Consultez la <a>documentation</a> pour plus d'informations.",
|
|
||||||
"tr": "<b>Önemli: </b>Hedef çalışma alanınız için çalışma alanı entegrasyonunun zaten yapılandırılmış olduğundan emin olun. Daha fazla bilgi için <a>belgelere</a> bakın.",
|
|
||||||
"de": "<b>Wichtig:</b>Stellen Sie sicher, dass die Arbeitsbereichsintegration für Ihren Zielarbeitsbereich bereits konfiguriert ist. Weitere Informationen finden Sie in der <a>Dokumentation</a>.",
|
|
||||||
"uk": "<b>Важливо: </b>Переконайтеся, що інтеграцію робочого простору для вашого цільового робочого простору вже налаштовано. Перегляньте <a>документацію</a> для отримання додаткової інформації."
|
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION_STAGE_2": {
|
|
||||||
"en": "<b>Important:</b> Check the <a>documentation</a> for more information about configuring the workspace integration or updating an existing integration.",
|
|
||||||
"ja": "<b>重要:</b> ワークスペース統合の設定や既存の統合の更新について詳しくは<a>ドキュメント</a>をご確認ください。",
|
|
||||||
"zh-CN": "<b>重要提示:</b>有关配置工作区集成或更新现有集成的更多信息,请查看<a>文档</a>。",
|
|
||||||
"zh-TW": "<b>重要提示:</b>有關配置工作區整合或更新現有整合的更多資訊,請查看<a>文件</a>。",
|
|
||||||
"ko-KR": "<b>중요:</b> 작업공간 통합 구성이나 기존 통합 업데이트에 대한 자세한 내용은 <a>설명서</a>를 확인하세요.",
|
|
||||||
"no": "<b>Viktig:</b> Se <a>dokumentasjonen</a> for mer informasjon om konfigurering av arbeidsområdeintegrasjon eller oppdatering av eksisterende integrasjon.",
|
|
||||||
"it": "<b>Importante:</b> Consulta la <a>documentazione</a> per ulteriori informazioni sulla configurazione dell'integrazione dell'area di lavoro o sull'aggiornamento di un'integrazione esistente.",
|
|
||||||
"pt": "<b>Importante:</b> Consulte a <a>documentação</a> para obter mais informações sobre como configurar a integração do workspace ou atualizar uma integração existente.",
|
|
||||||
"es": "<b>Importante:</b> Consulte la <a>documentación</a> para obtener más información sobre la configuración de la integración del espacio de trabajo o la actualización de una integración existente.",
|
|
||||||
"ar": "<b>هام:</b> راجع <a>الوثائق</a> لمزيد من المعلومات حول تكوين تكامل مساحة العمل أو تحديث تكامل موجود.",
|
|
||||||
"fr": "<b>Important :</b> Consultez la <a>documentation</a> pour plus d'informations sur la configuration de l'intégration de l'espace de travail ou la mise à jour d'une intégration existante.",
|
|
||||||
"tr": "<b>Önemli:</b> Çalışma alanı entegrasyonunu yapılandırma veya mevcut bir entegrasyonu güncelleme hakkında daha fazla bilgi için <a>belgelere</a> bakın.",
|
|
||||||
"de": "<b>Wichtig:</b> Weitere Informationen zur Konfiguration der Arbeitsbereichsintegration oder zur Aktualisierung einer bestehenden Integration finden Sie in der <a>Dokumentation</a>.",
|
|
||||||
"uk": "<b>Важливо:</b> Перегляньте <a>документацію</a> для отримання додаткової інформації про налаштування інтеграції робочого простору або оновлення існуючої інтеграції."
|
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$WORKSPACE_NAME_HINT": {
|
|
||||||
"en": "Workspace name can be found in the browser URL when you're accessing a resource (eg: issue) in {{platform}}.",
|
|
||||||
"ja": "ワークスペース名は、{{platform}}のリソース(例:イシュー)にアクセスする際のブラウザURLで確認できます。",
|
|
||||||
"zh-CN": "工作区名称可以在访问{{platform}}资源(如:问题)时的浏览器URL中找到。",
|
|
||||||
"zh-TW": "工作區名稱可以在存取{{platform}}資源(如:問題)時的瀏覽器URL中找到。",
|
|
||||||
"ko-KR": "작업공간 이름은 {{platform}}의 리소스(예: 이슈)에 액세스할 때 브라우저 URL에서 찾을 수 있습니다.",
|
|
||||||
"no": "Arbeidsområdenavn kan finnes i nettleser-URL-en når du får tilgang til en ressurs (f.eks: sak) i {{platform}}.",
|
|
||||||
"it": "Il nome dell'area di lavoro può essere trovato nell'URL del browser quando stai accedendo a una risorsa (es: issue) in {{platform}}.",
|
|
||||||
"pt": "O nome do workspace pode ser encontrado na URL do navegador quando você está acessando um recurso (ex: issue) em {{platform}}.",
|
|
||||||
"es": "El nombre del espacio de trabajo se puede encontrar en la URL del navegador cuando accedes a un recurso (ej: issue) en {{platform}}.",
|
|
||||||
"ar": "يمكن العثور على اسم مساحة العمل في عنوان URL للمتصفح عند الوصول إلى مورد (مثل: مشكلة) في {{platform}}.",
|
|
||||||
"fr": "Le nom de l'espace de travail peut être trouvé dans l'URL du navigateur lorsque vous accédez à une ressource (ex : issue) dans {{platform}}.",
|
|
||||||
"tr": "Çalışma alanı adı, {{platform}}'da bir kaynağa (örn: sorun) erişirken tarayıcı URL'sinde bulunabilir.",
|
|
||||||
"de": "Der Arbeitsbereichsname ist in der Browser-URL zu finden, wenn Sie auf eine Ressource (z.B.: Issue) in {{platform}} zugreifen.",
|
|
||||||
"uk": "Назву робочого простору можна знайти в URL браузера під час доступу до ресурсу (наприклад: проблема) в {{platform}}."
|
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL": {
|
"PROJECT_MANAGEMENT$WORKSPACE_NAME_LABEL": {
|
||||||
"en": "Workspace Name",
|
"en": "Workspace Name",
|
||||||
"ja": "ワークスペース名",
|
"ja": "ワークスペース名",
|
||||||
@@ -12079,53 +12015,21 @@
|
|||||||
"de": "Arbeitsbereichsname",
|
"de": "Arbeitsbereichsname",
|
||||||
"uk": "Назва робочої області"
|
"uk": "Назва робочої області"
|
||||||
},
|
},
|
||||||
"PROJECT_MANAGEMENT$JIRA_WORKSPACE_NAME_PLACEHOLDER": {
|
"PROJECT_MANAGEMENT$WORKSPACE_NAME_PLACEHOLDER": {
|
||||||
"en": "yourcompany.atlassian.net",
|
"en": "myworkspace",
|
||||||
"ja": "yourcompany.atlassian.net",
|
"ja": "私のワークスペース",
|
||||||
"zh-CN": "yourcompany.atlassian.net",
|
"zh-CN": "我的工作空间",
|
||||||
"zh-TW": "yourcompany.atlassian.net",
|
"zh-TW": "我的工作區",
|
||||||
"ko-KR": "yourcompany.atlassian.net",
|
"ko-KR": "내워크스페이스",
|
||||||
"no": "yourcompany.atlassian.net",
|
"no": "mittarbeidsområde",
|
||||||
"it": "yourcompany.atlassian.net",
|
"it": "mioworkspace",
|
||||||
"pt": "yourcompany.atlassian.net",
|
"pt": "meuworkspace",
|
||||||
"es": "yourcompany.atlassian.net",
|
"es": "miespaciodetrabajo",
|
||||||
"ar": "yourcompany.atlassian.net",
|
"ar": "مساحةعملي",
|
||||||
"fr": "yourcompany.atlassian.net",
|
"fr": "monworkspace",
|
||||||
"tr": "yourcompany.atlassian.net",
|
"tr": "benimworkspace",
|
||||||
"de": "yourcompany.atlassian.net",
|
"de": "meinarbeitsbereich",
|
||||||
"uk": "yourcompany.atlassian.net"
|
"uk": "моя-робоча-область"
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$JIRA_DC_WORKSPACE_NAME_PLACEHOLDER": {
|
|
||||||
"en": "jira.yourcompany.com",
|
|
||||||
"ja": "jira.yourcompany.com",
|
|
||||||
"zh-CN": "jira.yourcompany.com",
|
|
||||||
"zh-TW": "jira.yourcompany.com",
|
|
||||||
"ko-KR": "jira.yourcompany.com",
|
|
||||||
"no": "jira.yourcompany.com",
|
|
||||||
"it": "jira.yourcompany.com",
|
|
||||||
"pt": "jira.yourcompany.com",
|
|
||||||
"es": "jira.yourcompany.com",
|
|
||||||
"ar": "jira.yourcompany.com",
|
|
||||||
"fr": "jira.yourcompany.com",
|
|
||||||
"tr": "jira.yourcompany.com",
|
|
||||||
"de": "jira.yourcompany.com",
|
|
||||||
"uk": "jira.yourcompany.com"
|
|
||||||
},
|
|
||||||
"PROJECT_MANAGEMENT$LINEAR_WORKSPACE_NAME_PLACEHOLDER": {
|
|
||||||
"en": "yourcompany",
|
|
||||||
"ja": "yourcompany",
|
|
||||||
"zh-CN": "yourcompany",
|
|
||||||
"zh-TW": "yourcompany",
|
|
||||||
"ko-KR": "yourcompany",
|
|
||||||
"no": "yourcompany",
|
|
||||||
"it": "yourcompany",
|
|
||||||
"pt": "yourcompany",
|
|
||||||
"es": "yourcompany",
|
|
||||||
"ar": "yourcompany",
|
|
||||||
"fr": "yourcompany",
|
|
||||||
"tr": "yourcompany",
|
|
||||||
"de": "yourcompany",
|
|
||||||
"uk": "yourcompany"
|
|
||||||
},
|
},
|
||||||
"PROJECT_MANAGEMENT$WEBHOOK_SECRET_LABEL": {
|
"PROJECT_MANAGEMENT$WEBHOOK_SECRET_LABEL": {
|
||||||
"en": "Webhook Secret",
|
"en": "Webhook Secret",
|
||||||
@@ -12255,6 +12159,54 @@
|
|||||||
"de": "Aktiv",
|
"de": "Aktiv",
|
||||||
"uk": "Активний"
|
"uk": "Активний"
|
||||||
},
|
},
|
||||||
|
"PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION": {
|
||||||
|
"en": "<b>Important:</b> Check the <a>documentation</a> for more information about configuring the workspace integration or updating an existing integration.",
|
||||||
|
"ja": "<b>重要:</b> ワークスペース統合の設定や既存の統合の更新について詳しくは<a>ドキュメント</a>をご確認ください。",
|
||||||
|
"zh-CN": "<b>重要提示:</b>有关配置工作区集成或更新现有集成的更多信息,请查看<a>文档</a>。",
|
||||||
|
"zh-TW": "<b>重要提示:</b>有關配置工作區整合或更新現有整合的更多資訊,請查看<a>文件</a>。",
|
||||||
|
"ko-KR": "<b>중요:</b> 작업공간 통합 구성이나 기존 통합 업데이트에 대한 자세한 내용은 <a>설명서</a>를 확인하세요.",
|
||||||
|
"no": "<b>Viktig:</b> Se <a>dokumentasjonen</a> for mer informasjon om konfigurering av arbeidsområdeintegrasjon eller oppdatering av eksisterende integrasjon.",
|
||||||
|
"it": "<b>Importante:</b> Consulta la <a>documentazione</a> per ulteriori informazioni sulla configurazione dell'integrazione dell'area di lavoro o sull'aggiornamento di un'integrazione esistente.",
|
||||||
|
"pt": "<b>Importante:</b> Consulte a <a>documentação</a> para obter mais informações sobre como configurar a integração do workspace ou atualizar uma integração existente.",
|
||||||
|
"es": "<b>Importante:</b> Consulte la <a>documentación</a> para obtener más información sobre la configuración de la integración del espacio de trabajo o la actualización de una integración existente.",
|
||||||
|
"ar": "<b>هام:</b> راجع <a>الوثائق</a> لمزيد من المعلومات حول تكوين تكامل مساحة العمل أو تحديث تكامل موجود.",
|
||||||
|
"fr": "<b>Important :</b> Consultez la <a>documentation</a> pour plus d'informations sur la configuration de l'intégration de l'espace de travail ou la mise à jour d'une intégration existante.",
|
||||||
|
"tr": "<b>Önemli:</b> Çalışma alanı entegrasyonunu yapılandırma veya mevcut bir entegrasyonu güncelleme hakkında daha fazla bilgi için <a>belgelere</a> bakın.",
|
||||||
|
"de": "<b>Wichtig:</b> Weitere Informationen zur Konfiguration der Arbeitsbereichsintegration oder zur Aktualisierung einer bestehenden Integration finden Sie in der <a>Dokumentation</a>.",
|
||||||
|
"uk": "<b>Важливо:</b> Перегляньте <a>документацію</a> для отримання додаткової інформації про налаштування інтеграції робочого простору або оновлення існуючої інтеграції."
|
||||||
|
},
|
||||||
|
"PROJECT_MANAGEMENT$CONFIGURE_MODAL_TITLE": {
|
||||||
|
"en": "Configure {{platform}} Integration",
|
||||||
|
"ja": "{{platform}}統合を設定",
|
||||||
|
"zh-CN": "配置{{platform}}集成",
|
||||||
|
"zh-TW": "配置{{platform}}集成",
|
||||||
|
"ko-KR": "{{platform}} 통합 구성",
|
||||||
|
"no": "Konfigurer {{platform}}-integrasjon",
|
||||||
|
"it": "Configura integrazione {{platform}}",
|
||||||
|
"pt": "Configurar integração {{platform}}",
|
||||||
|
"es": "Configurar integración de {{platform}}",
|
||||||
|
"ar": "تكوين تكامل {{platform}}",
|
||||||
|
"fr": "Configurer l'intégration {{platform}}",
|
||||||
|
"tr": "{{platform}} Entegrasyonunu Yapılandır",
|
||||||
|
"de": "{{platform}}-Integration konfigurieren",
|
||||||
|
"uk": "Налаштувати інтеграцію {{platform}}"
|
||||||
|
},
|
||||||
|
"PROJECT_MANAGEMENT$IMPORTANT_WORKSPACE_INTEGRATION": {
|
||||||
|
"en": "<b>Important: </b>Make sure the workspace integration for your target workspace is already configured. Check the <a>documentation</a> for more information.",
|
||||||
|
"ja": "<b>重要: </b>対象のワークスペースのワークスペース統合がすでに設定されていることを確認してください。詳しくは<a>ドキュメント</a>をご覧ください。",
|
||||||
|
"zh-CN": "<b>重要提示:</b>请确保目标工作区的工作区集成已配置完毕。查看<a>文档</a>了解更多信息。",
|
||||||
|
"zh-TW": "<b>重要提示:</b>請確保目標工作區的工作區整合已設定完成。查看<a>文件</a>以了解更多資訊。",
|
||||||
|
"ko-KR": "<b>중요:</b>대상 작업 공간에 대한 작업 공간 통합이 이미 구성되어 있는지 확인하세요. 자세한 내용은 <a>설명서</a>를 참조하세요.",
|
||||||
|
"no": "<b>Viktig:</b>Sørg for at arbeidsområdeintegrasjonen for målarbeidsområdet ditt allerede er konfigurert. Se <a>dokumentasjonen</a> for mer informasjon.",
|
||||||
|
"it": "<b>Importante: </b>Assicurati che l'integrazione dell'area di lavoro per l'area di lavoro di destinazione sia già configurata. Consulta la <a>documentazione</a> per ulteriori informazioni.",
|
||||||
|
"pt": "<b>Importante:</b>Certifique-se de que a integração do workspace de destino já esteja configurada. Consulte a <a>documentação</a> para obter mais informações.",
|
||||||
|
"es": "Importante: Asegúrate de que la integración del espacio de trabajo de destino ya esté configurada. Consulta la documentación para obtener más información.",
|
||||||
|
"ar": "<b>هام: </b>تأكد من إعداد تكامل مساحة العمل لمساحة العمل المستهدفة. <a>راجع المستند لمزيد من المعلومات</a>.",
|
||||||
|
"fr": "<b>Important :</b>Assurez-vous que l'intégration de l'espace de travail cible est déjà configurée. Consultez la <a>documentation</a> pour plus d'informations.",
|
||||||
|
"tr": "<b>Önemli: </b>Hedef çalışma alanınız için çalışma alanı entegrasyonunun zaten yapılandırılmış olduğundan emin olun. Daha fazla bilgi için <a>belgelere</a> bakın.",
|
||||||
|
"de": "<b>Wichtig:</b>Stellen Sie sicher, dass die Arbeitsbereichsintegration für Ihren Zielarbeitsbereich bereits konfiguriert ist. Weitere Informationen finden Sie in der <a>Dokumentation</a>.",
|
||||||
|
"uk": "<b>Важливо: </b>Переконайтеся, що інтеграцію робочого простору для вашого цільового робочого простору вже налаштовано. Перегляньте <a>документацію</a> для отримання додаткової інформації."
|
||||||
|
},
|
||||||
"PROJECT_MANAGEMENT$WORKSPACE_NAME_VALIDATION_ERROR": {
|
"PROJECT_MANAGEMENT$WORKSPACE_NAME_VALIDATION_ERROR": {
|
||||||
"en": "Workspace name can only contain letters, numbers, hyphens, and underscores",
|
"en": "Workspace name can only contain letters, numbers, hyphens, and underscores",
|
||||||
"ja": "ワークスペース名は文字、数字、ハイフン、アンダースコアのみ使用できます",
|
"ja": "ワークスペース名は文字、数字、ハイフン、アンダースコアのみ使用できます",
|
||||||
|
|||||||
@@ -249,11 +249,11 @@ function AppSettingsScreen() {
|
|||||||
className="w-full max-w-[680px]" // Match the width of the language field
|
className="w-full max-w-[680px]" // Match the width of the language field
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="border-t border-t-tertiary pt-6 mt-2">
|
<div className="border-t border-t-tertiary pt-6 mt-2 hidden">
|
||||||
<h3 className="text-lg font-medium mb-2">
|
<h3 className="text-lg font-medium mb-2">
|
||||||
{t(I18nKey.SETTINGS$GIT_SETTINGS)}
|
{t(I18nKey.SETTINGS$GIT_SETTINGS)}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs mb-4">
|
<p className="text-sm text-secondary mb-4">
|
||||||
{t(I18nKey.SETTINGS$GIT_SETTINGS_DESCRIPTION)}
|
{t(I18nKey.SETTINGS$GIT_SETTINGS_DESCRIPTION)}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
import { useSettings } from "#/hooks/query/use-settings";
|
import { useSettings } from "#/hooks/query/use-settings";
|
||||||
@@ -18,15 +18,11 @@ function MCPSettingsScreen() {
|
|||||||
const { data: settings, isLoading } = useSettings();
|
const { data: settings, isLoading } = useSettings();
|
||||||
const { mutate: saveSettings, isPending } = useSaveSettings();
|
const { mutate: saveSettings, isPending } = useSaveSettings();
|
||||||
|
|
||||||
const [mcpConfig, setMcpConfig] = useState<MCPConfig | undefined>(undefined);
|
const [mcpConfig, setMcpConfig] = useState<MCPConfig | undefined>(
|
||||||
|
settings?.MCP_CONFIG,
|
||||||
|
);
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!mcpConfig && settings?.MCP_CONFIG) {
|
|
||||||
setMcpConfig(settings.MCP_CONFIG);
|
|
||||||
}
|
|
||||||
}, [settings, mcpConfig]);
|
|
||||||
|
|
||||||
const handleConfigChange = (config: MCPConfig) => {
|
const handleConfigChange = (config: MCPConfig) => {
|
||||||
setMcpConfig(config);
|
setMcpConfig(config);
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ function SecretsSettingsScreen() {
|
|||||||
to="/settings/integrations"
|
to="/settings/integrations"
|
||||||
data-testid="connect-git-button"
|
data-testid="connect-git-button"
|
||||||
type="button"
|
type="button"
|
||||||
className="self-start"
|
|
||||||
>
|
>
|
||||||
<BrandButton type="button" variant="secondary">
|
<BrandButton type="button" variant="secondary">
|
||||||
{t(I18nKey.SECRETS$CONNECT_GIT_PROVIDER)}
|
{t(I18nKey.SECRETS$CONNECT_GIT_PROVIDER)}
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ export function getIndicatorColor(
|
|||||||
webSocketStatus === "DISCONNECTED" ||
|
webSocketStatus === "DISCONNECTED" ||
|
||||||
conversationStatus === "STOPPED" ||
|
conversationStatus === "STOPPED" ||
|
||||||
runtimeStatus === "STATUS$STOPPED" ||
|
runtimeStatus === "STATUS$STOPPED" ||
|
||||||
agentState === AgentState.STOPPED ||
|
agentState === AgentState.STOPPED
|
||||||
agentState === AgentState.ERROR
|
|
||||||
) {
|
) {
|
||||||
return IndicatorColor.RED;
|
return IndicatorColor.RED;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,52 +31,6 @@ export default defineConfig(({ mode }) => {
|
|||||||
svgr(),
|
svgr(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
],
|
],
|
||||||
optimizeDeps: {
|
|
||||||
include: [
|
|
||||||
// Pre-bundle ALL dependencies to prevent runtime optimization and page reloads
|
|
||||||
// These are discovered during initial app load:
|
|
||||||
"react-redux",
|
|
||||||
"posthog-js",
|
|
||||||
"@tanstack/react-query",
|
|
||||||
"react-hot-toast",
|
|
||||||
"@reduxjs/toolkit",
|
|
||||||
"i18next",
|
|
||||||
"i18next-http-backend",
|
|
||||||
"i18next-browser-languagedetector",
|
|
||||||
"react-i18next",
|
|
||||||
"axios",
|
|
||||||
"date-fns",
|
|
||||||
"@uidotdev/usehooks",
|
|
||||||
"react-icons/fa6",
|
|
||||||
"react-icons/fa",
|
|
||||||
"clsx",
|
|
||||||
"tailwind-merge",
|
|
||||||
"@heroui/react",
|
|
||||||
"lucide-react",
|
|
||||||
"react-select",
|
|
||||||
"react-select/async",
|
|
||||||
"@microlink/react-json-view",
|
|
||||||
"socket.io-client",
|
|
||||||
// These are discovered when launching conversations:
|
|
||||||
"react-icons/vsc",
|
|
||||||
"react-icons/lu",
|
|
||||||
"react-icons/di",
|
|
||||||
"react-icons/io5",
|
|
||||||
"react-icons/io", // Added to prevent runtime optimization
|
|
||||||
"@monaco-editor/react",
|
|
||||||
"react-textarea-autosize",
|
|
||||||
"react-markdown",
|
|
||||||
"remark-gfm",
|
|
||||||
"remark-breaks",
|
|
||||||
"react-syntax-highlighter",
|
|
||||||
"react-syntax-highlighter/dist/esm/styles/prism",
|
|
||||||
"react-syntax-highlighter/dist/esm/styles/hljs",
|
|
||||||
// Terminal dependencies - added to prevent runtime optimization
|
|
||||||
"@xterm/addon-fit",
|
|
||||||
"@xterm/xterm",
|
|
||||||
"@xterm/xterm/css/xterm.css",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
server: {
|
server: {
|
||||||
port: FE_PORT,
|
port: FE_PORT,
|
||||||
host: true,
|
host: true,
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import sys
|
|||||||
|
|
||||||
|
|
||||||
def refine_prompt(prompt: str):
|
def refine_prompt(prompt: str):
|
||||||
"""Refines the prompt based on the platform.
|
"""
|
||||||
|
Refines the prompt based on the platform.
|
||||||
|
|
||||||
On Windows systems, replaces 'bash' with 'powershell' and 'execute_bash' with 'execute_powershell'
|
On Windows systems, replaces 'bash' with 'powershell' and 'execute_bash' with 'execute_powershell'
|
||||||
to ensure commands work correctly on the Windows platform.
|
to ensure commands work correctly on the Windows platform.
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
"""ReadOnlyAgent - A specialized version of CodeActAgent that only uses read-only tools."""
|
"""
|
||||||
|
ReadOnlyAgent - A specialized version of CodeActAgent that only uses read-only tools.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|||||||
@@ -723,6 +723,9 @@ def run_cli_command(args):
|
|||||||
except ConnectionRefusedError as e:
|
except ConnectionRefusedError as e:
|
||||||
print_formatted_text(f'Connection refused: {e}')
|
print_formatted_text(f'Connection refused: {e}')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print_formatted_text(f'An error occurred: {e}')
|
||||||
|
sys.exit(1)
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
# Cancel all running tasks
|
# Cancel all running tasks
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from prompt_toolkit import PromptSession, print_formatted_text
|
from prompt_toolkit import PromptSession, print_formatted_text
|
||||||
from prompt_toolkit.completion import FuzzyWordCompleter
|
from prompt_toolkit.completion import FuzzyWordCompleter
|
||||||
@@ -20,7 +19,6 @@ from openhands.cli.utils import (
|
|||||||
VERIFIED_OPENAI_MODELS,
|
VERIFIED_OPENAI_MODELS,
|
||||||
VERIFIED_OPENHANDS_MODELS,
|
VERIFIED_OPENHANDS_MODELS,
|
||||||
VERIFIED_PROVIDERS,
|
VERIFIED_PROVIDERS,
|
||||||
extract_model_and_provider,
|
|
||||||
organize_models_and_providers,
|
organize_models_and_providers,
|
||||||
)
|
)
|
||||||
from openhands.controller.agent import Agent
|
from openhands.controller.agent import Agent
|
||||||
@@ -126,36 +124,12 @@ async def get_validated_input(
|
|||||||
completer=None,
|
completer=None,
|
||||||
validator=None,
|
validator=None,
|
||||||
error_message: str = 'Input cannot be empty',
|
error_message: str = 'Input cannot be empty',
|
||||||
*,
|
|
||||||
default_value: str = '',
|
|
||||||
enter_keeps_value: Optional[str] = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
|
||||||
Get validated input from user.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
session: PromptSession instance
|
|
||||||
prompt_text: The text to display before the input
|
|
||||||
completer: Completer instance
|
|
||||||
validator: Function to validate input
|
|
||||||
error_message: Error message to display if input is invalid
|
|
||||||
default_value: Value to show prefilled in the prompt (prompt placeholder)
|
|
||||||
enter_keeps_value: If provided, pressing Enter on an empty input will
|
|
||||||
return this value (useful for keeping existing sensitive values)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The validated input
|
|
||||||
"""
|
|
||||||
|
|
||||||
session.completer = completer
|
session.completer = completer
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
value = await session.prompt_async(prompt_text, default=default_value)
|
value = await session.prompt_async(prompt_text)
|
||||||
|
|
||||||
# If user submits empty input and a keep-value is provided, use it.
|
|
||||||
if not value.strip() and enter_keeps_value is not None:
|
|
||||||
value = enter_keeps_value
|
|
||||||
|
|
||||||
if validator:
|
if validator:
|
||||||
is_valid = validator(value)
|
is_valid = validator(value)
|
||||||
@@ -186,50 +160,6 @@ def save_settings_confirmation(config: OpenHandsConfig) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_current_values_for_modification_basic(
|
|
||||||
config: OpenHandsConfig,
|
|
||||||
) -> tuple[str, str, str]:
|
|
||||||
llm_config = config.get_llm_config()
|
|
||||||
current_provider = ''
|
|
||||||
current_model = ''
|
|
||||||
current_api_key = (
|
|
||||||
llm_config.api_key.get_secret_value() if llm_config.api_key else ''
|
|
||||||
)
|
|
||||||
if llm_config.model:
|
|
||||||
model_info = extract_model_and_provider(llm_config.model)
|
|
||||||
current_provider = model_info.provider or ''
|
|
||||||
current_model = model_info.model or ''
|
|
||||||
return current_provider, current_model, current_api_key
|
|
||||||
|
|
||||||
|
|
||||||
def _get_default_provider(provider_list: list[str]) -> str:
|
|
||||||
if 'anthropic' in provider_list:
|
|
||||||
return 'anthropic'
|
|
||||||
else:
|
|
||||||
return provider_list[0] if provider_list else ''
|
|
||||||
|
|
||||||
|
|
||||||
def _get_initial_provider_index(
|
|
||||||
verified_providers: list[str],
|
|
||||||
current_provider: str,
|
|
||||||
default_provider: str,
|
|
||||||
provider_choices: list[str],
|
|
||||||
) -> int:
|
|
||||||
if (current_provider or default_provider) in verified_providers:
|
|
||||||
return verified_providers.index(current_provider or default_provider)
|
|
||||||
elif current_provider or default_provider:
|
|
||||||
return len(provider_choices) - 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _get_initial_model_index(
|
|
||||||
verified_models: list[str], current_model: str, default_model: str
|
|
||||||
) -> int:
|
|
||||||
if (current_model or default_model) in verified_models:
|
|
||||||
return verified_models.index(current_model or default_model)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
async def modify_llm_settings_basic(
|
async def modify_llm_settings_basic(
|
||||||
config: OpenHandsConfig, settings_store: FileSettingsStore
|
config: OpenHandsConfig, settings_store: FileSettingsStore
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -241,35 +171,26 @@ async def modify_llm_settings_basic(
|
|||||||
provider_list = [p for p in provider_list if p not in verified_providers]
|
provider_list = [p for p in provider_list if p not in verified_providers]
|
||||||
provider_list = verified_providers + provider_list
|
provider_list = verified_providers + provider_list
|
||||||
|
|
||||||
provider_completer = FuzzyWordCompleter(provider_list, WORD=True)
|
provider_completer = FuzzyWordCompleter(provider_list)
|
||||||
session = PromptSession(key_bindings=kb_cancel())
|
session = PromptSession(key_bindings=kb_cancel())
|
||||||
|
|
||||||
current_provider, current_model, current_api_key = (
|
# Set default provider - prefer 'anthropic' if available, otherwise use first
|
||||||
_get_current_values_for_modification_basic(config)
|
provider = 'anthropic' if 'anthropic' in provider_list else provider_list[0]
|
||||||
)
|
|
||||||
|
|
||||||
default_provider = _get_default_provider(provider_list)
|
|
||||||
|
|
||||||
provider = None
|
|
||||||
model = None
|
model = None
|
||||||
api_key = None
|
api_key = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Show the default provider but allow changing it
|
# Show the default provider but allow changing it
|
||||||
print_formatted_text(
|
print_formatted_text(
|
||||||
HTML(f'\n<grey>Default provider: </grey><green>{default_provider}</green>')
|
HTML(f'\n<grey>Default provider: </grey><green>{provider}</green>')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Show verified providers plus "Select another provider" option
|
# Show verified providers plus "Select another provider" option
|
||||||
provider_choices = verified_providers + ['Select another provider']
|
provider_choices = verified_providers + ['Select another provider']
|
||||||
|
|
||||||
provider_choice = cli_confirm(
|
provider_choice = cli_confirm(
|
||||||
config,
|
config,
|
||||||
'(Step 1/3) Select LLM Provider:',
|
'(Step 1/3) Select LLM Provider:',
|
||||||
provider_choices,
|
provider_choices,
|
||||||
initial_selection=_get_initial_provider_index(
|
|
||||||
verified_providers, current_provider, default_provider, provider_choices
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure provider_choice is an integer (for test compatibility)
|
# Ensure provider_choice is an integer (for test compatibility)
|
||||||
@@ -290,19 +211,8 @@ async def modify_llm_settings_basic(
|
|||||||
completer=provider_completer,
|
completer=provider_completer,
|
||||||
validator=lambda x: x in organized_models,
|
validator=lambda x: x in organized_models,
|
||||||
error_message='Invalid provider selected',
|
error_message='Invalid provider selected',
|
||||||
default_value=(
|
|
||||||
# Prefill only for unverified providers.
|
|
||||||
current_provider
|
|
||||||
if current_provider not in verified_providers
|
|
||||||
else ''
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset current model and api key if provider changes
|
|
||||||
if provider != current_provider:
|
|
||||||
current_model = ''
|
|
||||||
current_api_key = ''
|
|
||||||
|
|
||||||
# Make sure the provider exists in organized_models
|
# Make sure the provider exists in organized_models
|
||||||
if provider not in organized_models:
|
if provider not in organized_models:
|
||||||
# If the provider doesn't exist, prefer 'anthropic' if available,
|
# If the provider doesn't exist, prefer 'anthropic' if available,
|
||||||
@@ -366,9 +276,6 @@ async def modify_llm_settings_basic(
|
|||||||
+ 'LLM usage is billed at the providers’ rates with no markup. Details: https://docs.all-hands.dev/usage/llms/openhands-llms'
|
+ 'LLM usage is billed at the providers’ rates with no markup. Details: https://docs.all-hands.dev/usage/llms/openhands-llms'
|
||||||
),
|
),
|
||||||
model_choices,
|
model_choices,
|
||||||
initial_selection=_get_initial_model_index(
|
|
||||||
VERIFIED_OPENHANDS_MODELS, current_model, default_model
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the selected model from the list
|
# Get the selected model from the list
|
||||||
@@ -384,15 +291,12 @@ async def modify_llm_settings_basic(
|
|||||||
config,
|
config,
|
||||||
'Do you want to use a different model?',
|
'Do you want to use a different model?',
|
||||||
[f'Use {default_model}', 'Select another model'],
|
[f'Use {default_model}', 'Select another model'],
|
||||||
initial_selection=0
|
|
||||||
if (current_model or default_model) == default_model
|
|
||||||
else 1,
|
|
||||||
)
|
)
|
||||||
== 1
|
== 1
|
||||||
)
|
)
|
||||||
|
|
||||||
if change_model:
|
if change_model:
|
||||||
model_completer = FuzzyWordCompleter(provider_models, WORD=True)
|
model_completer = FuzzyWordCompleter(provider_models)
|
||||||
|
|
||||||
# Define a validator function that allows custom models but shows a warning
|
# Define a validator function that allows custom models but shows a warning
|
||||||
def model_validator(x):
|
def model_validator(x):
|
||||||
@@ -416,10 +320,6 @@ async def modify_llm_settings_basic(
|
|||||||
completer=model_completer,
|
completer=model_completer,
|
||||||
validator=model_validator,
|
validator=model_validator,
|
||||||
error_message='Model name cannot be empty',
|
error_message='Model name cannot be empty',
|
||||||
default_value=(
|
|
||||||
# Prefill only for models that are not the default model.
|
|
||||||
current_model if current_model != default_model else ''
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Use the default model
|
# Use the default model
|
||||||
@@ -432,15 +332,10 @@ async def modify_llm_settings_basic(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
prompt_text = '(Step 3/3) Enter API Key (CTRL-c to cancel): '
|
|
||||||
if current_api_key:
|
|
||||||
prompt_text = f'(Step 3/3) Enter API Key [{current_api_key[:4]}***{current_api_key[-4:]}] (CTRL-c to cancel, ENTER to keep current, type new to change): '
|
|
||||||
api_key = await get_validated_input(
|
api_key = await get_validated_input(
|
||||||
session,
|
session,
|
||||||
prompt_text,
|
'(Step 3/3) Enter API Key (CTRL-c to cancel): ',
|
||||||
error_message='API Key cannot be empty',
|
error_message='API Key cannot be empty',
|
||||||
default_value='',
|
|
||||||
enter_keeps_value=current_api_key,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except (
|
except (
|
||||||
@@ -491,7 +386,6 @@ async def modify_llm_settings_advanced(
|
|||||||
config: OpenHandsConfig, settings_store: FileSettingsStore
|
config: OpenHandsConfig, settings_store: FileSettingsStore
|
||||||
) -> None:
|
) -> None:
|
||||||
session = PromptSession(key_bindings=kb_cancel())
|
session = PromptSession(key_bindings=kb_cancel())
|
||||||
llm_config = config.get_llm_config()
|
|
||||||
|
|
||||||
custom_model = None
|
custom_model = None
|
||||||
base_url = None
|
base_url = None
|
||||||
@@ -503,39 +397,28 @@ async def modify_llm_settings_advanced(
|
|||||||
session,
|
session,
|
||||||
'(Step 1/6) Custom Model (CTRL-c to cancel): ',
|
'(Step 1/6) Custom Model (CTRL-c to cancel): ',
|
||||||
error_message='Custom Model cannot be empty',
|
error_message='Custom Model cannot be empty',
|
||||||
default_value=llm_config.model or '',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
base_url = await get_validated_input(
|
base_url = await get_validated_input(
|
||||||
session,
|
session,
|
||||||
'(Step 2/6) Base URL (CTRL-c to cancel): ',
|
'(Step 2/6) Base URL (CTRL-c to cancel): ',
|
||||||
error_message='Base URL cannot be empty',
|
error_message='Base URL cannot be empty',
|
||||||
default_value=llm_config.base_url or '',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
prompt_text = '(Step 3/6) API Key (CTRL-c to cancel): '
|
|
||||||
current_api_key = (
|
|
||||||
llm_config.api_key.get_secret_value() if llm_config.api_key else ''
|
|
||||||
)
|
|
||||||
if current_api_key:
|
|
||||||
prompt_text = f'(Step 3/6) API Key [{current_api_key[:4]}***{current_api_key[-4:]}] (CTRL-c to cancel, ENTER to keep current, type new to change): '
|
|
||||||
api_key = await get_validated_input(
|
api_key = await get_validated_input(
|
||||||
session,
|
session,
|
||||||
prompt_text,
|
'(Step 3/6) API Key (CTRL-c to cancel): ',
|
||||||
error_message='API Key cannot be empty',
|
error_message='API Key cannot be empty',
|
||||||
default_value='',
|
|
||||||
enter_keeps_value=current_api_key,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
agent_list = Agent.list_agents()
|
agent_list = Agent.list_agents()
|
||||||
agent_completer = FuzzyWordCompleter(agent_list, WORD=True)
|
agent_completer = FuzzyWordCompleter(agent_list)
|
||||||
agent = await get_validated_input(
|
agent = await get_validated_input(
|
||||||
session,
|
session,
|
||||||
'(Step 4/6) Agent (TAB for options, CTRL-c to cancel): ',
|
'(Step 4/6) Agent (TAB for options, CTRL-c to cancel): ',
|
||||||
completer=agent_completer,
|
completer=agent_completer,
|
||||||
validator=lambda x: x in agent_list,
|
validator=lambda x: x in agent_list,
|
||||||
error_message='Invalid agent selected',
|
error_message='Invalid agent selected',
|
||||||
default_value=config.default_agent or '',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
enable_confirmation_mode = (
|
enable_confirmation_mode = (
|
||||||
@@ -543,7 +426,6 @@ async def modify_llm_settings_advanced(
|
|||||||
config,
|
config,
|
||||||
question='(Step 5/6) Confirmation Mode (CTRL-c to cancel):',
|
question='(Step 5/6) Confirmation Mode (CTRL-c to cancel):',
|
||||||
choices=['Enable', 'Disable'],
|
choices=['Enable', 'Disable'],
|
||||||
initial_selection=0 if config.security.confirmation_mode else 1,
|
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
@@ -553,7 +435,6 @@ async def modify_llm_settings_advanced(
|
|||||||
config,
|
config,
|
||||||
question='(Step 6/6) Memory Condensation (CTRL-c to cancel):',
|
question='(Step 6/6) Memory Condensation (CTRL-c to cancel):',
|
||||||
choices=['Enable', 'Disable'],
|
choices=['Enable', 'Disable'],
|
||||||
initial_selection=0 if config.enable_default_condenser else 1,
|
|
||||||
)
|
)
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
@@ -582,7 +463,6 @@ async def modify_llm_settings_advanced(
|
|||||||
config.default_agent = agent
|
config.default_agent = agent
|
||||||
|
|
||||||
config.security.confirmation_mode = enable_confirmation_mode
|
config.security.confirmation_mode = enable_confirmation_mode
|
||||||
config.enable_default_condenser = enable_memory_condensation
|
|
||||||
|
|
||||||
agent_config = config.get_agent_config(config.default_agent)
|
agent_config = config.get_agent_config(config.default_agent)
|
||||||
if enable_memory_condensation:
|
if enable_memory_condensation:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import warnings
|
|||||||
|
|
||||||
def suppress_cli_warnings():
|
def suppress_cli_warnings():
|
||||||
"""Suppress common warnings that appear during CLI usage."""
|
"""Suppress common warnings that appear during CLI usage."""
|
||||||
|
|
||||||
# Suppress pydub warning about ffmpeg/avconv
|
# Suppress pydub warning about ffmpeg/avconv
|
||||||
warnings.filterwarnings(
|
warnings.filterwarnings(
|
||||||
'ignore',
|
'ignore',
|
||||||
|
|||||||
@@ -239,7 +239,8 @@ def display_mcp_errors() -> None:
|
|||||||
|
|
||||||
# Prompt output display functions
|
# Prompt output display functions
|
||||||
def display_thought_if_new(thought: str, is_agent_message: bool = False) -> None:
|
def display_thought_if_new(thought: str, is_agent_message: bool = False) -> None:
|
||||||
"""Display a thought only if it hasn't been displayed recently.
|
"""
|
||||||
|
Display a thought only if it hasn't been displayed recently.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
thought: The thought to display
|
thought: The thought to display
|
||||||
@@ -300,7 +301,8 @@ def display_event(event: Event, config: OpenHandsConfig) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def display_message(message: str, is_agent_message: bool = False) -> None:
|
def display_message(message: str, is_agent_message: bool = False) -> None:
|
||||||
"""Display a message in the terminal with markdown rendering.
|
"""
|
||||||
|
Display a message in the terminal with markdown rendering.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message: The message to display
|
message: The message to display
|
||||||
@@ -336,7 +338,8 @@ def display_message(message: str, is_agent_message: bool = False) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def convert_markdown_to_html(text: str) -> str:
|
def convert_markdown_to_html(text: str) -> str:
|
||||||
"""Convert markdown to HTML for prompt_toolkit's HTML renderer using the markdown library.
|
"""
|
||||||
|
Convert markdown to HTML for prompt_toolkit's HTML renderer using the markdown library.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text: Markdown text to convert
|
text: Markdown text to convert
|
||||||
@@ -842,7 +845,6 @@ def cli_confirm(
|
|||||||
config: OpenHandsConfig,
|
config: OpenHandsConfig,
|
||||||
question: str = 'Are you sure?',
|
question: str = 'Are you sure?',
|
||||||
choices: list[str] | None = None,
|
choices: list[str] | None = None,
|
||||||
initial_selection: int = 0,
|
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Display a confirmation prompt with the given question and choices.
|
"""Display a confirmation prompt with the given question and choices.
|
||||||
|
|
||||||
@@ -850,7 +852,7 @@ def cli_confirm(
|
|||||||
"""
|
"""
|
||||||
if choices is None:
|
if choices is None:
|
||||||
choices = ['Yes', 'No']
|
choices = ['Yes', 'No']
|
||||||
selected = [initial_selection] # Using list to allow modification in closure
|
selected = [0] # Using list to allow modification in closure
|
||||||
|
|
||||||
def get_choice_text() -> list:
|
def get_choice_text() -> list:
|
||||||
return [
|
return [
|
||||||
@@ -906,6 +908,7 @@ def cli_confirm(
|
|||||||
layout=layout,
|
layout=layout,
|
||||||
key_bindings=kb,
|
key_bindings=kb,
|
||||||
style=style,
|
style=style,
|
||||||
|
mouse_support=True,
|
||||||
full_screen=False,
|
full_screen=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ def download_latest_vsix_from_github() -> str | None:
|
|||||||
|
|
||||||
|
|
||||||
def attempt_vscode_extension_install():
|
def attempt_vscode_extension_install():
|
||||||
"""Checks if running in a supported editor and attempts to install the OpenHands companion extension.
|
"""
|
||||||
|
Checks if running in a supported editor and attempts to install the OpenHands companion extension.
|
||||||
This is a best-effort, one-time attempt.
|
This is a best-effort, one-time attempt.
|
||||||
"""
|
"""
|
||||||
# 1. Check if we are in a supported editor environment
|
# 1. Check if we are in a supported editor environment
|
||||||
@@ -131,7 +132,8 @@ def attempt_vscode_extension_install():
|
|||||||
|
|
||||||
|
|
||||||
def _mark_installation_successful(flag_file: pathlib.Path, editor_name: str) -> None:
|
def _mark_installation_successful(flag_file: pathlib.Path, editor_name: str) -> None:
|
||||||
"""Mark the extension installation as successful by creating the flag file.
|
"""
|
||||||
|
Mark the extension installation as successful by creating the flag file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
flag_file: Path to the flag file to create
|
flag_file: Path to the flag file to create
|
||||||
@@ -145,7 +147,8 @@ def _mark_installation_successful(flag_file: pathlib.Path, editor_name: str) ->
|
|||||||
|
|
||||||
|
|
||||||
def _is_extension_installed(editor_command: str, extension_id: str) -> bool:
|
def _is_extension_installed(editor_command: str, extension_id: str) -> bool:
|
||||||
"""Check if the OpenHands extension is already installed.
|
"""
|
||||||
|
Check if the OpenHands extension is already installed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
editor_command: The command to run the editor (e.g., 'code', 'windsurf')
|
editor_command: The command to run the editor (e.g., 'code', 'windsurf')
|
||||||
@@ -171,7 +174,8 @@ def _is_extension_installed(editor_command: str, extension_id: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _attempt_github_install(editor_command: str, editor_name: str) -> bool:
|
def _attempt_github_install(editor_command: str, editor_name: str) -> bool:
|
||||||
"""Attempt to install the extension from GitHub Releases.
|
"""
|
||||||
|
Attempt to install the extension from GitHub Releases.
|
||||||
|
|
||||||
Downloads the latest VSIX file from GitHub releases and attempts to install it.
|
Downloads the latest VSIX file from GitHub releases and attempts to install it.
|
||||||
Ensures proper cleanup of temporary files.
|
Ensures proper cleanup of temporary files.
|
||||||
@@ -223,7 +227,8 @@ def _attempt_github_install(editor_command: str, editor_name: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _attempt_bundled_install(editor_command: str, editor_name: str) -> bool:
|
def _attempt_bundled_install(editor_command: str, editor_name: str) -> bool:
|
||||||
"""Attempt to install the extension from the bundled VSIX file.
|
"""
|
||||||
|
Attempt to install the extension from the bundled VSIX file.
|
||||||
|
|
||||||
Uses the VSIX file packaged with the OpenHands installation.
|
Uses the VSIX file packaged with the OpenHands installation.
|
||||||
|
|
||||||
@@ -275,7 +280,8 @@ def _attempt_bundled_install(editor_command: str, editor_name: str) -> bool:
|
|||||||
def _attempt_marketplace_install(
|
def _attempt_marketplace_install(
|
||||||
editor_command: str, editor_name: str, extension_id: str
|
editor_command: str, editor_name: str, extension_id: str
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Attempt to install the extension from the marketplace.
|
"""
|
||||||
|
Attempt to install the extension from the marketplace.
|
||||||
|
|
||||||
This method is currently unused as the OpenHands extension is not yet published
|
This method is currently unused as the OpenHands extension is not yet published
|
||||||
to the VS Code/Windsurf marketplace. It's kept here for future use when the
|
to the VS Code/Windsurf marketplace. It's kept here for future use when the
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ class Agent(ABC):
|
|||||||
return self._prompt_manager
|
return self._prompt_manager
|
||||||
|
|
||||||
def get_system_message(self) -> 'SystemMessageAction | None':
|
def get_system_message(self) -> 'SystemMessageAction | None':
|
||||||
"""Returns a SystemMessageAction containing the system message and tools.
|
"""
|
||||||
|
Returns a SystemMessageAction containing the system message and tools.
|
||||||
This will be added to the event stream as the first message.
|
This will be added to the event stream as the first message.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ class AgentController:
|
|||||||
status_callback: Optional callback function to handle status updates.
|
status_callback: Optional callback function to handle status updates.
|
||||||
replay_events: A list of logs to replay.
|
replay_events: A list of logs to replay.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.id = sid or event_stream.sid
|
self.id = sid or event_stream.sid
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.file_store = file_store
|
self.file_store = file_store
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ class ReplayManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def should_replay(self) -> bool:
|
def should_replay(self) -> bool:
|
||||||
"""Whether the controller is in trajectory replay mode, and the replay
|
"""
|
||||||
|
Whether the controller is in trajectory replay mode, and the replay
|
||||||
hasn't finished. Note: after the replay is finished, the user and
|
hasn't finished. Note: after the replay is finished, the user and
|
||||||
the agent could continue to message/act.
|
the agent could continue to message/act.
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ class TrafficControlState(str, Enum):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class State:
|
class State:
|
||||||
"""Represents the running state of an agent in the OpenHands system, saving data of its operation and memory.
|
"""
|
||||||
|
Represents the running state of an agent in the OpenHands system, saving data of its operation and memory.
|
||||||
|
|
||||||
- Multi-agent/delegate state:
|
- Multi-agent/delegate state:
|
||||||
- store the task (conversation between the agent and the user)
|
- store the task (conversation between the agent and the user)
|
||||||
@@ -142,7 +143,10 @@ class State:
|
|||||||
def restore_from_session(
|
def restore_from_session(
|
||||||
sid: str, file_store: FileStore, user_id: str | None = None
|
sid: str, file_store: FileStore, user_id: str | None = None
|
||||||
) -> 'State':
|
) -> 'State':
|
||||||
"""Restores the state from the previously saved session."""
|
"""
|
||||||
|
Restores the state from the previously saved session.
|
||||||
|
"""
|
||||||
|
|
||||||
state: State
|
state: State
|
||||||
try:
|
try:
|
||||||
encoded = file_store.read(
|
encoded = file_store.read(
|
||||||
|
|||||||
@@ -242,33 +242,41 @@ class StateTracker:
|
|||||||
self.state.budget_flag.increase_limit(headless_mode)
|
self.state.budget_flag.increase_limit(headless_mode)
|
||||||
|
|
||||||
def get_metrics_snapshot(self):
|
def get_metrics_snapshot(self):
|
||||||
"""Deep copy of metrics
|
"""
|
||||||
|
Deep copy of metrics
|
||||||
This serves as a snapshot for the parent's metrics at the time a delegate is created
|
This serves as a snapshot for the parent's metrics at the time a delegate is created
|
||||||
It will be stored and used to compute local metrics for the delegate
|
It will be stored and used to compute local metrics for the delegate
|
||||||
(since delegates now accumulate metrics from where its parent left off)
|
(since delegates now accumulate metrics from where its parent left off)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.state.metrics.copy()
|
return self.state.metrics.copy()
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
"""Save's current state to persistent store"""
|
"""
|
||||||
|
Save's current state to persistent store
|
||||||
|
"""
|
||||||
if self.sid and self.file_store:
|
if self.sid and self.file_store:
|
||||||
self.state.save_to_session(self.sid, self.file_store, self.user_id)
|
self.state.save_to_session(self.sid, self.file_store, self.user_id)
|
||||||
|
|
||||||
def run_control_flags(self):
|
def run_control_flags(self):
|
||||||
"""Performs one step of the control flags"""
|
"""
|
||||||
|
Performs one step of the control flags
|
||||||
|
"""
|
||||||
self.state.iteration_flag.step()
|
self.state.iteration_flag.step()
|
||||||
if self.state.budget_flag:
|
if self.state.budget_flag:
|
||||||
self.state.budget_flag.step()
|
self.state.budget_flag.step()
|
||||||
|
|
||||||
def sync_budget_flag_with_metrics(self):
|
def sync_budget_flag_with_metrics(self):
|
||||||
"""Ensures that budget flag is up to date with accumulated costs from llm completions
|
"""
|
||||||
|
Ensures that budget flag is up to date with accumulated costs from llm completions
|
||||||
Budget flag will monitor for when budget is exceeded
|
Budget flag will monitor for when budget is exceeded
|
||||||
"""
|
"""
|
||||||
if self.state.budget_flag:
|
if self.state.budget_flag:
|
||||||
self.state.budget_flag.current_value = self.state.metrics.accumulated_cost
|
self.state.budget_flag.current_value = self.state.metrics.accumulated_cost
|
||||||
|
|
||||||
def merge_metrics(self, metrics: Metrics):
|
def merge_metrics(self, metrics: Metrics):
|
||||||
"""Merges metrics with the state metrics
|
"""
|
||||||
|
Merges metrics with the state metrics
|
||||||
|
|
||||||
NOTE: this should be refactored in the future. We should have services (draft llm, title autocomplete, condenser, etc)
|
NOTE: this should be refactored in the future. We should have services (draft llm, title autocomplete, condenser, etc)
|
||||||
use their own LLMs, but the metrics object should be shared. This way we have one source of truth for accumulated costs from
|
use their own LLMs, but the metrics object should be shared. This way we have one source of truth for accumulated costs from
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ class KubernetesConfig(BaseModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toml_section(cls, data: dict) -> dict[str, 'KubernetesConfig']:
|
def from_toml_section(cls, data: dict) -> dict[str, 'KubernetesConfig']:
|
||||||
"""Create a mapping of KubernetesConfig instances from a toml dictionary representing the [kubernetes] section.
|
"""
|
||||||
|
Create a mapping of KubernetesConfig instances from a toml dictionary representing the [kubernetes] section.
|
||||||
|
|
||||||
The configuration is built from all keys in data.
|
The configuration is built from all keys in data.
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ class LLMConfig(BaseModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toml_section(cls, data: dict) -> dict[str, LLMConfig]:
|
def from_toml_section(cls, data: dict) -> dict[str, LLMConfig]:
|
||||||
"""Create a mapping of LLMConfig instances from a toml dictionary representing the [llm] section.
|
"""
|
||||||
|
Create a mapping of LLMConfig instances from a toml dictionary representing the [llm] section.
|
||||||
|
|
||||||
The default configuration is built from all non-dict keys in data.
|
The default configuration is built from all non-dict keys in data.
|
||||||
Then, each key with a dict value (e.g. [llm.random_name]) is treated as a custom LLM configuration,
|
Then, each key with a dict value (e.g. [llm.random_name]) is treated as a custom LLM configuration,
|
||||||
@@ -116,6 +117,7 @@ class LLMConfig(BaseModel):
|
|||||||
dict[str, LLMConfig]: A mapping where the key "llm" corresponds to the default configuration
|
dict[str, LLMConfig]: A mapping where the key "llm" corresponds to the default configuration
|
||||||
and additional keys represent custom configurations.
|
and additional keys represent custom configurations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Initialize the result mapping
|
# Initialize the result mapping
|
||||||
llm_mapping: dict[str, LLMConfig] = {}
|
llm_mapping: dict[str, LLMConfig] = {}
|
||||||
|
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ class OpenHandsMCPConfig:
|
|||||||
Returns:
|
Returns:
|
||||||
tuple[MCPSHTTPServerConfig | None, list[MCPStdioServerConfig]]: A tuple containing the default SHTTP server configuration (or None) and a list of MCP stdio server configurations
|
tuple[MCPSHTTPServerConfig | None, list[MCPStdioServerConfig]]: A tuple containing the default SHTTP server configuration (or None) and a list of MCP stdio server configurations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
stdio_servers = []
|
stdio_servers = []
|
||||||
search_engine_stdio_server = OpenHandsMCPConfig.add_search_engine(config)
|
search_engine_stdio_server = OpenHandsMCPConfig.add_search_engine(config)
|
||||||
if search_engine_stdio_server:
|
if search_engine_stdio_server:
|
||||||
|
|||||||