mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f1a90fdf71 | |||
| f0297c819a | |||
| 30c714147c | |||
| 733f5172b7 | |||
| 3f9b8a78fa | |||
| d5f017483a | |||
| 1fb9a194a0 | |||
| 43698329bb | |||
| fe14089feb | |||
| 52d881c98d | |||
| 6ac23aea80 | |||
| 0412949018 | |||
| 5b5adc5c7b | |||
| 0b40f6fac8 | |||
| 44d488b718 | |||
| 4f9120ffc6 | |||
| 50426edaa1 | |||
| a792f84a83 | |||
| cd9d96766c | |||
| 14564b25d6 | |||
| 0637b5b912 | |||
| 20bf48b693 | |||
| 5b3270be2d | |||
| ae43744052 |
@@ -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'
|
||||
|
||||
|
||||
@@ -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
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",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Generated
+108
-15
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
+10
@@ -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,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
export type OpenHandsEventType =
|
||||
| "message"
|
||||
| "system"
|
||||
| "agent_state_changed"
|
||||
| "run"
|
||||
| "read"
|
||||
|
||||
Vendored
+2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }})`
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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()
|
||||
+544
-532
File diff suppressed because it is too large
Load Diff
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user