Compare commits

..

117 Commits

Author SHA1 Message Date
Robert Brennan 9ef79cbe22 fix advanced settings issue (#3849) 2024-09-12 22:00:31 +00:00
Robert Brennan 1f13d80ddc fix saves (#3848) 2024-09-12 21:47:02 +00:00
Robert Brennan 58de5221f5 fix file access (#3847) 2024-09-12 15:30:21 -04:00
Robert Brennan 9bbb35ec18 minor settings fixes (#3809)
* minor settings fixes

* useMemo

* fix code
2024-09-12 19:08:18 +00:00
Xingyao Wang 797f02ff6f rename huggingface evaluation benchmark (#3845) 2024-09-12 18:50:26 +00:00
Xingyao Wang 47d9621742 [eval] SWE-Bench eval usability fixes (#3836)
* [eval] increase timeout for swebench eval init/complete

* allow CmdRunAction to optionally block when .timeout is setted

* fix unit test for serialization

* fix unit tests for security analyzer

* fix integration tests

* add more timeout

* only check P2P when instances are non-empty;
convert P2P and F2P columns to string instead of list

---------

Co-authored-by: Graham Neubig <neubig@gmail.com>
2024-09-12 16:33:51 +00:00
dependabot[bot] 8c12f5b67d chore(deps-dev): bump tailwindcss from 3.4.10 to 3.4.11 in /frontend (#3838)
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.10 to 3.4.11.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.11/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.10...v3.4.11)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 11:38:21 -04:00
dependabot[bot] 6132a58367 chore(deps-dev): bump husky from 9.1.5 to 9.1.6 in /frontend (#3839)
Bumps [husky](https://github.com/typicode/husky) from 9.1.5 to 9.1.6.
- [Release notes](https://github.com/typicode/husky/releases)
- [Commits](https://github.com/typicode/husky/compare/v9.1.5...v9.1.6)

---
updated-dependencies:
- dependency-name: husky
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 11:38:15 -04:00
Xingyao Wang 2fe2f4c530 [eval] increase timeout for SWEBench eval init/complete (#3829)
* [eval] increase timeout for swebench eval init/complete

* allow CmdRunAction to optionally block when .timeout is setted

* fix unit test for serialization

* fix unit tests for security analyzer

* fix integration tests

* add more timeout
2024-09-12 15:20:58 +00:00
tofarr 4d8f812f88 chore: update the icons from OpenDevin to OpenHands (#3835)
* chore: update the icons from OpenDevin to OpenHands

* Update logo
2024-09-12 14:19:31 +00:00
tofarr e5cb80d59d docs: Update steps for running integration tests in a local environment (#3830)
* docs: Update steps for running integration tests in a local environment
2024-09-12 03:22:53 -06:00
sp.wack ee9bea393f feat(frontend): Update settings to include base URL (#3755)
* Update settings to include base URL

* Fix tests
2024-09-11 21:57:19 -04:00
dependabot[bot] 940de86caa chore(deps): bump browsergym from 0.4.3 to 0.5.1 (#3833)
Bumps browsergym from 0.4.3 to 0.5.1.

---
updated-dependencies:
- dependency-name: browsergym
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2024-09-12 03:55:21 +02:00
Frank Xu fe5ecb6da8 add url info in browsing observation (#3815)
* add url info in browsing observation

* fix integration tests for url

---------

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2024-09-12 02:46:39 +02:00
mamoodi 41b8f3e4a7 Fail the Runtime tests check if the previous jobs were cancelled or failed (#3825)
* Fail the Runtime tests check if the previous jobs were cancelled or failed

* Check only for failures

* Add test exit

* Test cancel

* fix conditions

* fix condition again

* Try another way

* Now test success
2024-09-11 16:40:00 -04:00
dependabot[bot] 93ecd82e61 chore(deps): bump google-cloud-aiplatform from 1.65.0 to 1.66.0 (#3828)
Bumps [google-cloud-aiplatform](https://github.com/googleapis/python-aiplatform) from 1.65.0 to 1.66.0.
- [Release notes](https://github.com/googleapis/python-aiplatform/releases)
- [Changelog](https://github.com/googleapis/python-aiplatform/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-aiplatform/compare/v1.65.0...v1.66.0)

---
updated-dependencies:
- dependency-name: google-cloud-aiplatform
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 18:52:35 +02:00
dependabot[bot] 052149ccf3 chore(deps-dev): bump llama-index from 0.11.6 to 0.11.8 (#3827)
Bumps [llama-index](https://github.com/run-llama/llama_index) from 0.11.6 to 0.11.8.
- [Release notes](https://github.com/run-llama/llama_index/releases)
- [Changelog](https://github.com/run-llama/llama_index/blob/main/CHANGELOG.md)
- [Commits](https://github.com/run-llama/llama_index/compare/v0.11.6...v0.11.8)

---
updated-dependencies:
- dependency-name: llama-index
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 18:51:05 +02:00
dependabot[bot] 3aba722a59 chore(deps-dev): bump evaluate from 0.4.2 to 0.4.3 (#3818)
Bumps [evaluate](https://github.com/huggingface/evaluate) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/huggingface/evaluate/releases)
- [Commits](https://github.com/huggingface/evaluate/compare/v0.4.2...v0.4.3)

---
updated-dependencies:
- dependency-name: evaluate
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 18:47:22 +02:00
dependabot[bot] 4738640f4e chore(deps): bump datasets from 2.21.0 to 3.0.0 (#3821)
Bumps [datasets](https://github.com/huggingface/datasets) from 2.21.0 to 3.0.0.
- [Release notes](https://github.com/huggingface/datasets/releases)
- [Commits](https://github.com/huggingface/datasets/compare/2.21.0...3.0.0)

---
updated-dependencies:
- dependency-name: datasets
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 18:46:23 +02:00
dependabot[bot] bf6f7b9943 chore(deps): bump google-generativeai from 0.7.2 to 0.8.0 (#3822)
Bumps [google-generativeai](https://github.com/google/generative-ai-python) from 0.7.2 to 0.8.0.
- [Release notes](https://github.com/google/generative-ai-python/releases)
- [Changelog](https://github.com/google-gemini/generative-ai-python/blob/main/RELEASE.md)
- [Commits](https://github.com/google/generative-ai-python/compare/v0.7.2...v0.8.0)

---
updated-dependencies:
- dependency-name: google-generativeai
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 18:35:33 +02:00
dependabot[bot] 0f20965999 chore(deps): bump fastapi from 0.114.0 to 0.114.1 (#3823)
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.114.0 to 0.114.1.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.114.0...0.114.1)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 16:21:08 +00:00
dependabot[bot] e6034b301b chore(deps): bump vite from 5.4.3 to 5.4.4 in /frontend (#3817)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.3 to 5.4.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 19:19:49 +03:00
dependabot[bot] 9e50b88f9f chore(deps): bump litellm from 1.44.23 to 1.44.24 (#3826)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.44.23 to 1.44.24.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/compare/v1.44.23...v1.44.24)

---
updated-dependencies:
- dependency-name: litellm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 16:10:43 +00:00
dependabot[bot] fd0ecdd4e0 chore(deps): bump boto3 from 1.35.15 to 1.35.16 (#3824)
Bumps [boto3](https://github.com/boto/boto3) from 1.35.15 to 1.35.16.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.35.15...1.35.16)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 16:08:19 +00:00
Robert Brennan c6105f264f Improvements to file list UI (#3794)
* move filematching logic into server

* wait until ready before returning

* show loading message instead of empty

* logspam

* delint

* fix type

* add a few more default ignores
2024-09-11 09:44:37 -04:00
Engel Nyst 93f271579c Revert "chore(deps): bump numpy from 1.26.4 to 2.1.1 (#3713)" (#3816)
This reverts commit 5100d12cea.
2024-09-11 12:29:32 +02:00
Boxuan Li 1976763152 Fix wrong mention of Devin in localLLMs.md (#3814) 2024-09-11 07:49:33 +02:00
Engel Nyst 516ca701d4 Update local-llms.md (#3811) 2024-09-11 01:08:20 +02:00
mamoodi f3b2085f9b Reduce runtime tests duration by running them across CPUs (#3779)
* Reduce runtime tests duration by running them across CPUs

* fix hardcoded image name

* test two cpus

* Test folder change

* Up the CPU to 4 again to test

* Change to 3 CPUs

* Down to 2

* Add param to remove all openhands containers

* Add comment

* Add reruns just in case

* Fix ordering of if
2024-09-10 14:31:17 -04:00
Cole Murray 97a03faf33 Add Handling of Cache Prompt When Formatting Messages (#3773)
* Add Handling of Cache Prompt When Formatting Messages

* Fix Value for Cache Control

* Fix Value for Cache Control

* Update openhands/core/message.py

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>

* Fix lint error

* Serialize Messages if Propt Caching Is Enabled

* Remove formatting message change

---------

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Co-authored-by: tobitege <10787084+tobitege@users.noreply.github.com>
2024-09-10 16:34:41 +00:00
dependabot[bot] 06ed142191 chore(deps): bump json-repair from 0.29.1 to 0.29.2 (#3800)
Bumps [json-repair](https://github.com/mangiucugna/json_repair) from 0.29.1 to 0.29.2.
- [Release notes](https://github.com/mangiucugna/json_repair/releases)
- [Commits](https://github.com/mangiucugna/json_repair/compare/v0.29.1...v0.29.2)

---
updated-dependencies:
- dependency-name: json-repair
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 16:18:36 +00:00
dependabot[bot] dbd0786345 chore(deps-dev): bump pytest from 8.3.2 to 8.3.3 (#3801)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.2 to 8.3.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.2...8.3.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 16:17:30 +00:00
dependabot[bot] 3bc9a485b8 chore(deps-dev): bump openai from 1.44.0 to 1.44.1 (#3802)
Bumps [openai](https://github.com/openai/openai-python) from 1.44.0 to 1.44.1.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-python/compare/v1.44.0...v1.44.1)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 16:16:32 +00:00
dependabot[bot] 2b845d9568 chore(deps): bump i18next from 23.15.0 to 23.15.1 in /frontend (#3805)
Bumps [i18next](https://github.com/i18next/i18next) from 23.15.0 to 23.15.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.15.0...v23.15.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 23:57:59 +08:00
dependabot[bot] d7e0db0b35 chore(deps-dev): bump typescript from 5.5.4 to 5.6.2 in /frontend (#3804)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.5.4 to 5.6.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.5.4...v5.6.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 23:57:49 +08:00
dependabot[bot] 508681691a chore(deps-dev): bump typescript from 5.5.4 to 5.6.2 in /docs (#3806)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.5.4 to 5.6.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.5.4...v5.6.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 23:57:41 +08:00
tobitege 1c9b4ad78a fix regression in fork artifact upload in ghcr_runtime (#3807) 2024-09-10 17:36:36 +02:00
dependabot[bot] ecb1b9b2a0 chore(deps): bump boto3 from 1.35.14 to 1.35.15 (#3798)
Bumps [boto3](https://github.com/boto/boto3) from 1.35.14 to 1.35.15.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.35.14...1.35.15)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 17:29:56 +02:00
dependabot[bot] e4f8708656 chore(deps): bump litellm from 1.44.22 to 1.44.23 (#3799)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.44.22 to 1.44.23.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/compare/v1.44.22...v1.44.23)

---
updated-dependencies:
- dependency-name: litellm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 17:25:41 +02:00
dependabot[bot] 822de89394 chore(deps): bump browsergym from 0.3.4 to 0.4.3 (#3762)
* chore(deps): bump browsergym from 0.3.4 to 0.4.3

Bumps browsergym from 0.3.4 to 0.4.3.

---
updated-dependencies:
- dependency-name: browsergym
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* integration tests updated to browsergym

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2024-09-10 17:24:40 +02:00
Robert Brennan c5e89be6de fix runtime tags (#3790) 2024-09-09 20:03:48 +00:00
dependabot[bot] 386688da5f chore(deps): bump fastapi from 0.113.0 to 0.114.0 (#3783)
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.113.0 to 0.114.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.113.0...0.114.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 15:23:34 -04:00
dependabot[bot] 6c01d25976 chore(deps): bump boto3 from 1.35.13 to 1.35.14 (#3784)
Bumps [boto3](https://github.com/boto/boto3) from 1.35.13 to 1.35.14.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.35.13...1.35.14)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 15:23:26 -04:00
dependabot[bot] 05e2b0c352 chore(deps-dev): bump openai from 1.43.1 to 1.44.0 (#3787)
Bumps [openai](https://github.com/openai/openai-python) from 1.43.1 to 1.44.0.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-python/compare/v1.43.1...v1.44.0)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 15:23:16 -04:00
dependabot[bot] 5066468c36 chore(deps-dev): bump build from 1.2.1 to 1.2.2 (#3785)
Bumps [build](https://github.com/pypa/build) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/1.2.1...1.2.2)

---
updated-dependencies:
- dependency-name: build
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 15:23:04 -04:00
dependabot[bot] 9349009074 chore(deps): bump litellm from 1.44.19 to 1.44.22 (#3786)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.44.19 to 1.44.22.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/compare/v1.44.19...v1.44.22)

---
updated-dependencies:
- dependency-name: litellm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 15:22:52 -04:00
tobitege 5ffff742de Regression fixes: LLM logging; client readiness (EventStreamRuntime) (#3776)
* Regression fixes: LLM logging; client readiness (EventStreamRuntime)

* fix llm.async_completion_wrapper bad edit in previous commit

* regen couple of mock files

* client: always log initialized status
2024-09-09 21:02:43 +02:00
dependabot[bot] 50dc17c65c chore(deps): bump i18next from 23.14.0 to 23.15.0 in /frontend (#3788)
Bumps [i18next](https://github.com/i18next/i18next) from 23.14.0 to 23.15.0.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.14.0...v23.15.0)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 19:31:27 +03:00
Robert Brennan f4d6b3262d fix runtime image (#3782) 2024-09-09 09:55:48 -04:00
Robert Brennan cb3168da12 Redesign settings modal (#3751)
* add advanced options switch

* remove logic for custom models

* remove extra span

* consolidate row

* no more toast

* remove default subtitle

* fix title tags

* remove getSettingsDifference

* delint

* delint

* fix select buttons

* delete test

* fix test

* fix test

* fix labels

* remove model-id tests

* fix labels

* fix labels

* rename var

* fix test

* fix more tests

* change some mocks

* more fixes

* one more fix

* remove some mocks

* rename things

* one more test

* ahhhhh

* change required settings

* fix test

* delint

* fix build
2024-09-09 09:02:58 -04:00
tobitege 2b7517e542 (enh) add caching@v4 action in workflows (#3780)
* dummy test change

* regen yml: 1st install python 3.11, then poetry

* fix caching for poetry; old entry for python was rather useless

* fix steps order (cache before poetry)

* add poetry caching to ghcr_runtime; fix fork conditions

* ghcr_runtime: more caching actions; condition fixes

* fix interim action error (order of steps)

* cache@v4 instead of v3

* fixed interim typo for 2 fork conditions

* runtime/test_env_vars: compacted multiple tests into one to reduce time

* ugh if fork condition changes again
2024-09-09 10:49:49 +02:00
Cole Murray dadada18ce Add Anthropic Models to Cache Prompt (#3775)
* Add Anthropic Models to Cache Prompt

* Update Cache Prompt Active Check for Partial String Matching
2024-09-08 22:09:14 +00:00
Robert Brennan ab3851593d Support interactive commands (#3653)
* hacky solution for interactive commands

* add more behavior

* debug

* fix continue functionality

* remove prints

* refactor a bit

* reduce test sleep

* fix python version

* fix pre-commit issue

* Regenerate integration tests

* Update openhands/runtime/client/client.py

* revert some prompt stuff

* several integration mock files regenerated

* execute_action: remove duplicate exception logging

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: tobitege <10787084+tobitege@users.noreply.github.com>
2024-09-08 21:45:51 +02:00
dependabot[bot] 5100d12cea chore(deps): bump numpy from 1.26.4 to 2.1.1 (#3713)
Bumps [numpy](https://github.com/numpy/numpy) from 1.26.4 to 2.1.1.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v1.26.4...v2.1.1)

---
updated-dependencies:
- dependency-name: numpy
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 15:52:27 -04:00
mamoodi a9cf7e6ee6 docs: fix cli and headless commands (#3768) 2024-09-06 15:52:11 -04:00
sp.wack 8392a3fb6b Remove unwanted (large) keys when sharing feedback (#3766) 2024-09-06 22:45:18 +03:00
dependabot[bot] c376b81505 chore(deps): bump fastapi from 0.112.3 to 0.113.0 (#3760)
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.112.3 to 0.113.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.112.3...0.113.0)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 19:33:12 +02:00
dependabot[bot] 8440604dd1 chore(deps-dev): bump ruff from 0.6.3 to 0.6.4 (#3763)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.3 to 0.6.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.3...0.6.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 19:31:58 +02:00
dependabot[bot] a4d75cd190 chore(deps-dev): bump llama-index from 0.11.5 to 0.11.6 (#3761)
Bumps [llama-index](https://github.com/run-llama/llama_index) from 0.11.5 to 0.11.6.
- [Release notes](https://github.com/run-llama/llama_index/releases)
- [Changelog](https://github.com/run-llama/llama_index/blob/main/CHANGELOG.md)
- [Commits](https://github.com/run-llama/llama_index/compare/v0.11.5...v0.11.6)

---
updated-dependencies:
- dependency-name: llama-index
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 19:31:01 +02:00
dependabot[bot] 4db929b986 chore(deps): bump litellm from 1.44.17 to 1.44.19 (#3764)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.44.17 to 1.44.19.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/compare/v1.44.17...v1.44.19)

---
updated-dependencies:
- dependency-name: litellm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 19:30:20 +02:00
dependabot[bot] b6b38fcd37 chore(deps-dev): bump openai from 1.43.0 to 1.43.1 (#3765)
Bumps [openai](https://github.com/openai/openai-python) from 1.43.0 to 1.43.1.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Changelog](https://github.com/openai/openai-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-python/compare/v1.43.0...v1.43.1)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 19:29:02 +02:00
dependabot[bot] d619be96d1 chore(deps): bump boto3 from 1.35.12 to 1.35.13 (#3759)
Bumps [boto3](https://github.com/boto/boto3) from 1.35.12 to 1.35.13.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.35.12...1.35.13)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 18:09:36 +02:00
dependabot[bot] 6f44ea0115 chore(deps): bump json-repair from 0.28.4 to 0.29.1 (#3745)
Bumps [json-repair](https://github.com/mangiucugna/json_repair) from 0.28.4 to 0.29.1.
- [Release notes](https://github.com/mangiucugna/json_repair/releases)
- [Commits](https://github.com/mangiucugna/json_repair/compare/v0.28.4...v0.29.1)

---
updated-dependencies:
- dependency-name: json-repair
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 18:08:17 +02:00
Robert Brennan 0f118df910 Docs: fix "architecture" and "llms" sections (#3749)
* fix llm docs

* fix architecture
2024-09-06 09:51:45 -04:00
Jiayi Pan 43c4a7fff4 Allow Generalized SWE-Bench format for evaluation (#3752)
* allow generalized swe-bench format

* Update run_infer.py

* fix linter

---------

Co-authored-by: Xingyao Wang <xingyao6@illinois.edu>
2024-09-06 13:05:00 +00:00
tobitege 57187417b7 revert enabling litellm verbose mode from testing (#3750) 2024-09-05 20:12:04 +00:00
tobitege 03b5b03bb2 (enh) CodeActAgent: improve logging; sensible retry defaults in config (#3729)
* CodeActAgent: improve logging; sensible retry defaults for completion errors

* CodeActAgent: reduce completion error message sent to UI

* tweak values; docs+config template changes

* fix format_messages; log exception in codeactagent again
2024-09-05 18:14:15 +00:00
dependabot[bot] 681276f27c chore(deps): bump google-cloud-aiplatform from 1.64.0 to 1.65.0 (#3747)
Bumps [google-cloud-aiplatform](https://github.com/googleapis/python-aiplatform) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/googleapis/python-aiplatform/releases)
- [Changelog](https://github.com/googleapis/python-aiplatform/blob/main/CHANGELOG.md)
- [Commits](https://github.com/googleapis/python-aiplatform/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: google-cloud-aiplatform
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 13:31:15 -04:00
niliy01 82a154f7e7 (feat) making prompt caching optional instead of enabled default (#3689)
* (feat) making prompt caching optional instead of enabled default

At present, only the Claude models support prompt caching as a experimental feature, therefore, this feature should be implemented as an optional setting rather than being enabled by default.

Signed-off-by: Yi Lin <teroincn@gmail.com>

* handle the conflict

* fix unittest mock return value

* fix lint error in whitespace

---------

Signed-off-by: Yi Lin <teroincn@gmail.com>
2024-09-05 18:52:26 +02:00
dependabot[bot] 5b7ab28511 chore(deps): bump vite from 5.4.2 to 5.4.3 in /frontend (#3721)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.2 to 5.4.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:54:09 -04:00
dependabot[bot] ca3f39e918 chore(deps-dev): bump postcss from 8.4.44 to 8.4.45 in /frontend (#3722)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.44 to 8.4.45.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.44...8.4.45)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:53:50 -04:00
dependabot[bot] 6c2630e506 chore(deps-dev): bump @types/node from 22.5.2 to 22.5.4 in /frontend (#3740)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.5.2 to 22.5.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:53:38 -04:00
dependabot[bot] 260e41486e chore(deps-dev): bump llama-index from 0.11.4 to 0.11.5 (#3741)
Bumps [llama-index](https://github.com/run-llama/llama_index) from 0.11.4 to 0.11.5.
- [Release notes](https://github.com/run-llama/llama_index/releases)
- [Changelog](https://github.com/run-llama/llama_index/blob/main/CHANGELOG.md)
- [Commits](https://github.com/run-llama/llama_index/compare/v0.11.4...v0.11.5)

---
updated-dependencies:
- dependency-name: llama-index
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:53:28 -04:00
dependabot[bot] 9b0fb8f81a chore(deps): bump boto3 from 1.35.11 to 1.35.12 (#3742)
Bumps [boto3](https://github.com/boto/boto3) from 1.35.11 to 1.35.12.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.35.11...1.35.12)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:53:22 -04:00
dependabot[bot] cd360ef6aa chore(deps): bump litellm from 1.44.15 to 1.44.17 (#3743)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.44.15 to 1.44.17.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/compare/v1.44.15...v1.44.17)

---
updated-dependencies:
- dependency-name: litellm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:53:15 -04:00
dependabot[bot] 5bb46525a4 chore(deps): bump fastapi from 0.112.2 to 0.112.3 (#3744)
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.112.2 to 0.112.3.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.112.2...0.112.3)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:53:08 -04:00
dependabot[bot] f80e4a9e5d chore(deps): bump anthropic from 0.34.1 to 0.34.2 (#3746)
Bumps [anthropic](https://github.com/anthropics/anthropic-sdk-python) from 0.34.1 to 0.34.2.
- [Release notes](https://github.com/anthropics/anthropic-sdk-python/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-python/compare/v0.34.1...v0.34.2)

---
updated-dependencies:
- dependency-name: anthropic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-05 11:52:59 -04:00
mamoodi 60c5fd41ec Update docs on LLM providers for consistency (#3738)
* Update docs on LLM providers for consistency

* Update headless command

* minor tweaks based on feedback

---------

Co-authored-by: Robert Brennan <contact@rbren.io>
Co-authored-by: Robert Brennan <accounts@rbren.io>
2024-09-05 15:10:28 +00:00
Xingyao Wang 688068a44e Fix issues for running RemoteRuntime in parallel on SWE-Bench (#3716)
* feat: add SWE-bench fullset support

* fix instance image list

* update eval script and documentation

* increase timeout for remote runtime

* add push script

* handle the case when ret push is an generator

* update pbar

* set SWE-Bench default to run SWE-Bench lite

* add script to cleanup remote runtime

* fix the cases when tag is too long

* update README

* update readme for cleanup

* rename od to oh

* Update evaluation/swe_bench/README.md

Co-authored-by: Graham Neubig <neubig@gmail.com>

* Update evaluation/swe_bench/README.md

Co-authored-by: Graham Neubig <neubig@gmail.com>

* Update evaluation/swe_bench/scripts/cleanup_remote_runtime.sh

Co-authored-by: Graham Neubig <neubig@gmail.com>

* Update evaluation/swe_bench/scripts/cleanup_remote_runtime.sh

Co-authored-by: Graham Neubig <neubig@gmail.com>

* Update evaluation/swe_bench/scripts/cleanup_remote_runtime.sh

Co-authored-by: Graham Neubig <neubig@gmail.com>

* gets API key and Runtime from env var

---------

Co-authored-by: Graham Neubig <neubig@gmail.com>
2024-09-05 10:34:31 +08:00
Robert Brennan ee158feb15 Documentation updates (#3733)
* update badges

* fix badges

* better badges

* move credits

* more badge work

* add gh logo

* update some copy

* update logo

* fix height

* update text

* emdash

* remove cruft

* move title

* update links

* add hr

* white logo

* move some stuff to getting-started

* revert logo

* more copy changes

* minor tweaks

* fix sidebar

* explicit sidebar

* words

* fix tag

* fix how-to

* more docs work

* update styles

* fix up custom sandbox docs

* change eval title

* fix up getting-started

* fix getting started

* update to 0.9.2

* update screenshot

* add company link

* fix dark mode

* minor fixes

* update image

* update headless and cli docs

* update readme

* fix links

* revert package

* rename links

* fix links

* fix link

* chagne to claude
2024-09-04 21:22:52 +00:00
dependabot[bot] ec25abd98b chore(deps): bump boto3 from 1.35.10 to 1.35.11 (#3724)
Bumps [boto3](https://github.com/boto/boto3) from 1.35.10 to 1.35.11.
- [Release notes](https://github.com/boto/boto3/releases)
- [Commits](https://github.com/boto/boto3/compare/1.35.10...1.35.11)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: tofarr <tofarr@gmail.com>
2024-09-04 12:49:11 -07:00
leo bb28dea51f Add documentation for CLI mode (#3706)
* Add documentation for CLI mode

Fixes #3703

Add documentation for CLI mode in OpenHands.

* **New Documentation**: Add `docs/modules/usage/how-to/cli-mode.md` to document CLI mode.
  - Include instructions on starting an interactive OpenHands session via the command line.
  - Explain the difference between CLI mode and headless mode.
  - Provide examples of CLI commands and expected outputs.
* **Update Existing Documentation**: Modify `docs/modules/usage/how-to/headless-mode.md`.
  - Clarify the difference between headless mode and CLI mode.
  - Add a reference to the new CLI mode documentation.

* Update cli-mode.md

* Update headless-mode.md

---------

Co-authored-by: Robert Brennan <accounts@rbren.io>
Co-authored-by: tofarr <tofarr@gmail.com>
2024-09-04 15:38:57 -04:00
Robert Brennan 2a99aa6679 Cosmetic README updates (#3728)
* update badges

* fix badges

* better badges

* move credits

* more badge work

* add gh logo

* update some copy

* update logo

* fix height

* update text

* emdash

* remove cruft

* move title

* update links

* add hr

* white logo

* move some stuff to getting-started

* revert logo

* more copy changes

* minor tweaks

* words

* fix getting started

* update to 0.9.2

* update screenshot

* minor fixes
2024-09-04 19:38:28 +00:00
mamoodi add4653335 Release 0.9.2 (#3727) 2024-09-04 14:31:54 -04:00
Robert Brennan 2557c18fb1 specify runtime image (#3726) 2024-09-04 12:03:26 -04:00
tobitege bc31fb15fe (fix) CodeActAgent: fix issues with vision support in prompts (#3665)
* CodeActAgent: fix message prep if prompt caching is not supported

* fix python version in regen tests workflow

* fix in conftest "mock_completion" method

* add disable_vision to LLMConfig; revert change in message parsing in llm.py

* format messages in several files for completion

* refactored message(s) formatting (llm.py); added vision_is_active()

* fix a unit test

* regenerate: added LOG_TO_FILE and FORCE_REGENERATE env flags

* try to fix path to logs folder in workflow

* llm: prevent index error

* try FORCE_USE_LLM in regenerate

* tweaks everywhere...

* fix 2 random unit test errors :(

* added FORCE_REGENERATE_TESTS=true to regenerate CLI

* fix test_lint_file_fail_typescript again

* double-quotes for env vars in workflow; llm logger set to debug

* fix typo in regenerate

* regenerate iterations now 20; applied iteration counter fix by Li

* regenerate: pass FORCE_REGENERATE flag into env

* fixes for int tests. several mock files updated.

* browsing_agent: fix response_parser.py adding ) to empty response

* test_browse_internet: fix skipif and revert obsolete mock files

* regenerate: fi bracketing for http server start/kill conditions

* disable test_browse_internet for CodeAct*Agents; mock files updated after merge

* missed to include more mock files earlier

* reverts after review feedback from Li

* forgot one

* browsing agent test, partial fixes and updated mock files

* test_browse_internet works in my WSL now!

* adapt unit test test_prompt_caching.py

* add DEBUG to regenerate workflow command

* convert regenerate workflow params to inputs

* more integration test mock files updated

* more files

* test_prompt_caching: restored test_prompt_caching_headers purpose

* file_ops: fix potential exception, like "cross device copy"; fixed mock files accordingly

* reverts/changes wrt feedback from xingyao

* updated docs and config template

* code cleanup wrt review feedback
2024-09-04 17:58:30 +02:00
mamoodi 1b66f2e777 Revert "Create a reusable workflow for building, testing and publishing runti…" (#3725)
This reverts commit d1a741792f.
2024-09-04 11:47:01 -04:00
mamoodi d1a741792f Create a reusable workflow for building, testing and publishing runtime images and use it (#3717)
* Test resuable workflow

* fix path for reusable workflow

* Fix workflow typo

* fix reusable workflow

* input typo

* Add secrets to reusable workflow

* Make token required

* Fix secret indentation

* Fix image with tag
2024-09-04 08:58:59 -04:00
Shubham raj 2bc3e8d584 Fix: llm completion exception breaks CodeActAgent (#3678)
* Catch exception and return finish action with an exception message in case of exception in llm completion

* Remove exception logs

* Raise llm response error for any exception in llm completion

* Raise LLMResponseError from async completion and async streaming completion as well
2024-09-04 05:51:49 +02:00
sp.wack 0bb0903a22 Fix anthropic providers pointing to the correct model ID (#3715) 2024-09-03 18:17:07 +00:00
dependabot[bot] dd3a701b93 chore(deps): bump litellm from 1.44.14 to 1.44.15 (#3712)
Bumps [litellm](https://github.com/BerriAI/litellm) from 1.44.14 to 1.44.15.
- [Release notes](https://github.com/BerriAI/litellm/releases)
- [Commits](https://github.com/BerriAI/litellm/commits)

---
updated-dependencies:
- dependency-name: litellm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-03 10:57:29 -07:00
dependabot[bot] d4e6ea5e49 chore(deps-dev): bump llama-index from 0.11.2 to 0.11.4 (#3711)
Bumps [llama-index](https://github.com/run-llama/llama_index) from 0.11.2 to 0.11.4.
- [Release notes](https://github.com/run-llama/llama_index/releases)
- [Changelog](https://github.com/run-llama/llama_index/blob/main/CHANGELOG.md)
- [Commits](https://github.com/run-llama/llama_index/compare/v0.11.2...v0.11.4)

---
updated-dependencies:
- dependency-name: llama-index
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: yufansong <yufan@risingwave-labs.com>
2024-09-03 10:57:12 -07:00
mamoodi 510a82a039 Verified list of LLMs working with OpenHands and some LLM doc changes (#3714)
* Verified list of LLMs working with OpenHands and some LLM doc changes

* Update list of verified

* Add warning for ollama guide
2024-09-03 13:14:38 -04:00
Xingyao Wang d8a87d7ccb [Eval] Make SWE-Bench run_infer.sh to default to run SWE-Bench Lite (#3704)
* feat: add SWE-bench fullset support

* fix instance image list

* update eval script and documentation

* increase timeout for remote runtime

* add push script

* handle the case when ret push is an generator

* update pbar

* set SWE-Bench default to run SWE-Bench lite
2024-09-04 00:58:14 +08:00
tofarr ff64085042 doc updates for headless / cli mode (#3707)
* Documentation update for headless / cli mode

* WIP

* WIP

* WIP

* Fix link
2024-09-03 10:56:21 -04:00
mamoodi 31a2dbb372 Indicate browser as experimental so user is aware it may not be fully stable (#3676)
* Indicate browser as experimental so user knows it may not be fully stable

* Add translation of change for all languages
2024-09-02 22:25:22 -04:00
sp.wack 7df3ca4ac8 Fix (#3694) 2024-09-02 22:23:57 -04:00
dependabot[bot] 0a9369df5e chore(deps): bump litellm from 1.44.11 to 1.44.14 (#3698) 2024-09-03 02:10:51 +00:00
dependabot[bot] 327393670a chore(deps): bump boto3 from 1.35.9 to 1.35.10 (#3696) 2024-09-03 09:17:00 +08:00
Xingyao Wang d283420ac2 feat: add SWE-bench fullset support (#3477)
* feat: add SWE-bench fullset support

* fix instance image list

* update eval script and documentation

* add push script

* handle the case when ret push is an generator

* update pbar
2024-09-02 20:28:52 -04:00
dependabot[bot] 57ad0583b7 chore(deps-dev): bump lint-staged from 15.2.9 to 15.2.10 in /frontend (#3700)
Bumps [lint-staged](https://github.com/lint-staged/lint-staged) from 15.2.9 to 15.2.10.
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/master/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v15.2.9...v15.2.10)

---
updated-dependencies:
- dependency-name: lint-staged
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 16:38:38 +00:00
dependabot[bot] 2f359b4f29 chore(deps-dev): bump @types/node from 22.5.1 to 22.5.2 in /frontend (#3702)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.5.1 to 22.5.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 15:56:45 +00:00
dependabot[bot] 5f3ee286bf chore(deps-dev): bump postcss from 8.4.41 to 8.4.44 in /frontend (#3701)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.41 to 8.4.44.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.41...8.4.44)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-02 15:56:03 +00:00
Mislav Balunovic f979d612ec (fix) confirmation mode bugfix for the EventStreamRuntime (#3695) 2024-09-02 13:27:33 +00:00
Boxuan Li 75d5591816 file_ops: Use tmp file for original linting (#3681)
Fix a potential issue that might lead to file corruption when edit linting is enabled

#3124 introduces a feature for editing: running linter twice before and after the change and only extract new errors introduced by the agent. This has some potential issues and I am working on #3649 to address them, but I feel like I am not gonna finish it in the next few days, and that PR has become harder and harder to review, thus this PR, which only focuses on a small improvement.

So what's the issue? When we run linters on the original file before our edits, we need to copy the original file and use a temporary file to lint, because linting may have side-effect (e.g. modifying the file in-place). I used the word "may" because:

Flake8 has no side-effect, so not a problem as of now.
We don't enforce this or document this "no side-effect" as a requirement for linter implementation, so side-effect is allowed.
Regardless, the "after-edit-linting" uses the same approach: backup the file before linting to avoid data corruption. We should keep our "before-edit-linting" consistent.

Why no new unittest that reproduces the issue? Well, as I have mentioned earlier, flake8 has no side-effect, so technically it's not a bug but a flaw. Therefore, there's no way to write a test that reproduces the issue.
2024-09-01 23:36:57 -07:00
tobitege 7068a73ae7 (enh) Improve CodeActAgent's file editing reliability (#3610)
* improve file editing prompts and unit test
converted most raise calls to a _output_error call in file_ops.py

* tweaks in test_agent_skill.py wrt to SEP separator

* tweaked the separator

* remove server runtime remnants and TEST_RUNTIME references

* restore use of TEST_RUNTIME args and variables

* fix integration tests

* added hint to properly escape docstrings

* revert latest prompt change

---------

Co-authored-by: Xingyao Wang <xingyao6@illinois.edu>
2024-09-02 06:03:22 +02:00
Yufan Song 15a32e973e Docs: add logo to repo (#3691)
* add logo to repo

* change position

* change position

* change position
2024-09-02 02:24:35 +00:00
mamoodi 6fcc4ca052 fix eval README link (#3692) 2024-09-02 09:29:42 +08:00
tobitege c83fab8a00 llm: add NotFoundError to completion exception handling (#3668)
Co-authored-by: tofarr <tofarr@gmail.com>
2024-09-01 07:47:21 +00:00
Boxuan Li 1e2796e168 Fix step count out-of-sync bug when child agent fails (#3680) 2024-09-01 09:36:51 +02:00
dependabot[bot] e5bff4bca8 chore(deps-dev): bump llama-index-embeddings-azure-openai (#3669)
Bumps llama-index-embeddings-azure-openai from 0.2.4 to 0.2.5.

---
updated-dependencies:
- dependency-name: llama-index-embeddings-azure-openai
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-30 16:39:01 -07:00
dependabot[bot] 72a705f9a5 chore(deps-dev): bump ruff from 0.6.2 to 0.6.3 (#3674) 2024-08-30 20:28:04 +00:00
dependabot[bot] 6b8adde7bc chore(deps): bump litellm from 1.44.9 to 1.44.11 (#3671) 2024-08-30 20:18:16 +00:00
dependabot[bot] a130367ec7 chore(deps): bump boto3 from 1.35.7 to 1.35.9 (#3670) 2024-08-31 00:11:35 +08:00
dependabot[bot] 52cc0977ca chore(deps-dev): bump @types/react from 18.3.4 to 18.3.5 in /frontend (#3672) 2024-08-31 00:11:21 +08:00
dependabot[bot] 1955fba2ca chore(deps-dev): bump openai from 1.42.0 to 1.43.0 (#3673) 2024-08-31 00:11:03 +08:00
dependabot[bot] 46dae57cf2 chore(deps): bump prism-react-renderer from 2.3.1 to 2.4.0 in /docs (#3675) 2024-08-31 00:10:47 +08:00
430 changed files with 11297 additions and 21310 deletions
+69 -26
View File
@@ -38,10 +38,6 @@ jobs:
base_image:
- image: 'nikolaik/python-nodejs:python3.11-nodejs22'
tag: nikolaik
- image: 'python:3.11-bookworm'
tag: python
- image: 'node:22-bookworm'
tag: node
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -70,31 +66,39 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'poetry'
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Create source distribution and Dockerfile
run: poetry run python3 openhands/runtime/utils/runtime_build.py --base_image ${{ matrix.base_image.image }} --build_folder containers/runtime --force_rebuild
- name: Build and push runtime image ${{ matrix.base_image.image }}
if: "!github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork != true
run: |
./containers/build.sh runtime ${{ github.repository_owner }} --push ${{ matrix.base_image.tag }}
# Forked repos can't push to GHCR, so we need to upload the image as an artifact
- name: Build runtime image ${{ matrix.base_image.image }} for fork
if: "github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork
uses: docker/build-push-action@v6
with:
tags: ghcr.io/all-hands-ai/runtime:${{ github.sha }}-${{ matrix.base_image.tag }}
outputs: type=docker,dest=/tmp/runtime-${{ matrix.base_image.tag }}.tar
context: containers/runtime
- name: Upload runtime image for fork
if: "github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@v4
with:
name: runtime-${{ matrix.base_image.tag }}
@@ -103,11 +107,12 @@ jobs:
# Run unit tests with the EventStream runtime Docker images
test_runtime:
name: Test Runtime
runs-on: ubuntu-latest
needs: [ghcr_build_runtime]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
base_image: ['nikolaik', 'python', 'node']
base_image: ['nikolaik']
steps:
- uses: actions/checkout@v4
- name: Free Disk Space (Ubuntu)
@@ -121,26 +126,41 @@ jobs:
swap-storage: true
# Forked repos can't push to GHCR, so we need to download the image as an artifact
- name: Download runtime image for fork
if: "github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork
uses: actions/download-artifact@v4
with:
name: runtime-${{ matrix.base_image }}
path: /tmp
- name: Load runtime image for fork
if: "github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork
run: |
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
- name: Install poetry via pipx
run: pipx install poetry
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'poetry'
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Run runtime tests
run: |
# We install pytest-xdist in order to run tests across CPUs. However, tests start to fail when we run
# then across more than 2 CPUs for some reason
poetry run pip install pytest-xdist
# Install to be able to retry on failures for flaky tests
poetry run pip install pytest-rerunfailures
image_name=ghcr.io/${{ github.repository_owner }}/runtime:${{ github.sha }}-${{ matrix.base_image }}
image_name=$(echo $image_name | tr '[:upper:]' '[:lower:]')
@@ -148,7 +168,7 @@ jobs:
SANDBOX_USER_ID=$(id -u) \
SANDBOX_BASE_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \
poetry run pytest --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
poetry run pytest -n 2 --reruns 2 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
@@ -162,27 +182,35 @@ jobs:
strategy:
fail-fast: false
matrix:
base_image: ['nikolaik', 'python', 'node']
base_image: ['nikolaik']
steps:
- uses: actions/checkout@v4
# Forked repos can't push to GHCR, so we need to download the image as an artifact
- name: Download runtime image for fork
if: "github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork
uses: actions/download-artifact@v4
with:
name: runtime-${{ matrix.base_image }}
path: /tmp
- name: Load runtime image for fork
if: "github.event.pull_request.head.repo.fork"
if: github.event.pull_request.head.repo.fork
run: |
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
- name: Install poetry via pipx
run: pipx install poetry
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'poetry'
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: make install-python-dependencies
- name: Run integration tests
@@ -201,11 +229,26 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# Checks that all runtime tests have passed
all_runtime_tests_passed:
# The two following jobs (named identically) are to check whether all the runtime tests have passed as the
# "All Runtime Tests Passed" is a required job for PRs to merge
# Due to this bug: https://github.com/actions/runner/issues/2566, we want to create a job that runs when the
# prerequisites have been cancelled or failed so merging is disallowed, otherwise Github considers "skipped" as "success"
runtime_tests_check_success:
name: All Runtime Tests Passed
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
runs-on: ubuntu-latest
needs: [test_runtime, runtime_integration_tests_on_linux]
steps:
- name: All tests passed
run: echo "All runtime tests have passed successfully!"
runtime_tests_check_fail:
name: All Runtime Tests Passed
if: ${{ cancelled() || contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
runs-on: ubuntu-latest
needs: [test_runtime, runtime_integration_tests_on_linux]
steps:
- name: Some tests failed
run: |
echo "Some runtime tests failed or were cancelled"
exit 1
+11 -3
View File
@@ -22,13 +22,21 @@ jobs:
python-version: ['3.11']
steps:
- uses: actions/checkout@v4
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: poetry install --without evaluation,llama-index
- name: Install & Start Docker
@@ -3,6 +3,23 @@ name: Regenerate Integration Tests
on:
workflow_dispatch:
inputs:
debug:
description: 'Enable debug mode'
type: boolean
default: true
log_to_file:
description: 'Enable logging to file'
type: boolean
default: true
force_regenerate_tests:
description: 'Force regeneration of tests'
type: boolean
default: false
force_use_llm:
description: 'Force use of LLM'
type: boolean
default: false
jobs:
regenerate_integration_tests:
@@ -12,21 +29,32 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install poetry via pipx
run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
python-version: "3.11"
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.virtualenvs
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
${{ runner.os }}-poetry-
- name: Install poetry via pipx
run: pipx install poetry
- name: Install Python dependencies using Poetry
run: poetry install --without evaluation,llama-index
run: make install-python-dependencies
- name: Build Environment
run: make build
- name: Regenerate integration tests
run: ./tests/integration/regenerate.sh
run: |
DEBUG=${{ inputs.debug }} \
LOG_TO_FILE=${{ inputs.log_to_file }} \
FORCE_REGENERATE_TESTS=${{ inputs.force_regenerate_tests }} \
FORCE_USE_LLM=${{ inputs.force_use_llm }} \
./tests/integration/regenerate.sh
- name: Commit changes
run: |
if git diff --quiet --exit-code; then
@@ -37,5 +65,6 @@ jobs:
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add .
git commit -m "Regenerate integration tests"
# run it twice in case pre-commit makes changes
git commit -am "Regenerate integration tests" || git commit -am "Regenerate integration tests"
git push
+33 -66
View File
@@ -1,65 +1,49 @@
<a name="readme-top"></a>
<!--
*** Thanks for checking out the Best-README-Template. If you have a suggestion
*** that would make this better, please fork the repo and create a pull request
*** or simply open an issue with the tag "enhancement".
*** Don't forget to give the project a star!
*** Thanks again! Now go create something AMAZING! :D
-->
<div align="center">
<img src="./docs/static/img/logo.png" alt="Logo" width="200">
<h1 align="center">OpenHands: Code Less, Make More</h1>
</div>
<!-- PROJECT SHIELDS -->
<!--
*** I'm using markdown "reference style" links for readability.
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
*** See the bottom of this document for the declaration of the reference variables
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
-->
<div align="center">
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors"><img src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Contributors"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/network/members"><img src="https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Forks"></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/issues"><img src="https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Issues"></a>
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge&color=blue"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=blue" alt="Credits"></a>
<br/>
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community"></a>
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge"></a>
</div>
<!-- PROJECT LOGO -->
<div align="center">
<img src="./docs/static/img/logo.png" alt="Logo" width="200" height="200">
<h1 align="center">OpenHands: Code Less, Make More</h1>
<a href="https://docs.all-hands.dev/modules/usage/intro"><img src="https://img.shields.io/badge/Documentation-OpenHands-blue?logo=googledocs&logoColor=white&style=for-the-badge" alt="Check out the documentation"></a>
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper-%20on%20Arxiv-red?logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="Credits"></a>
<br/>
<a href="https://huggingface.co/spaces/OpenDevin/evaluation"><img src="https://img.shields.io/badge/Evaluation-Benchmark%20on%20HF%20Space-green?logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark"></a>
<a href="https://docs.all-hands.dev/modules/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="Check out the documentation"></a>
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
<a href="https://huggingface.co/spaces/OpenHands/evaluation"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark Score"></a>
<hr>
</div>
<hr>
Welcome to OpenHands, a platform for autonomous software engineers, powered by AI and LLMs (previously called "OpenDevin").
Welcome to OpenHands (formerly OpenDevin), a platform for software development agents powered by AI.
OpenHands agents collaborate with human developers to write code, fix bugs, and ship features.
OpenHands agents can do anything a human developer can: modify code, run commands, browse the web,
call APIs, and yes—even copy code snippets from StackOverflow.
Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or jump to the [Quick Start](#-quick-start).
![App screenshot](./docs/static/img/screenshot.png)
## ⚡ Getting Started
OpenHands works best with Docker version 26.0.0+ (Docker Desktop 4.31.0+).
You must be using Linux, Mac OS, or WSL on Windows.
## ⚡ Quick Start
To start OpenHands in a docker container, run the following commands in your terminal:
The easiest way to run OpenHands is in Docker. You can change `WORKSPACE_BASE` below to
point OpenHands to existing code that you'd like to modify.
> [!WARNING]
> When you run the following command, files in `./workspace` may be modified or deleted.
See the [Getting Started](https://docs.all-hands.dev/modules/usage/getting-started) guide for
system requirements and more information.
```bash
WORKSPACE_BASE=$(pwd)/workspace
docker run -it \
--pull=always \
export WORKSPACE_BASE=$(pwd)/workspace
docker run -it --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-v $WORKSPACE_BASE:/opt/workspace_base \
@@ -70,29 +54,23 @@ docker run -it \
ghcr.io/all-hands-ai/openhands:0.9
```
> [!NOTE]
> This command pulls the `0.9` tag, which represents the most recent stable release of OpenHands. You have other options as well:
> - For a specific release version, use `ghcr.io/all-hands-ai/openhands:<OpenHands_version>` (replace <OpenHands_version> with the desired version number).
> - For the most up-to-date development version, use `ghcr.io/all-hands-ai/openhands:main`. This version may be **(unstable!)** and is recommended for testing or development purposes only.
>
> Choose the tag that best suits your needs based on stability requirements and desired features.
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000) with access to `./workspace`. To have OpenHands operate on your code, place it in `./workspace`.
OpenHands will only have access to this workspace folder. The rest of your system will not be affected as it runs in a secured docker sandbox.
You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
or as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
Upon opening OpenHands, you must select the appropriate `Model` and enter the `API Key` within the settings that should pop up automatically. These can be set at any time by selecting
the `Settings` button (gear icon) in the UI. If the required `Model` does not exist in the list, you can manually enter it in the text box.
Visit [Getting Started](https://docs.all-hands.dev/modules/usage/getting-started) for more information and setup instructions.
For the development workflow, see [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
If you want to modify the OpenHands source code, check out [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
Having issues? The [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting) can help.
## 🚀 Documentation
## 📖 Documentation
To learn more about the project, and for tips on using OpenHands,
**check out our [documentation](https://docs.all-hands.dev/modules/usage/intro)**.
**check out our [documentation](https://docs.all-hands.dev/modules/usage/getting-started)**.
There you'll find resources on how to use different LLM providers (like ollama and Anthropic's Claude),
There you'll find resources on how to use different LLM providers,
troubleshooting resources, and advanced configuration options.
## 🤝 How to Contribute
@@ -127,17 +105,6 @@ Let's make software engineering better together!
Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
[contributors-shield]: https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge
[contributors-url]: https://github.com/All-Hands-AI/OpenHands/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge
[forks-url]: https://github.com/All-Hands-AI/OpenHands/network/members
[stars-shield]: https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge
[stars-url]: https://github.com/All-Hands-AI/OpenHands/stargazers
[issues-shield]: https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge
[issues-url]: https://github.com/All-Hands-AI/OpenHands/issues
[license-shield]: https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge
[license-url]: https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE
## 🙏 Acknowledgements
OpenHands is built by a large number of contributors, and every contribution is greatly appreciated! We also build upon other open source projects, and we are deeply thankful for their work.
+15 -4
View File
@@ -65,10 +65,15 @@ In order to accomplish my goal I need to send the information asked back to the
"""
def get_prompt(error_prefix: str, cur_axtree_txt: str, prev_action_str: str) -> str:
def get_prompt(
error_prefix: str, cur_url: str, cur_axtree_txt: str, prev_action_str: str
) -> str:
prompt = f"""\
{error_prefix}
# Current Page URL:
{cur_url}
# Current Accessibility Tree:
{cur_axtree_txt}
@@ -139,6 +144,7 @@ class BrowsingAgent(Agent):
"""
messages: list[Message] = []
prev_actions = []
cur_url = ''
cur_axtree_txt = ''
error_prefix = ''
last_obs = None
@@ -179,6 +185,9 @@ class BrowsingAgent(Agent):
self.error_accumulator += 1
if self.error_accumulator > 5:
return MessageAction('Too many errors encountered. Task failed.')
cur_url = last_obs.url
try:
cur_axtree_txt = flatten_axtree_to_str(
last_obs.axtree_object,
@@ -204,11 +213,13 @@ class BrowsingAgent(Agent):
messages.append(Message(role='system', content=[TextContent(text=system_msg)]))
prompt = get_prompt(error_prefix, cur_axtree_txt, prev_action_str)
prompt = get_prompt(error_prefix, cur_url, cur_axtree_txt, prev_action_str)
messages.append(Message(role='user', content=[TextContent(text=prompt)]))
logger.debug(prompt)
flat_messages = self.llm.format_messages_for_llm(messages)
response = self.llm.completion(
messages=[message.model_dump() for message in messages],
messages=flat_messages,
temperature=0.0,
stop=[')```', ')\n```'],
)
+1 -1
View File
@@ -354,7 +354,7 @@ and executed by a program, make sure to follow the formatting instructions.
self._prompt += '\n'.join(
[
f"""\
- [{msg['role']}] {msg['message']}"""
- [{msg['role']}], {msg['message']}"""
for msg in chat_messages
]
)
+2 -2
View File
@@ -24,9 +24,9 @@ class BrowsingResponseParser(ResponseParser):
if action_str is None:
return ''
action_str = action_str.strip()
if not action_str.endswith('```'):
if action_str and not action_str.endswith('```'):
action_str = action_str + ')```'
logger.info(action_str)
logger.debug(action_str)
return action_str
def parse_action(self, action_str: str) -> Action:
+40 -21
View File
@@ -1,9 +1,11 @@
import os
from itertools import islice
from agenthub.codeact_agent.action_parser import CodeActResponseParser
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
from openhands.core.logger import openhands_logger as logger
from openhands.core.message import ImageContent, Message, TextContent
from openhands.events.action import (
Action,
@@ -17,6 +19,7 @@ from openhands.events.observation import (
AgentDelegateObservation,
CmdOutputObservation,
IPythonRunCellObservation,
UserRejectObservation,
)
from openhands.events.observation.error import ErrorObservation
from openhands.events.observation.observation import Observation
@@ -116,7 +119,11 @@ class CodeActAgent(Agent):
):
content = [TextContent(text=self.action_to_str(action))]
if isinstance(action, MessageAction) and action.images_urls:
if (
self.llm.vision_is_active()
and isinstance(action, MessageAction)
and action.images_urls
):
content.append(ImageContent(image_urls=action.images_urls))
return Message(
@@ -126,14 +133,15 @@ class CodeActAgent(Agent):
def get_observation_message(self, obs: Observation) -> Message | None:
max_message_chars = self.llm.config.max_message_chars
obs_prefix = 'OBSERVATION:\n'
if isinstance(obs, CmdOutputObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text = obs_prefix + truncate_content(obs.content, max_message_chars)
text += (
f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]'
)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, IPythonRunCellObservation):
text = 'OBSERVATION:\n' + obs.content
text = obs_prefix + obs.content
# replace base64 images with a placeholder
splitted = text.split('\n')
for i, line in enumerate(splitted):
@@ -145,14 +153,16 @@ class CodeActAgent(Agent):
text = truncate_content(text, max_message_chars)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, AgentDelegateObservation):
text = 'OBSERVATION:\n' + truncate_content(
str(obs.outputs), max_message_chars
)
text = obs_prefix + truncate_content(str(obs.outputs), max_message_chars)
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, ErrorObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text = obs_prefix + truncate_content(obs.content, max_message_chars)
text += '\n[Error occurred in processing last action]'
return Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, UserRejectObservation):
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
text += '\n[Last action has been rejected by the user]'
return Message(role='user', content=[TextContent(text=text)])
else:
# If an observation message is not returned, it will cause an error
# when the LLM tries to return the next message
@@ -183,9 +193,8 @@ class CodeActAgent(Agent):
# prepare what we want to send to the LLM
messages = self._get_messages(state)
params = {
'messages': [message.model_dump() for message in messages],
'messages': self.llm.format_messages_for_llm(messages),
'stop': [
'</execute_ipython>',
'</execute_bash>',
@@ -194,12 +203,19 @@ class CodeActAgent(Agent):
'temperature': 0.0,
}
if self.llm.supports_prompt_caching:
if self.llm.is_caching_prompt_active():
params['extra_headers'] = {
'anthropic-beta': 'prompt-caching-2024-07-31',
}
response = self.llm.completion(**params)
try:
response = self.llm.completion(**params)
except Exception as e:
logger.error(f'{e}')
error_message = '{}: {}'.format(type(e).__name__, str(e).split('\n')[0])
return AgentFinishAction(
thought=f'Agent encountered an error while processing the last action.\nError: {error_message}\nPlease try again.'
)
return self.action_parser.parse(response)
@@ -210,7 +226,7 @@ class CodeActAgent(Agent):
content=[
TextContent(
text=self.prompt_manager.system_message,
cache_prompt=self.llm.supports_prompt_caching, # Cache system prompt
cache_prompt=self.llm.is_caching_prompt_active(), # Cache system prompt
)
],
),
@@ -219,7 +235,7 @@ class CodeActAgent(Agent):
content=[
TextContent(
text=self.prompt_manager.initial_user_message,
cache_prompt=self.llm.supports_prompt_caching, # if the user asks the same query,
cache_prompt=self.llm.is_caching_prompt_active(), # if the user asks the same query,
)
],
),
@@ -238,14 +254,14 @@ class CodeActAgent(Agent):
if message:
# handle error if the message is the SAME role as the previous message
# litellm.exceptions.BadRequestError: litellm.BadRequestError: OpenAIException - Error code: 400 - {'detail': 'Only supports u/a/u/a/u...'}
# there should not have two consecutive messages from the same role
# there shouldn't be two consecutive messages from the same role
if messages and messages[-1].role == message.role:
messages[-1].content.extend(message.content)
else:
messages.append(message)
# Add caching to the last 2 user messages
if self.llm.supports_prompt_caching:
if self.llm.is_caching_prompt_active():
user_turns_processed = 0
for message in reversed(messages):
if message.role == 'user' and user_turns_processed < 2:
@@ -254,14 +270,17 @@ class CodeActAgent(Agent):
].cache_prompt = True # Last item inside the message content
user_turns_processed += 1
# the latest user message is important:
# The latest user message is important:
# we want to remind the agent of the environment constraints
latest_user_message = next(
(
m
for m in reversed(messages)
if m.role == 'user'
and any(isinstance(c, TextContent) for c in m.content)
islice(
(
m
for m in reversed(messages)
if m.role == 'user'
and any(isinstance(c, TextContent) for c in m.content)
),
1,
),
None,
)
+21 -10
View File
@@ -1,16 +1,19 @@
{% set MINIMAL_SYSTEM_PREFIX %}
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.
The assistant can use an interactive Python (Jupyter Notebook) environment, executing code with <execute_ipython>.
A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed answers to the user's questions.
The assistant can use a Python environment with <execute_ipython>, e.g.:
<execute_ipython>
print("Hello World!")
</execute_ipython>
The assistant can execute bash commands on behalf of the user by wrapping them with <execute_bash> and </execute_bash>.
The assistant can execute bash commands wrapped with <execute_bash>, e.g. <execute_bash> ls </execute_bash>.
If a bash command returns exit code `-1`, this means the process is not yet finished.
The assistant must then send a second <execute_bash>. The second <execute_bash> can be empty
(which will retrieve any additional logs), or it can contain text to be sent to STDIN of the running process,
or it can contain the text `ctrl+c` to interrupt the process.
For example, you can list the files in the current directory by <execute_bash> ls </execute_bash>.
Important, however: do not run interactive commands. You do not have access to stdin.
Also, you need to handle commands that may run indefinitely and not return a result. For such cases, you should redirect the output to a file and run the command in the background to avoid blocking the execution.
For example, to run a Python script that might run indefinitely without returning immediately, you can use the following format: <execute_bash> python3 app.py > server.log 2>&1 & </execute_bash>
Also, if a command execution result saying like: Command: "npm start" timed out. Sending SIGINT to the process, you should also retry with running the command in the background.
For commands that may run indefinitely, the output should be redirected to a file and the command run
in the background, e.g. <execute_bash> python3 app.py > server.log 2>&1 & </execute_bash>
If a command execution result says "Command timed out. Sending SIGINT to the process",
the assistant should retry running the command in the background.
{% endset %}
{% set BROWSING_PREFIX %}
The assistant can browse the Internet with <execute_browse> and </execute_browse>.
@@ -24,7 +27,14 @@ The assistant can install Python packages using the %pip magic command in an IPy
{% set COMMAND_DOCS %}
Apart from the standard Python library, the assistant can also use the following functions (already imported) in <execute_ipython> environment:
{{ agent_skills_docs }}
Please note that THE `edit_file_by_replace`, `append_file` and `insert_content_at_line` FUNCTIONS REQUIRE PROPER INDENTATION. If the assistant would like to add the line ' print(x)', it must fully write that out, with all those spaces before the code! Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
IMPORTANT:
- `open_file` only returns the first 100 lines of the file by default! The assistant MUST use `scroll_down` repeatedly to read the full file BEFORE making edits!
- The assistant shall adhere to THE `edit_file_by_replace`, `append_file` and `insert_content_at_line` FUNCTIONS REQUIRING PROPER INDENTATION. If the assistant would like to add the line ' print(x)', it must fully write the line out, with all leading spaces before the code!
- Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
- Any code issued should be less than 50 lines to avoid context being cut off!
- After EVERY `create_file` the method `append_file` shall be used to write the FIRST content!
- For `edit_file_by_replace` NEVER provide empty parameters!
- For `edit_file_by_replace` the file must be read fully before any replacements!
{% endset %}
{% set SYSTEM_SUFFIX %}
Responses should be concise.
@@ -32,7 +42,8 @@ The assistant should attempt fewer things at a time instead of putting too many
Include ONLY ONE <execute_ipython>, <execute_bash>, or <execute_browse> per response, unless the assistant is finished with the task or needs more input or action from the user in order to proceed.
If the assistant is finished with the task you MUST include <finish></finish> in your response.
IMPORTANT: Execute code using <execute_ipython>, <execute_bash>, or <execute_browse> whenever possible.
The assistant should utilize full file paths and the 'pwd' command to prevent path-related errors. The assistant should refrain from excessive apologies in its responses.
The assistant should utilize full file paths and the `pwd` command to prevent path-related errors.
The assistant must avoid apologies and thanks in its responses.
{% endset %}
{# Combine all parts without newlines between them #}
@@ -94,7 +94,11 @@ class CodeActSWEAgent(Agent):
):
content = [TextContent(text=self.action_to_str(action))]
if isinstance(action, MessageAction) and action.images_urls:
if (
self.llm.vision_is_active()
and isinstance(action, MessageAction)
and action.images_urls
):
content.append(ImageContent(image_urls=action.images_urls))
return Message(
@@ -156,9 +160,8 @@ class CodeActSWEAgent(Agent):
# prepare what we want to send to the LLM
messages: list[Message] = self._get_messages(state)
response = self.llm.completion(
messages=[message.model_dump() for message in messages],
messages=self.llm.format_messages_for_llm(messages),
stop=[
'</execute_ipython>',
'</execute_bash>',
+5 -2
View File
@@ -73,10 +73,13 @@ class MicroAgent(Agent):
latest_user_message=last_user_message,
)
content = [TextContent(text=prompt)]
if last_image_urls:
if self.llm.vision_is_active() and last_image_urls:
content.append(ImageContent(image_urls=last_image_urls))
message = Message(role='user', content=content)
resp = self.llm.completion(messages=[message.model_dump()])
resp = self.llm.completion(
messages=self.llm.format_messages_for_llm(message),
temperature=0.0,
)
action_resp = resp['choices'][0]['message']['content']
action = parse_response(action_resp)
return action
+2 -2
View File
@@ -46,8 +46,8 @@ class PlannerAgent(Agent):
state, self.llm.config.max_message_chars
)
content = [TextContent(text=prompt)]
if image_urls:
if self.llm.vision_is_active() and image_urls:
content.append(ImageContent(image_urls=image_urls))
message = Message(role='user', content=content)
resp = self.llm.completion(messages=[message.model_dump()])
resp = self.llm.completion(messages=self.llm.format_messages_for_llm(message))
return self.response_parser.parse(resp)
+18 -7
View File
@@ -126,21 +126,29 @@ embedding_model = ""
# Model to use
model = "gpt-4o"
# Number of retries to attempt
#num_retries = 5
# Number of retries to attempt when an operation fails with the LLM.
# Increase this value to allow more attempts before giving up
#num_retries = 8
# Retry maximum wait time
#retry_max_wait = 60
# Maximum wait time (in seconds) between retry attempts
# This caps the exponential backoff to prevent excessively long
#retry_max_wait = 120
# Retry minimum wait time
#retry_min_wait = 3
# Minimum wait time (in seconds) between retry attempts
# This sets the initial delay before the first retry
#retry_min_wait = 15
# Retry multiplier for exponential backoff
# Multiplier for exponential backoff calculation
# The wait time increases by this factor after each failed attempt
# A value of 2.0 means each retry waits twice as long as the previous one
#retry_multiplier = 2.0
# Drop any unmapped (unsupported) params without causing an exception
#drop_params = false
# Using the prompt caching feature provided by the LLM
#caching_prompt = false
# Base URL for the OLLAMA API
#ollama_base_url = ""
@@ -153,6 +161,9 @@ model = "gpt-4o"
# Top p for the API
#top_p = 0.5
# If model is vision capable, this option allows to disable image processing (useful for cost reduction).
#disable_vision = true
[llm.gpt3]
# API key to use
api_key = "your-api-key"
+13 -8
View File
@@ -4,8 +4,8 @@ import { themes as prismThemes } from "prism-react-renderer";
const config: Config = {
title: "OpenHands",
tagline: "An Open Platform for AI Software Developers as Generalist Agents",
favicon: "img/logo.png",
tagline: "Code Less, Make More",
favicon: "img/logo-square.png",
// Set the production url of your site here
url: "https://docs.all-hands.dev",
@@ -73,23 +73,28 @@ const config: Config = {
type: "docSidebar",
sidebarId: "docsSidebar",
position: "left",
label: "Docs",
label: "User Guides",
},
{
type: "docSidebar",
sidebarId: "apiSidebar",
position: "left",
label: "Codebase",
label: "Python API",
},
{
type: 'localeDropdown',
position: 'left',
},
{
href: "https://all-hands.dev",
label: "Company",
position: "right",
},
{
href: "https://github.com/All-Hands-AI/OpenHands",
label: "GitHub",
position: "right",
},
{
type: 'localeDropdown',
position: 'left',
},
],
},
prism: {
@@ -41,4 +41,4 @@ ne peut être aussi puissant que les modèles qui le pilotent -- heureusement, l
Certains LLM ont des limites de taux et peuvent nécessiter des réessais. OpenHands réessaiera automatiquement les demandes s'il reçoit une erreur 429 ou une erreur de connexion API.
Vous pouvez définir les variables d'environnement `LLM_NUM_RETRIES`, `LLM_RETRY_MIN_WAIT`, `LLM_RETRY_MAX_WAIT` pour contrôler le nombre de réessais et le temps entre les réessais.
Par défaut, `LLM_NUM_RETRIES` est 5 et `LLM_RETRY_MIN_WAIT`, `LLM_RETRY_MAX_WAIT` sont respectivement de 3 secondes et 60 secondes.
Par défaut, `LLM_NUM_RETRIES` est 8 et `LLM_RETRY_MIN_WAIT`, `LLM_RETRY_MAX_WAIT` sont respectivement de 15 secondes et 120 secondes.
@@ -43,4 +43,4 @@ OpenHands 将向你配置的 LLM 发出许多提示。大多数这些 LLM 都是
一些 LLM 有速率限制,可能需要重试操作。OpenHands 会在收到 429 错误或 API 连接错误时自动重试请求。
你可以设置 `LLM_NUM_RETRIES``LLM_RETRY_MIN_WAIT``LLM_RETRY_MAX_WAIT` 环境变量来控制重试次数和重试之间的时间。
默认情况下,`LLM_NUM_RETRIES`5`LLM_RETRY_MIN_WAIT``LLM_RETRY_MAX_WAIT` 分别为 3 秒和 60 秒。
默认情况下,`LLM_NUM_RETRIES`8`LLM_RETRY_MIN_WAIT``LLM_RETRY_MAX_WAIT` 分别为 15 秒和 120 秒。
@@ -74,7 +74,7 @@ WORKSPACE_DIR="$(pwd)/workspace"
如有需要,可以替换您选择的 `LLM_MODEL`
完成!现在您可以通过 `make run` 启动 Devin 而无需 Docker。现在您应该可以连接到 `http://localhost:3000/`
完成!现在您可以通过 `make run` 启动 OpenHands 而无需 Docker。现在您应该可以连接到 `http://localhost:3000/`
## 选择您的模型
+6
View File
@@ -5,6 +5,7 @@ sidebar_position: 8
# 📚 Misc
## ⭐️ Research Strategy
Achieving full replication of production-grade applications with LLMs is a complex endeavor. Our strategy involves:
1. **Core Technical Research:** Focusing on foundational research to understand and improve the technical aspects of code generation and handling
@@ -13,9 +14,11 @@ Achieving full replication of production-grade applications with LLMs is a compl
4. **Evaluation:** Establishing comprehensive evaluation metrics to better understand and improve our models
## 🚧 Default Agent
Our default Agent is currently the [CodeActAgent](agents), which is capable of generating code and handling files.
## 🤝 How to Contribute
OpenHands is a community-driven project, and we welcome contributions from everyone. Whether you're a developer, a researcher, or simply enthusiastic about advancing the field of software engineering with AI, there are many ways to get involved:
- **Code Contributions:** Help us develop the core functionalities, frontend interface, or sandboxing solutions
@@ -25,6 +28,7 @@ OpenHands is a community-driven project, and we welcome contributions from every
For details, please check [this document](https://github.com/All-Hands-AI/OpenHands/blob/main/CONTRIBUTING.md).
## 🤖 Join Our Community
We have both Slack workspace for the collaboration on building OpenHands and Discord server for discussion about anything related, e.g., this project, LLM, agent, etc.
- [Slack workspace](https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA)
@@ -37,6 +41,7 @@ If you would love to contribute, feel free to join our community. Let's simplify
[![Star History Chart](https://api.star-history.com/svg?repos=All-Hands-AI/OpenHands&type=Date)](https://star-history.com/#All-Hands-AI/OpenHands&Date)
## 🛠️ Built With
OpenHands is built using a combination of powerful frameworks and libraries, providing a robust foundation for its development. Here are the key technologies used in the project:
![FastAPI](https://img.shields.io/badge/FastAPI-black?style=for-the-badge) ![uvicorn](https://img.shields.io/badge/uvicorn-black?style=for-the-badge) ![LiteLLM](https://img.shields.io/badge/LiteLLM-black?style=for-the-badge) ![Docker](https://img.shields.io/badge/Docker-black?style=for-the-badge) ![Ruff](https://img.shields.io/badge/Ruff-black?style=for-the-badge) ![MyPy](https://img.shields.io/badge/MyPy-black?style=for-the-badge) ![LlamaIndex](https://img.shields.io/badge/LlamaIndex-black?style=for-the-badge) ![React](https://img.shields.io/badge/React-black?style=for-the-badge)
@@ -44,4 +49,5 @@ OpenHands is built using a combination of powerful frameworks and libraries, pro
Please note that the selection of these technologies is in progress, and additional technologies may be added or existing ones may be removed as the project evolves. We strive to adopt the most suitable and efficient tools to enhance the capabilities of OpenHands.
## 📜 License
Distributed under the MIT License. See [our license](https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE) for more information.
+4
View File
@@ -3,8 +3,11 @@ sidebar_position: 3
---
# 🧠 Main Agent and Capabilities
## CodeActAgent
### Description
This agent implements the CodeAct idea ([paper](https://arxiv.org/abs/2402.01030), [tweet](https://twitter.com/xingyaow_/status/1754556835703751087)) that consolidates LLM agents **act**ions into a
unified **code** action space for both _simplicity_ and _performance_.
@@ -19,6 +22,7 @@ The conceptual idea is illustrated below. At each turn, the agent can:
![image](https://github.com/All-Hands-AI/OpenHands/assets/38853559/92b622e3-72ad-4a61-8f41-8c040b6d5fb3)
### Demo
https://github.com/All-Hands-AI/OpenHands/assets/38853559/f592a192-e86c-4f48-ad31-d69282d5f6ac
_Example of CodeActAgent with `gpt-4-turbo-2024-04-09` performing a data science task (linear regression)_.
+60
View File
@@ -0,0 +1,60 @@
---
sidebar_position: 2
---
# Getting Started
## System Requirements
* Docker version 26.0.0+ or Docker Desktop 4.31.0+
* You must be using Linux or Mac OS
* If you are on Windows, you must use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
## Installation
The easiest way to run OpenHands is in Docker. You can change `WORKSPACE_BASE` below to point OpenHands to
existing code that you'd like to modify.
```bash
export WORKSPACE_BASE=$(pwd)/workspace
docker run -it --pull=always \
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-v $WORKSPACE_BASE:/opt/workspace_base \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/all-hands-ai/openhands:0.9
```
You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
or as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
## Setup
After running the command above, you'll find OpenHands running at [http://localhost:3000](http://localhost:3000).
The agent will have access to the `./workspace` folder to do its work. You can copy existing code here, or change `WORKSPACE_BASE` in the
command to point to an existing folder.
Upon launching OpenHands, you'll see a settings modal. You must select an LLM backend using `Model`, and enter a corresponding `API Key`.
These can be changed at any time by selecting the `Settings` button (gear icon) in the UI.
If the required `Model` does not exist in the list, you can toggle `Use custom model` and manually enter it in the text box.
<img src="/img/settings-screenshot.png" alt="settings-modal" width="340" />
## Versions
The command above pulls the `0.9` tag, which represents the most recent stable release of OpenHands. You have other options as well:
- For a specific release, use `ghcr.io/all-hands-ai/openhands:$VERSION`, replacing $VERSION with the version number.
- We use semver, and release major, minor, and patch tags. So `0.9` will automatically point to the latest `0.9.x` release, and `0` will point to the latest `0.x.x` release.
- For the most up-to-date development version, you can use `ghcr.io/all-hands-ai/openhands:main`. This version is unstable and is recommended for testing or development purposes only.
You can choose the tag that best suits your needs based on stability requirements and desired features.
For the development workflow, see [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
+108
View File
@@ -0,0 +1,108 @@
# CLI Mode
OpenHands can be run in an interactive CLI mode, which allows users to start an interactive session via the command line.
This mode is different from the [headless mode](headless-mode), which is non-interactive and better for scripting.
## With Python
To start an interactive OpenHands session via the command line, follow these steps:
1. Ensure you have followed the [Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md)
2. Run the following command:
```bash
poetry run python -m openhands.core.cli
```
This command will start an interactive session where you can input tasks and receive responses from OpenHands.
You'll need to be sure to set your model, API key, and other settings via environment variables
[or the `config.toml` file](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml).
## With Docker
To run OpenHands in CLI mode with Docker, follow these steps:
1. Set `WORKSPACE_BASE` to the directory you want OpenHands to edit:
```bash
WORKSPACE_BASE=$(pwd)/workspace
```
2. Set `LLM_MODEL` to the model you want to use:
```bash
LLM_MODEL="anthropic/claude-3-5-sonnet-20240620"
```
3. Set `LLM_API_KEY` to your API key:
```bash
LLM_API_KEY="abcde"
```
4. Run the following Docker command:
```bash
docker run -it \
--pull=always \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-e LLM_API_KEY=$LLM_API_KEY \
-e LLM_MODEL=$LLM_MODEL \
-v $WORKSPACE_BASE:/opt/workspace_base \
-v /var/run/docker.sock:/var/run/docker.sock \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/all-hands-ai/openhands:0.9 \
python -m openhands.core.cli
```
This command will start an interactive session in Docker where you can input tasks and receive responses from OpenHands.
## Examples of CLI Commands and Expected Outputs
Here are some examples of CLI commands and their expected outputs:
### Example 1: Simple Task
```bash
How can I help? >> Write a Python script that prints "Hello, World!"
```
Expected Output:
```bash
🤖 Sure! Here is a Python script that prints "Hello, World!":
print("Hello, World!")
```
### Example 2: Bash Command
```bash
How can I help? >> Create a directory named "test_dir"
```
Expected Output:
```bash
🤖 Creating a directory named "test_dir":
mkdir test_dir
```
### Example 3: Error Handling
```bash
How can I help? >> Delete a non-existent file
```
Expected Output:
```bash
🤖 An error occurred. Please try again.
```
@@ -1,26 +1,19 @@
# Create and Use a Custom Docker Sandbox
# Custom Sandbox
The default OpenHands sandbox comes with a [minimal ubuntu configuration](https://github.com/All-Hands-AI/OpenHands/blob/main/containers/sandbox/Dockerfile).
Your use case may need additional software installed by default.
The sandbox is where the agent does its work. Instead of running commands directly on your computer
(which could be dangerous), the agent runs them inside of a Docker container.
The default OpenHands sandbox (`python-nodejs:python3.11-nodejs22`
from [nikolaik/python-nodejs](https://hub.docker.com/r/nikolaik/python-nodejs)) comes with some packages installed such
as python and Node.js but your use case may need additional software installed by default.
There are two ways you can do so:
1. Use an existing image from docker hub. For instance, if you want to have `nodejs` installed, you can do so by using the `node:20` image
1. Use an existing image from docker hub
2. Creating your own custom docker image and using it
If you want to take the first approach, you can skip the `Create Your Docker Image` section.
For a more feature-rich environment, you might consider using pre-built images like **[nikolaik/python-nodejs](https://hub.docker.com/r/nikolaik/python-nodejs)**, which comes with both Python and Node.js pre-installed, along with many other useful tools and libraries, like:
- Node.js: 22.x
- npm: 10.x
- yarn: stable
- Python: latest
- pip: latest
- pipenv: latest
- poetry: latest
- uv: latest
## Setup
Make sure you are able to run OpenHands using the [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) first.
@@ -79,7 +72,7 @@ Run OpenHands by running ```make run``` in the top level directory.
Navigate to ```localhost:3001``` and check if your desired dependencies are available.
In the case of the example above, running ```node -v``` in the terminal produces ```v20.15.0```
In the case of the example above, running ```node -v``` in the terminal produces ```v20.15.0```.
Congratulations!
@@ -1,4 +1,4 @@
# Contribute to OpenHands Evaluation Harness
# Evaluation
This guide provides an overview of how to integrate your own evaluation benchmark into the OpenHands framework.
@@ -12,7 +12,7 @@ Here's an example configuration file you can use to define and use multiple LLMs
```toml
[llm]
# IMPORTANT: add your API key here, and set the model to the one you want to evaluate
model = "gpt-4o-2024-05-13"
model = "claude-3-5-sonnet-20240620"
api_key = "sk-XXX"
[llm.eval_gpt4_1106_preview_llm]
+26 -11
View File
@@ -1,9 +1,12 @@
# Running in Headless Mode
# Headless Mode
You can run OpenHands via a CLI, without starting the web application. This makes it easy
to automate tasks with OpenHands.
You can run OpenHands with a single command, without starting the web application.
This makes it easy to write scripts and automate tasks with OpenHands.
This is different from [CLI Mode](cli-mode), which is interactive, and better for active development.
## With Python
To run OpenHands in headless mode with Python,
[follow the Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md),
and then run:
@@ -12,19 +15,32 @@ and then run:
poetry run python -m openhands.core.main -t "write a bash script that prints hi"
```
You'll need to be sure to set your model, API key, and other settings via environment variables
[or the `config.toml` file](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml).
## With Docker
To run OpenHands in headless mode with Docker, run:
1. Set `WORKSPACE_BASE` to the directory you want OpenHands to edit:
```bash
# Set WORKSPACE_BASE to the directory you want OpenHands to edit
WORKSPACE_BASE=$(pwd)/workspace
```
# Set LLM_API_KEY to an API key, e.g. for OpenAI or Anthropic
2. Set `LLM_MODEL` to the model you want to use:
```bash
LLM_MODEL="anthropic/claude-3-5-sonnet-20240620"
```
3. Set `LLM_API_KEY` to your API key:
```bash
LLM_API_KEY="abcde"
```
# Set LLM_MODEL to the model you want to use
LLM_MODEL="gpt-4o"
4. Run the following Docker command:
```bash
docker run -it \
--pull=always \
-e SANDBOX_USER_ID=$(id -u) \
@@ -35,7 +51,6 @@ docker run -it \
-v /var/run/docker.sock:/var/run/docker.sock \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/all-hands-ai/openhands:main \ # TODO: pin a version here
python -m openhands.core.main \
-t "Write a bash script that prints Hello World"
ghcr.io/all-hands-ai/openhands:0.9 \
python -m openhands.core.main -t "write a bash script that prints hi"
```
-5
View File
@@ -1,5 +0,0 @@
---
sidebar_position: 6
---
# 🔎 How To Section
@@ -1,11 +1,12 @@
# Use OpenHands in OpenShift/K8S
# Kubernetes
There are different ways this can be accomplished. This guide goes through one possible way:
There are different ways you might run OpenHands on Kubernetes or OpenShift. This guide goes through one possible way:
1. Create a PV "as a cluster admin" to map workspace_base data and docker directory to the pod through the worker node
2. Create a PVC to be able to mount those PVs to the pod
3. Create a pod which contains two containers; the OpenHands and Sandbox containers
## Detailed Steps for the Example Above
> Note: Make sure you are logged in to the cluster first with the proper account for each step. PV creation requires cluster administrator!
> Make sure you have read/write permissions on the hostPath used below (i.e. /tmp/workspace)
-71
View File
@@ -1,71 +0,0 @@
---
sidebar_position: 1
---
# 💻 OpenHands
OpenHands is an **autonomous AI software engineer** capable of executing complex engineering tasks and collaborating actively with users on software development projects.
This project is fully open-source, so you can use and modify it however you like.
:::tip
Explore the codebase of OpenHands on [GitHub](https://github.com/All-Hands-AI/OpenHands) or join one of our communities!
<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"
alt="Contributors"
/>
</a>
<a href="https://github.com/All-Hands-AI/OpenHands/network/members">
<img
src="https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge"
alt="Forks"
/>
</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"
alt="Stargazers"
/>
</a>
<a href="https://github.com/All-Hands-AI/OpenHands/issues">
<img
src="https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge"
alt="Issues"
/>
</a>
<br></br>
<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"
alt="MIT License"
/>
</a>
<br></br>
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA">
<img
src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge"
alt="Join our Slack community"
/>
</a>
<a href="https://discord.gg/ESHStjSjD4">
<img
src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge"
alt="Join our Discord community"
/>
</a>
:::
## 🛠️ Getting Started
[Check out the getting started guide on Github](https://github.com/All-Hands-AI/OpenHands?tab=readme-ov-file#-getting-started)
[contributors-shield]: https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge
[contributors-url]: https://github.com/All-Hands-AI/OpenHands/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge
[forks-url]: https://github.com/All-Hands-AI/OpenHands/network/members
[stars-shield]: https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge
[stars-url]: https://github.com/All-Hands-AI/OpenHands/stargazers
[issues-shield]: https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge
[issues-url]: https://github.com/All-Hands-AI/OpenHands/issues
[license-shield]: https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge
[license-url]: https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE
+10 -7
View File
@@ -2,7 +2,7 @@
## Completion
OpenHands uses LiteLLM for completion calls. You can find their documentation on Azure [here](https://docs.litellm.ai/docs/providers/azure)
OpenHands uses LiteLLM for completion calls. You can find their documentation on Azure [here](https://docs.litellm.ai/docs/providers/azure).
### Azure openai configs
@@ -12,7 +12,7 @@ When running the OpenHands Docker image, you'll need to set the following enviro
LLM_BASE_URL="<azure-api-base-url>" # e.g. "https://openai-gpt-4-test-v-1.openai.azure.com/"
LLM_API_KEY="<azure-api-key>"
LLM_MODEL="azure/<your-gpt-deployment-name>"
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
```
Example:
@@ -31,15 +31,18 @@ docker run -it \
ghcr.io/all-hands-ai/openhands:main
```
You can set the LLM_MODEL and LLM_API_KEY in the OpenHands UI itself.
You can also set the model and API key in the OpenHands UI through the Settings.
:::note
You can find your ChatGPT deployment name on the deployments page in Azure. It could be the same with the chat model name (e.g. 'GPT4-1106-preview'), by default or initially set, but it doesn't have to be the same. Run openhands, and when you load it in the browser, go to Settings and set model as above: "azure/&lt;your-actual-gpt-deployment-name&gt;". If it's not in the list, enter your own text and save it.
You can find your ChatGPT deployment name on the deployments page in Azure. It could be the same with the chat model
name (e.g. 'GPT4-1106-preview'), by default or initially set, but it doesn't have to be the same. Run OpenHands,
and when you load it in the browser, go to Settings and set model as above: "azure/&lt;your-actual-gpt-deployment-name&gt;".
If it's not in the list, you can open the Settings modal, switch to "Custom Model", and enter your model name.
:::
## Embeddings
OpenHands uses llama-index for embeddings. You can find their documentation on Azure [here](https://docs.llamaindex.ai/en/stable/api_reference/embeddings/azure_openai/)
OpenHands uses llama-index for embeddings. You can find their documentation on Azure [here](https://docs.llamaindex.ai/en/stable/api_reference/embeddings/azure_openai/).
### Azure openai configs
@@ -50,6 +53,6 @@ When running OpenHands in Docker, set the following environment variables using
```
LLM_EMBEDDING_MODEL="azureopenai"
LLM_EMBEDDING_DEPLOYMENT_NAME="<your-embedding-deployment-name>" # e.g. "TextEmbedding...<etc>"
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
LLM_EMBEDDING_DEPLOYMENT_NAME="<your-embedding-deployment-name>" # e.g. "TextEmbedding...<etc>"
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
```
+1 -1
View File
@@ -2,7 +2,7 @@
## Completion
OpenHands uses LiteLLM for completion calls. The following resources are relevant for using OpenHands with Google's LLMs
OpenHands uses LiteLLM for completion calls. The following resources are relevant for using OpenHands with Google's LLMs:
- [Gemini - Google AI Studio](https://docs.litellm.ai/docs/providers/gemini)
- [VertexAI - Google Cloud Platform](https://docs.litellm.ai/docs/providers/vertex)
+41 -24
View File
@@ -1,46 +1,63 @@
---
sidebar_position: 2
sidebar_position: 3
---
# 🤖 LLM Backends
OpenHands can connect to many LLMs. However, the recommended models to use are GPT-4 and Claude 3.5.
OpenHands can connect to any LLM supported by LiteLLM. However, it requires a powerful model to work.
The following are verified by the community to work with OpenHands:
Current local and open source models are not nearly as powerful. When using an alternative model, you may see long
wait times between messages, poor responses, or errors about malformed JSON. OpenHands can only be as powerful as the
models driving it.
For a full list of the LM providers and models available, please consult the
[litellm documentation](https://docs.litellm.ai/docs/providers).
* claude-3-5-sonnet
* gemini-1.5-pro / gemini-1.5-flash
* gpt-4 / gpt-4o
* llama-3.1-405b / hermes-3-llama-3.1-405b
* wizardlm-2-8x22b
:::warning
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money--be sure to set spending limits and monitor usage.
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money, so be sure to set spending
limits and monitor usage.
:::
If you have successfully run OpenHands with specific LLMs not in the list, please add them to the verified list. We
also encourage you to open a PR to share your setup process to help others using the same provider and LLM!
For a full list of the providers and models available, please consult the
[litellm documentation](https://docs.litellm.ai/docs/providers).
## Local and Open Source Models
Most current local and open source models are not as powerful. When using such models, you may see long
wait times between messages, poor responses, or errors about malformed JSON. OpenHands can only be as powerful as the
models driving it. However, if you do find ones that work, please add them to the verified list above.
## LLM Configuration
The `LLM_MODEL` environment variable controls which model is used in programmatic interactions.
But when using the OpenHands UI, you'll need to choose your model in the settings window.
The following environment variables might be necessary for some LLMs/providers:
- `LLM_API_KEY`
- `LLM_BASE_URL`
- `LLM_EMBEDDING_MODEL`
- `LLM_EMBEDDING_DEPLOYMENT_NAME`
- `LLM_API_VERSION`
- `LLM_DROP_PARAMS`
* `LLM_API_KEY`
* `LLM_API_VERSION`
* `LLM_BASE_URL`
* `LLM_EMBEDDING_MODEL`
* `LLM_EMBEDDING_DEPLOYMENT_NAME`
* `LLM_DROP_PARAMS`
* `LLM_DISABLE_VISION`
* `LLM_CACHING_PROMPT`
We have a few guides for running OpenHands with specific model providers:
- [OpenAI](llms/openai-llms)
- [ollama](llms/local-llms)
- [Azure](llms/azure-llms)
- [Google](llms/google-llms)
* [Azure](llms/azure-llms)
* [Google](llms/google-llms)
* [ollama](llms/local-llms)
* [OpenAI](llms/openai-llms)
If you're using another provider, we encourage you to open a PR to share your setup!
## API retries and rate limits
### API retries and rate limits
Some LLMs have rate limits and may require retries. OpenHands will automatically retry requests if it receives a 429 error or API connection error.
You can set the following environment variables to control the number of retries and the time between retries:
* `LLM_NUM_RETRIES` (Default of 5)
* `LLM_RETRY_MIN_WAIT` (Default of 3 seconds)
* `LLM_RETRY_MAX_WAIT` (Default of 60 seconds)
* `LLM_NUM_RETRIES` (Default of 8)
* `LLM_RETRY_MIN_WAIT` (Default of 15 seconds)
* `LLM_RETRY_MAX_WAIT` (Default of 120 seconds)
+8 -4
View File
@@ -1,7 +1,11 @@
# Local LLM with Ollama
:::warning
When using a Local LLM, OpenHands may have limited functionality.
:::
Ensure that you have the Ollama server up and running.
For detailed startup instructions, refer to [here](https://github.com/ollama/ollama)
For detailed startup instructions, refer to [here](https://github.com/ollama/ollama).
This guide assumes you've started ollama with `ollama serve`. If you're running ollama differently (e.g. inside docker), the instructions might need to be modified. Please note that if you're running WSL the default ollama configuration blocks requests from docker containers. See [here](#configuring-ollama-service-wsl-en).
@@ -28,7 +32,7 @@ starcoder2:latest f67ae0f64584 1.7 GB 19 hours ago
### Docker
Use the instructions [here](../intro) to start OpenHands using Docker.
Use the instructions [here](../getting-started) to start OpenHands using Docker.
But when running `docker run`, you'll need to add a few more arguments:
```bash
@@ -196,9 +200,9 @@ base_url="http://localhost:1234/v1"
custom_llm_provider="openai"
```
Done! Now you can start Devin by: `make run` without Docker. You now should be able to connect to `http://localhost:3000/`
Done! Now you can start OpenHands by: `make run` without Docker. You now should be able to connect to `http://localhost:3000/`
# Note:
# Note
For WSL, run the following commands in cmd to set up the networking mode to mirrored:
+4 -56
View File
@@ -4,72 +4,20 @@ OpenHands uses [LiteLLM](https://www.litellm.ai/) to make calls to OpenAI's chat
## Configuration
### Manual Configuration
When running the OpenHands Docker image, you'll need to set the following environment variables:
```sh
LLM_MODEL="openai/<gpt-model-name>" # e.g. "openai/gpt-4o"
LLM_API_KEY="<your-openai-project-api-key>"
```
When running the OpenHands Docker image, you'll need to choose a model and set your API key in the OpenHands UI through the Settings.
To see a full list of OpenAI models that LiteLLM supports, please visit https://docs.litellm.ai/docs/providers/openai#openai-chat-completion-models.
To find or create your OpenAI Project API Key, please visit https://platform.openai.com/api-keys.
**Example**:
```sh
export WORKSPACE_BASE=$(pwd)/workspace
docker run -it \
--pull=always \
-e SANDBOX_USER_ID=$(id -u) \
-e LLM_MODEL="openai/<gpt-model-name>" \
-e LLM_API_KEY="<your-openai-project-api-key>" \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-v $WORKSPACE_BASE:/opt/workspace_base \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/opendevin/opendevin:0.8
```
### UI Configuration
You can also directly set the `LLM_MODEL` and `LLM_API_KEY` in the OpenHands client itself. Follow this guide to get up and running with the OpenHands client.
From there, you can set your model and API key in the settings window.
## Using OpenAI-Compatible Endpoints
Just as for OpenAI Chat completions, we use LiteLLM for OpenAI-compatible endpoints. You can find their full documentation on this topic [here](https://docs.litellm.ai/docs/providers/openai_compatible).
When running the OpenHands Docker image, you'll need to set the following environment variables:
When running the OpenHands Docker image, you'll need to set the following environment variables using `-e`:
```sh
LLM_BASE_URL="<api-base-url>" # e.g. "http://0.0.0.0:3000"
LLM_MODEL="openai/<model-name>" # e.g. "openai/mistral"
LLM_API_KEY="<your-api-key>"
LLM_BASE_URL="<api-base-url>" # e.g. "http://0.0.0.0:3000"
```
**Example**:
```sh
export WORKSPACE_BASE=$(pwd)/workspace
docker run -it \
--pull=always \
-e SANDBOX_USER_ID=$(id -u) \
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-e LLM_BASE_URL="<api-base-url>" \
-e LLM_MODEL="openai/<model-name>" \
-e LLM_API_KEY="<your-api-key>" \
-v $WORKSPACE_BASE:/opt/workspace_base \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 3000:3000 \
--add-host host.docker.internal:host-gateway \
--name openhands-app-$(date +%Y%m%d%H%M%S) \
ghcr.io/opendevin/opendevin:0.8
```
Then set your model and API key in the OpenHands UI through the Settings.
+8 -8
View File
@@ -14,7 +14,7 @@
"@docusaurus/theme-mermaid": "^3.5.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"prism-react-renderer": "^2.4.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
@@ -24,7 +24,7 @@
"@docusaurus/module-type-aliases": "^3.5.1",
"@docusaurus/tsconfig": "^3.5.2",
"@docusaurus/types": "^3.5.1",
"typescript": "~5.5.4"
"typescript": "~5.6.2"
},
"engines": {
"node": ">=18.0"
@@ -12640,9 +12640,9 @@
}
},
"node_modules/prism-react-renderer": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz",
"integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz",
"integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==",
"dependencies": {
"@types/prismjs": "^1.26.0",
"clsx": "^2.0.0"
@@ -14853,9 +14853,9 @@
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
+2 -2
View File
@@ -21,7 +21,7 @@
"@docusaurus/theme-mermaid": "^3.5.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"prism-react-renderer": "^2.4.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
@@ -31,7 +31,7 @@
"@docusaurus/module-type-aliases": "^3.5.1",
"@docusaurus/tsconfig": "^3.5.2",
"@docusaurus/types": "^3.5.1",
"typescript": "~5.5.4"
"typescript": "~5.6.2"
},
"browserslist": {
"production": [
+72 -1
View File
@@ -1,8 +1,79 @@
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
const sidebars: SidebarsConfig = {
docsSidebar: [{ type: "autogenerated", dirName: "usage" }],
apiSidebar: [require("./modules/python/sidebar.json")],
docsSidebar: [{
type: 'doc',
label: 'Getting Started',
id: 'usage/getting-started',
}, {
type: 'doc',
label: 'Troubleshooting',
id: 'usage/troubleshooting/troubleshooting',
}, {
type: 'doc',
label: 'Feedback',
id: 'usage/feedback',
}, {
type: 'category',
label: 'How-to Guides',
items: [{
type: 'doc',
id: 'usage/how-to/cli-mode',
}, {
type: 'doc',
id: 'usage/how-to/headless-mode',
}, {
type: 'doc',
id: 'usage/how-to/custom-sandbox-guide',
}, {
type: 'doc',
id: 'usage/how-to/evaluation-harness',
}, {
type: 'doc',
id: 'usage/how-to/openshift-example',
}]
}, {
type: 'category',
label: 'LLMs',
items: [{
type: 'doc',
label: 'Overview',
id: 'usage/llms/llms',
}, {
type: 'doc',
label: 'OpenAI',
id: 'usage/llms/openai-llms',
}, {
type: 'doc',
label: 'Azure',
id: 'usage/llms/azure-llms',
}, {
type: 'doc',
label: 'Google',
id: 'usage/llms/google-llms',
}, {
type: 'doc',
label: 'Local/ollama',
id: 'usage/llms/local-llms',
}],
}, {
type: 'category',
label: 'Architecture',
items: [{
type: 'doc',
label: 'Backend',
id: 'usage/architecture/backend',
}, {
type: 'doc',
label: 'Runtime',
id: 'usage/architecture/runtime',
}],
}, {
type: 'doc',
label: 'About',
id: 'usage/about',
}],
};
export default sidebars;
+1 -12
View File
@@ -7,17 +7,6 @@ function CustomFooter() {
return (
<footer className="custom-footer">
<div className="footer-content">
<div className="footer-top">
<div className="footer-title">
<Translate id="footer.title">OpenHands</Translate>
</div>
<div className="footer-link">
<a href="/modules/usage/intro">
<Translate id="footer.docs">Docs</Translate>
</a>
</div>
</div>
<div className="footer-icons">
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA" target="_blank" rel="noopener noreferrer">
<FaSlack />
@@ -32,7 +21,7 @@ function CustomFooter() {
<div className="footer-bottom">
<p>
<Translate id="footer.copyright" values={{ year: new Date().getFullYear() }}>
{'Copyright © {year} OpenHands'}
{'Copyright © {year} All Hands AI, Inc'}
</Translate>
</p>
</div>
@@ -17,23 +17,19 @@ export function HomepageHeader() {
<p className="header-subtitle">{siteConfig.tagline}</p>
<div className="header-links">
<a href="https://github.com/All-Hands-AI/OpenHands">
<img src="https://img.shields.io/badge/Code-Github-purple?logo=github&logoColor=white&style=for-the-badge" alt="Code" />
</a>
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA">
<img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community" />
</a>
<a href="https://discord.gg/ESHStjSjD4">
<img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community" />
</a>
<a href="https://arxiv.org/abs/2407.16741">
<img src="https://img.shields.io/badge/Paper-%20on%20Arxiv-red?logo=arxiv&style=for-the-badge" alt="Paper on Arxiv" />
</a>
<a href="https://huggingface.co/spaces/OpenDevin/evaluation">
<img src="https://img.shields.io/badge/Evaluation-Benchmark%20on%20HF%20Space-green?logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark" />
</a>
<div align="center" className="header-links">
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors"><img src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Contributors" /></a>
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers" /></a>
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" /></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License" /></a>
<br/>
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community" /></a>
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community" /></a>
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="Credits" /></a>
<br/>
<a href="https://docs.all-hands.dev/modules/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="Check out the documentation" /></a>
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Paper on Arxiv" /></a>
<a href="https://huggingface.co/spaces/OpenHands/evaluation"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark Score" /></a>
</div>
<Demo />
+9 -13
View File
@@ -7,8 +7,10 @@
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #4465db;
--ifm-code-font-size: 95%;
--ifm-color-primary: #000;
--ifm-background-color: #F1EAE0;
--ifm-navbar-background-color: #F1EAE0;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
--secondary: #171717;
--secondary-dark: #0a0a0a;
@@ -17,21 +19,15 @@
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme="dark"] {
--ifm-color-primary: #4465db;
--ifm-color-primary: #FFF;
--ifm-background-color: #000;
--ifm-navbar-background-color: #000;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
--secondary: #737373;
--secondary-dark: #171717;
--secondary-light: #d4d4d4;
--secondary-light: #ccc;
}
.footer--dark {
background-image: linear-gradient(
140deg,
var(--secondary) 20%,
var(--secondary-light) 100%
);
}
.a {
p a, .a {
text-decoration: underline;
}
}
+9 -8
View File
@@ -2,13 +2,19 @@
.custom-footer {
background-color: dark;
color: white;
height: 200px;
color: #000;
height: 100px;
/* background: linear-gradient(to bottom, #1a1a1a, #1a1a1a); */
background: linear-gradient(to bottom, #1f2937, #000000);
background-color: #F1EAE0;
}
[data-theme="dark"] .custom-footer {
background-color: #000;
color: #fff;
}
.footer-content {
display: flex;
flex-direction: column;
@@ -47,7 +53,6 @@
}
.footer-community {
text-transform: uppercase;
font-weight: 300;
}
@@ -65,7 +70,3 @@
.footer-icons a:hover {
color: white;
}
.footer-bottom {
text-transform: uppercase;
}
-2
View File
@@ -2,8 +2,6 @@
.homepage-header {
height: 800px;
color: white;
background: linear-gradient(to top, #64748b, #000000);
}
.header-content {
+1 -2
View File
@@ -20,8 +20,7 @@ export default function Home(): JSX.Element {
title={`${siteConfig.title}`}
description={translate({
id: 'homepage.description',
message: 'An Open Platform for AI Software Developers as Generalist Agents',
description: 'The homepage description',
message: 'Code Less, Make More',
})}
>
<HomepageHeader />
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

+4 -4
View File
@@ -9,12 +9,12 @@ To better organize the evaluation folder, we should follow the rules below:
- Each subfolder contains a specific benchmark or experiment. For example, `evaluation/swe_bench` should contain
all the preprocessing/evaluation/analysis scripts.
- Raw data and experimental records should not be stored within this repo.
- For model outputs, they should be stored at [this huggingface space](https://huggingface.co/spaces/OpenDevin/evaluation) for visualization.
- For model outputs, they should be stored at [this huggingface space](https://huggingface.co/spaces/OpenHands/evaluation) for visualization.
- Important data files of manageable size and analysis scripts (e.g., jupyter notebooks) can be directly uploaded to this repo.
## Supported Benchmarks
To learn more about how to integrate your benchmark into OpenHands, check out [tutorial here](https://docs.all-hands.dev/modules/usage/evaluation_harness).
To learn more about how to integrate your benchmark into OpenHands, check out [tutorial here](https://docs.all-hands.dev/modules/usage/how-to/evaluation-harness).
### Software Engineering
@@ -69,8 +69,8 @@ temperature = 0.0
### Result Visualization
Check [this huggingface space](https://huggingface.co/spaces/OpenDevin/evaluation) for visualization of existing experimental results.
Check [this huggingface space](https://huggingface.co/spaces/OpenHands/evaluation) for visualization of existing experimental results.
### Upload your results
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results to our hosted huggingface repo via PR following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results to our hosted huggingface repo via PR following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
+1 -1
View File
@@ -26,7 +26,7 @@ poetry run python evaluation/miniwob/get_success_rate.py evaluation/evaluation_o
## Submit your evaluation results
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
## BrowsingAgent V1.0 result
+47 -23
View File
@@ -19,27 +19,16 @@ Please follow instruction [here](../README.md#setup) to setup your local develop
OpenHands now support using the [official evaluation docker](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md) for both **[inference](#run-inference-on-swe-bench-instances) and [evaluation](#evaluate-generated-patches)**.
This is now the default behavior.
### Download Docker Images
**(Recommended for reproducibility)** If you have extra local space (e.g., 100GB), you can try pull the [instance-level docker images](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md#choosing-the-right-cache_level) we've prepared by running:
```bash
evaluation/swe_bench/scripts/docker/pull_all_eval_docker.sh instance
```
If you want to save disk space a bit (e.g., with ~50GB free disk space), while speeding up the image pre-build process, you can pull the environment-level docker images:
```bash
evaluation/swe_bench/scripts/docker/pull_all_eval_docker.sh env
```
## Run Inference on SWE-Bench Instances
Make sure your Docker daemon is running, and you have pulled the [instance-level docker image](#openhands-swe-bench-instance-level-docker-support).
Make sure your Docker daemon is running, and you have ample disk space (at least 200-500GB, depends on the SWE-Bench set you are running on) for the [instance-level docker image](#openhands-swe-bench-instance-level-docker-support).
When the `run_infer.sh` script is started, it will automatically pull the relavant SWE-Bench images. For example, for instance ID `django_django-11011`, it will try to pull our pre-build docker image `sweb.eval.x86_64.django_s_django-11011` from DockerHub. This image will be used create an OpenHands runtime image where the agent will operate on.
```bash
./evaluation/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers]
# e.g., ./evaluation/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 300
./evaluation/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
# e.g., ./evaluation/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 300 30 1 princeton-nlp/SWE-bench_Lite test
```
where `model_config` is mandatory, and the rest are optional.
@@ -57,6 +46,8 @@ in order to use `eval_limit`, you must also set `agent`.
default, it is set to 30.
- `num_workers`, e.g. `3`, is the number of parallel workers to run the evaluation. By
default, it is set to 1.
- `dataset`, a huggingface dataset name. e.g. `princeton-nlp/SWE-bench` or `princeton-nlp/SWE-bench_Lite`, specifies which dataset to evaluate on.
- `dataset_split`, split for the huggingface dataset. e.g., `test`, `dev`. Default to `test`.
There are also two optional environment variables you can set.
```
@@ -72,11 +63,22 @@ then your command would be:
./evaluation/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 10
```
**Evaluate on `RemoteRuntime` (alpha)** (contact Xingyao over slack if you want to try this out!)
### Run Inference on `RemoteRuntime`
This is in limited beta. Contact Xingyao over slack if you want to try this out!
```bash
SANDBOX_API_KEY="CONTACT-XINGYAO-TO-GET-A-TESTING-API-KEY" RUNTIME=remote EVAL_DOCKER_IMAGE_PREFIX="us-docker.pkg.dev/evaluation-428620/swe-bench-images" ./evaluation/swe_bench/scripts/run_infer.sh llm.eval HEAD CodeActAgent 300
# ./evaluation/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
ALLHANDS_API_KEY="YOUR-API-KEY" RUNTIME=remote EVAL_DOCKER_IMAGE_PREFIX="us-docker.pkg.dev/evaluation-428620/swe-bench-images" \
./evaluation/swe_bench/scripts/run_infer.sh llm.eval HEAD CodeActAgent 300 30 16 "princeton-nlp/SWE-bench_Lite" test
# This example runs evaluation on CodeActAgent for 300 instances on "princeton-nlp/SWE-bench_Lite"'s test set, with max 30 iteration per instances, with 16 number of workers running in parallel
```
To clean-up all existing runtime you've already started, run:
```bash
ALLHANDS_API_KEY="YOUR-API-KEY" ./evaluation/swe_bench/scripts/cleanup_remote_runtime.sh
```
Multi-processing is still WIP.
### Specify a subset of tasks to run infer
@@ -95,13 +97,35 @@ After running the inference, you will obtain a `output.jsonl` (by default it wil
## Evaluate Generated Patches
### Download Docker Images
**(Recommended for reproducibility)** If you have extra local space (e.g., 200GB), you can try pull the [instance-level docker images](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md#choosing-the-right-cache_level) we've prepared by running:
```bash
evaluation/swe_bench/scripts/docker/pull_all_eval_docker.sh instance
```
If you want to save disk space a bit (e.g., with ~50GB free disk space), while speeding up the image pre-build process, you can pull the environment-level docker images:
```bash
evaluation/swe_bench/scripts/docker/pull_all_eval_docker.sh env
```
If you want to evaluate on the full SWE-Bench test set:
```bash
evaluation/swe_bench/scripts/docker/pull_all_eval_docker.sh instance full
```
### Run evaluation
With `output.jsonl` file, you can run `eval_infer.sh` to evaluate generated patches, and produce a fine-grained report.
**This evaluation is performed using the official dockerized evaluation announced [here](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md).**
> If you want to evaluate existing results, you should first run this to clone existing outputs
>```bash
>git clone https://huggingface.co/spaces/OpenDevin/evaluation evaluation/evaluation_outputs
>git clone https://huggingface.co/spaces/OpenHands/evaluation evaluation/evaluation_outputs
>```
NOTE, you should have already pulled the instance-level OR env-level docker images following [this section](#openhands-swe-bench-instance-level-docker-support).
@@ -135,10 +159,10 @@ The final results will be saved to `evaluation/evaluation_outputs/outputs/swe_be
## Visualize Results
First you need to clone `https://huggingface.co/spaces/OpenDevin/evaluation` and add your own running results from openhands into the `outputs` of the cloned repo.
First you need to clone `https://huggingface.co/spaces/OpenHands/evaluation` and add your own running results from openhands into the `outputs` of the cloned repo.
```bash
git clone https://huggingface.co/spaces/OpenDevin/evaluation
git clone https://huggingface.co/spaces/OpenHands/evaluation
```
**(optional) setup streamlit environment with conda**:
@@ -162,4 +186,4 @@ Then you can access the SWE-Bench trajectory visualizer at `localhost:8501`.
## Submit your evaluation results
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
+41 -14
View File
@@ -25,8 +25,7 @@ from openhands.core.config import (
AppConfig,
SandboxConfig,
get_llm_config_arg,
load_from_env,
parse_arguments,
get_parser,
)
from openhands.core.logger import openhands_logger as logger
from openhands.core.main import create_runtime, run_controller
@@ -109,6 +108,11 @@ def get_config(
if USE_INSTANCE_IMAGE:
# We use a different instance image for the each instance of swe-bench eval
base_container_image = get_instance_docker_image(instance['instance_id'])
logger.info(
f'Using instance container image: {base_container_image}. '
f'Please make sure this image exists. '
f'Submit an issue on https://github.com/All-Hands-AI/OpenHands if you run into any issues.'
)
else:
base_container_image = SWE_BENCH_CONTAINER_IMAGE
logger.info(f'Using swe-bench container image: {base_container_image}')
@@ -118,26 +122,19 @@ def get_config(
run_as_openhands=False,
max_budget_per_task=4,
max_iterations=metadata.max_iterations,
runtime=os.environ.get('RUNTIME', 'eventstream'),
sandbox=SandboxConfig(
base_container_image=base_container_image,
enable_auto_lint=True,
use_host_network=False,
# large enough timeout, since some testcases take very long to run
timeout=300,
api_key=os.environ.get('ALLHANDS_API_KEY', None),
),
# do not mount workspace
workspace_base=None,
workspace_mount_path=None,
)
selected_env_vars = {'runtime', 'sandbox_api_key'}
selected_env_vars = {
k: v for k, v in os.environ.items() if k.lower() in selected_env_vars
}
if selected_env_vars:
logger.info(
f'Loading config keys from env vars: {list(selected_env_vars.keys())}'
)
load_from_env(config, selected_env_vars)
config.set_llm_config(metadata.llm_config)
return config
@@ -160,12 +157,14 @@ def initialize_runtime(
action = CmdRunAction(
command=f"""echo 'export SWE_INSTANCE_ID={instance['instance_id']}' >> ~/.bashrc && echo 'export PIP_CACHE_DIR=~/.cache/pip' >> ~/.bashrc && echo "alias git='git --no-pager'" >> ~/.bashrc"""
)
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
assert obs.exit_code == 0
action = CmdRunAction(command="""export USER=$(whoami); echo USER=${USER} """)
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
@@ -204,18 +203,21 @@ def initialize_runtime(
'/swe_util/',
)
action = CmdRunAction(command='cat ~/.bashrc')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
assert obs.exit_code == 0
action = CmdRunAction(command='source ~/.bashrc')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
assert obs.exit_code == 0
action = CmdRunAction(command='source /swe_util/instance_swe_entry.sh')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
@@ -237,6 +239,7 @@ def initialize_runtime(
assert obs.exit_code == 0
action = CmdRunAction(command='git reset --hard')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
@@ -245,6 +248,7 @@ def initialize_runtime(
action = CmdRunAction(
command='for remote_name in $(git remote); do git remote remove "${remote_name}"; done'
)
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
@@ -272,18 +276,21 @@ def complete_runtime(
workspace_dir_name = _get_swebench_workspace_dir_name(instance)
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
assert obs.exit_code == 0
action = CmdRunAction(command='git config --global core.pager ""')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
assert obs.exit_code == 0
action = CmdRunAction(command='git add -A')
action.timeout = 600
logger.info(action, extra={'msg_type': 'ACTION'})
obs = runtime.run_action(action)
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
@@ -411,12 +418,26 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame:
if __name__ == '__main__':
args = parse_arguments()
parser = get_parser()
parser.add_argument(
'--dataset',
type=str,
default='princeton-nlp/SWE-bench',
help='data set to evaluate on, either full-test or lite-test',
)
parser.add_argument(
'--split',
type=str,
default='test',
help='split to evaluate on',
)
args, _ = parser.parse_known_args()
# NOTE: It is preferable to load datasets from huggingface datasets and perform post-processing
# so we don't need to manage file uploading to OpenHands's repo
dataset = load_dataset('princeton-nlp/SWE-bench_Lite')
swe_bench_tests = filter_dataset(dataset['test'].to_pandas(), 'instance_id')
dataset = load_dataset(args.dataset, split=args.split)
logger.info(f'Loaded dataset {args.dataset} with split {args.split}')
swe_bench_tests = filter_dataset(dataset.to_pandas(), 'instance_id')
llm_config = None
if args.llm_config:
@@ -445,6 +466,12 @@ if __name__ == '__main__':
output_file = os.path.join(metadata.eval_output_dir, 'output.jsonl')
instances = prepare_dataset(swe_bench_tests, output_file, args.eval_n_limit)
if len(instances) > 0 and not isinstance(
instances['PASS_TO_PASS'][instances['PASS_TO_PASS'].index[0]], str
):
for col in ['PASS_TO_PASS', 'FAIL_TO_PASS']:
instances[col] = instances[col].apply(lambda x: str(x))
run_evaluation(
instances, metadata, output_file, args.eval_num_workers, process_instance
)
@@ -0,0 +1,21 @@
#!/bin/bash
# API base URL
BASE_URL="https://api.all-hands.dev/v0"
# Get the list of runtimes
runtimes=$(curl --silent --location --request GET "${BASE_URL}/runtime/list" \
--header "X-API-Key: ${ALLHANDS_API_KEY}" | jq -r '.runtimes | .[].runtime_id')
# Loop through each runtime and stop it
for runtime_id in $runtimes; do
echo "Stopping runtime: ${runtime_id}"
curl --silent --location --request POST "${BASE_URL}/runtime/stop" \
--header "X-API-Key: ${ALLHANDS_API_KEY}" \
--header "Content-Type: application/json" \
--data-raw "{\"runtime_id\": \"${runtime_id}\"}"
echo
done
echo "All runtimes have been stopped."
File diff suppressed because it is too large Load Diff
@@ -6,19 +6,33 @@ LEVEL=$1
# - base, keyword "sweb.base"
# - env, keyword "sweb.env"
# - instance, keyword "sweb.eval"
SET=$2
if [ -z "$LEVEL" ]; then
echo "Usage: $0 <cache_level>"
echo "Usage: $0 <cache_level> <set>"
echo "cache_level: base, env, or instance"
echo "set: lite, full"
exit 1
fi
if [ -z "$SET" ]; then
echo "Usage: $0 <cache_level> <set>"
echo "cache_level: base, env, or instance"
echo "set: lite, full, default is lite"
SET="lite"
fi
NAMESPACE=$2 # xingyaoww
if [ -z "$NAMESPACE" ]; then
echo "Default to namespace: xingyaoww"
NAMESPACE="xingyaoww"
fi
IMAGE_FILE="$(dirname "$0")/all-swebench-lite-instance-images.txt"
if [ "$SET" == "lite" ]; then
IMAGE_FILE="$(dirname "$0")/all-swebench-lite-instance-images.txt"
else
IMAGE_FILE="$(dirname "$0")/all-swebench-full-instance-images.txt"
fi
# Define a pattern based on the level
case $LEVEL in
@@ -0,0 +1,79 @@
"""You should first perform the following steps:
1. Build the docker images. Install SWE-Bench first (https://github.com/princeton-nlp/SWE-bench). Then run:
```bash
export DATASET_NAME=princeton-nlp/SWE-bench_Lite
export SPLIT=test
export MAX_WORKERS=4
export RUN_ID=some-random-ID
python -m swebench.harness.run_evaluation \
--dataset_name $DATASET_NAME \
--split $SPLIT \
--predictions_path gold \
--max_workers $MAX_WORKERS \
--run_id $RUN_ID \
--cache_level instance
```
2. Then run this script to push the docker images to the docker hub. Some of the docker images might fail to build in the previous step - start an issue in the SWE-Bench repo for possible fixes.
To push the docker images for "princeton-nlp/SWE-bench_Lite" test set to the docker hub (e.g., under `docker.io/xingyaoww/`), run:
```bash
EVAL_DOCKER_IMAGE_PREFIX='docker.io/xingyaoww/' python3 evaluation/swe_bench/scripts/docker/push_docker_instance_images.py --dataset princeton-nlp/SWE-bench_Lite --split test
```
"""
import argparse
import docker
from datasets import load_dataset
from tqdm import tqdm
from openhands.core.logger import openhands_logger as logger
logger.setLevel('ERROR')
from evaluation.swe_bench.run_infer import get_instance_docker_image # noqa
parser = argparse.ArgumentParser()
parser.add_argument('--dataset', type=str, default='princeton-nlp/SWE-bench_Lite')
parser.add_argument('--split', type=str, default='test')
args = parser.parse_args()
dataset = load_dataset(args.dataset, split=args.split)
client = docker.from_env()
pbar = tqdm(total=len(dataset))
counter = {'success': 0, 'failed': 0}
failed_instances = []
for instance in dataset:
instance_id = instance['instance_id']
image_name = f'sweb.eval.x86_64.{instance_id}'
target_image_name = get_instance_docker_image(instance_id)
print('-' * 100)
# check if image exists
try:
image: docker.models.images.Image = client.images.get(image_name)
image.tag(target_image_name)
print(f'Image {image_name} -- tagging to --> {target_image_name}')
ret_push = client.images.push(target_image_name)
if isinstance(ret_push, str):
print(ret_push)
else:
for line in ret_push:
print(line)
print(f'Image {image_name} -- pushed to --> {target_image_name}')
counter['success'] += 1
except docker.errors.ImageNotFound:
print(f'ERROR: Image {image_name} does not exist')
counter['failed'] += 1
failed_instances.append(instance_id)
finally:
pbar.update(1)
pbar.set_postfix(counter)
print(f'Success: {counter["success"]}, Failed: {counter["failed"]}')
print('Failed instances IDs:')
for failed_instance in failed_instances:
print(failed_instance)
+18 -1
View File
@@ -9,6 +9,8 @@ AGENT=$3
EVAL_LIMIT=$4
MAX_ITER=$5
NUM_WORKERS=$6
DATASET=$7
SPLIT=$8
if [ -z "$NUM_WORKERS" ]; then
NUM_WORKERS=1
@@ -31,6 +33,17 @@ if [ -z "$USE_INSTANCE_IMAGE" ]; then
USE_INSTANCE_IMAGE=true
fi
if [ -z "$DATASET" ]; then
echo "DATASET not specified, use default princeton-nlp/SWE-bench_Lite"
DATASET="princeton-nlp/SWE-bench_Lite"
fi
if [ -z "$SPLIT" ]; then
echo "SPLIT not specified, use default test"
SPLIT="test"
fi
export USE_INSTANCE_IMAGE=$USE_INSTANCE_IMAGE
echo "USE_INSTANCE_IMAGE: $USE_INSTANCE_IMAGE"
@@ -39,6 +52,8 @@ get_agent_version
echo "AGENT: $AGENT"
echo "AGENT_VERSION: $AGENT_VERSION"
echo "MODEL_CONFIG: $MODEL_CONFIG"
echo "DATASET: $DATASET"
echo "SPLIT: $SPLIT"
# Default to NOT use Hint
if [ -z "$USE_HINT_TEXT" ]; then
@@ -59,7 +74,9 @@ COMMAND="poetry run python evaluation/swe_bench/run_infer.py \
--max-iterations $MAX_ITER \
--max-chars 10000000 \
--eval-num-workers $NUM_WORKERS \
--eval-note $EVAL_NOTE"
--eval-note $EVAL_NOTE \
--dataset $DATASET \
--split $SPLIT"
if [ -n "$EVAL_LIMIT" ]; then
echo "EVAL_LIMIT: $EVAL_LIMIT"
+1 -1
View File
@@ -37,7 +37,7 @@ poetry run python evaluation/webarena/get_success_rate.py evaluation/evaluation_
## Submit your evaluation results
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
## BrowsingAgent V1.0 result
+40 -40
View File
@@ -1,12 +1,12 @@
{
"name": "openhands-frontend",
"version": "0.9.1",
"version": "0.9.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openhands-frontend",
"version": "0.9.1",
"version": "0.9.2",
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"@nextui-org/react": "^2.4.6",
@@ -17,7 +17,7 @@
"@xterm/xterm": "^5.4.0",
"clsx": "^2.1.1",
"eslint-config-airbnb-typescript": "^18.0.0",
"i18next": "^23.14.0",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jose": "^5.8.0",
@@ -33,7 +33,7 @@
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0",
"tailwind-merge": "^2.5.2",
"vite": "^5.4.2",
"vite": "^5.4.4",
"web-vitals": "^3.5.2"
},
"devDependencies": {
@@ -41,8 +41,8 @@
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.5.1",
"@types/react": "^18.3.4",
"@types/node": "^22.5.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
@@ -59,13 +59,13 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"husky": "^9.1.5",
"husky": "^9.1.6",
"jsdom": "^25.0.0",
"lint-staged": "^15.2.9",
"postcss": "^8.4.41",
"lint-staged": "^15.2.10",
"postcss": "^8.4.45",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.4",
"tailwindcss": "^3.4.11",
"typescript": "^5.6.2",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^1.6.0"
},
@@ -4857,9 +4857,9 @@
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
},
"node_modules/@types/node": {
"version": "22.5.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz",
"integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==",
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"devOptional": true,
"dependencies": {
"undici-types": "~6.19.2"
@@ -4871,9 +4871,9 @@
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.3.4",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz",
"integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==",
"version": "18.3.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
"integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -8118,9 +8118,9 @@
}
},
"node_modules/husky": {
"version": "9.1.5",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.5.tgz",
"integrity": "sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==",
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz",
"integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==",
"dev": true,
"bin": {
"husky": "bin.js"
@@ -8133,9 +8133,9 @@
}
},
"node_modules/i18next": {
"version": "23.14.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.14.0.tgz",
"integrity": "sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==",
"version": "23.15.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.1.tgz",
"integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==",
"funding": [
{
"type": "individual",
@@ -9020,9 +9020,9 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/lint-staged": {
"version": "15.2.9",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz",
"integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==",
"version": "15.2.10",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz",
"integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==",
"dev": true,
"dependencies": {
"chalk": "~5.3.0",
@@ -9031,7 +9031,7 @@
"execa": "~8.0.1",
"lilconfig": "~3.1.2",
"listr2": "~8.2.4",
"micromatch": "~4.0.7",
"micromatch": "~4.0.8",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
"yaml": "~2.5.0"
@@ -10812,9 +10812,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
"integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
"version": "8.4.45",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
"funding": [
{
"type": "opencollective",
@@ -12423,9 +12423,9 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
"version": "3.4.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz",
"integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -12800,9 +12800,9 @@
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -13109,12 +13109,12 @@
}
},
"node_modules/vite": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
"integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
"version": "5.4.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.4.tgz",
"integrity": "sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==",
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.41",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "openhands-frontend",
"version": "0.9.1",
"version": "0.9.2",
"private": true,
"type": "module",
"engines": {
@@ -16,7 +16,7 @@
"@xterm/xterm": "^5.4.0",
"clsx": "^2.1.1",
"eslint-config-airbnb-typescript": "^18.0.0",
"i18next": "^23.14.0",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jose": "^5.8.0",
@@ -32,7 +32,7 @@
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0",
"tailwind-merge": "^2.5.2",
"vite": "^5.4.2",
"vite": "^5.4.4",
"web-vitals": "^3.5.2"
},
"scripts": {
@@ -64,8 +64,8 @@
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.5.1",
"@types/react": "^18.3.4",
"@types/node": "^22.5.4",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/react-highlight": "^0.12.8",
"@types/react-syntax-highlighter": "^15.5.13",
@@ -82,13 +82,13 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"husky": "^9.1.5",
"husky": "^9.1.6",
"jsdom": "^25.0.0",
"lint-staged": "^15.2.9",
"postcss": "^8.4.41",
"lint-staged": "^15.2.10",
"postcss": "^8.4.45",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.4",
"tailwindcss": "^3.4.11",
"typescript": "^5.6.2",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^1.6.0"
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 B

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

+32 -472
View File
@@ -1,472 +1,32 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M5195 8749 c-122 -19 -282 -101 -383 -197 -66 -61 -59 -81 52 -160
49 -34 99 -65 111 -68 16 -4 44 9 94 41 93 60 169 94 256 112 l70 14 82 -42
c46 -23 83 -44 83 -48 -1 -3 -34 -21 -74 -40 -217 -98 -499 -312 -667 -505
-138 -157 -309 -430 -309 -493 0 -38 66 -100 149 -140 42 -20 77 -38 79 -39 2
-1 -17 -32 -43 -70 -185 -277 -278 -669 -246 -1041 20 -245 61 -392 167 -612
35 -74 63 -135 62 -135 -2 -1 -30 -10 -62 -21 -38 -13 -67 -30 -79 -47 -19
-26 -19 -29 -3 -71 23 -61 101 -184 166 -262 99 -119 265 -247 425 -327 149
-75 397 -152 602 -188 l72 -13 -63 -67 c-139 -147 -205 -389 -139 -503 11 -18
40 -43 70 -58 50 -26 51 -28 33 -44 -11 -10 -40 -58 -66 -109 -77 -149 -235
-389 -267 -407 -17 -10 -269 -65 -352 -78 -38 -6 -115 -15 -169 -20 -74 -8
-107 -15 -128 -31 -21 -16 -47 -21 -110 -24 -61 -3 -121 -15 -223 -46 -77 -23
-153 -49 -168 -57 -16 -8 -52 -51 -84 -101 -31 -48 -69 -103 -85 -122 -54 -66
-31 -122 68 -159 47 -18 48 -19 18 -25 -49 -10 -352 -44 -694 -76 -314 -30
-326 -32 -347 -55 -19 -22 -31 -25 -85 -25 -91 0 -104 -12 -112 -103 -8 -94 3
-121 58 -142 23 -8 174 -56 336 -106 162 -50 445 -137 628 -195 183 -57 482
-149 665 -205 182 -55 367 -111 410 -125 43 -13 102 -24 130 -24 97 0 1501
180 1564 201 46 15 77 70 77 135 0 82 -43 130 -126 141 -24 4 -59 16 -78 29
-22 14 -129 46 -280 83 -135 34 -255 64 -268 67 -20 6 -16 10 30 31 29 13 63
34 75 45 22 21 24 21 122 5 124 -21 158 -21 217 -1 46 15 48 15 108 -13 64
-29 144 -37 231 -21 22 4 29 4 16 0 -24 -7 -23 -8 15 -19 68 -20 155 -35 169
-29 8 3 15 0 17 -6 3 -7 10 -8 17 -4 11 7 11 10 0 17 -10 6 -8 9 9 9 14 0 21
-4 16 -11 -4 -7 0 -9 14 -5 12 4 128 -13 259 -38 131 -25 362 -67 513 -95 815
-146 759 -135 707 -143 -27 -3 -51 -9 -54 -11 -2 -3 38 -12 89 -21 51 -10 125
-23 163 -31 98 -19 336 -53 420 -60 68 -5 69 -5 30 8 -22 7 -73 19 -114 27
-41 8 -70 15 -64 17 11 4 120 -12 607 -92 154 -25 281 -43 284 -40 8 7 -16 13
-153 39 -407 77 -958 186 -963 190 -8 8 15 135 48 268 46 182 103 350 221 643
144 360 219 605 288 936 69 337 104 670 99 964 -3 232 -6 241 -89 282 -50 25
-234 64 -414 88 -66 9 -134 18 -151 21 l-31 4 57 60 c233 242 426 579 505 884
14 55 30 105 35 112 23 27 71 180 78 247 4 40 3 110 -1 157 -11 105 -11 95 -2
210 5 73 3 111 -9 164 -31 131 -107 254 -204 328 -26 20 -42 40 -42 54 0 12
-27 74 -59 137 -204 399 -520 697 -963 912 -375 181 -795 278 -1268 292 -338
10 -691 -35 -972 -123 l-77 -25 -233 109 c-295 138 -320 146 -413 131z m107
-89 c20 -6 65 -24 100 -40 l63 -30 -65 6 c-68 6 -173 -10 -260 -41 -52 -18
-72 -32 -87 -59 -10 -18 -14 -14 -37 32 l-26 52 47 23 c68 34 154 60 213 66 8
0 32 -4 52 -9z m1843 -110 c448 -52 824 -167 1126 -343 307 -179 534 -393 691
-652 63 -102 101 -175 96 -181 -2 -2 -26 12 -52 31 -27 19 -64 39 -82 45 -19
7 -34 15 -34 18 -1 31 -32 148 -48 178 -32 63 -65 88 -105 80 -29 -5 -35 -3
-48 20 -8 15 -44 52 -81 83 -178 150 -544 337 -968 495 -129 48 -139 50 -235
50 -130 -1 -217 -23 -378 -96 -86 -40 -93 -59 -44 -126 41 -55 141 -152 157
-152 6 0 34 12 63 26 100 51 255 94 338 94 51 0 160 -42 324 -126 173 -88 320
-176 394 -236 l61 -49 -35 -30 c-19 -17 -38 -42 -41 -54 -3 -14 -16 -26 -32
-30 -63 -16 -56 -59 31 -192 l73 -110 -39 -44 c-53 -61 -138 -191 -180 -274
-64 -130 -120 -339 -134 -502 l-6 -72 -71 -11 c-39 -6 -87 -13 -107 -16 l-36
-5 -13 73 c-56 317 -249 568 -557 726 -254 130 -574 200 -1108 244 -339 27
-1062 15 -1363 -23 -46 -6 -85 -9 -88 -7 -6 7 63 131 124 220 365 540 1013
885 1797 958 120 11 484 5 610 -10z m-2169 -15 c20 -30 11 -81 -15 -88 -18 -4
-81 28 -81 41 0 6 70 70 77 72 2 0 10 -11 19 -25z m2662 -299 c67 -27 122 -52
122 -57 0 -12 -22 -7 -35 7 -5 6 -26 14 -45 17 -19 3 -44 9 -54 13 -14 5 -17
4 -12 -4 4 -7 2 -12 -3 -12 -6 0 -11 5 -11 11 0 6 -7 9 -15 6 -8 -4 -17 -2
-20 3 -3 5 -12 7 -20 4 -8 -4 -29 -1 -46 5 -24 8 -36 8 -45 0 -11 -9 -14 -8
-14 2 0 11 -2 11 -9 0 -6 -10 -30 -16 -68 -17 -32 -2 -58 -4 -58 -4 0 -1 -8
-3 -17 -5 -9 -2 -19 4 -22 12 -9 21 -29 4 -22 -19 2 -12 -1 -27 -9 -34 -18
-18 -25 -18 -25 1 0 9 -7 30 -16 46 -8 17 -13 34 -10 37 21 20 149 42 236 39
87 -2 105 -6 218 -51z m-478 -25 c17 -32 12 -61 -10 -61 -10 0 -23 -8 -29 -17
-9 -16 -12 -15 -40 11 -17 17 -31 33 -31 37 0 7 77 47 92 49 4 0 12 -9 18 -19z
m710 -76 c10 6 102 -34 112 -49 4 -6 8 -8 8 -4 0 4 13 2 30 -3 16 -6 28 -14
25 -19 -2 -4 8 -13 23 -20 22 -11 24 -14 9 -18 -21 -4 -47 14 -47 34 0 14 -73
14 -101 0 -8 -4 -5 -1 5 8 17 14 17 16 3 22 -9 3 -15 10 -12 14 3 5 -5 5 -17
2 -19 -5 -20 -7 -5 -10 20 -4 22 -17 5 -28 -7 -5 -8 -3 -3 6 6 10 4 12 -8 7
-9 -3 -23 1 -32 9 -8 8 -20 12 -26 8 -8 -4 -9 -3 -5 5 4 6 16 9 27 6 13 -4 19
-2 19 9 0 9 -5 15 -11 14 -6 -2 -13 3 -14 10 -2 7 -1 8 2 2 2 -5 8 -8 13 -5z
m384 -187 c-3 -4 -9 -8 -15 -8 -5 0 -9 4 -9 8 0 5 7 9 15 9 8 0 12 -4 9 -9z
m241 -144 c39 -28 83 -65 99 -82 l29 -31 -25 -6 c-40 -10 -61 -2 -79 30 -9 17
-37 51 -63 77 -25 26 -46 53 -46 58 0 6 -5 8 -12 4 -7 -4 -8 -3 -4 5 9 13 11
12 101 -55z m-61 -152 c7 -12 -101 -68 -111 -59 -11 12 58 66 84 67 12 0 24
-4 27 -8z m348 -64 l18 -48 -73 0 c-85 0 -150 -16 -246 -60 -68 -32 -95 -33
-84 -5 12 33 204 102 306 112 37 3 64 9 60 14 -10 9 -157 -14 -225 -37 -29 -9
-82 -32 -117 -50 -74 -39 -96 -41 -131 -13 l-25 21 36 -5 c25 -4 54 3 95 20
126 53 162 65 244 83 116 26 121 24 142 -32z m-24 -113 c-2 -3 -29 -9 -59 -15
-30 -6 -97 -31 -149 -56 -93 -45 -94 -45 -66 -15 32 34 111 68 182 80 59 10
99 13 92 6z m317 -219 c113 -53 219 -169 265 -292 18 -51 19 -59 6 -85 -8 -17
-10 -31 -5 -35 5 -3 13 5 18 18 8 19 9 16 9 -19 0 -22 3 -46 6 -52 4 -6 5 -11
3 -11 -2 0 -16 17 -30 39 -20 30 -23 41 -14 51 9 10 9 11 -3 8 -9 -3 -35 14
-63 39 -46 43 -156 103 -188 103 -9 0 -19 10 -23 23 -8 32 -60 144 -91 195
l-26 42 43 0 c24 0 65 -11 93 -24z m-250 -76 c41 -67 79 -166 70 -181 -4 -5
-12 -9 -18 -9 -31 0 -158 -72 -208 -118 -43 -39 -68 -74 -103 -144 -95 -192
-101 -405 -16 -586 18 -38 27 -69 22 -74 -16 -16 -88 -11 -134 10 -86 37 -172
152 -184 245 -4 26 -11 47 -17 47 -17 0 1 160 28 256 51 176 158 351 286 468
62 56 201 146 226 146 6 0 28 -27 48 -60z m-295 2 c0 -5 -34 -43 -75 -86 -41
-42 -96 -107 -122 -144 -39 -54 -49 -63 -51 -45 -10 67 166 283 230 283 10 0
18 -4 18 -8z m-3625 -38 c-42 -65 -90 -153 -129 -237 -35 -76 -41 -97 -34
-122 13 -48 0 -84 -38 -107 -19 -11 -34 -21 -34 -22 0 -1 -9 -42 -19 -91 -36
-162 -47 -318 -41 -525 3 -69 5 -55 11 75 11 230 41 363 78 341 5 -3 12 2 15
11 6 14 19 15 109 9 91 -6 142 -11 142 -16 0 -1 10 -3 22 -6 18 -3 27 4 44 37
11 23 22 39 24 37 2 -2 26 -69 53 -148 28 -80 73 -201 101 -270 62 -148 61
-145 67 -240 7 -93 -16 -250 -45 -310 -13 -26 -40 -59 -67 -79 -74 -56 -104
-68 -156 -64 -27 2 -48 -1 -48 -6 0 -5 -8 -11 -17 -13 -16 -3 -15 -7 8 -29 14
-14 32 -29 40 -32 12 -5 12 -7 -1 -16 -10 -7 -17 -6 -22 2 -6 9 -8 9 -8 -1 0
-10 -5 -10 -22 -1 -20 11 -22 11 -10 -3 10 -13 10 -18 1 -24 -8 -4 -9 -3 -5 4
6 11 2 13 -19 12 -3 0 -26 11 -50 26 -27 16 -54 24 -66 21 -18 -5 -28 10 -79
116 -68 142 -111 268 -142 411 -18 85 -22 134 -22 311 0 218 13 336 54 466 11
37 15 75 12 112 -5 67 14 107 52 107 20 0 31 13 62 73 42 81 75 136 112 185
18 23 32 32 54 32 l30 0 -17 -26z m1046 16 c27 -15 24 -24 -18 -71 -49 -54
-89 -78 -198 -119 -195 -72 -303 -179 -415 -411 -22 -46 -40 -78 -40 -72 0 9
-82 51 -115 58 -29 6 -46 19 -41 31 3 7 0 16 -6 20 -7 4 -8 3 -4 -4 7 -12 -6
-17 -17 -6 -18 18 122 230 222 336 113 121 235 201 352 232 63 17 251 21 280
6z m3039 -130 c0 -5 -8 -10 -17 -10 -15 0 -16 2 -3 10 19 12 20 12 20 0z
m-823 -221 c-35 -74 -69 -150 -77 -169 -21 -55 -30 -58 -30 -9 0 100 54 249
113 312 25 27 30 28 43 16 13 -13 7 -31 -49 -150z m952 138 c24 -12 59 -90 76
-166 l7 -34 -38 31 c-54 44 -114 62 -200 62 l-74 0 0 49 c0 48 1 49 38 59 53
14 163 14 191 -1z m-63 -219 c51 -45 74 -99 74 -175 0 -36 -4 -63 -10 -63 -5
0 -10 -9 -10 -19 0 -10 -6 -21 -14 -24 -11 -4 -13 -13 -7 -34 8 -28 -5 -73
-21 -73 -4 0 -7 -6 -5 -12 1 -7 -3 -12 -9 -11 -5 1 -16 -7 -23 -19 -10 -16
-10 -19 1 -12 10 6 10 4 -1 -9 -7 -9 -17 -14 -22 -12 -4 3 -15 -5 -24 -19 -29
-44 -108 -65 -185 -50 -70 14 -121 159 -99 280 29 159 148 283 271 284 39 0
54 -5 84 -32z m-961 -224 c0 -22 -5 -29 -20 -29 -24 0 -34 43 -13 56 21 14 33
4 33 -27z m-1195 -115 c22 -7 40 -16 40 -21 0 -4 6 -8 14 -8 8 0 33 -16 55
-34 71 -59 157 -204 180 -301 14 -60 19 -272 8 -335 -13 -71 -13 -72 -36 -125
-36 -80 -44 -95 -53 -95 -4 0 -8 -6 -8 -14 0 -8 -5 -18 -12 -22 -7 -5 -9 -2
-4 7 4 8 -1 4 -11 -8 -21 -25 -138 -85 -147 -76 -3 4 -6 1 -6 -5 0 -6 -7 -9
-15 -6 -8 4 -15 1 -15 -6 0 -6 -4 -9 -9 -6 -5 3 -26 8 -47 11 -137 20 -247 93
-274 180 -7 22 -19 53 -27 68 -8 16 -16 36 -18 45 -32 137 -36 169 -36 302 0
80 2 152 4 160 12 59 44 140 65 168 34 47 79 92 92 92 6 0 22 8 37 17 47 31
142 36 223 12z m1060 -98 c0 -38 9 -121 20 -184 35 -210 110 -369 229 -488
145 -144 299 -168 529 -83 116 43 296 135 334 172 46 43 43 18 -7 -70 -216
-375 -485 -627 -883 -827 -522 -262 -1255 -395 -1988 -360 -514 24 -883 122
-1173 312 -133 88 -278 228 -334 324 -4 6 39 -12 95 -41 275 -139 616 -208
953 -191 403 19 1003 133 1286 244 238 93 447 251 567 428 129 191 177 368
170 636 l-3 144 90 25 c50 14 96 26 103 27 8 1 12 -19 12 -68z m128 37 c14
-14 16 -94 3 -102 -30 -19 -56 28 -45 83 7 32 22 39 42 19z m1114 -185 c-6
-26 -14 -57 -18 -68 -6 -16 1 -13 36 14 45 33 45 33 -7 -17 -95 -91 -226 -131
-331 -103 l-43 12 22 37 c12 20 24 39 26 41 2 3 36 8 75 12 84 9 159 43 207
93 19 20 37 34 39 32 2 -3 -1 -26 -6 -53z m32 -158 c-4 -8 -10 -15 -16 -15 -5
0 -6 -6 -2 -12 4 -7 4 -10 -1 -6 -4 4 -16 -3 -26 -15 -16 -19 -18 -19 -12 -3
5 15 3 17 -8 11 -19 -11 -2 6 35 33 34 26 37 27 30 7z m-149 -88 c-11 -7 -35
-28 -54 -47 l-35 -35 20 34 c10 18 33 40 49 47 37 16 47 17 20 1z m-297 -84
c-60 -88 -154 -190 -165 -180 -4 4 14 29 39 55 25 26 71 82 102 125 73 100 93
100 24 0z m372 54 c0 -2 -19 -21 -42 -42 l-43 -40 40 43 c36 39 45 47 45 39z
m-215 -146 c3 -6 -4 -15 -15 -21 -20 -11 -21 -10 -10 9 12 23 16 25 25 12z
m-165 -41 c-11 -16 -26 -33 -33 -37 -12 -7 0 11 32 50 23 28 24 20 1 -13z
m-1221 -46 c9 -8 -102 -149 -183 -230 -206 -208 -417 -302 -876 -394 -340 -68
-617 -95 -885 -87 -217 6 -371 31 -539 87 -186 63 -464 202 -425 214 8 2 36
11 63 20 67 21 74 21 126 -15 120 -81 319 -161 504 -201 90 -20 129 -23 366
-22 297 0 442 15 747 79 484 101 758 245 945 496 l50 68 51 -6 c29 -3 54 -7
56 -9z m1341 -640 c104 -15 247 -40 285 -51 13 -4 -3 -12 -50 -25 -91 -25 -99
-25 -170 5 -33 14 -103 38 -155 52 -176 49 -154 53 90 19z m462 -132 c17 -65
0 -429 -33 -692 -57 -461 -173 -905 -341 -1309 -24 -58 -48 -106 -55 -108 -15
-5 -80 53 -98 88 -14 26 -12 37 35 170 28 78 49 146 48 151 -2 5 -25 -44 -52
-109 -27 -65 -93 -217 -146 -338 -54 -121 -103 -237 -111 -257 -14 -42 -17
-44 -43 -25 -17 13 -12 27 82 228 160 343 224 502 316 784 123 378 198 842
182 1120 -6 111 -28 229 -46 251 -12 13 17 27 90 43 113 26 166 27 172 3z
m-1252 -392 c63 -5 215 -10 336 -10 l222 0 7 -27 c3 -16 9 -131 12 -256 6
-245 -6 -401 -35 -449 -21 -36 -69 -58 -126 -58 -40 0 -46 3 -52 25 -3 14 -3
36 0 51 4 14 7 123 7 242 1 282 -18 394 -70 421 -10 6 -90 15 -177 21 -150 10
-240 27 -274 50 -12 8 -9 9 10 5 14 -2 77 -9 140 -15z m-275 -35 c241 -62 374
-242 375 -505 0 -115 -19 -144 -133 -199 -101 -50 -171 -69 -306 -82 -113 -11
-271 1 -340 26 -170 60 -231 160 -201 333 33 197 175 352 364 398 39 9 85 23
101 30 40 17 69 17 140 -1z m-1874 -81 l36 -6 -14 -41 c-9 -27 -12 -57 -8 -85
5 -33 3 -47 -9 -55 -8 -7 -24 -41 -36 -77 -12 -36 -36 -90 -54 -120 -18 -30
-41 -76 -51 -102 -16 -42 -21 -48 -47 -48 -45 0 -97 29 -115 63 -29 57 9 210
78 315 38 58 150 162 174 162 5 0 26 -3 46 -6z m126 -131 c10 -12 12 -13 8 -3
-4 8 1 6 10 -5 9 -11 19 -19 23 -18 4 2 15 0 25 -3 13 -4 15 -9 8 -18 -14 -17
-2 -29 15 -15 10 8 11 5 7 -12 -4 -15 -3 -20 5 -15 7 4 12 3 12 -2 0 -5 10
-12 22 -15 12 -4 20 -9 17 -12 -9 -8 -88 30 -105 51 -9 10 -13 13 -9 7 10 -22
-23 -3 -38 20 -18 28 -28 57 -20 57 3 0 12 -8 20 -17z m180 -74 c21 -8 24 -12
14 -19 -17 -10 -38 -3 -45 16 -6 17 -5 17 31 3z m724 -40 c-6 -24 -11 -78 -11
-120 l0 -76 -46 -36 c-180 -140 -693 -157 -854 -29 -53 43 -72 87 -66 149 6
53 25 106 35 101 3 -2 44 -22 91 -45 114 -57 238 -86 365 -86 160 -1 284 42
435 151 30 22 56 39 58 37 1 -1 -2 -22 -7 -46z m-711 -9 c-20 -13 -33 -13 -25
0 3 6 14 10 23 10 15 0 15 -2 2 -10z m-309 -254 c73 -120 247 -173 536 -163
222 8 391 54 495 137 28 23 38 27 46 17 14 -17 47 -143 63 -241 14 -87 38
-131 135 -242 29 -34 50 -64 47 -68 -3 -3 -1 -6 6 -6 6 0 28 -15 47 -32 33
-30 36 -37 31 -73 -7 -53 -15 -89 -33 -155 -25 -89 -24 -87 -34 -70 -6 8 -10
11 -10 5 0 -5 -12 4 -26 20 -14 17 -22 24 -19 17 4 -7 -31 14 -78 47 -161 116
-495 262 -714 312 -109 25 -337 36 -411 20 -75 -16 -140 -49 -159 -82 -10 -16
-21 -29 -25 -29 -28 0 -46 210 -29 330 18 118 77 301 98 300 4 0 20 -20 34
-44z m2441 -248 c74 -8 112 -8 159 1 33 7 64 10 67 6 16 -16 -161 -611 -192
-642 -28 -30 -107 -6 -121 37 -3 11 16 97 43 192 65 224 94 358 78 358 -6 0
-42 5 -79 10 -62 9 -93 24 -150 71 -5 3 16 0 45 -7 29 -8 97 -19 150 -26z
m-233 -47 c24 -18 23 -18 -120 -24 -86 -3 -202 -15 -289 -30 -156 -28 -310
-46 -310 -38 0 3 21 27 47 53 l47 48 91 0 c133 0 262 16 346 43 l74 23 45 -28
c25 -15 56 -36 69 -47z m-2433 51 c7 -5 8 -32 3 -82 -10 -104 4 -236 32 -300
18 -42 38 -65 96 -111 91 -72 88 -78 -3 -9 -113 87 -224 129 -336 130 l-47 0
74 110 c41 60 92 146 112 190 37 78 46 88 69 72z m2448 -162 l59 0 -6 -94
c-12 -175 -77 -427 -156 -600 l-31 -69 -28 53 c-63 125 -217 236 -362 262 -59
10 -186 0 -235 -18 -10 -4 -8 11 8 58 17 51 21 89 21 200 l1 138 45 5 c25 3
126 17 225 32 226 32 320 42 365 37 19 -2 62 -4 94 -4z m-1904 -334 c0 -88 24
-218 59 -316 l29 -83 -81 6 c-78 5 -84 7 -110 39 -60 71 -98 153 -104 227 -9
112 18 152 122 177 87 21 85 22 85 -50z m225 34 c141 -29 340 -116 488 -213
49 -33 73 -54 65 -58 -38 -21 -228 -50 -385 -60 -164 -9 -211 -16 -254 -34
-14 -5 -21 6 -37 57 -36 109 -42 141 -48 239 l-7 96 59 -7 c33 -4 86 -13 119
-20z m-969 -20 c42 -21 84 -70 103 -123 23 -61 2 -238 -31 -258 -48 -30 -558
-89 -558 -65 0 4 5 16 10 27 48 89 14 270 -62 323 -12 9 -20 17 -17 19 2 2 49
11 104 21 55 9 150 30 210 46 134 34 185 37 241 10z m-730 -164 c28 -9 63 -25
78 -37 24 -19 26 -27 26 -92 0 -40 -5 -89 -10 -109 -30 -106 -187 -190 -249
-134 -12 11 -21 25 -21 33 0 7 31 46 69 85 l69 73 -42 2 c-22 1 -65 5 -93 8
l-53 6 0 49 c0 68 -24 94 -84 95 -25 0 -46 1 -46 2 0 5 48 27 75 34 46 12 223
2 281 -15z m204 -18 c15 -24 20 -49 19 -93 l-2 -60 -7 67 c-3 36 -14 78 -23
92 -10 14 -15 26 -12 26 3 0 14 -15 25 -32z m1049 -164 c17 -28 31 -54 31 -57
0 -3 -15 -8 -32 -12 -18 -4 -68 -22 -110 -41 -43 -18 -115 -43 -160 -54 -45
-11 -92 -29 -103 -40 -12 -11 -31 -20 -43 -20 -24 0 -303 48 -308 53 -2 2 36
9 84 15 193 26 356 84 439 156 23 20 61 66 84 102 l43 65 21 -58 c12 -32 36
-81 54 -109z m-1776 111 c-12 -8 -51 -57 -87 -108 -87 -122 -95 -132 -102
-124 -4 4 11 30 32 59 22 29 55 77 73 106 18 29 45 59 60 67 35 19 51 19 24 0z
m245 3 c17 -17 15 -42 -7 -69 -16 -20 -28 -23 -79 -24 -43 0 -65 5 -79 18 -11
9 -25 17 -33 17 -7 0 -1 -9 14 -21 14 -11 26 -26 26 -33 0 -7 -14 -41 -31 -75
-34 -68 -68 -92 -131 -90 -26 0 -29 2 -14 8 14 5 16 10 7 15 -6 4 -17 3 -24
-3 -7 -5 -18 -6 -25 -2 -10 6 5 36 57 113 38 57 77 111 86 118 40 33 208 53
233 28z m2962 -43 c66 -23 125 -77 159 -147 26 -52 31 -73 31 -135 0 -104 -26
-202 -57 -214 -74 -28 -500 -69 -716 -69 -91 0 -99 2 -92 18 4 9 12 42 17 72
17 102 -20 226 -93 305 -48 52 -49 51 71 60 112 8 299 46 435 88 123 39 183
44 245 22z m265 -52 c26 -25 36 -60 25 -88 -10 -25 -30 -35 -30 -14 0 7 -13
34 -30 61 -16 26 -30 50 -30 53 0 12 50 3 65 -12z m538 -21 c58 -4 59 -5 53
-32 -3 -15 -29 -80 -58 -143 l-53 -116 -48 -1 c-27 0 -46 3 -44 8 73 124 127
223 127 231 0 6 -18 11 -40 11 -41 0 -48 9 -34 45 4 10 12 14 23 9 9 -4 42 -9
74 -12z m-3726 -149 c-9 -28 -7 -38 9 -61 60 -81 178 -81 289 -1 l45 32 117
-27 c65 -15 214 -46 330 -69 l213 -42 -61 -49 c-57 -46 -61 -52 -56 -83 5 -28
3 -33 -14 -33 -28 0 -299 62 -534 122 -110 28 -294 72 -410 98 l-210 47 60 31
c33 16 77 43 98 59 36 27 42 29 86 19 l48 -9 -10 -34z m1868 -9 l116 -18 -49
-18 c-80 -30 -87 -43 -88 -172 l-1 -111 -66 -3 -65 -3 10 35 c8 26 8 42 -2 66
-18 43 -49 61 -109 62 -45 0 -52 3 -64 26 -9 19 -22 28 -47 32 -26 4 -35 11
-40 30 -4 19 -13 26 -38 28 -42 4 -40 18 6 38 88 37 221 40 437 8z m319 -68
c22 -80 29 -129 17 -121 -6 3 -11 18 -11 33 0 32 -22 107 -45 153 l-16 34 21
-24 c11 -13 26 -46 34 -75z m-839 6 c-6 -5 -28 -14 -51 -20 -63 -18 -104 -51
-165 -132 -31 -41 -70 -83 -85 -92 -32 -20 -69 -23 -79 -8 -7 11 173 187 214
209 20 12 82 30 171 50 2 1 0 -3 -5 -7z m76 -36 c0 -5 -24 -17 -53 -26 -82
-26 -114 -46 -212 -134 -50 -45 -100 -85 -111 -89 -16 -6 -15 -3 5 13 14 11
27 20 30 20 3 0 34 36 69 80 67 83 106 109 199 135 52 15 73 15 73 1z m605
-27 c14 -16 18 -34 17 -70 -3 -74 -12 -84 -72 -83 -28 0 -53 3 -56 6 -3 3 -6
34 -6 69 -1 54 2 66 21 81 30 24 73 23 96 -3z m-2335 -21 l52 -12 -66 -12
c-57 -11 -72 -10 -124 6 -47 15 -53 19 -33 23 49 9 119 7 171 -5z m1819 -6 c0
-4 -23 -16 -52 -26 -35 -12 -72 -37 -114 -78 -34 -33 -65 -59 -68 -58 -4 1
-31 -11 -61 -26 -53 -25 -105 -28 -105 -4 0 16 163 142 216 167 29 13 78 27
110 31 59 8 74 7 74 -6z m-2055 -19 l40 -8 -40 -5 c-50 -6 -105 -1 -105 11 0
11 50 11 105 2z m-190 -28 c29 -14 26 -15 -48 -11 -43 1 -83 8 -90 15 -17 17
101 14 138 -4z m623 3 l47 -12 -55 -11 c-59 -12 -116 -5 -171 19 -34 14 -33
14 49 15 46 1 104 -4 130 -11z m-903 -18 c27 -7 27 -8 5 -8 -14 0 -36 4 -50 8
-20 6 -21 8 -5 8 11 0 34 -4 50 -8z m2689 -12 c7 -12 7 -26 0 -45 -9 -27 -12
-28 -82 -28 -62 0 -72 -2 -72 -17 0 -23 -67 -67 -113 -75 -45 -7 -87 3 -87 21
0 19 123 134 162 151 47 21 177 16 192 -7z m-2802 -5 l68 -17 -60 -13 c-55
-11 -67 -10 -138 12 l-77 23 45 4 c25 3 56 5 70 6 14 1 55 -6 92 -15z m727 -5
c36 -6 62 -13 56 -14 -5 -2 -44 2 -85 10 -41 8 -66 14 -56 15 11 1 49 -4 85
-11z m441 -7 l55 -18 -50 -7 c-31 -4 -78 -2 -120 5 l-70 13 50 12 c66 16 71
16 135 -5z m-625 -2 c13 -6 0 -7 -40 -3 -33 4 -62 8 -64 10 -9 8 85 2 104 -7z
m-242 -6 c-13 -2 -35 -2 -50 0 -16 2 -5 4 22 4 28 0 40 -2 28 -4z m4772 -13
c25 -28 8 -45 -31 -30 -20 8 -115 9 -316 4 -384 -9 -384 8 0 28 158 8 296 16
308 17 11 0 29 -8 39 -19z m-4229 1 c-3 -5 -15 -7 -26 -4 -28 7 -25 13 6 13
14 0 23 -4 20 -9z m47 -8 c-7 -2 -19 -2 -25 0 -7 3 -2 5 12 5 14 0 19 -2 13
-5z m452 -9 l40 -7 -41 -18 c-41 -17 -78 -15 -164 7 -22 6 -19 8 25 19 49 13
61 12 140 -1z m-640 -3 c14 -6 5 -8 -30 -9 -27 -1 -57 3 -65 8 -19 12 65 13
95 1z m232 3 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-622 -47
c122 -32 331 -89 455 -124 l55 -15 -34 -14 c-42 -18 -66 -14 -251 41 -80 24
-219 64 -310 89 -222 60 -237 68 -130 62 52 -3 136 -18 215 -39z m160 33 c19
-5 6 -6 -35 -3 -36 3 -67 7 -69 9 -8 7 73 2 104 -6z m383 -41 c-13 -9 -88 -3
-118 8 -11 4 -8 8 10 12 33 9 126 -9 108 -20z m194 10 c-15 -5 -36 -6 -55 0
-29 8 -27 9 23 9 44 1 50 -1 32 -9z m508 -10 l55 -13 -64 -10 c-51 -7 -78 -6
-140 10 -73 17 -75 18 -41 25 44 9 118 4 190 -12z m-925 1 c27 -7 28 -8 5 -8
-14 0 -45 3 -70 7 l-45 8 40 1 c22 0 54 -4 70 -8z m1742 -15 c-15 -14 -36 -25
-45 -25 -10 0 0 11 23 24 52 31 57 31 22 1z m-1098 -25 c21 -13 8 -12 -69 5
l-65 15 59 -5 c32 -3 66 -10 75 -15z m-474 -15 c6 -3 -16 -3 -48 -2 -32 2 -62
8 -68 14 -10 10 92 -1 116 -12z m175 15 c-12 -8 -12 -10 -1 -10 8 0 12 -2 9
-5 -3 -3 -25 -3 -49 1 -34 6 -39 9 -24 15 11 4 34 8 50 8 23 1 26 -2 15 -9z
m800 -14 l45 -13 -61 -7 c-62 -8 -184 9 -184 25 0 22 122 19 200 -5z m-722 7
c-10 -2 -28 -2 -40 0 -13 2 -5 4 17 4 22 1 32 -1 23 -4z m357 -18 l40 -14 -58
3 c-32 1 -63 8 -69 14 -17 17 35 15 87 -3z m-1473 -16 c44 -12 77 -23 75 -26
-4 -4 -158 36 -166 43 -9 8 14 4 91 -17z m1263 0 c26 0 27 -2 10 -9 -25 -11
-102 -12 -135 -3 -23 7 -21 8 15 14 22 4 49 5 60 3 11 -2 34 -4 50 -5z m1210
1 c4 -7 -3 -8 -22 -4 -38 9 -42 14 -10 14 14 0 29 -5 32 -10z m1393 3 c-10 -2
-26 -2 -35 0 -10 3 -2 5 17 5 19 0 27 -2 18 -5z m-1841 -20 c52 -9 90 -20 85
-25 -20 -20 -113 -21 -198 -2 l-85 19 48 11 c26 6 50 12 52 13 3 0 47 -7 98
-16z m-1027 -3 l25 -8 -25 0 c-14 -1 -43 3 -65 7 l-40 9 40 0 c22 0 51 -4 65
-8z m745 -21 l40 -13 -42 -9 c-52 -12 -226 22 -182 35 32 10 134 2 184 -13z
m-1085 -152 c578 -168 1031 -304 1038 -312 18 -18 -78 6 -332 81 -160 47 -356
104 -435 125 -225 63 -901 279 -826 265 11 -2 261 -74 555 -159z m769 144 c25
-16 -6 -22 -76 -15 -47 5 -61 10 -48 15 27 10 107 11 124 0z m-259 -11 c19 -6
12 -8 -30 -8 -30 0 -64 3 -75 8 -26 11 70 11 105 0z m1148 -28 c97 -34 108
-42 56 -42 -30 0 -36 3 -24 11 11 8 -1 9 -44 5 -35 -3 -91 1 -135 10 -104 21
-114 27 -61 34 88 13 128 10 208 -18z m2355 -9 c-2 -10 -11 -27 -18 -37 -14
-18 -17 -18 -97 -2 -139 27 -154 35 -73 34 39 0 81 4 95 10 14 6 41 11 62 11
30 1 35 -2 31 -16z m-2978 -22 c44 -11 113 -25 154 -32 l74 -11 -32 -14 c-19
-8 -45 -14 -60 -14 -24 0 -290 69 -351 91 -47 17 132 0 215 -20z m245 22 c73
-5 74 -5 46 -20 -23 -12 -39 -13 -99 -3 -133 22 -105 35 53 23z m-815 -31 c64
-21 61 -27 -22 -37 -42 -4 -71 0 -140 22 l-88 28 50 6 c69 9 130 4 200 -19z
m225 19 l25 -7 -30 -6 c-16 -4 -57 0 -90 7 l-60 13 65 0 c36 0 76 -3 90 -7z
m765 -28 c31 -5 35 -8 20 -14 -30 -12 -86 -9 -125 6 l-35 13 50 1 c28 0 68 -2
90 -6z m508 -24 c26 -6 47 -14 47 -17 0 -15 -139 -11 -210 7 l-75 18 95 1 c52
1 116 -3 143 -9z m107 -19 c-36 -11 -52 -11 -45 0 3 6 21 10 38 9 31 -1 31 -1
7 -9z m-359 -14 l34 -14 -28 -7 c-29 -8 -172 1 -217 13 -24 6 -23 7 10 13 65
12 167 9 201 -5z m-986 -10 c28 -7 66 -20 85 -28 l35 -15 -83 -6 c-75 -6 -89
-4 -160 22 -68 26 -74 29 -47 33 61 10 121 7 170 -6z m1544 -14 l75 -17 -78
-14 c-74 -12 -83 -11 -160 11 l-81 23 65 6 c36 3 74 6 85 7 11 1 53 -7 94 -16z
m-819 -2 c0 -5 -6 -10 -12 -11 -7 0 -4 -4 7 -9 40 -17 -59 -10 -125 10 l-65
19 98 0 c58 1 97 -3 97 -9z m-181 -38 c20 -7 11 -10 -52 -19 -65 -9 -86 -8
-144 7 -65 17 -66 18 -33 23 49 7 200 0 229 -11z m743 -1 c11 -12 10 -13 -3
-8 -8 3 -38 1 -65 -5 -56 -10 -211 10 -177 24 32 13 231 5 245 -11z m-265 -30
c15 -9 13 -10 -12 -11 -17 0 -45 -4 -62 -9 -24 -7 -49 -5 -95 9 l-63 18 40 5
c57 7 172 0 192 -12z m-730 -15 l37 -13 -46 -6 c-47 -6 -143 14 -163 35 -13
14 123 1 172 -16z m488 -16 c14 -5 -1 -11 -55 -20 -60 -10 -87 -10 -135 0 -63
12 -115 34 -60 24 17 -2 57 0 90 5 59 9 121 6 160 -9z m1154 -44 c-2 -2 -13 8
-23 22 l-19 27 24 -22 c12 -12 21 -24 18 -27z m-1420 -6 c12 -7 8 -10 -19 -11
-53 -3 -80 1 -114 17 -29 13 -27 14 43 9 40 -3 81 -10 90 -15z m1351 17 c0 -2
-9 -12 -20 -22 -24 -21 -27 -14 -4 9 15 15 24 20 24 13z m35 -17 c3 -6 -1 -7
-9 -4 -18 7 -21 14 -7 14 6 0 13 -4 16 -10z m2322 -26 c-3 -3 -12 -4 -19 -1
-8 3 -5 6 6 6 11 1 17 -2 13 -5z m-2383 -57 c7 -6 6 -7 -4 -4 -8 2 -16 14 -17
28 -1 19 -1 20 4 4 3 -11 10 -24 17 -28z"/>
<path d="M5283 8633 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z"/>
<path d="M5218 8623 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
<path d="M5113 8592 c-23 -10 -48 -23 -55 -30 -11 -11 97 33 111 44 10 9 -17
2 -56 -14z"/>
<path d="M7275 8230 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
-7 -4 -4 -10z"/>
<path d="M9280 6916 c0 -2 7 -9 15 -16 9 -7 15 -8 15 -2 0 5 -7 12 -15 16 -8
3 -15 4 -15 2z"/>
<path d="M8555 7085 c-33 -30 -55 -54 -49 -55 6 0 17 6 23 13 6 8 28 22 49 31
29 13 38 23 38 42 0 13 0 24 0 24 -1 0 -28 -25 -61 -55z"/>
<path d="M8865 7010 c-16 -7 -17 -9 -3 -9 9 -1 20 4 23 9 7 11 7 11 -20 0z"/>
<path d="M8760 6979 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
-5 -10 -11z"/>
<path d="M4910 6295 c0 -8 4 -15 8 -15 8 0 32 -43 32 -57 0 -3 -8 1 -17 8 -16
13 -17 12 -4 -3 7 -10 17 -15 22 -13 12 8 36 -32 59 -100 22 -64 25 -77 10
-55 -8 12 -10 11 -10 -7 0 -15 6 -23 18 -24 10 0 12 -3 5 -6 -9 -3 -13 -31
-12 -91 0 -58 3 -80 9 -67 7 15 9 16 9 4 1 -9 -6 -22 -14 -29 -17 -14 -20 -40
-5 -40 6 0 6 -9 -1 -26 -5 -15 -9 -45 -7 -66 2 -39 2 -39 18 -17 39 51 55 120
55 239 0 93 -5 128 -23 184 -25 75 -67 143 -108 175 -32 25 -44 26 -44 6z"/>
<path d="M4622 5910 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z"/>
<path d="M4632 5840 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
<path d="M4760 5454 c0 -5 7 -18 15 -28 8 -11 15 -16 15 -10 0 5 -7 18 -15 28
-8 11 -15 16 -15 10z"/>
<path d="M7960 4356 c24 -26 60 -52 60 -42 0 2 -19 19 -42 37 -40 30 -41 31
-18 5z"/>
<path d="M8027 4286 c-3 -8 1 -13 11 -12 9 0 16 -5 14 -12 -1 -8 2 -11 8 -7 8
5 6 13 -6 26 -20 23 -20 23 -27 5z"/>
<path d="M8143 3935 c0 -27 2 -38 4 -22 2 15 2 37 0 50 -2 12 -4 0 -4 -28z"/>
<path d="M8131 3834 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
<path d="M4667 2598 c-22 -14 46 -12 71 1 8 4 -3 7 -23 7 -20 -1 -42 -4 -48
-8z"/>
<path d="M6554 2467 c-12 -13 4 -87 20 -87 5 0 1 -7 -10 -15 -10 -8 -14 -15
-7 -15 36 -1 56 67 33 111 -12 22 -20 23 -36 6z"/>
<path d="M6520 2441 c0 -12 5 -21 10 -21 6 0 10 6 10 14 0 8 -4 18 -10 21 -5
3 -10 -3 -10 -14z"/>
<path d="M6098 2353 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
<path d="M1455 5297 c-20 -17 -28 -32 -27 -55 0 -17 41 -128 91 -247 90 -217
90 -217 70 -234 -21 -17 -21 -22 -14 -209 7 -173 10 -195 28 -219 20 -25 23
-26 108 -20 62 4 90 2 97 -6 5 -7 71 -156 146 -332 89 -207 133 -321 125 -324
-15 -5 -45 -35 -51 -51 -2 -7 -83 -20 -238 -36 -441 -45 -636 -92 -873 -210
-204 -103 -362 -274 -412 -448 -69 -241 88 -466 431 -615 119 -51 269 -94 489
-138 145 -29 185 -41 206 -60 21 -20 491 -223 643 -279 48 -17 222 -18 600 -3
290 11 307 15 327 65 6 18 5 34 -3 52 -14 29 -107 72 -433 202 -88 35 -165 68
-172 74 -7 6 -17 41 -23 77 -30 195 76 616 194 763 l23 30 79 -17 c148 -31
889 -188 1033 -218 160 -35 178 -33 206 21 20 39 19 42 -95 315 -98 234 -363
899 -605 1515 -201 514 -202 516 -233 536 -28 19 -28 19 -1052 63 -256 11
-504 23 -551 26 -81 6 -88 5 -114 -18z m792 -83 c-3 -3 -12 -4 -19 -1 -8 3 -5
6 6 6 11 1 17 -2 13 -5z m578 -25 c73 -5 19 -6 -160 -3 -148 3 -301 9 -340 14
-86 10 341 1 500 -11z m-947 -135 c23 -4 32 -10 32 -24 0 -24 -2 -24 -105 -8
-76 11 -94 20 -95 44 0 9 38 7 168 -12z m132 -67 c0 -32 0 -32 -45 -29 -42 4
-45 6 -45 33 0 28 2 29 45 29 44 0 45 -1 45 -33z m90 4 c0 -17 -4 -33 -10 -36
-6 -4 -10 8 -10 29 0 20 5 36 10 36 6 0 10 -13 10 -29z m190 4 c0 -11 -5 -12
-23 -6 -14 6 -30 6 -38 0 -19 -12 -32 -11 -24 1 3 6 -5 10 -19 10 -15 0 -26
-7 -29 -17 -4 -14 -5 -12 -6 5 -1 21 3 22 69 22 55 0 70 -3 70 -15z m-421 -1
c21 -4 31 -12 31 -25 0 -17 -3 -17 -45 -2 -68 23 -59 41 14 27z m718 -20 c18
-5 21 -13 21 -48 l-1 -42 -56 8 c-130 19 -121 14 -121 60 l0 41 68 -7 c37 -4
77 -9 89 -12z m-217 -25 c0 -17 -4 -28 -10 -24 -5 3 -10 17 -10 31 0 13 5 24
10 24 6 0 10 -14 10 -31z m308 -16 c2 -8 -5 -13 -17 -13 -12 0 -21 6 -21 16 0
18 31 15 38 -3z m-816 -9 c53 -6 56 -8 61 -38 2 -17 3 -32 2 -34 -2 -2 -46 5
-99 14 -96 17 -96 17 -96 47 0 29 1 29 38 24 20 -4 62 -9 94 -13z m1101 5 c3
-2 8 -21 11 -43 l7 -39 -63 7 c-140 16 -138 15 -138 54 l0 34 90 -4 c49 -2 91
-6 93 -9z m-1253 -96 c0 -5 -5 -15 -10 -23 -8 -12 -10 -11 -10 8 0 12 5 22 10
22 6 0 10 -3 10 -7z m1039 -14 c83 -10 102 -15 106 -30 4 -11 12 -19 19 -19 8
0 19 -7 26 -15 16 -19 9 -19 -145 0 -66 8 -123 15 -127 15 -5 0 -8 14 -8 30 0
35 -6 34 129 19z m-611 -40 c116 -12 157 -29 52 -21 l-60 4 6 -78 c3 -44 7
-92 9 -109 4 -28 4 -29 -10 -11 -31 40 -76 86 -85 86 -15 0 -23 49 -11 64 9
10 4 19 -24 40 -40 31 -45 48 -12 42 12 -3 73 -10 135 -17z m850 -30 c4 -53
-13 -42 -21 14 -4 27 -3 37 6 34 7 -2 13 -23 15 -48z m-1020 10 c-3 -8 -7 -3
-11 10 -4 17 -3 21 5 13 5 -5 8 -16 6 -23z m952 21 c0 -5 -2 -10 -4 -10 -3 0
-8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m477 -210 c30 -69 79 -183 108
-255 29 -71 110 -269 179 -439 69 -170 124 -311 122 -313 -2 -2 -9 11 -16 29
-7 18 -47 107 -87 198 -117 260 -414 977 -413 997 1 14 50 -85 107 -217z
m-1462 196 c18 -13 18 -14 -8 -9 -15 3 -27 9 -27 14 0 13 13 11 35 -5z m24
-24 c54 -7 54 -7 58 -44 4 -38 4 -38 -34 -38 -49 1 -122 15 -133 26 -5 5 -12
21 -15 37 -7 26 -6 27 31 26 22 -1 63 -4 93 -7z m567 -12 l61 0 7 -47 c3 -25
4 -48 1 -51 -2 -2 -16 -2 -30 2 -21 5 -25 12 -25 42 0 19 -4 33 -9 30 -12 -8
-124 14 -136 27 -7 6 4 8 30 3 22 -3 68 -6 101 -6z m-336 -21 c0 -11 -5 -17
-10 -14 -6 4 -8 10 -5 15 3 4 -1 11 -7 13 -8 3 -6 6 5 6 11 1 17 -6 17 -20z
m53 14 c-7 -2 -21 -2 -30 0 -10 3 -4 5 12 5 17 0 24 -2 18 -5z m497 -18 c10
-6 50 -78 50 -90 0 -7 -85 -6 -104 1 -12 5 -16 20 -16 59 l0 54 30 -9 c16 -5
34 -11 40 -15z m-1026 -165 l65 -155 -61 -8 c-34 -4 -64 -4 -68 0 -11 12 -22
340 -11 328 5 -6 39 -80 75 -165z m713 123 c-3 -10 -5 -2 -5 17 0 19 2 27 5
18 2 -10 2 -26 0 -35z m-17 13 c0 -9 -8 -12 -25 -8 -37 7 -41 22 -6 22 20 0
31 -5 31 -14z m-535 -46 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10
3 0 8 -4 11 -10z m82 -22 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z
m51 4 c2 -7 -2 -10 -12 -6 -9 3 -16 11 -16 16 0 13 23 5 28 -10z m-19 -52 c71
-18 91 -30 91 -57 0 -16 1 -16 -97 4 -73 15 -83 20 -83 38 0 17 8 36 14 35 1
0 34 -9 75 -20z m98 -111 c57 -10 61 -13 65 -40 3 -16 3 -29 0 -29 -25 0 -190
35 -199 43 -7 5 -13 18 -13 29 0 22 3 21 147 -3z m661 -33 c3 -33 1 -36 -22
-36 -21 0 -26 6 -32 34 -8 44 0 57 29 47 16 -6 23 -18 25 -45z m248 -6 c1 0 4
-14 6 -31 l5 -31 -65 6 c-36 4 -73 11 -83 17 -17 9 -26 48 -14 60 3 4 126 -13
151 -21z m-770 10 c21 0 24 -5 24 -35 0 -33 -1 -35 -27 -29 -36 8 -40 13 -49
48 -6 25 -5 28 10 23 10 -4 29 -7 42 -7z m124 -36 c0 -19 -4 -34 -10 -34 -5 0
-10 18 -10 41 0 24 4 38 10 34 6 -3 10 -22 10 -41z m176 6 c20 0 24 -5 24 -29
0 -17 5 -33 10 -36 15 -9 12 -35 -4 -35 -8 0 -16 4 -18 9 -1 5 -26 11 -54 15
-72 8 -117 27 -50 20 27 -2 60 -7 73 -10 19 -5 23 -2 23 16 0 19 -4 22 -26 17
-14 -2 -31 1 -37 7 -7 6 -23 17 -37 24 -23 11 -20 12 24 7 27 -3 59 -5 72 -5z
m-599 -6 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m59 -61 c-2
-3 -15 5 -28 17 -16 16 -18 21 -6 17 10 -3 19 1 24 11 6 13 9 11 12 -11 2 -16
1 -31 -2 -34z m150 25 c54 -10 64 -15 69 -36 3 -13 4 -26 1 -29 -2 -2 -40 1
-85 8 -79 12 -81 13 -81 41 0 32 5 33 96 16z m489 2 c3 -5 -1 -10 -10 -10 -9
0 -13 5 -10 10 3 6 8 10 10 10 2 0 7 -4 10 -10z m-183 -75 c4 -16 1 -25 -8
-25 -14 0 -23 19 -16 38 7 20 19 14 24 -13z m88 -5 c0 -5 -4 -10 -9 -10 -6 0
-13 5 -16 10 -3 6 1 10 9 10 9 0 16 -4 16 -10z m140 -41 c0 -16 -4 -29 -10
-29 -5 0 -10 16 -10 36 0 21 4 33 10 29 6 -3 10 -19 10 -36z m-614 23 c6 -4
14 -16 17 -27 7 -18 6 -18 -10 2 -12 14 -32 23 -58 26 l-40 4 40 1 c22 1 45
-2 51 -6z m222 -27 c6 -2 12 -16 12 -30 0 -28 -12 -31 -70 -16 -19 5 -52 12
-72 16 -34 5 -38 9 -38 34 l0 29 78 -15 c42 -7 83 -16 90 -18z m-238 5 c0 -5
-4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m877 -12
c53 -8 62 -13 68 -35 3 -14 3 -28 0 -31 -5 -5 -213 32 -220 40 -2 2 -2 14 2
27 6 24 -2 24 150 -1z m-567 -34 c0 -16 -26 -19 -35 -4 -3 6 -4 14 0 20 8 13
35 1 35 -16z m173 9 c13 -4 17 -16 17 -49 0 -50 -12 -55 -47 -22 -29 27 -34
95 -5 83 9 -4 25 -10 35 -12z m-405 -71 c2 -32 0 -42 -12 -42 -9 0 -18 12 -22
28 -3 15 -7 32 -9 39 -5 15 6 24 25 21 10 -2 16 -17 18 -46z m512 8 c0 -19 -5
-30 -14 -30 -8 0 -16 13 -18 30 -4 24 -1 30 14 30 13 0 18 -8 18 -30z m-325
-13 c57 -11 60 -14 63 -43 l3 -31 -93 18 c-51 10 -94 19 -95 19 -2 0 -3 14 -3
30 0 34 -12 34 125 7z m485 10 c50 -11 55 -15 58 -40 3 -27 4 -28 18 -10 12
16 15 16 24 3 19 -30 10 -33 -77 -27 -130 10 -148 17 -148 55 -1 30 1 32 35
31 19 0 60 -6 90 -12z m-248 -88 c5 -33 6 -33 -59 -18 -46 11 -49 14 -45 39 4
26 4 27 52 17 43 -8 49 -13 52 -38z m88 -19 c0 -11 -2 -20 -4 -20 -2 0 -6 9
-9 20 -3 11 -1 20 4 20 5 0 9 -9 9 -20z m198 -7 c5 -34 -34 -31 -41 3 -4 21
-1 25 17 22 13 -2 22 -12 24 -25z m-144 -8 c3 -8 1 -15 -4 -15 -6 0 -10 7 -10
15 0 8 2 15 4 15 2 0 6 -7 10 -15z m-279 -24 c75 -15 80 -18 83 -43 4 -33 10
-32 -101 -13 -119 21 -137 29 -137 59 0 28 -1 28 155 -3z m623 1 c8 -11 12
-12 12 -3 0 8 11 11 32 9 24 -2 34 -9 37 -23 5 -29 40 -50 37 -22 0 6 3 13 9
14 5 2 10 -5 10 -15 0 -13 11 -18 52 -22 45 -5 87 -20 54 -20 -7 0 -9 -4 -6
-10 4 -6 -21 -10 -68 -10 -107 0 -137 8 -151 40 -6 15 -16 25 -23 23 -6 -2
-29 1 -51 6 -34 10 -39 14 -35 35 5 22 9 24 42 19 20 -3 42 -12 49 -21z m-168
-16 c0 -3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5 11 10 11 6 0 10 -2 10 -4z
m-632 -37 c1 -8 -4 -23 -12 -34 -15 -19 -15 -18 -16 18 0 27 4 37 13 35 6 -3
13 -11 15 -19z m212 -73 c56 -10 65 -15 68 -35 4 -23 2 -23 -35 -17 -21 4 -57
10 -80 13 -24 3 -43 9 -43 13 0 5 -3 15 -6 24 -7 19 6 20 96 2z m778 -13 c2
-16 4 -37 5 -47 1 -10 -4 -21 -10 -23 -9 -3 -13 5 -13 24 0 15 -3 38 -6 51 -4
15 -2 22 8 22 8 0 15 -12 16 -27z m69 21 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6
11 1 17 -2 13 -5z m-1080 -46 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1
-19z m93 -8 c0 -5 -7 -10 -15 -10 -8 0 -15 5 -15 10 0 6 7 10 15 10 8 0 15 -4
15 -10z m400 -51 c0 -28 -4 -39 -12 -37 -18 6 -19 78 -2 78 10 0 14 -13 14
-41z m-221 25 c18 -4 31 -13 31 -21 0 -12 -8 -14 -37 -8 -44 8 -53 13 -53 26
0 11 15 11 59 3z m-295 -10 c20 -8 21 -48 2 -63 -12 -10 -17 -8 -25 8 -13 24
-14 61 -2 61 5 0 16 -3 25 -6z m416 -19 c0 -23 -3 -25 -27 -19 -38 9 -43 13
-43 30 0 9 11 14 35 14 31 0 35 -3 35 -25z m317 -25 c3 -44 -2 -50 -37 -39
-24 7 -24 7 -2 8 19 1 22 6 22 43 0 51 13 42 17 -12z m48 10 c3 -5 2 -10 -4
-10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0 13 -4 16 -10z m-665 -20 c7 0 -49
-38 -85 -57 -29 -15 -30 -15 -14 3 9 11 19 31 23 46 6 25 8 26 38 17 18 -5 35
-9 38 -9z m817 -106 c-14 -14 -27 11 -27 50 0 62 14 66 24 7 5 -28 6 -54 3
-57z m298 31 c119 -23 115 -20 115 -70 l0 -43 -72 14 c-122 23 -118 21 -118
57 0 18 -5 38 -12 45 -15 15 -9 15 87 -3z m-155 -19 c0 -2 -7 -7 -16 -10 -8
-3 -12 -2 -9 4 6 10 25 14 25 6z m312 -68 c-9 -9 -12 -7 -12 12 0 19 3 21 12
12 9 -9 9 -15 0 -24z m-1505 -78 c15 -12 41 -20 62 -20 35 0 37 -2 59 -58 12
-32 22 -61 22 -64 0 -4 -20 -36 -45 -71 -81 -117 -123 -227 -179 -478 -45
-202 -52 -358 -21 -482 6 -23 4 -24 -127 -46 -73 -13 -154 -30 -179 -38 -44
-14 -55 -13 -220 21 -381 78 -596 167 -739 307 -82 80 -115 153 -115 250 1
110 48 204 159 315 123 123 270 201 506 269 144 41 264 61 555 90 127 12 232
23 233 24 2 0 15 -8 29 -19z m311 -216 c2 -22 -25 -85 -104 -242 -58 -117
-110 -212 -115 -212 -16 0 -10 94 11 176 24 97 77 203 131 267 47 54 72 58 77
11z m26 -181 c-31 -74 -85 -226 -104 -296 -23 -87 -26 -55 -4 50 18 88 44 157
86 231 35 61 45 68 22 15z m-268 -940 c65 -31 148 -67 184 -80 52 -19 57 -22
25 -17 -46 7 -279 96 -312 119 -20 14 -31 35 -19 35 1 0 57 -25 122 -57z"/>
<path d="M7090 1674 c-52 -7 -131 -25 -175 -39 -92 -29 -208 -103 -242 -154
-26 -39 -30 -93 -8 -111 11 -9 13 -16 5 -24 -38 -38 23 -52 265 -61 250 -10
695 25 783 61 41 17 52 42 26 63 -14 12 -19 29 -19 61 0 93 -49 126 -278 185
-152 39 -203 41 -357 19z m270 -74 c11 -7 6 -10 -21 -10 -20 0 -72 -3 -115 -6
-43 -4 -76 -3 -72 1 20 21 181 32 208 15z m-427 -166 c56 -36 48 -48 -25 -40
-34 3 -66 10 -70 14 -5 5 -4 21 2 36 12 32 31 30 93 -10z"/>
</g>
</svg>
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1365 1365" width="1365" height="1365">
<title>safari-pinned-tab-svg</title>
<defs>
<clipPath clipPathUnits="userSpaceOnUse" id="cp1">
<path d="m655.2 313.1v822.23h-622.69v-822.23z"/>
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="cp2">
<path d="m1308.84 304v828.5h-617.24v-828.5z"/>
</clipPath>
</defs>
<style>
.s0 { fill: none }
.s1 { fill: #000000 }
</style>
<g id="surface1">
<path class="s0" d="m1258.6 499.2c-40.8-26.1-68 13.8-64.7 68.1l-0.3 0.4c0.1-56.6-7.3-119.2-31.7-169.8-8.7-17.9-26.2-47.4-61-33.5-15.2 6.1-29 24.4-21.9 71.7 0 0 8 49.7 6.5 112.2v0.8c-9.9-172.2-47.3-224.7-100.7-221.3-17.1 3.1-40.5 10.8-32.6 63.8 0 0 8.5 55.2 11.3 99.2l0.2 2.2h-0.2c-25.2-94.9-59-96.2-83.5-92.5-22.3 3.3-46.6 27.3-34.3 74.7 38.6 148.4 31 327.2 28.2 352.9-7.9-17.6-10.3-31.4-21.3-50.7-43.9-77.1-64.8-82.8-90.4-84-25.5-1.1-53 15.2-51.2 46.3 1.9 31.1 17.1 36.3 38.7 79.6 16.9 33.8 21.7 78 55.7 158.4 28.1 66.5 101.6 139.5 235.6 130.8 108.5-3.7 270.6-43.2 242.4-302.5-7-45-1.7-82.7 1.9-121.4 5.8-60 14.1-159.4-26.7-185.5z"/>
<path class="s0" d="m580.9 695.3c-25.7 1.7-46.4 7.7-89 85.7-10.6 19.4-12.7 33.3-20.3 50.9-3.3-25.5-14-204.2 21.9-353.4 11.4-47.5-13.3-71-35.6-73.9-24.6-3.3-58.5-1.3-81.9 94.6h-0.3l0.4-2.8c1.9-44 9.5-99.4 9.5-99.4 6.9-53.1-16.6-60.3-33.7-63.2-53.4-2.3-89.7 50.4-96.7 221.1h-0.2c-2.4-61.8 4.6-111 4.6-111 6.3-47.5-7.9-65.5-23.2-71.3-35-13.3-52 16.6-60.3 34.7-23.6 51-29.9 113.7-28.7 170.3l-0.4-0.4c2.4-54.2-25.6-93.7-65.9-66.9-40.3 26.9-30.1 126.2-23.4 186 4.5 38.6 10.4 76.2 4.1 121.4-23.5 259.7 139.3 296.1 247.8 297.8 134.1 6.1 206.3-68.3 233.3-135.4 32.5-80.9 36.6-125.3 52.8-159.3 20.8-43.8 36-49.2 37.3-80.3 1.3-31.1-26.5-46.9-52-45.3z"/>
<g id="Clip-Path" clip-path="url(#cp1)">
<g>
<path fill-rule="evenodd" class="s1" d="m634.4 695.7c11.9 12 17.8 27.9 17.1 45.7-1 24.6-9.5 37.8-19.4 53.2-5.8 9-12.3 19.2-19.8 34.9-6.6 13.8-11.2 30.4-17 51.5-7.6 27.3-17 61.2-35.3 106.7-14.1 35.1-71.4 146.1-231.3 147.6q-9.8 0.1-20-0.3c-93.6-1.5-164.2-28.5-209.3-80.6-46.9-54-65.8-134.2-56.4-238.4l0.1-0.9c5.2-37.6 1.5-69.5-2.6-103.3-0.5-4.4-1-8.7-1.5-13.1-9.4-82.5-15.4-173.1 31.8-204.6 27.9-18.5 49.3-11.1 59.6-4.9 1 0.6 1.9 1.2 2.9 1.9 4.5-32.3 12.6-63.9 25.6-92.2 11.2-24.2 37.1-62.2 83.8-44.5 7.6 3 17.3 8.8 24.8 20.2 6.7-14.7 14.5-26.6 23.5-35.8 16.6-17.2 37.4-25.4 61.6-24.3l2.1 0.2c39.2 6.5 55.9 35 49.4 84.9 0 0 0 0.3 0 0.6 20-17 40.4-16.9 56.1-14.8 17.2 2.2 32.8 12.1 42.8 27.2 8.6 13 17.1 35.8 8.7 70.7-22.3 92.9-26.1 198.5-25.2 268.8 36.3-61.5 59.9-74.1 93.2-76.2 20.8-1.3 41.3 6 54.8 19.8zm-316.9-329.4c-35.1 36.2-49.7 148.5-43.4 333.7 19.5-3 39.5-5.2 59.6-6.5 4.7-91.2 13.3-153.7 23.7-197q0-0.5 0-0.9c2-44.4 9.3-98.9 9.7-101.2 4.7-36.3-5.7-39.2-17-41.1-13.2-0.3-23.5 3.8-32.5 13zm-124.6 49.5c-50 108.3-16.2 277.2-8.5 303.8 16.1-4.8 33.8-9.2 52.4-13-2.1-57.7-2.3-108.1-0.6-151.9-2.3-62.3 4.4-111.4 4.7-113.5 1.8-13 4.2-44.5-11.1-50.3-10.9-4.1-22.8-5.7-36.9 24.8zm407.9 357.3c8.9-13.8 12.6-19.5 13.2-33.3 0.2-6.7-1.6-12-5.9-16.3-5.9-6.1-16-9.4-26.1-8.8-17.3 1.2-33.6 2.2-73.8 75.9-5.6 10.4-8.5 18.9-11.8 28.6-2.1 6.3-4.4 13.2-7.7 20.8-0.1 0.3-0.2 0.6-0.4 0.8-1.4 3.3-3 6.8-4.8 10.4-17.6 34.2-46.2 53.4-76.5 51.6-10.4-0.7-18.2-9.9-17.6-20.6 0.6-10.7 9.6-18.7 19.8-18.2 18 1.1 33.1-15.3 41.2-31.1 0.7-1.4 1.3-2.8 2-4.2-4-41.1-11.8-210.7 22.9-354.8 3.9-16.5 2.8-30.1-3.3-39.3-5.6-8.4-13.4-10.3-16.5-10.7-11.2-1.5-19.3-0.9-27.8 6.4-20.5 17.8-46.8 77.8-56.4 261.8 5.8 0 11.6 0 17.3 0.2 10.3 0.3 18.5 9.2 18.2 19.9-0.3 10.7-8.8 19.1-19.2 18.9-95.8-2.8-204.4 22.8-250.2 47.9-2.8 1.5-5.7 2.3-8.6 2.3-6.8 0.1-13.4-3.7-16.8-10.3-4.8-9.5-1.4-21.2 7.8-26.3 8.1-4.4 17.9-8.9 28.9-13.1-6.7-22.4-18.6-82-20.1-150.6-0.3-1.5-0.5-3-0.4-4.6 1.2-29.4-7.6-48.4-16.4-53.6-5.2-3.1-12.1-1.8-20.6 3.9-31.6 21-19.4 127.4-14.9 167.4 0.5 4.3 1 8.6 1.5 12.9 4.1 34.7 8.4 70.6 2.6 113-8.3 92.7 7.5 162.9 47 208.5 38.4 44.2 98 66.3 182.4 67.6 151.5 7.1 203.3-92.6 215.7-123.3 17.4-43.4 26.5-76.2 33.8-102.5 6.4-22.9 11.4-41 19.5-58 8.5-17.9 16.1-29.7 22.2-39.2z"/>
</g>
</g>
<g id="Clip-Path" clip-path="url(#cp2)">
<g>
<path fill-rule="evenodd" class="s1" d="m1301.9 802.9l0.1 0.9c11.3 104-6.3 184.6-52.2 239.5-44.1 52.8-114.2 81.3-208.3 84.5q-10.2 0.7-19.9 0.8c-159.5 1.6-218.8-108.4-233.5-143.2-19.1-45.1-29.1-78.9-37.2-106-6.2-21-11.1-37.5-18-51.2-7.7-15.5-14.4-25.6-20.4-34.5-10.1-15.1-18.8-28.2-20.3-52.8-1.1-17.7 4.5-33.6 16.2-45.9 13.3-14 33.8-21.8 54.5-20.9 33.3 1.5 57.1 13.7 94.5 74.5-0.3-70.4-6-175.9-30-268.4-9-34.8-0.9-57.7 7.5-70.8 9.7-15.3 25.1-25.5 42.2-28.1 15.7-2.3 36.2-2.9 56.4 13.8 0-0.2 0-0.4 0-0.4-7.4-49.9 8.8-78.8 47.9-86l2.1-0.3c24.1-1.6 45 6.3 62 23.2 9.1 9 17.1 20.7 24.1 35.3 7.4-11.6 16.9-17.6 24.5-20.6 46.4-18.7 72.9 18.9 84.5 42.9 13.5 28 22.1 59.5 27.3 91.6 0.9-0.6 1.8-1.3 2.8-1.9 10.2-6.3 31.5-14.3 59.7 3.8 47.8 30.6 43.4 121.3 35.5 203.9-0.4 4.4-0.8 8.7-1.3 13.1-3.4 33.9-6.6 65.9-0.8 103.3zm-197.6-256.5c2.5 43.7 3.2 94.2 2.1 151.9 18.7 3.5 36.4 7.5 52.7 12 7.2-26.7 37.9-196.3-14-303.7-14.6-30.1-26.5-28.4-37.4-24.1-15.1 6.1-12.2 37.5-10.2 50.7 0.4 1.9 8 50.9 6.8 113.2zm-117.5-199.2c-11.4 2.2-21.6 5.2-16.3 41.6 0.4 2.1 8.8 56.4 11.5 100.8q0 0.5 0 0.9c11.2 43.2 21 105.4 27.2 196.6 20.2 0.9 40.2 2.7 59.7 5.3 3-185.3-13.5-297.2-49.3-332.8-9.1-9.1-19.5-13-32.7-12.4zm278.5 348.5c0.5-4.3 0.9-8.6 1.3-13 3.9-40.1 14.1-146.6-17.9-167-8.6-5.5-15.5-6.7-20.6-3.5-8.7 5.4-17.2 24.5-15.4 53.9 0.1 1.5 0 3.1-0.3 4.6-0.4 68.5-11.2 128.4-17.5 150.9 11.1 4.1 20.9 8.3 29.1 12.6 9.3 4.9 13 16.6 8.3 26.2-3.3 6.7-9.8 10.6-16.6 10.6-2.9 0-5.9-0.6-8.6-2.1-46.2-24.3-155.4-47.7-251-43.2-10.5 0.3-19.2-7.7-19.6-18.5-0.5-10.7 7.5-19.8 17.9-20.2 5.6-0.3 11.4-0.5 17.2-0.6-12.8-183.8-40.2-243.3-61-260.7-8.6-7.1-16.8-7.6-27.9-5.9-3.2 0.5-10.9 2.5-16.4 11.1-5.9 9.3-6.8 22.9-2.5 39.3 37.3 143.5 32.4 313.2 29.2 354.3 0.7 1.4 1.3 2.7 2.1 4.2 8.4 15.6 23.7 31.7 41.7 30.3 10.3-0.7 19.3 7.2 20.1 17.9 0.8 10.6-6.9 20-17.2 20.8-30.3 2.5-59.3-16.3-77.4-50.1-2-3.6-3.6-7-5.1-10.3-0.1-0.2-0.2-0.5-0.3-0.7-3.5-7.5-5.9-14.5-8.2-20.8-3.5-9.6-6.4-18-12.3-28.3-41.6-72.9-57.9-73.7-75.1-74.5-10.2-0.5-20.2 3.1-26.1 9.3-4.1 4.3-5.9 9.7-5.5 16.4 0.8 13.8 4.6 19.4 13.7 33.1 6.3 9.4 14.1 21 23 38.7 8.3 16.9 13.7 34.8 20.5 57.7 7.7 26.2 17.4 58.7 35.6 101.8 12.9 30.5 66.7 129.1 217.3 119.2 84.9-2.9 144.2-26.2 181.7-71.1 38.7-46.3 53.2-116.8 43.3-209.4-6.6-42.3-3-78.2 0.5-113z"/>
</g>
</g>
<path class="s1" d="m739.8 434.2c-3 0-6.1-0.6-9-2-10.5-5-15.2-18-10.3-28.9 15.7-35.6 38.8-68.4 66.6-94.9 8.5-8.1 21.9-7.6 29.7 1.3 7.9 8.9 7.4 22.7-1.2 30.8-23.7 22.7-43.4 50.6-56.8 81-3.6 7.9-11.1 12.6-19 12.7z"/>
<path class="s1" d="m668.8 421.6c-10.9 0.1-20.3-8.5-21.2-20-4-51.4-4.2-103.5-0.4-154.8 0.9-12 11.1-21 22.6-20.1 11.6 0.9 20.3 11.3 19.4 23.3-3.6 49.1-3.4 98.9 0.4 148 1 12-7.7 22.5-19.3 23.5-0.5 0-1 0-1.5 0z"/>
<path class="s1" d="m596.2 435.1c-9.4 0.1-18.2-6.5-20.6-16.4-8.9-36.3-25.9-70.8-48.9-99.7-7.4-9.3-6.1-23 2.8-30.7 9-7.6 22.3-6.3 29.7 3 26.9 33.8 46.7 74.2 57.2 116.7 2.9 11.6-4 23.5-15.2 26.5-1.8 0.4-3.4 0.6-5.1 0.7z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

@@ -4,18 +4,17 @@ import TreeNode from "./TreeNode";
import { I18nKey } from "#/i18n/declaration";
interface ExplorerTreeProps {
files: string[];
files: string[] | null;
defaultOpen?: boolean;
}
function ExplorerTree({ files, defaultOpen = false }: ExplorerTreeProps) {
const { t } = useTranslation();
if (files.length === 0) {
return (
<div className="text-sm text-gray-400 pt-4">
{t(I18nKey.EXPLORER$EMPTY_WORKSPACE_MESSAGE)}
</div>
);
if (!files?.length) {
const message = !files
? I18nKey.EXPLORER$LOADING_WORKSPACE_MESSAGE
: I18nKey.EXPLORER$EMPTY_WORKSPACE_MESSAGE;
return <div className="text-sm text-gray-400 pt-4">{t(message)}</div>;
}
return (
<div className="w-full h-full pt-[4px]">
@@ -90,7 +90,7 @@ function ExplorerActions({
function FileExplorer() {
const [isHidden, setIsHidden] = React.useState(false);
const [isDragging, setIsDragging] = React.useState(false);
const [files, setFiles] = React.useState<string[]>([]);
const [files, setFiles] = React.useState<string[] | null>(null);
const { curAgentState } = useSelector((state: RootState) => state.agent);
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
const dispatch = useDispatch();
@@ -79,12 +79,10 @@ function TreeNode({ path, defaultOpen = false }: TreeNodeProps) {
setIsOpen((prev) => !prev);
} else {
let newFileState = fileStates.find((f) => f.path === path);
if (!newFileState) {
const code = await selectFile(path);
newFileState = { path, savedContent: code, unsavedContent: code };
}
const code = await selectFile(path);
newFileState = { path, savedContent: code, unsavedContent: code };
dispatch(addOrUpdateFileState(newFileState));
dispatch(setCode(newFileState.unsavedContent));
dispatch(setCode(code));
dispatch(setActiveFilepath(path));
}
};
@@ -39,7 +39,6 @@ function BaseModal({
data-testid={testID}
isOpen={isOpen}
onOpenChange={onOpenChange}
title={title}
isDismissable={isDismissable}
backdrop="blur"
hideCloseButton
@@ -51,14 +50,14 @@ function BaseModal({
<>
{title && (
<ModalHeader className="flex flex-col p-0">
<HeaderContent title={title} subtitle={subtitle} />
<HeaderContent maintitle={title} subtitle={subtitle} />
</ModalHeader>
)}
<ModalBody className={bodyClassName}>{children}</ModalBody>
{actions && actions.length > 0 && (
<ModalFooter className="flex-col flex justify-start p-0">
<ModalFooter className="flex-row flex justify-start p-0">
<FooterContent actions={actions} closeModal={closeModal} />
</ModalFooter>
)}
@@ -1,17 +1,17 @@
import React from "react";
interface HeaderContentProps {
title: string;
maintitle: string;
subtitle?: string;
}
export function HeaderContent({
title,
maintitle,
subtitle = undefined,
}: HeaderContentProps) {
return (
<>
<h3>{title}</h3>
<h3>{maintitle}</h3>
{subtitle && (
<span className="text-neutral-400 text-sm font-light">{subtitle}</span>
)}
@@ -8,7 +8,7 @@ import { Feedback, sendFeedback } from "#/services/feedbackService";
import toast from "#/utils/toast";
import { getToken } from "#/services/auth";
import Session from "#/services/session";
import { removeApiKey } from "#/utils/utils";
import { removeApiKey, removeUnwantedKeys } from "#/utils/utils";
const isEmailValid = (email: string) => {
// Regular expression to validate email format
@@ -95,7 +95,7 @@ function FeedbackModal({
email,
permissions,
token: getToken(),
trajectory: removeApiKey(Session._history),
trajectory: removeApiKey(removeUnwantedKeys(Session._history)),
};
try {
@@ -29,7 +29,7 @@ describe("ModelSelector", () => {
const onModelChange = vi.fn();
render(<ModelSelector models={models} onModelChange={onModelChange} />);
const selector = screen.getByLabelText("Provider");
const selector = screen.getByLabelText("LLM Provider");
expect(selector).toBeInTheDocument();
await user.click(selector);
@@ -45,10 +45,10 @@ describe("ModelSelector", () => {
const onModelChange = vi.fn();
render(<ModelSelector models={models} onModelChange={onModelChange} />);
const modelSelector = screen.getByLabelText("Model");
const modelSelector = screen.getByLabelText("LLM Model");
expect(modelSelector).toBeDisabled();
const providerSelector = screen.getByLabelText("Provider");
const providerSelector = screen.getByLabelText("LLM Provider");
await user.click(providerSelector);
const vertexAI = screen.getByText("VertexAI");
@@ -62,13 +62,13 @@ describe("ModelSelector", () => {
const onModelChange = vi.fn();
render(<ModelSelector models={models} onModelChange={onModelChange} />);
const providerSelector = screen.getByLabelText("Provider");
const providerSelector = screen.getByLabelText("LLM Provider");
await user.click(providerSelector);
const azureProvider = screen.getByText("Azure");
await user.click(azureProvider);
const modelSelector = screen.getByLabelText("Model");
const modelSelector = screen.getByLabelText("LLM Model");
await user.click(modelSelector);
expect(screen.getByText("ada")).toBeInTheDocument();
@@ -84,42 +84,13 @@ describe("ModelSelector", () => {
expect(screen.getByText("chat-bison-32k")).toBeInTheDocument();
});
it("should display the actual litellm model ID as the user is making the selections", async () => {
const user = userEvent.setup();
const onModelChange = vi.fn();
render(<ModelSelector models={models} onModelChange={onModelChange} />);
const id = screen.getByTestId("model-id");
const providerSelector = screen.getByLabelText("Provider");
const modelSelector = screen.getByLabelText("Model");
expect(id).toHaveTextContent("No model selected");
await user.click(providerSelector);
await user.click(screen.getByText("Azure"));
expect(id).toHaveTextContent("azure/");
await user.click(modelSelector);
await user.click(screen.getByText("ada"));
expect(id).toHaveTextContent("azure/ada");
await user.click(providerSelector);
await user.click(screen.getByText("cohere"));
expect(id).toHaveTextContent("cohere.");
await user.click(modelSelector);
await user.click(screen.getByText("command-r-v1:0"));
expect(id).toHaveTextContent("cohere.command-r-v1:0");
});
it("should call onModelChange when the model is changed", async () => {
const user = userEvent.setup();
const onModelChange = vi.fn();
render(<ModelSelector models={models} onModelChange={onModelChange} />);
const providerSelector = screen.getByLabelText("Provider");
const modelSelector = screen.getByLabelText("Model");
const providerSelector = screen.getByLabelText("LLM Provider");
const modelSelector = screen.getByLabelText("LLM Model");
await user.click(providerSelector);
await user.click(screen.getByText("Azure"));
@@ -146,29 +117,6 @@ describe("ModelSelector", () => {
expect(onModelChange).toHaveBeenCalledWith("cohere.command-r-v1:0");
});
it("should clear the model ID when the provider is cleared", async () => {
const user = userEvent.setup();
const onModelChange = vi.fn();
render(<ModelSelector models={models} onModelChange={onModelChange} />);
const providerSelector = screen.getByLabelText("Provider");
const modelSelector = screen.getByLabelText("Model");
await user.click(providerSelector);
await user.click(screen.getByText("Azure"));
await user.click(modelSelector);
await user.click(screen.getByText("ada"));
expect(screen.getByTestId("model-id")).toHaveTextContent("azure/ada");
await user.clear(providerSelector);
expect(screen.getByTestId("model-id")).toHaveTextContent(
"No model selected",
);
});
it("should have a default value if passed", async () => {
const onModelChange = vi.fn();
render(
@@ -179,9 +127,8 @@ describe("ModelSelector", () => {
/>,
);
expect(screen.getByTestId("model-id")).toHaveTextContent("azure/ada");
expect(screen.getByLabelText("Provider")).toHaveValue("Azure");
expect(screen.getByLabelText("Model")).toHaveValue("ada");
expect(screen.getByLabelText("LLM Provider")).toHaveValue("Azure");
expect(screen.getByLabelText("LLM Model")).toHaveValue("ada");
});
it.todo("should disable provider if isDisabled is true");
@@ -21,7 +21,7 @@ export function ModelSelector({
onModelChange,
defaultModel,
}: ModelSelectorProps) {
const [litellmId, setLitellmId] = React.useState<string | null>(null);
const [, setLitellmId] = React.useState<string | null>(null);
const [selectedProvider, setSelectedProvider] = React.useState<string | null>(
null,
);
@@ -48,7 +48,11 @@ export function ModelSelector({
const handleChangeModel = (model: string) => {
const separator = models[selectedProvider || ""]?.separator || "";
const fullModel = selectedProvider + separator + model;
let fullModel = selectedProvider + separator + model;
if (selectedProvider === "openai") {
// LiteLLM lists OpenAI models without the openai/ prefix
fullModel = model;
}
setLitellmId(fullModel);
onModelChange(fullModel);
setSelectedModel(model);
@@ -61,14 +65,10 @@ export function ModelSelector({
return (
<div data-testid="model-selector" className="flex flex-col gap-2">
<span className="text-center italic text-gray-500" data-testid="model-id">
{litellmId?.replace("other", "") || "No model selected"}
</span>
<div className="flex flex-col gap-3">
<div className="flex flex-row gap-3">
<Autocomplete
isDisabled={isDisabled}
label="Provider"
label="LLM Provider"
placeholder="Select a provider"
isClearable={false}
onSelectionChange={(e) => {
@@ -99,7 +99,7 @@ export function ModelSelector({
</Autocomplete>
<Autocomplete
label="Model"
label="LLM Model"
placeholder="Select a model"
onSelectionChange={(e) => {
if (e?.toString()) handleChangeModel(e.toString());
@@ -6,8 +6,7 @@ import { Settings } from "#/services/settings";
import SettingsForm from "./SettingsForm";
const onModelChangeMock = vi.fn();
const onCustomModelChangeMock = vi.fn();
const onModelTypeChangeMock = vi.fn();
const onBaseURLChangeMock = vi.fn();
const onAgentChangeMock = vi.fn();
const onLanguageChangeMock = vi.fn();
const onAPIKeyChangeMock = vi.fn();
@@ -21,21 +20,19 @@ const renderSettingsForm = (settings?: Settings) => {
settings={
settings || {
LLM_MODEL: "gpt-4o",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
AGENT: "agent1",
LANGUAGE: "en",
LLM_API_KEY: "sk-...",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "analyzer1",
LLM_BASE_URL: "",
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
}
}
models={["gpt-4o", "gpt-3.5-turbo", "azure/ada"]}
agents={["agent1", "agent2", "agent3"]}
securityAnalyzers={["analyzer1", "analyzer2", "analyzer3"]}
onModelChange={onModelChangeMock}
onCustomModelChange={onCustomModelChangeMock}
onModelTypeChange={onModelTypeChangeMock}
onBaseURLChange={onBaseURLChangeMock}
onAgentChange={onAgentChangeMock}
onLanguageChange={onLanguageChangeMock}
onAPIKeyChange={onAPIKeyChangeMock}
@@ -49,50 +46,90 @@ describe("SettingsForm", () => {
it("should display the first values in the array by default", () => {
renderSettingsForm();
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
const agentInput = screen.getByRole("combobox", { name: "agent" });
const providerInput = screen.getByRole("combobox", {
name: "LLM Provider",
});
const modelInput = screen.getByRole("combobox", { name: "LLM Model" });
const languageInput = screen.getByRole("combobox", { name: "language" });
const apiKeyInput = screen.getByTestId("apikey");
const confirmationModeInput = screen.getByTestId("confirmationmode");
const securityAnalyzerInput = screen.getByRole("combobox", {
name: "securityanalyzer",
});
expect(providerInput).toHaveValue("OpenAI");
expect(modelInput).toHaveValue("gpt-4o");
expect(agentInput).toHaveValue("agent1");
expect(languageInput).toHaveValue("English");
expect(apiKeyInput).toHaveValue("sk-...");
expect(confirmationModeInput).toHaveAttribute("data-selected", "true");
expect(securityAnalyzerInput).toHaveValue("analyzer1");
});
it("should display the existing values if they are present", () => {
renderSettingsForm({
LLM_MODEL: "gpt-3.5-turbo",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
AGENT: "agent2",
LANGUAGE: "es",
LLM_API_KEY: "sk-...",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "analyzer2",
LLM_BASE_URL: "",
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
});
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
const agentInput = screen.getByRole("combobox", { name: "agent" });
const languageInput = screen.getByRole("combobox", { name: "language" });
const securityAnalyzerInput = screen.getByRole("combobox", {
name: "securityanalyzer",
const providerInput = screen.getByRole("combobox", {
name: "LLM Provider",
});
const modelInput = screen.getByRole("combobox", { name: "LLM Model" });
const languageInput = screen.getByRole("combobox", { name: "language" });
expect(providerInput).toHaveValue("OpenAI");
expect(modelInput).toHaveValue("gpt-3.5-turbo");
expect(agentInput).toHaveValue("agent2");
expect(languageInput).toHaveValue("Español");
expect(securityAnalyzerInput).toHaveValue("analyzer2");
});
it("should show advanced settings by default if advanced settings are in use", () => {
renderSettingsForm({
LLM_MODEL: "gpt-3.5-turbo",
AGENT: "agent2",
LANGUAGE: "es",
LLM_API_KEY: "sk-...",
LLM_BASE_URL: "",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "",
});
const customModelInput = screen.getByTestId("custom-model-input");
expect(customModelInput).toBeInTheDocument();
});
it("should show advanced settings if using a custom model", () => {
renderSettingsForm({
LLM_MODEL: "bagel",
AGENT: "agent2",
LANGUAGE: "es",
LLM_API_KEY: "sk-...",
LLM_BASE_URL: "",
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
});
const customModelInput = screen.getByTestId("custom-model-input");
expect(customModelInput).toBeInTheDocument();
});
it("should show advanced settings if button is clicked", async () => {
renderSettingsForm({
LLM_MODEL: "gpt-3.5-turbo",
AGENT: "agent2",
LANGUAGE: "es",
LLM_API_KEY: "sk-...",
LLM_BASE_URL: "",
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
});
let customModelInput = screen.queryByTestId("custom-model-input");
expect(customModelInput).not.toBeInTheDocument();
const advancedToggle = screen.getByTestId("advanced-options-toggle");
await userEvent.click(advancedToggle);
customModelInput = screen.getByTestId("custom-model-input");
expect(customModelInput).toBeInTheDocument();
});
it("should disable settings when disabled is true", () => {
@@ -100,21 +137,19 @@ describe("SettingsForm", () => {
<SettingsForm
settings={{
LLM_MODEL: "gpt-4o",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
AGENT: "agent1",
LANGUAGE: "en",
LLM_API_KEY: "sk-...",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "analyzer1",
LLM_BASE_URL: "",
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
}}
models={["gpt-4o", "gpt-3.5-turbo", "azure/ada"]}
agents={["agent1", "agent2", "agent3"]}
securityAnalyzers={["analyzer1", "analyzer2", "analyzer3"]}
disabled
onModelChange={onModelChangeMock}
onCustomModelChange={onCustomModelChangeMock}
onModelTypeChange={onModelTypeChangeMock}
onBaseURLChange={onBaseURLChangeMock}
onAgentChange={onAgentChangeMock}
onLanguageChange={onLanguageChangeMock}
onAPIKeyChange={onAPIKeyChangeMock}
@@ -123,21 +158,15 @@ describe("SettingsForm", () => {
/>,
);
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
const agentInput = screen.getByRole("combobox", { name: "agent" });
const languageInput = screen.getByRole("combobox", { name: "language" });
const confirmationModeInput = screen.getByTestId("confirmationmode");
const securityAnalyzerInput = screen.getByRole("combobox", {
name: "securityanalyzer",
const providerInput = screen.getByRole("combobox", {
name: "LLM Provider",
});
const modelInput = screen.getByRole("combobox", { name: "LLM Model" });
const languageInput = screen.getByRole("combobox", { name: "language" });
expect(providerInput).toBeDisabled();
expect(modelInput).toBeDisabled();
expect(agentInput).toBeDisabled();
expect(languageInput).toBeDisabled();
expect(confirmationModeInput).toHaveAttribute("data-disabled", "true");
expect(securityAnalyzerInput).toBeDisabled();
});
describe("onChange handlers", () => {
@@ -146,7 +175,7 @@ describe("SettingsForm", () => {
renderSettingsForm();
// We need to enable the agent select
const agentSwitch = screen.getByTestId("enableagentselect");
const agentSwitch = screen.getByTestId("advanced-options-toggle");
await user.click(agentSwitch);
const agentInput = screen.getByRole("combobox", { name: "agent" });
@@ -201,8 +230,8 @@ describe("SettingsForm", () => {
const user = userEvent.setup();
renderSettingsForm();
const customModelToggle = screen.getByTestId("custom-model-toggle");
await user.click(customModelToggle);
const advancedToggle = screen.getByTestId("advanced-options-toggle");
await user.click(advancedToggle);
const modelSelector = screen.queryByTestId("model-selector");
expect(modelSelector).not.toBeInTheDocument();
@@ -215,23 +244,22 @@ describe("SettingsForm", () => {
const user = userEvent.setup();
renderSettingsForm();
const customModelToggle = screen.getByTestId("custom-model-toggle");
await user.click(customModelToggle);
const advancedToggle = screen.getByTestId("advanced-options-toggle");
await user.click(advancedToggle);
const customModelInput = screen.getByTestId("custom-model-input");
await userEvent.clear(customModelInput);
await userEvent.type(customModelInput, "my/custom-model");
expect(onCustomModelChangeMock).toHaveBeenCalledWith("my/custom-model");
expect(onModelTypeChangeMock).toHaveBeenCalledWith("custom");
expect(onModelChangeMock).toHaveBeenCalledWith("my/custom-model");
});
it("should have custom model switched if using custom model", () => {
it("should have advanced options switched if using advanced options", () => {
renderWithProviders(
<SettingsForm
settings={{
LLM_MODEL: "gpt-4o",
CUSTOM_LLM_MODEL: "CUSTOM_MODEL",
USING_CUSTOM_MODEL: true,
LLM_BASE_URL: "base_url",
AGENT: "agent1",
LANGUAGE: "en",
LLM_API_KEY: "sk-...",
@@ -243,8 +271,7 @@ describe("SettingsForm", () => {
securityAnalyzers={["analyzer1", "analyzer2", "analyzer3"]}
disabled
onModelChange={onModelChangeMock}
onCustomModelChange={onCustomModelChangeMock}
onModelTypeChange={onModelTypeChangeMock}
onBaseURLChange={onBaseURLChangeMock}
onAgentChange={onAgentChangeMock}
onLanguageChange={onLanguageChangeMock}
onAPIKeyChange={onAPIKeyChangeMock}
@@ -253,8 +280,8 @@ describe("SettingsForm", () => {
/>,
);
const customModelToggle = screen.getByTestId("custom-model-toggle");
expect(customModelToggle).toHaveAttribute("aria-checked", "true");
const advancedToggle = screen.getByTestId("advanced-options-toggle");
expect(advancedToggle).toHaveAttribute("aria-checked", "true");
});
});
});
@@ -17,8 +17,7 @@ interface SettingsFormProps {
disabled: boolean;
onModelChange: (model: string) => void;
onCustomModelChange: (model: string) => void;
onModelTypeChange: (type: "custom" | "default") => void;
onBaseURLChange: (baseURL: string) => void;
onAPIKeyChange: (apiKey: string) => void;
onAgentChange: (agent: string) => void;
onLanguageChange: (language: string) => void;
@@ -33,8 +32,7 @@ function SettingsForm({
securityAnalyzers,
disabled,
onModelChange,
onCustomModelChange,
onModelTypeChange,
onBaseURLChange,
onAPIKeyChange,
onAgentChange,
onLanguageChange,
@@ -43,40 +41,44 @@ function SettingsForm({
}: SettingsFormProps) {
const { t } = useTranslation();
const { isOpen: isVisible, onOpenChange: onVisibleChange } = useDisclosure();
const [isAgentSelectEnabled, setIsAgentSelectEnabled] = React.useState(false);
const [usingCustomModel, setUsingCustomModel] = React.useState(
settings.USING_CUSTOM_MODEL,
const advancedAlreadyInUse = React.useMemo(
() =>
!!settings.SECURITY_ANALYZER ||
!!settings.CONFIRMATION_MODE ||
!!settings.LLM_BASE_URL ||
(!!settings.LLM_MODEL && !models.includes(settings.LLM_MODEL)),
[],
);
const changeModelType = (type: "custom" | "default") => {
if (type === "custom") {
setUsingCustomModel(true);
onModelTypeChange("custom");
} else {
setUsingCustomModel(false);
onModelTypeChange("default");
}
};
const [enableAdvanced, setEnableAdvanced] =
React.useState(advancedAlreadyInUse);
return (
<>
<Switch
data-testid="custom-model-toggle"
aria-checked={usingCustomModel}
isSelected={usingCustomModel}
onValueChange={(value) => changeModelType(value ? "custom" : "default")}
data-testid="advanced-options-toggle"
aria-checked={enableAdvanced}
isSelected={enableAdvanced}
onValueChange={(value) => setEnableAdvanced(value)}
>
Use custom model
Advanced Options
</Switch>
{usingCustomModel && (
<Input
data-testid="custom-model-input"
label="Custom Model"
onValueChange={onCustomModelChange}
defaultValue={settings.CUSTOM_LLM_MODEL}
/>
{enableAdvanced && (
<>
<Input
data-testid="custom-model-input"
label="Custom Model"
onValueChange={onModelChange}
defaultValue={settings.LLM_MODEL}
/>
<Input
data-testid="base-url-input"
label="Base URL"
onValueChange={onBaseURLChange}
defaultValue={settings.LLM_BASE_URL}
/>
</>
)}
{!usingCustomModel && (
{!enableAdvanced && (
<ModelSelector
isDisabled={disabled}
models={organizeModelsAndProviders(models)}
@@ -117,52 +119,48 @@ function SettingsForm({
tooltip={t(I18nKey.SETTINGS$LANGUAGE_TOOLTIP)}
disabled={disabled}
/>
<AutocompleteCombobox
ariaLabel="agent"
items={agents.map((agent) => ({ value: agent, label: agent }))}
defaultKey={settings.AGENT}
onChange={onAgentChange}
tooltip={t(I18nKey.SETTINGS$AGENT_TOOLTIP)}
disabled={disabled || !isAgentSelectEnabled}
/>
<Switch
defaultSelected={false}
isSelected={isAgentSelectEnabled}
onValueChange={setIsAgentSelectEnabled}
aria-label="enableagentselect"
data-testid="enableagentselect"
>
{t(I18nKey.SETTINGS$AGENT_SELECT_ENABLED)}
</Switch>
<AutocompleteCombobox
ariaLabel="securityanalyzer"
items={securityAnalyzers.map((securityAnalyzer) => ({
value: securityAnalyzer,
label: securityAnalyzer,
}))}
defaultKey={settings.SECURITY_ANALYZER}
onChange={onSecurityAnalyzerChange}
tooltip={t(I18nKey.SETTINGS$SECURITY_ANALYZER)}
disabled={disabled}
/>
<Switch
aria-label="confirmationmode"
data-testid="confirmationmode"
defaultSelected={
settings.CONFIRMATION_MODE || !!settings.SECURITY_ANALYZER
}
onValueChange={onConfirmationModeChange}
isDisabled={disabled || !!settings.SECURITY_ANALYZER}
isSelected={settings.CONFIRMATION_MODE}
>
<Tooltip
content={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
closeDelay={100}
delay={500}
{enableAdvanced && (
<AutocompleteCombobox
ariaLabel="agent"
items={agents.map((agent) => ({ value: agent, label: agent }))}
defaultKey={settings.AGENT}
onChange={onAgentChange}
tooltip={t(I18nKey.SETTINGS$AGENT_TOOLTIP)}
/>
)}
{enableAdvanced && (
<AutocompleteCombobox
ariaLabel="securityanalyzer"
items={securityAnalyzers.map((securityAnalyzer) => ({
value: securityAnalyzer,
label: securityAnalyzer,
}))}
defaultKey={settings.SECURITY_ANALYZER}
onChange={onSecurityAnalyzerChange}
tooltip={t(I18nKey.SETTINGS$SECURITY_ANALYZER)}
disabled={disabled}
/>
)}
{enableAdvanced && (
<Switch
aria-label="confirmationmode"
data-testid="confirmationmode"
defaultSelected={
settings.CONFIRMATION_MODE || !!settings.SECURITY_ANALYZER
}
onValueChange={onConfirmationModeChange}
isDisabled={disabled || !!settings.SECURITY_ANALYZER}
isSelected={settings.CONFIRMATION_MODE}
>
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
</Tooltip>
</Switch>
<Tooltip
content={t(I18nKey.SETTINGS$CONFIRMATION_MODE_TOOLTIP)}
closeDelay={100}
delay={500}
>
{t(I18nKey.SETTINGS$CONFIRMATION_MODE)}
</Tooltip>
</Switch>
)}
</>
);
}
@@ -4,7 +4,6 @@ import i18next from "i18next";
import React from "react";
import { renderWithProviders } from "test-utils";
import { Mock } from "vitest";
import toast from "#/utils/toast";
import {
Settings,
getSettings,
@@ -15,7 +14,6 @@ import Session from "#/services/session";
import { fetchAgents, fetchModels } from "#/services/options";
import SettingsModal from "./SettingsModal";
const toastSpy = vi.spyOn(toast, "settingsChanged");
const i18nSpy = vi.spyOn(i18next, "changeLanguage");
const startNewSessionSpy = vi.spyOn(Session, "startNewSession");
vi.spyOn(Session, "isConnected").mockImplementation(() => true);
@@ -24,18 +22,14 @@ vi.mock("#/services/settings", async (importOriginal) => ({
...(await importOriginal<typeof import("#/services/settings")>()),
getSettings: vi.fn().mockReturnValue({
LLM_MODEL: "gpt-4o",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
AGENT: "CodeActAgent",
LANGUAGE: "en",
LLM_API_KEY: "sk-...",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "invariant",
CONFIRMATION_MODE: false,
SECURITY_ANALYZER: "",
}),
getDefaultSettings: vi.fn().mockReturnValue({
LLM_MODEL: "gpt-4o",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
AGENT: "CodeActAgent",
LANGUAGE: "en",
LLM_API_KEY: "",
@@ -85,7 +79,9 @@ describe("SettingsModal", () => {
it("should close the modal when the close button is clicked", async () => {
const user = userEvent.setup();
const onOpenChange = vi.fn();
renderWithProviders(<SettingsModal isOpen onOpenChange={onOpenChange} />);
await act(async () =>
renderWithProviders(<SettingsModal isOpen onOpenChange={onOpenChange} />),
);
const cancelButton = screen.getByRole("button", {
name: /MODAL_CLOSE_BUTTON_LABEL/i, // i18n key
@@ -98,8 +94,7 @@ describe("SettingsModal", () => {
it("should disabled the save button if the settings contain a missing value", async () => {
const onOpenChangeMock = vi.fn();
(getSettings as Mock).mockReturnValueOnce({
LLM_MODEL: "gpt-4o",
AGENT: "",
LLM_MODEL: "",
});
await act(async () =>
renderWithProviders(
@@ -113,15 +108,13 @@ describe("SettingsModal", () => {
});
describe("onHandleSave", () => {
const initialSettings: Settings = {
const initialSettings: Partial<Settings> = {
LLM_MODEL: "gpt-4o",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
AGENT: "CodeActAgent",
LANGUAGE: "en",
LLM_API_KEY: "sk-...",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "invariant",
SECURITY_ANALYZER: "",
CONFIRMATION_MODE: false,
};
it("should save the settings", async () => {
@@ -135,8 +128,10 @@ describe("SettingsModal", () => {
await assertModelsAndAgentsFetched();
const saveButton = screen.getByRole("button", { name: /save/i });
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
const providerInput = screen.getByRole("combobox", {
name: "LLM Provider",
});
const modelInput = screen.getByRole("combobox", { name: "LLM Model" });
await user.click(providerInput);
const azure = screen.getByText("Azure");
@@ -164,8 +159,10 @@ describe("SettingsModal", () => {
);
const saveButton = screen.getByRole("button", { name: /save/i });
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
const providerInput = screen.getByRole("combobox", {
name: "LLM Provider",
});
const modelInput = screen.getByRole("combobox", { name: "LLM Model" });
await user.click(providerInput);
const openai = screen.getByText("OpenAI");
@@ -180,32 +177,6 @@ describe("SettingsModal", () => {
expect(startNewSessionSpy).toHaveBeenCalled();
});
it("should display a toast for every change", async () => {
const user = userEvent.setup();
const onOpenChangeMock = vi.fn();
await act(async () =>
renderWithProviders(
<SettingsModal isOpen onOpenChange={onOpenChangeMock} />,
),
);
const saveButton = screen.getByRole("button", { name: /save/i });
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
await user.click(providerInput);
const cohere = screen.getByText("cohere");
await user.click(cohere);
await user.click(modelInput);
const model3 = screen.getByText("command-r-v1:0");
await user.click(model3);
await user.click(saveButton);
expect(toastSpy).toHaveBeenCalledTimes(4);
});
it("should change the language", async () => {
const user = userEvent.setup();
const onOpenChangeMock = vi.fn();
@@ -230,6 +201,10 @@ describe("SettingsModal", () => {
it("should close the modal", async () => {
const user = userEvent.setup();
const onOpenChangeMock = vi.fn();
(getSettings as Mock).mockReturnValueOnce({
LLM_MODEL: "gpt-4o",
LLM_API_KEY: "sk-...",
});
await act(async () =>
renderWithProviders(
<SettingsModal isOpen onOpenChange={onOpenChangeMock} />,
@@ -241,8 +216,10 @@ describe("SettingsModal", () => {
});
const saveButton = screen.getByRole("button", { name: /save/i });
const providerInput = screen.getByRole("combobox", { name: "Provider" });
const modelInput = screen.getByRole("combobox", { name: "Model" });
const providerInput = screen.getByRole("combobox", {
name: "LLM Provider",
});
const modelInput = screen.getByRole("combobox", { name: "LLM Model" });
await user.click(providerInput);
const cohere = screen.getByText("cohere");
@@ -252,6 +229,7 @@ describe("SettingsModal", () => {
const model3 = screen.getByText("command-r-v1:0");
await user.click(model3);
expect(saveButton).not.toBeDisabled();
await user.click(saveButton);
expect(onOpenChangeMock).toHaveBeenCalledWith(false);
@@ -261,16 +239,16 @@ describe("SettingsModal", () => {
it("should reset settings to defaults when the 'reset to defaults' button is clicked", async () => {
const user = userEvent.setup();
const onOpenChangeMock = vi.fn();
(getSettings as Mock).mockReturnValueOnce({
LLM_MODEL: "gpt-4o",
SECURITY_ANALYZER: "fakeanalyzer",
});
await act(async () =>
renderWithProviders(
<SettingsModal isOpen onOpenChange={onOpenChangeMock} />,
),
);
// We need to enable the agent select first
const agentSwitch = screen.getByTestId("enableagentselect");
await user.click(agentSwitch);
const resetButton = screen.getByRole("button", {
name: /MODAL_RESET_BUTTON_LABEL/i,
});
@@ -17,7 +17,6 @@ import {
Settings,
getSettings,
getDefaultSettings,
getSettingsDifference,
settingsAreUpToDate,
maybeMigrateSettings,
saveSettings,
@@ -31,7 +30,7 @@ interface SettingsProps {
onOpenChange: (isOpen: boolean) => void;
}
const REQUIRED_SETTINGS = ["LLM_MODEL", "AGENT"];
const REQUIRED_SETTINGS = ["LLM_MODEL"];
function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
const { t } = useTranslation();
@@ -83,17 +82,10 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
}));
};
const handleCustomModelChange = (model: string) => {
const handleBaseURLChange = (baseURL: string) => {
setSettings((prev) => ({
...prev,
CUSTOM_LLM_MODEL: model,
}));
};
const handleModelTypeChange = (type: "custom" | "default") => {
setSettings((prev) => ({
...prev,
USING_CUSTOM_MODEL: type === "custom",
LLM_BASE_URL: baseURL,
}));
};
@@ -131,28 +123,17 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
};
const handleSaveSettings = () => {
const updatedSettings = getSettingsDifference(settings);
saveSettings(settings);
i18next.changeLanguage(settings.LANGUAGE);
Session.startNewSession();
const sensitiveKeys = ["LLM_API_KEY"];
Object.entries(updatedSettings).forEach(([key, value]) => {
if (!sensitiveKeys.includes(key)) {
toast.settingsChanged(`${key} set to "${value}"`);
} else {
toast.settingsChanged(`${key} has been updated securely.`);
}
});
localStorage.setItem(
`API_KEY_${settings.LLM_MODEL || models[0]}`,
settings.LLM_API_KEY,
);
};
let subtitle = t(I18nKey.CONFIGURATION$MODAL_SUB_TITLE);
let subtitle = "";
if (loading) {
subtitle = t(I18nKey.CONFIGURATION$AGENT_LOADING);
} else if (agentIsRunning) {
@@ -171,30 +152,34 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
title={t(I18nKey.CONFIGURATION$MODAL_TITLE)}
isDismissable={settingsAreUpToDate()}
subtitle={subtitle}
actions={[
{
label: t(I18nKey.CONFIGURATION$MODAL_SAVE_BUTTON_LABEL),
action: handleSaveSettings,
isDisabled: saveIsDisabled,
closeAfterAction: true,
className: "bg-primary rounded-lg",
},
{
label: t(I18nKey.CONFIGURATION$MODAL_RESET_BUTTON_LABEL),
action: handleResetSettings,
closeAfterAction: false,
className: "bg-neutral-500 rounded-lg",
},
{
label: t(I18nKey.CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL),
action: () => {
setSettings(getSettings()); // reset settings from any changes
},
isDisabled: !settingsAreUpToDate(),
closeAfterAction: true,
className: "bg-rose-600 rounded-lg",
},
]}
actions={
loading
? []
: [
{
label: t(I18nKey.CONFIGURATION$MODAL_SAVE_BUTTON_LABEL),
action: handleSaveSettings,
isDisabled: saveIsDisabled,
closeAfterAction: true,
className: "bg-primary rounded-lg",
},
{
label: t(I18nKey.CONFIGURATION$MODAL_RESET_BUTTON_LABEL),
action: handleResetSettings,
closeAfterAction: false,
className: "bg-neutral-500 rounded-lg",
},
{
label: t(I18nKey.CONFIGURATION$MODAL_CLOSE_BUTTON_LABEL),
action: () => {
setSettings(getSettings()); // reset settings from any changes
},
isDisabled: !settingsAreUpToDate(),
closeAfterAction: true,
className: "bg-rose-600 rounded-lg",
},
]
}
>
{loading && <Spinner />}
{!loading && (
@@ -205,8 +190,7 @@ function SettingsModal({ isOpen, onOpenChange }: SettingsProps) {
agents={agents}
securityAnalyzers={securityAnalyzers}
onModelChange={handleModelChange}
onCustomModelChange={handleCustomModelChange}
onModelTypeChange={handleModelTypeChange}
onBaseURLChange={handleBaseURLChange}
onAgentChange={handleAgentChange}
onLanguageChange={handleLanguageChange}
onAPIKeyChange={handleAPIKeyChange}
+17 -24
View File
@@ -196,18 +196,18 @@
"es": "Editor de código"
},
"WORKSPACE$BROWSER_TAB_LABEL": {
"en": "Browser",
"zh-CN": "浏览器",
"de": "Browser",
"ko-KR": "브라우저",
"no": "Nettleser",
"zh-TW": "瀏覽器",
"it": "Browser",
"pt": "Navegador",
"es": "Navegador",
"ar": "المتصفح",
"fr": "Navigateur",
"tr": "Tarayıcı"
"en": "Browser (Experimental)",
"zh-CN": "浏览器(实验性)",
"de": "Browser (Experimentell)",
"ko-KR": "브라우저 (실험적)",
"no": "Nettleser (Eksperimentell)",
"zh-TW": "瀏覽器 (實驗性)",
"it": "Browser (Sperimentale)",
"pt": "Navegador (Experimental)",
"es": "Navegador (Experimental)",
"ar": "المتصفح (تجريبي)",
"fr": "Navigateur (Expérimental)",
"tr": "Tarayıcı (Deneysel)"
},
"CONFIGURATION$OPENHANDS_WORKSPACE_DIRECTORY_INPUT_LABEL": {
"en": "OpenHands Workspace directory",
@@ -250,18 +250,6 @@
"fr": "Configuration",
"tr": "Konfigürasyon"
},
"CONFIGURATION$MODAL_SUB_TITLE": {
"en": "Adjust settings to your liking",
"zh-CN": "根据您的喜好调整设置",
"de": "Passen Sie die Einstellungen nach Ihren Wünschen an ",
"ko-KR": "원하는 대로 설정 조정",
"no": "Juster innstillinger etter dine ønsker ",
"zh-TW": "調整設定以符合您的喜好",
"it": "Regola le impostazioni in base alle tue preferenze",
"pt": "Ajuste as configurações de acordo com sua preferência",
"es": "Ajusta la configuración a tu gusto",
"tr": "Ayarları isteğinize göre ayarlayın"
},
"CONFIGURATION$MODEL_SELECT_LABEL": {
"en": "Model",
"zh-CN": "模型",
@@ -453,6 +441,11 @@
"zh-CN": "工作区没有文件",
"de": "Keine Dateien im Arbeitsbereich"
},
"EXPLORER$LOADING_WORKSPACE_MESSAGE": {
"en": "Loading workspace...",
"zh-CN": "正在加载工作区...",
"de": "Arbeitsbereich wird geladen..."
},
"EXPLORER$REFRESH_ERROR_MESSAGE": {
"en": "Error refreshing workspace",
"zh-CN": "工作区刷新错误",
+1 -31
View File
@@ -19,8 +19,7 @@ describe("startNewSession", () => {
it("Should start a new session with the current settings", () => {
const settings: Settings = {
LLM_MODEL: "llm_value",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
LLM_BASE_URL: "base_url",
AGENT: "agent_value",
LANGUAGE: "language_value",
LLM_API_KEY: "sk-...",
@@ -39,33 +38,4 @@ describe("startNewSession", () => {
expect(setupSpy).toHaveBeenCalledTimes(1);
expect(sendSpy).toHaveBeenCalledWith(JSON.stringify(event));
});
it("should start with the custom llm if set", () => {
const settings: Settings = {
LLM_MODEL: "llm_value",
CUSTOM_LLM_MODEL: "custom_llm_value",
USING_CUSTOM_MODEL: true,
AGENT: "agent_value",
LANGUAGE: "language_value",
LLM_API_KEY: "sk-...",
CONFIRMATION_MODE: true,
SECURITY_ANALYZER: "analyzer",
};
const event = {
action: ActionType.INIT,
args: settings,
};
saveSettings(settings);
Session.startNewSession();
expect(setupSpy).toHaveBeenCalledTimes(1);
expect(sendSpy).toHaveBeenCalledWith(
JSON.stringify({
...event,
args: { ...settings, LLM_MODEL: "custom_llm_value" },
}),
);
});
});
-3
View File
@@ -50,9 +50,6 @@ class Session {
action: ActionType.INIT,
args: {
...settings,
LLM_MODEL: settings.USING_CUSTOM_MODEL
? settings.CUSTOM_LLM_MODEL
: settings.LLM_MODEL,
},
};
const eventString = JSON.stringify(event);
+5 -62
View File
@@ -3,7 +3,6 @@ import {
DEFAULT_SETTINGS,
Settings,
getSettings,
getSettingsDifference,
saveSettings,
} from "./settings";
@@ -18,8 +17,7 @@ describe("getSettings", () => {
it("should get the stored settings", () => {
(localStorage.getItem as Mock)
.mockReturnValueOnce("llm_value")
.mockReturnValueOnce("custom_llm_value")
.mockReturnValueOnce("true")
.mockReturnValueOnce("base_url")
.mockReturnValueOnce("agent_value")
.mockReturnValueOnce("language_value")
.mockReturnValueOnce("api_key")
@@ -30,8 +28,7 @@ describe("getSettings", () => {
expect(settings).toEqual({
LLM_MODEL: "llm_value",
CUSTOM_LLM_MODEL: "custom_llm_value",
USING_CUSTOM_MODEL: true,
LLM_BASE_URL: "base_url",
AGENT: "agent_value",
LANGUAGE: "language_value",
LLM_API_KEY: "api_key",
@@ -55,11 +52,10 @@ describe("getSettings", () => {
expect(settings).toEqual({
LLM_MODEL: DEFAULT_SETTINGS.LLM_MODEL,
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: DEFAULT_SETTINGS.USING_CUSTOM_MODEL,
AGENT: DEFAULT_SETTINGS.AGENT,
LANGUAGE: DEFAULT_SETTINGS.LANGUAGE,
LLM_API_KEY: "",
LLM_BASE_URL: DEFAULT_SETTINGS.LLM_BASE_URL,
CONFIRMATION_MODE: DEFAULT_SETTINGS.CONFIRMATION_MODE,
SECURITY_ANALYZER: DEFAULT_SETTINGS.SECURITY_ANALYZER,
});
@@ -70,8 +66,7 @@ describe("saveSettings", () => {
it("should save the settings", () => {
const settings: Settings = {
LLM_MODEL: "llm_value",
CUSTOM_LLM_MODEL: "custom_llm_value",
USING_CUSTOM_MODEL: true,
LLM_BASE_URL: "base_url",
AGENT: "agent_value",
LANGUAGE: "language_value",
LLM_API_KEY: "some_key",
@@ -82,14 +77,6 @@ describe("saveSettings", () => {
saveSettings(settings);
expect(localStorage.setItem).toHaveBeenCalledWith("LLM_MODEL", "llm_value");
expect(localStorage.setItem).toHaveBeenCalledWith(
"CUSTOM_LLM_MODEL",
"custom_llm_value",
);
expect(localStorage.setItem).toHaveBeenCalledWith(
"USING_CUSTOM_MODEL",
"true",
);
expect(localStorage.setItem).toHaveBeenCalledWith("AGENT", "agent_value");
expect(localStorage.setItem).toHaveBeenCalledWith(
"LANGUAGE",
@@ -110,7 +97,7 @@ describe("saveSettings", () => {
expect(localStorage.setItem).toHaveBeenCalledTimes(2);
expect(localStorage.setItem).toHaveBeenCalledWith("LLM_MODEL", "llm_value");
expect(localStorage.setItem).toHaveBeenCalledWith("SETTINGS_VERSION", "1");
expect(localStorage.setItem).toHaveBeenCalledWith("SETTINGS_VERSION", "2");
});
it("should not save invalid settings", () => {
@@ -135,47 +122,3 @@ describe("saveSettings", () => {
);
});
});
describe("getSettingsDifference", () => {
beforeEach(() => {
(localStorage.getItem as Mock)
.mockReturnValueOnce("llm_value")
.mockReturnValueOnce("custom_llm_value")
.mockReturnValueOnce("false")
.mockReturnValueOnce("agent_value")
.mockReturnValueOnce("language_value");
});
it("should return updated settings", () => {
const settings = {
LLM_MODEL: "new_llm_value",
CUSTOM_LLM_MODEL: "custom_llm_value",
USING_CUSTOM_MODEL: true,
AGENT: "new_agent_value",
LANGUAGE: "language_value",
};
const updatedSettings = getSettingsDifference(settings);
expect(updatedSettings).toEqual({
USING_CUSTOM_MODEL: true,
LLM_MODEL: "new_llm_value",
AGENT: "new_agent_value",
});
});
it("should not handle invalid settings", () => {
const settings = {
LLM_MODEL: "new_llm_value",
AGENT: "new_agent_value",
INVALID: "invalid_value",
};
const updatedSettings = getSettingsDifference(settings);
expect(updatedSettings).toEqual({
LLM_MODEL: "new_llm_value",
AGENT: "new_agent_value",
});
});
});
+14 -44
View File
@@ -1,9 +1,8 @@
const LATEST_SETTINGS_VERSION = 1;
const LATEST_SETTINGS_VERSION = 2;
export type Settings = {
LLM_MODEL: string;
CUSTOM_LLM_MODEL: string;
USING_CUSTOM_MODEL: boolean;
LLM_BASE_URL: string;
AGENT: string;
LANGUAGE: string;
LLM_API_KEY: string;
@@ -11,12 +10,9 @@ export type Settings = {
SECURITY_ANALYZER: string;
};
type SettingsInput = Settings[keyof Settings];
export const DEFAULT_SETTINGS: Settings = {
LLM_MODEL: "openai/gpt-4o",
CUSTOM_LLM_MODEL: "",
USING_CUSTOM_MODEL: false,
LLM_MODEL: "gpt-4o",
LLM_BASE_URL: "",
AGENT: "CodeActAgent",
LANGUAGE: "en",
LLM_API_KEY: "",
@@ -46,6 +42,14 @@ export const maybeMigrateSettings = () => {
if (currentVersion < 1) {
localStorage.setItem("AGENT", DEFAULT_SETTINGS.AGENT);
}
if (currentVersion < 2) {
const customModel = localStorage.getItem("CUSTOM_LLM_MODEL");
if (customModel) {
localStorage.setItem("LLM_MODEL", customModel);
}
localStorage.removeItem("CUSTOM_LLM_MODEL");
localStorage.removeItem("USING_CUSTOM_MODEL");
}
};
/**
@@ -58,9 +62,7 @@ export const getDefaultSettings = (): Settings => DEFAULT_SETTINGS;
*/
export const getSettings = (): Settings => {
const model = localStorage.getItem("LLM_MODEL");
const customModel = localStorage.getItem("CUSTOM_LLM_MODEL");
const usingCustomModel =
localStorage.getItem("USING_CUSTOM_MODEL") === "true";
const baseUrl = localStorage.getItem("LLM_BASE_URL");
const agent = localStorage.getItem("AGENT");
const language = localStorage.getItem("LANGUAGE");
const apiKey = localStorage.getItem("LLM_API_KEY");
@@ -69,8 +71,7 @@ export const getSettings = (): Settings => {
return {
LLM_MODEL: model || DEFAULT_SETTINGS.LLM_MODEL,
CUSTOM_LLM_MODEL: customModel || DEFAULT_SETTINGS.CUSTOM_LLM_MODEL,
USING_CUSTOM_MODEL: usingCustomModel || DEFAULT_SETTINGS.USING_CUSTOM_MODEL,
LLM_BASE_URL: baseUrl || DEFAULT_SETTINGS.LLM_BASE_URL,
AGENT: agent || DEFAULT_SETTINGS.AGENT,
LANGUAGE: language || DEFAULT_SETTINGS.LANGUAGE,
LLM_API_KEY: apiKey || DEFAULT_SETTINGS.LLM_API_KEY,
@@ -93,34 +94,3 @@ export const saveSettings = (settings: Partial<Settings>) => {
});
localStorage.setItem("SETTINGS_VERSION", LATEST_SETTINGS_VERSION.toString());
};
/**
* Get the difference between the current settings and the provided settings.
* Useful for notifying the user of exact changes.
*
* @example
* // Assuming the current settings are: { LLM_MODEL: "gpt-4o", AGENT: "CodeActAgent", LANGUAGE: "en" }
* const updatedSettings = getSettingsDifference({ LLM_MODEL: "gpt-4o", AGENT: "OTHER_AGENT", LANGUAGE: "en" });
* // updatedSettings = { AGENT: "OTHER_AGENT" }
*
* @param settings - the settings to compare
* @returns the updated settings
*/
export const getSettingsDifference = (settings: Partial<Settings>) => {
const currentSettings = getSettings();
const updatedSettings: Partial<Settings> = {};
Object.keys(settings).forEach((key) => {
const typedKey = key as keyof Settings;
if (
validKeys.includes(typedKey) &&
settings[typedKey] !== currentSettings[typedKey]
) {
(updatedSettings[typedKey] as SettingsInput) = settings[
typedKey
] as SettingsInput;
}
});
return updatedSettings;
};
@@ -58,5 +58,23 @@ describe("extractModelAndProvider", () => {
model: "gpt-4o",
separator: "/",
});
expect(extractModelAndProvider("claude-3-5-sonnet-20240620")).toEqual({
provider: "anthropic",
model: "claude-3-5-sonnet-20240620",
separator: "/",
});
expect(extractModelAndProvider("claude-3-haiku-20240307")).toEqual({
provider: "anthropic",
model: "claude-3-haiku-20240307",
separator: "/",
});
expect(extractModelAndProvider("claude-2.1")).toEqual({
provider: "anthropic",
model: "claude-2.1",
separator: "/",
});
});
});
@@ -1,5 +1,8 @@
import { isNumber } from "./isNumber";
import { VERIFIED_OPENAI_MODELS } from "./verified-models";
import {
VERIFIED_ANTHROPIC_MODELS,
VERIFIED_OPENAI_MODELS,
} from "./verified-models";
/**
* Checks if the split array is actually a version number.
@@ -41,6 +44,9 @@ export const extractModelAndProvider = (model: string) => {
if (VERIFIED_OPENAI_MODELS.includes(split[0])) {
return { provider: "openai", model: split[0], separator: "/" };
}
if (VERIFIED_ANTHROPIC_MODELS.includes(split[0])) {
return { provider: "anthropic", model: split[0], separator: "/" };
}
// return as model only
return { provider: "", model, separator: "" };
}
@@ -15,6 +15,11 @@ test("organizeModelsAndProviders", () => {
"gpt-4o",
"together-ai-21.1b-41b",
"gpt-3.5-turbo",
"claude-3-5-sonnet-20240620",
"claude-3-haiku-20240307",
"claude-2",
"claude-2.1",
"anthropic.unsafe-claude-2.1",
];
const object = organizeModelsAndProviders(models);
@@ -43,6 +48,15 @@ test("organizeModelsAndProviders", () => {
separator: "/",
models: ["gpt-4o", "gpt-3.5-turbo"],
},
anthropic: {
separator: "/",
models: [
"claude-3-5-sonnet-20240620",
"claude-3-haiku-20240307",
"claude-2",
"claude-2.1",
],
},
other: {
separator: "",
models: ["together-ai-21.1b-41b"],
@@ -32,6 +32,13 @@ export const organizeModelsAndProviders = (models: string[]) => {
provider,
model: modelId,
} = extractModelAndProvider(model);
// Ignore "anthropic" providers with a separator of "."
// These are outdated and incompatible providers.
if (provider === "anthropic" && separator === ".") {
return;
}
const key = provider || "other";
if (!object[key]) {
object[key] = { separator, models: [] };
+41
View File
@@ -10,9 +10,50 @@ interface EventActionHistory {
LLM_API_KEY?: string;
[key: string]: unknown;
};
extras?: {
open_page_urls: string[];
active_page_index: number;
dom_object: Record<string, unknown>;
axtree_object: Record<string, unknown>;
extra_element_properties: Record<string, unknown>;
last_browser_action: string;
last_browser_action_error: unknown;
focused_element_bid: string;
};
[key: string]: unknown;
}
export const removeUnwantedKeys = (
data: EventActionHistory[],
): EventActionHistory[] => {
const UNDESIRED_KEYS = [
"open_page_urls",
"active_page_index",
"dom_object",
"axtree_object",
"extra_element_properties",
"last_browser_action",
"last_browser_action_error",
"focused_element_bid",
];
return data.map((item) => {
// Create a shallow copy of item
const newItem = { ...item };
// Check if extras exists and delete it from a new extras object
if (newItem.extras) {
const newExtras = { ...newItem.extras };
UNDESIRED_KEYS.forEach((key) => {
delete newExtras[key as keyof typeof newExtras];
});
newItem.extras = newExtras;
}
return newItem;
});
};
export const removeApiKey = (
data: EventActionHistory[],
): EventActionHistory[] =>
+14 -1
View File
@@ -1,6 +1,6 @@
// Here are the list of verified models and providers that we know work well with OpenHands.
export const VERIFIED_PROVIDERS = ["openai", "azure", "anthropic"];
export const VERIFIED_MODELS = ["gpt-4o", "claude-3-5-sonnet-20240620-v1:0"];
export const VERIFIED_MODELS = ["gpt-4o", "claude-3-5-sonnet-20240620"];
// LiteLLM does not return OpenAI models with the provider, so we list them here to set them ourselves for consistency
// (e.g., they return `gpt-4o` instead of `openai/gpt-4o`)
@@ -12,3 +12,16 @@ export const VERIFIED_OPENAI_MODELS = [
"gpt-4-32k",
"gpt-3.5-turbo",
];
// LiteLLM does not return the compatible Anthropic models with the provider, so we list them here to set them ourselves
// (e.g., they return `claude-3-5-sonnet-20240620` instead of `anthropic/claude-3-5-sonnet-20240620`)
export const VERIFIED_ANTHROPIC_MODELS = [
"claude-2",
"claude-2.1",
"claude-3-5-sonnet-20240620",
"claude-3-haiku-20240307",
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-instant-1",
"claude-instant-1.2",
];
+4
View File
@@ -365,10 +365,14 @@ class AgentController:
f'[Agent Controller {self.id}] Delegate state: {delegate_state}'
)
if delegate_state == AgentState.ERROR:
# update iteration that shall be shared across agents
self.state.iteration = self.delegate.state.iteration
# close the delegate upon error
await self.delegate.close()
self.delegate = None
self.delegateAction = None
await self.report_error('Delegator agent encounters an error')
return
delegate_done = delegate_state in (AgentState.FINISHED, AgentState.REJECTED)
+8 -4
View File
@@ -51,6 +51,8 @@ class LLMConfig:
output_cost_per_token: The cost per output token. This will available in logs for the user to check.
ollama_base_url: The base URL for the OLLAMA API.
drop_params: Drop any unmapped (unsupported) params without causing an exception.
disable_vision: If model is vision capable, this option allows to disable image processing (useful for cost reduction).
caching_prompt: Using the prompt caching feature provided by the LLM.
"""
model: str = 'gpt-4o'
@@ -63,10 +65,10 @@ class LLMConfig:
aws_access_key_id: str | None = None
aws_secret_access_key: str | None = None
aws_region_name: str | None = None
num_retries: int = 10
num_retries: int = 8
retry_multiplier: float = 2
retry_min_wait: int = 3
retry_max_wait: int = 300
retry_min_wait: int = 15
retry_max_wait: int = 120
timeout: int | None = None
max_message_chars: int = 10_000 # maximum number of characters in an observation's content when sent to the llm
temperature: float = 0
@@ -78,6 +80,8 @@ class LLMConfig:
output_cost_per_token: float | None = None
ollama_base_url: str | None = None
drop_params: bool | None = None
disable_vision: bool | None = None
caching_prompt: bool = False
def defaults_to_dict(self) -> dict:
"""Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional."""
@@ -619,7 +623,7 @@ def get_llm_config_arg(
model = 'gpt-3.5-turbo'
api_key = '...'
temperature = 0.5
num_retries = 10
num_retries = 8
...
```
+2 -2
View File
@@ -239,7 +239,7 @@ class LlmFileHandler(logging.FileHandler):
self.message_counter += 1
def _get_llm_file_handler(name, log_level=logging.INFO):
def _get_llm_file_handler(name: str, log_level: int):
# The 'delay' parameter, when set to True, postpones the opening of the log file
# until the first log message is emitted.
llm_file_handler = LlmFileHandler(name, delay=True)
@@ -248,7 +248,7 @@ def _get_llm_file_handler(name, log_level=logging.INFO):
return llm_file_handler
def _setup_llm_logger(name, log_level=logging.INFO):
def _setup_llm_logger(name: str, log_level: int):
logger = logging.getLogger(name)
logger.propagate = False
logger.setLevel(log_level)
+57 -6
View File
@@ -1,8 +1,11 @@
from enum import Enum
from typing import Union
from pydantic import BaseModel, Field, model_serializer
from typing_extensions import Literal
from openhands.core.logger import openhands_logger as logger
class ContentType(Enum):
TEXT = 'text'
@@ -10,7 +13,7 @@ class ContentType(Enum):
class Content(BaseModel):
type: ContentType
type: str
cache_prompt: bool = False
@model_serializer
@@ -19,13 +22,13 @@ class Content(BaseModel):
class TextContent(Content):
type: ContentType = ContentType.TEXT
type: str = ContentType.TEXT.value
text: str
@model_serializer
def serialize_model(self):
data: dict[str, str | dict[str, str]] = {
'type': self.type.value,
'type': self.type,
'text': self.text,
}
if self.cache_prompt:
@@ -34,14 +37,14 @@ class TextContent(Content):
class ImageContent(Content):
type: ContentType = ContentType.IMAGE_URL
type: str = ContentType.IMAGE_URL.value
image_urls: list[str]
@model_serializer
def serialize_model(self):
images: list[dict[str, str | dict[str, str]]] = []
for url in self.image_urls:
images.append({'type': self.type.value, 'image_url': {'url': url}})
images.append({'type': self.type, 'image_url': {'url': url}})
if self.cache_prompt and images:
images[-1]['cache_control'] = {'type': 'ephemeral'}
return images
@@ -65,4 +68,52 @@ class Message(BaseModel):
elif isinstance(item, ImageContent):
content.extend(item.model_dump())
return {'role': self.role, 'content': content}
return {'content': content, 'role': self.role}
def format_messages(
messages: Union[Message, list[Message]],
with_images: bool,
with_prompt_caching: bool,
) -> list[dict]:
if not isinstance(messages, list):
messages = [messages]
if with_images or with_prompt_caching:
return [message.model_dump() for message in messages]
converted_messages = []
for message in messages:
content_parts = []
role = 'user'
if isinstance(message, str) and message:
content_parts.append(message)
elif isinstance(message, dict):
role = message.get('role', 'user')
if 'content' in message and message['content']:
content_parts.append(message['content'])
elif isinstance(message, Message):
role = message.role
for content in message.content:
if isinstance(content, list):
for item in content:
if isinstance(item, TextContent) and item.text:
content_parts.append(item.text)
elif isinstance(content, TextContent) and content.text:
content_parts.append(content.text)
else:
logger.error(
f'>>> `message` is not a string, dict, or Message: {type(message)}'
)
if content_parts:
content_str = '\n'.join(content_parts)
converted_messages.append(
{
'role': role,
'content': content_str,
}
)
return converted_messages
+40 -38
View File
@@ -3,45 +3,47 @@ from enum import Enum
class ConfigType(str, Enum):
# For frontend
LLM_CUSTOM_LLM_PROVIDER = 'LLM_CUSTOM_LLM_PROVIDER'
LLM_DROP_PARAMS = 'LLM_DROP_PARAMS'
LLM_MAX_INPUT_TOKENS = 'LLM_MAX_INPUT_TOKENS'
LLM_MAX_OUTPUT_TOKENS = 'LLM_MAX_OUTPUT_TOKENS'
LLM_TOP_P = 'LLM_TOP_P'
LLM_TEMPERATURE = 'LLM_TEMPERATURE'
LLM_TIMEOUT = 'LLM_TIMEOUT'
LLM_API_KEY = 'LLM_API_KEY'
LLM_BASE_URL = 'LLM_BASE_URL'
AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID'
AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY'
AWS_REGION_NAME = 'AWS_REGION_NAME'
WORKSPACE_BASE = 'WORKSPACE_BASE'
WORKSPACE_MOUNT_PATH = 'WORKSPACE_MOUNT_PATH'
WORKSPACE_MOUNT_REWRITE = 'WORKSPACE_MOUNT_REWRITE'
WORKSPACE_MOUNT_PATH_IN_SANDBOX = 'WORKSPACE_MOUNT_PATH_IN_SANDBOX'
CACHE_DIR = 'CACHE_DIR'
LLM_MODEL = 'LLM_MODEL'
CONFIRMATION_MODE = 'CONFIRMATION_MODE'
BASE_CONTAINER_IMAGE = 'BASE_CONTAINER_IMAGE'
RUN_AS_OPENHANDS = 'RUN_AS_OPENHANDS'
LLM_EMBEDDING_MODEL = 'LLM_EMBEDDING_MODEL'
LLM_EMBEDDING_BASE_URL = 'LLM_EMBEDDING_BASE_URL'
LLM_EMBEDDING_DEPLOYMENT_NAME = 'LLM_EMBEDDING_DEPLOYMENT_NAME'
LLM_API_VERSION = 'LLM_API_VERSION'
LLM_NUM_RETRIES = 'LLM_NUM_RETRIES'
LLM_RETRY_MIN_WAIT = 'LLM_RETRY_MIN_WAIT'
LLM_RETRY_MAX_WAIT = 'LLM_RETRY_MAX_WAIT'
AGENT_MEMORY_MAX_THREADS = 'AGENT_MEMORY_MAX_THREADS'
AGENT_MEMORY_ENABLED = 'AGENT_MEMORY_ENABLED'
MAX_ITERATIONS = 'MAX_ITERATIONS'
AGENT = 'AGENT'
E2B_API_KEY = 'E2B_API_KEY'
SECURITY_ANALYZER = 'SECURITY_ANALYZER'
SANDBOX_USER_ID = 'SANDBOX_USER_ID'
SANDBOX_TIMEOUT = 'SANDBOX_TIMEOUT'
USE_HOST_NETWORK = 'USE_HOST_NETWORK'
DISABLE_COLOR = 'DISABLE_COLOR'
AGENT_MEMORY_ENABLED = 'AGENT_MEMORY_ENABLED'
AGENT_MEMORY_MAX_THREADS = 'AGENT_MEMORY_MAX_THREADS'
AWS_ACCESS_KEY_ID = 'AWS_ACCESS_KEY_ID'
AWS_REGION_NAME = 'AWS_REGION_NAME'
AWS_SECRET_ACCESS_KEY = 'AWS_SECRET_ACCESS_KEY'
BASE_CONTAINER_IMAGE = 'BASE_CONTAINER_IMAGE'
CACHE_DIR = 'CACHE_DIR'
CONFIRMATION_MODE = 'CONFIRMATION_MODE'
DEBUG = 'DEBUG'
DISABLE_COLOR = 'DISABLE_COLOR'
E2B_API_KEY = 'E2B_API_KEY'
FILE_UPLOADS_ALLOWED_EXTENSIONS = 'FILE_UPLOADS_ALLOWED_EXTENSIONS'
FILE_UPLOADS_MAX_FILE_SIZE_MB = 'FILE_UPLOADS_MAX_FILE_SIZE_MB'
FILE_UPLOADS_RESTRICT_FILE_TYPES = 'FILE_UPLOADS_RESTRICT_FILE_TYPES'
FILE_UPLOADS_ALLOWED_EXTENSIONS = 'FILE_UPLOADS_ALLOWED_EXTENSIONS'
LLM_API_KEY = 'LLM_API_KEY'
LLM_API_VERSION = 'LLM_API_VERSION'
LLM_BASE_URL = 'LLM_BASE_URL'
LLM_CACHING_PROMPT = 'LLM_CACHING_PROMPT'
LLM_CUSTOM_LLM_PROVIDER = 'LLM_CUSTOM_LLM_PROVIDER'
LLM_DROP_PARAMS = 'LLM_DROP_PARAMS'
LLM_EMBEDDING_BASE_URL = 'LLM_EMBEDDING_BASE_URL'
LLM_EMBEDDING_DEPLOYMENT_NAME = 'LLM_EMBEDDING_DEPLOYMENT_NAME'
LLM_EMBEDDING_MODEL = 'LLM_EMBEDDING_MODEL'
LLM_MAX_INPUT_TOKENS = 'LLM_MAX_INPUT_TOKENS'
LLM_MAX_OUTPUT_TOKENS = 'LLM_MAX_OUTPUT_TOKENS'
LLM_MODEL = 'LLM_MODEL'
LLM_NUM_RETRIES = 'LLM_NUM_RETRIES'
LLM_RETRY_MAX_WAIT = 'LLM_RETRY_MAX_WAIT'
LLM_RETRY_MIN_WAIT = 'LLM_RETRY_MIN_WAIT'
LLM_TEMPERATURE = 'LLM_TEMPERATURE'
LLM_TIMEOUT = 'LLM_TIMEOUT'
LLM_TOP_P = 'LLM_TOP_P'
LLM_DISABLE_VISION = 'LLM_DISABLE_VISION'
MAX_ITERATIONS = 'MAX_ITERATIONS'
RUN_AS_OPENHANDS = 'RUN_AS_OPENHANDS'
SANDBOX_TIMEOUT = 'SANDBOX_TIMEOUT'
SANDBOX_USER_ID = 'SANDBOX_USER_ID'
SECURITY_ANALYZER = 'SECURITY_ANALYZER'
USE_HOST_NETWORK = 'USE_HOST_NETWORK'
WORKSPACE_BASE = 'WORKSPACE_BASE'
WORKSPACE_MOUNT_PATH = 'WORKSPACE_MOUNT_PATH'
WORKSPACE_MOUNT_PATH_IN_SANDBOX = 'WORKSPACE_MOUNT_PATH_IN_SANDBOX'
WORKSPACE_MOUNT_REWRITE = 'WORKSPACE_MOUNT_REWRITE'
+4
View File
@@ -13,6 +13,10 @@ from openhands.events.action.action import (
class CmdRunAction(Action):
command: str
thought: str = ''
blocking: bool = False
# If False, the command will be run in a non-blocking / interactive way
# The partial command outputs will be returned as output observation.
# If True, the command will be run for max .timeout seconds.
keep_prompt: bool = True
# if True, the command prompt will be kept in the command output observation
# Example of command output:
+5
View File
@@ -49,3 +49,8 @@ class Event:
@timeout.setter
def timeout(self, value: int | None) -> None:
self._timeout = value
# Check if .blocking is an attribute of the event
if hasattr(self, 'blocking'):
# .blocking needs to be set to True if .timeout is set
self.blocking = True
+1 -1
View File
@@ -6,7 +6,7 @@ from openhands.events.observation.observation import Observation
@dataclass
class UserRejectObservation(Observation):
"""This data class represents the result of a successful action."""
"""This data class represents the result of a rejected action."""
observation: str = ObservationType.USER_REJECTED
+179 -129
View File
@@ -2,6 +2,7 @@ import asyncio
import copy
import warnings
from functools import partial
from typing import Union
from openhands.core.config import LLMConfig
@@ -14,6 +15,7 @@ from litellm.exceptions import (
APIConnectionError,
ContentPolicyViolationError,
InternalServerError,
NotFoundError,
OpenAIError,
RateLimitError,
ServiceUnavailableError,
@@ -23,12 +25,13 @@ from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_random_exponential,
wait_exponential,
)
from openhands.core.exceptions import UserCancelledError
from openhands.core.exceptions import LLMResponseError, UserCancelledError
from openhands.core.logger import llm_prompt_logger, llm_response_logger
from openhands.core.logger import openhands_logger as logger
from openhands.core.message import Message, format_messages
from openhands.core.metrics import Metrics
__all__ = ['LLM']
@@ -60,12 +63,9 @@ class LLM:
Args:
config: The LLM configuration
"""
self.config = copy.deepcopy(config)
self.metrics = metrics if metrics is not None else Metrics()
self.cost_metric_supported = True
self.supports_prompt_caching = (
self.config.model in cache_prompting_supported_models
)
self.config = copy.deepcopy(config)
# Set up config attributes with default values to prevent AttributeError
LLMConfig.set_missing_attributes(self.config)
@@ -83,6 +83,15 @@ class LLM:
except Exception as e:
logger.warning(f'Could not get model info for {config.model}:\n{e}')
# Tuple of exceptions to retry on
self.retry_exceptions = (
APIConnectionError,
ContentPolicyViolationError,
InternalServerError,
OpenAIError,
RateLimitError,
)
# Set the max tokens in an LM-specific way if not set
if self.config.max_input_tokens is None:
if (
@@ -122,33 +131,58 @@ class LLM:
top_p=self.config.top_p,
)
if self.vision_is_active():
logger.debug('LLM: model has vision enabled')
completion_unwrapped = self._completion
def attempt_on_error(retry_state):
"""Custom attempt function for litellm completion."""
logger.error(
f'{retry_state.outcome.exception()}. Attempt #{retry_state.attempt_number} | You can customize these settings in the configuration.',
f'{retry_state.outcome.exception()}. Attempt #{retry_state.attempt_number} | You can customize retry values in the configuration.',
exc_info=False,
)
return None
@retry(
reraise=True,
stop=stop_after_attempt(self.config.num_retries),
wait=wait_random_exponential(
def custom_completion_wait(retry_state):
"""Custom wait function for litellm completion."""
if not retry_state:
return 0
exception = retry_state.outcome.exception() if retry_state.outcome else None
if exception is None:
return 0
min_wait_time = self.config.retry_min_wait
max_wait_time = self.config.retry_max_wait
# for rate limit errors, wait 1 minute by default, max 4 minutes between retries
exception_type = type(exception).__name__
logger.error(f'\nexception_type: {exception_type}\n')
if exception_type == 'RateLimitError':
min_wait_time = 60
max_wait_time = 240
elif exception_type == 'BadRequestError' and exception.response:
# this should give us the burried, actual error message from
# the LLM model.
logger.error(f'\n\nBadRequestError: {exception.response}\n\n')
# Return the wait time using exponential backoff
exponential_wait = wait_exponential(
multiplier=self.config.retry_multiplier,
min=self.config.retry_min_wait,
max=self.config.retry_max_wait,
),
retry=retry_if_exception_type(
(
RateLimitError,
APIConnectionError,
ServiceUnavailableError,
InternalServerError,
ContentPolicyViolationError,
)
),
min=min_wait_time,
max=max_wait_time,
)
# Call the exponential wait function with retry_state to get the actual wait time
return exponential_wait(retry_state)
@retry(
after=attempt_on_error,
stop=stop_after_attempt(self.config.num_retries),
reraise=True,
retry=retry_if_exception_type(self.retry_exceptions),
wait=custom_completion_wait,
)
def wrapper(*args, **kwargs):
"""Wrapper for the litellm completion function. Logs the input and output of the completion function."""
@@ -156,47 +190,33 @@ class LLM:
if 'messages' in kwargs:
messages = kwargs['messages']
else:
messages = args[1]
messages = args[1] if len(args) > 1 else []
# log the prompt
debug_message = ''
for message in messages:
content = message['content']
# this serves to prevent empty messages and logging the messages
debug_message = self._get_debug_message(messages)
if isinstance(content, list):
for element in content:
if isinstance(element, dict):
if 'text' in element:
content_str = element['text'].strip()
elif (
'image_url' in element and 'url' in element['image_url']
):
content_str = element['image_url']['url']
else:
content_str = str(element)
else:
content_str = str(element)
debug_message += message_separator + content_str
else:
content_str = str(content)
debug_message += message_separator + content_str
llm_prompt_logger.debug(debug_message)
if self.is_caching_prompt_active():
# Anthropic-specific prompt caching
if 'claude-3' in self.config.model:
kwargs['extra_headers'] = {
'anthropic-beta': 'prompt-caching-2024-07-31',
}
# skip if messages is empty (thus debug_message is empty)
if debug_message:
llm_prompt_logger.debug(debug_message)
resp = completion_unwrapped(*args, **kwargs)
else:
logger.debug('No completion messages!')
resp = {'choices': [{'message': {'content': ''}}]}
# log the response
message_back = resp['choices'][0]['message']['content']
if message_back:
llm_response_logger.debug(message_back)
llm_response_logger.debug(message_back)
# post-process to log costs
self._post_completion(resp)
# post-process to log costs
self._post_completion(resp)
return resp
@@ -220,23 +240,11 @@ class LLM:
async_completion_unwrapped = self._async_completion
@retry(
reraise=True,
stop=stop_after_attempt(self.config.num_retries),
wait=wait_random_exponential(
multiplier=self.config.retry_multiplier,
min=self.config.retry_min_wait,
max=self.config.retry_max_wait,
),
retry=retry_if_exception_type(
(
RateLimitError,
APIConnectionError,
ServiceUnavailableError,
InternalServerError,
ContentPolicyViolationError,
)
),
after=attempt_on_error,
stop=stop_after_attempt(self.config.num_retries),
reraise=True,
retry=retry_if_exception_type(self.retry_exceptions),
wait=custom_completion_wait,
)
async def async_completion_wrapper(*args, **kwargs):
"""Async wrapper for the litellm acompletion function."""
@@ -244,34 +252,10 @@ class LLM:
if 'messages' in kwargs:
messages = kwargs['messages']
else:
messages = args[1]
messages = args[1] if len(args) > 1 else []
# log the prompt
debug_message = ''
for message in messages:
content = message['content']
if isinstance(content, list):
for element in content:
if isinstance(element, dict):
if 'text' in element:
content_str = element['text']
elif (
'image_url' in element and 'url' in element['image_url']
):
content_str = element['image_url']['url']
else:
content_str = str(element)
else:
content_str = str(element)
debug_message += message_separator + content_str
else:
content_str = str(content)
debug_message += message_separator + content_str
llm_prompt_logger.debug(debug_message)
# this serves to prevent empty messages and logging the messages
debug_message = self._get_debug_message(messages)
async def check_stopped():
while True:
@@ -287,7 +271,12 @@ class LLM:
try:
# Directly call and await litellm_acompletion
resp = await async_completion_unwrapped(*args, **kwargs)
if debug_message:
llm_prompt_logger.debug(debug_message)
resp = await async_completion_unwrapped(*args, **kwargs)
else:
logger.debug('No completion messages!')
resp = {'choices': [{'message': {'content': ''}}]}
# skip if messages is empty (thus debug_message is empty)
if debug_message:
@@ -303,14 +292,14 @@ class LLM:
except UserCancelledError:
logger.info('LLM request cancelled by user.')
raise
except OpenAIError as e:
logger.error(f'OpenAIError occurred:\n{e}')
raise
except (
RateLimitError,
APIConnectionError,
ServiceUnavailableError,
ContentPolicyViolationError,
InternalServerError,
NotFoundError,
OpenAIError,
RateLimitError,
ServiceUnavailableError,
) as e:
logger.error(f'Completion Error occurred:\n{e}')
raise
@@ -324,23 +313,11 @@ class LLM:
pass
@retry(
reraise=True,
stop=stop_after_attempt(self.config.num_retries),
wait=wait_random_exponential(
multiplier=self.config.retry_multiplier,
min=self.config.retry_min_wait,
max=self.config.retry_max_wait,
),
retry=retry_if_exception_type(
(
RateLimitError,
APIConnectionError,
ServiceUnavailableError,
InternalServerError,
ContentPolicyViolationError,
)
),
after=attempt_on_error,
stop=stop_after_attempt(self.config.num_retries),
reraise=True,
retry=retry_if_exception_type(self.retry_exceptions),
wait=custom_completion_wait,
)
async def async_acompletion_stream_wrapper(*args, **kwargs):
"""Async wrapper for the litellm acompletion with streaming function."""
@@ -348,7 +325,7 @@ class LLM:
if 'messages' in kwargs:
messages = kwargs['messages']
else:
messages = args[1]
messages = args[1] if len(args) > 1 else []
# log the prompt
debug_message = ''
@@ -381,14 +358,14 @@ class LLM:
except UserCancelledError:
logger.info('LLM request cancelled by user.')
raise
except OpenAIError as e:
logger.error(f'OpenAIError occurred:\n{e}')
raise
except (
RateLimitError,
APIConnectionError,
ServiceUnavailableError,
ContentPolicyViolationError,
InternalServerError,
NotFoundError,
OpenAIError,
RateLimitError,
ServiceUnavailableError,
) as e:
logger.error(f'Completion Error occurred:\n{e}')
raise
@@ -400,6 +377,38 @@ class LLM:
self._async_completion = async_completion_wrapper # type: ignore
self._async_streaming_completion = async_acompletion_stream_wrapper # type: ignore
def _get_debug_message(self, messages):
if not messages:
return ''
messages = messages if isinstance(messages, list) else [messages]
return message_separator.join(
self._format_message_content(msg) for msg in messages if msg['content']
)
def _format_message_content(self, message):
content = message['content']
if isinstance(content, list):
return self._format_list_content(content)
return str(content)
def _format_list_content(self, content_list):
return '\n'.join(
self._format_content_element(element) for element in content_list
)
def _format_content_element(self, element):
if isinstance(element, dict):
if 'text' in element:
return element['text']
if (
self.vision_is_active()
and 'image_url' in element
and 'url' in element['image_url']
):
return element['image_url']['url']
return str(element)
async def _call_acompletion(self, *args, **kwargs):
return await litellm.acompletion(*args, **kwargs)
@@ -409,7 +418,10 @@ class LLM:
Check the complete documentation at https://litellm.vercel.app/docs/completion
"""
return self._completion
try:
return self._completion
except Exception as e:
raise LLMResponseError(e)
@property
def async_completion(self):
@@ -417,7 +429,10 @@ class LLM:
Check the complete documentation at https://litellm.vercel.app/docs/providers/ollama#example-usage---streaming--acompletion
"""
return self._async_completion
try:
return self._async_completion
except Exception as e:
raise LLMResponseError(e)
@property
def async_streaming_completion(self):
@@ -425,10 +440,34 @@ class LLM:
Check the complete documentation at https://litellm.vercel.app/docs/providers/ollama#example-usage---streaming--acompletion
"""
return self._async_streaming_completion
try:
return self._async_streaming_completion
except Exception as e:
raise LLMResponseError(e)
def supports_vision(self):
return litellm.supports_vision(self.config.model)
def vision_is_active(self):
return not self.config.disable_vision and self._supports_vision()
def _supports_vision(self):
"""Acquire from litellm if model is vision capable.
Returns:
bool: True if model is vision capable. If model is not supported by litellm, it will return False.
"""
try:
return litellm.supports_vision(self.config.model)
except Exception:
return False
def is_caching_prompt_active(self) -> bool:
"""Check if prompt caching is enabled and supported for current model.
Returns:
boolean: True if prompt caching is active for the given model.
"""
return self.config.caching_prompt is True and any(
model in self.config.model for model in cache_prompting_supported_models
)
def _post_completion(self, response) -> None:
"""Post-process the completion response."""
@@ -484,7 +523,11 @@ class LLM:
Returns:
int: The number of tokens.
"""
return litellm.token_counter(model=self.config.model, messages=messages)
try:
return litellm.token_counter(model=self.config.model, messages=messages)
except Exception:
# TODO: this is to limit logspam in case token count is not supported
return 0
def is_local(self):
"""Determines if the system is using a locally running LLM.
@@ -550,3 +593,10 @@ class LLM:
def reset(self):
self.metrics = Metrics()
def format_messages_for_llm(
self, messages: Union[Message, list[Message]]
) -> list[dict]:
return format_messages(
messages, self.vision_is_active(), self.is_caching_prompt_active()
)
+1 -1
View File
@@ -43,7 +43,7 @@ if LLAMA_INDEX_AVAILABLE:
def attempt_on_error(retry_state):
logger.error(
f'{retry_state.outcome.exception()}. Attempt #{retry_state.attempt_number} | You can customize these settings in the configuration.',
f'{retry_state.outcome.exception()}. Attempt #{retry_state.attempt_number} | You can customize retry values in the configuration.',
exc_info=False,
)
return None
+57 -63
View File
@@ -17,8 +17,6 @@ from pathlib import Path
import pexpect
from fastapi import FastAPI, HTTPException, Request, UploadFile
from fastapi.responses import JSONResponse
from pathspec import PathSpec
from pathspec.patterns import GitWildMatchPattern
from pydantic import BaseModel
from uvicorn import run
@@ -60,6 +58,7 @@ ROOT_GID = 0
INIT_COMMANDS = [
'git config --global user.name "openhands" && git config --global user.email "openhands@all-hands.dev" && alias git="git --no-pager"',
]
SOFT_TIMEOUT_SECONDS = 5
class RuntimeClient:
@@ -114,6 +113,7 @@ class RuntimeClient:
logger.info(f'AgentSkills initialized: {obs}')
await self._init_bash_commands()
logger.info('Runtime client initialized.')
def _init_user(self, username: str, user_id: int) -> None:
"""Create user if not exists."""
@@ -212,6 +212,9 @@ class RuntimeClient:
if ps1 == pexpect.EOF:
logger.error(f'Bash shell EOF! {self.shell.after=}, {self.shell.before=}')
raise RuntimeError('Bash shell EOF')
if ps1 == pexpect.TIMEOUT:
logger.warning('Bash shell timeout')
return ''
# begin at the last occurrence of '[PEXPECT_BEGIN]'.
# In multi-line bash commands, the prompt will be repeated
@@ -243,39 +246,56 @@ class RuntimeClient:
command: str,
timeout: int | None,
keep_prompt: bool = True,
kill_on_timeout: bool = True,
) -> tuple[str, int]:
logger.debug(f'Executing command: {command}')
self.shell.sendline(command)
return self._continue_bash(
timeout=timeout, keep_prompt=keep_prompt, kill_on_timeout=kill_on_timeout
)
def _interrupt_bash(self, timeout: int | None = None) -> tuple[str, int]:
self.shell.sendintr() # send SIGINT to the shell
self.shell.expect(self.__bash_expect_regex, timeout=timeout)
output = self.shell.before
exit_code = 130 # SIGINT
return output, exit_code
def _continue_bash(
self,
timeout: int | None,
keep_prompt: bool = True,
kill_on_timeout: bool = True,
) -> tuple[str, int]:
try:
self.shell.sendline(command)
self.shell.expect(self.__bash_expect_regex, timeout=timeout)
output = self.shell.before
# Get exit code
self.shell.sendline('echo $?')
logger.debug(f'Executing command for exit code: {command}')
logger.debug('Requesting exit code...')
self.shell.expect(self.__bash_expect_regex, timeout=timeout)
_exit_code_output = self.shell.before
logger.debug(f'Exit code Output: {_exit_code_output}')
exit_code = int(_exit_code_output.strip().split()[0])
except pexpect.TIMEOUT as e:
self.shell.sendintr() # send SIGINT to the shell
self.shell.expect(self.__bash_expect_regex, timeout=timeout)
output = self.shell.before
output += (
'\r\n\r\n'
+ f'[Command timed out after {timeout} seconds. SIGINT was sent to interrupt it.]'
)
exit_code = 130 # SIGINT
logger.error(f'Failed to execute command: {command}. Error: {e}')
if kill_on_timeout:
output, exit_code = self._interrupt_bash()
output += (
'\r\n\r\n'
+ f'[Command timed out after {timeout} seconds. SIGINT was sent to interrupt it.]'
)
logger.error(f'Failed to execute command. Error: {e}')
else:
output = self.shell.before or ''
exit_code = -1
finally:
bash_prompt = self._get_bash_prompt_and_update_pwd()
if keep_prompt:
output += '\r\n' + bash_prompt
logger.debug(f'Command output: {output}')
return output, exit_code
async def run_action(self, action) -> Observation:
@@ -293,11 +313,25 @@ class RuntimeClient:
commands = split_bash_commands(action.command)
all_output = ''
for command in commands:
output, exit_code = self._execute_bash(
command,
timeout=action.timeout,
keep_prompt=action.keep_prompt,
)
if command == '':
output, exit_code = self._continue_bash(
timeout=SOFT_TIMEOUT_SECONDS,
keep_prompt=action.keep_prompt,
kill_on_timeout=False,
)
elif command.lower() == 'ctrl+c':
output, exit_code = self._interrupt_bash(
timeout=SOFT_TIMEOUT_SECONDS
)
else:
output, exit_code = self._execute_bash(
command,
timeout=SOFT_TIMEOUT_SECONDS
if not action.blocking
else action.timeout,
keep_prompt=action.keep_prompt,
kill_on_timeout=False if not action.blocking else True,
)
if all_output:
# previous output already exists with prompt "user@hostname:working_dir #""
# we need to add the command to the previous output,
@@ -482,7 +516,6 @@ if __name__ == '__main__':
browsergym_eval_env=args.browsergym_eval_env,
)
await client.ainit()
logger.info('Runtime client initialized.')
yield
# Clean up & release the resources
client.close()
@@ -616,52 +649,12 @@ if __name__ == '__main__':
if not os.path.exists(full_path) or not os.path.isdir(full_path):
return []
# Check if .gitignore exists
gitignore_path = os.path.join(full_path, '.gitignore')
if os.path.exists(gitignore_path):
# Use PathSpec to parse .gitignore
with open(gitignore_path, 'r') as f:
spec = PathSpec.from_lines(GitWildMatchPattern, f.readlines())
else:
# Fallback to default exclude list if .gitignore doesn't exist
default_exclude = [
'.git',
'.DS_Store',
'.svn',
'.hg',
'.idea',
'.vscode',
'.settings',
'.pytest_cache',
'__pycache__',
'node_modules',
'vendor',
'build',
'dist',
'bin',
'logs',
'log',
'tmp',
'temp',
'coverage',
'venv',
'env',
]
spec = PathSpec.from_lines(GitWildMatchPattern, default_exclude)
entries = os.listdir(full_path)
# Filter entries using PathSpec
filtered_entries = [
os.path.join(full_path, entry)
for entry in entries
if not spec.match_file(os.path.relpath(entry, str(full_path)))
]
# Separate directories and files
directories = []
files = []
for entry in filtered_entries:
for entry in entries:
# Remove leading slash and any parent directory components
entry_relative = entry.lstrip('/').split('/')[-1]
@@ -689,6 +682,7 @@ if __name__ == '__main__':
logger.error(f'Error listing files: {e}', exc_info=True)
return []
logger.info('Runtime client initialized.')
logger.info(f'Starting action execution API on port {args.port}')
print(f'Starting action execution API on port {args.port}')
run(app, host='0.0.0.0', port=args.port)
+54 -23
View File
@@ -13,6 +13,7 @@ from openhands.core.config import AppConfig
from openhands.core.logger import openhands_logger as logger
from openhands.events import EventStream
from openhands.events.action import (
ActionConfirmationStatus,
BrowseInteractiveAction,
BrowseURLAction,
CmdRunAction,
@@ -25,6 +26,7 @@ from openhands.events.observation import (
ErrorObservation,
NullObservation,
Observation,
UserRejectObservation,
)
from openhands.events.serialization import event_to_dict, observation_from_dict
from openhands.events.serialization.action import ACTION_TYPE_TO_CLASS
@@ -45,6 +47,9 @@ class LogBuffer:
"""
def __init__(self, container: docker.models.containers.Container):
self.client_ready = False
self.init_msg = 'Runtime client initialized.'
self.buffer: list[str] = []
self.lock = threading.Lock()
self.log_generator = container.logs(stream=True, follow=True)
@@ -75,9 +80,12 @@ class LogBuffer:
if self._stop_event.is_set():
break
if log_line:
self.append(log_line.decode('utf-8').rstrip())
decoded_line = log_line.decode('utf-8').rstrip()
self.append(decoded_line)
if self.init_msg in decoded_line:
self.client_ready = True
except Exception as e:
logger.error(f'Error in stream_logs: {e}')
logger.error(f'Error streaming docker logs: {e}')
def __del__(self):
if self.log_stream_thread.is_alive():
@@ -123,11 +131,10 @@ class EventStreamRuntime(Runtime):
self.action_semaphore = threading.Semaphore(1) # Ensure one action at a time
self.runtime_builder = DockerRuntimeBuilder(self.docker_client)
logger.debug(f'EventStreamRuntime `{sid}` config:\n{self.config}')
logger.debug(f'EventStreamRuntime `{sid}`')
# Buffer for container logs
self.log_buffer: LogBuffer | None = None
self.startup_done = False
if self.config.sandbox.runtime_extra_deps:
logger.info(
@@ -152,6 +159,8 @@ class EventStreamRuntime(Runtime):
# will initialize both the event stream and the env vars
super().__init__(config, event_stream, sid, plugins, env_vars)
self._wait_until_alive()
logger.info(
f'Container initialized with plugins: {[plugin.name for plugin in self.plugins]}'
)
@@ -163,7 +172,7 @@ class EventStreamRuntime(Runtime):
return docker.from_env()
except Exception as ex:
logger.error(
'Launch docker client failed. Please make sure you have installed docker and started the docker daemon.'
'Launch docker client failed. Please make sure you have installed docker and started docker desktop/daemon.'
)
raise ex
@@ -244,9 +253,9 @@ class EventStreamRuntime(Runtime):
@tenacity.retry(
stop=tenacity.stop_after_attempt(10),
wait=tenacity.wait_exponential(multiplier=2, min=10, max=60),
reraise=(ConnectionRefusedError,),
)
def _wait_until_alive(self):
init_msg = 'Runtime client initialized.'
logger.debug('Getting container logs...')
# Print and clear the log buffer
@@ -254,26 +263,23 @@ class EventStreamRuntime(Runtime):
self.log_buffer is not None
), 'Log buffer is expected to be initialized when container is started'
# Always process logs, regardless of startup_done status
# Always process logs, regardless of client_ready status
logs = self.log_buffer.get_and_clear()
if logs:
formatted_logs = '\n'.join([f' |{log}' for log in logs])
logger.info(
'\n'
+ '-' * 30
+ '-' * 35
+ 'Container logs:'
+ '-' * 30
+ '-' * 35
+ f'\n{formatted_logs}'
+ '\n'
+ '-' * 90
+ '-' * 80
)
# Check for initialization message even if startup_done is True
if any(init_msg in log for log in logs):
self.startup_done = True
if not self.startup_done:
if not self.log_buffer.client_ready:
attempts = 0
while not self.startup_done and attempts < 10:
while not self.log_buffer.client_ready and attempts < 5:
attempts += 1
time.sleep(1)
logs = self.log_buffer.get_and_clear()
@@ -281,16 +287,13 @@ class EventStreamRuntime(Runtime):
formatted_logs = '\n'.join([f' |{log}' for log in logs])
logger.info(
'\n'
+ '-' * 30
+ '-' * 35
+ 'Container logs:'
+ '-' * 30
+ '-' * 35
+ f'\n{formatted_logs}'
+ '\n'
+ '-' * 90
+ '-' * 80
)
if any(init_msg in log for log in logs):
self.startup_done = True
break
response = self.session.get(f'{self.api_url}/alive')
if response.status_code == 200:
@@ -304,7 +307,15 @@ class EventStreamRuntime(Runtime):
def sandbox_workspace_dir(self):
return self.config.workspace_mount_path_in_sandbox
def close(self, close_client: bool = True):
def close(self, close_client: bool = True, rm_all_containers: bool = True):
"""
Closes the EventStreamRuntime and associated objects
Parameters:
- close_client (bool): Whether to close the DockerClient
- rm_all_containers (bool): Whether to remove all containers with the 'openhands-sandbox-' prefix
"""
if self.log_buffer:
self.log_buffer.close()
@@ -314,7 +325,13 @@ class EventStreamRuntime(Runtime):
containers = self.docker_client.containers.list(all=True)
for container in containers:
try:
if container.name.startswith(self.container_name_prefix):
# If the app doesn't shut down properly, it can leave runtime containers on the system. This ensures
# that all 'openhands-sandbox-' containers are removed as well.
if rm_all_containers and container.name.startswith(
self.container_name_prefix
):
container.remove(force=True)
elif container.name == self.container_name:
logs = container.logs(tail=1000).decode('utf-8')
logger.debug(
f'==== Container logs ====\n{logs}\n==== End of container logs ===='
@@ -322,6 +339,7 @@ class EventStreamRuntime(Runtime):
container.remove(force=True)
except docker.errors.NotFound:
pass
if close_client:
self.docker_client.close()
@@ -333,6 +351,12 @@ class EventStreamRuntime(Runtime):
with self.action_semaphore:
if not action.runnable:
return NullObservation('')
if (
hasattr(action, 'is_confirmed')
and action.is_confirmed
== ActionConfirmationStatus.AWAITING_CONFIRMATION
):
return NullObservation('')
action_type = action.action # type: ignore[attr-defined]
if action_type not in ACTION_TYPE_TO_CLASS:
return ErrorObservation(f'Action {action_type} does not exist.')
@@ -340,6 +364,13 @@ class EventStreamRuntime(Runtime):
return ErrorObservation(
f'Action {action_type} is not supported in the current runtime.'
)
if (
hasattr(action, 'is_confirmed')
and action.is_confirmed == ActionConfirmationStatus.REJECTED
):
return UserRejectObservation(
'Action has been rejected by the user! Waiting for further user input.'
)
logger.info('Awaiting session')
self._wait_until_alive()

Some files were not shown because too many files have changed in this diff Show More