Compare commits
99 Commits
enyst/cli-
...
update-mic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93287ef9ac | ||
|
|
e70595f46f | ||
|
|
1d3ff66987 | ||
|
|
1a95f86802 | ||
|
|
eee12bfd94 | ||
|
|
8c2d4dbe8b | ||
|
|
f5ae1759b6 | ||
|
|
9ec94737ed | ||
|
|
63c7815823 | ||
|
|
95ae47307c | ||
|
|
035050252b | ||
|
|
5b48aee0c9 | ||
|
|
1a89dbb738 | ||
|
|
bba62c26fd | ||
|
|
9b4ad4e6e3 | ||
|
|
1e33624951 | ||
|
|
8b90d610c6 | ||
|
|
834abc0eee | ||
|
|
c9bb0fc168 | ||
|
|
5d69e606eb | ||
|
|
081880248c | ||
|
|
4ee269c3f7 | ||
|
|
711315c3b9 | ||
|
|
c2e6244b86 | ||
|
|
a1479adfd3 | ||
|
|
99fd3f7bb2 | ||
|
|
c617881b3c | ||
|
|
7ca3607dcd | ||
|
|
89999a8e09 | ||
|
|
3d9761df7e | ||
|
|
ea3c4f9366 | ||
|
|
bda0a64a3d | ||
|
|
8badcb7b35 | ||
|
|
078534c2ab | ||
|
|
ba885cd04c | ||
|
|
ee64a6662a | ||
|
|
075ef4db9f | ||
|
|
a526f73ea6 | ||
|
|
516f9fa635 | ||
|
|
8c5995a5d8 | ||
|
|
afe130f6db | ||
|
|
cc2f96c6c4 | ||
|
|
0ca3188afa | ||
|
|
283f503870 | ||
|
|
0691e5c0d0 | ||
|
|
fc16da8fd2 | ||
|
|
bd3ff43c67 | ||
|
|
0fe5b808af | ||
|
|
6c49686ff0 | ||
|
|
17212bb2f2 | ||
|
|
9d9f931e95 | ||
|
|
6fe9680474 | ||
|
|
53c80d1c92 | ||
|
|
401262f353 | ||
|
|
58845b01a3 | ||
|
|
469d184157 | ||
|
|
4837c4dc74 | ||
|
|
6763f21cc3 | ||
|
|
32e610ac1d | ||
|
|
85c65391ca | ||
|
|
c444dbfbbf | ||
|
|
dd988d0f14 | ||
|
|
6f1a74e286 | ||
|
|
7b956b6103 | ||
|
|
34b097115d | ||
|
|
3e4ab4f379 | ||
|
|
54cd9f7e44 | ||
|
|
802b765f98 | ||
|
|
18c88f99ff | ||
|
|
f3934be07b | ||
|
|
6ce9f49d1e | ||
|
|
fc07622b20 | ||
|
|
da935f9d8f | ||
|
|
642cc52a1a | ||
|
|
4c361ab9e5 | ||
|
|
5dfa1bb6eb | ||
|
|
a07cf972a5 | ||
|
|
f2e3bc3254 | ||
|
|
3790ec7d60 | ||
|
|
3c0719309e | ||
|
|
0236e0943e | ||
|
|
cd464c0022 | ||
|
|
4519a7f4f3 | ||
|
|
fdc591330b | ||
|
|
98e454e82c | ||
|
|
e088d2d24a | ||
|
|
58c574af1e | ||
|
|
405f0069f8 | ||
|
|
f26d770d03 | ||
|
|
bf2c3de219 | ||
|
|
7c35ce16e5 | ||
|
|
f4024ccd94 | ||
|
|
b55bfed831 | ||
|
|
cb0994027f | ||
|
|
bcc9bd0b9a | ||
|
|
6c144e6b5a | ||
|
|
e90b841b0d | ||
|
|
a1e6ed4dff | ||
|
|
ad6311d3cd |
7
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
@@ -45,6 +45,13 @@ body:
|
||||
description: What version of OpenHands are you using?
|
||||
placeholder: ex. 0.9.8, main, etc.
|
||||
|
||||
- type: input
|
||||
id: model-name
|
||||
attributes:
|
||||
label: Model Name
|
||||
description: What model are you using?
|
||||
placeholder: ex. gpt-4o, claude-3-5-sonnet, openrouter/deepseek-r1, etc.
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
|
||||
@@ -159,7 +159,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
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.44-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.45-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
23
README.md
@@ -48,7 +48,7 @@ Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or [sign up for
|
||||
|
||||
## ☁️ OpenHands Cloud
|
||||
The easiest way to get started with OpenHands is on [OpenHands Cloud](https://app.all-hands.dev),
|
||||
which comes with $50 in free credits for new users.
|
||||
which comes with $20 in free credits for new users.
|
||||
|
||||
## 💻 Running OpenHands Locally
|
||||
|
||||
@@ -62,17 +62,17 @@ system requirements and more information.
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.44
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
@@ -147,13 +147,12 @@ For a list of open source projects and licenses used in OpenHands, please see ou
|
||||
## 📚 Cite
|
||||
|
||||
```
|
||||
@misc{openhands,
|
||||
title={{OpenHands: An Open Platform for AI Software Developers as Generalist Agents}},
|
||||
author={Xingyao Wang and Boxuan Li and Yufan Song and Frank F. Xu and Xiangru Tang and Mingchen Zhuge and Jiayi Pan and Yueqi Song and Bowen Li and Jaskirat Singh and Hoang H. Tran and Fuqiang Li and Ren Ma and Mingzhang Zheng and Bill Qian and Yanjun Shao and Niklas Muennighoff and Yizhe Zhang and Binyuan Hui and Junyang Lin and Robert Brennan and Hao Peng and Heng Ji and Graham Neubig},
|
||||
year={2024},
|
||||
eprint={2407.16741},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.SE},
|
||||
url={https://arxiv.org/abs/2407.16741},
|
||||
@inproceedings{
|
||||
wang2025openhands,
|
||||
title={OpenHands: An Open Platform for {AI} Software Developers as Generalist Agents},
|
||||
author={Xingyao Wang and Boxuan Li and Yufan Song and Frank F. Xu and Xiangru Tang and Mingchen Zhuge and Jiayi Pan and Yueqi Song and Bowen Li and Jaskirat Singh and Hoang H. Tran and Fuqiang Li and Ren Ma and Mingzhang Zheng and Bill Qian and Yanjun Shao and Niklas Muennighoff and Yizhe Zhang and Binyuan Hui and Junyang Lin and Robert Brennan and Hao Peng and Heng Ji and Graham Neubig},
|
||||
booktitle={The Thirteenth International Conference on Learning Representations},
|
||||
year={2025},
|
||||
url={https://openreview.net/forum?id=OJd3ayDDoF}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.44
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45
|
||||
```
|
||||
|
||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||
|
||||
60
README_JA.md
Normal file
@@ -0,0 +1,60 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
<div align="center">
|
||||
<img src="./docs/static/img/logo.png" alt="Logo" width="200">
|
||||
<h1 align="center">OpenHands: コードを減らして、もっと作ろう</h1>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors"><img src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Contributors"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
|
||||
<br/>
|
||||
<a href="https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Slackコミュニティに参加"></a>
|
||||
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Discordコミュニティに参加"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="クレジット"></a>
|
||||
<br/>
|
||||
<a href="https://docs.all-hands.dev/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="ドキュメントを見る"></a>
|
||||
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Arxiv論文"></a>
|
||||
<a href="https://docs.google.com/spreadsheets/d/1wOUdFCMyY6Nt0AIqF705KN4JKOWgeI4wUGUP60krXXs/edit?gid=0#gid=0"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="評価ベンチマークスコア"></a>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
OpenHands(旧OpenDevin)へようこそ。これはAIが駆動するソフトウェア開発エージェントのプラットフォームです。
|
||||
|
||||
OpenHandsのエージェントは人間の開発者ができることは何でもこなします。コードを修正し、コマンドを実行し、ウェブを閲覧し、APIを呼び出し、StackOverflowからコードスニペットをコピーすることさえできます。
|
||||
|
||||
詳細は[docs.all-hands.dev](https://docs.all-hands.dev)をご覧いただくか、[OpenHands Cloud](https://app.all-hands.dev)に登録して始めましょう。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 仕事でOpenHandsを使っていますか?ぜひお話を聞かせてください。[こちらの短いフォーム](https://docs.google.com/forms/d/e/1FAIpQLSet3VbGaz8z32gW9Wm-Grl4jpt5WgMXPgJ4EDPVmCETCBpJtQ/viewform)にご記入いただき、Design Partnerプログラムにご参加ください。商用機能の早期アクセスや製品ロードマップへのフィードバックの機会を提供します。
|
||||
|
||||

|
||||
|
||||
## ☁️ OpenHands Cloud
|
||||
OpenHandsを始める最も簡単な方法は[OpenHands Cloud](https://app.all-hands.dev)を利用することです。新規ユーザーには50ドル分の無料クレジットが付与されます。
|
||||
|
||||
## 💻 OpenHandsをローカルで実行する
|
||||
|
||||
OpenHandsはDockerを利用してローカル環境でも実行できます。システム要件や詳細については[Running OpenHands](https://docs.all-hands.dev/usage/installation)ガイドをご覧ください。
|
||||
|
||||
> [!WARNING]
|
||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45
|
||||
```
|
||||
|
||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||
|
||||
OpenHandsは[http://localhost:3000](http://localhost:3000)で起動します!
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
- DOCKER_HOST_ADDR=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.44-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.45-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:22.04
|
||||
FROM ubuntu:24.04
|
||||
|
||||
# install basic packages
|
||||
RUN apt-get update && apt-get install -y \
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
17
docs/README_JA.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# セットアップ
|
||||
|
||||
```
|
||||
npm install -g mint
|
||||
```
|
||||
|
||||
または
|
||||
|
||||
```
|
||||
yarn global add mint
|
||||
```
|
||||
|
||||
# プレビュー
|
||||
|
||||
```
|
||||
mint dev
|
||||
```
|
||||
@@ -26,6 +26,7 @@
|
||||
"usage/installation",
|
||||
"usage/getting-started",
|
||||
"usage/key-features",
|
||||
"usage/faqs",
|
||||
{
|
||||
"group": "OpenHands Cloud",
|
||||
"pages": [
|
||||
@@ -43,7 +44,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Running OpenHands on Your Own",
|
||||
"group": "Run OpenHands on Your Own",
|
||||
"pages": [
|
||||
"usage/local-setup",
|
||||
"usage/how-to/gui-mode",
|
||||
@@ -103,8 +104,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Customization",
|
||||
"group": "Customizations & Settings",
|
||||
"pages": [
|
||||
"usage/common-settings",
|
||||
"usage/prompting/repository",
|
||||
{
|
||||
"group": "Microagents",
|
||||
@@ -149,6 +151,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Success Stories",
|
||||
"pages": [
|
||||
"success-stories/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "API Reference",
|
||||
"openapi": "/openapi.json"
|
||||
|
||||
217
docs/success-stories/index.mdx
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
title: "Success Stories"
|
||||
description: "Real-world examples of what you can achieve with OpenHands"
|
||||
---
|
||||
|
||||
Discover how developers and teams are using OpenHands to automate their software development workflows. From quick fixes to complex projects, see what's possible with AI-powered development assistance.
|
||||
|
||||
Check out the [#success-stories](https://www.linen.dev/s/openhands/c/success-stories) channel on our Slack for more!
|
||||
|
||||
<Update label="2025-06-13 OpenHands helps frontline support" description="@Joe Pelletier">
|
||||
|
||||
## One of the cool things about OpenHands, and especially the Slack Integration, is the ability to empower folks who are on the ‘front lines’ with customers.
|
||||
|
||||
For example, often times Support and Customer Success teams will field bug reports, doc questions, and other ‘nits’ from customers. They tend to have few options to deal with this, other than file a feedback ticket with product teams and hope it gets prioritized in an upcoming sprint.
|
||||
|
||||
Instead, with tools like OpenHands and the Slack integration, they can request OpenHands to make fixes proactively and then have someone on the engineering team (like a lead engineer, a merge engineer, or even technical product manager) review the PR and approve it — thus reducing the cycle time for ‘quick wins’ from weeks to just a few hours.
|
||||
|
||||
Here's how we do that with the OpenHands project:
|
||||
|
||||
<iframe
|
||||
width="560"
|
||||
height="560"
|
||||
src="https://www.linen.dev/s/openhands/t/29118545/seems-mcp-config-from-config-toml-is-being-overwritten-hence#629f8e2b-cde8-427e-920c-390557a06cc9"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29124350/one-of-the-cool-things-about-openhands-and-especially-the-sl#25029f37-7b0d-4535-9187-83b3e06a4011)
|
||||
|
||||
</Update>
|
||||
|
||||
|
||||
<Update label="2025-06-13 Ask OpenHands to show me some love" description="@Graham Neubig">
|
||||
|
||||
## Asked openhands to “show me some love” and...
|
||||
|
||||
Asked openhands to “show me some love” and it coded up this app for me, actually kinda genuinely feel loved
|
||||
|
||||
<video
|
||||
controls
|
||||
autoplay
|
||||
className="w-full aspect-video"
|
||||
src="/success-stories/stories/2025-06-13-show-love/v1.mp4"
|
||||
></video>
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100731/asked-openhands-to-show-me-some-love-and-it-coded-up-this-ap#1e08af6b-b7d5-4167-8a53-17e6806555e0)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-11 OpenHands does 100% of my infra IAM research for me" description="@Xingyao Wang">
|
||||
|
||||
## Now, OpenHands does 100% of my infra IAM research for me
|
||||
|
||||
Got an IAM error on GCP? Send a screenshot to OH... and it just works!!!
|
||||
Can't imagine going back to the early days without OH: I'd spend an entire afternoon figuring how to get IAM right
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100732/now-openhands-does-100-of-my-infra-iam-research-for-me-sweat#20482a73-4e2e-4edd-b6d1-c9e8442fccd1)
|
||||
|
||||

|
||||

|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-08 OpenHands builds an interactive map for me" description="@Rodrigo Argenton Freire (ODLab)">
|
||||
|
||||
## Very simple example, but baby steps....
|
||||
|
||||
I am a professor of architecture and urban design. We built, me and some students, an interactive map prototype to help visitors and new students to find important places in the campus. Considering that we lack a lot of knowledge in programming, that was really nice to build and a smooth process.
|
||||
We first created the main components with all-hands and then adjusted some details locally. Definitely, saved us a lot of time and money.
|
||||
That's a prototype but we will have all the info by tuesday.
|
||||
https://buriti-emau.github.io/Mapa-UFU/
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100736/very-simple-example-but-baby-steps-i-am-a-professor-of-archi#8f2e3f3f-44e6-44ea-b9a8-d53487470179)
|
||||
|
||||

|
||||
|
||||
</Update>
|
||||
|
||||
|
||||
<Update label="2025-06-06 Web Search Saves the Day" description="@Ian Walker">
|
||||
|
||||
## Tavily adapter helps solve persistent debugging issue
|
||||
|
||||
Big congratulations to the new [Tavily adapter](https://www.all-hands.dev/blog/building-a-provably-versatile-agent)... OpenHands and I have been beavering away at a Lightstreamer client library for most of this week but were getting a persistent (and unhelpful) "unexpected error" from the server.
|
||||
|
||||
Coming back to the problem today, after trying several unsuccessful fixes prompted by me, OH decided all by itself to search the web, and found the cause of the problem (of course it was simply CRLF line endings...). I was on the verge of giving up - good thing OH has more stamina than me!
|
||||
|
||||
This demonstrates how OpenHands' web search capabilities can help solve debugging issues that would otherwise require extensive manual research.
|
||||
|
||||
<iframe
|
||||
width="560"
|
||||
height="560"
|
||||
src="https://www.linen.dev/s/openhands/t/29100737/big-congratulations-to-the-new-tavily-adapter-openhands-and-#87b027e5-188b-425e-8aa9-719dcb4929f4"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100737/big-congratulations-to-the-new-tavily-adapter-openhands-and-#76f1fb26-6ef7-4709-b9ea-fb99105e47e4)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-05 OpenHands updates my personal website for a new paper" description="@Xingyao Wang">
|
||||
|
||||
## I asked OpenHands to update my personal website for the "OpenHands Versa" paper.
|
||||
|
||||
It is an extremely trivial task: You just need to browse to arxiv, copy the author names, format them for BibTeX, and then modify the papers.bib file. But now I'm getting way too lazy to even open my IDE and actually do this one-file change!
|
||||
|
||||
[Original Tweet/X thread](https://x.com/xingyaow_/status/1930796287919542410)
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100738/i-asked-openhands-to-update-my-personal-website-for-the-open#f0324022-b12b-4d34-b12b-bdbc43823f69)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-06-02 OpenHands makes an animated gif of swe-bench verified scores over time" description="@Graham Neubig">
|
||||
|
||||
## I asked OpenHands to make an animated gif of swe-bench verified scores over time.
|
||||
|
||||
It took a bit of prompting but ended up looking pretty nice I think
|
||||
|
||||
<video width="560" height="315" autoPlay loop muted src="/success-stories/stories/2025-06-02-swebench-score/s1.mp4"></video>
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100744/i-asked-openhands-to-make-an-animated-gif-of-swe-bench-verif#fb3b82c9-6222-4311-b97b-b2ac1cfe6dff)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-05-30 AWS Troubleshooting" description="@Graham Neubig">
|
||||
|
||||
## Quick AWS security group fix
|
||||
|
||||
I really don't like trying to fix issues with AWS, especially security groups and other finicky things like this. But I started up an instance and wasn't able to ssh in. So I asked OpenHands:
|
||||
|
||||
> Currently, the following ssh command is timing out:
|
||||
>
|
||||
> $ ssh -i gneubig.pem ubuntu@XXX.us-east-2.compute.amazonaws.com
|
||||
> ssh: connect to host XXX.us-east-2.compute.amazonaws.com port 22: Operation timed out
|
||||
>
|
||||
> Use the provided AWS credentials to take a look at i-XXX and examine why
|
||||
|
||||
And 2 minutes later I was able to SSH in!
|
||||
|
||||
This shows how OpenHands can quickly diagnose and fix AWS infrastructure issues that would normally require manual investigation.
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100747/i-really-don-t-like-trying-to-fix-issues-with-aws-especially#d92a66d2-3bc1-4467-9d09-dc983004d083)
|
||||
|
||||
</Update>
|
||||
|
||||
|
||||
<Update label="2025-05-04 Chrome Extension Development" description="@Xingyao Wang">
|
||||
|
||||
## OpenHands builds Chrome extension for GitHub integration
|
||||
|
||||
I asked OpenHands to write a Chrome extension based on our [OpenHands Cloud API](https://docs.all-hands.dev/modules/usage/cloud/cloud-api). Once installed, you can now easily launch an OpenHands cloud session from your GitHub webpage/PR!
|
||||
|
||||
This demonstrates OpenHands' ability to create browser extensions and integrate with external APIs, enabling seamless workflows between GitHub and OpenHands Cloud.
|
||||
|
||||

|
||||

|
||||
|
||||
[GitHub Repository](https://github.com/xingyaoww/openhands-chrome-extension)
|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100755/i-asked-openhands-to-write-a-chrome-extension-based-on-our-h#88f14b7f-f8ff-40a6-83c2-bd64e95924c5)
|
||||
|
||||
</Update>
|
||||
|
||||
|
||||
<Update label="2025-04-11 Visual UI Testing" description="@Xingyao Wang">
|
||||
|
||||
## OpenHands tests UI automatically with visual browsing
|
||||
|
||||
Thanks to visual browsing -- OpenHands can actually test some simple UI by serving the website, clicking the button in the browser and looking at screenshots now!
|
||||
|
||||
Prompt is just:
|
||||
```
|
||||
I want to create a Hello World app in Javascript that:
|
||||
* Displays Hello World in the middle.
|
||||
* Has a button that when clicked, changes the greeting with a bouncing animation to fun versions of Hello.
|
||||
* Has a counter for how many times the button has been clicked.
|
||||
* Has another button that changes the app's background color.
|
||||
```
|
||||
|
||||
Eager-to-work Sonnet 3.7 will test stuff for you without you asking!
|
||||
|
||||
This showcases OpenHands' visual browsing capabilities, enabling it to create, serve, and automatically test web applications through actual browser interactions and screenshot analysis.
|
||||
|
||||

|
||||
|
||||
[Original Slack thread](https://www.linen.dev/s/openhands/t/29100764/thanks-to-u07k0p3bdb9-s-visual-browsing-openhands-can-actual#21beb9bc-1a04-4272-87e9-4d3e3b9925e7)
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2025-03-07 Proactive Error Handling" description="@Graham Neubig">
|
||||
|
||||
## OpenHands fixes crashes before you notice them
|
||||
|
||||
Interesting story, I asked OpenHands to start an app on port 12000, it showed up on the app pane. I started using the app, and then it crashed... But because it crashed in OpenHands, OpenHands immediately saw the error message and started fixing the problem without me having to do anything. It was already fixing the problem before I even realized what was going wrong.
|
||||
|
||||
This demonstrates OpenHands' proactive monitoring capabilities - it doesn't just execute commands, but actively watches for errors and begins remediation automatically, often faster than human reaction time.
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2024-12-03 Creative Design Acceleration" description="@Rohit Malhotra">
|
||||
|
||||
## Pair programming for interactive design projects
|
||||
|
||||
Used OpenHands as a pair programmer to do heavy lifting for a creative/interactive design project in p5js.
|
||||
|
||||
I usually take around 2 days for high fidelity interactions (planning strategy + writing code + circling back with designer), did this in around 5hrs instead with the designer watching curiously the entire time.
|
||||
|
||||
This showcases how OpenHands can accelerate creative and interactive design workflows, reducing development time by 75% while maintaining high quality output.
|
||||
|
||||
[Original Tweet](https://x.com/rohit_malh5/status/1863995531657425225)
|
||||
|
||||
</Update>
|
||||
BIN
docs/success-stories/stories/2025-04-11-visual-ui/s1.png
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
docs/success-stories/stories/2025-05-04-chrome-extension/s1.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
docs/success-stories/stories/2025-05-04-chrome-extension/s2.png
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
docs/success-stories/stories/2025-06-02-swebench-score/s1.mp4
Normal file
BIN
docs/success-stories/stories/2025-06-08-map/s1.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/success-stories/stories/2025-06-11-infra-iam/s1.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
docs/success-stories/stories/2025-06-11-infra-iam/s2.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
docs/success-stories/stories/2025-06-13-show-love/v1.mp4
Normal file
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Cloud UI
|
||||
description: The Cloud UI provides a web interface for interacting with OpenHands. This page explains how to use the
|
||||
OpenHands Cloud UI.
|
||||
description: The Cloud UI provides a web interface for interacting with OpenHands. This page provides references on
|
||||
how to use the OpenHands Cloud UI.
|
||||
---
|
||||
|
||||
## Landing Page
|
||||
@@ -19,10 +19,12 @@ The landing page is where you can:
|
||||
The Settings page allows you to:
|
||||
|
||||
- [Configure GitHub repository access](/usage/cloud/github-installation#modifying-repository-access) for OpenHands.
|
||||
- [Install the OpenHands Slack app](/usage/cloud/slack-installation).
|
||||
- Set application settings like your preferred language, notifications and other preferences.
|
||||
- Add credits to your account.
|
||||
- Generate custom secrets.
|
||||
- Create API keys to work with OpenHands programmatically.
|
||||
- [Generate custom secrets](/usage/common-settings#secrets-management).
|
||||
- [Create API keys to work with OpenHands programmatically](/usage/cloud/cloud-api).
|
||||
- Change your email address.
|
||||
|
||||
## Key Features
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ description: This guide walks you through installing the OpenHands Slack app.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Access to OpenHands Cloud
|
||||
- Access to OpenHands Cloud.
|
||||
|
||||
## Installation Steps
|
||||
|
||||
@@ -23,11 +23,11 @@ description: This guide walks you through installing the OpenHands Slack app.
|
||||
|
||||
<Accordion title="Authorize Slack App (for all Slack workspace members)">
|
||||
|
||||
**Make sure your Slack workspace admin/owner has installed OpenHands Slack App first**
|
||||
**Make sure your Slack workspace admin/owner has installed OpenHands Slack App first.**
|
||||
|
||||
Every user in the Slack workspace (including admins/owners) must link their Cloud OpenHands account to the OpenHands Slack App. To do this:
|
||||
Every user in the Slack workspace (including admins/owners) must link their OpenHands Cloud account to the OpenHands Slack App. To do this:
|
||||
1. Visit [integrations settings](https://app.all-hands.dev/settings/integrations) in OpenHands Cloud.
|
||||
2. Click the button "Install Slack App".
|
||||
2. Click `Install OpenHands Slack App`.
|
||||
3. In the top right corner, select the workspace to install the OpenHands Slack app.
|
||||
4. Review permissions and click allow.
|
||||
|
||||
|
||||
52
docs/usage/common-settings.mdx
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: OpenHands Settings
|
||||
description: Overview of some of the settings available in OpenHands.
|
||||
---
|
||||
|
||||
## Openhands Cloud vs Running on Your Own
|
||||
|
||||
There are some differences between the settings available in OpenHands Cloud and those available when running OpenHands
|
||||
on your own:
|
||||
* [OpenHands Cloud settings](/usage/cloud/cloud-ui#settings)
|
||||
* [Settings available when running on your own](/usage/how-to/gui-mode#settings)
|
||||
|
||||
Refer to these pages for more detailed information.
|
||||
|
||||
## Secrets Management
|
||||
|
||||
OpenHands provides a secrets manager that allows you to securely store and manage sensitive information that can be
|
||||
accessed by the agent during runtime, such as API keys. These secrets are automatically exported as environment
|
||||
variables in the agent's runtime environment.
|
||||
|
||||
### Accessing the Secrets Manager
|
||||
|
||||
In the Settings page, navigate to the `Secrets` tab. Here, you'll see a list of all your existing custom secrets.
|
||||
|
||||
### Adding a New Secret
|
||||
1. Click `Add a new secret`.
|
||||
2. Fill in the following fields:
|
||||
- **Name**: A unique identifier for your secret (e.g., `AWS_ACCESS_KEY`). This will be the environment variable name.
|
||||
- **Value**: The sensitive information you want to store.
|
||||
- **Description** (optional): A brief description of what the secret is used for, which is also provided to the agent.
|
||||
3. Click `Add secret` to save.
|
||||
|
||||
### Editing a Secret
|
||||
|
||||
1. Click the `Edit` button next to the secret you want to modify.
|
||||
2. You can update the name and description of the secret.
|
||||
<Note>
|
||||
For security reasons, you cannot view or edit the value of an existing secret. If you need to change the
|
||||
value, delete the secret and create a new one.
|
||||
</Note>
|
||||
|
||||
### Deleting a Secret
|
||||
|
||||
1. Click the `Delete` button next to the secret you want to remove.
|
||||
2. Select `Confirm` to delete the secret.
|
||||
|
||||
### Using Secrets in the Agent
|
||||
- All custom secrets are automatically exported as environment variables in the agent's runtime environment.
|
||||
- You can access them in your code using standard environment variable access methods
|
||||
(e.g., `os.environ['SECRET_NAME']` in Python).
|
||||
- Example: If you create a secret named `OPENAI_API_KEY`, you can access it in your code as
|
||||
`process.env.OPENAI_API_KEY` in JavaScript or `os.environ['OPENAI_API_KEY']` in Python.
|
||||
96
docs/usage/faqs.mdx
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: FAQs
|
||||
description: Frequently asked questions about OpenHands
|
||||
icon: question
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### I'm new to OpenHands. Where should I start?
|
||||
|
||||
1. **Quick start**: Use [OpenHands Cloud](/usage/cloud/openhands-cloud) to get started quickly with
|
||||
[GitHub](/usage/cloud/github-installation), [GitLab](/usage/cloud/gitlab-installation),
|
||||
and [Slack](/usage/cloud/slack-installation) integrations.
|
||||
2. **Run on your own**: If you prefer to run it on your own hardware, follow our [Getting Started guide](/usage/local-setup).
|
||||
3. **First steps**: Complete the [start building tutorial](/usage/getting-started) to learn the basics.
|
||||
|
||||
### Can I use OpenHands for production workloads?
|
||||
|
||||
OpenHands is meant to be run by a single user on their local workstation. It is not appropriate for multi-tenant
|
||||
deployments where multiple users share the same instance. There is no built-in authentication, isolation, or scalability.
|
||||
|
||||
If you're interested in running OpenHands in a multi-tenant environment, check out the source-available,
|
||||
commercially-licensed [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud).
|
||||
|
||||
<Info>
|
||||
Using OpenHands for work? We'd love to chat! Fill out
|
||||
[this short form](https://docs.google.com/forms/d/e/1FAIpQLSet3VbGaz8z32gW9Wm-Grl4jpt5WgMXPgJ4EDPVmCETCBpJtQ/viewform)
|
||||
to join our Design Partner program, where you'll get early access to commercial features and the opportunity to provide
|
||||
input on our product roadmap.
|
||||
</Info>
|
||||
|
||||
## Safety and Security
|
||||
|
||||
### It's doing stuff without asking, is that safe?
|
||||
|
||||
**Generally yes, but with important considerations.** OpenHands runs all code in a secure, isolated Docker container
|
||||
(called a "sandbox") that is separate from your host system. However, the safety depends on your configuration:
|
||||
|
||||
**What's protected:**
|
||||
- Your host system files and programs (unless you mount them using [this feature](/usage/runtimes/docker#connecting-to-your-filesystem))
|
||||
- Host system resources
|
||||
- Other containers and processes
|
||||
|
||||
**Potential risks to consider:**
|
||||
- The agent can access the internet from within the container.
|
||||
- If you provide credentials (API keys, tokens), the agent can use them.
|
||||
- Mounted files and directories can be modified or deleted.
|
||||
- Network requests can be made to external services.
|
||||
|
||||
For detailed security information, see our [Runtime Architecture](/usage/architecture/runtime),
|
||||
[Security Configuration](/usage/configuration-options#security-configuration),
|
||||
and [Hardened Docker Installation](/usage/runtimes/docker#hardened-docker-installation) documentation.
|
||||
|
||||
## File Storage and Access
|
||||
|
||||
### Where are my files stored?
|
||||
|
||||
Your files are stored in different locations depending on how you've configured OpenHands:
|
||||
|
||||
**Default behavior (no file mounting):**
|
||||
- Files created by the agent are stored inside the runtime Docker container.
|
||||
- These files are temporary and will be lost when the container is removed.
|
||||
- The agent works in the `/workspace` directory inside the runtime container.
|
||||
|
||||
**When you mount your local filesystem (following [this](/usage/runtimes/docker#connecting-to-your-filesystem)):**
|
||||
- Your local files are mounted into the container's `/workspace` directory.
|
||||
- Changes made by the agent are reflected in your local filesystem.
|
||||
- Files persist after the container is stopped.
|
||||
|
||||
<Warning>
|
||||
Be careful when mounting your filesystem - the agent can modify or delete any files in the mounted directory.
|
||||
</Warning>
|
||||
|
||||
## Development Tools and Environment
|
||||
|
||||
### How do I get the dev tools I need?
|
||||
|
||||
OpenHands comes with a basic runtime environment that includes Python and Node.js.
|
||||
It also has the ability to install any tools it needs, so usually it's sufficient to ask it to set up its environment.
|
||||
|
||||
If you would like to set things up more systematically, you can:
|
||||
- **Use setup.sh**: Add a [setup.sh file](/usage/prompting/repository#setup-script) file to
|
||||
your repository, which will be run every time the agent starts.
|
||||
- **Use a custom sandbox**: Use a [custom docker image](/usage/how-to/custom-sandbox-guide) to initialize the sandbox.
|
||||
|
||||
### Something's not working. Where can I get help?
|
||||
|
||||
1. **Search existing issues**: Check our [GitHub issues](https://github.com/All-Hands-AI/OpenHands/issues) to see if
|
||||
others have encountered the same problem.
|
||||
2. **Join our community**: Get help from other users and developers:
|
||||
- [Slack community](https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A)
|
||||
- [Discord server](https://discord.gg/ESHStjSjD4)
|
||||
3. **Check our troubleshooting guide**: Common issues and solutions are documented in
|
||||
[Troubleshooting](/usage/troubleshooting/troubleshooting).
|
||||
4. **Report bugs**: If you've found a bug, please [create an issue](https://github.com/All-Hands-AI/OpenHands/issues/new)
|
||||
and fill in as much detail as possible.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Start Building
|
||||
description: So you've [run OpenHands](./installation) and have [set up your LLM](./installation#setup). Now what?
|
||||
description: So you've [run OpenHands](/usage/installation). Now what?
|
||||
icon: code
|
||||
---
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ poetry run python -m openhands.cli.main
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -64,7 +64,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.44 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45 \
|
||||
python -m openhands.cli.main --override-cli-mode true
|
||||
```
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ You can use the Settings page at any time to:
|
||||
- Setup the LLM provider and model for OpenHands.
|
||||
- [Setup the search engine](/usage/search-engine-setup).
|
||||
- [Configure MCP servers](/usage/mcp).
|
||||
- [Connect to GitHub](/usage/how-to/gui-mode#github-setup) and [connect to GitLab](/usage/how-to/gui-mode#gitlab-setup)
|
||||
- [Connect to GitHub](/usage/how-to/gui-mode#github-setup) and [connect to GitLab](/usage/how-to/gui-mode#gitlab-setup).
|
||||
- Set application settings like your preferred language, notifications and other preferences.
|
||||
- [Manage custom secrets](/usage/how-to/gui-mode#secrets-management).
|
||||
- [Manage custom secrets](/usage/common-settings#secrets-management).
|
||||
|
||||
#### GitHub Setup
|
||||
|
||||
@@ -157,37 +157,6 @@ OpenHands automatically exports a `GITLAB_TOKEN` to the shell environment if pro
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
#### Secrets Management
|
||||
|
||||
OpenHands provides a secrets manager that allows you to securely store and manage sensitive information that can be accessed by the agent during runtime, such as API keys. These secrets are automatically exported as environment variables in the agent's runtime environment.
|
||||
|
||||
1. **Accessing the Secrets Manager**:
|
||||
- In the Settings page, navigate to the `Secrets` tab.
|
||||
- You'll see a list of all your existing custom secrets (if any).
|
||||
|
||||
2. **Adding a New Secret**:
|
||||
- Click the `Add New Secret` button.
|
||||
- Fill in the following fields:
|
||||
- **Name**: A unique identifier for your secret (e.g., `AWS_ACCESS_KEY`). This will be the environment variable name.
|
||||
- **Value**: The sensitive information you want to store.
|
||||
- **Description** (optional): A brief description of what the secret is used for, which is also provided to the agent.
|
||||
- Click `Add Secret` to save.
|
||||
|
||||
3. **Editing a Secret**:
|
||||
- Click the `Edit` button next to the secret you want to modify.
|
||||
- You can update the name and description of the secret.
|
||||
- Note: For security reasons, you cannot view or edit the value of an existing secret. If you need to change the value, delete the secret and create a new one.
|
||||
|
||||
4. **Deleting a Secret**:
|
||||
- Click the `Delete` button next to the secret you want to remove.
|
||||
- Confirm the deletion when prompted.
|
||||
|
||||
5. **Using Secrets in the Agent**:
|
||||
- All custom secrets are automatically exported as environment variables in the agent's runtime environment.
|
||||
- You can access them in your code using standard environment variable access methods (e.g., `os.environ['SECRET_NAME']` in Python).
|
||||
- Example: If you create a secret named `OPENAI_API_KEY`, you can access it in your code as `process.env.OPENAI_API_KEY` in JavaScript or `os.environ['OPENAI_API_KEY']` in Python.
|
||||
|
||||
#### Advanced Settings
|
||||
|
||||
The `Advanced` settings allows configuration of additional LLM settings. Inside the Settings page, under the `LLM` tab,
|
||||
@@ -208,11 +177,11 @@ section of the documentation.
|
||||
The status indicator located in the bottom left of the screen will cycle through a number of states as a new conversation
|
||||
is loaded. Typically these include:
|
||||
|
||||
* `Disconnected` : The frontend is not connected to any conversation
|
||||
* `Disconnected` : The frontend is not connected to any conversation.
|
||||
* `Connecting` : The frontend is connecting a websocket to a conversation.
|
||||
* `Building Runtime...` : The server is building a runtime. This is typically in development mode only while building a docker image.
|
||||
* `Starting Runtime...` : The server is starting a new runtime instance - probably a new docker container or remote runtime.
|
||||
* `Initializing Agent...` : The server is starting the agent loop. (This step does not appear at present with Nested runtimes)
|
||||
* `Initializing Agent...` : The server is starting the agent loop (This step does not appear at present with Nested runtimes).
|
||||
* `Setting up workspace...` : Usually this means a `git clone ...` operation.
|
||||
* `Setting up git hooks` : Setting up the git pre commit hooks for the workspace.
|
||||
* `Agent is awaiting user input...` : Ready to go!
|
||||
|
||||
@@ -32,7 +32,7 @@ To run OpenHands in Headless mode with Docker:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -42,7 +42,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.44 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
title: Quick Start
|
||||
description: Running OpenHands Cloud or running on your local system.
|
||||
description: Running OpenHands Cloud or running on your own.
|
||||
icon: rocket
|
||||
---
|
||||
|
||||
## OpenHands Cloud
|
||||
|
||||
The easiest way to get started with OpenHands is on OpenHands Cloud, which comes with $50 in free credits for new users.
|
||||
The easiest way to get started with OpenHands is on OpenHands Cloud, which comes with $20 in free credits for new users.
|
||||
|
||||
To get started with OpenHands Cloud, visit [app.all-hands.dev](https://app.all-hands.dev).
|
||||
|
||||
|
||||
@@ -6,75 +6,85 @@ description: When using a Local LLM, OpenHands may have limited functionality. I
|
||||
## News
|
||||
|
||||
- 2025/05/21: We collaborated with Mistral AI and released [Devstral Small](https://mistral.ai/news/devstral) that achieves [46.8% on SWE-Bench Verified](https://github.com/SWE-bench/experiments/pull/228)!
|
||||
- 2025/03/31: We released an open model OpenHands LM v0.1 32B that achieves 37.1% on SWE-Bench Verified
|
||||
- 2025/03/31: We released an open model OpenHands LM 32B v0.1 that achieves 37.1% on SWE-Bench Verified
|
||||
([blog](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model), [model](https://huggingface.co/all-hands/openhands-lm-32b-v0.1)).
|
||||
|
||||
## Quickstart: Running OpenHands with a Local LLM using LM Studio
|
||||
|
||||
## Quickstart: Running OpenHands on Your Macbook
|
||||
This guide explains how to serve a local Devstral LLM using [LM Studio](https://lmstudio.ai/) and have OpenHands connect to it.
|
||||
|
||||
### Serve the model on your Macbook
|
||||
We recommend:
|
||||
- **LM Studio** as the local model server, which handles metadata downloads automatically and offers a simple, user-friendly interface for configuration.
|
||||
- **Devstral Small 2505** as the LLM for software development, trained on real GitHub issues and optimized for agent-style workflows like OpenHands.
|
||||
|
||||
We recommend using [LMStudio](https://lmstudio.ai/) for serving these models locally.
|
||||
### Hardware Requirements
|
||||
|
||||
1. Download [LM Studio](https://lmstudio.ai/) and install it
|
||||
Running Devstral requires a recent GPU with at least 16GB of VRAM, or a Mac with Apple Silicon (M1, M2, etc.) with at least 32GB of RAM.
|
||||
|
||||
2. Download the model:
|
||||
- Option 1: Directly download the LLM from [this link](https://lmstudio.ai/model/devstral-small-2505-mlx) or by searching for the name `Devstral-Small-2505` in LM Studio
|
||||
- Option 2: Download a LLM in GGUF format. For example, to download [Devstral Small 2505 GGUF](https://huggingface.co/mistralai/Devstral-Small-2505_gguf), using `huggingface-cli download mistralai/Devstral-Small-2505_gguf --local-dir mistralai/Devstral-Small-2505_gguf`. Then in bash terminal, run `lms import {model_name}` in the directory where you've downloaded the model checkpoint (e.g. run `lms import devstralQ4_K_M.gguf` in `mistralai/Devstral-Small-2505_gguf`)
|
||||
### 1. Install LM Studio
|
||||
|
||||
3. Open LM Studio application, you should first switch to `power user` mode, and then open the developer tab:
|
||||
Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstudio.ai/).
|
||||
|
||||

|
||||
### 2. Download Devstral Small
|
||||
|
||||
4. Then click `Select a model to load` on top of the application:
|
||||
1. Make sure to set the User Interface Complexity Level to "Power User", by clicking on the appropriate label at the bottom of the window.
|
||||
2. Click the "Discover" button (Magnifying Glass icon) on the left navigation bar to open the Models download page.
|
||||
|
||||

|
||||

|
||||
|
||||
5. And choose the model you want to use, holding `option` on mac to enable advanced loading options:
|
||||
3. Search for the "Devstral Small 2505" model, confirm it's the official Mistral AI (mistralai) model, then proceed to download.
|
||||
|
||||

|
||||

|
||||
|
||||
6. You should then pick an appropriate context window for OpenHands based on your hardware configuration (larger than 32768 is recommended for using OpenHands, but too large may cause you to run out of memory); Flash attention is also recommended if it works on your machine.
|
||||
4. Wait for the download to finish.
|
||||
|
||||

|
||||
### 3. Load the Model
|
||||
|
||||
7. And you should start the server (if it is not already in `Running` status), un-toggle `Serve on Local Network` and remember the port number of the LMStudio URL (`1234` is the port number for `http://127.0.0.1:1234` in this example):
|
||||
1. Click the "Developer" button (Console icon) on the left navigation bar to open the Developer Console.
|
||||
2. Click the "Select a model to load" dropdown at the top of the application window.
|
||||
|
||||

|
||||

|
||||
|
||||
8. Finally, you can click the `copy` button near model name to copy the model name (`imported-models/uncategorized/devstralq4_k_m.gguf` in this example):
|
||||
3. Enable the "Manually choose model load parameters" switch.
|
||||
4. Select 'Devstral Small 2505' from the model list.
|
||||
|
||||

|
||||

|
||||
|
||||
### Start OpenHands with locally served model
|
||||
5. Enable the "Show advanced settings" switch at the bottom of the Model settings flyout to show all the available settings.
|
||||
6. Set "Context Length" to at least 32768 and enable Flash Attention.
|
||||
7. Click "Load Model" to start loading the model.
|
||||
|
||||
Check [the installation guide](/usage/local-setup) to make sure you have all the prerequisites for running OpenHands.
|
||||

|
||||
|
||||
### 4. Start the LLM server
|
||||
|
||||
1. Enable the switch next to "Status" at the top-left of the Window.
|
||||
2. Take note of the Model API Identifier shown on the sidebar on the right.
|
||||
|
||||

|
||||
|
||||
### 5. Start OpenHands
|
||||
|
||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||
|
||||
```bash
|
||||
export LMSTUDIO_MODEL_NAME="imported-models/uncategorized/devstralq4_k_m.gguf" # <- Replace this with the model name you copied from LMStudio
|
||||
export LMSTUDIO_URL="http://host.docker.internal:1234" # <- Replace this with the port from LMStudio
|
||||
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
|
||||
|
||||
mkdir -p ~/.openhands && echo '{"language":"en","agent":"CodeActAgent","max_iterations":null,"security_analyzer":null,"confirmation_mode":false,"llm_model":"lm_studio/'$LMSTUDIO_MODEL_NAME'","llm_api_key":"dummy","llm_base_url":"'$LMSTUDIO_URL/v1'","remote_runtime_resource_factor":null,"github_token":null,"enable_default_condenser":true,"user_consents_to_analytics":true}' > ~/.openhands/settings.json
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.44
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
Once your server is running -- you can visit `http://localhost:3000` in your browser to use OpenHands with local Devstral model:
|
||||
2. Wait until the server is running (see log below):
|
||||
```
|
||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.44
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.45
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
@@ -84,65 +94,88 @@ INFO: Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:3000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
3. Visit `http://localhost:3000` in your browser.
|
||||
|
||||
## Advanced: Serving LLM on GPUs
|
||||
### 6. Configure OpenHands to use the LLM server
|
||||
|
||||
### Download model checkpoints
|
||||
Once you open OpenHands in your browser, you'll need to configure it to use the local LLM server you just started.
|
||||
|
||||
<Note>
|
||||
The model checkpoints downloaded here should NOT be in GGUF format.
|
||||
</Note>
|
||||
When started for the first time, OpenHands will prompt you to set up the LLM provider.
|
||||
|
||||
For example, to download [OpenHands LM 32B v0.1](https://huggingface.co/all-hands/openhands-lm-32b-v0.1):
|
||||
1. Click "see advanced settings" to open the LLM Settings page.
|
||||
|
||||

|
||||
|
||||
2. Enable the "Advanced" switch at the top of the page to show all the available settings.
|
||||
|
||||
3. Set the following values:
|
||||
- **Custom Model**: `openai/mistralai/devstral-small-2505` (the Model API identifier from LM Studio, prefixed with "openai/")
|
||||
- **Base URL**: `http://host.docker.internal:1234/v1`
|
||||
- **API Key**: `local-llm`
|
||||
|
||||
4. Click "Save Settings" to save the configuration.
|
||||
|
||||

|
||||
|
||||
That's it! You can now start using OpenHands with the local LLM server.
|
||||
|
||||
If you encounter any issues, let us know on [Slack](https://join.slack.com/t/openhands-ai/shared_invite/zt-34zm4j0gj-Qz5kRHoca8DFCbqXPS~f_A) or [Discord](https://discord.gg/ESHStjSjD4).
|
||||
|
||||
## Advanced: Alternative LLM Backends
|
||||
|
||||
This section describes how to run local LLMs with OpenHands using alternative backends like Ollama, SGLang, or vLLM — without relying on LM Studio.
|
||||
|
||||
### Create an OpenAI-Compatible Endpoint with Ollama
|
||||
|
||||
- Install Ollama following [the official documentation](https://ollama.com/download).
|
||||
- Example launch command for Devstral Small 2505:
|
||||
|
||||
```bash
|
||||
huggingface-cli download all-hands/openhands-lm-32b-v0.1 --local-dir all-hands/openhands-lm-32b-v0.1
|
||||
# ⚠️ WARNING: OpenHands requires a large context size to work properly.
|
||||
# When using Ollama, set OLLAMA_CONTEXT_LENGTH to at least 32768.
|
||||
# The default (4096) is way too small — not even the system prompt will fit, and the agent will not behave correctly.
|
||||
OLLAMA_CONTEXT_LENGTH=32768 OLLAMA_HOST=0.0.0.0:11434 OLLAMA_KEEP_ALIVE=-1 nohup ollama serve &
|
||||
ollama pull devstral:latest
|
||||
```
|
||||
|
||||
### Create an OpenAI-Compatible Endpoint With SGLang
|
||||
### Create an OpenAI-Compatible Endpoint with vLLM or SGLang
|
||||
|
||||
First, download the model checkpoints. For [Devstral Small 2505](https://huggingface.co/mistralai/Devstral-Small-2505):
|
||||
|
||||
```bash
|
||||
huggingface-cli download mistralai/Devstral-Small-2505 --local-dir mistralai/Devstral-Small-2505
|
||||
```
|
||||
|
||||
#### Serving the model using SGLang
|
||||
|
||||
- Install SGLang following [the official documentation](https://docs.sglang.ai/start/install.html).
|
||||
- Example launch command for OpenHands LM 32B (with at least 2 GPUs):
|
||||
- Example launch command for Devstral Small 2505 (with at least 2 GPUs):
|
||||
|
||||
```bash
|
||||
SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN=1 python3 -m sglang.launch_server \
|
||||
--model all-hands/openhands-lm-32b-v0.1 \
|
||||
--served-model-name openhands-lm-32b-v0.1 \
|
||||
--model mistralai/Devstral-Small-2505 \
|
||||
--served-model-name Devstral-Small-2505 \
|
||||
--port 8000 \
|
||||
--tp 2 --dp 1 \
|
||||
--host 0.0.0.0 \
|
||||
--api-key mykey --context-length 131072
|
||||
```
|
||||
|
||||
### Create an OpenAI-Compatible Endpoint with vLLM
|
||||
#### Serving the model using vLLM
|
||||
|
||||
- Install vLLM following [the official documentation](https://docs.vllm.ai/en/latest/getting_started/installation.html).
|
||||
- Example launch command for OpenHands LM 32B (with at least 2 GPUs):
|
||||
- Example launch command for Devstral Small 2505 (with at least 2 GPUs):
|
||||
|
||||
```bash
|
||||
vllm serve all-hands/openhands-lm-32b-v0.1 \
|
||||
vllm serve mistralai/Devstral-Small-2505 \
|
||||
--host 0.0.0.0 --port 8000 \
|
||||
--api-key mykey \
|
||||
--tensor-parallel-size 2 \
|
||||
--served-model-name openhands-lm-32b-v0.1
|
||||
--served-model-name Devstral-Small-2505 \
|
||||
--enable-prefix-caching
|
||||
```
|
||||
|
||||
### Create an OpenAI-Compatible Endpoint with Ollama
|
||||
|
||||
- Install Ollama following [the official documentation](https://ollama.com/download).
|
||||
- For Ollama configuration, use `ollama/<modelname>` as custom model in web. Api key also can be set to `ollama`.
|
||||
- Example launch command for Devstral LM 24B:
|
||||
|
||||
```bash
|
||||
OLLAMA_CONTEXT_LENGTH=32768 OLLAMA_HOST=0.0.0.0:11434 OLLAMA_KEEP_ALIVE=-1 nohup ollama serve&
|
||||
#The minimum context size is ~8196, even the system prompt won't fit smaller
|
||||
ollama pull devstral:latest
|
||||
```
|
||||
|
||||
## Advanced: Run and Configure OpenHands
|
||||
|
||||
### Run OpenHands
|
||||
### Run OpenHands (Alternative Backends)
|
||||
|
||||
#### Using Docker
|
||||
|
||||
@@ -151,24 +184,20 @@ Run OpenHands using [the official docker run command](../installation#start-the-
|
||||
#### Using Development Mode
|
||||
|
||||
Use the instructions in [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) to build OpenHands.
|
||||
Ensure `config.toml` exists by running `make setup-config` which will create one for you. In the `config.toml`, enter the following:
|
||||
|
||||
```
|
||||
[core]
|
||||
workspace_base="/path/to/your/workspace"
|
||||
|
||||
[llm]
|
||||
model="openhands-lm-32b-v0.1"
|
||||
ollama_base_url="http://localhost:8000"
|
||||
```
|
||||
|
||||
Start OpenHands using `make run`.
|
||||
|
||||
### Configure OpenHands
|
||||
### Configure OpenHands (Alternative Backends)
|
||||
|
||||
Once OpenHands is running, you'll need to set the following in the OpenHands UI through the Settings under the `LLM` tab:
|
||||
1. Enable `Advanced` options.
|
||||
2. Set the following:
|
||||
- `Custom Model` to `openai/<served-model-name>` (e.g. `openai/openhands-lm-32b-v0.1`)
|
||||
- `Base URL` to `http://host.docker.internal:8000`
|
||||
- `API key` to the same string you set when serving the model (e.g. `mykey`)
|
||||
Once OpenHands is running, open the Settings page in the UI and go to the `LLM` tab.
|
||||
|
||||
1. Click **"see advanced settings"** to access the full configuration panel.
|
||||
2. Enable the **Advanced** toggle at the top of the page.
|
||||
3. Set the following parameters, if you followed the examples above:
|
||||
- **Custom Model**: `openai/<served-model-name>`
|
||||
e.g. `openai/devstral` if you're using Ollama, or `openai/Devstral-Small-2505` for SGLang or vLLM.
|
||||
- **Base URL**: `http://host.docker.internal:<port>/v1`
|
||||
Use port `11434` for Ollama, or `8000` for SGLang and vLLM.
|
||||
- **API Key**:
|
||||
- For **Ollama**: any placeholder value (e.g. `dummy`, `local-llm`)
|
||||
- For **SGLang** or **vLLM**: use the same key provided when starting the server (e.g. `mykey`)
|
||||
|
||||
BIN
docs/usage/llms/screenshots/01_lm_studio_open_model_hub.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docs/usage/llms/screenshots/02_lm_studio_download_devstral.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
docs/usage/llms/screenshots/03_lm_studio_open_load_model.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 127 KiB |
BIN
docs/usage/llms/screenshots/06_lm_studio_start_server.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 420 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 558 KiB |
|
Before Width: | Height: | Size: 646 KiB |
|
Before Width: | Height: | Size: 93 KiB |
@@ -67,17 +67,17 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
### Start the App
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.44
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.45
|
||||
```
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
@@ -153,8 +153,6 @@ To enable search functionality in OpenHands:
|
||||
|
||||
For more details, see the [Search Engine Setup](/usage/search-engine-setup) guide.
|
||||
|
||||
Now you're ready to [get started with OpenHands](/usage/getting-started).
|
||||
|
||||
### Versions
|
||||
|
||||
The [docker command above](/usage/local-setup#start-the-app) pulls the most recent stable release of OpenHands. You have other options as well:
|
||||
|
||||
@@ -5,26 +5,111 @@ description: Keyword-triggered microagents provide OpenHands with specific instr
|
||||
|
||||
## Usage
|
||||
|
||||
These microagents are only loaded when a prompt includes one of the trigger words.
|
||||
Keyword-triggered microagents are only loaded when a prompt includes one of the trigger words. There are two types of keyword-triggered microagents:
|
||||
|
||||
1. **Standard Keyword Microagents**: Triggered by keywords embedded in text
|
||||
2. **Command-Style Microagents**: Triggered by command-style inputs (e.g., `/fix_test`) that can prompt for user input
|
||||
|
||||
Additionally, there's a special type of microagent that's always active:
|
||||
|
||||
3. **Repository Microagents**: Always active for a specific repository, providing repository-specific context and tools
|
||||
|
||||
## Frontmatter Syntax
|
||||
|
||||
Frontmatter is required for keyword-triggered microagents. It must be placed at the top of the file,
|
||||
above the guidelines.
|
||||
above the guidelines. Enclose the frontmatter in triple dashes (---).
|
||||
|
||||
Enclose the frontmatter in triple dashes (---) and include the following fields:
|
||||
### Standard Keyword Microagents
|
||||
|
||||
For standard keyword microagents, include the following fields:
|
||||
|
||||
| Field | Description | Required | Default |
|
||||
|------------|--------------------------------------------------|----------|------------------|
|
||||
| `name` | The name of the microagent | No | Filename |
|
||||
| `type` | The type of microagent (`knowledge`) | No | Inferred |
|
||||
| `triggers` | A list of keywords that activate the microagent. | Yes | None |
|
||||
| `agent` | The agent this microagent applies to. | No | 'CodeActAgent' |
|
||||
|
||||
### Command-Style Microagents
|
||||
|
||||
## Example
|
||||
For command-style microagents that require user input, include the following fields:
|
||||
|
||||
Keyword-triggered microagent file example located at `.openhands/microagents/yummy.md`:
|
||||
```
|
||||
| Field | Description | Required | Default |
|
||||
|------------|------------------------------------------------------------|----------|------------------|
|
||||
| `name` | The name of the microagent | No | Filename |
|
||||
| `type` | The type of microagent (`task`) | No | Inferred |
|
||||
| `triggers` | A list of command triggers (e.g., `/fix_test`) | No | `/[name]` |
|
||||
| `inputs` | A list of input variables the microagent requires | Yes | None |
|
||||
|
||||
### Repository Microagents
|
||||
|
||||
Repository microagents are always active for a specific repository. They provide repository-specific context and tools.
|
||||
|
||||
| Field | Description | Required | Default |
|
||||
|------------|------------------------------------------------------------|----------|------------------|
|
||||
| `name` | The name of the microagent | No | Filename |
|
||||
| `type` | The type of microagent (`repo`) | No | Inferred |
|
||||
|
||||
#### Repository Microagent Example
|
||||
|
||||
Here's an example of a repository microagent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
# The type field is optional and will be inferred as 'repo' when no triggers are present
|
||||
---
|
||||
|
||||
# Repository Guidelines
|
||||
|
||||
This repository follows these coding standards:
|
||||
1. Use PEP 8 for Python code
|
||||
2. Use ESLint for JavaScript code
|
||||
3. Write unit tests for all new features
|
||||
```
|
||||
|
||||
This microagent is always active when working with the repository and provides repository-specific guidelines.
|
||||
|
||||
### MCP Tools Support
|
||||
|
||||
Microagents can also provide additional MCP (Model-Code-Prompt) tools to the agent. This is useful for extending the agent's capabilities with custom tools.
|
||||
|
||||
| Field | Description | Required | Default |
|
||||
|--------------|-----------------------------------------------------------|----------|------------------|
|
||||
| `mcp_tools` | Configuration for additional MCP tools | No | None |
|
||||
|
||||
#### MCP Tools Example
|
||||
|
||||
Here's an example of a microagent that provides an additional MCP tool (the `fetch` tool for accessing web content):
|
||||
|
||||
```yaml
|
||||
---
|
||||
# The type field is optional and will be inferred as 'repo' when no triggers are present
|
||||
mcp_tools:
|
||||
stdio_servers:
|
||||
- name: "fetch"
|
||||
command: uvx
|
||||
args:
|
||||
- mcp-server-fetch
|
||||
---
|
||||
```
|
||||
|
||||
This microagent is a repository microagent (always active) that adds the `fetch` tool to the agent's capabilities.
|
||||
|
||||
Each input in the `inputs` list requires:
|
||||
|
||||
| Field | Description | Required |
|
||||
|---------------|--------------------------------------------------|----------|
|
||||
| `name` | The name of the input variable | Yes |
|
||||
| `description` | A description of what the input should contain | Yes |
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Standard Keyword Microagent Example
|
||||
|
||||
Standard keyword microagent file example located at `.openhands/microagents/yummy.md`:
|
||||
```yaml
|
||||
---
|
||||
# The type field is optional and will be inferred as 'knowledge' when triggers are present
|
||||
triggers:
|
||||
- yummyhappy
|
||||
- happyyummy
|
||||
@@ -33,4 +118,58 @@ triggers:
|
||||
The user has said the magic word. Respond with "That was delicious!"
|
||||
```
|
||||
|
||||
[See examples of microagents triggered by keywords in the official OpenHands repository](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents)
|
||||
### Command-Style Microagent Example
|
||||
|
||||
Command-style microagent file example located at `.openhands/microagents/fix_test.md`:
|
||||
```yaml
|
||||
---
|
||||
# The type field is optional and will be inferred as 'task' when inputs are present
|
||||
triggers:
|
||||
- /fix_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
- name: FUNCTION_TO_FIX
|
||||
description: "The name of function to fix"
|
||||
- name: FILE_FOR_FUNCTION
|
||||
description: "The path of the file that contains the function"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
Help me fix these tests to pass by fixing the {{ FUNCTION_TO_FIX }} function in file {{ FILE_FOR_FUNCTION }}.
|
||||
|
||||
PLEASE DO NOT modify the tests by yourself -- Let me know if you think some of the tests are incorrect.
|
||||
```
|
||||
|
||||
## Using Command-Style Microagents
|
||||
|
||||
Command-style microagents are designed to streamline common development tasks by providing structured templates for specific operations. They are triggered using a command-style format and will prompt the user for any required inputs.
|
||||
|
||||
### How to Use
|
||||
|
||||
1. Type `/` in the chat input to see available command-style microagents
|
||||
2. Select a microagent from the dropdown or type its name (e.g., `/fix_test`)
|
||||
3. The agent will prompt you for any required inputs
|
||||
4. Provide the requested information
|
||||
5. The agent will execute the task with your inputs
|
||||
|
||||
### Template Variables
|
||||
|
||||
In the body of a command-style microagent, you can reference input variables using the `{{ VARIABLE_NAME }}` syntax. These will be replaced with the user-provided values when the microagent is triggered.
|
||||
|
||||
### Available Command-Style Microagents
|
||||
|
||||
OpenHands includes several built-in command-style microagents:
|
||||
|
||||
| Command | Description |
|
||||
|----------------------|-------------------------------------------------------|
|
||||
| `/fix_test` | Fix failing tests by modifying a specific function |
|
||||
| `/update_test` | Update tests for a new implementation |
|
||||
| `/update_pr` | Update a pull request description |
|
||||
| `/address_pr_comments` | Address comments on a pull request |
|
||||
| `/add_repo_instruction` | Add instructions to the repository microagent |
|
||||
|
||||
[See examples of microagents in the official OpenHands repository](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents)
|
||||
|
||||
@@ -8,7 +8,7 @@ description: Microagents are specialized prompts that enhance OpenHands with dom
|
||||
Currently OpenHands supports the following types of microagents:
|
||||
|
||||
- [General Microagents](./microagents-repo): General guidelines for OpenHands about the repository.
|
||||
- [Keyword-Triggered Microagents](./microagents-keyword): Guidelines activated by specific keywords in prompts.
|
||||
- [Keyword-Triggered Microagents](./microagents-keyword): Guidelines activated by specific keywords in prompts, including command-style microagents that prompt for user inputs.
|
||||
|
||||
To customize OpenHands' behavior, create a .openhands/microagents/ directory in the root of your repository and
|
||||
add `<microagent_name>.md` files inside. For repository-specific guidelines, you can ask OpenHands to analyze your repository and create a comprehensive `repo.md` file (see [General Microagents](./microagents-repo) for details).
|
||||
@@ -34,7 +34,7 @@ some-repository/
|
||||
Each microagent file may include frontmatter that provides additional information. In some cases, this frontmatter
|
||||
is required:
|
||||
|
||||
| Microagent Type | Required |
|
||||
|---------------------------------|----------|
|
||||
| `General Microagents` | No |
|
||||
| `Keyword-Triggered Microagents` | Yes |
|
||||
| Microagent Type | Required |
|
||||
|------------------------------------------------|----------|
|
||||
| `General Microagents` | No |
|
||||
| `Keyword-Triggered Microagents (all types)` | Yes |
|
||||
|
||||
@@ -128,3 +128,7 @@ docker network create openhands-network
|
||||
docker run # ... \
|
||||
--network openhands-network \
|
||||
```
|
||||
|
||||
<Note>
|
||||
**Docker Desktop Required**: Network isolation features, including custom networks and `host.docker.internal` routing, require Docker Desktop. Docker Engine alone does not support these features on localhost across custom networks. If you're using Docker Engine without Docker Desktop, network isolation may not work as expected.
|
||||
</Note>
|
||||
|
||||
@@ -133,13 +133,66 @@ This guide provides step-by-step instructions for running OpenHands on a Windows
|
||||
|
||||
> **Note**: If you're running the frontend in development mode (using `npm run dev`), use port 3001 instead: `http://localhost:3001`
|
||||
|
||||
## Installing and Running the CLI
|
||||
|
||||
To install and run the OpenHands CLI on Windows without WSL, follow these steps:
|
||||
|
||||
### 1. Install uv (Python Package Manager)
|
||||
|
||||
Open PowerShell as Administrator and run:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
### 2. Install .NET SDK (Required)
|
||||
|
||||
The OpenHands CLI **requires** the .NET Core runtime for PowerShell integration. Without it, the CLI will fail to start with a `coreclr` error. Install the .NET SDK which includes the runtime:
|
||||
|
||||
```powershell
|
||||
winget install Microsoft.DotNet.SDK.8
|
||||
```
|
||||
|
||||
Alternatively, you can download and install the .NET SDK from the [official Microsoft website](https://dotnet.microsoft.com/download).
|
||||
|
||||
After installation, restart your PowerShell session to ensure the environment variables are updated.
|
||||
|
||||
### 3. Install and Run OpenHands
|
||||
|
||||
After installing the prerequisites, you can install and run OpenHands with:
|
||||
|
||||
```powershell
|
||||
uvx --python 3.12 --from openhands-ai openhands
|
||||
```
|
||||
|
||||
### Troubleshooting CLI Issues
|
||||
|
||||
#### CoreCLR Error
|
||||
|
||||
If you encounter an error like `Failed to load CoreCLR` or `pythonnet.load('coreclr')` when running OpenHands CLI, this indicates that the .NET Core runtime is missing or not properly configured. To fix this:
|
||||
|
||||
1. Install the .NET SDK as described in step 2 above
|
||||
2. Verify that your system PATH includes the .NET SDK directories
|
||||
3. Restart your PowerShell session completely after installing the .NET SDK
|
||||
4. Make sure you're using PowerShell 7 (pwsh) rather than Windows PowerShell
|
||||
|
||||
To verify your .NET installation, run:
|
||||
|
||||
```powershell
|
||||
dotnet --info
|
||||
```
|
||||
|
||||
This should display information about your installed .NET SDKs and runtimes. If this command fails, the .NET SDK is not properly installed or not in your PATH.
|
||||
|
||||
If the issue persists after installing the .NET SDK, try installing the specific .NET Runtime version 6.0 or later from the [.NET download page](https://dotnet.microsoft.com/download).
|
||||
|
||||
## Limitations on Windows
|
||||
|
||||
When running OpenHands on Windows without WSL or Docker, be aware of the following limitations:
|
||||
|
||||
1. **Browser Tool Not Supported**: The browser tool is not currently supported on Windows.
|
||||
|
||||
2. **.NET Core Requirement**: The PowerShell integration requires .NET Core Runtime to be installed. If .NET Core is not available, OpenHands will automatically fall back to a more limited PowerShell implementation with reduced functionality.
|
||||
2. **.NET Core Requirement**: The PowerShell integration requires .NET Core Runtime to be installed. The CLI implementation attempts to load the CoreCLR at startup with `pythonnet.load('coreclr')` and will fail with an error if .NET Core is not properly installed.
|
||||
|
||||
3. **Interactive Shell Commands**: Some interactive shell commands may not work as expected. The PowerShell session implementation has limitations compared to the bash session used on Linux/macOS.
|
||||
|
||||
|
||||
95
frontend/__tests__/components/likert-scale.test.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { LikertScale } from "#/components/features/feedback/likert-scale";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
// Mock the mutation hook
|
||||
vi.mock("#/hooks/mutation/use-submit-conversation-feedback", () => ({
|
||||
useSubmitConversationFeedback: () => ({
|
||||
mutate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("LikertScale", () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should render with proper localized text for rating prompt", () => {
|
||||
renderWithProviders(<LikertScale eventId={1} />);
|
||||
|
||||
// Check that the rating prompt is displayed with proper translation key
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$RATE_AGENT_PERFORMANCE)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show localized feedback reasons when rating is 3 or below", async () => {
|
||||
renderWithProviders(<LikertScale eventId={1} />);
|
||||
|
||||
// Click on a rating of 3 (which should show reasons)
|
||||
const threeStarButton = screen.getAllByRole("button")[2]; // 3rd button (rating 3)
|
||||
await user.click(threeStarButton);
|
||||
|
||||
// Wait for reasons to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$SELECT_REASON)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Check that all feedback reasons are properly localized
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION)).toBeInTheDocument();
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$REASON_FORGOT_CONTEXT)).toBeInTheDocument();
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$REASON_UNNECESSARY_CHANGES)).toBeInTheDocument();
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$REASON_OTHER)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show countdown message with proper localization", async () => {
|
||||
renderWithProviders(<LikertScale eventId={1} />);
|
||||
|
||||
// Click on a rating of 2 (which should show reasons and countdown)
|
||||
const twoStarButton = screen.getAllByRole("button")[1]; // 2nd button (rating 2)
|
||||
await user.click(twoStarButton);
|
||||
|
||||
// Wait for countdown to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$SELECT_REASON_COUNTDOWN)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show thank you message after submission", () => {
|
||||
renderWithProviders(
|
||||
<LikertScale eventId={1} initiallySubmitted={true} initialRating={4} />
|
||||
);
|
||||
|
||||
// Check that thank you message is displayed with proper translation key
|
||||
expect(screen.getByText(I18nKey.FEEDBACK$THANK_YOU_FOR_FEEDBACK)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render all 5 star rating buttons", () => {
|
||||
renderWithProviders(<LikertScale eventId={1} />);
|
||||
|
||||
// Check that all 5 star buttons are rendered
|
||||
const starButtons = screen.getAllByRole("button");
|
||||
expect(starButtons).toHaveLength(5);
|
||||
|
||||
// Check that each button has proper aria-label
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
expect(screen.getByLabelText(`Rate ${i} stars`)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it("should not show reasons for ratings above 3", async () => {
|
||||
renderWithProviders(<LikertScale eventId={1} />);
|
||||
|
||||
// Click on a rating of 5 (which should NOT show reasons)
|
||||
const fiveStarButton = screen.getAllByRole("button")[4]; // 5th button (rating 5)
|
||||
await user.click(fiveStarButton);
|
||||
|
||||
// Wait a bit to ensure reasons don't appear
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(I18nKey.FEEDBACK$SELECT_REASON)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
1064
frontend/package-lock.json
generated
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.44.0",
|
||||
"version": "0.45.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.0-beta.7",
|
||||
"@heroui/react": "^2.8.0-beta.9",
|
||||
"@microlink/react-json-view": "^1.26.2",
|
||||
"@monaco-editor/react": "^4.7.0-rc.0",
|
||||
"@react-router/node": "^7.6.2",
|
||||
@@ -18,7 +18,7 @@
|
||||
"@stripe/stripe-js": "^7.3.1",
|
||||
"@tailwindcss/postcss": "^4.1.10",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@tanstack/react-query": "^5.80.7",
|
||||
"@tanstack/react-query": "^5.80.10",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"isbot": "^5.1.28",
|
||||
"jose": "^6.0.11",
|
||||
"lucide-react": "^0.517.0",
|
||||
"lucide-react": "^0.519.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"posthog-js": "^1.255.0",
|
||||
"react": "^19.1.0",
|
||||
@@ -84,7 +84,7 @@
|
||||
"@babel/traverse": "^7.27.1",
|
||||
"@babel/types": "^7.27.0",
|
||||
"@mswjs/socket.io-binding": "^0.2.0",
|
||||
"@playwright/test": "^1.53.0",
|
||||
"@playwright/test": "^1.53.1",
|
||||
"@react-router/dev": "^7.6.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/eslint-plugin-query": "^5.78.0",
|
||||
|
||||
@@ -293,9 +293,11 @@ class OpenHands {
|
||||
|
||||
static async startConversation(
|
||||
conversationId: string,
|
||||
providers?: Provider[],
|
||||
): Promise<Conversation | null> {
|
||||
const { data } = await openHands.post<Conversation | null>(
|
||||
`/api/conversations/${conversationId}/start`,
|
||||
providers ? { providers_set: providers } : {},
|
||||
);
|
||||
|
||||
return data;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "#/utils/utils";
|
||||
import i18n from "#/i18n";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useSubmitConversationFeedback } from "#/hooks/mutation/use-submit-conversation-feedback";
|
||||
import { ScrollContext } from "#/context/scroll-context";
|
||||
|
||||
@@ -14,19 +15,14 @@ interface LikertScaleProps {
|
||||
initialReason?: string;
|
||||
}
|
||||
|
||||
const FEEDBACK_REASONS = [
|
||||
i18n.t("FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION"),
|
||||
i18n.t("FEEDBACK$REASON_FORGOT_CONTEXT"),
|
||||
i18n.t("FEEDBACK$REASON_UNNECESSARY_CHANGES"),
|
||||
i18n.t("FEEDBACK$REASON_OTHER"),
|
||||
];
|
||||
|
||||
export function LikertScale({
|
||||
eventId,
|
||||
initiallySubmitted = false,
|
||||
initialRating,
|
||||
initialReason,
|
||||
}: LikertScaleProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selectedRating, setSelectedRating] = useState<number | null>(
|
||||
initialRating || null,
|
||||
);
|
||||
@@ -43,6 +39,14 @@ export function LikertScale({
|
||||
// Get scroll context
|
||||
const scrollContext = useContext(ScrollContext);
|
||||
|
||||
// Define feedback reasons using the translation hook
|
||||
const FEEDBACK_REASONS = [
|
||||
t(I18nKey.FEEDBACK$REASON_MISUNDERSTOOD_INSTRUCTION),
|
||||
t(I18nKey.FEEDBACK$REASON_FORGOT_CONTEXT),
|
||||
t(I18nKey.FEEDBACK$REASON_UNNECESSARY_CHANGES),
|
||||
t(I18nKey.FEEDBACK$REASON_OTHER),
|
||||
];
|
||||
|
||||
// If scrollContext is undefined, we're not inside a ScrollProvider
|
||||
const scrollToBottom = scrollContext?.scrollDomToBottom;
|
||||
const autoScroll = scrollContext?.autoScroll;
|
||||
@@ -188,8 +192,8 @@ export function LikertScale({
|
||||
<div className="mt-3 flex flex-col gap-1">
|
||||
<div className="text-sm text-gray-500 mb-1">
|
||||
{isSubmitted
|
||||
? i18n.t("FEEDBACK$THANK_YOU_FOR_FEEDBACK")
|
||||
: i18n.t("FEEDBACK$RATE_AGENT_PERFORMANCE")}
|
||||
? t(I18nKey.FEEDBACK$THANK_YOU_FOR_FEEDBACK)
|
||||
: t(I18nKey.FEEDBACK$RATE_AGENT_PERFORMANCE)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="flex gap-2 items-center flex-wrap">
|
||||
@@ -220,11 +224,11 @@ export function LikertScale({
|
||||
{showReasons && !isSubmitted && (
|
||||
<div className="mt-1 flex flex-col gap-1">
|
||||
<div className="text-xs text-gray-500 mb-1">
|
||||
{i18n.t("FEEDBACK$SELECT_REASON")}
|
||||
{t(I18nKey.FEEDBACK$SELECT_REASON)}
|
||||
</div>
|
||||
{countdown > 0 && (
|
||||
<div className="text-xs text-gray-400 mb-1 italic">
|
||||
{i18n.t("FEEDBACK$SELECT_REASON_COUNTDOWN", {
|
||||
{t(I18nKey.FEEDBACK$SELECT_REASON_COUNTDOWN, {
|
||||
countdown,
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import GitHubLogo from "#/assets/branding/github-logo.svg?react";
|
||||
import GitLabLogo from "#/assets/branding/gitlab-logo.svg?react";
|
||||
import BitbucketLogo from "#/assets/branding/bitbucket-logo.svg?react";
|
||||
import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||
|
||||
@@ -24,11 +23,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
identityProvider: "gitlab",
|
||||
});
|
||||
|
||||
const bitbucketAuthUrl = useAuthUrl({
|
||||
appMode: appMode || null,
|
||||
identityProvider: "bitbucket",
|
||||
});
|
||||
|
||||
const handleGitHubAuth = () => {
|
||||
if (githubAuthUrl) {
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
@@ -43,13 +37,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBitbucketAuth = () => {
|
||||
if (bitbucketAuthUrl) {
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = bitbucketAuthUrl;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop>
|
||||
<ModalBody className="border border-tertiary">
|
||||
@@ -80,16 +67,6 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
>
|
||||
{t(I18nKey.GITLAB$CONNECT_TO_GITLAB)}
|
||||
</BrandButton>
|
||||
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleBitbucketAuth}
|
||||
className="w-full"
|
||||
startContent={<BitbucketLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.BITBUCKET$CONNECT_TO_BITBUCKET)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
|
||||
@@ -348,6 +348,7 @@ export function WsClientProvider({
|
||||
conversation?.url,
|
||||
conversation?.status,
|
||||
conversation?.runtime_status,
|
||||
providers,
|
||||
]);
|
||||
|
||||
React.useEffect(
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import React from "react";
|
||||
import { convertRawProvidersToList } from "#/utils/convert-raw-providers-to-list";
|
||||
import { useSettings } from "./query/use-settings";
|
||||
|
||||
export const useUserProviders = () => {
|
||||
const { data: settings } = useSettings();
|
||||
|
||||
const providers = React.useMemo(
|
||||
() => convertRawProvidersToList(settings?.PROVIDER_TOKENS_SET),
|
||||
[settings?.PROVIDER_TOKENS_SET],
|
||||
);
|
||||
|
||||
return {
|
||||
providers: convertRawProvidersToList(settings?.PROVIDER_TOKENS_SET),
|
||||
providers,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ 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";
|
||||
import { useUserProviders } from "#/hooks/use-user-providers";
|
||||
|
||||
function AppContent() {
|
||||
useConversationConfig();
|
||||
@@ -45,6 +46,7 @@ function AppContent() {
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation, isFetched, refetch } = useActiveConversation();
|
||||
const { data: isAuthed } = useIsAuthed();
|
||||
const { providers } = useUserProviders();
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const dispatch = useDispatch();
|
||||
@@ -63,11 +65,11 @@ function AppContent() {
|
||||
navigate("/");
|
||||
} else if (conversation?.status === "STOPPED") {
|
||||
// start the conversation if the state is stopped on initial load
|
||||
OpenHands.startConversation(conversation.conversation_id).then(() =>
|
||||
refetch(),
|
||||
OpenHands.startConversation(conversation.conversation_id, providers).then(
|
||||
() => refetch(),
|
||||
);
|
||||
}
|
||||
}, [conversation?.conversation_id, isFetched, isAuthed]);
|
||||
}, [conversation?.conversation_id, isFetched, isAuthed, providers]);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(clearTerminal());
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
---
|
||||
name: add_agent
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- new agent
|
||||
- new microagent
|
||||
- create agent
|
||||
- create an agent
|
||||
- create microagent
|
||||
- create a microagent
|
||||
- add agent
|
||||
- add an agent
|
||||
- add microagent
|
||||
- add a microagent
|
||||
- microagent template
|
||||
- new agent
|
||||
- new microagent
|
||||
- create agent
|
||||
- create an agent
|
||||
- create microagent
|
||||
- create a microagent
|
||||
- add agent
|
||||
- add an agent
|
||||
- add microagent
|
||||
- add a microagent
|
||||
- microagent template
|
||||
---
|
||||
|
||||
This agent helps create new microagents in the `.openhands/microagents` directory by providing guidance and templates.
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
---
|
||||
name: add_repo_inst
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
inputs:
|
||||
- description: Branch for the agent to work on
|
||||
name: REPO_FOLDER_NAME
|
||||
triggers:
|
||||
- /add_repo_inst
|
||||
inputs:
|
||||
- name: REPO_FOLDER_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
---
|
||||
|
||||
Please browse the current repository under /workspace/{{ REPO_FOLDER_NAME }}, look at the documentation and relevant code, and understand the purpose of this repository.
|
||||
@@ -18,7 +14,6 @@ Here's an example:
|
||||
```markdown
|
||||
---
|
||||
name: repo
|
||||
type: repo
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
---
|
||||
name: address_pr_comments
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
inputs:
|
||||
- description: URL of the pull request
|
||||
name: PR_URL
|
||||
- description: Branch name corresponds to the pull request
|
||||
name: BRANCH_NAME
|
||||
triggers:
|
||||
- /address_pr_comments
|
||||
inputs:
|
||||
- name: PR_URL
|
||||
description: "URL of the pull request"
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch name corresponds to the pull request"
|
||||
---
|
||||
|
||||
First, check the branch {{ BRANCH_NAME }} and read the diff against the main branch to understand the purpose.
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: agent_memory
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- /remember
|
||||
---
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
---
|
||||
# This is a repo microagent that is always activated
|
||||
# to include necessary default tools implemented with MCP
|
||||
name: default-tools
|
||||
type: repo
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
mcp_tools:
|
||||
stdio_servers:
|
||||
- name: "fetch"
|
||||
command: "uvx"
|
||||
args: ["mcp-server-fetch"]
|
||||
# We leave the body empty because MCP tools will automatically add the
|
||||
# tool description for LLMs in tool calls, so there's no need to add extra descriptions.
|
||||
- args:
|
||||
- mcp-server-fetch
|
||||
command: uvx
|
||||
name: fetch
|
||||
---
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: docker
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- docker
|
||||
- container
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
---
|
||||
name: fix_test
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
inputs:
|
||||
- description: Branch for the agent to work on
|
||||
name: BRANCH_NAME
|
||||
- description: The test command you want the agent to work on. For example, `pytest
|
||||
tests/unit/test_bash_parsing.py`
|
||||
name: TEST_COMMAND_TO_RUN
|
||||
- description: The name of function to fix
|
||||
name: FUNCTION_TO_FIX
|
||||
- description: The path of the file that contains the function
|
||||
name: FILE_FOR_FUNCTION
|
||||
triggers:
|
||||
- /fix_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
- name: FUNCTION_TO_FIX
|
||||
description: "The name of function to fix"
|
||||
- name: FILE_FOR_FUNCTION
|
||||
description: "The path of the file that contains the function"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: flarglebargle
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- flarglebargle
|
||||
---
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: github
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- github
|
||||
- git
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: gitlab
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- gitlab
|
||||
- git
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: kubernetes
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- kubernetes
|
||||
- k8s
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: npm
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- npm
|
||||
---
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
---
|
||||
name: pdflatex
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- pdflatex
|
||||
---
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
name: security
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- security
|
||||
- vulnerability
|
||||
- authentication
|
||||
- authorization
|
||||
- permissions
|
||||
- security
|
||||
- vulnerability
|
||||
- authentication
|
||||
- authorization
|
||||
- permissions
|
||||
---
|
||||
|
||||
This document provides guidance on security best practices
|
||||
|
||||
You should always be considering security implications when developing.
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
---
|
||||
name: SSH Microagent
|
||||
type: knowledge
|
||||
version: 1.0.0
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- ssh
|
||||
- remote server
|
||||
- remote machine
|
||||
- remote host
|
||||
- remote connection
|
||||
- secure shell
|
||||
- ssh keys
|
||||
- ssh
|
||||
- remote server
|
||||
- remote machine
|
||||
- remote host
|
||||
- remote connection
|
||||
- secure shell
|
||||
- ssh keys
|
||||
---
|
||||
|
||||
# SSH Microagent
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
---
|
||||
name: swift-linux
|
||||
type: knowledge
|
||||
agent: CodeActAgent
|
||||
version: 1.0.0
|
||||
triggers:
|
||||
- swift-linux
|
||||
- swift-debian
|
||||
- swift-installation
|
||||
triggers:
|
||||
- swift-linux
|
||||
- swift-debian
|
||||
- swift-installation
|
||||
---
|
||||
|
||||
# Swift Installation Guide for Debian Linux
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
---
|
||||
name: update_pr_description
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
inputs:
|
||||
- description: URL of the pull request
|
||||
name: PR_URL
|
||||
type: string
|
||||
validation:
|
||||
pattern: ^https://github.com/.+/.+/pull/[0-9]+$
|
||||
- description: Branch name corresponds to the pull request
|
||||
name: BRANCH_NAME
|
||||
type: string
|
||||
triggers:
|
||||
- /update_pr_description
|
||||
inputs:
|
||||
- name: PR_URL
|
||||
description: "URL of the pull request"
|
||||
type: string
|
||||
validation:
|
||||
pattern: "^https://github.com/.+/.+/pull/[0-9]+$"
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch name corresponds to the pull request"
|
||||
type: string
|
||||
---
|
||||
|
||||
Please check the branch "{{ BRANCH_NAME }}" and look at the diff against the main branch. This branch belongs to this PR "{{ PR_URL }}".
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
---
|
||||
name: update_test
|
||||
version: 1.0.0
|
||||
author: openhands
|
||||
agent: CodeActAgent
|
||||
inputs:
|
||||
- description: Branch for the agent to work on
|
||||
name: BRANCH_NAME
|
||||
- description: The test command you want the agent to work on. For example, `pytest
|
||||
tests/unit/test_bash_parsing.py`
|
||||
name: TEST_COMMAND_TO_RUN
|
||||
triggers:
|
||||
- /update_test
|
||||
inputs:
|
||||
- name: BRANCH_NAME
|
||||
description: "Branch for the agent to work on"
|
||||
- name: TEST_COMMAND_TO_RUN
|
||||
description: "The test command you want the agent to work on. For example, `pytest tests/unit/test_bash_parsing.py`"
|
||||
---
|
||||
|
||||
Can you check out branch "{{ BRANCH_NAME }}", and run {{ TEST_COMMAND_TO_RUN }}.
|
||||
|
||||
@@ -95,6 +95,7 @@ class CodeActAgent(Agent):
|
||||
if self._prompt_manager is None:
|
||||
self._prompt_manager = PromptManager(
|
||||
prompt_dir=os.path.join(os.path.dirname(__file__), 'prompts'),
|
||||
system_prompt_filename=self.config.system_prompt_filename,
|
||||
)
|
||||
|
||||
return self._prompt_manager
|
||||
|
||||
@@ -6,17 +6,20 @@ from openhands.llm.tool_names import EXECUTE_BASH_TOOL_NAME
|
||||
|
||||
_DETAILED_BASH_DESCRIPTION = """Execute a bash command in the terminal within a persistent shell session.
|
||||
|
||||
|
||||
### Command Execution
|
||||
* One command at a time: You can only execute one bash command at a time. If you need to run multiple commands sequentially, use `&&` or `;` to chain them together.
|
||||
* Persistent session: Commands execute in a persistent shell session where environment variables, virtual environments, and working directory persist between commands.
|
||||
* Timeout: Commands have a soft timeout of 10 seconds, once that's reached, you have the option to continue or interrupt the command (see section below for details)
|
||||
* Soft timeout: Commands have a soft timeout of 10 seconds, once that's reached, you have the option to continue or interrupt the command (see section below for details)
|
||||
|
||||
### Running and Interacting with Processes
|
||||
* Long running commands: For commands that may run indefinitely, run them in the background and redirect output to a file, e.g. `python3 app.py > server.log 2>&1 &`. For commands that need to run for a specific duration, like "sleep", you can set the "timeout" argument to specify a hard timeout in seconds.
|
||||
* Interact with running process: If a bash command returns exit code `-1`, this means the process is not yet finished. By setting `is_input` to `true`, you can:
|
||||
### Long-running Commands
|
||||
* For commands that may run indefinitely, run them in the background and redirect output to a file, e.g. `python3 app.py > server.log 2>&1 &`.
|
||||
* For commands that may run for a long time (e.g. installation or testing commands), or commands that run for a fixed amount of time (e.g. sleep), you should set the "timeout" parameter of your function call to an appropriate value.
|
||||
* If a bash command returns exit code `-1`, this means the process hit the soft timeout and is not yet finished. By setting `is_input` to `true`, you can:
|
||||
- Send empty `command` to retrieve additional logs
|
||||
- Send text (set `command` to the text) to STDIN of the running process
|
||||
- Send control commands like `C-c` (Ctrl+C), `C-d` (Ctrl+D), or `C-z` (Ctrl+Z) to interrupt the process
|
||||
- If you do C-c, you can re-start the process with a longer "timeout" parameter to let it run to completion
|
||||
|
||||
### Best Practices
|
||||
* Directory verification: Before creating new directories or files, first verify the parent directory exists and is the correct location.
|
||||
|
||||
@@ -52,6 +52,7 @@ async def handle_commands(
|
||||
|
||||
if command == '/exit':
|
||||
close_repl = handle_exit_command(
|
||||
config,
|
||||
event_stream,
|
||||
usage_metrics,
|
||||
sid,
|
||||
@@ -66,7 +67,7 @@ async def handle_commands(
|
||||
handle_status_command(usage_metrics, sid)
|
||||
elif command == '/new':
|
||||
close_repl, new_session_requested = handle_new_command(
|
||||
event_stream, usage_metrics, sid
|
||||
config, event_stream, usage_metrics, sid
|
||||
)
|
||||
elif command == '/settings':
|
||||
await handle_settings_command(config, settings_store)
|
||||
@@ -81,12 +82,16 @@ async def handle_commands(
|
||||
|
||||
|
||||
def handle_exit_command(
|
||||
event_stream: EventStream, usage_metrics: UsageMetrics, sid: str
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
usage_metrics: UsageMetrics,
|
||||
sid: str,
|
||||
) -> bool:
|
||||
close_repl = False
|
||||
|
||||
confirm_exit = (
|
||||
cli_confirm('\nTerminate session?', ['Yes, proceed', 'No, dismiss']) == 0
|
||||
cli_confirm(config, '\nTerminate session?', ['Yes, proceed', 'No, dismiss'])
|
||||
== 0
|
||||
)
|
||||
|
||||
if confirm_exit:
|
||||
@@ -119,7 +124,7 @@ async def handle_init_command(
|
||||
reload_microagents = False
|
||||
|
||||
if config.runtime == 'local':
|
||||
init_repo = await init_repository(current_dir)
|
||||
init_repo = await init_repository(config, current_dir)
|
||||
if init_repo:
|
||||
event_stream.add_event(
|
||||
MessageAction(content=REPO_MD_CREATE_PROMPT),
|
||||
@@ -140,13 +145,17 @@ def handle_status_command(usage_metrics: UsageMetrics, sid: str) -> None:
|
||||
|
||||
|
||||
def handle_new_command(
|
||||
event_stream: EventStream, usage_metrics: UsageMetrics, sid: str
|
||||
config: OpenHandsConfig,
|
||||
event_stream: EventStream,
|
||||
usage_metrics: UsageMetrics,
|
||||
sid: str,
|
||||
) -> tuple[bool, bool]:
|
||||
close_repl = False
|
||||
new_session_requested = False
|
||||
|
||||
new_session_requested = (
|
||||
cli_confirm(
|
||||
config,
|
||||
'\nCurrent session will be terminated and you will lose the conversation history.\n\nContinue?',
|
||||
['Yes, proceed', 'No, dismiss'],
|
||||
)
|
||||
@@ -171,6 +180,7 @@ async def handle_settings_command(
|
||||
) -> None:
|
||||
display_settings(config)
|
||||
modify_settings = cli_confirm(
|
||||
config,
|
||||
'\nWhich settings would you like to modify?',
|
||||
[
|
||||
'Basic',
|
||||
@@ -207,7 +217,7 @@ async def handle_resume_command(
|
||||
return close_repl, new_session_requested
|
||||
|
||||
|
||||
async def init_repository(current_dir: str) -> bool:
|
||||
async def init_repository(config: OpenHandsConfig, current_dir: str) -> bool:
|
||||
repo_file_path = Path(current_dir) / '.openhands' / 'microagents' / 'repo.md'
|
||||
init_repo = False
|
||||
|
||||
@@ -237,6 +247,7 @@ async def init_repository(current_dir: str) -> bool:
|
||||
|
||||
init_repo = (
|
||||
cli_confirm(
|
||||
config,
|
||||
'Do you want to re-initialize?',
|
||||
['Yes, re-initialize', 'No, dismiss'],
|
||||
)
|
||||
@@ -255,6 +266,7 @@ async def init_repository(current_dir: str) -> bool:
|
||||
|
||||
init_repo = (
|
||||
cli_confirm(
|
||||
config,
|
||||
'Do you want to proceed?',
|
||||
['Yes, create', 'No, dismiss'],
|
||||
)
|
||||
@@ -297,7 +309,10 @@ def check_folder_security_agreement(config: OpenHandsConfig, current_dir: str) -
|
||||
print_formatted_text('')
|
||||
|
||||
confirm = (
|
||||
cli_confirm('Do you wish to continue?', ['Yes, proceed', 'No, exit']) == 0
|
||||
cli_confirm(
|
||||
config, 'Do you wish to continue?', ['Yes, proceed', 'No, exit']
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
if confirm:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
@@ -156,7 +155,7 @@ async def run_session(
|
||||
nonlocal reload_microagents, new_session_requested
|
||||
while True:
|
||||
next_message = await read_prompt_input(
|
||||
agent_state, multiline=config.cli_multiline_input
|
||||
config, agent_state, multiline=config.cli_multiline_input
|
||||
)
|
||||
|
||||
if not next_message.strip():
|
||||
@@ -215,7 +214,7 @@ async def run_session(
|
||||
)
|
||||
return
|
||||
|
||||
confirmation_status = await read_confirmation_input()
|
||||
confirmation_status = await read_confirmation_input(config)
|
||||
if confirmation_status == 'yes' or confirmation_status == 'always':
|
||||
event_stream.add_event(
|
||||
ChangeAgentStateAction(AgentState.USER_CONFIRMED),
|
||||
@@ -349,10 +348,9 @@ async def run_setup_flow(config: OpenHandsConfig, settings_store: FileSettingsSt
|
||||
await modify_llm_settings_basic(config, settings_store)
|
||||
|
||||
|
||||
async def main_with_loop(
|
||||
loop: asyncio.AbstractEventLoop, args: argparse.Namespace
|
||||
) -> None:
|
||||
async def main_with_loop(loop: asyncio.AbstractEventLoop) -> None:
|
||||
"""Runs the agent in CLI mode."""
|
||||
args = parse_arguments()
|
||||
|
||||
logger.setLevel(logging.WARNING)
|
||||
|
||||
@@ -457,14 +455,10 @@ async def main_with_loop(
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for the OpenHands CLI."""
|
||||
print(f'DEBUG: sys.argv = {sys.argv}')
|
||||
args = parse_arguments()
|
||||
print(f'DEBUG: parsed args.config_file = {args.config_file}')
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_until_complete(main_with_loop(loop, args))
|
||||
loop.run_until_complete(main_with_loop(loop))
|
||||
except KeyboardInterrupt:
|
||||
print('Received keyboard interrupt, shutting down...')
|
||||
except ConnectionRefusedError as e:
|
||||
|
||||
@@ -13,6 +13,7 @@ from openhands.cli.tui import (
|
||||
)
|
||||
from openhands.cli.utils import (
|
||||
VERIFIED_ANTHROPIC_MODELS,
|
||||
VERIFIED_MISTRAL_MODELS,
|
||||
VERIFIED_OPENAI_MODELS,
|
||||
VERIFIED_PROVIDERS,
|
||||
organize_models_and_providers,
|
||||
@@ -134,9 +135,10 @@ async def get_validated_input(
|
||||
return value
|
||||
|
||||
|
||||
def save_settings_confirmation() -> bool:
|
||||
def save_settings_confirmation(config: OpenHandsConfig) -> bool:
|
||||
return (
|
||||
cli_confirm(
|
||||
config,
|
||||
'\nSave new settings? (They will take effect after restart)',
|
||||
['Yes, save', 'No, discard'],
|
||||
)
|
||||
@@ -158,7 +160,7 @@ async def modify_llm_settings_basic(
|
||||
provider_completer = FuzzyWordCompleter(provider_list)
|
||||
session = PromptSession(key_bindings=kb_cancel())
|
||||
|
||||
# Set default provider - prefer 'anthropic' if available, otherwise use the first provider
|
||||
# Set default provider - prefer 'anthropic' if available, otherwise use first
|
||||
provider = 'anthropic' if 'anthropic' in provider_list else provider_list[0]
|
||||
model = None
|
||||
api_key = None
|
||||
@@ -168,15 +170,27 @@ async def modify_llm_settings_basic(
|
||||
print_formatted_text(
|
||||
HTML(f'\n<grey>Default provider: </grey><green>{provider}</green>')
|
||||
)
|
||||
change_provider = (
|
||||
cli_confirm(
|
||||
'Do you want to use a different provider?',
|
||||
[f'Use {provider}', 'Select another provider'],
|
||||
)
|
||||
== 1
|
||||
|
||||
# Show verified providers plus "Select another provider" option
|
||||
provider_choices = verified_providers + ['Select another provider']
|
||||
provider_choice = cli_confirm(
|
||||
config,
|
||||
'(Step 1/3) Select LLM Provider:',
|
||||
provider_choices,
|
||||
)
|
||||
|
||||
if change_provider:
|
||||
# Ensure provider_choice is an integer (for test compatibility)
|
||||
try:
|
||||
choice_index = int(provider_choice)
|
||||
except (TypeError, ValueError):
|
||||
# If conversion fails (e.g., in tests with mocks), default to 0
|
||||
choice_index = 0
|
||||
|
||||
if choice_index < len(verified_providers):
|
||||
# User selected one of the verified providers
|
||||
provider = verified_providers[choice_index]
|
||||
else:
|
||||
# User selected "Select another provider" - use manual selection
|
||||
# Define a validator function that prints an error message
|
||||
def provider_validator(x):
|
||||
is_valid = x in organized_models
|
||||
@@ -196,7 +210,8 @@ async def modify_llm_settings_basic(
|
||||
|
||||
# Make sure the provider exists in organized_models
|
||||
if provider not in organized_models:
|
||||
# If the provider doesn't exist, prefer 'anthropic' if available, otherwise use the first provider
|
||||
# If the provider doesn't exist, prefer 'anthropic' if available,
|
||||
# otherwise use the first provider
|
||||
provider = (
|
||||
'anthropic'
|
||||
if 'anthropic' in organized_models
|
||||
@@ -214,6 +229,11 @@ async def modify_llm_settings_basic(
|
||||
m for m in provider_models if m not in VERIFIED_ANTHROPIC_MODELS
|
||||
]
|
||||
provider_models = VERIFIED_ANTHROPIC_MODELS + provider_models
|
||||
if provider == 'mistral':
|
||||
provider_models = [
|
||||
m for m in provider_models if m not in VERIFIED_MISTRAL_MODELS
|
||||
]
|
||||
provider_models = VERIFIED_MISTRAL_MODELS + provider_models
|
||||
|
||||
# Set default model to the best verified model for the provider
|
||||
if provider == 'anthropic' and VERIFIED_ANTHROPIC_MODELS:
|
||||
@@ -222,6 +242,9 @@ async def modify_llm_settings_basic(
|
||||
elif provider == 'openai' and VERIFIED_OPENAI_MODELS:
|
||||
# Use the first model in the VERIFIED_OPENAI_MODELS list as it's the best/newest
|
||||
default_model = VERIFIED_OPENAI_MODELS[0]
|
||||
elif provider == 'mistral' and VERIFIED_MISTRAL_MODELS:
|
||||
# Use the first model in the VERIFIED_MISTRAL_MODELS list as it's the best/newest
|
||||
default_model = VERIFIED_MISTRAL_MODELS[0]
|
||||
else:
|
||||
# For other providers, use the first model in the list
|
||||
default_model = (
|
||||
@@ -234,6 +257,7 @@ async def modify_llm_settings_basic(
|
||||
)
|
||||
change_model = (
|
||||
cli_confirm(
|
||||
config,
|
||||
'Do you want to use a different model?',
|
||||
[f'Use {default_model}', 'Select another model'],
|
||||
)
|
||||
@@ -243,23 +267,28 @@ async def modify_llm_settings_basic(
|
||||
if change_model:
|
||||
model_completer = FuzzyWordCompleter(provider_models)
|
||||
|
||||
# Define a validator function that prints an error message
|
||||
# Define a validator function that allows custom models but shows a warning
|
||||
def model_validator(x):
|
||||
is_valid = x in provider_models
|
||||
if not is_valid:
|
||||
# Allow any non-empty model name
|
||||
if not x.strip():
|
||||
return False
|
||||
|
||||
# Show a warning for models not in the predefined list, but still allow them
|
||||
if x not in provider_models:
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
f'<grey>Invalid model selected for provider {provider}: {x}</grey>'
|
||||
f'<yellow>Warning: {x} is not in the predefined list for provider {provider}. '
|
||||
f'Make sure this model name is correct.</yellow>'
|
||||
)
|
||||
)
|
||||
return is_valid
|
||||
return True
|
||||
|
||||
model = await get_validated_input(
|
||||
session,
|
||||
'(Step 2/3) Select LLM Model (TAB for options, CTRL-c to cancel): ',
|
||||
completer=model_completer,
|
||||
validator=model_validator,
|
||||
error_message=f'Invalid model selected for provider {provider}',
|
||||
error_message='Model name cannot be empty',
|
||||
)
|
||||
else:
|
||||
# Use the default model
|
||||
@@ -281,7 +310,7 @@ async def modify_llm_settings_basic(
|
||||
# The try-except block above ensures we either have valid inputs or we've already returned
|
||||
# No need to check for None values here
|
||||
|
||||
save_settings = save_settings_confirmation()
|
||||
save_settings = save_settings_confirmation(config)
|
||||
|
||||
if not save_settings:
|
||||
return
|
||||
@@ -356,6 +385,7 @@ async def modify_llm_settings_advanced(
|
||||
|
||||
enable_confirmation_mode = (
|
||||
cli_confirm(
|
||||
config,
|
||||
question='(Step 5/6) Confirmation Mode (CTRL-c to cancel):',
|
||||
choices=['Enable', 'Disable'],
|
||||
)
|
||||
@@ -364,6 +394,7 @@ async def modify_llm_settings_advanced(
|
||||
|
||||
enable_memory_condensation = (
|
||||
cli_confirm(
|
||||
config,
|
||||
question='(Step 6/6) Memory Condensation (CTRL-c to cancel):',
|
||||
choices=['Enable', 'Disable'],
|
||||
)
|
||||
@@ -380,7 +411,7 @@ async def modify_llm_settings_advanced(
|
||||
# The try-except block above ensures we either have valid inputs or we've already returned
|
||||
# No need to check for None values here
|
||||
|
||||
save_settings = save_settings_confirmation()
|
||||
save_settings = save_settings_confirmation(config)
|
||||
|
||||
if not save_settings:
|
||||
return
|
||||
|
||||
@@ -27,13 +27,6 @@ def suppress_cli_warnings():
|
||||
category=UserWarning,
|
||||
)
|
||||
|
||||
# Suppress httpx deprecation warnings about content parameter
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
message=".*Use 'content=<...>' to upload raw bytes/text content.*",
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
|
||||
# Suppress general deprecation warnings from dependencies during CLI usage
|
||||
# This catches the "Call to deprecated method get_events" warning
|
||||
warnings.filterwarnings(
|
||||
|
||||
@@ -191,19 +191,24 @@ def display_event(event: Event, config: OpenHandsConfig) -> None:
|
||||
if isinstance(event, MessageAction):
|
||||
if event.source == EventSource.AGENT:
|
||||
display_message(event.content)
|
||||
|
||||
if isinstance(event, CmdRunAction):
|
||||
display_command(event)
|
||||
# Only display the command if it's not already confirmed
|
||||
# Commands are always shown when AWAITING_CONFIRMATION, so we don't need to show them again when CONFIRMED
|
||||
if event.confirmation_state != ActionConfirmationStatus.CONFIRMED:
|
||||
display_command(event)
|
||||
|
||||
if event.confirmation_state == ActionConfirmationStatus.CONFIRMED:
|
||||
initialize_streaming_output()
|
||||
if isinstance(event, CmdOutputObservation):
|
||||
elif isinstance(event, CmdOutputObservation):
|
||||
display_command_output(event.content)
|
||||
if isinstance(event, FileEditObservation):
|
||||
elif isinstance(event, FileEditObservation):
|
||||
display_file_edit(event)
|
||||
if isinstance(event, FileReadObservation):
|
||||
elif isinstance(event, FileReadObservation):
|
||||
display_file_read(event)
|
||||
if isinstance(event, AgentStateChangedObservation):
|
||||
elif isinstance(event, AgentStateChangedObservation):
|
||||
display_agent_state_change_message(event.agent_state)
|
||||
if isinstance(event, ErrorObservation):
|
||||
elif isinstance(event, ErrorObservation):
|
||||
display_error(event.content)
|
||||
|
||||
|
||||
@@ -515,13 +520,16 @@ class CommandCompleter(Completer):
|
||||
)
|
||||
|
||||
|
||||
def create_prompt_session() -> PromptSession[str]:
|
||||
return PromptSession(style=DEFAULT_STYLE)
|
||||
def create_prompt_session(config: OpenHandsConfig) -> PromptSession[str]:
|
||||
"""Creates a prompt session with VI mode enabled if specified in the config."""
|
||||
return PromptSession(style=DEFAULT_STYLE, vi_mode=config.cli.vi_mode)
|
||||
|
||||
|
||||
async def read_prompt_input(agent_state: str, multiline: bool = False) -> str:
|
||||
async def read_prompt_input(
|
||||
config: OpenHandsConfig, agent_state: str, multiline: bool = False
|
||||
) -> str:
|
||||
try:
|
||||
prompt_session = create_prompt_session()
|
||||
prompt_session = create_prompt_session(config)
|
||||
prompt_session.completer = (
|
||||
CommandCompleter(agent_state) if not multiline else None
|
||||
)
|
||||
@@ -553,9 +561,9 @@ async def read_prompt_input(agent_state: str, multiline: bool = False) -> str:
|
||||
return '/exit'
|
||||
|
||||
|
||||
async def read_confirmation_input() -> str:
|
||||
async def read_confirmation_input(config: OpenHandsConfig) -> str:
|
||||
try:
|
||||
prompt_session = create_prompt_session()
|
||||
prompt_session = create_prompt_session(config)
|
||||
|
||||
with patch_stdout():
|
||||
print_formatted_text('')
|
||||
@@ -601,7 +609,9 @@ async def process_agent_pause(done: asyncio.Event, event_stream: EventStream) ->
|
||||
|
||||
|
||||
def cli_confirm(
|
||||
question: str = 'Are you sure?', choices: list[str] | None = None
|
||||
config: OpenHandsConfig,
|
||||
question: str = 'Are you sure?',
|
||||
choices: list[str] | None = None,
|
||||
) -> int:
|
||||
"""Display a confirmation prompt with the given question and choices.
|
||||
|
||||
@@ -625,15 +635,27 @@ def cli_confirm(
|
||||
kb = KeyBindings()
|
||||
|
||||
@kb.add('up')
|
||||
def _(event: KeyPressEvent) -> None:
|
||||
def _handle_up(event: KeyPressEvent) -> None:
|
||||
selected[0] = (selected[0] - 1) % len(choices)
|
||||
|
||||
if config.cli.vi_mode:
|
||||
|
||||
@kb.add('k')
|
||||
def _handle_k(event: KeyPressEvent) -> None:
|
||||
selected[0] = (selected[0] - 1) % len(choices)
|
||||
|
||||
@kb.add('down')
|
||||
def _(event: KeyPressEvent) -> None:
|
||||
def _handle_down(event: KeyPressEvent) -> None:
|
||||
selected[0] = (selected[0] + 1) % len(choices)
|
||||
|
||||
if config.cli.vi_mode:
|
||||
|
||||
@kb.add('j')
|
||||
def _handle_j(event: KeyPressEvent) -> None:
|
||||
selected[0] = (selected[0] + 1) % len(choices)
|
||||
|
||||
@kb.add('enter')
|
||||
def _(event: KeyPressEvent) -> None:
|
||||
def _handle_enter(event: KeyPressEvent) -> None:
|
||||
event.app.exit(result=selected[0])
|
||||
|
||||
style = Style.from_dict({'selected': COLOR_GOLD, 'unselected': ''})
|
||||
|
||||
@@ -102,6 +102,8 @@ def extract_model_and_provider(model: str) -> ModelInfo:
|
||||
return ModelInfo(provider='openai', model=split[0], separator='/')
|
||||
if split[0] in VERIFIED_ANTHROPIC_MODELS:
|
||||
return ModelInfo(provider='anthropic', model=split[0], separator='/')
|
||||
if split[0] in VERIFIED_MISTRAL_MODELS:
|
||||
return ModelInfo(provider='mistral', model=split[0], separator='/')
|
||||
# return as model only
|
||||
return ModelInfo(provider='', model=model, separator='')
|
||||
|
||||
@@ -143,9 +145,10 @@ def organize_models_and_providers(
|
||||
return result_dict
|
||||
|
||||
|
||||
VERIFIED_PROVIDERS = ['openai', 'azure', 'anthropic', 'deepseek']
|
||||
VERIFIED_PROVIDERS = ['anthropic', 'openai', 'mistral']
|
||||
|
||||
VERIFIED_OPENAI_MODELS = [
|
||||
'o4-mini',
|
||||
'gpt-4o',
|
||||
'gpt-4o-mini',
|
||||
'gpt-4-turbo',
|
||||
@@ -171,6 +174,10 @@ VERIFIED_ANTHROPIC_MODELS = [
|
||||
'claude-2',
|
||||
]
|
||||
|
||||
VERIFIED_MISTRAL_MODELS = [
|
||||
'devstral-small-2505',
|
||||
]
|
||||
|
||||
|
||||
class ProviderInfo(BaseModel):
|
||||
"""Information about a provider and its models."""
|
||||
|
||||
@@ -5,12 +5,12 @@ from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import AgentConfig
|
||||
from openhands.events.action import Action
|
||||
from openhands.events.action.message import SystemMessageAction
|
||||
from openhands.utils.prompt import PromptManager
|
||||
from litellm import ChatCompletionToolParam
|
||||
|
||||
from openhands.core.config import AgentConfig
|
||||
from openhands.core.exceptions import (
|
||||
AgentAlreadyRegisteredError,
|
||||
AgentNotRegisteredError,
|
||||
@@ -33,10 +33,13 @@ class Agent(ABC):
|
||||
_registry: dict[str, type['Agent']] = {}
|
||||
sandbox_plugins: list[PluginRequirement] = []
|
||||
|
||||
config_model: type[AgentConfig] = AgentConfig
|
||||
"""Class field that specifies the config model to use for the agent. Subclasses may override with a derived config model if needed."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm: LLM,
|
||||
config: 'AgentConfig',
|
||||
config: AgentConfig,
|
||||
):
|
||||
self.llm = llm
|
||||
self.config = config
|
||||
|
||||
@@ -821,6 +821,11 @@ class AgentController:
|
||||
or 'input length and `max_tokens` exceed context limit' in error_str
|
||||
or 'please reduce the length of either one'
|
||||
in error_str # For OpenRouter context window errors
|
||||
or (
|
||||
'sambanovaexception' in error_str
|
||||
and 'maximum context length' in error_str
|
||||
)
|
||||
# For SambaNova context window errors - only match when both patterns are present
|
||||
or isinstance(e, ContextWindowExceededError)
|
||||
):
|
||||
if self.agent.config.enable_history_truncation:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from openhands.core.config.agent_config import AgentConfig
|
||||
from openhands.core.config.cli_config import CLIConfig
|
||||
from openhands.core.config.config_utils import (
|
||||
OH_DEFAULT_AGENT,
|
||||
OH_MAX_ITERATIONS,
|
||||
@@ -26,6 +27,7 @@ __all__ = [
|
||||
'OH_DEFAULT_AGENT',
|
||||
'OH_MAX_ITERATIONS',
|
||||
'AgentConfig',
|
||||
'CLIConfig',
|
||||
'OpenHandsConfig',
|
||||
'MCPConfig',
|
||||
'LLMConfig',
|
||||
|
||||
@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, ValidationError
|
||||
from openhands.core.config.condenser_config import CondenserConfig, NoOpCondenserConfig
|
||||
from openhands.core.config.extended_config import ExtendedConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.utils.import_utils import get_impl
|
||||
|
||||
|
||||
class AgentConfig(BaseModel):
|
||||
@@ -12,6 +13,8 @@ class AgentConfig(BaseModel):
|
||||
"""The name of the llm config to use. If specified, this will override global llm config."""
|
||||
classpath: str | None = Field(default=None)
|
||||
"""The classpath of the agent to use. To be used for custom agents that are not defined in the openhands.agenthub package."""
|
||||
system_prompt_filename: str = Field(default='system_prompt.j2')
|
||||
"""Filename of the system prompt template file within the agent's prompt directory. Defaults to 'system_prompt.j2'."""
|
||||
enable_browsing: bool = Field(default=True)
|
||||
"""Whether to enable browsing tool.
|
||||
Note: If using CLIRuntime, browsing is not implemented and should be disabled."""
|
||||
@@ -96,7 +99,27 @@ class AgentConfig(BaseModel):
|
||||
try:
|
||||
# Merge base config with overrides
|
||||
merged = {**base_config.model_dump(), **overrides}
|
||||
custom_config = cls.model_validate(merged)
|
||||
if merged.get('classpath'):
|
||||
# if an explicit classpath is given, try to load it and look up its config model class
|
||||
from openhands.controller.agent import Agent
|
||||
|
||||
try:
|
||||
agent_cls = get_impl(Agent, merged.get('classpath'))
|
||||
custom_config = agent_cls.config_model.model_validate(merged)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f'Failed to load custom agent class [{merged.get("classpath")}]: {e}. Using default config model.'
|
||||
)
|
||||
custom_config = cls.model_validate(merged)
|
||||
else:
|
||||
# otherwise, try to look up the agent class by name (i.e. if it's a built-in)
|
||||
# if that fails, just use the default AgentConfig class.
|
||||
try:
|
||||
agent_cls = Agent.get_cls(name)
|
||||
custom_config = agent_cls.config_model.model_validate(merged)
|
||||
except Exception:
|
||||
# otherwise, just fall back to the default config model
|
||||
custom_config = cls.model_validate(merged)
|
||||
agent_mapping[name] = custom_config
|
||||
except ValidationError as e:
|
||||
logger.warning(
|
||||
|
||||
9
openhands/core/config/cli_config.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CLIConfig(BaseModel):
|
||||
"""Configuration for CLI-specific settings."""
|
||||
|
||||
vi_mode: bool = Field(default=False)
|
||||
|
||||
model_config = {'extra': 'forbid'}
|
||||
@@ -158,6 +158,7 @@ class MCPConfig(BaseModel):
|
||||
mcp_mapping['mcp'] = cls(
|
||||
sse_servers=mcp_config.sse_servers,
|
||||
stdio_servers=mcp_config.stdio_servers,
|
||||
shttp_servers=mcp_config.shttp_servers,
|
||||
)
|
||||
except ValidationError as e:
|
||||
raise ValueError(f'Invalid MCP configuration: {e}')
|
||||
|
||||
@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, SecretStr
|
||||
|
||||
from openhands.core import logger
|
||||
from openhands.core.config.agent_config import AgentConfig
|
||||
from openhands.core.config.cli_config import CLIConfig
|
||||
from openhands.core.config.config_utils import (
|
||||
OH_DEFAULT_AGENT,
|
||||
OH_MAX_ITERATIONS,
|
||||
@@ -109,6 +110,7 @@ class OpenHandsConfig(BaseModel):
|
||||
mcp_host: str = Field(default=f'localhost:{os.getenv("port", 3000)}')
|
||||
mcp: MCPConfig = Field(default_factory=MCPConfig)
|
||||
kubernetes: KubernetesConfig = Field(default_factory=KubernetesConfig)
|
||||
cli: CLIConfig = Field(default_factory=CLIConfig)
|
||||
|
||||
defaults_dict: ClassVar[dict] = {}
|
||||
|
||||
|
||||
@@ -794,17 +794,19 @@ def convert_non_fncall_messages_to_fncall_messages(
|
||||
)
|
||||
|
||||
if tool_result_match:
|
||||
if not (
|
||||
isinstance(content, str)
|
||||
or (
|
||||
isinstance(content, list)
|
||||
and len(content) == 1
|
||||
and content[0].get('type') == 'text'
|
||||
)
|
||||
):
|
||||
if isinstance(content, list):
|
||||
text_content_items = [
|
||||
item for item in content if item.get('type') == 'text'
|
||||
]
|
||||
if not text_content_items:
|
||||
raise FunctionCallConversionError(
|
||||
f'Could not find text content in message with tool result. Content: {content}'
|
||||
)
|
||||
elif not isinstance(content, str):
|
||||
raise FunctionCallConversionError(
|
||||
f'Expected str or list with one text item when tool result is present in the message. Content: {content}'
|
||||
f'Unexpected content type {type(content)}. Expected str or list. Content: {content}'
|
||||
)
|
||||
|
||||
tool_name = tool_result_match.group(1)
|
||||
tool_result = tool_result_match.group(2).strip()
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ class LLM(RetryMixin, DebugMixin):
|
||||
'temperature': self.config.temperature,
|
||||
'max_completion_tokens': self.config.max_output_tokens,
|
||||
}
|
||||
|
||||
if self.config.top_k is not None:
|
||||
# openai doesn't expose top_k
|
||||
# litellm will handle it a bit differently than the openai-compatible params
|
||||
@@ -288,8 +289,20 @@ class LLM(RetryMixin, DebugMixin):
|
||||
# Record start time for latency measurement
|
||||
start_time = time.time()
|
||||
# we don't support streaming here, thus we get a ModelResponse
|
||||
logger.warning(f'LLM: {self.config.model}\n{self.config.base_url}')
|
||||
resp: ModelResponse = self._completion_unwrapped(*args, **kwargs)
|
||||
|
||||
# Suppress httpx deprecation warnings during LiteLLM calls
|
||||
# This prevents the "Use 'content=<...>' to upload raw bytes/text content" warning
|
||||
# that appears when LiteLLM makes HTTP requests to LLM providers
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
'ignore', category=DeprecationWarning, module='httpx.*'
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
message=r'.*content=.*upload.*',
|
||||
category=DeprecationWarning,
|
||||
)
|
||||
resp: ModelResponse = self._completion_unwrapped(*args, **kwargs)
|
||||
|
||||
# Calculate and record latency
|
||||
latency = time.time() - start_time
|
||||
@@ -474,26 +487,6 @@ class LLM(RetryMixin, DebugMixin):
|
||||
# Safe fallback for any potentially viable model
|
||||
self.config.max_input_tokens = 4096
|
||||
|
||||
if self.config.max_output_tokens is None:
|
||||
# Safe default for any potentially viable model
|
||||
self.config.max_output_tokens = 4096
|
||||
if self.model_info is not None:
|
||||
# max_output_tokens has precedence over max_tokens, if either exists.
|
||||
# litellm has models with both, one or none of these 2 parameters!
|
||||
if 'max_output_tokens' in self.model_info and isinstance(
|
||||
self.model_info['max_output_tokens'], int
|
||||
):
|
||||
self.config.max_output_tokens = self.model_info['max_output_tokens']
|
||||
elif 'max_tokens' in self.model_info and isinstance(
|
||||
self.model_info['max_tokens'], int
|
||||
):
|
||||
self.config.max_output_tokens = self.model_info['max_tokens']
|
||||
if any(
|
||||
model in self.config.model
|
||||
for model in ['claude-3-7-sonnet', 'claude-3.7-sonnet']
|
||||
):
|
||||
self.config.max_output_tokens = 64000 # litellm set max to 128k, but that requires a header to be set
|
||||
|
||||
# Initialize function calling capability
|
||||
# Check if model name is in our supported list
|
||||
model_name_supported = (
|
||||
|
||||
@@ -56,6 +56,18 @@ class ConversationMemory:
|
||||
self.agent_config = config
|
||||
self.prompt_manager = prompt_manager
|
||||
|
||||
@staticmethod
|
||||
def _is_valid_image_url(url: str | None) -> bool:
|
||||
"""Check if an image URL is valid and non-empty.
|
||||
|
||||
Args:
|
||||
url: The image URL to validate
|
||||
|
||||
Returns:
|
||||
True if the URL is valid, False otherwise
|
||||
"""
|
||||
return bool(url and url.strip())
|
||||
|
||||
def process_events(
|
||||
self,
|
||||
condensed_history: list[Event],
|
||||
@@ -380,7 +392,27 @@ class ConversationMemory:
|
||||
|
||||
# Add image URLs if available and vision is active
|
||||
if vision_is_active and obs.image_urls:
|
||||
content.append(ImageContent(image_urls=obs.image_urls))
|
||||
# Filter out empty or invalid image URLs
|
||||
valid_image_urls = [
|
||||
url for url in obs.image_urls if self._is_valid_image_url(url)
|
||||
]
|
||||
invalid_count = len(obs.image_urls) - len(valid_image_urls)
|
||||
|
||||
if valid_image_urls:
|
||||
content.append(ImageContent(image_urls=valid_image_urls))
|
||||
if invalid_count > 0:
|
||||
# Add text indicating some images were filtered
|
||||
content[
|
||||
0
|
||||
].text += f'\n\nNote: {invalid_count} invalid or empty image(s) were filtered from this output. The agent may need to use alternative methods to access visual information.'
|
||||
else:
|
||||
logger.debug(
|
||||
'IPython observation has image URLs but none are valid'
|
||||
)
|
||||
# Add text indicating all images were filtered
|
||||
content[
|
||||
0
|
||||
].text += f'\n\nNote: All {len(obs.image_urls)} image(s) in this output were invalid or empty and have been filtered. The agent should use alternative methods to access visual information.'
|
||||
|
||||
message = Message(role='user', content=content)
|
||||
elif isinstance(obs, FileEditObservation):
|
||||
@@ -398,25 +430,42 @@ class ConversationMemory:
|
||||
and vision_is_active
|
||||
):
|
||||
text += 'Image: Current webpage screenshot (Note that only visible portion of webpage is present in the screenshot. You may need to scroll to view the remaining portion of the web-page.)\n'
|
||||
message = Message(
|
||||
role='user',
|
||||
content=[
|
||||
TextContent(text=text),
|
||||
ImageContent(
|
||||
image_urls=[
|
||||
# show set of marks if it exists
|
||||
# otherwise, show raw screenshot when using vision-supported model
|
||||
obs.set_of_marks
|
||||
if obs.set_of_marks is not None
|
||||
and len(obs.set_of_marks) > 0
|
||||
else obs.screenshot
|
||||
]
|
||||
),
|
||||
],
|
||||
)
|
||||
logger.debug(
|
||||
f'Vision enabled for browsing, showing {"set of marks" if obs.set_of_marks and len(obs.set_of_marks) > 0 else "screenshot"}'
|
||||
)
|
||||
|
||||
# Determine which image to use and validate it
|
||||
image_url = None
|
||||
if obs.set_of_marks is not None and len(obs.set_of_marks) > 0:
|
||||
image_url = obs.set_of_marks
|
||||
image_type = 'set of marks'
|
||||
elif obs.screenshot is not None and len(obs.screenshot) > 0:
|
||||
image_url = obs.screenshot
|
||||
image_type = 'screenshot'
|
||||
|
||||
# Create message content with text
|
||||
content = [TextContent(text=text)]
|
||||
|
||||
# Only add ImageContent if we have a valid image URL
|
||||
if self._is_valid_image_url(image_url):
|
||||
content.append(ImageContent(image_urls=[image_url]))
|
||||
logger.debug(f'Vision enabled for browsing, showing {image_type}')
|
||||
else:
|
||||
if image_url:
|
||||
logger.warning(
|
||||
f'Invalid image URL format for {image_type}: {image_url[:50]}...'
|
||||
)
|
||||
# Add text indicating the image was filtered
|
||||
content[
|
||||
0
|
||||
].text += f'\n\nNote: The {image_type} for this webpage was invalid or empty and has been filtered. The agent should use alternative methods to access visual information about the webpage.'
|
||||
else:
|
||||
logger.debug(
|
||||
'Vision enabled for browsing, but no valid image available'
|
||||
)
|
||||
# Add text indicating no image was available
|
||||
content[
|
||||
0
|
||||
].text += '\n\nNote: No visual information (screenshot or set of marks) is available for this webpage. The agent should rely on the text content above.'
|
||||
|
||||
message = Message(role='user', content=content)
|
||||
else:
|
||||
message = Message(
|
||||
role='user',
|
||||
|
||||
@@ -40,6 +40,11 @@ class BaseMicroagent(BaseModel):
|
||||
derived_name = None
|
||||
if microagent_dir is not None:
|
||||
derived_name = str(path.relative_to(microagent_dir).with_suffix(''))
|
||||
else:
|
||||
derived_name = path.with_suffix('').name
|
||||
logger.warning(
|
||||
f'No microagent_dir provided. Microagent name will be the file name: {derived_name}'
|
||||
)
|
||||
|
||||
# Only load directly from path if file_content is not provided
|
||||
if file_content is None:
|
||||
@@ -95,6 +100,16 @@ class BaseMicroagent(BaseModel):
|
||||
MicroagentType.TASK: TaskMicroagent,
|
||||
}
|
||||
|
||||
# We will always use derived_name if available
|
||||
assert derived_name is not None
|
||||
agent_name = derived_name
|
||||
if metadata.name is not None:
|
||||
logger.warning(
|
||||
f'Detected `name:` field in frontmatter for microagent {metadata.name}. '
|
||||
"This is deprecated. Microagent's name will use the file name "
|
||||
f'({derived_name}) instead.'
|
||||
)
|
||||
|
||||
# Infer the agent type:
|
||||
# 1. If inputs exist -> TASK
|
||||
# 2. If triggers exist -> KNOWLEDGE
|
||||
@@ -102,8 +117,7 @@ class BaseMicroagent(BaseModel):
|
||||
inferred_type: MicroagentType
|
||||
if metadata.inputs:
|
||||
inferred_type = MicroagentType.TASK
|
||||
# Add a trigger for the agent name if not already present
|
||||
trigger = f'/{metadata.name}'
|
||||
trigger = f'/{agent_name}'
|
||||
if not metadata.triggers or trigger not in metadata.triggers:
|
||||
if not metadata.triggers:
|
||||
metadata.triggers = [trigger]
|
||||
@@ -120,9 +134,6 @@ class BaseMicroagent(BaseModel):
|
||||
# This should theoretically not happen with the logic above
|
||||
raise ValueError(f'Could not determine microagent type for: {path}')
|
||||
|
||||
# Use derived_name if available (from relative path), otherwise fallback to metadata.name
|
||||
agent_name = derived_name if derived_name is not None else metadata.name
|
||||
|
||||
agent_class = subclass_map[inferred_type]
|
||||
return agent_class(
|
||||
name=agent_name,
|
||||
|
||||
@@ -25,10 +25,12 @@ class InputMetadata(BaseModel):
|
||||
class MicroagentMetadata(BaseModel):
|
||||
"""Metadata for all microagents."""
|
||||
|
||||
name: str = 'default'
|
||||
name: str = Field(default='default', exclude=True)
|
||||
type: MicroagentType = Field(default=MicroagentType.REPO_KNOWLEDGE)
|
||||
version: str = Field(default='1.0.0')
|
||||
agent: str = Field(default='CodeActAgent')
|
||||
# Keep these fields for backward compatibility but they're not used
|
||||
version: str = Field(default='1.0.0', exclude=True)
|
||||
agent: str = Field(default='CodeActAgent', exclude=True)
|
||||
author: str = Field(default='', exclude=True)
|
||||
triggers: list[str] = [] # optional, only exists for knowledge microagents
|
||||
inputs: list[InputMetadata] = [] # optional, only exists for task microagents
|
||||
mcp_tools: MCPConfig | None = (
|
||||
|
||||
@@ -9,11 +9,12 @@ import select
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
|
||||
from binaryornot.check import is_binary
|
||||
from openhands_aci.editor.editor import OHEditor
|
||||
@@ -51,6 +52,41 @@ from openhands.runtime.base import Runtime
|
||||
from openhands.runtime.plugins import PluginRequirement
|
||||
from openhands.runtime.runtime_status import RuntimeStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from openhands.runtime.utils.windows_bash import WindowsPowershellSession
|
||||
|
||||
# Import Windows PowerShell support if on Windows
|
||||
if sys.platform == 'win32':
|
||||
try:
|
||||
from openhands.runtime.utils.windows_bash import WindowsPowershellSession
|
||||
from openhands.runtime.utils.windows_exceptions import DotNetMissingError
|
||||
except (ImportError, DotNetMissingError) as err:
|
||||
# Print a user-friendly error message without stack trace
|
||||
friendly_message = """
|
||||
ERROR: PowerShell and .NET SDK are required but not properly configured
|
||||
|
||||
The .NET SDK and PowerShell are required for OpenHands CLI on Windows.
|
||||
PowerShell integration cannot function without .NET Core.
|
||||
|
||||
Please install the .NET SDK by following the instructions at:
|
||||
https://docs.all-hands.dev/usage/windows-without-wsl
|
||||
|
||||
After installing .NET SDK, restart your terminal and try again.
|
||||
"""
|
||||
print(friendly_message, file=sys.stderr)
|
||||
logger.error(
|
||||
f'Windows runtime initialization failed: {type(err).__name__}: {str(err)}'
|
||||
)
|
||||
if (
|
||||
isinstance(err, DotNetMissingError)
|
||||
and hasattr(err, 'details')
|
||||
and err.details
|
||||
):
|
||||
logger.debug(f'Details: {err.details}')
|
||||
|
||||
# Exit the program with an error code
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class CLIRuntime(Runtime):
|
||||
"""
|
||||
@@ -119,6 +155,10 @@ class CLIRuntime(Runtime):
|
||||
self.file_editor = OHEditor(workspace_root=self._workspace_path)
|
||||
self._shell_stream_callback: Callable[[str], None] | None = None
|
||||
|
||||
# Initialize PowerShell session on Windows
|
||||
self._is_windows = sys.platform == 'win32'
|
||||
self._powershell_session: WindowsPowershellSession | None = None
|
||||
|
||||
logger.warning(
|
||||
'Initializing CLIRuntime. WARNING: NO SANDBOX IS USED. '
|
||||
'This runtime executes commands directly on the local system. '
|
||||
@@ -135,6 +175,15 @@ class CLIRuntime(Runtime):
|
||||
# Change to the workspace directory
|
||||
os.chdir(self._workspace_path)
|
||||
|
||||
# Initialize PowerShell session if on Windows
|
||||
if self._is_windows:
|
||||
self._powershell_session = WindowsPowershellSession(
|
||||
work_dir=self._workspace_path,
|
||||
username=None, # Use current user
|
||||
no_change_timeout_seconds=30,
|
||||
max_memory_mb=None,
|
||||
)
|
||||
|
||||
if not self.attach_to_existing:
|
||||
await asyncio.to_thread(self.setup_initial_env)
|
||||
|
||||
@@ -241,6 +290,40 @@ class CLIRuntime(Runtime):
|
||||
except Exception as e:
|
||||
logger.error(f'Error: {e}')
|
||||
|
||||
def _execute_powershell_command(
|
||||
self, command: str, timeout: float
|
||||
) -> CmdOutputObservation | ErrorObservation:
|
||||
"""
|
||||
Execute a command using PowerShell session on Windows.
|
||||
Args:
|
||||
command: The command to execute
|
||||
timeout: Timeout in seconds for the command
|
||||
Returns:
|
||||
CmdOutputObservation containing the complete output and exit code
|
||||
"""
|
||||
if self._powershell_session is None:
|
||||
return ErrorObservation(
|
||||
content='PowerShell session is not available.',
|
||||
error_id='POWERSHELL_SESSION_ERROR',
|
||||
)
|
||||
|
||||
try:
|
||||
# Create a CmdRunAction for the PowerShell session
|
||||
from openhands.events.action import CmdRunAction
|
||||
|
||||
ps_action = CmdRunAction(command=command)
|
||||
ps_action.set_hard_timeout(timeout)
|
||||
|
||||
# Execute the command using the PowerShell session
|
||||
return self._powershell_session.execute(ps_action)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Error executing PowerShell command "{command}": {e}')
|
||||
return ErrorObservation(
|
||||
content=f'Error executing PowerShell command "{command}": {str(e)}',
|
||||
error_id='POWERSHELL_EXECUTION_ERROR',
|
||||
)
|
||||
|
||||
def _execute_shell_command(
|
||||
self, command: str, timeout: float
|
||||
) -> CmdOutputObservation:
|
||||
@@ -378,9 +461,16 @@ class CLIRuntime(Runtime):
|
||||
logger.debug(
|
||||
f'Running command in CLIRuntime: "{action.command}" with effective timeout: {effective_timeout}s'
|
||||
)
|
||||
return self._execute_shell_command(
|
||||
action.command, timeout=effective_timeout
|
||||
)
|
||||
|
||||
# Use PowerShell on Windows if available, otherwise use subprocess
|
||||
if self._is_windows and self._powershell_session is not None:
|
||||
return self._execute_powershell_command(
|
||||
action.command, timeout=effective_timeout
|
||||
)
|
||||
else:
|
||||
return self._execute_shell_command(
|
||||
action.command, timeout=effective_timeout
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f'Error in CLIRuntime.run for command "{action.command}": {str(e)}'
|
||||
@@ -737,6 +827,16 @@ class CLIRuntime(Runtime):
|
||||
raise RuntimeError(f'Error creating zip file: {str(e)}')
|
||||
|
||||
def close(self) -> None:
|
||||
# Clean up PowerShell session if it exists
|
||||
if self._powershell_session is not None:
|
||||
try:
|
||||
self._powershell_session.close()
|
||||
logger.debug('PowerShell session closed successfully.')
|
||||
except Exception as e:
|
||||
logger.warning(f'Error closing PowerShell session: {e}')
|
||||
finally:
|
||||
self._powershell_session = None
|
||||
|
||||
self._runtime_initialized = False
|
||||
super().close()
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Two configuration options are required to use the Kubernetes runtime:
|
||||
2. **Runtime Container Image**: Specify the container image to use for the runtime environment
|
||||
```toml
|
||||
[sandbox]
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.44-nikolaik"
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.45-nikolaik"
|
||||
```
|
||||
|
||||
#### Additional Kubernetes Options
|
||||
|
||||
@@ -21,21 +21,31 @@ from openhands.events.observation.commands import (
|
||||
CmdOutputObservation,
|
||||
)
|
||||
from openhands.runtime.utils.bash_constants import TIMEOUT_MESSAGE_TEMPLATE
|
||||
from openhands.runtime.utils.windows_exceptions import DotNetMissingError
|
||||
from openhands.utils.shutdown_listener import should_continue
|
||||
|
||||
pythonnet.load('coreclr')
|
||||
logger.info("Successfully called pythonnet.load('coreclr')")
|
||||
|
||||
# Now that pythonnet is initialized, import clr and System
|
||||
try:
|
||||
import clr
|
||||
pythonnet.load('coreclr')
|
||||
logger.info("Successfully called pythonnet.load('coreclr')")
|
||||
|
||||
logger.debug(f'Imported clr module from: {clr.__file__}')
|
||||
# Load System assembly *after* pythonnet is initialized
|
||||
clr.AddReference('System')
|
||||
import System
|
||||
except Exception as clr_sys_ex:
|
||||
raise RuntimeError(f'FATAL: Failed to import clr or System. Error: {clr_sys_ex}')
|
||||
# Now that pythonnet is initialized, import clr and System
|
||||
try:
|
||||
import clr
|
||||
|
||||
logger.debug(f'Imported clr module from: {clr.__file__}')
|
||||
# Load System assembly *after* pythonnet is initialized
|
||||
clr.AddReference('System')
|
||||
import System
|
||||
except Exception as clr_sys_ex:
|
||||
error_msg = 'Failed to import .NET components.'
|
||||
details = str(clr_sys_ex)
|
||||
logger.error(f'{error_msg} Details: {details}')
|
||||
raise DotNetMissingError(error_msg, details)
|
||||
except Exception as coreclr_ex:
|
||||
error_msg = 'Failed to load CoreCLR.'
|
||||
details = str(coreclr_ex)
|
||||
logger.error(f'{error_msg} Details: {details}')
|
||||
raise DotNetMissingError(error_msg, details)
|
||||
|
||||
# Attempt to load the PowerShell SDK assembly only if clr and System loaded
|
||||
ps_sdk_path = None
|
||||
@@ -78,9 +88,10 @@ try:
|
||||
RunspaceState,
|
||||
)
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
f'FATAL: Failed to load PowerShell SDK components. Error: {e}. Check pythonnet installation and .NET Runtime compatibility. Path searched: {ps_sdk_path}'
|
||||
)
|
||||
error_msg = 'Failed to load PowerShell SDK components.'
|
||||
details = f'{str(e)} (Path searched: {ps_sdk_path})'
|
||||
logger.error(f'{error_msg} Details: {details}')
|
||||
raise DotNetMissingError(error_msg, details)
|
||||
|
||||
|
||||
class WindowsPowershellSession:
|
||||
@@ -115,9 +126,11 @@ class WindowsPowershellSession:
|
||||
|
||||
if PowerShell is None: # Check if SDK loading failed during module import
|
||||
# Logged critical error during import, just raise here to prevent instantiation
|
||||
raise RuntimeError(
|
||||
'PowerShell SDK (System.Management.Automation.dll) could not be loaded. Cannot initialize WindowsPowershellSession.'
|
||||
error_msg = (
|
||||
'PowerShell SDK (System.Management.Automation.dll) could not be loaded.'
|
||||
)
|
||||
logger.error(error_msg)
|
||||
raise DotNetMissingError(error_msg)
|
||||
|
||||
self.work_dir = os.path.abspath(work_dir)
|
||||
self.username = username
|
||||
|
||||
15
openhands/runtime/utils/windows_exceptions.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Custom exceptions for Windows-specific runtime issues.
|
||||
"""
|
||||
|
||||
|
||||
class DotNetMissingError(Exception):
|
||||
"""
|
||||
Exception raised when .NET SDK or CoreCLR is missing or cannot be loaded.
|
||||
This is used to provide a cleaner error message to users without a full stack trace.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, details: str | None = None):
|
||||
self.message = message
|
||||
self.details = details
|
||||
super().__init__(message)
|
||||
@@ -107,6 +107,10 @@ class ConversationManager(ABC):
|
||||
async def send_to_event_stream(self, connection_id: str, data: dict):
|
||||
"""Send data to an event stream."""
|
||||
|
||||
@abstractmethod
|
||||
async def send_event_to_conversation(self, sid: str, data: dict):
|
||||
"""Send an event to a conversation."""
|
||||
|
||||
@abstractmethod
|
||||
async def disconnect_from_session(self, connection_id: str):
|
||||
"""Disconnect from a session."""
|
||||
|
||||