Compare commits

..

24 Commits

Author SHA1 Message Date
Robert Brennan f1a90fdf71 Revert "Add Swagger UI to docs with API navbar link"
This reverts commit f0297c819a.
2025-04-21 17:23:16 -04:00
openhands f0297c819a Add Swagger UI to docs with API navbar link 2025-04-21 21:17:16 +00:00
Robert Brennan 30c714147c Revert "Integrate OpenAPI documentation using docusaurus-openapi-docs"
This reverts commit 43698329bb.
2025-04-21 17:11:00 -04:00
Robert Brennan 733f5172b7 Revert "Fix sidebar.ts format to match Docusaurus requirements"
This reverts commit 1fb9a194a0.
2025-04-21 17:10:50 -04:00
Robert Brennan 3f9b8a78fa Revert "sort of working"
This reverts commit d5f017483a.
2025-04-21 17:10:43 -04:00
Robert Brennan d5f017483a sort of working 2025-04-21 17:07:51 -04:00
openhands 1fb9a194a0 Fix sidebar.ts format to match Docusaurus requirements 2025-04-21 20:28:53 +00:00
openhands 43698329bb Integrate OpenAPI documentation using docusaurus-openapi-docs 2025-04-21 20:10:14 +00:00
openhands fe14089feb Add OpenAPI specification for the API 2025-04-21 19:45:21 +00:00
Michael Panchenko 52d881c98d Fix issue with "default" in tool schema for gemini-preview (#7964) 2025-04-21 21:21:33 +02:00
Rohit Malhotra 6ac23aea80 [Feat]: Add branch naming convention (#7989) 2025-04-21 18:35:16 +00:00
Xingyao Wang 0412949018 Add agent_class to SystemMessageAction and display in frontend (#7935)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-04-22 02:08:15 +08:00
Michael Panchenko 5b5adc5c7b Minor refactoring: remove step and task-creation in AgentController (#7981) 2025-04-21 19:28:22 +02:00
dependabot[bot] 0b40f6fac8 chore(deps): bump the version-all group with 7 updates (#7980)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-21 19:14:58 +02:00
Rohit Malhotra 44d488b718 [Fix]: Building runtime image with unspecified base container img (#7977) 2025-04-21 11:30:22 -04:00
Rohit Malhotra 4f9120ffc6 (Chore): Unsupport resolve all issues in resolver (#7975) 2025-04-21 14:37:53 +00:00
Tetsuuuuuuu 50426edaa1 feat: add argument --base-container-image to resolver (#7612)
Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
2025-04-21 10:01:03 -04:00
Xingyao Wang a792f84a83 chore: improve logging for "not a git repository" (#7944) 2025-04-21 16:13:48 +04:00
Alexander Litzenberger cd9d96766c Update documentation on local-llms (#7805)
Co-authored-by: Alex Litzenberger <alex@agot.ai>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2025-04-21 00:20:13 +00:00
Michael Panchenko 14564b25d6 Fix linting (#7965) 2025-04-21 06:34:40 +08:00
Rohit Malhotra 0637b5b912 [Fix]: Replace duplicate enums for providers in resolver (#7954) 2025-04-20 14:06:18 -04:00
Rohit Malhotra 20bf48b693 [Fix]: mismatch between repo object definition between FE and BE (#7953) 2025-04-20 00:28:10 +00:00
Michael Panchenko 5b3270be2d Possibility to disable default tools (#7951) 2025-04-20 02:14:46 +02:00
Tom Deckers ae43744052 Fix Github Enterprise GraphQL URL (#7939)
Co-authored-by: Tom Deckers <tdeckers@cisco.com>
Co-authored-by: Robert Brennan <accounts@rbren.io>
Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
2025-04-19 21:09:00 +00:00
54 changed files with 3968 additions and 1613 deletions
+13
View File
@@ -221,9 +221,22 @@ enable_browsing = true
# Whether the LLM draft editor is enabled
enable_llm_editor = false
# Whether the standard editor tool (str_replace_editor) is enabled
# Only has an effect if enable_llm_editor is False
enable_editor = true
# Whether the IPython tool is enabled
enable_jupyter = true
# Whether the command tool is enabled
enable_cmd = true
# Whether the think tool is enabled
enable_think = true
# Whether the finish tool is enabled
enable_finish = true
# LLM config group to use
#llm_config = 'your-llm-config-group'
+4 -4
View File
@@ -15,7 +15,7 @@ It is highly recommended that you use GPUs to serve local models for optimal exp
For example, to download [OpenHands LM 32B v0.1](https://huggingface.co/all-hands/openhands-lm-32b-v0.1):
```bash
huggingface-cli download all-hands/openhands-lm-32b-v0.1 --local-dir my_folder/openhands-lm-32b-v0.1
huggingface-cli download all-hands/openhands-lm-32b-v0.1 --local-dir all-hands/openhands-lm-32b-v0.1
```
## Create an OpenAI-Compatible Endpoint With a Model Serving Framework
@@ -27,7 +27,7 @@ huggingface-cli download all-hands/openhands-lm-32b-v0.1 --local-dir my_folder/o
```bash
SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN=1 python3 -m sglang.launch_server \
--model my_folder/openhands-lm-32b-v0.1 \
--model all-hands/openhands-lm-32b-v0.1 \
--served-model-name openhands-lm-32b-v0.1 \
--port 8000 \
--tp 2 --dp 1 \
@@ -41,7 +41,7 @@ SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN=1 python3 -m sglang.launch_server \
- Example launch command for OpenHands LM 32B (with at least 2 GPUs):
```bash
vllm serve my_folder/openhands-lm-32b-v0.1 \
vllm serve all-hands/openhands-lm-32b-v0.1 \
--host 0.0.0.0 --port 8000 \
--api-key mykey \
--tensor-parallel-size 2 \
@@ -67,7 +67,7 @@ Ensure `config.toml` exists by running `make setup-config` which will create one
workspace_base="/path/to/your/workspace"
[llm]
embedding_model="local"
model="openhands-lm-32b-v0.1"
ollama_base_url="http://localhost:8000"
```
+2098
View File
File diff suppressed because it is too large Load Diff
@@ -65,7 +65,7 @@ def main(
ci_mode = pred['metadata']['details'].get('mode', '') == 'swt-ci'
try:
git_diff = remove_setup_files(git_diff, pred['instance'], ci_mode)
except:
except: # noqa: E722
_LOGGER.warning(
'Warning: Invalid git diff found for instance %s',
pred['instance_id'],
@@ -45,7 +45,15 @@ describe("Empty state", () => {
it("should render suggestions if empty", () => {
const { store } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@@ -68,7 +76,15 @@ describe("Empty state", () => {
it("should render the default suggestions", () => {
renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@@ -98,7 +114,15 @@ describe("Empty state", () => {
const user = userEvent.setup();
const { store } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@@ -127,7 +151,15 @@ describe("Empty state", () => {
const user = userEvent.setup();
const { rerender } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@@ -56,12 +56,16 @@ describe("GitRepositorySelector", () => {
full_name: "test/repo1",
git_provider: "github" as Provider,
stargazers_count: 100,
is_public: true,
pushed_at: "2023-01-01T00:00:00Z",
},
{
id: 2,
full_name: "test/repo2",
git_provider: "github" as Provider,
stargazers_count: 200,
is_public: true,
pushed_at: "2023-01-02T00:00:00Z",
},
];
+108 -15
View File
@@ -9,6 +9,7 @@
"version": "0.33.0",
"dependencies": {
"@heroui/react": "2.7.6",
"@microlink/react-json-view": "^1.26.1",
"@monaco-editor/react": "^4.7.0-rc.0",
"@react-router/node": "^7.5.1",
"@react-router/serve": "^7.5.1",
@@ -29,6 +30,7 @@
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.25",
"jose": "^6.0.10",
"lucide-react": "^0.501.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.236.2",
"react": "^19.1.0",
@@ -3588,6 +3590,24 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@microlink/react-json-view": {
"version": "1.26.1",
"resolved": "https://registry.npmjs.org/@microlink/react-json-view/-/react-json-view-1.26.1.tgz",
"integrity": "sha512-2H5QCYdZlJi+oN4YBiUYPPFTNh/KLCN9i9yz8NwmSkRqXSRXYtEVIRffc9L34jdopKGK/tK21SeuzXVJHQLkfQ==",
"license": "MIT",
"dependencies": {
"react-base16-styling": "~0.9.0",
"react-lifecycles-compat": "~3.0.4",
"react-textarea-autosize": "~8.5.7"
},
"engines": {
"node": ">=17"
},
"peerDependencies": {
"react": ">= 15",
"react-dom": ">= 15"
}
},
"node_modules/@mjackson/node-fetch-server": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz",
@@ -6736,6 +6756,12 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/base16": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz",
"integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==",
"license": "MIT"
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@@ -7248,9 +7274,9 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.0.tgz",
"integrity": "sha512-x/EztcTKVj+TDeANY1WjNeYsvZjZdfWRMP/KXi5Yn8BoTzpa13ZltaQqKfvWYbX8CE10GOHHdC5v86jY9x8i/g==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
"integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.26.10",
@@ -7935,6 +7961,12 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base16": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
"integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==",
"license": "MIT"
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@@ -9120,9 +9152,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.138",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.138.tgz",
"integrity": "sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==",
"version": "1.5.139",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.139.tgz",
"integrity": "sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -12502,6 +12534,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.curry": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
"integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -12716,6 +12754,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.501.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.501.0.tgz",
"integrity": "sha512-E2KoyhW59fCb/yUbR3rbDer83fqn7a8NG91ZhIot2yWaPHjPyGzzsNKh40N//GezYShAuycf3TcQksRQznIsRw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@@ -13885,9 +13932,9 @@
"license": "MIT"
},
"node_modules/msw": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.7.4.tgz",
"integrity": "sha512-A2kuMopOjAjNEYkn0AnB1uj+x7oBjLIunFk7Ud4icEnVWFf6iBekn8oXW4zIwcpfEdWP9sLqyVaHVzneWoGEww==",
"version": "2.7.5",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.7.5.tgz",
"integrity": "sha512-00MyTlY3TJutBa5kiU+jWiz2z5pNJDYHn2TgPkGkh92kMmNH43RqvMXd8y/7HxNn8RjzUbvZWYZjcS36fdb6sw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -15149,6 +15196,46 @@
"node": ">=0.10.0"
}
},
"node_modules/react-base16-styling": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
"integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.16.7",
"@types/base16": "^1.0.2",
"@types/lodash": "^4.14.178",
"base16": "^1.0.0",
"color": "^3.2.1",
"csstype": "^3.0.10",
"lodash.curry": "^4.1.1"
}
},
"node_modules/react-base16-styling/node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.3",
"color-string": "^1.6.0"
}
},
"node_modules/react-base16-styling/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/react-base16-styling/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
@@ -15225,6 +15312,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-markdown": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
@@ -17154,9 +17247,9 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
@@ -17981,9 +18074,9 @@
}
},
"node_modules/vite/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
+2
View File
@@ -8,6 +8,7 @@
},
"dependencies": {
"@heroui/react": "2.7.6",
"@microlink/react-json-view": "^1.26.1",
"@monaco-editor/react": "^4.7.0-rc.0",
"@react-router/node": "^7.5.1",
"@react-router/serve": "^7.5.1",
@@ -28,6 +29,7 @@
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.25",
"jose": "^6.0.10",
"lucide-react": "^0.501.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.236.2",
"react": "^19.1.0",
+1 -1
View File
@@ -8,7 +8,7 @@
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.7.3'
const PACKAGE_VERSION = '2.7.5'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
@@ -8,6 +8,7 @@ interface ConversationCardContextMenuProps {
onDelete?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onEdit?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDisplayCost?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDownloadViaVSCode?: (event: React.MouseEvent<HTMLButtonElement>) => void;
position?: "top" | "bottom";
}
@@ -17,6 +18,7 @@ export function ConversationCardContextMenu({
onDelete,
onEdit,
onDisplayCost,
onShowAgentTools,
onDownloadViaVSCode,
position = "bottom",
}: ConversationCardContextMenuProps) {
@@ -58,6 +60,14 @@ export function ConversationCardContextMenu({
Display Cost
</ContextMenuListItem>
)}
{onShowAgentTools && (
<ContextMenuListItem
testId="show-agent-tools-button"
onClick={onShowAgentTools}
>
Show Agent Tools & Metadata
</ContextMenuListItem>
)}
</ContextMenu>
);
}
@@ -10,10 +10,12 @@ import {
} from "./conversation-state-indicator";
import { EllipsisButton } from "./ellipsis-button";
import { ConversationCardContextMenu } from "./conversation-card-context-menu";
import { SystemMessageModal } from "./system-message-modal";
import { cn } from "#/utils/utils";
import { BaseModal } from "../../shared/modals/base-modal/base-modal";
import { RootState } from "#/store";
import { I18nKey } from "#/i18n/declaration";
import { selectSystemMessage } from "#/state/chat-slice";
interface ConversationCardProps {
onClick?: () => void;
@@ -52,10 +54,12 @@ export function ConversationCard({
const [contextMenuVisible, setContextMenuVisible] = React.useState(false);
const [titleMode, setTitleMode] = React.useState<"view" | "edit">("view");
const [metricsModalVisible, setMetricsModalVisible] = React.useState(false);
const [systemModalVisible, setSystemModalVisible] = React.useState(false);
const inputRef = React.useRef<HTMLInputElement>(null);
// Subscribe to metrics data from Redux store
const metrics = useSelector((state: RootState) => state.metrics);
const systemMessage = useSelector(selectSystemMessage);
const handleBlur = () => {
if (inputRef.current?.value) {
@@ -129,6 +133,11 @@ export function ConversationCard({
setMetricsModalVisible(true);
};
const handleShowAgentTools = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
setSystemModalVisible(true);
};
React.useEffect(() => {
if (titleMode === "edit") {
inputRef.current?.focus();
@@ -207,6 +216,11 @@ export function ConversationCard({
: undefined
}
onDisplayCost={showOptions ? handleDisplayCost : undefined}
onShowAgentTools={
showOptions && systemMessage
? handleShowAgentTools
: undefined
}
position={variant === "compact" ? "top" : "bottom"}
/>
)}
@@ -315,6 +329,12 @@ export function ConversationCard({
)}
</div>
</BaseModal>
<SystemMessageModal
isOpen={systemModalVisible}
onClose={() => setSystemModalVisible(false)}
systemMessage={systemMessage}
/>
</>
);
}
@@ -0,0 +1,238 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { ChevronDown, ChevronRight } from "lucide-react";
import ReactJsonView from "@microlink/react-json-view";
import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal";
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
import { ModalBody } from "#/components/shared/modals/modal-body";
import { cn } from "#/utils/utils";
// Custom JSON viewer theme that matches our application theme
const jsonViewTheme = {
base00: "transparent", // background
base01: "#2d2d2d", // lighter background
base02: "#4e4e4e", // selection background
base03: "#6c6c6c", // comments, invisibles
base04: "#969896", // dark foreground
base05: "#d9d9d9", // default foreground
base06: "#e8e8e8", // light foreground
base07: "#ffffff", // light background
base08: "#ff5370", // variables, red
base09: "#f78c6c", // integers, orange
base0A: "#ffcb6b", // booleans, yellow
base0B: "#c3e88d", // strings, green
base0C: "#89ddff", // support, cyan
base0D: "#82aaff", // functions, blue
base0E: "#c792ea", // keywords, purple
base0F: "#ff5370", // deprecated, red
};
interface SystemMessageModalProps {
isOpen: boolean;
onClose: () => void;
systemMessage: {
content: string;
tools: Array<Record<string, unknown>> | null;
openhands_version: string | null;
agent_class: string | null;
} | null;
}
interface FunctionData {
name?: string;
description?: string;
parameters?: Record<string, unknown>;
}
interface ToolData {
type?: string;
function?: FunctionData;
name?: string;
description?: string;
parameters?: Record<string, unknown>;
}
export function SystemMessageModal({
isOpen,
onClose,
systemMessage,
}: SystemMessageModalProps) {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<"system" | "tools">("system");
const [expandedTools, setExpandedTools] = useState<Record<number, boolean>>(
{},
);
if (!systemMessage) {
return null;
}
const toggleTool = (index: number) => {
setExpandedTools((prev) => ({
...prev,
[index]: !prev[index],
}));
};
return (
isOpen && (
<ModalBackdrop onClose={onClose}>
<ModalBody
width="medium"
className="max-h-[80vh] flex flex-col items-start"
>
<div className="flex flex-col gap-6 w-full">
<BaseModalTitle title={t("SYSTEM_MESSAGE_MODAL$TITLE")} />
<div className="flex flex-col gap-2">
{systemMessage.agent_class && (
<div className="text-sm">
<span className="font-semibold text-gray-300">
{t("SYSTEM_MESSAGE_MODAL$AGENT_CLASS")}
</span>{" "}
<span className="font-medium text-gray-100">
{systemMessage.agent_class}
</span>
</div>
)}
{systemMessage.openhands_version && (
<div className="text-sm">
<span className="font-semibold text-gray-300">
{t("SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION")}
</span>{" "}
<span className="text-gray-100">
{systemMessage.openhands_version}
</span>
</div>
)}
</div>
</div>
<div className="w-full">
<div className="flex border-b mb-2">
<button
type="button"
className={cn(
"px-4 py-2 font-medium border-b-2 transition-colors",
activeTab === "system"
? "border-primary text-gray-100"
: "border-transparent hover:text-gray-700 dark:hover:text-gray-300",
)}
onClick={() => setActiveTab("system")}
>
{t("SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB")}
</button>
{systemMessage.tools && systemMessage.tools.length > 0 && (
<button
type="button"
className={cn(
"px-4 py-2 font-medium border-b-2 transition-colors",
activeTab === "tools"
? "border-primary text-gray-100"
: "border-transparent hover:text-gray-700 dark:hover:text-gray-300",
)}
onClick={() => setActiveTab("tools")}
>
{t("SYSTEM_MESSAGE_MODAL$TOOLS_TAB")}
</button>
)}
</div>
<div className="h-[60vh] overflow-auto rounded-md">
{activeTab === "system" && (
<div className="p-4 whitespace-pre-wrap font-mono text-sm leading-relaxed text-gray-300 shadow-inner">
{systemMessage.content}
</div>
)}
{activeTab === "tools" &&
systemMessage.tools &&
systemMessage.tools.length > 0 && (
<div className="p-2 space-y-3">
{systemMessage.tools.map((tool, index) => {
// Extract function data from the nested structure
const toolData = tool as ToolData;
const functionData = toolData.function || toolData;
const name =
functionData.name ||
(toolData.type === "function" &&
toolData.function?.name) ||
"";
const description =
functionData.description ||
(toolData.type === "function" &&
toolData.function?.description) ||
"";
const parameters =
functionData.parameters ||
(toolData.type === "function" &&
toolData.function?.parameters) ||
null;
const isExpanded = expandedTools[index] || false;
return (
<div key={index} className="rounded-md overflow-hidden">
<button
type="button"
onClick={() => toggleTool(index)}
className="w-full py-3 px-2 text-left flex items-center justify-between hover:bg-gray-700 transition-colors"
>
<div className="flex items-center">
<h3 className="font-bold text-gray-100">
{String(name)}
</h3>
</div>
<span className="text-gray-300">
{isExpanded ? (
<ChevronDown size={18} />
) : (
<ChevronRight size={18} />
)}
</span>
</button>
{isExpanded && (
<div className="px-2 pb-3 pt-1">
<div className="mt-2 mb-3">
<p className="text-sm whitespace-pre-wrap text-gray-300 leading-relaxed">
{String(description)}
</p>
</div>
{/* Parameters section */}
{parameters && (
<div className="mt-2">
<h4 className="text-sm font-semibold text-gray-300">
{t("SYSTEM_MESSAGE_MODAL$PARAMETERS")}
</h4>
<div className="text-sm mt-2 p-3 bg-gray-900 rounded-md overflow-auto text-gray-300 max-h-[400px] shadow-inner">
<ReactJsonView
src={parameters}
theme={jsonViewTheme}
/>
</div>
</div>
)}
</div>
)}
</div>
);
})}
</div>
)}
{activeTab === "tools" &&
(!systemMessage.tools || systemMessage.tools.length === 0) && (
<div className="flex items-center justify-center h-full p-4">
<p className="text-gray-400">
{t("SYSTEM_MESSAGE_MODAL$NO_TOOLS")}
</p>
</div>
)}
</div>
</div>
</ModalBody>
</ModalBackdrop>
)
);
}
@@ -1,18 +1,28 @@
import React from "react";
import { cn } from "#/utils/utils";
type ModalWidth = "small" | "medium";
interface ModalBodyProps {
testID?: string;
children: React.ReactNode;
className?: React.HTMLProps<HTMLDivElement>["className"];
width?: ModalWidth;
}
export function ModalBody({ testID, children, className }: ModalBodyProps) {
export function ModalBody({
testID,
children,
className,
width = "small",
}: ModalBodyProps) {
return (
<div
data-testid={testID}
className={cn(
"bg-base-secondary flex flex-col gap-6 items-center w-[384px] p-6 rounded-xl",
"bg-base-secondary flex flex-col gap-6 items-center p-6 rounded-xl",
width === "small" && "w-[384px]",
width === "medium" && "w-[700px]",
className,
)}
>
+8
View File
@@ -326,6 +326,7 @@ export enum I18nKey {
ACTION_MESSAGE$BROWSE = "ACTION_MESSAGE$BROWSE",
ACTION_MESSAGE$BROWSE_INTERACTIVE = "ACTION_MESSAGE$BROWSE_INTERACTIVE",
ACTION_MESSAGE$THINK = "ACTION_MESSAGE$THINK",
ACTION_MESSAGE$SYSTEM = "ACTION_MESSAGE$SYSTEM",
OBSERVATION_MESSAGE$RUN = "OBSERVATION_MESSAGE$RUN",
OBSERVATION_MESSAGE$RUN_IPYTHON = "OBSERVATION_MESSAGE$RUN_IPYTHON",
OBSERVATION_MESSAGE$READ = "OBSERVATION_MESSAGE$READ",
@@ -416,4 +417,11 @@ export enum I18nKey {
DIFF_VIEWER$ASK_OH = "DIFF_VIEWER$ASK_OH",
DIFF_VIEWER$NO_CHANGES = "DIFF_VIEWER$NO_CHANGES",
DIFF_VIEWER$WAITING_FOR_RUNTIME = "DIFF_VIEWER$WAITING_FOR_RUNTIME",
SYSTEM_MESSAGE_MODAL$TITLE = "SYSTEM_MESSAGE_MODAL$TITLE",
SYSTEM_MESSAGE_MODAL$AGENT_CLASS = "SYSTEM_MESSAGE_MODAL$AGENT_CLASS",
SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION = "SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION",
SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB = "SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB",
SYSTEM_MESSAGE_MODAL$TOOLS_TAB = "SYSTEM_MESSAGE_MODAL$TOOLS_TAB",
SYSTEM_MESSAGE_MODAL$PARAMETERS = "SYSTEM_MESSAGE_MODAL$PARAMETERS",
SYSTEM_MESSAGE_MODAL$NO_TOOLS = "SYSTEM_MESSAGE_MODAL$NO_TOOLS",
}
+120
View File
@@ -4869,6 +4869,21 @@
"es": "Pensando",
"tr": "Düşünüyor"
},
"ACTION_MESSAGE$SYSTEM": {
"en": "System Message",
"zh-CN": "系统消息",
"zh-TW": "系統訊息",
"ko-KR": "시스템 메시지",
"ja": "システムメッセージ",
"no": "Systemmelding",
"ar": "رسالة النظام",
"de": "Systemnachricht",
"fr": "Message Système",
"it": "Messaggio di Sistema",
"pt": "Mensagem do Sistema",
"es": "Mensaje del Sistema",
"tr": "Sistem Mesajı"
},
"OBSERVATION_MESSAGE$RUN": {
"en": "Ran <cmd>{{observation.payload.extras.command}}</cmd>",
"zh-CN": "运行 <cmd>{{observation.payload.extras.command}}</cmd>",
@@ -6207,5 +6222,110 @@
"fr": "En attente du démarrage de l'exécution...",
"tr": "Çalışma zamanının başlamasını bekliyor...",
"de": "Warten auf den Start der Laufzeit..."
},
"SYSTEM_MESSAGE_MODAL$TITLE": {
"en": "Agent Tools & Metadata",
"zh-CN": "代理工具和元数据",
"zh-TW": "代理工具和元數據",
"ko-KR": "에이전트 도구 및 메타데이터",
"ja": "エージェントツールとメタデータ",
"no": "Agent-verktøy og metadata",
"ar": "أدوات الوكيل والبيانات الوصفية",
"de": "Agent-Tools und Metadaten",
"fr": "Outils d'agent et métadonnées",
"it": "Strumenti e metadati dell'agente",
"pt": "Ferramentas e metadados do agente",
"es": "Herramientas y metadatos del agente",
"tr": "Ajan Araçları ve Meta Verileri"
},
"SYSTEM_MESSAGE_MODAL$AGENT_CLASS": {
"en": "Agent Class:",
"zh-CN": "代理类别:",
"zh-TW": "代理類別:",
"ko-KR": "에이전트 클래스:",
"ja": "エージェントクラス:",
"no": "Agent-klasse:",
"ar": "فئة الوكيل:",
"de": "Agent-Klasse:",
"fr": "Classe d'agent :",
"it": "Classe agente:",
"pt": "Classe do agente:",
"es": "Clase de agente:",
"tr": "Ajan Sınıfı:"
},
"SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION": {
"en": "OpenHands Version:",
"zh-CN": "OpenHands 版本:",
"zh-TW": "OpenHands 版本:",
"ko-KR": "OpenHands 버전:",
"ja": "OpenHands バージョン:",
"no": "OpenHands-versjon:",
"ar": "إصدار OpenHands:",
"de": "OpenHands-Version:",
"fr": "Version OpenHands :",
"it": "Versione OpenHands:",
"pt": "Versão OpenHands:",
"es": "Versión de OpenHands:",
"tr": "OpenHands Sürümü:"
},
"SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB": {
"en": "System Message",
"zh-CN": "系统消息",
"zh-TW": "系統訊息",
"ko-KR": "시스템 메시지",
"ja": "システムメッセージ",
"no": "Systemmelding",
"ar": "رسالة النظام",
"de": "Systemnachricht",
"fr": "Message système",
"it": "Messaggio di sistema",
"pt": "Mensagem do sistema",
"es": "Mensaje del sistema",
"tr": "Sistem Mesajı"
},
"SYSTEM_MESSAGE_MODAL$TOOLS_TAB": {
"en": "Available Tools",
"zh-CN": "可用工具",
"zh-TW": "可用工具",
"ko-KR": "사용 가능한 도구",
"ja": "利用可能なツール",
"no": "Tilgjengelige verktøy",
"ar": "الأدوات المتاحة",
"de": "Verfügbare Tools",
"fr": "Outils disponibles",
"it": "Strumenti disponibili",
"pt": "Ferramentas disponíveis",
"es": "Herramientas disponibles",
"tr": "Kullanılabilir Araçlar"
},
"SYSTEM_MESSAGE_MODAL$PARAMETERS": {
"en": "Parameters:",
"zh-CN": "参数:",
"zh-TW": "參數:",
"ko-KR": "매개변수:",
"ja": "パラメータ:",
"no": "Parametere:",
"ar": "المعلمات:",
"de": "Parameter:",
"fr": "Paramètres :",
"it": "Parametri:",
"pt": "Parâmetros:",
"es": "Parámetros:",
"tr": "Parametreler:"
},
"SYSTEM_MESSAGE_MODAL$NO_TOOLS": {
"en": "No tools available for this agent",
"zh-CN": "此代理没有可用的工具",
"zh-TW": "此代理沒有可用的工具",
"ko-KR": "이 에이전트에 사용 가능한 도구가 없습니다",
"ja": "このエージェントで利用可能なツールはありません",
"no": "Ingen verktøy tilgjengelig for denne agenten",
"ar": "لا توجد أدوات متاحة لهذا الوكيل",
"de": "Keine Tools für diesen Agenten verfügbar",
"fr": "Aucun outil disponible pour cet agent",
"it": "Nessuno strumento disponibile per questo agente",
"pt": "Nenhuma ferramenta disponível para este agente",
"es": "No hay herramientas disponibles para este agente",
"tr": "Bu ajan için kullanılabilir araç yok"
}
}
+29 -1
View File
@@ -11,7 +11,15 @@ import {
RecallObservation,
} from "#/types/core/observations";
type SliceState = { messages: Message[] };
type SliceState = {
messages: Message[];
systemMessage: {
content: string;
tools: Array<Record<string, unknown>> | null;
openhands_version: string | null;
agent_class: string | null;
} | null;
};
const MAX_CONTENT_LENGTH = 1000;
@@ -25,6 +33,7 @@ const HANDLED_ACTIONS: OpenHandsEventType[] = [
"edit",
"recall",
"think",
"system",
];
function getRiskText(risk: ActionSecurityRisk) {
@@ -43,6 +52,7 @@ function getRiskText(risk: ActionSecurityRisk) {
const initialState: SliceState = {
messages: [],
systemMessage: null,
};
export const chatSlice = createSlice({
@@ -100,6 +110,18 @@ export const chatSlice = createSlice({
}
const translationID = `ACTION_MESSAGE$${actionID.toUpperCase()}`;
let text = "";
if (actionID === "system") {
// Store the system message in the state
state.systemMessage = {
content: action.payload.args.content,
tools: action.payload.args.tools,
openhands_version: action.payload.args.openhands_version,
agent_class: action.payload.args.agent_class,
};
// Don't add a message for system actions
return;
}
if (actionID === "run") {
text = `Command:\n\`${action.payload.args.command}\``;
} else if (actionID === "run_ipython") {
@@ -295,6 +317,7 @@ export const chatSlice = createSlice({
clearMessages(state: SliceState) {
state.messages = [];
state.systemMessage = null;
},
},
});
@@ -307,4 +330,9 @@ export const {
addErrorMessage,
clearMessages,
} = chatSlice.actions;
// Selectors
export const selectSystemMessage = (state: { chat: SliceState }) =>
state.chat.systemMessage;
export default chatSlice.reducer;
+3
View File
@@ -5,6 +5,9 @@ enum ActionType {
// Represents a message from the user or agent.
MESSAGE = "message",
// Represents a system message for an agent, including the system prompt and available tools.
SYSTEM = "system",
// Reads the contents of a file.
READ = "read",
+11
View File
@@ -9,6 +9,16 @@ export interface UserMessageAction extends OpenHandsActionEvent<"message"> {
};
}
export interface SystemMessageAction extends OpenHandsActionEvent<"system"> {
source: "agent";
args: {
content: string;
tools: Array<Record<string, unknown>> | null;
openhands_version: string | null;
agent_class: string | null;
};
}
export interface CommandAction extends OpenHandsActionEvent<"run"> {
source: "agent";
args: {
@@ -145,6 +155,7 @@ export interface RecallAction extends OpenHandsActionEvent<"recall"> {
export type OpenHandsAction =
| UserMessageAction
| AssistantMessageAction
| SystemMessageAction
| CommandAction
| IPythonAction
| ThinkAction
+1
View File
@@ -1,5 +1,6 @@
export type OpenHandsEventType =
| "message"
| "system"
| "agent_state_changed"
| "run"
| "read"
+2
View File
@@ -19,8 +19,10 @@ interface GitRepository {
id: number;
full_name: string;
git_provider: Provider;
is_public: boolean;
stargazers_count?: number;
link_header?: string;
pushed_at?: string;
}
interface GitHubCommit {
@@ -1,7 +1,20 @@
import copy
import os
from collections import deque
from litellm import ChatCompletionToolParam
import openhands.agenthub.codeact_agent.function_calling as codeact_function_calling
from openhands.agenthub.codeact_agent.tools.bash import create_cmd_run_tool
from openhands.agenthub.codeact_agent.tools.browser import BrowserTool
from openhands.agenthub.codeact_agent.tools.finish import FinishTool
from openhands.agenthub.codeact_agent.tools.ipython import IPythonTool
from openhands.agenthub.codeact_agent.tools.llm_based_edit import LLMBasedFileEditTool
from openhands.agenthub.codeact_agent.tools.str_replace_editor import (
create_str_replace_editor_tool,
)
from openhands.agenthub.codeact_agent.tools.think import ThinkTool
from openhands.agenthub.codeact_agent.tools.web_read import WebReadTool
from openhands.controller.agent import Agent
from openhands.controller.state.state import State
from openhands.core.config import AgentConfig
@@ -67,15 +80,7 @@ class CodeActAgent(Agent):
super().__init__(llm, config)
self.pending_actions: deque[Action] = deque()
self.reset()
built_in_tools = codeact_function_calling.get_tools(
enable_browsing=self.config.enable_browsing,
enable_jupyter=self.config.enable_jupyter,
enable_llm_editor=self.config.enable_llm_editor,
llm=self.llm,
)
self.tools = built_in_tools
self.tools = self._get_tools()
self.prompt_manager = PromptManager(
prompt_dir=os.path.join(os.path.dirname(__file__), 'prompts'),
@@ -89,6 +94,40 @@ class CodeActAgent(Agent):
self.response_to_actions_fn = codeact_function_calling.response_to_actions
def _get_tools(self) -> list[ChatCompletionToolParam]:
SIMPLIFIED_TOOL_DESCRIPTION_LLM_SUBSTRS = ['gpt-', 'o3', 'o1']
use_simplified_tool_desc = False
if self.llm is not None:
use_simplified_tool_desc = any(
model_substr in self.llm.config.model
for model_substr in SIMPLIFIED_TOOL_DESCRIPTION_LLM_SUBSTRS
)
tools = []
if self.config.enable_cmd:
tools.append(
create_cmd_run_tool(use_simplified_description=use_simplified_tool_desc)
)
if self.config.enable_think:
tools.append(ThinkTool)
if self.config.enable_finish:
tools.append(FinishTool)
if self.config.enable_browsing:
tools.append(WebReadTool)
tools.append(BrowserTool)
if self.config.enable_jupyter:
tools.append(IPythonTool)
if self.config.enable_llm_editor:
tools.append(LLMBasedFileEditTool)
elif self.config.enable_editor:
tools.append(
create_str_replace_editor_tool(
use_simplified_description=use_simplified_tool_desc
)
)
return tools
def reset(self) -> None:
"""Resets the CodeAct Agent."""
super().reset()
@@ -148,8 +187,25 @@ class CodeActAgent(Agent):
for tool in self.mcp_tools
if tool['function']['name'] not in existing_names
]
params['tools'] += unique_mcp_tools
if self.llm.config.model == 'gemini-2.5-pro-preview-03-25':
logger.info(
f'Removing the default fields from the MCP tools for {self.llm.config.model} '
"since it doesn't support them and the request would crash."
)
# prevent mutation of input tools
unique_mcp_tools = copy.deepcopy(unique_mcp_tools)
# Strip off default fields that cause errors with gemini-preview
for tool in unique_mcp_tools:
if 'function' in tool and 'parameters' in tool['function']:
if 'properties' in tool['function']['parameters']:
for prop_name, prop in tool['function']['parameters'][
'properties'
].items():
if 'default' in prop:
del prop['default']
params['tools'] += unique_mcp_tools
# log to litellm proxy if possible
params['extra_body'] = {'metadata': state.to_llm_metadata(agent_name=self.name)}
response = self.llm.completion(**params)
@@ -6,7 +6,6 @@ This is similar to the functionality of `CodeActResponseParser`.
import json
from litellm import (
ChatCompletionToolParam,
ModelResponse,
)
@@ -41,7 +40,6 @@ from openhands.events.action import (
from openhands.events.action.mcp import McpAction
from openhands.events.event import FileEditSource, FileReadSource
from openhands.events.tool import ToolCallMetadata
from openhands.llm import LLM
from openhands.mcp import MCPClientTool
@@ -237,39 +235,3 @@ def response_to_actions(response: ModelResponse) -> list[Action]:
assert len(actions) >= 1
return actions
def get_tools(
enable_browsing: bool = False,
enable_llm_editor: bool = False,
enable_jupyter: bool = False,
llm: LLM | None = None,
) -> list[ChatCompletionToolParam]:
SIMPLIFIED_TOOL_DESCRIPTION_LLM_SUBSTRS = ['gpt-', 'o3', 'o1']
use_simplified_tool_desc = False
if llm is not None:
use_simplified_tool_desc = any(
model_substr in llm.config.model
for model_substr in SIMPLIFIED_TOOL_DESCRIPTION_LLM_SUBSTRS
)
tools = [
create_cmd_run_tool(use_simplified_description=use_simplified_tool_desc),
ThinkTool,
FinishTool,
]
if enable_browsing:
tools.append(WebReadTool)
tools.append(BrowserTool)
if enable_jupyter:
tools.append(IPythonTool)
if enable_llm_editor:
tools.append(LLMBasedFileEditTool)
else:
tools.append(
create_str_replace_editor_tool(
use_simplified_description=use_simplified_tool_desc
)
)
return tools
+1 -1
View File
@@ -68,7 +68,7 @@ class Agent(ABC):
tools = getattr(self, 'tools', None)
system_message_action = SystemMessageAction(
content=system_message, tools=tools
content=system_message, tools=tools, agent_class=self.name
)
# Set the source attribute
system_message_action._source = EventSource.AGENT # type: ignore
+1 -4
View File
@@ -293,9 +293,6 @@ class AgentController:
# Set the agent state to ERROR after storing the reason
await self.set_agent_state_to(AgentState.ERROR)
def step(self):
asyncio.create_task(self._step_with_exception_handling())
async def _step_with_exception_handling(self):
try:
await self._step()
@@ -416,7 +413,7 @@ class AgentController:
f'Stepping agent after event: {type(event).__name__}',
extra={'msg_type': 'STEPPING_AGENT'},
)
self.step()
await self._step_with_exception_handling()
elif isinstance(event, MessageAction) and event.source == EventSource.USER:
# If we received a user message but aren't stepping, log why
self.log(
+16 -14
View File
@@ -7,28 +7,30 @@ from openhands.core.logger import openhands_logger as logger
class AgentConfig(BaseModel):
"""Configuration for the agent.
Attributes:
enable_browsing: Whether browsing delegate is enabled in the action space. Default is False. Only works with function calling.
enable_llm_editor: Whether LLM editor is enabled in the action space. Default is False. Only works with function calling.
enable_jupyter: Whether Jupyter is enabled in the action space. Default is False.
llm_config: The name of the llm config to use. If specified, this will override global llm config.
enable_prompt_extensions: Whether to use prompt extensions (e.g., microagents, inject runtime info). Default is True.
disabled_microagents: A list of microagents to disable (by name, without .py extension, e.g. ["github", "lint"]). Default is None.
condenser: Configuration for the memory condenser. Default is NoOpCondenserConfig.
enable_history_truncation: Whether history should be truncated to continue the session when hitting LLM context length limit.
enable_som_visual_browsing: Whether to enable SoM (Set of Marks) visual browsing. Default is False.
"""
llm_config: str | None = Field(default=None)
"""The name of the llm config to use. If specified, this will override global llm config."""
enable_browsing: bool = Field(default=True)
"""Whether to enable browsing tool"""
enable_llm_editor: bool = Field(default=False)
"""Whether to enable LLM editor tool"""
enable_editor: bool = Field(default=True)
"""Whether to enable the standard editor tool (str_replace_editor), only has an effect if enable_llm_editor is False."""
enable_jupyter: bool = Field(default=True)
"""Whether to enable Jupyter tool"""
enable_cmd: bool = Field(default=True)
"""Whether to enable bash tool"""
enable_think: bool = Field(default=True)
"""Whether to enable think tool"""
enable_finish: bool = Field(default=True)
"""Whether to enable finish tool"""
enable_prompt_extensions: bool = Field(default=True)
"""Whether to enable prompt extensions"""
disabled_microagents: list[str] = Field(default_factory=list)
"""A list of microagents to disable (by name, without .py extension, e.g. ["github", "lint"]). Default is None."""
enable_history_truncation: bool = Field(default=True)
"""Whether history should be truncated to continue the session when hitting LLM context length limit."""
enable_som_visual_browsing: bool = Field(default=True)
"""Whether to enable SoM (Set of Marks) visual browsing."""
condenser: CondenserConfig = Field(
default_factory=lambda: NoOpCondenserConfig(type='noop')
)
+8 -2
View File
@@ -1,6 +1,6 @@
import os
from pydantic import BaseModel, Field, ValidationError
from pydantic import BaseModel, Field, ValidationError, model_validator
class SandboxConfig(BaseModel):
@@ -46,7 +46,7 @@ class SandboxConfig(BaseModel):
pause_closed_runtimes: bool = Field(default=True)
rm_all_containers: bool = Field(default=False)
api_key: str | None = Field(default=None)
base_container_image: str = Field(
base_container_image: str | None = Field(
default='nikolaik/python-nodejs:python3.12-nodejs22'
)
runtime_container_image: str | None = Field(default=None)
@@ -98,3 +98,9 @@ class SandboxConfig(BaseModel):
raise ValueError(f'Invalid sandbox configuration: {e}')
return sandbox_mapping
@model_validator(mode="after")
def set_default_base_image(self) -> "SandboxConfig":
if self.base_container_image is None:
self.base_container_image = 'nikolaik/python-nodejs:python3.12-nodejs22'
return self
+3
View File
@@ -46,6 +46,7 @@ class SystemMessageAction(Action):
content: str
tools: list[Any] | None = None
openhands_version: str | None = openhands.__version__
agent_class: str | None = None
action: ActionType = ActionType.SYSTEM
@property
@@ -57,4 +58,6 @@ class SystemMessageAction(Action):
ret += f'CONTENT: {self.content}'
if self.tools:
ret += f'\nTOOLS: {len(self.tools)} tools available'
if self.agent_class:
ret += f'\nAGENT_CLASS: {self.agent_class}'
return ret
-5
View File
@@ -139,8 +139,6 @@ class EventStream(EventStore):
f'Callback ID on subscriber {subscriber_id} already exists: {callback_id}'
)
logger.info(f'subscribing {subscriber_id} {callback_id}')
self._subscribers[subscriber_id][callback_id] = callback
self._thread_pools[subscriber_id][callback_id] = pool
@@ -155,8 +153,6 @@ class EventStream(EventStore):
logger.warning(f'Callback not found during unsubscribe: {callback_id}')
return
logger.info(f'unsubscribing {subscriber_id} {callback_id}')
self._clean_up_subscriber(subscriber_id, callback_id)
def add_event(self, event: Event, source: EventSource) -> None:
@@ -236,7 +232,6 @@ class EventStream(EventStore):
# pass each event to each callback in order
for key in sorted(self._subscribers.keys()):
callbacks = self._subscribers[key]
logger.info(f'Process callbacks {callbacks}')
for callback_id in callbacks:
callback = callbacks[callback_id]
pool = self._thread_pools[key][callback_id]
@@ -179,7 +179,7 @@ class GitHubService(GitService):
full_name=repo.get('full_name'),
stargazers_count=repo.get('stargazers_count'),
git_provider=ProviderType.GITHUB,
is_public=not repo.get('private', True)
is_public=not repo.get('private', True),
)
for repo in all_repos
]
@@ -16,5 +16,6 @@ A comment on the issue has been addressed to you.
When you're done, make sure to
1. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
2. The PR description should mention that it "fixes" or "closes" the issue number
3. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
2. Name the branch using the format: `openhands/{branch-name}`
3. The PR description should mention that it "fixes" or "closes" the issue number
4. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
+23 -26
View File
@@ -4,6 +4,7 @@ from typing import Any
import httpx
from pydantic import SecretStr
from openhands.core.logger import openhands_logger as logger
from openhands.integrations.service_types import (
AuthenticationError,
GitService,
@@ -14,7 +15,7 @@ from openhands.integrations.service_types import (
)
from openhands.server.types import AppMode
from openhands.utils.import_utils import get_impl
from openhands.core.logger import openhands_logger as logger
class GitLabService(GitService):
BASE_URL = 'https://gitlab.com/api/v4'
@@ -77,24 +78,22 @@ class GitLabService(GitService):
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
raise AuthenticationError('Invalid GitLab token')
logger.warning(f'Status error on GL API: {e}')
raise UnknownException('Unknown error')
except httpx.HTTPError as e:
logger.warning(f'HTTP error on GL API: {e}')
raise UnknownException('Unknown error')
async def execute_graphql_query(
self, query: str, variables: dict[str, Any]
) -> Any:
async def execute_graphql_query(self, query: str, variables: dict[str, Any]) -> Any:
"""
Execute a GraphQL query against the GitLab GraphQL API
Args:
query: The GraphQL query string
variables: Optional variables for the GraphQL query
Returns:
The data portion of the GraphQL response
"""
@@ -103,37 +102,35 @@ class GitLabService(GitService):
gitlab_headers = await self._get_gitlab_headers()
# Add content type header for GraphQL
gitlab_headers['Content-Type'] = 'application/json'
payload = {
"query": query,
"variables": variables,
'query': query,
'variables': variables,
}
response = await client.post(
self.GRAPHQL_URL,
headers=gitlab_headers,
json=payload
self.GRAPHQL_URL, headers=gitlab_headers, json=payload
)
if self.refresh and self._has_token_expired(response.status_code):
await self.get_latest_token()
gitlab_headers = await self._get_gitlab_headers()
gitlab_headers['Content-Type'] = 'application/json'
response = await client.post(
self.GRAPHQL_URL,
headers=gitlab_headers,
json=payload
self.GRAPHQL_URL, headers=gitlab_headers, json=payload
)
response.raise_for_status()
result = response.json()
# Check for GraphQL errors
if "errors" in result:
error_message = result["errors"][0].get("message", "Unknown GraphQL error")
raise UnknownException(f"GraphQL error: {error_message}")
return result.get("data")
if 'errors' in result:
error_message = result['errors'][0].get(
'message', 'Unknown GraphQL error'
)
raise UnknownException(f'GraphQL error: {error_message}')
return result.get('data')
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
raise AuthenticationError('Invalid GitLab token')
@@ -228,7 +225,7 @@ class GitLabService(GitService):
full_name=repo.get('path_with_namespace'),
stargazers_count=repo.get('star_count'),
git_provider=ProviderType.GITLAB,
is_public = repo.get('visibility') == 'public'
is_public=repo.get('visibility') == 'public',
)
for repo in all_repos
]
-12
View File
@@ -138,18 +138,6 @@ If you've installed the package from source using poetry, you can use:
poetry run python openhands/resolver/resolve_issue.py --selected-repo all-hands-ai/openhands --issue-number 100
```
For resolving multiple issues at once (e.g., in a batch process), you can use the `resolve_all_issues` command:
```bash
python -m openhands.resolver.resolve_all_issues --selected-repo [OWNER]/[REPO] --issue-numbers [NUMBERS]
```
For example:
```bash
python -m openhands.resolver.resolve_all_issues --selected-repo all-hands-ai/openhands --issue-numbers 100,101,102
```
## Responding to PR Comments
The resolver can also respond to comments on pull requests using:
+1 -1
View File
@@ -75,7 +75,7 @@ class GithubIssueHandler(IssueHandlerInterface):
if self.base_domain == 'github.com':
return 'https://api.github.com/graphql'
else:
return f'https://{self.base_domain}/api/v3/graphql'
return f'https://{self.base_domain}/api/graphql'
def get_compare_url(self, branch_name: str) -> str:
return f'https://{self.base_domain}/{self.owner}/{self.repo}/compare/{branch_name}?expand=1'
-308
View File
@@ -1,308 +0,0 @@
# flake8: noqa: E501
import argparse
import asyncio
import multiprocessing as mp
import os
import pathlib
import subprocess
from argparse import Namespace
from typing import Any, Awaitable, TextIO
from tqdm import tqdm
from openhands.core.logger import openhands_logger as logger
from openhands.resolver.interfaces.issue import Issue
from openhands.resolver.resolve_issue import IssueResolver
from openhands.resolver.resolver_output import ResolverOutput
class AllIssueResolver(IssueResolver):
def __init__(self, my_args: Namespace) -> None:
"""Initialize the AllIssueResolver with the given parameters."""
self.my_args = my_args
super().__init__(my_args)
issue_numbers = None
if my_args.issue_numbers:
issue_numbers = [int(number) for number in my_args.issue_numbers.split(',')]
self.issue_numbers = issue_numbers
self.num_workers = my_args.num_workers
self.limit_issues = my_args.limit_issues
def cleanup(self) -> None:
logger.info('Cleaning up child processes...')
for process in mp.active_children():
logger.info(f'Terminating child process: {process.name}')
process.terminate()
process.join()
# This function tracks the progress AND write the output to a JSONL file
async def update_progress(
self, output: Awaitable[ResolverOutput], output_fp: TextIO, pbar: tqdm
) -> None:
resolved_output = await output
pbar.update(1)
pbar.set_description(f'issue {resolved_output.issue.number}')
pbar.set_postfix_str(
f'Test Result: {resolved_output.metrics.get("test_result", "N/A") if resolved_output.metrics else "N/A"}'
)
logger.info(
f'Finished issue {resolved_output.issue.number}: {resolved_output.metrics.get("test_result", "N/A") if resolved_output.metrics else "N/A"}'
)
output_fp.write(resolved_output.model_dump_json() + '\n')
output_fp.flush()
async def resolve_issues(self) -> None:
"""Resolve multiple github or gitlab issues using the instance variables."""
issue_handler = self.issue_handler_factory()
# Load dataset
issues: list[Issue] = issue_handler.get_converted_issues(
issue_numbers=self.issue_numbers
)
if self.limit_issues is not None:
issues = issues[: self.limit_issues]
logger.info(f'Limiting resolving to first {self.limit_issues} issues.')
# TEST METADATA
model_name = self.llm_config.model.split('/')[-1]
pathlib.Path(self.output_dir).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self.output_dir, 'infer_logs')).mkdir(
parents=True, exist_ok=True
)
logger.info(f'Using output directory: {self.output_dir}')
# checkout the repo
repo_dir = os.path.join(self.output_dir, 'repo')
if not os.path.exists(repo_dir):
checkout_output = subprocess.check_output( # noqa: ASYNC101
[
'git',
'clone',
issue_handler.get_clone_url(),
f'{self.output_dir}/repo',
]
).decode('utf-8')
if 'fatal' in checkout_output:
raise RuntimeError(f'Failed to clone repository: {checkout_output}')
# get the commit id of current repo for reproducibility
base_commit = (
subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=repo_dir) # noqa: ASYNC101
.decode('utf-8')
.strip()
)
logger.info(f'Base commit: {base_commit}')
if self.repo_instruction is None:
# Check for .openhands_instructions file in the workspace directory
openhands_instructions_path = os.path.join(
repo_dir, '.openhands_instructions'
)
if os.path.exists(openhands_instructions_path):
with open(openhands_instructions_path, 'r') as f: # noqa: ASYNC101
self.repo_instruction = f.read()
# OUTPUT FILE
output_file = os.path.join(self.output_dir, 'output.jsonl')
logger.info(f'Writing output to {output_file}')
finished_numbers = set()
if os.path.exists(output_file):
with open(output_file, 'r') as f: # noqa: ASYNC101
for line in f:
data = ResolverOutput.model_validate_json(line)
finished_numbers.add(data.issue.number)
logger.warning(
f'Output file {output_file} already exists. Loaded {len(finished_numbers)} finished issues.'
)
output_fp = open(output_file, 'a') # noqa: ASYNC101
logger.info(
f'Resolving issues with model {model_name}, max iterations {self.max_iterations}.'
)
# =============================================
# filter out finished issues
new_issues = []
for issue in issues:
if issue.number in finished_numbers:
logger.info(f'Skipping issue {issue.number} as it is already finished.')
continue
new_issues.append(issue)
logger.info(
f'Finished issues: {len(finished_numbers)}, Remaining issues: {len(issues)}'
)
# =============================================
pbar = tqdm(total=len(issues))
# This sets the multi-processing
logger.info(f'Using {self.num_workers} workers.')
try:
tasks = []
for issue in issues:
# checkout to pr branch
if self.issue_type == 'pr':
logger.info(
f'Checking out to PR branch {issue.head_branch} for issue {issue.number}'
)
subprocess.check_output( # noqa: ASYNC101
['git', 'checkout', f'{issue.head_branch}'],
cwd=repo_dir,
)
base_commit = (
subprocess.check_output( # noqa: ASYNC101
['git', 'rev-parse', 'HEAD'], cwd=repo_dir
)
.decode('utf-8')
.strip()
)
issue_resolver = IssueResolver(self.my_args)
task = self.update_progress(
issue_resolver.process_issue(
issue,
base_commit,
issue_handler,
bool(self.num_workers > 1),
),
output_fp,
pbar,
)
tasks.append(task)
# Use asyncio.gather with a semaphore to limit concurrency
sem = asyncio.Semaphore(self.num_workers)
async def run_with_semaphore(task: Awaitable[Any]) -> Any:
async with sem:
return await task
await asyncio.gather(*[run_with_semaphore(task) for task in tasks])
except KeyboardInterrupt:
logger.info('KeyboardInterrupt received. Cleaning up...')
self.cleanup()
output_fp.close()
logger.info('Finished.')
def main() -> None:
parser = argparse.ArgumentParser(
description='Resolve multiple issues from Github or Gitlab.'
)
parser.add_argument(
'--selected-repo',
type=str,
required=True,
help='Github or Gitlab repository to resolve issues in form of `owner/repo`.',
)
parser.add_argument(
'--token',
type=str,
default=None,
help='Github or Gitlab token to access the repository.',
)
parser.add_argument(
'--username',
type=str,
default=None,
help='Github or Gitlab username to access the repository.',
)
parser.add_argument(
'--runtime-container-image',
type=str,
default=None,
help='Container image to use.',
)
parser.add_argument(
'--max-iterations',
type=int,
default=50,
help='Maximum number of iterations to run.',
)
parser.add_argument(
'--limit-issues',
type=int,
default=None,
help='Limit the number of issues to resolve.',
)
parser.add_argument(
'--issue-numbers',
type=str,
default=None,
help='Comma separated list of issue numbers to resolve.',
)
parser.add_argument(
'--num-workers',
type=int,
default=1,
help='Number of workers to use for parallel processing.',
)
parser.add_argument(
'--output-dir',
type=str,
default='output',
help='Output directory to write the results.',
)
parser.add_argument(
'--llm-model',
type=str,
default=None,
help='LLM model to use.',
)
parser.add_argument(
'--llm-api-key',
type=str,
default=None,
help='LLM API key to use.',
)
parser.add_argument(
'--llm-base-url',
type=str,
default=None,
help='LLM base URL to use.',
)
parser.add_argument(
'--prompt-file',
type=str,
default=None,
help='Path to the prompt template file in Jinja format.',
)
parser.add_argument(
'--repo-instruction-file',
type=str,
default=None,
help='Path to the repository instruction file in text format.',
)
parser.add_argument(
'--issue-type',
type=str,
default='issue',
choices=['issue', 'pr'],
help='Type of issue to resolve, either open issue or pr comments.',
)
parser.add_argument(
'--base-domain',
type=str,
default='github.com',
help='Base domain for GitHub Enterprise (default: github.com)',
)
my_args = parser.parse_args()
all_issue_resolver = AllIssueResolver(my_args)
asyncio.run(all_issue_resolver.resolve_issues())
if __name__ == '__main__':
main()
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -9,6 +9,7 @@ from pydantic import SecretStr
from openhands.core.config import LLMConfig
from openhands.core.logger import openhands_logger as logger
from openhands.integrations.service_types import ProviderType
from openhands.llm.llm import LLM
from openhands.resolver.interfaces.github import GithubIssueHandler
from openhands.resolver.interfaces.gitlab import GitlabIssueHandler
@@ -20,10 +21,7 @@ from openhands.resolver.io_utils import (
)
from openhands.resolver.patching import apply_diff, parse_patch
from openhands.resolver.resolver_output import ResolverOutput
from openhands.resolver.utils import (
Platform,
identify_token,
)
from openhands.resolver.utils import identify_token
def apply_patch(repo_dir: str, patch: str) -> None:
@@ -227,7 +225,7 @@ def send_pull_request(
issue: Issue,
token: str,
username: str | None,
platform: Platform,
platform: ProviderType,
patch_dir: str,
pr_type: str,
fork_owner: str | None = None,
@@ -258,10 +256,10 @@ def send_pull_request(
# Determine default base_domain based on platform
if base_domain is None:
base_domain = 'github.com' if platform == Platform.GITHUB else 'gitlab.com'
base_domain = 'github.com' if platform == ProviderType.GITHUB else 'gitlab.com'
handler = None
if platform == Platform.GITHUB:
if platform == ProviderType.GITHUB:
handler = ServiceContextIssue(
GithubIssueHandler(issue.owner, issue.repo, token, username, base_domain),
None,
@@ -329,7 +327,7 @@ def send_pull_request(
# For cross repo pull request, we need to send head parameter like fork_owner:branch as per git documentation here : https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request
# head parameter usage : The name of the branch where your changes are implemented. For cross-repository pull requests in the same network, namespace head with a user like this: username:branch.
if fork_owner and platform == Platform.GITHUB:
if fork_owner and platform == ProviderType.GITHUB:
head_branch = f'{fork_owner}:{branch_name}'
else:
head_branch = branch_name
@@ -341,9 +339,13 @@ def send_pull_request(
# Prepare the PR for the GitHub API
data = {
'title': final_pr_title,
('body' if platform == Platform.GITHUB else 'description'): pr_body,
('head' if platform == Platform.GITHUB else 'source_branch'): head_branch,
('base' if platform == Platform.GITHUB else 'target_branch'): base_branch,
('body' if platform == ProviderType.GITHUB else 'description'): pr_body,
(
'head' if platform == ProviderType.GITHUB else 'source_branch'
): head_branch,
(
'base' if platform == ProviderType.GITHUB else 'target_branch'
): base_branch,
'draft': pr_type == 'draft',
}
@@ -366,7 +368,7 @@ def update_existing_pull_request(
issue: Issue,
token: str,
username: str | None,
platform: Platform,
platform: ProviderType,
patch_dir: str,
llm_config: LLMConfig,
comment_message: str | None = None,
@@ -390,10 +392,10 @@ def update_existing_pull_request(
# Determine default base_domain based on platform
if base_domain is None:
base_domain = 'github.com' if platform == Platform.GITHUB else 'gitlab.com'
base_domain = 'github.com' if platform == ProviderType.GITHUB else 'gitlab.com'
handler = None
if platform == Platform.GITHUB:
if platform == ProviderType.GITHUB:
handler = ServiceContextIssue(
GithubIssueHandler(issue.owner, issue.repo, token, username, base_domain),
llm_config,
@@ -476,7 +478,7 @@ def process_single_issue(
resolver_output: ResolverOutput,
token: str,
username: str,
platform: Platform,
platform: ProviderType,
pr_type: str,
llm_config: LLMConfig,
fork_owner: str | None,
@@ -488,7 +490,7 @@ def process_single_issue(
) -> None:
# Determine default base_domain based on platform
if base_domain is None:
base_domain = 'github.com' if platform == Platform.GITHUB else 'gitlab.com'
base_domain = 'github.com' if platform == ProviderType.GITHUB else 'gitlab.com'
if not resolver_output.success and not send_on_failure:
logger.info(
f'Issue {resolver_output.issue.number} was not successfully resolved. Skipping PR creation.'
@@ -550,7 +552,7 @@ def process_all_successful_issues(
output_dir: str,
token: str,
username: str,
platform: Platform,
platform: ProviderType,
pr_type: str,
llm_config: LLMConfig,
fork_owner: str | None,
@@ -558,7 +560,7 @@ def process_all_successful_issues(
) -> None:
# Determine default base_domain based on platform
if base_domain is None:
base_domain = 'github.com' if platform == Platform.GITHUB else 'gitlab.com'
base_domain = 'github.com' if platform == ProviderType.GITHUB else 'gitlab.com'
output_path = os.path.join(output_dir, 'output.jsonl')
for resolver_output in load_all_resolver_outputs(output_path):
if resolver_output.success:
@@ -684,8 +686,6 @@ def main() -> None:
username = my_args.username if my_args.username else os.getenv('GIT_USERNAME')
platform = identify_token(token, my_args.selected_repo, my_args.base_domain)
if platform == Platform.INVALID:
raise ValueError('Token is invalid.')
api_key = my_args.llm_api_key or os.environ['LLM_API_KEY']
llm_config = LLMConfig(
+8 -13
View File
@@ -2,7 +2,6 @@ import logging
import multiprocessing as mp
import os
import re
from enum import Enum
from typing import Callable
import httpx
@@ -12,17 +11,12 @@ from openhands.core.logger import get_console_handler
from openhands.core.logger import openhands_logger as logger
from openhands.events.action import Action
from openhands.events.action.message import MessageAction
class Platform(Enum):
INVALID = 0
GITHUB = 1
GITLAB = 2
from openhands.integrations.service_types import ProviderType
def identify_token(
token: str, selected_repo: str | None = None, base_domain: str | None = 'github.com'
) -> Platform:
) -> ProviderType:
"""
Identifies whether a token belongs to GitHub or GitLab.
@@ -32,7 +26,7 @@ def identify_token(
base_domain (str): The base domain for GitHub Enterprise (default: "github.com").
Returns:
Platform: "GitHub" if the token is valid for GitHub,
ProviderType: "GitHub" if the token is valid for GitHub,
"GitLab" if the token is valid for GitLab,
"Invalid" if the token is not recognized by either.
"""
@@ -55,7 +49,7 @@ def identify_token(
github_repo_url, headers=github_bearer_headers, timeout=5
)
if github_repo_response.status_code == 200:
return Platform.GITHUB
return ProviderType.GITHUB
except httpx.HTTPError as e:
logger.error(f'Error connecting to GitHub API (selected_repo check): {e}')
@@ -66,7 +60,7 @@ def identify_token(
try:
github_response = httpx.get(github_url, headers=github_headers, timeout=5)
if github_response.status_code == 200:
return Platform.GITHUB
return ProviderType.GITHUB
except httpx.HTTPError as e:
logger.error(f'Error connecting to GitHub API: {e}')
@@ -76,10 +70,11 @@ def identify_token(
try:
gitlab_response = httpx.get(gitlab_url, headers=gitlab_headers, timeout=5)
if gitlab_response.status_code == 200:
return Platform.GITLAB
return ProviderType.GITLAB
except httpx.HTTPError as e:
logger.error(f'Error connecting to GitLab API: {e}')
return Platform.INVALID
raise ValueError('Token is invalid.')
def codeact_user_response(
+1 -1
View File
@@ -610,7 +610,7 @@ class Runtime(FileEditRuntimeMixin):
return CommandResult(content=content, exit_code=exit_code)
def get_git_changes(self, cwd: str) -> list[dict[str, str]]:
def get_git_changes(self, cwd: str) -> list[dict[str, str]] | None:
self.git_handler.set_cwd(cwd)
return self.git_handler.get_git_changes()
+8
View File
@@ -159,6 +159,14 @@ class DockerRuntimeBuilder(RuntimeBuilder):
f'================ {buildx_cmd[0].upper()} BUILD STARTED ================'
)
builder_cmd = ['docker', 'buildx', 'use', 'default']
subprocess.Popen(
builder_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
)
try:
process = subprocess.Popen(
buildx_cmd,
+15 -18
View File
@@ -210,25 +210,22 @@ class LocalRuntime(ActionExecutionClient):
# Extract the poetry venv by parsing output of a shell command
# Equivalent to:
# run poetry show -v | head -n 1 | awk '{print $2}'
poetry_show_first_line = (
subprocess.check_output(
['poetry', 'show', '-v'],
env=env,
cwd=code_repo_path,
text=True,
# Redirect stderr to stdout
# Needed since there might be a message on stderr like
# "Skipping virtualenv creation, as specified in config file."
# which will cause the command to fail
stderr=subprocess.STDOUT,
shell=False,
)
.splitlines()[0]
)
poetry_show_first_line = subprocess.check_output( # noqa: ASYNC101
['poetry', 'show', '-v'],
env=env,
cwd=code_repo_path,
text=True,
# Redirect stderr to stdout
# Needed since there might be a message on stderr like
# "Skipping virtualenv creation, as specified in config file."
# which will cause the command to fail
stderr=subprocess.STDOUT,
shell=False,
).splitlines()[0]
if not poetry_show_first_line.lower().startswith('found:'):
raise RuntimeError(
"Cannot find poetry venv path. Please check your poetry installation."
f"First line of poetry show -v: {poetry_show_first_line}"
'Cannot find poetry venv path. Please check your poetry installation.'
f'First line of poetry show -v: {poetry_show_first_line}'
)
# Split off the 'Found:' part
poetry_venvs_path = poetry_show_first_line.split(':')[1].strip()
@@ -236,7 +233,7 @@ class LocalRuntime(ActionExecutionClient):
logger.debug(f'POETRY_VIRTUALENVS_PATH: {poetry_venvs_path}')
check_dependencies(code_repo_path, poetry_venvs_path)
self.server_process = subprocess.Popen(
self.server_process = subprocess.Popen( # noqa: ASYNC101
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
@@ -194,7 +194,10 @@ class RemoteRuntime(ActionExecutionClient):
'debug',
f'Runtime image repo: {os.environ["OH_RUNTIME_RUNTIME_IMAGE_REPO"]}',
)
if self.config.sandbox.base_container_image is None:
raise ValueError(
'base_container_image is required to build the runtime image. '
)
if self.config.sandbox.runtime_extra_deps:
self.log(
'debug',
+2 -2
View File
@@ -86,7 +86,7 @@ async def read_file(
)
try:
with open(whole_path, 'r', encoding='utf-8') as file:
with open(whole_path, 'r', encoding='utf-8') as file: # noqa: ASYNC101
lines = read_lines(file.readlines(), start, end)
except FileNotFoundError:
return ErrorObservation(f'File not found: {path}')
@@ -127,7 +127,7 @@ async def write_file(
os.makedirs(os.path.dirname(whole_path))
mode = 'w' if not os.path.exists(whole_path) else 'r+'
try:
with open(whole_path, mode, encoding='utf-8') as file:
with open(whole_path, mode, encoding='utf-8') as file: # noqa: ASYNC101
if mode != 'w':
all_lines = file.readlines()
new_file = insert_lines(insert, all_lines, start, end)
+3 -6
View File
@@ -153,18 +153,15 @@ class GitHandler:
else []
)
def get_git_changes(self) -> list[dict[str, str]]:
def get_git_changes(self) -> list[dict[str, str]] | None:
"""
Retrieves the list of changed files in the Git repository.
Returns:
list[dict[str, str]]: A list of dictionaries containing file paths and statuses.
Raises:
RuntimeError: If the directory is not a Git repository.
list[dict[str, str]] | None: A list of dictionaries containing file paths and statuses. None if not a git repository.
"""
if not self._is_git_repo():
raise RuntimeError('Not a git repository')
return None
changes_list = self._get_changed_files()
result = parse_git_changes(changes_list)
+5
View File
@@ -202,6 +202,11 @@ async def git_changes(request: Request, conversation_id: str):
try:
changes = await call_sync_from_async(runtime.get_git_changes, cwd)
if changes is None:
return JSONResponse(
status_code=500,
content={'error': 'Not a git repository'},
)
return changes
except AgentRuntimeUnavailableError as e:
logger.error(f'Runtime unavailable: {e}')
+4 -8
View File
@@ -15,11 +15,7 @@ from openhands.integrations.service_types import (
UnknownException,
User,
)
from openhands.server.auth import (
get_access_token,
get_provider_tokens,
get_user_id
)
from openhands.server.auth import get_access_token, get_provider_tokens, get_user_id
from openhands.server.shared import server_config
app = APIRouter(prefix='/api/user')
@@ -30,13 +26,13 @@ async def get_user_repositories(
sort: str = 'pushed',
provider_tokens: PROVIDER_TOKEN_TYPE | None = Depends(get_provider_tokens),
access_token: SecretStr | None = Depends(get_access_token),
user_id: str | None = Depends(get_user_id)
user_id: str | None = Depends(get_user_id),
):
if provider_tokens:
client = ProviderHandler(
provider_tokens=provider_tokens,
provider_tokens=provider_tokens,
external_auth_token=access_token,
external_auth_id=user_id
external_auth_id=user_id,
)
try:
+1 -1
View File
@@ -60,7 +60,7 @@ async def get_litellm_models() -> list[str]:
if ollama_base_url:
ollama_url = ollama_base_url.strip('/') + '/api/tags'
try:
ollama_models_list = httpx.get(ollama_url, timeout=3).json()['models']
ollama_models_list = httpx.get(ollama_url, timeout=3).json()['models'] # noqa: ASYNC100
for model in ollama_models_list:
model_list.append('ollama/' + model['name'])
break
Generated
+160 -160
View File
@@ -14,93 +14,93 @@ files = [
[[package]]
name = "aiohttp"
version = "3.11.16"
version = "3.11.18"
description = "Async http client/server framework (asyncio)"
optional = false
python-versions = ">=3.9"
groups = ["main", "evaluation"]
files = [
{file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"},
{file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"},
{file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"},
{file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"},
{file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"},
{file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"},
{file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"},
{file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"},
{file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"},
{file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"},
{file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"},
{file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"},
{file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"},
{file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"},
{file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"},
{file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"},
{file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"},
{file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"},
{file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"},
{file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"},
{file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"},
{file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"},
{file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"},
{file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"},
{file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"},
{file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"},
{file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"},
{file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"},
{file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"},
{file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"},
{file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"},
{file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"},
{file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"},
{file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"},
{file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"},
{file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"},
{file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"},
{file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"},
{file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"},
{file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"},
{file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"},
{file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"},
{file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"},
{file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"},
{file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"},
{file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"},
{file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"},
{file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"},
{file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"},
{file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"},
{file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"},
{file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"},
{file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"},
{file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"},
{file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"},
{file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"},
{file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"},
{file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"},
{file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"},
{file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"},
{file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"},
{file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"},
{file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"},
{file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"},
{file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"},
{file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"},
{file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"},
{file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"},
{file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"},
{file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"},
{file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"},
{file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"},
{file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"},
{file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"},
{file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"},
{file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"},
{file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"},
{file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"},
{file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"},
{file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"},
{file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"},
{file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96264854fedbea933a9ca4b7e0c745728f01380691687b7365d18d9e977179c4"},
{file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9602044ff047043430452bc3a2089743fa85da829e6fc9ee0025351d66c332b6"},
{file = "aiohttp-3.11.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5691dc38750fcb96a33ceef89642f139aa315c8a193bbd42a0c33476fd4a1609"},
{file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554c918ec43f8480b47a5ca758e10e793bd7410b83701676a4782672d670da55"},
{file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a4076a2b3ba5b004b8cffca6afe18a3b2c5c9ef679b4d1e9859cf76295f8d4f"},
{file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:767a97e6900edd11c762be96d82d13a1d7c4fc4b329f054e88b57cdc21fded94"},
{file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ddc9337a0fb0e727785ad4f41163cc314376e82b31846d3835673786420ef1"},
{file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f414f37b244f2a97e79b98d48c5ff0789a0b4b4609b17d64fa81771ad780e415"},
{file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fdb239f47328581e2ec7744ab5911f97afb10752332a6dd3d98e14e429e1a9e7"},
{file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2c50bad73ed629cc326cc0f75aed8ecfb013f88c5af116f33df556ed47143eb"},
{file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8d8f20c39d3fa84d1c28cdb97f3111387e48209e224408e75f29c6f8e0861d"},
{file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:106032eaf9e62fd6bc6578c8b9e6dc4f5ed9a5c1c7fb2231010a1b4304393421"},
{file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b491e42183e8fcc9901d8dcd8ae644ff785590f1727f76ca86e731c61bfe6643"},
{file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad8c745ff9460a16b710e58e06a9dec11ebc0d8f4dd82091cefb579844d69868"},
{file = "aiohttp-3.11.18-cp310-cp310-win32.whl", hash = "sha256:8e57da93e24303a883146510a434f0faf2f1e7e659f3041abc4e3fb3f6702a9f"},
{file = "aiohttp-3.11.18-cp310-cp310-win_amd64.whl", hash = "sha256:cc93a4121d87d9f12739fc8fab0a95f78444e571ed63e40bfc78cd5abe700ac9"},
{file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9"},
{file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b"},
{file = "aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66"},
{file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756"},
{file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717"},
{file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4"},
{file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f"},
{file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361"},
{file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1"},
{file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421"},
{file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e"},
{file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d"},
{file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f"},
{file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd"},
{file = "aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d"},
{file = "aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6"},
{file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2"},
{file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508"},
{file = "aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e"},
{file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f"},
{file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f"},
{file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec"},
{file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6"},
{file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009"},
{file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4"},
{file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9"},
{file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb"},
{file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda"},
{file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1"},
{file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea"},
{file = "aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8"},
{file = "aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8"},
{file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:474215ec618974054cf5dc465497ae9708543cbfc312c65212325d4212525811"},
{file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ced70adf03920d4e67c373fd692123e34d3ac81dfa1c27e45904a628567d804"},
{file = "aiohttp-3.11.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d9f6c0152f8d71361905aaf9ed979259537981f47ad099c8b3d81e0319814bd"},
{file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a35197013ed929c0aed5c9096de1fc5a9d336914d73ab3f9df14741668c0616c"},
{file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540b8a1f3a424f1af63e0af2d2853a759242a1769f9f1ab053996a392bd70118"},
{file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1"},
{file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000"},
{file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28c3f975e5ae3dbcbe95b7e3dcd30e51da561a0a0f2cfbcdea30fc1308d72137"},
{file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c28875e316c7b4c3e745172d882d8a5c835b11018e33432d281211af35794a93"},
{file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:13cd38515568ae230e1ef6919e2e33da5d0f46862943fcda74e7e915096815f3"},
{file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0e2a92101efb9f4c2942252c69c63ddb26d20f46f540c239ccfa5af865197bb8"},
{file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6d3e32b8753c8d45ac550b11a1090dd66d110d4ef805ffe60fa61495360b3b2"},
{file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261"},
{file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d4df95ad522c53f2b9ebc07f12ccd2cb15550941e11a5bbc5ddca2ca56316d7"},
{file = "aiohttp-3.11.18-cp313-cp313-win32.whl", hash = "sha256:cdd1bbaf1e61f0d94aced116d6e95fe25942f7a5f42382195fd9501089db5d78"},
{file = "aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01"},
{file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:469ac32375d9a716da49817cd26f1916ec787fc82b151c1c832f58420e6d3533"},
{file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3cec21dd68924179258ae14af9f5418c1ebdbba60b98c667815891293902e5e0"},
{file = "aiohttp-3.11.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b426495fb9140e75719b3ae70a5e8dd3a79def0ae3c6c27e012fc59f16544a4a"},
{file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2f41203e2808616292db5d7170cccf0c9f9c982d02544443c7eb0296e8b0c7"},
{file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc0ae0a5e9939e423e065a3e5b00b24b8379f1db46046d7ab71753dfc7dd0e1"},
{file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe7cdd3f7d1df43200e1c80f1aed86bb36033bf65e3c7cf46a2b97a253ef8798"},
{file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5199be2a2f01ffdfa8c3a6f5981205242986b9e63eb8ae03fd18f736e4840721"},
{file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ccec9e72660b10f8e283e91aa0295975c7bd85c204011d9f5eb69310555cf30"},
{file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1596ebf17e42e293cbacc7a24c3e0dc0f8f755b40aff0402cb74c1ff6baec1d3"},
{file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:eab7b040a8a873020113ba814b7db7fa935235e4cbaf8f3da17671baa1024863"},
{file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5d61df4a05476ff891cff0030329fee4088d40e4dc9b013fac01bc3c745542c2"},
{file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46533e6792e1410f9801d09fd40cbbff3f3518d1b501d6c3c5b218f427f6ff08"},
{file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c1b90407ced992331dd6d4f1355819ea1c274cc1ee4d5b7046c6761f9ec11829"},
{file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a2fd04ae4971b914e54fe459dd7edbbd3f2ba875d69e057d5e3c8e8cac094935"},
{file = "aiohttp-3.11.18-cp39-cp39-win32.whl", hash = "sha256:b2f317d1678002eee6fe85670039fb34a757972284614638f82b903a03feacdc"},
{file = "aiohttp-3.11.18-cp39-cp39-win_amd64.whl", hash = "sha256:5e7007b8d1d09bce37b54111f593d173691c530b80f27c6493b928dabed9e6ef"},
{file = "aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a"},
]
[package.dependencies]
@@ -496,18 +496,18 @@ files = [
[[package]]
name = "boto3"
version = "1.37.36"
version = "1.37.37"
description = "The AWS SDK for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "boto3-1.37.36-py3-none-any.whl", hash = "sha256:1ff9a15413b9a07d147ac143ad5eb7d1935ea0db96757211b5e6e148b1399850"},
{file = "boto3-1.37.36.tar.gz", hash = "sha256:3012bb083a7d7653f117a1d53bdd8a4185b59afed74422eaa32d06f55bd411ee"},
{file = "boto3-1.37.37-py3-none-any.whl", hash = "sha256:d125cb11e22817f7a2581bade4bf7b75247b401888890239ceb5d3e902ccaf38"},
{file = "boto3-1.37.37.tar.gz", hash = "sha256:752d31105a45e3e01c8c68471db14ae439990b75a35e72b591ca528e2575b28f"},
]
[package.dependencies]
botocore = ">=1.37.36,<1.38.0"
botocore = ">=1.37.37,<1.38.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.11.0,<0.12.0"
@@ -516,14 +516,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "boto3-stubs"
version = "1.37.36"
description = "Type annotations for boto3 1.37.36 generated with mypy-boto3-builder 8.10.1"
version = "1.37.37"
description = "Type annotations for boto3 1.37.37 generated with mypy-boto3-builder 8.10.1"
optional = false
python-versions = ">=3.8"
groups = ["evaluation"]
files = [
{file = "boto3_stubs-1.37.36-py3-none-any.whl", hash = "sha256:f7c00421bd0d665ed0fb876477d7ec70b589cc2159ccddf2013ada626877388b"},
{file = "boto3_stubs-1.37.36.tar.gz", hash = "sha256:d0895cc8b314a4c193355b96530ae8b051a52b9aa07bad371d5b307a140c8a60"},
{file = "boto3_stubs-1.37.37-py3-none-any.whl", hash = "sha256:937fabdc226b6661d90b7abb0dcaf4450c08e6e334d726381ba7479672d828c6"},
{file = "boto3_stubs-1.37.37.tar.gz", hash = "sha256:e467b7aa64c96f71266e3d3d763cd826e34e4063d511c0dec4341d3071d3428c"},
]
[package.dependencies]
@@ -579,7 +579,7 @@ bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime (
bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.37.0,<1.38.0)"]
billing = ["mypy-boto3-billing (>=1.37.0,<1.38.0)"]
billingconductor = ["mypy-boto3-billingconductor (>=1.37.0,<1.38.0)"]
boto3 = ["boto3 (==1.37.36)"]
boto3 = ["boto3 (==1.37.37)"]
braket = ["mypy-boto3-braket (>=1.37.0,<1.38.0)"]
budgets = ["mypy-boto3-budgets (>=1.37.0,<1.38.0)"]
ce = ["mypy-boto3-ce (>=1.37.0,<1.38.0)"]
@@ -943,14 +943,14 @@ xray = ["mypy-boto3-xray (>=1.37.0,<1.38.0)"]
[[package]]
name = "botocore"
version = "1.37.36"
version = "1.37.37"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "botocore-1.37.36-py3-none-any.whl", hash = "sha256:3dcc41a40a868f599bd9ea64f78c6640025df7810c939505d859979e4688b1ae"},
{file = "botocore-1.37.36.tar.gz", hash = "sha256:89cf1ca101432adc391e5604ab45851346b8f3a72e5a468fa0ec7a99a5ea3efc"},
{file = "botocore-1.37.37-py3-none-any.whl", hash = "sha256:eb730ff978f47c02f0c8ed07bccdc0db6d8fa098ed32ac31bee1da0e9be480d1"},
{file = "botocore-1.37.37.tar.gz", hash = "sha256:3eadde6fed95c4cb469cc39d1c3558528b7fa76d23e7e16d4bddc77250431a64"},
]
[package.dependencies]
@@ -4412,14 +4412,14 @@ types-tqdm = "*"
[[package]]
name = "litellm"
version = "1.66.3"
version = "1.67.0"
description = "Library to easily interface with LLM API providers"
optional = false
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
groups = ["main"]
files = [
{file = "litellm-1.66.3-py3-none-any.whl", hash = "sha256:f1c662afec14225cee3bae7c93961857edf13fcece42fe46d921d9df50f70dd2"},
{file = "litellm-1.66.3.tar.gz", hash = "sha256:909564f5dc33d7dac236de6cc8066512834467bcebe3494a664d72ae6506a5ca"},
{file = "litellm-1.67.0-py3-none-any.whl", hash = "sha256:d297126f45eea8d8a3df9c0de1d9491ff20e78dab5d1aa3820602082501ba89e"},
{file = "litellm-1.67.0.tar.gz", hash = "sha256:18439db292d85b1d886bfa35de9d999600ecc6b4fc1137f12e6810d2133c8cec"},
]
[package.dependencies]
@@ -4437,7 +4437,7 @@ tokenizers = "*"
[package.extras]
extra-proxy = ["azure-identity (>=1.15.0,<2.0.0)", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0) ; python_version >= \"3.9\" and python_version < \"3.14\"", "resend (>=0.8.0,<0.9.0)"]
proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "boto3 (==1.34.34)", "cryptography (>=43.0.1,<44.0.0)", "fastapi (>=0.115.5,<0.116.0)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-proxy-extras (==0.1.10)", "mcp (==1.5.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.29.0,<0.30.0)", "uvloop (>=0.21.0,<0.22.0)", "websockets (>=13.1.0,<14.0.0)"]
proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "boto3 (==1.34.34)", "cryptography (>=43.0.1,<44.0.0)", "fastapi (>=0.115.5,<0.116.0)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-proxy-extras (==0.1.11)", "mcp (==1.5.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.29.0,<0.30.0)", "uvloop (>=0.21.0,<0.22.0)", "websockets (>=13.1.0,<14.0.0)"]
[[package]]
name = "lxml"
@@ -4882,14 +4882,14 @@ files = [
[[package]]
name = "modal"
version = "0.74.10"
version = "0.74.14"
description = "Python client library for Modal"
optional = false
python-versions = ">=3.9"
groups = ["main", "evaluation"]
files = [
{file = "modal-0.74.10-py3-none-any.whl", hash = "sha256:c494dce12701a0d92eb98c88a8f87d1f95224d77661698ff1b499685f5ff92a1"},
{file = "modal-0.74.10.tar.gz", hash = "sha256:da27f83485c7d9cc525d04dc5fe808af0b26cd1fadb3b3a39d99462dca3034c7"},
{file = "modal-0.74.14-py3-none-any.whl", hash = "sha256:eb3edf5aa7a105a11c32c0f18aba240766d4a8b8089b33b6458f7d5eab632feb"},
{file = "modal-0.74.14.tar.gz", hash = "sha256:7757518feb53cca3e62022ce8ed9ba389c831a0c3cbe967de00e6dd2217aac0a"},
]
[package.dependencies]
@@ -5356,67 +5356,67 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"
[[package]]
name = "numpy"
version = "2.2.4"
version = "2.2.5"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.10"
groups = ["main", "evaluation", "test"]
files = [
{file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"},
{file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"},
{file = "numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775"},
{file = "numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9"},
{file = "numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2"},
{file = "numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020"},
{file = "numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3"},
{file = "numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017"},
{file = "numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a"},
{file = "numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542"},
{file = "numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4"},
{file = "numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4"},
{file = "numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f"},
{file = "numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880"},
{file = "numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1"},
{file = "numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5"},
{file = "numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687"},
{file = "numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6"},
{file = "numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09"},
{file = "numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91"},
{file = "numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4"},
{file = "numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854"},
{file = "numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24"},
{file = "numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee"},
{file = "numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba"},
{file = "numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592"},
{file = "numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb"},
{file = "numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f"},
{file = "numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00"},
{file = "numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146"},
{file = "numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7"},
{file = "numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0"},
{file = "numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392"},
{file = "numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc"},
{file = "numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298"},
{file = "numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7"},
{file = "numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6"},
{file = "numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd"},
{file = "numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c"},
{file = "numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3"},
{file = "numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8"},
{file = "numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39"},
{file = "numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd"},
{file = "numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0"},
{file = "numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960"},
{file = "numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8"},
{file = "numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc"},
{file = "numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff"},
{file = "numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286"},
{file = "numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d"},
{file = "numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8"},
{file = "numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c"},
{file = "numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d"},
{file = "numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d"},
{file = "numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f"},
{file = "numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26"},
{file = "numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a"},
{file = "numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f"},
{file = "numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba"},
{file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3"},
{file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57"},
{file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c"},
{file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1"},
{file = "numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88"},
{file = "numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7"},
{file = "numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b"},
{file = "numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda"},
{file = "numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d"},
{file = "numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54"},
{file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610"},
{file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b"},
{file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be"},
{file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906"},
{file = "numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175"},
{file = "numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd"},
{file = "numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051"},
{file = "numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc"},
{file = "numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e"},
{file = "numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa"},
{file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571"},
{file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073"},
{file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8"},
{file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae"},
{file = "numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb"},
{file = "numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282"},
{file = "numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4"},
{file = "numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f"},
{file = "numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9"},
{file = "numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191"},
{file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372"},
{file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d"},
{file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7"},
{file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73"},
{file = "numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b"},
{file = "numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471"},
{file = "numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6"},
{file = "numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba"},
{file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133"},
{file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376"},
{file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19"},
{file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0"},
{file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a"},
{file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066"},
{file = "numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e"},
{file = "numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8"},
{file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe"},
{file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e"},
{file = "numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70"},
{file = "numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169"},
{file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"},
]
[[package]]
@@ -9498,14 +9498,14 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "uvicorn"
version = "0.34.1"
version = "0.34.2"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "uvicorn-0.34.1-py3-none-any.whl", hash = "sha256:984c3a8c7ca18ebaad15995ee7401179212c59521e67bfc390c07fa2b8d2e065"},
{file = "uvicorn-0.34.1.tar.gz", hash = "sha256:af981725fc4b7ffc5cb3b0e9eda6258a90c4b52cb2a83ce567ae0a7ae1757afc"},
{file = "uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403"},
{file = "uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328"},
]
[package.dependencies]
@@ -2,9 +2,9 @@ import os
import subprocess
import tempfile
from openhands.integrations.service_types import ProviderType
from openhands.resolver.interfaces.issue import Issue
from openhands.resolver.send_pull_request import make_commit
from openhands.resolver.utils import Platform
def test_commit_message_with_quotes():
@@ -160,7 +160,7 @@ def test_pr_title_with_quotes(monkeypatch):
issue=issue,
token='dummy-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=temp_dir,
pr_type='ready',
)
+140 -145
View File
@@ -11,6 +11,7 @@ from openhands.events.observation import (
CmdOutputObservation,
NullObservation,
)
from openhands.integrations.service_types import ProviderType
from openhands.llm.llm import LLM
from openhands.resolver.interfaces.github import GithubIssueHandler, GithubPRHandler
from openhands.resolver.interfaces.issue import Issue, ReviewThread
@@ -18,9 +19,12 @@ from openhands.resolver.interfaces.issue_definitions import (
ServiceContextIssue,
ServiceContextPR,
)
from openhands.resolver.resolve_issue import IssueResolver
from openhands.resolver.resolve_issue import (
complete_runtime,
initialize_runtime,
process_issue,
)
from openhands.resolver.resolver_output import ResolverOutput
from openhands.resolver.utils import Platform
@pytest.fixture
@@ -77,30 +81,7 @@ def test_initialize_runtime():
),
]
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = 5
mock_args.output_dir = '/tmp'
mock_args.llm_model = 'test'
mock_args.llm_api_key = 'test'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = None
mock_args.is_experimental = False
mock_args.issue_number = None
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'issue'
mock_args.prompt_file = None
# Mock the identify_token function to return GitHub platform
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITHUB):
resolver = IssueResolver(mock_args)
resolver.initialize_runtime(mock_runtime)
initialize_runtime(mock_runtime, ProviderType.GITHUB)
assert mock_runtime.run_action.call_count == 2
mock_runtime.run_action.assert_any_call(CmdRunAction(command='cd /workspace'))
@@ -111,48 +92,39 @@ def test_initialize_runtime():
@pytest.mark.asyncio
async def test_resolve_issue_no_issues_found():
"""Test the resolve_issue method when no issues are found."""
from openhands.resolver.resolve_issue import resolve_issue
# Mock dependencies
mock_handler = MagicMock()
mock_handler.get_converted_issues.return_value = [] # Return empty list
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = 5
mock_args.output_dir = '/tmp'
mock_args.llm_model = 'test'
mock_args.llm_api_key = 'test'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = None
mock_args.is_experimental = False
mock_args.issue_number = 5432
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'issue'
mock_args.prompt_file = None
# Create a resolver instance with mocked identify_token
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITHUB):
resolver = IssueResolver(mock_args)
# Mock the issue_handler_factory method
resolver.issue_handler_factory = MagicMock(return_value=mock_handler)
# Test that the correct exception is raised
with pytest.raises(ValueError) as exc_info:
await resolver.resolve_issue()
# Verify the error message
assert 'No issues found for issue number 5432' in str(exc_info.value)
assert 'test-owner/test-repo' in str(exc_info.value)
# Verify that the handler was correctly configured and called
resolver.issue_handler_factory.assert_called_once()
mock_handler.get_converted_issues.assert_called_once_with(issue_numbers=[5432], comment_id=None)
with patch(
'openhands.resolver.resolve_issue.issue_handler_factory',
return_value=mock_handler,
):
with pytest.raises(ValueError) as exc_info:
await resolve_issue(
owner='test-owner',
repo='test-repo',
token='test-token',
username='test-user',
platform=ProviderType.GITHUB,
max_iterations=5,
output_dir='/tmp',
llm_config=LLMConfig(model='test', api_key='test'),
base_container_image='test-image',
runtime_container_image='test-image',
prompt_template='test-template',
issue_type='pr',
repo_instruction=None,
issue_number=5432,
comment_id=None,
)
assert 'No issues found for issue number 5432' in str(exc_info.value)
assert 'test-owner/test-repo' in str(exc_info.value)
assert 'exists in the repository' in str(exc_info.value)
assert 'correct permissions' in str(exc_info.value)
def test_download_issues_from_github():
@@ -339,35 +311,14 @@ async def test_complete_runtime():
command='git config --global --add safe.directory /workspace',
),
create_cmd_output(
exit_code=0, content='', command='git add -A'
exit_code=0, content='', command='git diff base_commit_hash fix'
),
create_cmd_output(exit_code=0, content='git diff content', command='git diff --no-color --cached base_commit_hash'),
create_cmd_output(exit_code=0, content='git diff content', command='git apply'),
]
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = 5
mock_args.output_dir = '/tmp'
mock_args.llm_model = 'test'
mock_args.llm_api_key = 'test'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = None
mock_args.is_experimental = False
mock_args.issue_number = None
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'issue'
mock_args.prompt_file = None
# Create a resolver instance with mocked identify_token
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITHUB):
resolver = IssueResolver(mock_args)
result = await resolver.complete_runtime(mock_runtime, 'base_commit_hash')
result = await complete_runtime(
mock_runtime, 'base_commit_hash', ProviderType.GITHUB
)
assert result == {'git_patch': 'git diff content'}
assert mock_runtime.run_action.call_count == 5
@@ -375,7 +326,13 @@ async def test_complete_runtime():
@pytest.mark.asyncio
async def test_process_issue(mock_output_dir, mock_prompt_template):
"""Test the process_issue method with different scenarios."""
# Mock dependencies
mock_create_runtime = MagicMock()
mock_initialize_runtime = AsyncMock()
mock_run_controller = AsyncMock()
mock_complete_runtime = AsyncMock()
handler_instance = MagicMock()
# Set up test data
issue = Issue(
owner='test_owner',
@@ -387,69 +344,80 @@ async def test_process_issue(mock_output_dir, mock_prompt_template):
base_commit = 'abcdef1234567890'
repo_instruction = 'Resolve this repo'
max_iterations = 5
llm_config = LLMConfig(model='gpt-4', api_key='test_api_key')
llm_config = LLMConfig(model='test_model', api_key='test_api_key')
base_container_image = 'test_image:latest'
runtime_container_image = 'test_image:latest'
# Test cases for different scenarios
test_cases = [
{
'name': 'successful_run',
'run_controller_return': MagicMock(
history=[NullObservation(content='')],
metrics=MagicMock(
get=MagicMock(return_value={'test_result': 'passed'})
),
last_error=None,
),
'run_controller_raises': None,
'expected_success': True,
'expected_error': None,
'expected_explanation': 'Issue resolved successfully',
},
{
'name': 'value_error',
'run_controller_return': None,
'run_controller_raises': ValueError('Test value error'),
'expected_success': False,
'expected_error': 'Agent failed to run or crashed',
'expected_explanation': 'Agent failed to run',
},
{
'name': 'runtime_error',
'run_controller_return': None,
'run_controller_raises': RuntimeError('Test runtime error'),
'expected_success': False,
'expected_error': 'Agent failed to run or crashed',
'expected_explanation': 'Agent failed to run',
},
{
'name': 'json_decode_error',
'run_controller_return': MagicMock(
history=[NullObservation(content='')],
metrics=MagicMock(
get=MagicMock(return_value={'test_result': 'passed'})
),
last_error=None,
),
'run_controller_raises': None,
'expected_success': True,
'expected_error': None,
'expected_explanation': 'Non-JSON explanation',
'is_pr': True,
'comment_success': [True, False], # To trigger the PR success logging code path
'comment_success': [
True,
False,
], # To trigger the PR success logging code path
},
]
for test_case in test_cases:
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = max_iterations
mock_args.output_dir = mock_output_dir
mock_args.llm_model = 'gpt-4'
mock_args.llm_api_key = 'test_api_key'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = runtime_container_image
mock_args.is_experimental = False
mock_args.issue_number = None
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
mock_args.prompt_file = None
# Create a resolver instance with mocked identify_token
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITHUB):
resolver = IssueResolver(mock_args)
# Set the prompt template and repo instruction directly
resolver.prompt_template = mock_prompt_template
resolver.repo_instruction = repo_instruction
# Mock the handler
handler_instance = MagicMock()
# Reset mocks
mock_create_runtime.reset_mock()
mock_initialize_runtime.reset_mock()
mock_run_controller.reset_mock()
mock_complete_runtime.reset_mock()
handler_instance.reset_mock()
# Mock return values
mock_create_runtime.return_value = MagicMock(connect=AsyncMock())
if test_case['run_controller_raises']:
mock_run_controller.side_effect = test_case['run_controller_raises']
else:
mock_run_controller.return_value = test_case['run_controller_return']
mock_run_controller.side_effect = None
mock_complete_runtime.return_value = {'git_patch': 'test patch'}
handler_instance.guess_success.return_value = (
test_case['expected_success'],
test_case.get('comment_success', None),
@@ -457,29 +425,44 @@ async def test_process_issue(mock_output_dir, mock_prompt_template):
)
handler_instance.get_instruction.return_value = ('Test instruction', [])
handler_instance.issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
# Mock the process_issue method to return a predefined result
expected_result = ResolverOutput(
issue=issue,
issue_type='pr' if test_case.get('is_pr', False) else 'issue',
instruction='Test instruction',
base_commit=base_commit,
git_patch='test patch',
history=[],
metrics={},
success=test_case['expected_success'],
comment_success=test_case.get('comment_success', None),
result_explanation=test_case['expected_explanation'],
error=test_case['expected_error'],
)
# Use patch to replace the process_issue method with a mock that returns our expected result
with patch.object(resolver, 'process_issue', return_value=expected_result):
# Call the mocked method
result = await resolver.process_issue(issue, base_commit, handler_instance)
# Assert the result matches our expectations
assert result == expected_result
with (
patch(
'openhands.resolver.resolve_issue.create_runtime', mock_create_runtime
),
patch(
'openhands.resolver.resolve_issue.initialize_runtime',
mock_initialize_runtime,
),
patch(
'openhands.resolver.resolve_issue.run_controller', mock_run_controller
),
patch(
'openhands.resolver.resolve_issue.complete_runtime',
mock_complete_runtime,
),
patch('openhands.resolver.resolve_issue.logger'),
):
# Call the function
result = await process_issue(
issue,
ProviderType.GITHUB,
base_commit,
max_iterations,
llm_config,
mock_output_dir,
base_container_image,
runtime_container_image,
mock_prompt_template,
handler_instance,
repo_instruction,
reset_logger=False,
)
# Assert the result
expected_issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
assert handler_instance.issue_type == expected_issue_type
assert isinstance(result, ResolverOutput)
assert result.issue == issue
assert result.base_commit == base_commit
assert result.git_patch == 'test patch'
@@ -487,6 +470,18 @@ async def test_process_issue(mock_output_dir, mock_prompt_template):
assert result.result_explanation == test_case['expected_explanation']
assert result.error == test_case['expected_error']
# Assert that the mocked functions were called
mock_create_runtime.assert_called_once()
mock_initialize_runtime.assert_called_once()
mock_run_controller.assert_called_once()
mock_complete_runtime.assert_called_once()
# Assert that guess_success was called only for successful runs
if test_case['expected_success']:
handler_instance.guess_success.assert_called_once()
else:
handler_instance.guess_success.assert_not_called()
def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
issue = Issue(
@@ -5,6 +5,7 @@ from unittest.mock import ANY, MagicMock, call, patch
import pytest
from openhands.core.config import LLMConfig
from openhands.integrations.service_types import ProviderType
from openhands.resolver.interfaces.github import GithubIssueHandler
from openhands.resolver.interfaces.issue import ReviewThread
from openhands.resolver.resolver_output import Issue, ResolverOutput
@@ -18,7 +19,6 @@ from openhands.resolver.send_pull_request import (
send_pull_request,
update_existing_pull_request,
)
from openhands.resolver.utils import Platform
@pytest.fixture
@@ -289,7 +289,7 @@ def test_update_existing_pull_request(
issue,
token,
username,
Platform.GITHUB,
ProviderType.GITHUB,
patch_dir,
llm_config,
comment_message=None,
@@ -388,7 +388,7 @@ def test_send_pull_request(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type=pr_type,
target_branch=target_branch,
@@ -478,7 +478,7 @@ def test_send_pull_request_with_reviewer(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='ready',
reviewer=reviewer,
@@ -536,7 +536,7 @@ def test_send_pull_request_target_branch_with_fork(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='ready',
fork_owner=fork_owner,
@@ -600,7 +600,7 @@ def test_send_pull_request_target_branch_with_additional_message(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='ready',
target_branch=target_branch,
@@ -639,7 +639,7 @@ def test_send_pull_request_invalid_target_branch(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='ready',
target_branch='nonexistent-branch',
@@ -674,7 +674,7 @@ def test_send_pull_request_git_push_failure(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='ready',
)
@@ -734,7 +734,7 @@ def test_send_pull_request_permission_error(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='ready',
)
@@ -861,7 +861,7 @@ def test_process_single_pr_update(
resolver_output,
token,
username,
Platform.GITHUB,
ProviderType.GITHUB,
pr_type,
mock_llm_config,
None,
@@ -880,7 +880,7 @@ def test_process_single_pr_update(
issue=resolver_output.issue,
token=token,
username=username,
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=f'{mock_output_dir}/patches/pr_1',
additional_message='[Test success 1]',
llm_config=mock_llm_config,
@@ -904,7 +904,7 @@ def test_process_single_issue(
token = 'test_token'
username = 'test_user'
pr_type = 'draft'
platform = Platform.GITHUB
platform = ProviderType.GITHUB
resolver_output = ResolverOutput(
issue=Issue(
@@ -1013,7 +1013,7 @@ def test_process_single_issue_unsuccessful(
resolver_output,
token,
username,
Platform.GITHUB,
ProviderType.GITHUB,
pr_type,
mock_llm_config,
None,
@@ -1105,7 +1105,7 @@ def test_process_all_successful_issues(
'output_dir',
'token',
'username',
Platform.GITHUB,
ProviderType.GITHUB,
'draft',
mock_llm_config, # llm_config
None, # fork_owner
@@ -1122,7 +1122,7 @@ def test_process_all_successful_issues(
resolver_output_1,
'token',
'username',
Platform.GITHUB,
ProviderType.GITHUB,
'draft',
mock_llm_config,
None,
@@ -1137,7 +1137,7 @@ def test_process_all_successful_issues(
resolver_output_3,
'token',
'username',
Platform.GITHUB,
ProviderType.GITHUB,
'draft',
mock_llm_config,
None,
@@ -1179,7 +1179,7 @@ def test_send_pull_request_branch_naming(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=repo_path,
pr_type='branch',
)
@@ -1264,7 +1264,7 @@ def test_main(
mock_resolver_output = MagicMock()
mock_load_single_resolver_output.return_value = mock_resolver_output
mock_identify_token.return_value = Platform.GITHUB
mock_identify_token.return_value = ProviderType.GITHUB
# Run main function
main()
@@ -1283,7 +1283,7 @@ def test_main(
mock_resolver_output,
'mock_token',
'mock_username',
Platform.GITHUB,
ProviderType.GITHUB,
'draft',
llm_config,
None,
@@ -1307,7 +1307,7 @@ def test_main(
'/mock/output',
'mock_token',
'mock_username',
Platform.GITHUB,
ProviderType.GITHUB,
'draft',
llm_config,
None,
@@ -1320,8 +1320,11 @@ def test_main(
main()
# Test for invalid token
mock_identify_token.return_value = Platform.INVALID
with pytest.raises(ValueError, match='Token is invalid.'):
mock_args.issue_number = '42' # Reset to valid issue number
mock_getenv.side_effect = (
lambda key, default=None: None
) # Return None for all env vars
with pytest.raises(ValueError, match='token is not set'):
main()
@@ -3,9 +3,9 @@ import subprocess
import tempfile
from openhands.core.logger import openhands_logger as logger
from openhands.integrations.service_types import ProviderType
from openhands.resolver.interfaces.issue import Issue
from openhands.resolver.send_pull_request import make_commit
from openhands.resolver.utils import Platform
from openhands.resolver.send_pull_request import make_commit, send_pull_request
def test_commit_message_with_quotes():
@@ -155,13 +155,12 @@ def test_pr_title_with_quotes(monkeypatch):
# Try to send a PR - this will fail if the title is incorrectly escaped
logger.info('Sending PR...')
from openhands.resolver.send_pull_request import send_pull_request
send_pull_request(
issue=issue,
token='dummy-token',
username='test-user',
platform=Platform.GITHUB,
platform=ProviderType.GITHUB,
patch_dir=temp_dir,
pr_type='ready',
)
@@ -11,6 +11,7 @@ from openhands.events.observation import (
CmdOutputObservation,
NullObservation,
)
from openhands.integrations.service_types import ProviderType
from openhands.llm.llm import LLM
from openhands.resolver.interfaces.gitlab import GitlabIssueHandler, GitlabPRHandler
from openhands.resolver.interfaces.issue import Issue, ReviewThread
@@ -18,9 +19,12 @@ from openhands.resolver.interfaces.issue_definitions import (
ServiceContextIssue,
ServiceContextPR,
)
from openhands.resolver.resolve_issue import IssueResolver
from openhands.resolver.resolve_issue import (
complete_runtime,
initialize_runtime,
process_issue,
)
from openhands.resolver.resolver_output import ResolverOutput
from openhands.resolver.utils import Platform
@pytest.fixture
@@ -89,30 +93,7 @@ def test_initialize_runtime():
),
]
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = 5
mock_args.output_dir = '/tmp'
mock_args.llm_model = 'test'
mock_args.llm_api_key = 'test'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = None
mock_args.is_experimental = False
mock_args.issue_number = None
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'issue'
mock_args.prompt_file = None
# Mock the identify_token function to return GitLab platform
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITLAB):
resolver = IssueResolver(mock_args)
resolver.initialize_runtime(mock_runtime)
initialize_runtime(mock_runtime, ProviderType.GITLAB)
if os.getenv('GITLAB_CI') == 'true':
assert mock_runtime.run_action.call_count == 3
@@ -131,48 +112,39 @@ def test_initialize_runtime():
@pytest.mark.asyncio
async def test_resolve_issue_no_issues_found():
"""Test the resolve_issue method when no issues are found."""
from openhands.resolver.resolve_issue import resolve_issue
# Mock dependencies
mock_handler = MagicMock()
mock_handler.get_converted_issues.return_value = [] # Return empty list
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = 5
mock_args.output_dir = '/tmp'
mock_args.llm_model = 'test'
mock_args.llm_api_key = 'test'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = None
mock_args.is_experimental = False
mock_args.issue_number = 5432
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'issue'
mock_args.prompt_file = None
# Create a resolver instance with mocked identify_token
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITLAB):
resolver = IssueResolver(mock_args)
# Mock the issue_handler_factory method
resolver.issue_handler_factory = MagicMock(return_value=mock_handler)
# Test that the correct exception is raised
with pytest.raises(ValueError) as exc_info:
await resolver.resolve_issue()
# Verify the error message
assert 'No issues found for issue number 5432' in str(exc_info.value)
assert 'test-owner/test-repo' in str(exc_info.value)
# Verify that the handler was correctly configured and called
resolver.issue_handler_factory.assert_called_once()
mock_handler.get_converted_issues.assert_called_once_with(issue_numbers=[5432], comment_id=None)
with patch(
'openhands.resolver.resolve_issue.issue_handler_factory',
return_value=mock_handler,
):
with pytest.raises(ValueError) as exc_info:
await resolve_issue(
owner='test-owner',
repo='test-repo',
token='test-token',
username='test-user',
platform=ProviderType.GITLAB,
max_iterations=5,
output_dir='/tmp',
llm_config=LLMConfig(model='test', api_key='test'),
base_container_image='test-image',
runtime_container_image='test-image',
prompt_template='test-template',
issue_type='pr',
repo_instruction=None,
issue_number=5432,
comment_id=None,
)
assert 'No issues found for issue number 5432' in str(exc_info.value)
assert 'test-owner/test-repo' in str(exc_info.value)
assert 'exists in the repository' in str(exc_info.value)
assert 'correct permissions' in str(exc_info.value)
def test_download_issues_from_gitlab():
@@ -379,35 +351,14 @@ async def test_complete_runtime():
command='git config --global --add safe.directory /workspace',
),
create_cmd_output(
exit_code=0, content='', command='git add -A'
exit_code=0, content='', command='git diff base_commit_hash fix'
),
create_cmd_output(exit_code=0, content='git diff content', command='git diff --no-color --cached base_commit_hash'),
create_cmd_output(exit_code=0, content='git diff content', command='git apply'),
]
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = 5
mock_args.output_dir = '/tmp'
mock_args.llm_model = 'test'
mock_args.llm_api_key = 'test'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = None
mock_args.is_experimental = False
mock_args.issue_number = None
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'issue'
mock_args.prompt_file = None
# Create a resolver instance with mocked identify_token
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITLAB):
resolver = IssueResolver(mock_args)
result = await resolver.complete_runtime(mock_runtime, 'base_commit_hash')
result = await complete_runtime(
mock_runtime, 'base_commit_hash', ProviderType.GITLAB
)
assert result == {'git_patch': 'git diff content'}
assert mock_runtime.run_action.call_count == 5
@@ -415,7 +366,13 @@ async def test_complete_runtime():
@pytest.mark.asyncio
async def test_process_issue(mock_output_dir, mock_prompt_template):
"""Test the process_issue method with different scenarios."""
# Mock dependencies
mock_create_runtime = MagicMock()
mock_initialize_runtime = AsyncMock()
mock_run_controller = AsyncMock()
mock_complete_runtime = AsyncMock()
handler_instance = MagicMock()
# Set up test data
issue = Issue(
owner='test_owner',
@@ -427,69 +384,80 @@ async def test_process_issue(mock_output_dir, mock_prompt_template):
base_commit = 'abcdef1234567890'
repo_instruction = 'Resolve this repo'
max_iterations = 5
llm_config = LLMConfig(model='gpt-4', api_key='test_api_key')
llm_config = LLMConfig(model='test_model', api_key='test_api_key')
base_container_image = 'test_image:latest'
runtime_container_image = 'test_image:latest'
# Test cases for different scenarios
test_cases = [
{
'name': 'successful_run',
'run_controller_return': MagicMock(
history=[NullObservation(content='')],
metrics=MagicMock(
get=MagicMock(return_value={'test_result': 'passed'})
),
last_error=None,
),
'run_controller_raises': None,
'expected_success': True,
'expected_error': None,
'expected_explanation': 'Issue resolved successfully',
},
{
'name': 'value_error',
'run_controller_return': None,
'run_controller_raises': ValueError('Test value error'),
'expected_success': False,
'expected_error': 'Agent failed to run or crashed',
'expected_explanation': 'Agent failed to run',
},
{
'name': 'runtime_error',
'run_controller_return': None,
'run_controller_raises': RuntimeError('Test runtime error'),
'expected_success': False,
'expected_error': 'Agent failed to run or crashed',
'expected_explanation': 'Agent failed to run',
},
{
'name': 'json_decode_error',
'run_controller_return': MagicMock(
history=[NullObservation(content='')],
metrics=MagicMock(
get=MagicMock(return_value={'test_result': 'passed'})
),
last_error=None,
),
'run_controller_raises': None,
'expected_success': True,
'expected_error': None,
'expected_explanation': 'Non-JSON explanation',
'is_pr': True,
'comment_success': [True, False], # To trigger the PR success logging code path
'comment_success': [
True,
False,
], # To trigger the PR success logging code path
},
]
for test_case in test_cases:
# Create a mock Namespace object with the required attributes
mock_args = MagicMock()
mock_args.selected_repo = 'test-owner/test-repo'
mock_args.token = 'test-token'
mock_args.username = 'test-user'
mock_args.max_iterations = max_iterations
mock_args.output_dir = mock_output_dir
mock_args.llm_model = 'gpt-4'
mock_args.llm_api_key = 'test_api_key'
mock_args.llm_base_url = None
mock_args.base_domain = None
mock_args.runtime_container_image = runtime_container_image
mock_args.is_experimental = False
mock_args.issue_number = None
mock_args.comment_id = None
mock_args.repo_instruction_file = None
mock_args.issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
mock_args.prompt_file = None
# Create a resolver instance with mocked identify_token
with patch('openhands.resolver.resolve_issue.identify_token', return_value=Platform.GITLAB):
resolver = IssueResolver(mock_args)
# Set the prompt template and repo instruction directly
resolver.prompt_template = mock_prompt_template
resolver.repo_instruction = repo_instruction
# Mock the handler
handler_instance = MagicMock()
# Reset mocks
mock_create_runtime.reset_mock()
mock_initialize_runtime.reset_mock()
mock_run_controller.reset_mock()
mock_complete_runtime.reset_mock()
handler_instance.reset_mock()
# Mock return values
mock_create_runtime.return_value = MagicMock(connect=AsyncMock())
if test_case['run_controller_raises']:
mock_run_controller.side_effect = test_case['run_controller_raises']
else:
mock_run_controller.return_value = test_case['run_controller_return']
mock_run_controller.side_effect = None
mock_complete_runtime.return_value = {'git_patch': 'test patch'}
handler_instance.guess_success.return_value = (
test_case['expected_success'],
test_case.get('comment_success', None),
@@ -497,29 +465,44 @@ async def test_process_issue(mock_output_dir, mock_prompt_template):
)
handler_instance.get_instruction.return_value = ('Test instruction', [])
handler_instance.issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
# Mock the process_issue method to return a predefined result
expected_result = ResolverOutput(
issue=issue,
issue_type='pr' if test_case.get('is_pr', False) else 'issue',
instruction='Test instruction',
base_commit=base_commit,
git_patch='test patch',
history=[],
metrics={},
success=test_case['expected_success'],
comment_success=test_case.get('comment_success', None),
result_explanation=test_case['expected_explanation'],
error=test_case['expected_error'],
)
# Use patch to replace the process_issue method with a mock that returns our expected result
with patch.object(resolver, 'process_issue', return_value=expected_result):
# Call the mocked method
result = await resolver.process_issue(issue, base_commit, handler_instance)
# Assert the result matches our expectations
assert result == expected_result
with (
patch(
'openhands.resolver.resolve_issue.create_runtime', mock_create_runtime
),
patch(
'openhands.resolver.resolve_issue.initialize_runtime',
mock_initialize_runtime,
),
patch(
'openhands.resolver.resolve_issue.run_controller', mock_run_controller
),
patch(
'openhands.resolver.resolve_issue.complete_runtime',
mock_complete_runtime,
),
patch('openhands.resolver.resolve_issue.logger'),
):
# Call the function
result = await process_issue(
issue,
ProviderType.GITLAB,
base_commit,
max_iterations,
llm_config,
mock_output_dir,
base_container_image,
runtime_container_image,
mock_prompt_template,
handler_instance,
repo_instruction,
reset_logger=False,
)
# Assert the result
expected_issue_type = 'pr' if test_case.get('is_pr', False) else 'issue'
assert handler_instance.issue_type == expected_issue_type
assert isinstance(result, ResolverOutput)
assert result.issue == issue
assert result.base_commit == base_commit
assert result.git_patch == 'test patch'
@@ -527,6 +510,18 @@ async def test_process_issue(mock_output_dir, mock_prompt_template):
assert result.result_explanation == test_case['expected_explanation']
assert result.error == test_case['expected_error']
# Assert that the mocked functions were called
mock_create_runtime.assert_called_once()
mock_initialize_runtime.assert_called_once()
mock_run_controller.assert_called_once()
mock_complete_runtime.assert_called_once()
# Assert that guess_success was called only for successful runs
if test_case['expected_success']:
handler_instance.guess_success.assert_called_once()
else:
handler_instance.guess_success.assert_not_called()
def test_get_instruction(mock_prompt_template, mock_followup_prompt_template):
issue = Issue(
@@ -6,6 +6,7 @@ from urllib.parse import quote
import pytest
from openhands.core.config import LLMConfig
from openhands.integrations.service_types import ProviderType
from openhands.resolver.interfaces.gitlab import GitlabIssueHandler
from openhands.resolver.interfaces.issue import ReviewThread
from openhands.resolver.resolver_output import Issue, ResolverOutput
@@ -19,7 +20,6 @@ from openhands.resolver.send_pull_request import (
send_pull_request,
update_existing_pull_request,
)
from openhands.resolver.utils import Platform
@pytest.fixture
@@ -290,7 +290,7 @@ def test_update_existing_pull_request(
issue,
token,
username,
Platform.GITLAB,
ProviderType.GITLAB,
patch_dir,
llm_config,
comment_message=None,
@@ -392,7 +392,7 @@ def test_send_pull_request(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=repo_path,
pr_type=pr_type,
target_branch=target_branch,
@@ -499,7 +499,7 @@ def test_send_pull_request_with_reviewer(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=repo_path,
pr_type='ready',
reviewer=reviewer,
@@ -547,7 +547,7 @@ def test_send_pull_request_invalid_target_branch(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=repo_path,
pr_type='ready',
target_branch='nonexistent-branch',
@@ -582,7 +582,7 @@ def test_send_pull_request_git_push_failure(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=repo_path,
pr_type='ready',
)
@@ -642,7 +642,7 @@ def test_send_pull_request_permission_error(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=repo_path,
pr_type='ready',
)
@@ -762,7 +762,7 @@ def test_process_single_pr_update(
resolver_output,
token,
username,
Platform.GITLAB,
ProviderType.GITLAB,
pr_type,
mock_llm_config,
None,
@@ -781,7 +781,7 @@ def test_process_single_pr_update(
issue=resolver_output.issue,
token=token,
username=username,
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=f'{mock_output_dir}/patches/pr_1',
additional_message='[Test success 1]',
llm_config=mock_llm_config,
@@ -805,7 +805,7 @@ def test_process_single_issue(
token = 'test_token'
username = 'test_user'
pr_type = 'draft'
platform = Platform.GITLAB
platform = ProviderType.GITLAB
resolver_output = ResolverOutput(
issue=Issue(
@@ -914,7 +914,7 @@ def test_process_single_issue_unsuccessful(
resolver_output,
token,
username,
Platform.GITLAB,
ProviderType.GITLAB,
pr_type,
mock_llm_config,
None,
@@ -1006,7 +1006,7 @@ def test_process_all_successful_issues(
'output_dir',
'token',
'username',
Platform.GITLAB,
ProviderType.GITLAB,
'draft',
mock_llm_config, # llm_config
None, # fork_owner
@@ -1023,7 +1023,7 @@ def test_process_all_successful_issues(
resolver_output_1,
'token',
'username',
Platform.GITLAB,
ProviderType.GITLAB,
'draft',
mock_llm_config,
None,
@@ -1038,7 +1038,7 @@ def test_process_all_successful_issues(
resolver_output_3,
'token',
'username',
Platform.GITLAB,
ProviderType.GITLAB,
'draft',
mock_llm_config,
None,
@@ -1081,7 +1081,7 @@ def test_send_pull_request_branch_naming(
issue=mock_issue,
token='test-token',
username='test-user',
platform=Platform.GITLAB,
platform=ProviderType.GITLAB,
patch_dir=repo_path,
pr_type='branch',
)
@@ -1166,7 +1166,7 @@ def test_main(
mock_resolver_output = MagicMock()
mock_load_single_resolver_output.return_value = mock_resolver_output
mock_identify_token.return_value = Platform.GITLAB
mock_identify_token.return_value = ProviderType.GITLAB
# Run main function
main()
@@ -1185,7 +1185,7 @@ def test_main(
mock_resolver_output,
'mock_token',
'mock_username',
Platform.GITLAB,
ProviderType.GITLAB,
'draft',
llm_config,
None,
@@ -1209,7 +1209,7 @@ def test_main(
'/mock/output',
'mock_token',
'mock_username',
Platform.GITLAB,
ProviderType.GITLAB,
'draft',
llm_config,
None,
@@ -1222,8 +1222,11 @@ def test_main(
main()
# Test for invalid token
mock_identify_token.return_value = Platform.INVALID
with pytest.raises(ValueError, match='Token is invalid.'):
mock_args.issue_number = '42' # Reset to valid issue number
mock_getenv.side_effect = (
lambda key, default=None: None
) # Return None for all env vars
with pytest.raises(ValueError, match='token is not set'):
main()
+16 -61
View File
@@ -5,9 +5,6 @@ import pytest
from litellm import ChatCompletionMessageToolCall
from openhands.agenthub.codeact_agent.codeact_agent import CodeActAgent
from openhands.agenthub.codeact_agent.function_calling import (
get_tools as codeact_get_tools,
)
from openhands.agenthub.codeact_agent.function_calling import (
response_to_actions as codeact_response_to_actions,
)
@@ -24,9 +21,6 @@ from openhands.agenthub.codeact_agent.tools.browser import (
_BROWSER_DESCRIPTION,
_BROWSER_TOOL_DESCRIPTION,
)
from openhands.agenthub.readonly_agent.function_calling import (
get_tools as readonly_get_tools,
)
from openhands.agenthub.readonly_agent.function_calling import (
response_to_actions as readonly_response_to_actions,
)
@@ -72,6 +66,22 @@ def agent(agent_class) -> Union[CodeActAgent, ReadOnlyAgent]:
return agent
def test_agent_with_default_config_has_default_tools():
config = AgentConfig()
codeact_agent = CodeActAgent(llm=LLM(LLMConfig()), config=config)
assert len(codeact_agent.tools) > 0
default_tool_names = [tool['function']['name'] for tool in codeact_agent.tools]
assert {
'browser',
'execute_bash',
'execute_ipython_cell',
'finish',
'str_replace_editor',
'think',
'web_read',
}.issubset(default_tool_names)
@pytest.fixture
def mock_state() -> State:
state = Mock(spec=State)
@@ -106,61 +116,6 @@ def test_step_with_pending_actions(agent):
assert len(agent.pending_actions) == 0
def test_codeact_get_tools_default():
tools = codeact_get_tools(
enable_jupyter=True,
enable_llm_editor=True,
enable_browsing=True,
)
assert len(tools) > 0
# Check required tools are present
tool_names = [tool['function']['name'] for tool in tools]
assert 'execute_bash' in tool_names
assert 'execute_ipython_cell' in tool_names
assert 'edit_file' in tool_names
assert 'web_read' in tool_names
def test_readonly_get_tools_default():
tools = readonly_get_tools()
assert len(tools) > 0
# Check required tools are present
tool_names = [tool['function']['name'] for tool in tools]
assert 'execute_bash' not in tool_names
assert 'execute_ipython_cell' not in tool_names
assert 'edit_file' not in tool_names
assert 'web_read' in tool_names
assert 'grep' in tool_names
assert 'glob' in tool_names
assert 'think' in tool_names
def test_codeact_get_tools_with_options():
# Test with all options enabled
tools = codeact_get_tools(
enable_browsing=True,
enable_jupyter=True,
enable_llm_editor=True,
)
tool_names = [tool['function']['name'] for tool in tools]
assert 'browser' in tool_names
assert 'execute_ipython_cell' in tool_names
assert 'edit_file' in tool_names
# Test with all options disabled
tools = codeact_get_tools(
enable_browsing=False,
enable_jupyter=False,
enable_llm_editor=False,
)
tool_names = [tool['function']['name'] for tool in tools]
assert 'browser' not in tool_names
assert 'execute_ipython_cell' not in tool_names
assert 'edit_file' not in tool_names
def test_cmd_run_tool():
CmdRunTool = create_cmd_run_tool()
assert CmdRunTool['type'] == 'function'