mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
22 Commits
fix-login-
...
openhands-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
378261ae00 | ||
|
|
cb0a1c91e4 | ||
|
|
e208bffade | ||
|
|
6491142364 | ||
|
|
205f0234e8 | ||
|
|
c76809a766 | ||
|
|
9f86f731a7 | ||
|
|
6fe5da810b | ||
|
|
52a1e94335 | ||
|
|
3e0532e8b9 | ||
|
|
90c440d709 | ||
|
|
82657b7ba1 | ||
|
|
3c51600260 | ||
|
|
b5f2a04ea2 | ||
|
|
155615bbb1 | ||
|
|
4b6f2aeb4d | ||
|
|
0023eb0982 | ||
|
|
c3ab4b480b | ||
|
|
35f7efb9d7 | ||
|
|
14498c5e25 | ||
|
|
cdb9aeb9ba | ||
|
|
318883e5e0 |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -161,7 +161,6 @@ cython_debug/
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
.cursorignore
|
||||
|
||||
# VS Code: Ignore all but certain files that specify repo-specific settings.
|
||||
# https://stackoverflow.com/questions/32964920/should-i-commit-the-vscode-folder-to-source-control
|
||||
@@ -171,6 +170,20 @@ cython_debug/
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
# VS Code extensions/forks:
|
||||
.cursorignore
|
||||
.rooignore
|
||||
.clineignore
|
||||
.windsurfignore
|
||||
.cursorrules
|
||||
.roorules
|
||||
.clinerules
|
||||
.windsurfrules
|
||||
.cursor/rules
|
||||
.roo/rules
|
||||
.cline/rules
|
||||
.windsurf/rules
|
||||
|
||||
# evaluation
|
||||
evaluation/evaluation_outputs
|
||||
evaluation/outputs
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Development Guide
|
||||
|
||||
This guide is for people working on OpenHands and editing the source code.
|
||||
If you wish to contribute your changes, check out the [CONTRIBUTING.md](https://github.com/All-Hands-AI/OpenHands/blob/main/CONTRIBUTING.md) on how to clone and setup the project
|
||||
initially before moving on. Otherwise, you can clone the OpenHands project directly.
|
||||
If you wish to contribute your changes, check out the
|
||||
[CONTRIBUTING.md](https://github.com/All-Hands-AI/OpenHands/blob/main/CONTRIBUTING.md)
|
||||
on how to clone and setup the project initially before moving on. Otherwise,
|
||||
you can clone the OpenHands project directly.
|
||||
|
||||
## Start the Server for Development
|
||||
|
||||
@@ -19,9 +21,20 @@ initially before moving on. Otherwise, you can clone the OpenHands project direc
|
||||
|
||||
Make sure you have all these dependencies installed before moving on to `make build`.
|
||||
|
||||
#### Dev container
|
||||
|
||||
There is a [dev container](https://containers.dev/) available which provides a
|
||||
pre-configured environment with all the necessary dependencies installed if you
|
||||
are using a [supported editor or tool](https://containers.dev/supporting). For
|
||||
example, if you are using Visual Studio Code (VS Code) with the
|
||||
[Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||
extension installed, you can open the project in a dev container by using the
|
||||
_Dev Container: Reopen in Container_ command from the Command Palette
|
||||
(Ctrl+Shift+P).
|
||||
|
||||
#### Develop without sudo access
|
||||
|
||||
If you want to develop without system admin/sudo access to upgrade/install `Python` and/or `NodeJs`, you can use
|
||||
If you want to develop without system admin/sudo access to upgrade/install `Python` and/or `NodeJs`, you can use
|
||||
`conda` or `mamba` to manage the packages for you:
|
||||
|
||||
```bash
|
||||
@@ -37,7 +50,7 @@ mamba install conda-forge::poetry
|
||||
|
||||
### 2. Build and Setup The Environment
|
||||
|
||||
Begin by building the project which includes setting up the environment and installing dependencies. This step ensures
|
||||
Begin by building the project which includes setting up the environment and installing dependencies. This step ensures
|
||||
that OpenHands is ready to run on your system:
|
||||
|
||||
```bash
|
||||
@@ -54,11 +67,11 @@ To configure the LM of your choice, run:
|
||||
make setup-config
|
||||
```
|
||||
|
||||
This command will prompt you to enter the LLM API key, model name, and other variables ensuring that OpenHands is
|
||||
tailored to your specific needs. Note that the model name will apply only when you run headless. If you use the UI,
|
||||
This command will prompt you to enter the LLM API key, model name, and other variables ensuring that OpenHands is
|
||||
tailored to your specific needs. Note that the model name will apply only when you run headless. If you use the UI,
|
||||
please set the model in the UI.
|
||||
|
||||
Note: If you have previously run OpenHands using the docker command, you may have already set some environmental
|
||||
Note: If you have previously run OpenHands using the docker command, you may have already set some environmental
|
||||
variables in your terminal. The final configurations are set from highest to lowest priority:
|
||||
Environment variables > config.toml variables > default variables
|
||||
|
||||
@@ -77,14 +90,14 @@ make run
|
||||
|
||||
#### Option B: Individual Server Startup
|
||||
|
||||
- **Start the Backend Server:** If you prefer, you can start the backend server independently to focus on
|
||||
- **Start the Backend Server:** If you prefer, you can start the backend server independently to focus on
|
||||
backend-related tasks or configurations.
|
||||
|
||||
```bash
|
||||
make start-backend
|
||||
```
|
||||
|
||||
- **Start the Frontend Server:** Similarly, you can start the frontend server on its own to work on frontend-related
|
||||
- **Start the Frontend Server:** Similarly, you can start the frontend server on its own to work on frontend-related
|
||||
components or interface enhancements.
|
||||
```bash
|
||||
make start-frontend
|
||||
@@ -120,7 +133,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
|
||||
### 9. Use existing Docker image
|
||||
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.39-nikolaik`
|
||||
|
||||
3594
docs/package-lock.json
generated
3594
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,10 +17,10 @@
|
||||
},
|
||||
"// Note": "The OpenAPI spec is stored in docs/static/openapi.json so it's accessible at /openapi.json in the deployed site",
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/plugin-content-pages": "^3.7.0",
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@docusaurus/core": "^3.8.0",
|
||||
"@docusaurus/plugin-content-pages": "^3.8.0",
|
||||
"@docusaurus/preset-classic": "^3.8.0",
|
||||
"@docusaurus/theme-mermaid": "^3.8.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@node-rs/jieba": "^2.0.1",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.5.1",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/tsconfig": "^3.8.0",
|
||||
"@docusaurus/types": "^3.5.1",
|
||||
"swagger-cli": "^4.0.4",
|
||||
"swagger-ui-dist": "^5.22.0",
|
||||
|
||||
@@ -17,7 +17,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -59,10 +59,10 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -25,7 +25,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -39,11 +39,11 @@ from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-slim'
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
|
||||
@@ -24,7 +24,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
load_from_toml,
|
||||
parse_arguments,
|
||||
@@ -46,10 +46,10 @@ SKIP_NUM = (
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.11-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
|
||||
@@ -22,7 +22,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -55,12 +55,12 @@ FILE_EXT_MAP = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
BIOCODER_BENCH_CONTAINER_IMAGE = 'public.ecr.aws/i5g0m1f6/eval_biocoder:v1.0'
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = BIOCODER_BENCH_CONTAINER_IMAGE
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -25,7 +25,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -70,11 +70,11 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -18,7 +18,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -33,13 +33,13 @@ SUPPORTED_AGENT_CLS = {'CodeActAgent'}
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
assert metadata.max_iterations == 1, (
|
||||
'max_iterations must be 1 for browsing delegation evaluation.'
|
||||
)
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -25,7 +25,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -101,7 +101,7 @@ def get_instance_docker_image(repo_name: str) -> str:
|
||||
def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
repo_name = instance['repo'].split('/')[1]
|
||||
base_container_image = get_instance_docker_image(repo_name)
|
||||
logger.info(
|
||||
@@ -113,7 +113,7 @@ def get_config(
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = base_container_image
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
max_iterations=metadata.max_iterations,
|
||||
|
||||
@@ -25,7 +25,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -61,10 +61,10 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -21,7 +21,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -47,10 +47,10 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -19,7 +19,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -39,10 +39,10 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -37,7 +37,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -60,10 +60,10 @@ ACTION_FORMAT = """
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -30,7 +30,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -81,10 +81,10 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -19,10 +19,10 @@ from evaluation.utils.shared import (
|
||||
make_metadata,
|
||||
)
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_parser,
|
||||
load_app_config,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime
|
||||
@@ -34,10 +34,10 @@ from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
@@ -53,7 +53,7 @@ def get_config(
|
||||
return config
|
||||
|
||||
|
||||
config = load_app_config()
|
||||
config = load_openhands_config()
|
||||
|
||||
|
||||
def load_bench_config():
|
||||
|
||||
@@ -29,10 +29,10 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_app_config,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -44,10 +44,10 @@ from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
@@ -63,7 +63,7 @@ def get_config(
|
||||
return config
|
||||
|
||||
|
||||
config = load_app_config()
|
||||
config = load_openhands_config()
|
||||
|
||||
|
||||
def load_bench_config():
|
||||
|
||||
@@ -17,7 +17,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -44,14 +44,14 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'xingyaoww/od-eval-logic-reasoning:v1.0'
|
||||
sandbox_config.runtime_extra_deps = (
|
||||
'$OH_INTERPRETER_PATH -m pip install scitools-pyke'
|
||||
)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -21,7 +21,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -54,10 +54,10 @@ AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
env_id: str,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'xingyaoww/od-eval-miniwob:v1.0'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
|
||||
@@ -22,7 +22,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -102,14 +102,14 @@ def load_incontext_example(task_name: str, with_tool: bool = True):
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'xingyaoww/od-eval-mint:v1.0'
|
||||
sandbox_config.runtime_extra_deps = (
|
||||
f'$OH_INTERPRETER_PATH -m pip install {" ".join(MINT_DEPENDENCIES)}'
|
||||
)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -4,11 +4,11 @@ import pprint
|
||||
|
||||
import tqdm
|
||||
|
||||
from openhands.core.config import get_llm_config_arg, get_parser, load_app_config
|
||||
from openhands.core.config import get_llm_config_arg, get_parser, load_openhands_config
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.llm.llm import LLM
|
||||
|
||||
config = load_app_config()
|
||||
config = load_openhands_config()
|
||||
|
||||
|
||||
def extract_test_results(res_file_path: str) -> tuple[list[str], list[str]]:
|
||||
|
||||
@@ -33,10 +33,10 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_app_config,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -45,7 +45,7 @@ from openhands.events.observation import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
config = load_app_config()
|
||||
config = load_openhands_config()
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': codeact_user_response,
|
||||
@@ -76,10 +76,10 @@ ID2CONDA = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'public.ecr.aws/i5g0m1f6/ml-bench'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -28,8 +28,8 @@ from evaluation.utils.shared import (
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -73,7 +73,7 @@ def process_git_patch(patch):
|
||||
return patch
|
||||
|
||||
|
||||
def get_config(metadata: EvalMetadata, instance: pd.Series) -> AppConfig:
|
||||
def get_config(metadata: EvalMetadata, instance: pd.Series) -> OpenHandsConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
base_container_image = get_instance_docker_image(instance['instance_id'])
|
||||
logger.info(
|
||||
@@ -87,7 +87,7 @@ def get_config(metadata: EvalMetadata, instance: pd.Series) -> AppConfig:
|
||||
dataset_name=metadata.dataset,
|
||||
instance_id=instance['instance_id'],
|
||||
)
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
sandbox=sandbox_config,
|
||||
|
||||
@@ -30,7 +30,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -314,7 +314,7 @@ def get_instance_docker_image(instance: pd.Series):
|
||||
def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
SWE_BENCH_CONTAINER_IMAGE = 'ghcr.io/opendevin/eval-swe-bench:full-v1.2.1'
|
||||
if USE_INSTANCE_IMAGE:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
@@ -340,7 +340,7 @@ def get_config(
|
||||
instance_id=instance['instance_id'],
|
||||
)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
max_iterations=metadata.max_iterations,
|
||||
|
||||
@@ -20,7 +20,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -58,12 +58,12 @@ def format_task_dict(example, use_knowledge):
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
instance_id: str,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = (
|
||||
'docker.io/xingyaoww/openhands-eval-scienceagentbench'
|
||||
)
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This folder contains the evaluation harness that we built on top of the original [SWE-Bench benchmark](https://www.swebench.com/) ([paper](https://arxiv.org/abs/2310.06770)).
|
||||
|
||||
**UPDATE (5/26/2025): We now support running interactive SWE-Bench evaluation (see the paper [here](https://arxiv.org/abs/2502.13069))! For how to run it, checkout [this README](./SWE-Interact.md).**
|
||||
|
||||
**UPDATE (4/8/2025): We now support running SWT-Bench evaluation! For more details, checkout [the corresponding section](#SWT-Bench-Evaluation).**
|
||||
|
||||
**UPDATE (03/27/2025): We now support SWE-Bench multimodal evaluation! Simply use "princeton-nlp/SWE-bench_Multimodal" as the dataset name in the `run_infer.sh` script to evaluate on multimodal instances.**
|
||||
|
||||
92
evaluation/benchmarks/swe_bench/SWE-Interact.md
Normal file
92
evaluation/benchmarks/swe_bench/SWE-Interact.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# SWE-Interact Benchmark
|
||||
|
||||
This document explains how to use the [Interactive SWE-Bench](https://arxiv.org/abs/2502.13069) benchmark scripts for running and evaluating interactive software engineering tasks.
|
||||
|
||||
## Setting things up
|
||||
After following the [README](./README.md) to set up the environment, you would need to additionally add LLM configurations for simulated human users. In the original [paper](https://arxiv.org/abs/2502.13069), we use gpt-4o as the simulated human user. You can add the following to your `config.toml` file:
|
||||
|
||||
```toml
|
||||
[llm.fake_user]
|
||||
model="litellm_proxy/gpt-4o-2024-08-06"
|
||||
api_key="<your-api-key>"
|
||||
temperature = 0.0
|
||||
base_url = "https://llm-proxy.eval.all-hands.dev"
|
||||
```
|
||||
|
||||
## Running the Benchmark
|
||||
|
||||
The main script for running the benchmark is `run_infer_interact.sh`. Here's how to use it:
|
||||
|
||||
```bash
|
||||
bash ./evaluation/benchmarks/swe_bench/scripts/run_infer_interact.sh <model_config> <commit_hash> <agent> <eval_limit> <max_iter> <num_workers> <split>
|
||||
```
|
||||
|
||||
### Parameters:
|
||||
|
||||
- `model_config`: Path to the LLM configuration file (e.g., `llm.claude-3-7-sonnet`)
|
||||
- `commit_hash`: Git commit hash to use (e.g., `HEAD`)
|
||||
- `agent`: The agent class to use (e.g., `CodeActAgent`)
|
||||
- `eval_limit`: Number of examples to evaluate (e.g., `500`)
|
||||
- `max_iter`: Maximum number of iterations per task (e.g., `100`)
|
||||
- `num_workers`: Number of parallel workers (e.g., `1`)
|
||||
- `split`: Dataset split to use (e.g., `test`)
|
||||
|
||||
### Example:
|
||||
|
||||
```bash
|
||||
bash ./evaluation/benchmarks/swe_bench/scripts/run_infer_interact.sh llm.claude-3-7-sonnet HEAD CodeActAgent 500 100 1 test
|
||||
```
|
||||
|
||||
### Additional Environment Variables:
|
||||
|
||||
You can customize the behavior using these environment variables:
|
||||
|
||||
- `RUN_WITH_BROWSING`: Enable/disable web browsing (default: false)
|
||||
- `USE_HINT_TEXT`: Enable/disable hint text (default: false)
|
||||
- `EVAL_CONDENSER`: Specify a condenser configuration
|
||||
- `EXP_NAME`: Add a custom experiment name to the output
|
||||
- `N_RUNS`: Number of runs to perform (default: 1)
|
||||
- `SKIP_RUNS`: Comma-separated list of run numbers to skip
|
||||
|
||||
## Evaluating Results
|
||||
|
||||
After running the benchmark, you can evaluate the results using `eval_infer.sh`:
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/eval_infer.sh <output_file> <instance_id> <dataset> <split>
|
||||
```
|
||||
|
||||
### Parameters:
|
||||
|
||||
- `output_file`: Path to the output JSONL file
|
||||
- `instance_id`: The specific instance ID to evaluate
|
||||
- `dataset`: Dataset name (e.g., `cmu-lti/interactive-swe`)
|
||||
- `split`: Dataset split (e.g., `test`)
|
||||
|
||||
### Example:
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/eval_infer.sh evaluation/evaluation_outputs/outputs/cmu-lti__interactive-swe-test/CodeActAgent/claude-3-7-sonnet-20250219_maxiter_100_N_v0.39.0-no-hint-run_1/output.jsonl sphinx-doc__sphinx-8721 cmu-lti/interactive-swe test
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
The benchmark outputs are stored in the `evaluation/evaluation_outputs/outputs/` directory with the following structure:
|
||||
|
||||
```
|
||||
evaluation/evaluation_outputs/outputs/
|
||||
└── cmu-lti__interactive-swe-{split}/
|
||||
└── {agent}/
|
||||
└── {model}-{date}_maxiter_{max_iter}_N_{version}-{options}-run_{run_number}/
|
||||
└── output.jsonl
|
||||
```
|
||||
|
||||
Where:
|
||||
- `{split}` is the dataset split (e.g., test)
|
||||
- `{agent}` is the agent class name
|
||||
- `{model}` is the model name
|
||||
- `{date}` is the run date
|
||||
- `{max_iter}` is the maximum iterations
|
||||
- `{version}` is the OpenHands version
|
||||
- `{options}` includes any additional options (e.g., no-hint, with-browsing)
|
||||
- `{run_number}` is the run number
|
||||
@@ -24,8 +24,8 @@ from evaluation.utils.shared import (
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -69,7 +69,7 @@ def process_git_patch(patch):
|
||||
return patch
|
||||
|
||||
|
||||
def get_config(metadata: EvalMetadata, instance: pd.Series) -> AppConfig:
|
||||
def get_config(metadata: EvalMetadata, instance: pd.Series) -> OpenHandsConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
base_container_image = get_instance_docker_image(instance['instance_id'])
|
||||
logger.info(
|
||||
@@ -83,7 +83,7 @@ def get_config(metadata: EvalMetadata, instance: pd.Series) -> AppConfig:
|
||||
dataset_name=metadata.dataset,
|
||||
instance_id=instance['instance_id'],
|
||||
)
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
sandbox=sandbox_config,
|
||||
|
||||
@@ -40,12 +40,12 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.config.utils import get_condenser_config_arg
|
||||
from openhands.core.config.condenser_config import NoOpCondenserConfig
|
||||
from openhands.core.config.utils import get_condenser_config_arg
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.critic import AgentFinishedCritic
|
||||
@@ -220,7 +220,7 @@ def get_instance_docker_image(
|
||||
def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
use_swebench_official_image = 'swe-gym' not in metadata.dataset.lower()
|
||||
base_container_image = get_instance_docker_image(
|
||||
@@ -244,7 +244,7 @@ def get_config(
|
||||
instance_id=instance['instance_id'],
|
||||
)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
max_iterations=metadata.max_iterations,
|
||||
@@ -721,15 +721,16 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame:
|
||||
# repos for the swe-bench instances:
|
||||
# ['astropy/astropy', 'django/django', 'matplotlib/matplotlib', 'mwaskom/seaborn', 'pallets/flask', 'psf/requests', 'pydata/xarray', 'pylint-dev/pylint', 'pytest-dev/pytest', 'scikit-learn/scikit-learn', 'sphinx-doc/sphinx', 'sympy/sympy']
|
||||
selected_repos = data['selected_repos']
|
||||
if isinstance(selected_repos, str): selected_repos = [selected_repos]
|
||||
if isinstance(selected_repos, str):
|
||||
selected_repos = [selected_repos]
|
||||
assert isinstance(selected_repos, list)
|
||||
logger.info(
|
||||
f'Filtering {selected_repos} tasks from "selected_repos"...'
|
||||
)
|
||||
subset = dataset[dataset["repo"].isin(selected_repos)]
|
||||
subset = dataset[dataset['repo'].isin(selected_repos)]
|
||||
logger.info(f'Retained {subset.shape[0]} tasks after filtering')
|
||||
return subset
|
||||
|
||||
|
||||
skip_ids = os.environ.get('SKIP_IDS', '').split(',')
|
||||
if len(skip_ids) > 0:
|
||||
logger.info(f'Filtering {len(skip_ids)} tasks from "SKIP_IDS"...')
|
||||
@@ -806,7 +807,9 @@ if __name__ == '__main__':
|
||||
else:
|
||||
# If no specific condenser config is provided via env var, default to NoOpCondenser
|
||||
condenser_config = NoOpCondenserConfig()
|
||||
logger.debug('No Condenser config provided via EVAL_CONDENSER, using NoOpCondenser.')
|
||||
logger.debug(
|
||||
'No Condenser config provided via EVAL_CONDENSER, using NoOpCondenser.'
|
||||
)
|
||||
|
||||
details = {'mode': args.mode}
|
||||
_agent_cls = openhands.agenthub.Agent.get_cls(args.agent_cls)
|
||||
|
||||
411
evaluation/benchmarks/swe_bench/run_infer_interact.py
Executable file
411
evaluation/benchmarks/swe_bench/run_infer_interact.py
Executable file
@@ -0,0 +1,411 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
from datasets import load_dataset
|
||||
from litellm import completion as litellm_completion
|
||||
|
||||
import openhands.agenthub
|
||||
from evaluation.benchmarks.swe_bench.run_infer import (
|
||||
AgentFinishedCritic,
|
||||
complete_runtime,
|
||||
filter_dataset,
|
||||
get_config,
|
||||
initialize_runtime,
|
||||
)
|
||||
from evaluation.benchmarks.swe_bench.run_infer import (
|
||||
get_instruction as base_get_instruction,
|
||||
)
|
||||
from evaluation.utils.shared import (
|
||||
EvalException,
|
||||
EvalMetadata,
|
||||
EvalOutput,
|
||||
make_metadata,
|
||||
prepare_dataset,
|
||||
reset_logger_for_multiprocessing,
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.config.condenser_config import NoOpCondenserConfig
|
||||
from openhands.core.config.utils import get_condenser_config_arg
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.events.serialization.event import event_from_dict, event_to_dict
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'
|
||||
USE_INSTANCE_IMAGE = os.environ.get('USE_INSTANCE_IMAGE', 'false').lower() == 'true'
|
||||
RUN_WITH_BROWSING = os.environ.get('RUN_WITH_BROWSING', 'false').lower() == 'false'
|
||||
|
||||
|
||||
class FakeUser:
|
||||
def __init__(self, issue, hints, files):
|
||||
self.system_message = f"""
|
||||
You are a GitHub user reporting an issue. Here are the details of your issue and environment:
|
||||
|
||||
Issue: {issue}
|
||||
|
||||
Hints: {hints}
|
||||
|
||||
Files relative to your current directory: {files}
|
||||
|
||||
Your task is to respond to questions from a coder who is trying to solve your issue. The coder has a summarized version of the issue you have. Follow these rules:
|
||||
1. If the coder asks a question that is directly related to the information in the issue you have, provide that information.
|
||||
2. Always stay in character as a user reporting an issue, not as an AI assistant.
|
||||
3. Keep your responses concise and to the point.
|
||||
4. The coder has limited turns to solve the issue. Do not interact with the coder beyond 3 turns.
|
||||
|
||||
Respond with "I don't have that information" if the question is unrelated or you're unsure.
|
||||
"""
|
||||
self.chat_history = [{'role': 'system', 'content': self.system_message}]
|
||||
self.turns = 0
|
||||
# Get LLM config from config.toml
|
||||
self.llm_config = get_llm_config_arg(
|
||||
'llm.fake_user'
|
||||
) # You can change 'fake_user' to any config name you want
|
||||
|
||||
def generate_reply(self, question):
|
||||
if self.turns > 3:
|
||||
return 'Please continue working on the task. Do NOT ask for more help.'
|
||||
self.chat_history.append({'role': 'user', 'content': question.content})
|
||||
|
||||
response = litellm_completion(
|
||||
model=self.llm_config.model,
|
||||
messages=self.chat_history,
|
||||
api_key=self.llm_config.api_key.get_secret_value(),
|
||||
temperature=self.llm_config.temperature,
|
||||
base_url=self.llm_config.base_url,
|
||||
)
|
||||
|
||||
reply = response.choices[0].message.content
|
||||
self.chat_history.append({'role': 'assistant', 'content': reply})
|
||||
self.turns += 1
|
||||
return reply
|
||||
|
||||
|
||||
# Global variable for fake user
|
||||
fake_user = None
|
||||
|
||||
|
||||
def get_fake_user_response(state: State) -> str:
|
||||
global fake_user
|
||||
if not fake_user:
|
||||
return 'Please continue working on the task.'
|
||||
last_agent_message = state.get_last_agent_message()
|
||||
if last_agent_message:
|
||||
return fake_user.generate_reply(last_agent_message)
|
||||
return 'Please continue working on the task.'
|
||||
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': get_fake_user_response,
|
||||
}
|
||||
|
||||
|
||||
def get_instruction(instance: pd.Series, metadata: EvalMetadata) -> MessageAction:
|
||||
instance_copy = instance.copy()
|
||||
instance_copy.problem_statement = f'{instance.problem_statement}\n\nHints:\nThe user has not provided all the necessary details about the issue, and there are some hidden details that are helpful. Please ask the user specific questions using non-code commands to gather the relevant information that the user has to help you solve the issue. Ensure you have all the details you require to solve the issue.'
|
||||
return base_get_instruction(instance_copy, metadata)
|
||||
|
||||
|
||||
def process_instance(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
reset_logger: bool = True,
|
||||
) -> EvalOutput:
|
||||
config = get_config(instance, metadata)
|
||||
global fake_user
|
||||
original_issue = instance.original_issue
|
||||
issue = str(original_issue)
|
||||
fake_user = FakeUser(issue=issue, hints=instance.hints_text, files=instance.files)
|
||||
|
||||
# Setup the logger properly, so you can run multi-processing to parallelize the evaluation
|
||||
if reset_logger:
|
||||
log_dir = os.path.join(metadata.eval_output_dir, 'infer_logs')
|
||||
reset_logger_for_multiprocessing(logger, instance.instance_id, log_dir)
|
||||
else:
|
||||
logger.info(f'Starting evaluation for instance {instance.instance_id}.')
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
try:
|
||||
initialize_runtime(runtime, instance, metadata)
|
||||
|
||||
message_action = get_instruction(instance, metadata)
|
||||
|
||||
# Here's how you can run the agent (similar to the `main` function) and get the final task state
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
config=config,
|
||||
initial_user_action=message_action,
|
||||
runtime=runtime,
|
||||
fake_user_response_fn=AGENT_CLS_TO_FAKE_USER_RESPONSE_FN[
|
||||
metadata.agent_class
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# if fatal error, throw EvalError to trigger re-run
|
||||
if (
|
||||
state
|
||||
and state.last_error
|
||||
and 'fatal error during agent execution' in state.last_error
|
||||
and 'stuck in a loop' not in state.last_error
|
||||
):
|
||||
raise EvalException('Fatal error detected: ' + state.last_error)
|
||||
|
||||
# Get git patch
|
||||
return_val = complete_runtime(runtime, instance)
|
||||
git_patch = return_val['git_patch']
|
||||
logger.info(
|
||||
f'Got git diff for instance {instance.instance_id}:\n--------\n{git_patch}\n--------'
|
||||
)
|
||||
finally:
|
||||
runtime.close()
|
||||
|
||||
# Prepare test result
|
||||
test_result = {
|
||||
'git_patch': git_patch,
|
||||
}
|
||||
|
||||
if state is None:
|
||||
raise ValueError('State should not be None.')
|
||||
|
||||
histories = [event_to_dict(event) for event in state.history]
|
||||
metrics = state.metrics.get() if state.metrics else None
|
||||
|
||||
# Save the output
|
||||
instruction = message_action.content
|
||||
if message_action.image_urls:
|
||||
instruction += (
|
||||
'\n\n<image_urls>' + '\n'.join(message_action.image_urls) + '</image_urls>'
|
||||
)
|
||||
output = EvalOutput(
|
||||
instance_id=instance.instance_id,
|
||||
instruction=instruction,
|
||||
instance=instance.to_dict(),
|
||||
test_result=test_result,
|
||||
metadata=metadata,
|
||||
history=histories,
|
||||
metrics=metrics,
|
||||
error=state.last_error if state and state.last_error else None,
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
default='cmu-lti/interactive-swe',
|
||||
help='dataset to evaluate on',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--split',
|
||||
type=str,
|
||||
default='test',
|
||||
help='split to evaluate on',
|
||||
)
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
# Load dataset from huggingface datasets
|
||||
dataset = load_dataset(args.dataset, split=args.split)
|
||||
swe_bench_tests = filter_dataset(dataset.to_pandas(), 'instance_id')
|
||||
logger.info(
|
||||
f'Loaded dataset {args.dataset} with split {args.split}: {len(swe_bench_tests)} tasks'
|
||||
)
|
||||
llm_config = None
|
||||
if args.llm_config:
|
||||
llm_config = get_llm_config_arg(args.llm_config)
|
||||
llm_config.log_completions = True
|
||||
# modify_params must be False for evaluation purpose, for reproducibility and accurancy of results
|
||||
llm_config.modify_params = False
|
||||
|
||||
if llm_config is None:
|
||||
raise ValueError(f'Could not find LLM config: --llm_config {args.llm_config}')
|
||||
|
||||
# Get condenser config from environment variable
|
||||
condenser_name = os.environ.get('EVAL_CONDENSER')
|
||||
if condenser_name:
|
||||
condenser_config = get_condenser_config_arg(condenser_name)
|
||||
if condenser_config is None:
|
||||
raise ValueError(
|
||||
f'Could not find Condenser config: EVAL_CONDENSER={condenser_name}'
|
||||
)
|
||||
else:
|
||||
# If no specific condenser config is provided via env var, default to NoOpCondenser
|
||||
condenser_config = NoOpCondenserConfig()
|
||||
logger.debug(
|
||||
'No Condenser config provided via EVAL_CONDENSER, using NoOpCondenser.'
|
||||
)
|
||||
|
||||
details = {'mode': 'interact'}
|
||||
_agent_cls = openhands.agenthub.Agent.get_cls(args.agent_cls)
|
||||
|
||||
dataset_descrption = (
|
||||
args.dataset.replace('/', '__') + '-' + args.split.replace('/', '__')
|
||||
)
|
||||
metadata = make_metadata(
|
||||
llm_config,
|
||||
dataset_descrption,
|
||||
args.agent_cls,
|
||||
args.max_iterations,
|
||||
args.eval_note,
|
||||
args.eval_output_dir,
|
||||
details=details,
|
||||
condenser_config=condenser_config,
|
||||
)
|
||||
|
||||
output_file = os.path.join(metadata.eval_output_dir, 'output.jsonl')
|
||||
print(f'### OUTPUT FILE: {output_file} ###')
|
||||
|
||||
# Run evaluation in iterative mode:
|
||||
# If a rollout fails to output AgentFinishAction, we will try again until it succeeds OR total 3 attempts have been made.
|
||||
ITERATIVE_EVAL_MODE = (
|
||||
os.environ.get('ITERATIVE_EVAL_MODE', 'false').lower() == 'true'
|
||||
)
|
||||
ITERATIVE_EVAL_MODE_MAX_ATTEMPTS = int(
|
||||
os.environ.get('ITERATIVE_EVAL_MODE_MAX_ATTEMPTS', '3')
|
||||
)
|
||||
|
||||
if not ITERATIVE_EVAL_MODE:
|
||||
# load the dataset
|
||||
instances = prepare_dataset(swe_bench_tests, output_file, args.eval_n_limit)
|
||||
if len(instances) > 0 and not isinstance(
|
||||
instances['PASS_TO_PASS'][instances['PASS_TO_PASS'].index[0]], str
|
||||
):
|
||||
for col in ['PASS_TO_PASS', 'FAIL_TO_PASS']:
|
||||
instances[col] = instances[col].apply(lambda x: str(x))
|
||||
run_evaluation(
|
||||
instances,
|
||||
metadata,
|
||||
output_file,
|
||||
args.eval_num_workers,
|
||||
process_instance,
|
||||
timeout_seconds=8
|
||||
* 60
|
||||
* 60, # 8 hour PER instance should be more than enough
|
||||
max_retries=5,
|
||||
)
|
||||
else:
|
||||
critic = AgentFinishedCritic()
|
||||
|
||||
def get_cur_output_file_path(attempt: int) -> str:
|
||||
return (
|
||||
f'{output_file.removesuffix(".jsonl")}.critic_attempt_{attempt}.jsonl'
|
||||
)
|
||||
|
||||
eval_ids = None
|
||||
for attempt in range(1, ITERATIVE_EVAL_MODE_MAX_ATTEMPTS + 1):
|
||||
cur_output_file = get_cur_output_file_path(attempt)
|
||||
logger.info(
|
||||
f'Running evaluation with critic {critic.__class__.__name__} for attempt {attempt} of {ITERATIVE_EVAL_MODE_MAX_ATTEMPTS}.'
|
||||
)
|
||||
|
||||
# For deterministic eval, we set temperature to 0.1 for (>1) attempt
|
||||
# so hopefully we get slightly different results
|
||||
if attempt > 1 and metadata.llm_config.temperature == 0:
|
||||
logger.info(
|
||||
f'Detected temperature is 0 for (>1) attempt {attempt}. Setting temperature to 0.1...'
|
||||
)
|
||||
metadata.llm_config.temperature = 0.1
|
||||
|
||||
# Load instances - at first attempt, we evaluate all instances
|
||||
# On subsequent attempts, we only evaluate the instances that failed the previous attempt determined by critic
|
||||
instances = prepare_dataset(
|
||||
swe_bench_tests, cur_output_file, args.eval_n_limit, eval_ids=eval_ids
|
||||
)
|
||||
if len(instances) > 0 and not isinstance(
|
||||
instances['PASS_TO_PASS'][instances['PASS_TO_PASS'].index[0]], str
|
||||
):
|
||||
for col in ['PASS_TO_PASS', 'FAIL_TO_PASS']:
|
||||
instances[col] = instances[col].apply(lambda x: str(x))
|
||||
|
||||
# Run evaluation - but save them to cur_output_file
|
||||
logger.info(
|
||||
f'Evaluating {len(instances)} instances for attempt {attempt}...'
|
||||
)
|
||||
run_evaluation(
|
||||
instances,
|
||||
metadata,
|
||||
cur_output_file,
|
||||
args.eval_num_workers,
|
||||
process_instance,
|
||||
timeout_seconds=8
|
||||
* 60
|
||||
* 60, # 8 hour PER instance should be more than enough
|
||||
max_retries=5,
|
||||
)
|
||||
|
||||
# When eval is done, we update eval_ids to the instances that failed the current attempt
|
||||
instances_failed = []
|
||||
logger.info(
|
||||
f'Use critic {critic.__class__.__name__} to check {len(instances)} instances for attempt {attempt}...'
|
||||
)
|
||||
with open(cur_output_file, 'r') as f:
|
||||
for line in f:
|
||||
instance = json.loads(line)
|
||||
try:
|
||||
history = [
|
||||
event_from_dict(event) for event in instance['history']
|
||||
]
|
||||
critic_result = critic.evaluate(
|
||||
history, instance['test_result'].get('git_patch', '')
|
||||
)
|
||||
if not critic_result.success:
|
||||
instances_failed.append(instance['instance_id'])
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f'Error loading history for instance {instance["instance_id"]}: {e}'
|
||||
)
|
||||
instances_failed.append(instance['instance_id'])
|
||||
logger.info(
|
||||
f'{len(instances_failed)} instances failed the current attempt {attempt}: {instances_failed}'
|
||||
)
|
||||
eval_ids = instances_failed
|
||||
|
||||
# If no instances failed, we break
|
||||
if len(instances_failed) == 0:
|
||||
break
|
||||
|
||||
# Then we should aggregate the results from all attempts into the original output file
|
||||
# and remove the intermediate files
|
||||
logger.info(
|
||||
'Aggregating results from all attempts into the original output file...'
|
||||
)
|
||||
fout = open(output_file, 'w')
|
||||
added_instance_ids = set()
|
||||
for attempt in reversed(range(1, ITERATIVE_EVAL_MODE_MAX_ATTEMPTS + 1)):
|
||||
cur_output_file = get_cur_output_file_path(attempt)
|
||||
if not os.path.exists(cur_output_file):
|
||||
logger.warning(
|
||||
f'Intermediate output file {cur_output_file} does not exist. Skipping...'
|
||||
)
|
||||
continue
|
||||
|
||||
with open(cur_output_file, 'r') as f:
|
||||
for line in f:
|
||||
instance = json.loads(line)
|
||||
# Also make sure git_patch is not empty - otherwise we fall back to previous attempt (empty patch is worse than anything else)
|
||||
if (
|
||||
instance['instance_id'] not in added_instance_ids
|
||||
and instance['test_result'].get('git_patch', '').strip()
|
||||
):
|
||||
fout.write(line)
|
||||
added_instance_ids.add(instance['instance_id'])
|
||||
logger.info(
|
||||
f'Aggregated instances from {cur_output_file}. Total instances added so far: {len(added_instance_ids)}'
|
||||
)
|
||||
fout.close()
|
||||
logger.info(
|
||||
f'Done! Total {len(added_instance_ids)} instances added to {output_file}'
|
||||
)
|
||||
@@ -30,7 +30,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -58,7 +58,7 @@ def _get_swebench_workspace_dir_name(instance: pd.Series) -> str:
|
||||
|
||||
|
||||
def get_instruction(instance: pd.Series, metadata: EvalMetadata):
|
||||
workspace_dir_name = _get_swebench_workspace_dir_name(instance)
|
||||
_get_swebench_workspace_dir_name(instance)
|
||||
instruction = f"""
|
||||
Consider the following issue description:
|
||||
|
||||
@@ -168,7 +168,7 @@ def get_instance_docker_image(instance_id: str, official_image: bool = False) ->
|
||||
def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
use_official_image = bool(
|
||||
'verified' in metadata.dataset.lower() or 'lite' in metadata.dataset.lower()
|
||||
@@ -197,7 +197,7 @@ def get_config(
|
||||
'REPO_PATH': f'/workspace/{workspace_dir_name}/',
|
||||
}
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
max_iterations=metadata.max_iterations,
|
||||
@@ -348,13 +348,13 @@ def initialize_runtime(
|
||||
|
||||
# Check if an existing graph index file is available
|
||||
graph_index_file_path = os.path.join(
|
||||
INDEX_BASE_DIR, 'graph_index_v2.3', f"{instance['instance_id']}.pkl"
|
||||
INDEX_BASE_DIR, 'graph_index_v2.3', f'{instance["instance_id"]}.pkl'
|
||||
)
|
||||
if INDEX_BASE_DIR and os.path.exists(graph_index_file_path):
|
||||
logger.info(
|
||||
f"Copying graph index from {graph_index_file_path} to /workspace/{workspace_dir_name}/_index_data/graph_index_v2.3"
|
||||
f'Copying graph index from {graph_index_file_path} to /workspace/{workspace_dir_name}/_index_data/graph_index_v2.3'
|
||||
)
|
||||
|
||||
|
||||
runtime.copy_to(
|
||||
graph_index_file_path,
|
||||
f'/workspace/{workspace_dir_name}/_index_data/graph_index_v2.3',
|
||||
@@ -364,9 +364,13 @@ def initialize_runtime(
|
||||
)
|
||||
obs = runtime.run_action(action)
|
||||
|
||||
bm25_index_dir = os.path.join(INDEX_BASE_DIR, 'BM25_index', instance['instance_id'])
|
||||
bm25_index_dir = os.path.join(
|
||||
INDEX_BASE_DIR, 'BM25_index', instance['instance_id']
|
||||
)
|
||||
runtime.copy_to(
|
||||
bm25_index_dir, f'/workspace/{workspace_dir_name}/_index_data', recursive=True
|
||||
bm25_index_dir,
|
||||
f'/workspace/{workspace_dir_name}/_index_data',
|
||||
recursive=True,
|
||||
)
|
||||
action = CmdRunAction(
|
||||
command=f'mv _index_data/{instance["instance_id"]} _index_data/bm25_index'
|
||||
|
||||
131
evaluation/benchmarks/swe_bench/scripts/run_infer_interact.sh
Normal file
131
evaluation/benchmarks/swe_bench/scripts/run_infer_interact.sh
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
source "evaluation/utils/version_control.sh"
|
||||
|
||||
MODEL_CONFIG=$1
|
||||
COMMIT_HASH=$2
|
||||
AGENT=$3
|
||||
EVAL_LIMIT=$4
|
||||
MAX_ITER=$5
|
||||
NUM_WORKERS=$6
|
||||
SPLIT=$8
|
||||
N_RUNS=$9
|
||||
|
||||
|
||||
if [ -z "$NUM_WORKERS" ]; then
|
||||
NUM_WORKERS=1
|
||||
echo "Number of workers not specified, use default $NUM_WORKERS"
|
||||
fi
|
||||
checkout_eval_branch
|
||||
|
||||
if [ -z "$AGENT" ]; then
|
||||
echo "Agent not specified, use default CodeActAgent"
|
||||
AGENT="CodeActAgent"
|
||||
fi
|
||||
|
||||
if [ -z "$MAX_ITER" ]; then
|
||||
echo "MAX_ITER not specified, use default 100"
|
||||
MAX_ITER=100
|
||||
fi
|
||||
|
||||
if [ -z "$RUN_WITH_BROWSING" ]; then
|
||||
echo "RUN_WITH_BROWSING not specified, use default false"
|
||||
RUN_WITH_BROWSING=false
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$DATASET" ]; then
|
||||
echo "DATASET not specified, use default cmu-lti/interactive-swe"
|
||||
DATASET="cmu-lti/interactive-swe"
|
||||
fi
|
||||
|
||||
if [ -z "$SPLIT" ]; then
|
||||
echo "SPLIT not specified, use default test"
|
||||
SPLIT="test"
|
||||
fi
|
||||
|
||||
if [ -n "$EVAL_CONDENSER" ]; then
|
||||
echo "Using Condenser Config: $EVAL_CONDENSER"
|
||||
else
|
||||
echo "No Condenser Config provided via EVAL_CONDENSER, use default (NoOpCondenser)."
|
||||
fi
|
||||
|
||||
export RUN_WITH_BROWSING=$RUN_WITH_BROWSING
|
||||
echo "RUN_WITH_BROWSING: $RUN_WITH_BROWSING"
|
||||
|
||||
get_openhands_version
|
||||
|
||||
echo "AGENT: $AGENT"
|
||||
echo "OPENHANDS_VERSION: $OPENHANDS_VERSION"
|
||||
echo "MODEL_CONFIG: $MODEL_CONFIG"
|
||||
echo "DATASET: $DATASET"
|
||||
echo "SPLIT: $SPLIT"
|
||||
echo "MAX_ITER: $MAX_ITER"
|
||||
echo "NUM_WORKERS: $NUM_WORKERS"
|
||||
echo "COMMIT_HASH: $COMMIT_HASH"
|
||||
echo "EVAL_CONDENSER: $EVAL_CONDENSER"
|
||||
|
||||
# Default to NOT use Hint
|
||||
if [ -z "$USE_HINT_TEXT" ]; then
|
||||
export USE_HINT_TEXT=false
|
||||
fi
|
||||
echo "USE_HINT_TEXT: $USE_HINT_TEXT"
|
||||
EVAL_NOTE="$OPENHANDS_VERSION"
|
||||
# if not using Hint, add -no-hint to the eval note
|
||||
if [ "$USE_HINT_TEXT" = false ]; then
|
||||
EVAL_NOTE="$EVAL_NOTE-no-hint"
|
||||
fi
|
||||
|
||||
if [ "$RUN_WITH_BROWSING" = true ]; then
|
||||
EVAL_NOTE="$EVAL_NOTE-with-browsing"
|
||||
fi
|
||||
|
||||
if [ -n "$EXP_NAME" ]; then
|
||||
EVAL_NOTE="$EVAL_NOTE-$EXP_NAME"
|
||||
fi
|
||||
# Add condenser config to eval note if provided
|
||||
if [ -n "$EVAL_CONDENSER" ]; then
|
||||
EVAL_NOTE="${EVAL_NOTE}-${EVAL_CONDENSER}"
|
||||
fi
|
||||
|
||||
function run_eval() {
|
||||
local eval_note="${1}"
|
||||
COMMAND="poetry run python evaluation/benchmarks/swe_bench/run_infer_interact.py \
|
||||
--agent-cls $AGENT \
|
||||
--llm-config $MODEL_CONFIG \
|
||||
--max-iterations $MAX_ITER \
|
||||
--eval-num-workers $NUM_WORKERS \
|
||||
--eval-note $eval_note \
|
||||
--dataset $DATASET \
|
||||
--split $SPLIT"
|
||||
|
||||
if [ -n "$EVAL_LIMIT" ]; then
|
||||
echo "EVAL_LIMIT: $EVAL_LIMIT"
|
||||
COMMAND="$COMMAND --eval-n-limit $EVAL_LIMIT"
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
eval $COMMAND
|
||||
}
|
||||
|
||||
unset SANDBOX_ENV_GITHUB_TOKEN # prevent the agent from using the github token to push
|
||||
if [ -z "$N_RUNS" ]; then
|
||||
N_RUNS=1
|
||||
echo "N_RUNS not specified, use default $N_RUNS"
|
||||
fi
|
||||
|
||||
# Skip runs if the run number is in the SKIP_RUNS list
|
||||
# read from env variable SKIP_RUNS as a comma separated list of run numbers
|
||||
SKIP_RUNS=(${SKIP_RUNS//,/ })
|
||||
for i in $(seq 1 $N_RUNS); do
|
||||
if [[ " ${SKIP_RUNS[@]} " =~ " $i " ]]; then
|
||||
echo "Skipping run $i"
|
||||
continue
|
||||
fi
|
||||
current_eval_note="$EVAL_NOTE-run_$i"
|
||||
echo "EVAL_NOTE: $current_eval_note"
|
||||
run_eval $current_eval_note
|
||||
done
|
||||
|
||||
checkout_original_branch
|
||||
@@ -41,7 +41,7 @@ from evaluation.utils.shared import (
|
||||
reset_logger_for_multiprocessing,
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.core.config import AppConfig, SandboxConfig, get_parser
|
||||
from openhands.core.config import OpenHandsConfig, SandboxConfig, get_parser
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime
|
||||
from openhands.events.action import CmdRunAction
|
||||
@@ -52,13 +52,13 @@ DOCKER_IMAGE_PREFIX = os.environ.get('EVAL_DOCKER_IMAGE_PREFIX', 'docker.io/kdja
|
||||
logger.info(f'Using docker image prefix: {DOCKER_IMAGE_PREFIX}')
|
||||
|
||||
|
||||
def get_config(instance: pd.Series) -> AppConfig:
|
||||
def get_config(instance: pd.Series) -> OpenHandsConfig:
|
||||
base_container_image = get_instance_docker_image(instance['instance_id_swebench'])
|
||||
assert base_container_image, (
|
||||
f'Invalid container image for instance {instance["instance_id_swebench"]}.'
|
||||
)
|
||||
logger.info(f'Using instance container image: {base_container_image}.')
|
||||
return AppConfig(
|
||||
return OpenHandsConfig(
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'eventstream'),
|
||||
sandbox=SandboxConfig(
|
||||
|
||||
@@ -35,7 +35,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
SandboxConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
@@ -117,7 +117,7 @@ def get_instance_docker_image(instance_id: str) -> str:
|
||||
def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
# We use a different instance image for the each instance of TestGenEval
|
||||
base_container_image = get_instance_docker_image(instance['instance_id_swebench'])
|
||||
logger.info(
|
||||
@@ -126,7 +126,7 @@ def get_config(
|
||||
f'Submit an issue on https://github.com/All-Hands-AI/OpenHands if you run into any issues.'
|
||||
)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
max_iterations=metadata.max_iterations,
|
||||
|
||||
@@ -15,8 +15,8 @@ from browsing import pre_login
|
||||
from evaluation.utils.shared import get_default_sandbox_config_for_eval
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_agent_config_arg,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
@@ -36,13 +36,13 @@ def get_config(
|
||||
mount_path_on_host: str,
|
||||
llm_config: LLMConfig,
|
||||
agent_config: AgentConfig | None,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = base_container_image
|
||||
sandbox_config.enable_auto_lint = True
|
||||
# If the web services are running on the host machine, this must be set to True
|
||||
sandbox_config.use_host_network = True
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
run_as_openhands=False,
|
||||
max_budget_per_task=4,
|
||||
max_iterations=100,
|
||||
@@ -126,7 +126,7 @@ def codeact_user_response(state: State) -> str:
|
||||
def run_solver(
|
||||
runtime: Runtime,
|
||||
task_name: str,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
dependencies: list[str],
|
||||
save_final_state: bool,
|
||||
state_dir: str,
|
||||
@@ -274,7 +274,7 @@ if __name__ == '__main__':
|
||||
temp_dir = os.path.abspath(os.getenv('TMPDIR'))
|
||||
else:
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
config: AppConfig = get_config(
|
||||
config: OpenHandsConfig = get_config(
|
||||
args.task_image_name, task_short_name, temp_dir, agent_llm_config, agent_config
|
||||
)
|
||||
runtime: Runtime = create_runtime(config)
|
||||
|
||||
@@ -18,7 +18,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -40,10 +40,10 @@ AGENT_CLS_TO_INST_SUFFIX = {
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.base_container_image = 'python:3.12-bookworm'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -30,7 +30,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
@@ -135,7 +135,7 @@ def get_instance_docker_image(instance_id: str, official_image: bool = False) ->
|
||||
def get_config(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
use_official_image = bool(
|
||||
'verified' in metadata.dataset.lower() or 'lite' in metadata.dataset.lower()
|
||||
@@ -160,7 +160,7 @@ def get_config(
|
||||
instance_id=instance['instance_id'],
|
||||
)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
max_iterations=metadata.max_iterations,
|
||||
|
||||
@@ -20,7 +20,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -48,7 +48,7 @@ AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
env_id: str,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
base_url = os.environ.get('VISUALWEBARENA_BASE_URL', None)
|
||||
openai_api_key = os.environ.get('OPENAI_API_KEY', None)
|
||||
openai_base_url = os.environ.get('OPENAI_BASE_URL', None)
|
||||
@@ -72,7 +72,7 @@ def get_config(
|
||||
'VWA_WIKIPEDIA': f'{base_url}:8888',
|
||||
'VWA_HOMEPAGE': f'{base_url}:4399',
|
||||
}
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -19,7 +19,7 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -44,7 +44,7 @@ SUPPORTED_AGENT_CLS = {'BrowsingAgent'}
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
env_id: str,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
base_url = os.environ.get('WEBARENA_BASE_URL', None)
|
||||
openai_api_key = os.environ.get('OPENAI_API_KEY', None)
|
||||
assert base_url is not None, 'WEBARENA_BASE_URL must be set'
|
||||
@@ -64,7 +64,7 @@ def get_config(
|
||||
'MAP': f'{base_url}:3000',
|
||||
'HOMEPAGE': f'{base_url}:4399',
|
||||
}
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='docker',
|
||||
|
||||
@@ -21,7 +21,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
get_llm_config_arg,
|
||||
parse_arguments,
|
||||
)
|
||||
@@ -41,10 +41,10 @@ FAKE_RESPONSES = {
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
instance_id: str,
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
sandbox_config = get_default_sandbox_config_for_eval()
|
||||
sandbox_config.platform = 'linux/amd64'
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'docker'),
|
||||
|
||||
@@ -2,9 +2,9 @@ import argparse
|
||||
|
||||
import pytest
|
||||
|
||||
from openhands.config import load_app_config
|
||||
from openhands.config import load_openhands_config
|
||||
|
||||
config = load_app_config()
|
||||
config = load_openhands_config()
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""Main entry point of the script.
|
||||
|
||||
@@ -8,6 +8,29 @@ import userEvent from "@testing-library/user-event";
|
||||
import { HomeHeader } from "#/components/features/home/home-header";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
|
||||
// Mock the translation function
|
||||
vi.mock("react-i18next", async () => {
|
||||
const actual = await vi.importActual("react-i18next");
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => {
|
||||
// Return a mock translation for the test
|
||||
const translations: Record<string, string> = {
|
||||
"HOME$LETS_START_BUILDING": "Let's start building",
|
||||
"HOME$LAUNCH_FROM_SCRATCH": "Launch from Scratch",
|
||||
"HOME$LOADING": "Loading...",
|
||||
"HOME$OPENHANDS_DESCRIPTION": "OpenHands is an AI software engineer",
|
||||
"HOME$NOT_SURE_HOW_TO_START": "Not sure how to start?",
|
||||
"HOME$READ_THIS": "Read this"
|
||||
};
|
||||
return translations[key] || key;
|
||||
},
|
||||
i18n: { language: "en" },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const renderHomeHeader = () => {
|
||||
const RouterStub = createRoutesStub([
|
||||
{
|
||||
@@ -38,7 +61,7 @@ describe("HomeHeader", () => {
|
||||
renderHomeHeader();
|
||||
|
||||
const launchButton = screen.getByRole("button", {
|
||||
name: /launch from scratch/i,
|
||||
name: /Launch from Scratch/i,
|
||||
});
|
||||
await userEvent.click(launchButton);
|
||||
|
||||
@@ -60,11 +83,11 @@ describe("HomeHeader", () => {
|
||||
renderHomeHeader();
|
||||
|
||||
const launchButton = screen.getByRole("button", {
|
||||
name: /launch from scratch/i,
|
||||
name: /Launch from Scratch/i,
|
||||
});
|
||||
await userEvent.click(launchButton);
|
||||
|
||||
expect(launchButton).toHaveTextContent(/Loading/i);
|
||||
expect(launchButton).toHaveTextContent(/Loading.../i);
|
||||
expect(launchButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,7 +19,11 @@ describe("Check for hardcoded English strings", () => {
|
||||
const text = container.textContent;
|
||||
|
||||
// List of English strings that should be translated
|
||||
const hardcodedStrings = ["What do you want to build?"];
|
||||
const hardcodedStrings = [
|
||||
"What do you want to build?",
|
||||
"Launch from Scratch",
|
||||
"Read this"
|
||||
];
|
||||
|
||||
// Check each string
|
||||
hardcodedStrings.forEach((str) => {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { test, expect, describe, vi } from "vitest";
|
||||
import { HomeHeader } from "#/components/features/home/home-header";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("#/hooks/mutation/use-create-conversation", () => ({
|
||||
useCreateConversation: () => ({
|
||||
mutate: vi.fn(),
|
||||
isPending: false,
|
||||
isSuccess: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-is-creating-conversation", () => ({
|
||||
useIsCreatingConversation: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("Check for hardcoded English strings in Home components", () => {
|
||||
test("HomeHeader should not have hardcoded English strings", () => {
|
||||
const { container } = render(<HomeHeader />);
|
||||
|
||||
// Get all text content
|
||||
const text = container.textContent;
|
||||
|
||||
// List of English strings that should be translated
|
||||
const hardcodedStrings = [
|
||||
"Launch from Scratch",
|
||||
"Read this",
|
||||
];
|
||||
|
||||
// Check each string
|
||||
hardcodedStrings.forEach((str) => {
|
||||
expect(text).not.toContain(str);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,6 @@ import { downloadTrajectory } from "#/utils/download-trajectory";
|
||||
import { displayErrorToast } from "#/utils/custom-toast-handlers";
|
||||
import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message";
|
||||
import { useWSErrorMessage } from "#/hooks/use-ws-error-message";
|
||||
import i18n from "#/i18n";
|
||||
import { ErrorMessageBanner } from "./error-message-banner";
|
||||
import { shouldRenderEvent } from "./event-content-helpers/should-render-event";
|
||||
|
||||
@@ -181,11 +180,7 @@ export function ChatInterface() {
|
||||
{!hitBottom && <ScrollToBottomButton onClick={scrollDomToBottom} />}
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<ErrorMessageBanner
|
||||
message={i18n.exists(errorMessage) ? t(errorMessage) : errorMessage}
|
||||
/>
|
||||
)}
|
||||
{errorMessage && <ErrorMessageBanner message={errorMessage} />}
|
||||
|
||||
<InteractiveChatBox
|
||||
onSubmit={handleSendMessage}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { Trans } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
import i18n from "#/i18n";
|
||||
|
||||
interface ErrorMessageBannerProps {
|
||||
message: string;
|
||||
}
|
||||
@@ -5,7 +9,23 @@ interface ErrorMessageBannerProps {
|
||||
export function ErrorMessageBanner({ message }: ErrorMessageBannerProps) {
|
||||
return (
|
||||
<div className="w-full rounded-lg p-2 text-black border border-red-800 bg-red-500">
|
||||
{message}
|
||||
{i18n.exists(message) ? (
|
||||
<Trans
|
||||
i18nKey={message}
|
||||
components={{
|
||||
a: (
|
||||
<Link
|
||||
className="underline font-bold cursor-pointer"
|
||||
to="/settings/billing"
|
||||
>
|
||||
link
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
message
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { OpenHandsAction } from "#/types/core/actions";
|
||||
import { OpenHandsEventType } from "#/types/core/base";
|
||||
import { isOpenHandsAction, isOpenHandsObservation } from "#/types/core/guards";
|
||||
import {
|
||||
isCommandAction,
|
||||
isCommandObservation,
|
||||
isOpenHandsAction,
|
||||
isOpenHandsObservation,
|
||||
} from "#/types/core/guards";
|
||||
import { OpenHandsObservation } from "#/types/core/observations";
|
||||
|
||||
const COMMON_NO_RENDER_LIST: OpenHandsEventType[] = [
|
||||
@@ -15,11 +20,21 @@ export const shouldRenderEvent = (
|
||||
event: OpenHandsAction | OpenHandsObservation,
|
||||
) => {
|
||||
if (isOpenHandsAction(event)) {
|
||||
if (isCommandAction(event) && event.source === "user") {
|
||||
// For user commands, we always hide them from the chat interface
|
||||
return false;
|
||||
}
|
||||
|
||||
const noRenderList = COMMON_NO_RENDER_LIST.concat(ACTION_NO_RENDER_LIST);
|
||||
return !noRenderList.includes(event.action);
|
||||
}
|
||||
|
||||
if (isOpenHandsObservation(event)) {
|
||||
if (isCommandObservation(event) && event.source === "user") {
|
||||
// For user commands, we always hide them from the chat interface
|
||||
return false;
|
||||
}
|
||||
|
||||
return !COMMON_NO_RENDER_LIST.includes(event.observation);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,32 +2,10 @@ import React from "react";
|
||||
import { OpenHandsAction } from "#/types/core/actions";
|
||||
import { OpenHandsObservation } from "#/types/core/observations";
|
||||
import { isOpenHandsAction, isOpenHandsObservation } from "#/types/core/guards";
|
||||
import { OpenHandsEventType } from "#/types/core/base";
|
||||
import { EventMessage } from "./event-message";
|
||||
import { ChatMessage } from "./chat-message";
|
||||
import { useOptimisticUserMessage } from "#/hooks/use-optimistic-user-message";
|
||||
|
||||
const COMMON_NO_RENDER_LIST: OpenHandsEventType[] = [
|
||||
"system",
|
||||
"agent_state_changed",
|
||||
"change_agent_state",
|
||||
];
|
||||
|
||||
const ACTION_NO_RENDER_LIST: OpenHandsEventType[] = ["recall"];
|
||||
|
||||
const shouldRenderEvent = (event: OpenHandsAction | OpenHandsObservation) => {
|
||||
if (isOpenHandsAction(event)) {
|
||||
const noRenderList = COMMON_NO_RENDER_LIST.concat(ACTION_NO_RENDER_LIST);
|
||||
return !noRenderList.includes(event.action);
|
||||
}
|
||||
|
||||
if (isOpenHandsObservation(event)) {
|
||||
return !COMMON_NO_RENDER_LIST.includes(event.observation);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
interface MessagesProps {
|
||||
messages: (OpenHandsAction | OpenHandsObservation)[];
|
||||
isAwaitingUserConfirmation: boolean;
|
||||
@@ -54,7 +32,7 @@ export const Messages: React.FC<MessagesProps> = React.memo(
|
||||
|
||||
return (
|
||||
<>
|
||||
{messages.filter(shouldRenderEvent).map((message, index) => (
|
||||
{messages.map((message, index) => (
|
||||
<EventMessage
|
||||
key={index}
|
||||
event={message}
|
||||
|
||||
@@ -129,7 +129,7 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
|
||||
isDisabled={isPending}
|
||||
>
|
||||
{isPending
|
||||
? t(I18nKey.FEEDBACK$SUBMITTING_LABEL) || "Submitting..."
|
||||
? t(I18nKey.FEEDBACK$SUBMITTING_LABEL)
|
||||
: t(I18nKey.FEEDBACK$SHARE_LABEL)}
|
||||
</BrandButton>
|
||||
<BrandButton
|
||||
@@ -144,8 +144,7 @@ export function FeedbackForm({ onClose, polarity }: FeedbackFormProps) {
|
||||
</div>
|
||||
{isPending && (
|
||||
<p className="text-sm text-center text-neutral-400">
|
||||
{t(I18nKey.FEEDBACK$SUBMITTING_MESSAGE) ||
|
||||
"Submitting your feedback, please wait..."}
|
||||
{t(I18nKey.FEEDBACK$SUBMITTING_MESSAGE)}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function HomeHeader() {
|
||||
onClick={() => createConversation({})}
|
||||
isDisabled={isCreatingConversation}
|
||||
>
|
||||
{!isCreatingConversation && "Launch from Scratch"}
|
||||
{!isCreatingConversation && t("HOME$LAUNCH_FROM_SCRATCH")}
|
||||
{isCreatingConversation && t("HOME$LOADING")}
|
||||
</BrandButton>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@ export function HomeHeader() {
|
||||
rel="noopener noreferrer"
|
||||
className="underline underline-offset-2"
|
||||
>
|
||||
Read this
|
||||
{t("HOME$READ_THIS")}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import GitHubLogo from "#/assets/branding/github-logo.svg?react";
|
||||
import GitLabLogo from "#/assets/branding/gitlab-logo.svg?react";
|
||||
import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||
import { LoginMethod, setLoginMethod } from "#/utils/local-storage";
|
||||
|
||||
interface AuthModalProps {
|
||||
githubAuthUrl: string | null;
|
||||
@@ -26,10 +25,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
|
||||
const handleGitHubAuth = () => {
|
||||
if (githubAuthUrl) {
|
||||
// Store the login method in local storage (only in SAAS mode)
|
||||
if (appMode === "saas") {
|
||||
setLoginMethod(LoginMethod.GITHUB);
|
||||
}
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = githubAuthUrl;
|
||||
}
|
||||
@@ -37,10 +32,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
|
||||
const handleGitLabAuth = () => {
|
||||
if (gitlabAuthUrl) {
|
||||
// Store the login method in local storage (only in SAAS mode)
|
||||
if (appMode === "saas") {
|
||||
setLoginMethod(LoginMethod.GITLAB);
|
||||
}
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = gitlabAuthUrl;
|
||||
}
|
||||
|
||||
@@ -166,6 +166,8 @@ export function WsClientProvider({
|
||||
}
|
||||
|
||||
function handleMessage(event: Record<string, unknown>) {
|
||||
handleAssistantMessage(event);
|
||||
|
||||
if (isOpenHandsEvent(event)) {
|
||||
const isStatusUpdateError =
|
||||
isStatusUpdate(event) && event.type === "error";
|
||||
@@ -217,9 +219,14 @@ export function WsClientProvider({
|
||||
isFileWriteAction(event) ||
|
||||
isCommandAction(event)
|
||||
) {
|
||||
queryClient.removeQueries({
|
||||
queryKey: ["file_changes", conversationId],
|
||||
});
|
||||
queryClient.invalidateQueries(
|
||||
{
|
||||
queryKey: ["file_changes", conversationId],
|
||||
},
|
||||
// Do not refetch if we are still receiving messages at a high rate (e.g., loading an existing conversation)
|
||||
// This prevents unnecessary refetches when the user is still receiving messages
|
||||
{ cancelRefetch: false },
|
||||
);
|
||||
|
||||
// Invalidate file diff cache when a file is edited or written
|
||||
if (!isCommandAction(event)) {
|
||||
@@ -250,8 +257,6 @@ export function WsClientProvider({
|
||||
if (!Number.isNaN(parseInt(event.id as string, 10))) {
|
||||
lastEventRef.current = event;
|
||||
}
|
||||
|
||||
handleAssistantMessage(event);
|
||||
}
|
||||
|
||||
function handleDisconnect(data: unknown) {
|
||||
@@ -284,14 +289,14 @@ export function WsClientProvider({
|
||||
|
||||
React.useEffect(() => {
|
||||
lastEventRef.current = null;
|
||||
}, [conversationId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// reset events when conversationId changes
|
||||
setEvents([]);
|
||||
setParsedEvents([]);
|
||||
setStatus(WsClientProviderStatus.DISCONNECTED);
|
||||
}, [conversationId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!conversationId) {
|
||||
throw new Error("No conversation ID provided");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router";
|
||||
import posthog from "posthog-js";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConfig } from "../query/use-config";
|
||||
@@ -8,7 +7,6 @@ import { clearLoginData } from "#/utils/local-storage";
|
||||
export const useLogout = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: config } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () => OpenHands.logout(config?.APP_MODE ?? "oss"),
|
||||
@@ -24,7 +22,6 @@ export const useLogout = () => {
|
||||
}
|
||||
|
||||
posthog.reset();
|
||||
await navigate("/");
|
||||
|
||||
// Refresh the page after all logout logic is completed
|
||||
window.location.reload();
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { useQueries, useQuery } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { RootState } from "#/store";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import { useActiveConversation } from "./use-active-conversation";
|
||||
import { useRuntimeIsReady } from "#/hooks/use-runtime-is-ready";
|
||||
|
||||
export const useActiveHost = () => {
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const [activeHost, setActiveHost] = React.useState<string | null>(null);
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const enabled =
|
||||
conversation?.status === "RUNNING" &&
|
||||
RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
const runtimeIsReady = useRuntimeIsReady();
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: [conversationId, "hosts"],
|
||||
@@ -23,7 +16,7 @@ export const useActiveHost = () => {
|
||||
const hosts = await OpenHands.getWebHosts(conversationId);
|
||||
return { hosts };
|
||||
},
|
||||
enabled,
|
||||
enabled: runtimeIsReady && !!conversationId,
|
||||
initialData: { hosts: [] },
|
||||
meta: {
|
||||
disableToast: true,
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import { GitChange } from "#/api/open-hands.types";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useActiveConversation } from "./use-active-conversation";
|
||||
import { useRuntimeIsReady } from "#/hooks/use-runtime-is-ready";
|
||||
|
||||
export const useGetGitChanges = () => {
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const [orderedChanges, setOrderedChanges] = React.useState<GitChange[]>([]);
|
||||
const previousDataRef = React.useRef<GitChange[]>(null);
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const enabled =
|
||||
conversation?.status === "RUNNING" &&
|
||||
RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
const runtimeIsReady = useRuntimeIsReady();
|
||||
|
||||
const result = useQuery({
|
||||
queryKey: ["file_changes", conversationId],
|
||||
@@ -25,7 +17,7 @@ export const useGetGitChanges = () => {
|
||||
retry: false,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 15, // 15 minutes
|
||||
enabled,
|
||||
enabled: runtimeIsReady && !!conversationId,
|
||||
meta: {
|
||||
disableToast: true,
|
||||
},
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import { useActiveConversation } from "./use-active-conversation";
|
||||
import { useRuntimeIsReady } from "#/hooks/use-runtime-is-ready";
|
||||
|
||||
// Define the return type for the VS Code URL query
|
||||
interface VSCodeUrlResult {
|
||||
@@ -18,11 +15,7 @@ interface VSCodeUrlResult {
|
||||
export const useVSCodeUrl = () => {
|
||||
const { t } = useTranslation();
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const enabled =
|
||||
conversation?.status === "RUNNING" &&
|
||||
RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
const runtimeIsReady = useRuntimeIsReady();
|
||||
|
||||
return useQuery<VSCodeUrlResult>({
|
||||
queryKey: ["vscode_url", conversationId],
|
||||
@@ -40,7 +33,7 @@ export const useVSCodeUrl = () => {
|
||||
error: t(I18nKey.VSCODE$URL_NOT_AVAILABLE),
|
||||
};
|
||||
},
|
||||
enabled,
|
||||
enabled: runtimeIsReady && !!conversationId,
|
||||
refetchOnMount: true,
|
||||
retry: 3,
|
||||
});
|
||||
|
||||
49
frontend/src/hooks/use-auth-callback.ts
Normal file
49
frontend/src/hooks/use-auth-callback.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useIsAuthed } from "./query/use-is-authed";
|
||||
import { LoginMethod, setLoginMethod } from "#/utils/local-storage";
|
||||
import { useConfig } from "./query/use-config";
|
||||
|
||||
/**
|
||||
* Hook to handle authentication callback and set login method after successful authentication
|
||||
*/
|
||||
export const useAuthCallback = () => {
|
||||
const location = useLocation();
|
||||
const { data: isAuthed, isLoading: isAuthLoading } = useIsAuthed();
|
||||
const { data: config } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Only run in SAAS mode
|
||||
if (config?.APP_MODE !== "saas") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for auth to load
|
||||
if (isAuthLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only set login method if authentication was successful
|
||||
if (!isAuthed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have a login_method query parameter
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const loginMethod = searchParams.get("login_method");
|
||||
|
||||
// Set the login method if it's valid
|
||||
if (
|
||||
loginMethod === LoginMethod.GITHUB ||
|
||||
loginMethod === LoginMethod.GITLAB
|
||||
) {
|
||||
setLoginMethod(loginMethod as LoginMethod);
|
||||
|
||||
// Clean up the URL by removing the login_method parameter
|
||||
searchParams.delete("login_method");
|
||||
const newUrl = `${location.pathname}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`;
|
||||
navigate(newUrl, { replace: true });
|
||||
}
|
||||
}, [isAuthed, isAuthLoading, location.search, config?.APP_MODE]);
|
||||
};
|
||||
@@ -53,8 +53,12 @@ export const useAutoLogin = () => {
|
||||
|
||||
// If we have an auth URL, redirect to it
|
||||
if (authUrl) {
|
||||
// Add the login method as a query parameter
|
||||
const url = new URL(authUrl);
|
||||
url.searchParams.append("login_method", loginMethod);
|
||||
|
||||
// After successful login, the user will be redirected back and can navigate to the last page
|
||||
window.location.href = authUrl;
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
}, [
|
||||
config?.APP_MODE,
|
||||
|
||||
19
frontend/src/hooks/use-runtime-is-ready.ts
Normal file
19
frontend/src/hooks/use-runtime-is-ready.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useActiveConversation } from "./query/use-active-conversation";
|
||||
|
||||
/**
|
||||
* Hook to determine if the runtime is ready for operations
|
||||
*
|
||||
* @returns boolean indicating if the runtime is ready
|
||||
*/
|
||||
export const useRuntimeIsReady = (): boolean => {
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
|
||||
return (
|
||||
conversation?.status === "RUNNING" &&
|
||||
!RUNTIME_INACTIVE_STATES.includes(curAgentState)
|
||||
);
|
||||
};
|
||||
@@ -228,8 +228,6 @@ export enum I18nKey {
|
||||
FEEDBACK$FAILED_TO_SHARE = "FEEDBACK$FAILED_TO_SHARE",
|
||||
FEEDBACK$COPY_LABEL = "FEEDBACK$COPY_LABEL",
|
||||
FEEDBACK$SHARING_SETTINGS_LABEL = "FEEDBACK$SHARING_SETTINGS_LABEL",
|
||||
FEEDBACK$SUBMITTING_LABEL = "FEEDBACK$SUBMITTING_LABEL",
|
||||
FEEDBACK$SUBMITTING_MESSAGE = "FEEDBACK$SUBMITTING_MESSAGE",
|
||||
SECURITY$UNKNOWN_ANALYZER_LABEL = "SECURITY$UNKNOWN_ANALYZER_LABEL",
|
||||
INVARIANT$UPDATE_POLICY_LABEL = "INVARIANT$UPDATE_POLICY_LABEL",
|
||||
INVARIANT$UPDATE_SETTINGS_LABEL = "INVARIANT$UPDATE_SETTINGS_LABEL",
|
||||
@@ -550,4 +548,6 @@ export enum I18nKey {
|
||||
TIPS$API_USAGE = "TIPS$API_USAGE",
|
||||
TIPS$LEARN_MORE = "TIPS$LEARN_MORE",
|
||||
TIPS$PROTIP = "TIPS$PROTIP",
|
||||
FEEDBACK$SUBMITTING_LABEL = "FEEDBACK$SUBMITTING_LABEL",
|
||||
FEEDBACK$SUBMITTING_MESSAGE = "FEEDBACK$SUBMITTING_MESSAGE",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,36 @@
|
||||
{
|
||||
"HOME$LAUNCH_FROM_SCRATCH": {
|
||||
"en": "Launch from Scratch",
|
||||
"ja": "ゼロから始める",
|
||||
"zh-CN": "从零开始",
|
||||
"zh-TW": "從零開始",
|
||||
"ko-KR": "처음부터 시작",
|
||||
"no": "Start fra bunnen",
|
||||
"it": "Inizia da zero",
|
||||
"pt": "Começar do zero",
|
||||
"es": "Comenzar desde cero",
|
||||
"ar": "البدء من الصفر",
|
||||
"fr": "Démarrer de zéro",
|
||||
"tr": "Sıfırdan başla",
|
||||
"de": "Von Grund auf starten",
|
||||
"uk": "Почати з нуля"
|
||||
},
|
||||
"HOME$READ_THIS": {
|
||||
"en": "Read this",
|
||||
"ja": "こちらを読む",
|
||||
"zh-CN": "阅读此内容",
|
||||
"zh-TW": "閱讀此內容",
|
||||
"ko-KR": "이것을 읽어보세요",
|
||||
"no": "Les dette",
|
||||
"it": "Leggi questo",
|
||||
"pt": "Leia isto",
|
||||
"es": "Leer esto",
|
||||
"ar": "اقرأ هذا",
|
||||
"fr": "Lire ceci",
|
||||
"tr": "Bunu oku",
|
||||
"de": "Lies dies",
|
||||
"uk": "Прочитайте це"
|
||||
},
|
||||
"AUTH$LOGGING_BACK_IN": {
|
||||
"en": "Logging back into OpenHands...",
|
||||
"ja": "OpenHandsに再ログインしています...",
|
||||
@@ -6400,20 +6432,20 @@
|
||||
"uk": "Запит не вдалося виконати через внутрішню помилку сервера."
|
||||
},
|
||||
"STATUS$ERROR_LLM_OUT_OF_CREDITS": {
|
||||
"en": "You're out of OpenHands Credits",
|
||||
"ja": "OpenHandsクレジットが不足しています",
|
||||
"zh-CN": "您的OpenHands点数已用完",
|
||||
"zh-TW": "您的OpenHands點數已用完",
|
||||
"ko-KR": "OpenHands 크레딧이 소진되었습니다",
|
||||
"no": "Du er tom for OpenHands-kreditter",
|
||||
"it": "Hai esaurito i crediti OpenHands",
|
||||
"pt": "Você está sem créditos OpenHands",
|
||||
"es": "Te has quedado sin créditos de OpenHands",
|
||||
"ar": "لقد نفدت رصيدك من OpenHands",
|
||||
"fr": "Vous n'avez plus de crédits OpenHands",
|
||||
"tr": "OpenHands kredileriniz tükendi",
|
||||
"de": "Ihre OpenHands-Guthaben sind aufgebraucht",
|
||||
"uk": "У вас закінчилися кредити OpenHands"
|
||||
"en": "You're out of OpenHands Credits. <a>Add funds</a>",
|
||||
"ja": "OpenHandsクレジットが不足しています。<a>資金を追加</a>",
|
||||
"zh-CN": "您的OpenHands点数已用完。<a>添加资金</a>",
|
||||
"zh-TW": "您的OpenHands點數已用完。<a>添加資金</a>",
|
||||
"ko-KR": "OpenHands 크레딧이 소진되었습니다. <a>자금 추가</a>",
|
||||
"no": "Du er tom for OpenHands-kreditter. <a>Legg til midler</a>",
|
||||
"it": "Hai esaurito i crediti OpenHands. <a>Aggiungi fondi</a>",
|
||||
"pt": "Você está sem créditos OpenHands. <a>Adicionar fundos</a>",
|
||||
"es": "Te has quedado sin créditos de OpenHands. <a>Añadir fondos</a>",
|
||||
"ar": "لقد نفدت رصيدك من OpenHands. <a>إضافة رصيد</a>",
|
||||
"fr": "Vous n'avez plus de crédits OpenHands. <a>Ajouter des fonds</a>",
|
||||
"tr": "OpenHands kredileriniz tükendi. <a>Bakiye ekle</a>",
|
||||
"de": "Ihre OpenHands-Guthaben sind aufgebraucht. <a>Guthaben hinzufügen</a>",
|
||||
"uk": "У вас закінчилися кредити OpenHands. <a>Додати кошти</a>"
|
||||
},
|
||||
"STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION": {
|
||||
"en": "Content policy violation. The output was blocked by content filtering policy.",
|
||||
@@ -8780,7 +8812,7 @@
|
||||
"ar": "إرسال...",
|
||||
"fr": "Envoi...",
|
||||
"tr": "Gönderiliyor...",
|
||||
"de": "Senden...",
|
||||
"de": "Senden...",
|
||||
"uk": "Відправляємо..."
|
||||
},
|
||||
"FEEDBACK$SUBMITTING_MESSAGE": {
|
||||
|
||||
@@ -36,6 +36,7 @@ import { useDocumentTitleFromState } from "#/hooks/use-document-title-from-state
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { TabContent } from "#/components/layout/tab-content";
|
||||
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
||||
|
||||
function AppContent() {
|
||||
useConversationConfig();
|
||||
@@ -43,6 +44,7 @@ function AppContent() {
|
||||
const { data: settings } = useSettings();
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation, isFetched } = useActiveConversation();
|
||||
const { data: isAuthed } = useIsAuthed();
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const dispatch = useDispatch();
|
||||
@@ -54,13 +56,13 @@ function AppContent() {
|
||||
const [width, setWidth] = React.useState(window.innerWidth);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isFetched && !conversation) {
|
||||
if (isFetched && !conversation && isAuthed) {
|
||||
displayErrorToast(
|
||||
"This conversation does not exist, or you do not have permission to access it.",
|
||||
);
|
||||
navigate("/");
|
||||
}
|
||||
}, [conversation, isFetched]);
|
||||
}, [conversation, isFetched, isAuthed]);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(clearTerminal());
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SetupPaymentModal } from "#/components/features/payment/setup-payment-m
|
||||
import { displaySuccessToast } from "#/utils/custom-toast-handlers";
|
||||
import { useIsOnTosPage } from "#/hooks/use-is-on-tos-page";
|
||||
import { useAutoLogin } from "#/hooks/use-auto-login";
|
||||
import { useAuthCallback } from "#/hooks/use-auth-callback";
|
||||
import { LOCAL_STORAGE_KEYS } from "#/utils/local-storage";
|
||||
|
||||
export function ErrorBoundary() {
|
||||
@@ -88,6 +89,9 @@ export default function MainApp() {
|
||||
// Auto-login if login method is stored in local storage
|
||||
useAutoLogin();
|
||||
|
||||
// Handle authentication callback and set login method after successful authentication
|
||||
useAuthCallback();
|
||||
|
||||
React.useEffect(() => {
|
||||
// Don't change language when on TOS page
|
||||
if (!isOnTosPage && settings?.LANGUAGE) {
|
||||
@@ -173,12 +177,6 @@ export default function MainApp() {
|
||||
setLoginMethodExists(checkLoginMethodExists());
|
||||
}, [isAuthed, checkLoginMethodExists]);
|
||||
|
||||
// Recheck on component mount to ensure we have the latest value after page reload
|
||||
React.useEffect(() => {
|
||||
// This will run once when the component mounts, ensuring we have the latest value
|
||||
setLoginMethodExists(checkLoginMethodExists());
|
||||
}, [checkLoginMethodExists]);
|
||||
|
||||
const renderAuthModal =
|
||||
!isAuthed &&
|
||||
!isAuthError &&
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface SystemMessageAction extends OpenHandsActionEvent<"system"> {
|
||||
}
|
||||
|
||||
export interface CommandAction extends OpenHandsActionEvent<"run"> {
|
||||
source: "agent";
|
||||
source: "agent" | "user";
|
||||
args: {
|
||||
command: string;
|
||||
security_risk: ActionSecurityRisk;
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AssistantMessageAction,
|
||||
OpenHandsAction,
|
||||
SystemMessageAction,
|
||||
CommandAction,
|
||||
} from "./actions";
|
||||
import {
|
||||
AgentStateChangeObservation,
|
||||
@@ -41,6 +42,10 @@ export const isErrorObservation = (
|
||||
): event is ErrorObservation =>
|
||||
isOpenHandsObservation(event) && event.observation === "error";
|
||||
|
||||
export const isCommandAction = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is CommandAction => isOpenHandsAction(event) && event.action === "run";
|
||||
|
||||
export const isAgentStateChangeObservation = (
|
||||
event: OpenHandsParsedEvent,
|
||||
): event is AgentStateChangeObservation =>
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface AgentStateChangeObservation
|
||||
}
|
||||
|
||||
export interface CommandObservation extends OpenHandsObservationEvent<"run"> {
|
||||
source: "agent";
|
||||
source: "agent" | "user";
|
||||
extras: {
|
||||
command: string;
|
||||
hidden?: boolean;
|
||||
|
||||
@@ -16,5 +16,8 @@ export const generateAuthUrl = (identityProvider: string, requestUrl: URL) => {
|
||||
authUrl = `auth.${requestUrl.hostname}`;
|
||||
}
|
||||
const scope = "openid email profile"; // OAuth scope - not user-facing
|
||||
return `https://${authUrl}/realms/allhands/protocol/openid-connect/auth?client_id=allhands&kc_idp_hint=${identityProvider}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(requestUrl.href)}`;
|
||||
const separator = requestUrl.search ? "&" : "?";
|
||||
const cleanHref = requestUrl.href.replace(/\/$/, "");
|
||||
const state = `${cleanHref}${separator}login_method=${identityProvider}`;
|
||||
return `https://${authUrl}/realms/allhands/protocol/openid-connect/auth?client_id=allhands&kc_idp_hint=${identityProvider}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scope)}&state=${encodeURIComponent(state)}`;
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ from openhands.cli.utils import (
|
||||
write_to_file,
|
||||
)
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
)
|
||||
from openhands.core.schema import AgentState
|
||||
from openhands.events import EventSource
|
||||
@@ -42,7 +42,7 @@ async def handle_commands(
|
||||
event_stream: EventStream,
|
||||
usage_metrics: UsageMetrics,
|
||||
sid: str,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
current_dir: str,
|
||||
settings_store: FileSettingsStore,
|
||||
) -> tuple[bool, bool, bool]:
|
||||
@@ -105,7 +105,7 @@ def handle_help_command() -> None:
|
||||
|
||||
|
||||
async def handle_init_command(
|
||||
config: AppConfig, event_stream: EventStream, current_dir: str
|
||||
config: OpenHandsConfig, event_stream: EventStream, current_dir: str
|
||||
) -> tuple[bool, bool]:
|
||||
REPO_MD_CREATE_PROMPT = """
|
||||
Please explore this repository. Create the file .openhands/microagents/repo.md with:
|
||||
@@ -166,7 +166,7 @@ def handle_new_command(
|
||||
|
||||
|
||||
async def handle_settings_command(
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
settings_store: FileSettingsStore,
|
||||
) -> None:
|
||||
display_settings(config)
|
||||
@@ -264,7 +264,7 @@ async def init_repository(current_dir: str) -> bool:
|
||||
return init_repo
|
||||
|
||||
|
||||
def check_folder_security_agreement(config: AppConfig, current_dir: str) -> bool:
|
||||
def check_folder_security_agreement(config: OpenHandsConfig, current_dir: str) -> bool:
|
||||
# Directories trusted by user for the CLI to use as workspace
|
||||
# Config from ~/.openhands/config.toml overrides the app config
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ from openhands.cli.utils import (
|
||||
from openhands.controller import AgentController
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
parse_arguments,
|
||||
setup_config_from_args,
|
||||
)
|
||||
@@ -103,7 +103,7 @@ async def cleanup_session(
|
||||
|
||||
async def run_session(
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
settings_store: FileSettingsStore,
|
||||
current_dir: str,
|
||||
task_content: str | None = None,
|
||||
@@ -334,7 +334,7 @@ async def main(loop: asyncio.AbstractEventLoop) -> None:
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
# Load config from toml and override with command line arguments
|
||||
config: AppConfig = setup_config_from_args(args)
|
||||
config: OpenHandsConfig = setup_config_from_args(args)
|
||||
|
||||
# Load settings from Settings Store
|
||||
# TODO: Make this generic?
|
||||
|
||||
@@ -18,7 +18,7 @@ from openhands.cli.utils import (
|
||||
organize_models_and_providers,
|
||||
)
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.config.condenser_config import NoOpCondenserConfig
|
||||
from openhands.core.config.utils import OH_DEFAULT_AGENT
|
||||
from openhands.memory.condenser.impl.llm_summarizing_condenser import (
|
||||
@@ -29,7 +29,7 @@ from openhands.storage.settings.file_settings_store import FileSettingsStore
|
||||
from openhands.utils.llm import get_supported_llm_models
|
||||
|
||||
|
||||
def display_settings(config: AppConfig) -> None:
|
||||
def display_settings(config: OpenHandsConfig) -> None:
|
||||
llm_config = config.get_llm_config()
|
||||
advanced_llm_settings = True if llm_config.base_url else False
|
||||
|
||||
@@ -145,7 +145,7 @@ def save_settings_confirmation() -> bool:
|
||||
|
||||
|
||||
async def modify_llm_settings_basic(
|
||||
config: AppConfig, settings_store: FileSettingsStore
|
||||
config: OpenHandsConfig, settings_store: FileSettingsStore
|
||||
) -> None:
|
||||
model_list = get_supported_llm_models(config)
|
||||
organized_models = organize_models_and_providers(model_list)
|
||||
@@ -243,7 +243,7 @@ async def modify_llm_settings_basic(
|
||||
|
||||
|
||||
async def modify_llm_settings_advanced(
|
||||
config: AppConfig, settings_store: FileSettingsStore
|
||||
config: OpenHandsConfig, settings_store: FileSettingsStore
|
||||
) -> None:
|
||||
session = PromptSession(key_bindings=kb_cancel())
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from prompt_toolkit.styles import Style
|
||||
from prompt_toolkit.widgets import Frame, TextArea
|
||||
|
||||
from openhands import __version__
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.schema import AgentState
|
||||
from openhands.events import EventSource, EventStream
|
||||
from openhands.events.action import (
|
||||
@@ -180,7 +180,7 @@ def display_initial_user_prompt(prompt: str) -> None:
|
||||
|
||||
|
||||
# Prompt output display functions
|
||||
def display_event(event: Event, config: AppConfig) -> None:
|
||||
def display_event(event: Event, config: OpenHandsConfig) -> None:
|
||||
global streaming_output_text_area
|
||||
with print_lock:
|
||||
if isinstance(event, Action):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from openhands.core.config.agent_config import AgentConfig
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.config_utils import (
|
||||
OH_DEFAULT_AGENT,
|
||||
OH_MAX_ITERATIONS,
|
||||
@@ -8,6 +7,7 @@ from openhands.core.config.config_utils import (
|
||||
from openhands.core.config.extended_config import ExtendedConfig
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.config.sandbox_config import SandboxConfig
|
||||
from openhands.core.config.security_config import SecurityConfig
|
||||
from openhands.core.config.utils import (
|
||||
@@ -15,9 +15,9 @@ from openhands.core.config.utils import (
|
||||
get_agent_config_arg,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_app_config,
|
||||
load_from_env,
|
||||
load_from_toml,
|
||||
load_openhands_config,
|
||||
parse_arguments,
|
||||
setup_config_from_args,
|
||||
)
|
||||
@@ -26,13 +26,13 @@ __all__ = [
|
||||
'OH_DEFAULT_AGENT',
|
||||
'OH_MAX_ITERATIONS',
|
||||
'AgentConfig',
|
||||
'AppConfig',
|
||||
'OpenHandsConfig',
|
||||
'MCPConfig',
|
||||
'LLMConfig',
|
||||
'SandboxConfig',
|
||||
'SecurityConfig',
|
||||
'ExtendedConfig',
|
||||
'load_app_config',
|
||||
'load_openhands_config',
|
||||
'load_from_env',
|
||||
'load_from_toml',
|
||||
'finalize_config',
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import BaseModel, Field, ValidationError, model_validator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.utils.import_utils import get_impl
|
||||
@@ -147,7 +148,7 @@ class MCPConfig(BaseModel):
|
||||
|
||||
class OpenHandsMCPConfig:
|
||||
@staticmethod
|
||||
def add_search_engine(app_config: "AppConfig") -> MCPStdioServerConfig | None:
|
||||
def add_search_engine(app_config: 'OpenHandsConfig') -> MCPStdioServerConfig | None:
|
||||
"""Add search engine to the MCP config"""
|
||||
if (
|
||||
app_config.search_api_key
|
||||
@@ -165,17 +166,16 @@ class OpenHandsMCPConfig:
|
||||
# Do not add search engine to MCP config in SaaS mode since it will be added by the OpenHands server
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_default_mcp_server_config(
|
||||
host: str, config: "AppConfig", user_id: str | None = None
|
||||
host: str, config: 'OpenHandsConfig', user_id: str | None = None
|
||||
) -> tuple[MCPSSEServerConfig, list[MCPStdioServerConfig]]:
|
||||
"""
|
||||
Create a default MCP server configuration.
|
||||
|
||||
Args:
|
||||
host: Host string
|
||||
config: AppConfig
|
||||
config: OpenHandsConfig
|
||||
Returns:
|
||||
tuple[MCPSSEServerConfig, list[MCPStdioServerConfig]]: A tuple containing the default SSE server configuration and a list of MCP stdio server configurations
|
||||
"""
|
||||
|
||||
@@ -16,7 +16,7 @@ from openhands.core.config.sandbox_config import SandboxConfig
|
||||
from openhands.core.config.security_config import SecurityConfig
|
||||
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
class OpenHandsConfig(BaseModel):
|
||||
"""Configuration for the app.
|
||||
|
||||
Attributes:
|
||||
@@ -65,7 +65,10 @@ class AppConfig(BaseModel):
|
||||
save_trajectory_path: str | None = Field(default=None)
|
||||
save_screenshots_in_trajectory: bool = Field(default=False)
|
||||
replay_trajectory_path: str | None = Field(default=None)
|
||||
search_api_key: SecretStr | None = Field(default=None, description="API key for Tavily search engine (https://tavily.com/). Required for search functionality.")
|
||||
search_api_key: SecretStr | None = Field(
|
||||
default=None,
|
||||
description='API key for Tavily search engine (https://tavily.com/). Required for search functionality.',
|
||||
)
|
||||
|
||||
# Deprecated parameters - will be removed in a future version
|
||||
workspace_base: str | None = Field(default=None, deprecated=True)
|
||||
@@ -73,7 +76,7 @@ class AppConfig(BaseModel):
|
||||
workspace_mount_path_in_sandbox: str = Field(default='/workspace', deprecated=True)
|
||||
workspace_mount_rewrite: str | None = Field(default=None, deprecated=True)
|
||||
# End of deprecated parameters
|
||||
|
||||
|
||||
cache_dir: str = Field(default='/tmp/cache')
|
||||
run_as_openhands: bool = Field(default=True)
|
||||
max_iterations: int = Field(default=OH_MAX_ITERATIONS)
|
||||
@@ -148,5 +151,5 @@ class AppConfig(BaseModel):
|
||||
"""Post-initialization hook, called when the instance is created with only default values."""
|
||||
super().model_post_init(__context)
|
||||
|
||||
if not AppConfig.defaults_dict: # Only set defaults_dict if it's empty
|
||||
AppConfig.defaults_dict = model_defaults_to_dict(self)
|
||||
if not OpenHandsConfig.defaults_dict: # Only set defaults_dict if it's empty
|
||||
OpenHandsConfig.defaults_dict = model_defaults_to_dict(self)
|
||||
@@ -15,7 +15,6 @@ from pydantic import BaseModel, SecretStr, ValidationError
|
||||
from openhands import __version__
|
||||
from openhands.core import logger
|
||||
from openhands.core.config.agent_config import AgentConfig
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.condenser_config import (
|
||||
CondenserConfig,
|
||||
condenser_config_from_toml_section,
|
||||
@@ -28,6 +27,7 @@ from openhands.core.config.config_utils import (
|
||||
from openhands.core.config.extended_config import ExtendedConfig
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.config.sandbox_config import SandboxConfig
|
||||
from openhands.core.config.security_config import SecurityConfig
|
||||
from openhands.storage import get_file_store
|
||||
@@ -39,7 +39,7 @@ load_dotenv()
|
||||
|
||||
|
||||
def load_from_env(
|
||||
cfg: AppConfig, env_or_toml_dict: dict | MutableMapping[str, str]
|
||||
cfg: OpenHandsConfig, env_or_toml_dict: dict | MutableMapping[str, str]
|
||||
) -> None:
|
||||
"""Sets config attributes from environment variables or TOML dictionary.
|
||||
|
||||
@@ -48,7 +48,7 @@ def load_from_env(
|
||||
(e.g., AGENT_MEMORY_ENABLED), sandbox settings (e.g., SANDBOX_TIMEOUT), and more.
|
||||
|
||||
Args:
|
||||
cfg: The AppConfig object to set attributes on.
|
||||
cfg: The OpenHandsConfig object to set attributes on.
|
||||
env_or_toml_dict: The environment variables or a config.toml dict.
|
||||
"""
|
||||
|
||||
@@ -121,11 +121,11 @@ def load_from_env(
|
||||
set_attr_from_env(default_agent_config, 'AGENT_')
|
||||
|
||||
|
||||
def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml') -> None:
|
||||
def load_from_toml(cfg: OpenHandsConfig, toml_file: str = 'config.toml') -> None:
|
||||
"""Load the config from the toml file. Supports both styles of config vars.
|
||||
|
||||
Args:
|
||||
cfg: The AppConfig object to update attributes of.
|
||||
cfg: The OpenHandsConfig object to update attributes of.
|
||||
toml_file: The path to the toml file. Defaults to 'config.toml'.
|
||||
|
||||
See Also:
|
||||
@@ -302,7 +302,7 @@ def get_or_create_jwt_secret(file_store: FileStore) -> str:
|
||||
return new_secret
|
||||
|
||||
|
||||
def finalize_config(cfg: AppConfig) -> None:
|
||||
def finalize_config(cfg: OpenHandsConfig) -> None:
|
||||
"""More tweaks to the config after it's been loaded."""
|
||||
# Handle the sandbox.volumes parameter
|
||||
if cfg.workspace_base is not None or cfg.workspace_mount_path is not None:
|
||||
@@ -759,7 +759,7 @@ def parse_arguments() -> argparse.Namespace:
|
||||
return args
|
||||
|
||||
|
||||
def register_custom_agents(config: AppConfig) -> None:
|
||||
def register_custom_agents(config: OpenHandsConfig) -> None:
|
||||
"""Register custom agents from configuration.
|
||||
|
||||
This function is called after configuration is loaded to ensure all custom agents
|
||||
@@ -782,16 +782,16 @@ def register_custom_agents(config: AppConfig) -> None:
|
||||
)
|
||||
|
||||
|
||||
def load_app_config(
|
||||
def load_openhands_config(
|
||||
set_logging_levels: bool = True, config_file: str = 'config.toml'
|
||||
) -> AppConfig:
|
||||
) -> OpenHandsConfig:
|
||||
"""Load the configuration from the specified config file and environment variables.
|
||||
|
||||
Args:
|
||||
set_logging_levels: Whether to set the global variables for logging levels.
|
||||
config_file: Path to the config file. Defaults to 'config.toml' in the current directory.
|
||||
"""
|
||||
config = AppConfig()
|
||||
config = OpenHandsConfig()
|
||||
load_from_toml(config, config_file)
|
||||
load_from_env(config, os.environ)
|
||||
finalize_config(config)
|
||||
@@ -802,13 +802,13 @@ def load_app_config(
|
||||
return config
|
||||
|
||||
|
||||
def setup_config_from_args(args: argparse.Namespace) -> AppConfig:
|
||||
def setup_config_from_args(args: argparse.Namespace) -> OpenHandsConfig:
|
||||
"""Load config from toml and override with command line arguments.
|
||||
|
||||
Common setup used by both CLI and main.py entry points.
|
||||
"""
|
||||
# Load base config from toml and env vars
|
||||
config = load_app_config(config_file=args.config_file)
|
||||
config = load_openhands_config(config_file=args.config_file)
|
||||
|
||||
# Override with command line arguments if provided
|
||||
if args.llm_config:
|
||||
|
||||
@@ -9,7 +9,7 @@ from openhands.controller.agent import Agent
|
||||
from openhands.controller.replay import ReplayManager
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
parse_arguments,
|
||||
setup_config_from_args,
|
||||
)
|
||||
@@ -47,7 +47,7 @@ class FakeUserResponseFunc(Protocol):
|
||||
|
||||
|
||||
async def run_controller(
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
initial_user_action: Action,
|
||||
sid: str | None = None,
|
||||
runtime: Runtime | None = None,
|
||||
@@ -90,7 +90,7 @@ async def run_controller(
|
||||
config.max_budget_per_task.
|
||||
|
||||
Example:
|
||||
>>> config = load_app_config()
|
||||
>>> config = load_openhands_config()
|
||||
>>> action = MessageAction(content="Write a hello world program")
|
||||
>>> state = await run_controller(config=config, initial_user_action=action)
|
||||
"""
|
||||
@@ -279,7 +279,7 @@ def load_replay_log(trajectory_path: str) -> tuple[list[Event] | None, Action]:
|
||||
if __name__ == '__main__':
|
||||
args = parse_arguments()
|
||||
|
||||
config: AppConfig = setup_config_from_args(args)
|
||||
config: OpenHandsConfig = setup_config_from_args(args)
|
||||
|
||||
# Read task from file, CLI args, or stdin
|
||||
task_str = read_task(args, config.cli_multiline_input)
|
||||
|
||||
@@ -10,7 +10,7 @@ from openhands.controller import AgentController
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
OpenHandsConfig,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events import EventStream
|
||||
@@ -28,7 +28,7 @@ from openhands.utils.async_utils import GENERAL_TIMEOUT, call_async_from_sync
|
||||
|
||||
|
||||
def create_runtime(
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
sid: str | None = None,
|
||||
headless_mode: bool = True,
|
||||
agent: Agent | None = None,
|
||||
@@ -172,7 +172,7 @@ def create_memory(
|
||||
return memory
|
||||
|
||||
|
||||
def create_agent(config: AppConfig) -> Agent:
|
||||
def create_agent(config: OpenHandsConfig) -> Agent:
|
||||
agent_cls: type[Agent] = Agent.get_cls(config.default_agent)
|
||||
agent_config = config.get_agent_config(config.default_agent)
|
||||
llm_config = config.get_llm_config_from_agent(config.default_agent)
|
||||
@@ -188,7 +188,7 @@ def create_agent(config: AppConfig) -> Agent:
|
||||
def create_controller(
|
||||
agent: Agent,
|
||||
runtime: Runtime,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
headless_mode: bool = True,
|
||||
replay_events: list[Event] | None = None,
|
||||
) -> tuple[AgentController, State | None]:
|
||||
@@ -218,7 +218,7 @@ def create_controller(
|
||||
return (controller, initial_state)
|
||||
|
||||
|
||||
def generate_sid(config: AppConfig, session_name: str | None = None) -> str:
|
||||
def generate_sid(config: OpenHandsConfig, session_name: str | None = None) -> str:
|
||||
"""Generate a session id based on the session name and the jwt secret."""
|
||||
session_name = session_name or str(uuid.uuid4())
|
||||
jwt_secret = config.jwt_secret
|
||||
|
||||
@@ -582,7 +582,7 @@ def _extract_and_validate_params(
|
||||
found_params = set()
|
||||
for param_match in param_matches:
|
||||
param_name = param_match.group(1)
|
||||
param_value = param_match.group(2).strip()
|
||||
param_value = param_match.group(2)
|
||||
|
||||
# Validate parameter is allowed
|
||||
if allowed_params and param_name not in allowed_params:
|
||||
|
||||
@@ -4,11 +4,11 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from openhands.controller.agent import Agent
|
||||
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.mcp_config import (
|
||||
MCPConfig,
|
||||
MCPSSEServerConfig,
|
||||
)
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events.action.mcp import MCPAction
|
||||
from openhands.events.observation.mcp import MCPObservation
|
||||
@@ -162,7 +162,7 @@ async def call_tool_mcp(mcp_clients: list[MCPClient], action: MCPAction) -> Obse
|
||||
|
||||
|
||||
async def add_mcp_tools_to_agent(
|
||||
agent: 'Agent', runtime: Runtime, memory: 'Memory', app_config: AppConfig
|
||||
agent: 'Agent', runtime: Runtime, memory: 'Memory', app_config: OpenHandsConfig
|
||||
):
|
||||
"""
|
||||
Add MCP tools to an agent.
|
||||
|
||||
@@ -16,7 +16,7 @@ from termcolor import colored
|
||||
|
||||
import openhands
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import AgentConfig, AppConfig, LLMConfig, SandboxConfig
|
||||
from openhands.core.config import AgentConfig, LLMConfig, OpenHandsConfig, SandboxConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
@@ -377,7 +377,7 @@ class IssueResolver:
|
||||
shutil.rmtree(workspace_base)
|
||||
shutil.copytree(os.path.join(self.output_dir, 'repo'), workspace_base)
|
||||
|
||||
config = AppConfig(
|
||||
config = OpenHandsConfig(
|
||||
default_agent='CodeActAgent',
|
||||
runtime='docker',
|
||||
max_budget_per_task=4,
|
||||
|
||||
@@ -1013,12 +1013,12 @@ if __name__ == '__main__':
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
# if user just removed a folder, prevent server error 500 in UI
|
||||
return []
|
||||
return JSONResponse(content=[])
|
||||
|
||||
try:
|
||||
# Check if the directory exists
|
||||
if not os.path.exists(full_path) or not os.path.isdir(full_path):
|
||||
return []
|
||||
return JSONResponse(content=[])
|
||||
|
||||
entries = os.listdir(full_path)
|
||||
|
||||
@@ -1047,11 +1047,11 @@ if __name__ == '__main__':
|
||||
|
||||
# Combine sorted directories and files
|
||||
sorted_entries = directories + files
|
||||
return sorted_entries
|
||||
return JSONResponse(content=sorted_entries)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Error listing files: {e}')
|
||||
return []
|
||||
return JSONResponse(content=[])
|
||||
|
||||
logger.debug(f'Starting action execution API on port {args.port}')
|
||||
run(app, host='0.0.0.0', port=args.port)
|
||||
|
||||
@@ -15,7 +15,7 @@ from zipfile import ZipFile
|
||||
|
||||
import httpx
|
||||
|
||||
from openhands.core.config import AppConfig, SandboxConfig
|
||||
from openhands.core.config import OpenHandsConfig, SandboxConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
from openhands.core.exceptions import AgentRuntimeDisconnectedError
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -97,7 +97,7 @@ class Runtime(FileEditRuntimeMixin):
|
||||
"""
|
||||
|
||||
sid: str
|
||||
config: AppConfig
|
||||
config: OpenHandsConfig
|
||||
initial_env_vars: dict[str, str]
|
||||
attach_to_existing: bool
|
||||
status_callback: Callable[[str, str, str], None] | None
|
||||
@@ -105,7 +105,7 @@ class Runtime(FileEditRuntimeMixin):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -9,7 +9,7 @@ import httpcore
|
||||
import httpx
|
||||
from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.config.mcp_config import (
|
||||
MCPConfig,
|
||||
MCPSSEServerConfig,
|
||||
@@ -65,7 +65,7 @@ class ActionExecutionClient(Runtime):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
@@ -404,12 +404,12 @@ class ActionExecutionClient(Runtime):
|
||||
if response.status_code != 200:
|
||||
self.log('warning', f'Failed to update MCP server: {response.text}')
|
||||
else:
|
||||
if result['router_error_log']:
|
||||
if result.get('router_error_log'):
|
||||
self.log(
|
||||
'warning',
|
||||
f'Some MCP servers failed to be added: {result["router_error_log"]}',
|
||||
)
|
||||
|
||||
|
||||
# Update our cached list with combined servers after successful update
|
||||
self._last_updated_mcp_stdio_servers = combined_servers.copy()
|
||||
self.log(
|
||||
|
||||
@@ -22,7 +22,7 @@ from openhands_aci.editor.results import ToolResult
|
||||
from openhands_aci.utils.diff import get_diff
|
||||
from pydantic import SecretStr
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig, MCPStdioServerConfig
|
||||
from openhands.core.exceptions import LLMMalformedActionError
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -57,7 +57,7 @@ class CLIRuntime(Runtime):
|
||||
file operations using Python's standard library. It does not implement browser functionality.
|
||||
|
||||
Args:
|
||||
config (AppConfig): The application configuration.
|
||||
config (OpenHandsConfig): The application configuration.
|
||||
event_stream (EventStream): The event stream to subscribe to.
|
||||
sid (str, optional): The session ID. Defaults to 'default'.
|
||||
plugins (list[PluginRequirement] | None, optional): List of plugin requirements. Defaults to None.
|
||||
@@ -71,7 +71,7 @@ class CLIRuntime(Runtime):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -11,7 +11,7 @@ from daytona_sdk import (
|
||||
Workspace,
|
||||
)
|
||||
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.events.stream import EventStream
|
||||
from openhands.runtime.impl.action_execution.action_execution_client import (
|
||||
ActionExecutionClient,
|
||||
@@ -33,7 +33,7 @@ class DaytonaRuntime(ActionExecutionClient):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -8,7 +8,7 @@ import httpx
|
||||
import tenacity
|
||||
from docker.models.containers import Container
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.exceptions import (
|
||||
AgentRuntimeDisconnectedError,
|
||||
AgentRuntimeNotFoundError,
|
||||
@@ -23,7 +23,10 @@ from openhands.runtime.impl.action_execution.action_execution_client import (
|
||||
from openhands.runtime.impl.docker.containers import stop_all_containers
|
||||
from openhands.runtime.plugins import PluginRequirement
|
||||
from openhands.runtime.utils import find_available_tcp_port
|
||||
from openhands.runtime.utils.command import DEFAULT_MAIN_MODULE, get_action_execution_server_startup_command
|
||||
from openhands.runtime.utils.command import (
|
||||
DEFAULT_MAIN_MODULE,
|
||||
get_action_execution_server_startup_command,
|
||||
)
|
||||
from openhands.runtime.utils.log_streamer import LogStreamer
|
||||
from openhands.runtime.utils.runtime_build import build_runtime_image
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
@@ -62,7 +65,7 @@ class DockerRuntime(ActionExecutionClient):
|
||||
When receive an event, it will send the event to runtime-client which run inside the docker environment.
|
||||
|
||||
Args:
|
||||
config (AppConfig): The application configuration.
|
||||
config (OpenHandsConfig): The application configuration.
|
||||
event_stream (EventStream): The event stream to subscribe to.
|
||||
sid (str, optional): The session ID. Defaults to 'default'.
|
||||
plugins (list[PluginRequirement] | None, optional): List of plugin requirements. Defaults to None.
|
||||
@@ -73,7 +76,7 @@ class DockerRuntime(ActionExecutionClient):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Callable
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.events.action import (
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
@@ -22,7 +22,7 @@ from openhands.runtime.utils.files import insert_lines, read_lines
|
||||
class E2BRuntime(Runtime):
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -12,7 +12,7 @@ import httpx
|
||||
import tenacity
|
||||
|
||||
import openhands
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.exceptions import AgentRuntimeDisconnectedError
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events import EventStream
|
||||
@@ -107,7 +107,7 @@ class LocalRuntime(ActionExecutionClient):
|
||||
When receiving an event, it will send the event to the server via HTTP.
|
||||
|
||||
Args:
|
||||
config (AppConfig): The application configuration.
|
||||
config (OpenHandsConfig): The application configuration.
|
||||
event_stream (EventStream): The event stream to subscribe to.
|
||||
sid (str, optional): The session ID. Defaults to 'default'.
|
||||
plugins (list[PluginRequirement] | None, optional): list of plugin requirements. Defaults to None.
|
||||
@@ -116,7 +116,7 @@ class LocalRuntime(ActionExecutionClient):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -7,7 +7,7 @@ import httpx
|
||||
import modal
|
||||
import tenacity
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.events import EventStream
|
||||
from openhands.runtime.impl.action_execution.action_execution_client import (
|
||||
ActionExecutionClient,
|
||||
@@ -31,7 +31,7 @@ class ModalRuntime(ActionExecutionClient):
|
||||
When receive an event, it will send the event to runtime-client which run inside the Modal sandbox environment.
|
||||
|
||||
Args:
|
||||
config (AppConfig): The application configuration.
|
||||
config (OpenHandsConfig): The application configuration.
|
||||
event_stream (EventStream): The event stream to subscribe to.
|
||||
sid (str, optional): The session ID. Defaults to 'default'.
|
||||
plugins (list[PluginRequirement] | None, optional): List of plugin requirements. Defaults to None.
|
||||
@@ -44,7 +44,7 @@ class ModalRuntime(ActionExecutionClient):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -8,7 +8,7 @@ import httpx
|
||||
import tenacity
|
||||
from tenacity import RetryCallState
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.exceptions import (
|
||||
AgentRuntimeDisconnectedError,
|
||||
AgentRuntimeError,
|
||||
@@ -24,7 +24,10 @@ from openhands.runtime.impl.action_execution.action_execution_client import (
|
||||
ActionExecutionClient,
|
||||
)
|
||||
from openhands.runtime.plugins import PluginRequirement
|
||||
from openhands.runtime.utils.command import DEFAULT_MAIN_MODULE, get_action_execution_server_startup_command
|
||||
from openhands.runtime.utils.command import (
|
||||
DEFAULT_MAIN_MODULE,
|
||||
get_action_execution_server_startup_command,
|
||||
)
|
||||
from openhands.runtime.utils.request import send_request
|
||||
from openhands.runtime.utils.runtime_build import build_runtime_image
|
||||
from openhands.utils.async_utils import call_sync_from_async
|
||||
@@ -45,7 +48,7 @@ class RemoteRuntime(ActionExecutionClient):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -6,7 +6,7 @@ from runloop_api_client import Runloop
|
||||
from runloop_api_client.types import DevboxView
|
||||
from runloop_api_client.types.shared_params import LaunchParameters
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events import EventStream
|
||||
from openhands.runtime.impl.action_execution.action_execution_client import (
|
||||
@@ -27,7 +27,7 @@ class RunloopRuntime(ActionExecutionClient):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
sid: str = 'default',
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.runtime.plugins import PluginRequirement
|
||||
|
||||
DEFAULT_PYTHON_PREFIX = [
|
||||
@@ -15,7 +15,7 @@ DEFAULT_MAIN_MODULE = 'openhands.runtime.action_execution_server'
|
||||
def get_action_execution_server_startup_command(
|
||||
server_port: int,
|
||||
plugins: list[PluginRequirement],
|
||||
app_config: AppConfig,
|
||||
app_config: OpenHandsConfig,
|
||||
python_prefix: list[str] = DEFAULT_PYTHON_PREFIX,
|
||||
override_user_id: int | None = None,
|
||||
override_username: str | None = None,
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any
|
||||
|
||||
from openhands_aci.utils.diff import get_diff
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events.action import (
|
||||
FileEditAction,
|
||||
@@ -83,7 +83,7 @@ def get_new_file_contents(
|
||||
|
||||
|
||||
class FileEditRuntimeInterface(ABC):
|
||||
config: AppConfig
|
||||
config: OpenHandsConfig
|
||||
|
||||
@abstractmethod
|
||||
def read(self, action: FileReadAction) -> Observation:
|
||||
|
||||
@@ -58,7 +58,8 @@ class GitHandler:
|
||||
Returns:
|
||||
str: The file content.
|
||||
"""
|
||||
output = self.execute(f'cat {file_path}', self.cwd)
|
||||
escaped_path = self._escape_path(file_path)
|
||||
output = self.execute(f'cat {escaped_path}', self.cwd)
|
||||
return output.content
|
||||
|
||||
def _verify_ref_exists(self, ref: str) -> bool:
|
||||
@@ -102,6 +103,19 @@ class GitHandler:
|
||||
|
||||
return None
|
||||
|
||||
def _escape_path(self, path: str) -> str:
|
||||
"""
|
||||
Escapes special characters in a file path to prevent shell interpretation.
|
||||
|
||||
Args:
|
||||
path (str): The file path to escape.
|
||||
|
||||
Returns:
|
||||
str: The escaped file path.
|
||||
"""
|
||||
# Escape special characters that could be interpreted by the shell
|
||||
return "'" + path.replace("'", "'\\''") + "'"
|
||||
|
||||
def _get_ref_content(self, file_path: str) -> str:
|
||||
"""
|
||||
Retrieves the content of a file from a valid Git reference.
|
||||
@@ -116,7 +130,9 @@ class GitHandler:
|
||||
if not ref:
|
||||
return ''
|
||||
|
||||
cmd = f'git show {ref}:{file_path}'
|
||||
# Escape the file path to handle special characters like parentheses
|
||||
escaped_path = self._escape_path(file_path)
|
||||
cmd = f'git show {ref}:{escaped_path}'
|
||||
output = self.execute(cmd, self.cwd)
|
||||
return output.content if output.exit_code == 0 else ''
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@ from abc import ABC, abstractmethod
|
||||
|
||||
import socketio
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.events.event_store import EventStore
|
||||
from openhands.server.config.server_config import ServerConfig
|
||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||
from openhands.server.data_models.conversation_info import ConversationInfo
|
||||
from openhands.server.monitoring import MonitoringListener
|
||||
from openhands.server.session.conversation import Conversation
|
||||
from openhands.server.session.conversation import ServerConversation
|
||||
from openhands.storage.conversation.conversation_store import ConversationStore
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.files import FileStore
|
||||
@@ -26,7 +24,7 @@ class ConversationManager(ABC):
|
||||
"""
|
||||
|
||||
sio: socketio.AsyncServer
|
||||
config: AppConfig
|
||||
config: OpenHandsConfig
|
||||
file_store: FileStore
|
||||
conversation_store: ConversationStore
|
||||
|
||||
@@ -41,11 +39,11 @@ class ConversationManager(ABC):
|
||||
@abstractmethod
|
||||
async def attach_to_conversation(
|
||||
self, sid: str, user_id: str | None = None
|
||||
) -> Conversation | None:
|
||||
) -> ServerConversation | None:
|
||||
"""Attach to an existing conversation or create a new one."""
|
||||
|
||||
@abstractmethod
|
||||
async def detach_from_conversation(self, conversation: Conversation):
|
||||
async def detach_from_conversation(self, conversation: ServerConversation):
|
||||
"""Detach from a conversation."""
|
||||
|
||||
@abstractmethod
|
||||
@@ -109,7 +107,7 @@ class ConversationManager(ABC):
|
||||
def get_instance(
|
||||
cls,
|
||||
sio: socketio.AsyncServer,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
file_store: FileStore,
|
||||
server_config: ServerConfig,
|
||||
monitoring_listener: MonitoringListener,
|
||||
|
||||
@@ -15,7 +15,7 @@ from docker.models.containers import Container
|
||||
from fastapi import status
|
||||
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.events.action import MessageAction
|
||||
from openhands.events.nested_event_store import NestedEventStore
|
||||
@@ -30,7 +30,7 @@ from openhands.server.conversation_manager.conversation_manager import (
|
||||
)
|
||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||
from openhands.server.monitoring import MonitoringListener
|
||||
from openhands.server.session.conversation import Conversation
|
||||
from openhands.server.session.conversation import ServerConversation
|
||||
from openhands.server.session.conversation_init_data import ConversationInitData
|
||||
from openhands.server.session.session import ROOM_KEY, Session
|
||||
from openhands.storage.conversation.conversation_store import ConversationStore
|
||||
@@ -45,10 +45,10 @@ from openhands.utils.import_utils import get_impl
|
||||
|
||||
@dataclass
|
||||
class DockerNestedConversationManager(ConversationManager):
|
||||
"""Conversation manager where the agent loops exist inside the docker containers."""
|
||||
"""ServerConversation manager where the agent loops exist inside the docker containers."""
|
||||
|
||||
sio: socketio.AsyncServer
|
||||
config: AppConfig
|
||||
config: OpenHandsConfig
|
||||
server_config: ServerConfig
|
||||
file_store: FileStore
|
||||
docker_client: docker.DockerClient = field(default_factory=docker.from_env)
|
||||
@@ -65,11 +65,11 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
|
||||
async def attach_to_conversation(
|
||||
self, sid: str, user_id: str | None = None
|
||||
) -> Conversation | None:
|
||||
) -> ServerConversation | None:
|
||||
# Not supported - clients should connect directly to the nested server!
|
||||
raise ValueError('unsupported_operation')
|
||||
|
||||
async def detach_from_conversation(self, conversation: Conversation):
|
||||
async def detach_from_conversation(self, conversation: ServerConversation):
|
||||
# Not supported - clients should connect directly to the nested server!
|
||||
raise ValueError('unsupported_operation')
|
||||
|
||||
@@ -309,7 +309,7 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
def get_instance(
|
||||
cls,
|
||||
sio: socketio.AsyncServer,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
file_store: FileStore,
|
||||
server_config: ServerConfig,
|
||||
monitoring_listener: MonitoringListener,
|
||||
@@ -433,7 +433,7 @@ class DockerNestedConversationManager(ConversationManager):
|
||||
volumes = [v.strip() for v in config.sandbox.volumes.split(',')]
|
||||
conversation_dir = get_conversation_dir(sid, user_id)
|
||||
volumes.append(
|
||||
f'{config.file_store_path}/{conversation_dir}:{AppConfig.model_fields["file_store_path"].default}/{conversation_dir}:rw'
|
||||
f'{config.file_store_path}/{conversation_dir}:{OpenHandsConfig.model_fields["file_store_path"].default}/{conversation_dir}:rw'
|
||||
)
|
||||
config.sandbox.volumes = ','.join(volumes)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Callable, Iterable
|
||||
|
||||
import socketio
|
||||
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.core.exceptions import AgentRuntimeUnavailableError
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.schema.agent import AgentState
|
||||
@@ -16,7 +16,7 @@ from openhands.server.config.server_config import ServerConfig
|
||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||
from openhands.server.monitoring import MonitoringListener
|
||||
from openhands.server.session.agent_session import WAIT_TIME_BEFORE_CLOSE
|
||||
from openhands.server.session.conversation import Conversation
|
||||
from openhands.server.session.conversation import ServerConversation
|
||||
from openhands.server.session.session import ROOM_KEY, Session
|
||||
from openhands.storage.conversation.conversation_store import ConversationStore
|
||||
from openhands.storage.data_models.conversation_metadata import ConversationMetadata
|
||||
@@ -41,17 +41,17 @@ class StandaloneConversationManager(ConversationManager):
|
||||
"""Manages conversations in standalone mode (single server instance)."""
|
||||
|
||||
sio: socketio.AsyncServer
|
||||
config: AppConfig
|
||||
config: OpenHandsConfig
|
||||
file_store: FileStore
|
||||
server_config: ServerConfig
|
||||
# Defaulting monitoring_listener for temp backward compatibility.
|
||||
monitoring_listener: MonitoringListener = MonitoringListener()
|
||||
_local_agent_loops_by_sid: dict[str, Session] = field(default_factory=dict)
|
||||
_local_connection_id_to_session_id: dict[str, str] = field(default_factory=dict)
|
||||
_active_conversations: dict[str, tuple[Conversation, int]] = field(
|
||||
_active_conversations: dict[str, tuple[ServerConversation, int]] = field(
|
||||
default_factory=dict
|
||||
)
|
||||
_detached_conversations: dict[str, tuple[Conversation, float]] = field(
|
||||
_detached_conversations: dict[str, tuple[ServerConversation, float]] = field(
|
||||
default_factory=dict
|
||||
)
|
||||
_conversations_lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
||||
@@ -69,7 +69,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
|
||||
async def attach_to_conversation(
|
||||
self, sid: str, user_id: str | None = None
|
||||
) -> Conversation | None:
|
||||
) -> ServerConversation | None:
|
||||
start_time = time.time()
|
||||
if not await session_exists(sid, self.file_store, user_id=user_id):
|
||||
return None
|
||||
@@ -94,7 +94,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
return conversation
|
||||
|
||||
# Create new conversation if none exists
|
||||
c = Conversation(
|
||||
c = ServerConversation(
|
||||
sid, file_store=self.file_store, config=self.config, user_id=user_id
|
||||
)
|
||||
try:
|
||||
@@ -108,7 +108,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
return None
|
||||
end_time = time.time()
|
||||
logger.info(
|
||||
f'Conversation {c.sid} connected in {end_time - start_time} seconds'
|
||||
f'ServerConversation {c.sid} connected in {end_time - start_time} seconds'
|
||||
)
|
||||
self._active_conversations[sid] = (c, 1)
|
||||
return c
|
||||
@@ -129,7 +129,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
agent_loop_info = await self.maybe_start_agent_loop(sid, settings, user_id)
|
||||
return agent_loop_info
|
||||
|
||||
async def detach_from_conversation(self, conversation: Conversation):
|
||||
async def detach_from_conversation(self, conversation: ServerConversation):
|
||||
sid = conversation.sid
|
||||
async with self._conversations_lock:
|
||||
if sid in self._active_conversations:
|
||||
@@ -377,7 +377,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
def get_instance(
|
||||
cls,
|
||||
sio: socketio.AsyncServer,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
file_store: FileStore,
|
||||
server_config: ServerConfig,
|
||||
monitoring_listener: MonitoringListener | None,
|
||||
@@ -475,7 +475,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
continue
|
||||
results.append(self._agent_loop_info_from_session(session))
|
||||
return results
|
||||
|
||||
|
||||
def _agent_loop_info_from_session(self, session: Session):
|
||||
return AgentLoopInfo(
|
||||
conversation_id=session.sid,
|
||||
@@ -485,7 +485,7 @@ class StandaloneConversationManager(ConversationManager):
|
||||
)
|
||||
|
||||
def _get_conversation_url(self, conversation_id: str):
|
||||
return f"/api/conversations/{conversation_id}"
|
||||
return f'/api/conversations/{conversation_id}'
|
||||
|
||||
|
||||
def _last_updated_at_key(conversation: ConversationMetadata) -> float:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.server.shared import config as shared_config
|
||||
|
||||
@@ -30,7 +30,7 @@ def sanitize_filename(filename: str) -> str:
|
||||
|
||||
|
||||
def load_file_upload_config(
|
||||
config: AppConfig = shared_config,
|
||||
config: OpenHandsConfig = shared_config,
|
||||
) -> tuple[int, bool, list[str]]:
|
||||
"""Load file upload configuration from the config object.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.openhands_config import OpenHandsConfig
|
||||
from openhands.events.event import Event
|
||||
|
||||
|
||||
@@ -33,6 +33,6 @@ class MonitoringListener:
|
||||
@classmethod
|
||||
def get_instance(
|
||||
cls,
|
||||
config: AppConfig,
|
||||
config: OpenHandsConfig,
|
||||
) -> 'MonitoringListener':
|
||||
return cls()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user