use-computer 0.0.27__tar.gz → 0.0.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {use_computer-0.0.27 → use_computer-0.0.28}/PKG-INFO +32 -1
- {use_computer-0.0.27 → use_computer-0.0.28}/README.md +8 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/pyproject.toml +27 -1
- use_computer-0.0.28/use_computer/agents/__init__.py +58 -0
- use_computer-0.0.28/use_computer/agents/base/__init__.py +31 -0
- use_computer-0.0.28/use_computer/agents/base/actions.py +234 -0
- use_computer-0.0.28/use_computer/agents/base/agent.py +263 -0
- use_computer-0.0.28/use_computer/agents/base/artifacts.py +90 -0
- use_computer-0.0.28/use_computer/agents/base/desktop.py +126 -0
- use_computer-0.0.28/use_computer/agents/base/final_state.py +138 -0
- use_computer-0.0.28/use_computer/agents/base/prompts.py +44 -0
- use_computer-0.0.28/use_computer/agents/base/recordings.py +48 -0
- use_computer-0.0.28/use_computer/agents/base/sandbox.py +11 -0
- use_computer-0.0.28/use_computer/agents/base/screenshots.py +52 -0
- use_computer-0.0.28/use_computer/agents/base/trajectory.py +34 -0
- use_computer-0.0.28/use_computer/agents/core.py +65 -0
- use_computer-0.0.28/use_computer/agents/debug/README.md +10 -0
- use_computer-0.0.28/use_computer/agents/debug/__init__.py +5 -0
- use_computer-0.0.28/use_computer/agents/debug/actions.py +147 -0
- use_computer-0.0.28/use_computer/agents/debug/agent.py +263 -0
- use_computer-0.0.28/use_computer/agents/debug/models.py +88 -0
- use_computer-0.0.28/use_computer/agents/ios/__init__.py +1 -0
- use_computer-0.0.28/use_computer/agents/ios/agent.py +341 -0
- use_computer-0.0.28/use_computer/agents/ios/tools.py +367 -0
- use_computer-0.0.28/use_computer/agents/prompts/anthropic.txt +7 -0
- use_computer-0.0.28/use_computer/agents/prompts/gemini.txt +6 -0
- use_computer-0.0.28/use_computer/agents/prompts/ios.txt +49 -0
- use_computer-0.0.28/use_computer/agents/prompts/openai.txt +7 -0
- use_computer-0.0.28/use_computer/agents/prompts/pyautogui.txt +50 -0
- use_computer-0.0.28/use_computer/agents/providers/__init__.py +1 -0
- use_computer-0.0.28/use_computer/agents/providers/anthropic.py +290 -0
- use_computer-0.0.28/use_computer/agents/providers/gemini.py +402 -0
- use_computer-0.0.28/use_computer/agents/providers/generic.py +627 -0
- use_computer-0.0.28/use_computer/agents/providers/openai.py +319 -0
- use_computer-0.0.28/use_computer/agents/types.py +192 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/client.py +1 -1
- use_computer-0.0.28/use_computer/harbor/__init__.py +1 -0
- use_computer-0.0.28/use_computer/harbor/agents.py +138 -0
- use_computer-0.0.28/use_computer/harbor/environment/__init__.py +17 -0
- use_computer-0.0.28/use_computer/harbor/environment/gateway.py +25 -0
- use_computer-0.0.28/use_computer/harbor/environment/platforms/__init__.py +205 -0
- use_computer-0.0.28/use_computer/harbor/environment/platforms/ios.py +105 -0
- use_computer-0.0.28/use_computer/harbor/environment/platforms/macos.py +293 -0
- use_computer-0.0.28/use_computer/harbor/environment/platforms/ubuntu.py +149 -0
- use_computer-0.0.28/use_computer/harbor/environment/platforms/windows.py +207 -0
- use_computer-0.0.28/use_computer/harbor/environment/setup_files.py +55 -0
- use_computer-0.0.28/use_computer/harbor/environment/use_computer.py +236 -0
- use_computer-0.0.28/use_computer/harbor/environment/utils/__init__.py +15 -0
- use_computer-0.0.28/use_computer/harbor/environment/utils/verifier.py +36 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/parsers.py +2 -3
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_ios.sh +2 -2
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_ubuntu.sh +0 -1
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_windows.sh +0 -1
- use_computer-0.0.28/uv.lock +5529 -0
- use_computer-0.0.27/uv.lock +0 -941
- {use_computer-0.0.27 → use_computer-0.0.28}/.env.example +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/.gitignore +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/.pre-commit-config.yaml +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/__init__.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/accessibility.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/ax_transpile.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/display.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/errors.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/ios/__init__.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/ios/apps.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/ios/environment.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/ios/input.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/macos/__init__.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/macos/keyboard.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/macos/mouse.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/models.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/py.typed +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/recording.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/resources/__init__.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/resources/accessibility.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/resources/display.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/resources/recording.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/resources/screenshot.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/retry.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/sandbox.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/screenshot.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/simulators.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/__init__.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/pre_command.ps1 +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/pre_command.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/task.toml +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_macos.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_macos_check.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_ubuntu_check.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_ubuntu_nograder.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_windows_check.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/tasks/templates/test_windows_nograder.sh +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/windows/__init__.py +0 -0
- {use_computer-0.0.27 → use_computer-0.0.28}/use_computer/windows/shell.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: use-computer
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.28
|
|
4
4
|
Summary: Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu
|
|
5
5
|
Project-URL: Homepage, https://use.computer
|
|
6
6
|
Project-URL: Documentation, https://api.use.computer/docs
|
|
@@ -18,6 +18,29 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Typing :: Typed
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Requires-Dist: httpx>=0.27
|
|
21
|
+
Provides-Extra: agents
|
|
22
|
+
Requires-Dist: pillow>=10; extra == 'agents'
|
|
23
|
+
Provides-Extra: agents-all
|
|
24
|
+
Requires-Dist: anthropic>=0.86; extra == 'agents-all'
|
|
25
|
+
Requires-Dist: google-genai>=1; extra == 'agents-all'
|
|
26
|
+
Requires-Dist: litellm>=1; extra == 'agents-all'
|
|
27
|
+
Requires-Dist: openai>=1; extra == 'agents-all'
|
|
28
|
+
Requires-Dist: pillow>=10; extra == 'agents-all'
|
|
29
|
+
Provides-Extra: agents-anthropic
|
|
30
|
+
Requires-Dist: anthropic>=0.86; extra == 'agents-anthropic'
|
|
31
|
+
Requires-Dist: pillow>=10; extra == 'agents-anthropic'
|
|
32
|
+
Provides-Extra: agents-gemini
|
|
33
|
+
Requires-Dist: google-genai>=1; extra == 'agents-gemini'
|
|
34
|
+
Requires-Dist: pillow>=10; extra == 'agents-gemini'
|
|
35
|
+
Provides-Extra: agents-litellm
|
|
36
|
+
Requires-Dist: litellm>=1; extra == 'agents-litellm'
|
|
37
|
+
Requires-Dist: pillow>=10; extra == 'agents-litellm'
|
|
38
|
+
Provides-Extra: agents-openai
|
|
39
|
+
Requires-Dist: openai>=1; extra == 'agents-openai'
|
|
40
|
+
Requires-Dist: pillow>=10; extra == 'agents-openai'
|
|
41
|
+
Provides-Extra: harbor
|
|
42
|
+
Requires-Dist: pyyaml>=6; extra == 'harbor'
|
|
43
|
+
Requires-Dist: tomli>=2; extra == 'harbor'
|
|
21
44
|
Description-Content-Type: text/markdown
|
|
22
45
|
|
|
23
46
|
# use-computer Python SDK
|
|
@@ -29,6 +52,14 @@ pip install use-computer
|
|
|
29
52
|
export USE_COMPUTER_API_KEY=mk_live_...
|
|
30
53
|
```
|
|
31
54
|
|
|
55
|
+
Optional agent and Harbor integrations are installed explicitly:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install "use-computer[agents-openai]" # or agents-anthropic / agents-gemini / agents-litellm / agents-all
|
|
59
|
+
pip install "use-computer[harbor]" # SDK Harbor adapter deps only; install Harbor separately until it is published
|
|
60
|
+
pip install "use-computer[harbor,agents-all]" # Harbor adapter plus all provider deps
|
|
61
|
+
```
|
|
62
|
+
|
|
32
63
|
```python
|
|
33
64
|
from use_computer import Computer, SandboxType, SimulatorFamily
|
|
34
65
|
|
|
@@ -7,6 +7,14 @@ pip install use-computer
|
|
|
7
7
|
export USE_COMPUTER_API_KEY=mk_live_...
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
Optional agent and Harbor integrations are installed explicitly:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install "use-computer[agents-openai]" # or agents-anthropic / agents-gemini / agents-litellm / agents-all
|
|
14
|
+
pip install "use-computer[harbor]" # SDK Harbor adapter deps only; install Harbor separately until it is published
|
|
15
|
+
pip install "use-computer[harbor,agents-all]" # Harbor adapter plus all provider deps
|
|
16
|
+
```
|
|
17
|
+
|
|
10
18
|
```python
|
|
11
19
|
from use_computer import Computer, SandboxType, SimulatorFamily
|
|
12
20
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "use-computer"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.28"
|
|
8
8
|
description = "Python SDK for use.computer sandboxes across macOS, iOS simulators, Windows, and Ubuntu"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -23,11 +23,37 @@ classifiers = [
|
|
|
23
23
|
"Typing :: Typed",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
agents = ["Pillow>=10"]
|
|
28
|
+
agents-openai = ["Pillow>=10", "openai>=1"]
|
|
29
|
+
agents-anthropic = ["Pillow>=10", "anthropic>=0.86"]
|
|
30
|
+
agents-litellm = ["Pillow>=10", "litellm>=1"]
|
|
31
|
+
agents-gemini = ["Pillow>=10", "google-genai>=1"]
|
|
32
|
+
agents-all = [
|
|
33
|
+
"Pillow>=10",
|
|
34
|
+
"openai>=1",
|
|
35
|
+
"anthropic>=0.86",
|
|
36
|
+
"litellm>=1",
|
|
37
|
+
"google-genai>=1",
|
|
38
|
+
]
|
|
39
|
+
harbor = [
|
|
40
|
+
"PyYAML>=6",
|
|
41
|
+
"tomli>=2",
|
|
42
|
+
]
|
|
43
|
+
|
|
26
44
|
[dependency-groups]
|
|
27
45
|
dev = [
|
|
46
|
+
"anthropic>=0.86",
|
|
47
|
+
"google-genai>=1",
|
|
48
|
+
"harbor @ git+https://github.com/josancamon19/harbor.git@mirror ; python_version >= '3.12'",
|
|
49
|
+
"litellm>=1",
|
|
50
|
+
"openai>=1",
|
|
28
51
|
"pre-commit>=4",
|
|
29
52
|
"pytest>=9",
|
|
30
53
|
"ruff>=0.15",
|
|
54
|
+
"Pillow>=10",
|
|
55
|
+
"PyYAML>=6",
|
|
56
|
+
"tomli>=2",
|
|
31
57
|
"twine>=6",
|
|
32
58
|
"ty>=0.0.34",
|
|
33
59
|
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Optional computer-use agents.
|
|
2
|
+
|
|
3
|
+
Install extras for provider-backed agents:
|
|
4
|
+
use-computer[agents-openai], use-computer[agents-anthropic],
|
|
5
|
+
use-computer[agents-litellm], use-computer[agents-gemini], or
|
|
6
|
+
use-computer[agents-all].
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from importlib import import_module
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from use_computer.agents.core import AgentRunOptions, AgentRunResult, AgentUsage
|
|
15
|
+
|
|
16
|
+
_LAZY_EXPORTS = {
|
|
17
|
+
"AnthropicComputerAgent": (
|
|
18
|
+
"use_computer.agents.providers.anthropic",
|
|
19
|
+
"AnthropicComputerAgent",
|
|
20
|
+
),
|
|
21
|
+
"AnthropicCUAAgent": ("use_computer.agents.providers.anthropic", "AnthropicCUAAgent"),
|
|
22
|
+
"DebugComputerAgent": ("use_computer.agents.debug.agent", "DebugComputerAgent"),
|
|
23
|
+
"DebugCUAAgent": ("use_computer.agents.debug.agent", "DebugCUAAgent"),
|
|
24
|
+
"GeminiComputerAgent": ("use_computer.agents.providers.gemini", "GeminiComputerAgent"),
|
|
25
|
+
"GeminiCUAAgent": ("use_computer.agents.providers.gemini", "GeminiCUAAgent"),
|
|
26
|
+
"GenericComputerAgent": ("use_computer.agents.providers.generic", "GenericComputerAgent"),
|
|
27
|
+
"GenericCUAAgent": ("use_computer.agents.providers.generic", "GenericCUAAgent"),
|
|
28
|
+
"IOSAgent": ("use_computer.agents.ios.agent", "IOSAgent"),
|
|
29
|
+
"IOSComputerAgent": ("use_computer.agents.ios.agent", "IOSComputerAgent"),
|
|
30
|
+
"OpenAIComputerAgent": ("use_computer.agents.providers.openai", "OpenAIComputerAgent"),
|
|
31
|
+
"OpenAICUAAgent": ("use_computer.agents.providers.openai", "OpenAICUAAgent"),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AgentRunOptions",
|
|
36
|
+
"AgentRunResult",
|
|
37
|
+
"AgentUsage",
|
|
38
|
+
*_LAZY_EXPORTS,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def __getattr__(name: str) -> Any:
|
|
43
|
+
target = _LAZY_EXPORTS.get(name)
|
|
44
|
+
if target is None:
|
|
45
|
+
raise AttributeError(name)
|
|
46
|
+
module_name, attr = target
|
|
47
|
+
try:
|
|
48
|
+
module = import_module(module_name)
|
|
49
|
+
except ModuleNotFoundError as exc:
|
|
50
|
+
raise ModuleNotFoundError(
|
|
51
|
+
f"{name} requires an optional agent extra. Install one of "
|
|
52
|
+
"`use-computer[agents]`, `use-computer[agents-openai]`, "
|
|
53
|
+
"`use-computer[agents-anthropic]`, `use-computer[agents-litellm]`, "
|
|
54
|
+
"`use-computer[agents-gemini]`, or `use-computer[agents-all]`."
|
|
55
|
+
) from exc
|
|
56
|
+
value = getattr(module, attr)
|
|
57
|
+
globals()[name] = value
|
|
58
|
+
return value
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Public base-agent surface for CUA agents."""
|
|
2
|
+
|
|
3
|
+
from use_computer.agents.base.actions import _annotate_click, execute_action
|
|
4
|
+
from use_computer.agents.base.agent import BaseCUAAgent
|
|
5
|
+
from use_computer.agents.base.desktop import (
|
|
6
|
+
DesktopProfile,
|
|
7
|
+
OpenAIComputerEnvironment,
|
|
8
|
+
desktop_profile,
|
|
9
|
+
)
|
|
10
|
+
from use_computer.agents.base.prompts import build_system_prompt, load_prompt
|
|
11
|
+
from use_computer.agents.base.sandbox import ComputerSandbox
|
|
12
|
+
from use_computer.agents.base.screenshots import (
|
|
13
|
+
scale_screenshot_for_model,
|
|
14
|
+
screenshot_cap_for_model,
|
|
15
|
+
)
|
|
16
|
+
from use_computer.agents.base.trajectory import write_trajectory
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BaseCUAAgent",
|
|
20
|
+
"ComputerSandbox",
|
|
21
|
+
"DesktopProfile",
|
|
22
|
+
"OpenAIComputerEnvironment",
|
|
23
|
+
"_annotate_click",
|
|
24
|
+
"build_system_prompt",
|
|
25
|
+
"desktop_profile",
|
|
26
|
+
"execute_action",
|
|
27
|
+
"load_prompt",
|
|
28
|
+
"scale_screenshot_for_model",
|
|
29
|
+
"screenshot_cap_for_model",
|
|
30
|
+
"write_trajectory",
|
|
31
|
+
]
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Desktop action execution for CUA agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import io
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from PIL import Image, ImageDraw
|
|
15
|
+
|
|
16
|
+
from use_computer.agents.base.desktop import normalize_key
|
|
17
|
+
from use_computer.agents.base.sandbox import ComputerSandbox
|
|
18
|
+
from use_computer.agents.types import DesktopAction, DesktopCommand, MouseButton
|
|
19
|
+
|
|
20
|
+
_log = logging.getLogger("use_computer.agent")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _extract_coords(
|
|
24
|
+
action: dict[str, Any],
|
|
25
|
+
) -> tuple[tuple[int, int] | None, tuple[int, int] | None]:
|
|
26
|
+
"""Pull (point, secondary_point) from any action shape we use."""
|
|
27
|
+
point: tuple[int, int] | None = None
|
|
28
|
+
secondary: tuple[int, int] | None = None
|
|
29
|
+
if "coordinate" in action and action["coordinate"]:
|
|
30
|
+
c = action["coordinate"]
|
|
31
|
+
point = (int(c[0]), int(c[1]))
|
|
32
|
+
if "start_coordinate" in action and action["start_coordinate"]:
|
|
33
|
+
s = action["start_coordinate"]
|
|
34
|
+
secondary = (int(s[0]), int(s[1]))
|
|
35
|
+
if point is None and "x" in action and "y" in action:
|
|
36
|
+
try:
|
|
37
|
+
point = (int(float(action["x"])), int(float(action["y"])))
|
|
38
|
+
except (TypeError, ValueError):
|
|
39
|
+
pass
|
|
40
|
+
if "to_x" in action and "to_y" in action:
|
|
41
|
+
try:
|
|
42
|
+
point = (int(float(action["to_x"])), int(float(action["to_y"])))
|
|
43
|
+
except (TypeError, ValueError):
|
|
44
|
+
pass
|
|
45
|
+
if "from_x" in action and "from_y" in action:
|
|
46
|
+
try:
|
|
47
|
+
secondary = (int(float(action["from_x"])), int(float(action["from_y"])))
|
|
48
|
+
except (TypeError, ValueError):
|
|
49
|
+
pass
|
|
50
|
+
return point, secondary
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _annotate_click(png_bytes: bytes, action: dict[str, Any]) -> bytes:
|
|
54
|
+
"""Draw a red marker at the click/move/scroll/drag coordinate."""
|
|
55
|
+
point, start = _extract_coords(action)
|
|
56
|
+
if point is None and start is None:
|
|
57
|
+
return png_bytes
|
|
58
|
+
try:
|
|
59
|
+
img = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
|
60
|
+
overlay = Image.new("RGBA", img.size, (0, 0, 0, 0))
|
|
61
|
+
draw = ImageDraw.Draw(overlay)
|
|
62
|
+
red = (255, 30, 30, 240)
|
|
63
|
+
radius = 5
|
|
64
|
+
if point and start:
|
|
65
|
+
draw.line([start, point], fill=red, width=2)
|
|
66
|
+
for px, py in (start, point):
|
|
67
|
+
draw.ellipse([px - radius, py - radius, px + radius, py + radius], fill=red)
|
|
68
|
+
else:
|
|
69
|
+
cx, cy = point or start # type: ignore
|
|
70
|
+
draw.ellipse([cx - radius, cy - radius, cx + radius, cy + radius], fill=red)
|
|
71
|
+
out = Image.alpha_composite(img, overlay).convert("RGB")
|
|
72
|
+
buf = io.BytesIO()
|
|
73
|
+
out.save(buf, format="PNG")
|
|
74
|
+
return buf.getvalue()
|
|
75
|
+
except Exception as e:
|
|
76
|
+
_log.warning(f"annotate_click failed: {e}")
|
|
77
|
+
return png_bytes
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def execute_action(
|
|
81
|
+
sandbox: ComputerSandbox,
|
|
82
|
+
action: dict[str, Any],
|
|
83
|
+
images_dir: Path,
|
|
84
|
+
step_idx: int,
|
|
85
|
+
key_aliases: Mapping[str, str] | None = None,
|
|
86
|
+
) -> tuple[str, bytes | None, str | None]:
|
|
87
|
+
"""Execute a computer-use action; returns (result_text, ss_bytes, ss_path)."""
|
|
88
|
+
command = DesktopCommand.from_mapping(action)
|
|
89
|
+
img_name = f"step_{step_idx:03d}.png"
|
|
90
|
+
t0 = time.monotonic()
|
|
91
|
+
|
|
92
|
+
if command.action is DesktopAction.SCREENSHOT:
|
|
93
|
+
ss = await _take_screenshot_with_recovery(sandbox, command.label)
|
|
94
|
+
if ss is None:
|
|
95
|
+
return "Screenshot failed after retries", None, None
|
|
96
|
+
(images_dir / img_name).write_bytes(ss)
|
|
97
|
+
_log.info(f"action screenshot: {time.monotonic() - t0:.2f}s, {len(ss)} bytes")
|
|
98
|
+
return "Screenshot taken", ss, f"images/{img_name}"
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
if command.action in {DesktopAction.LEFT_CLICK, DesktopAction.CLICK}:
|
|
102
|
+
await sandbox.mouse.click(
|
|
103
|
+
command.point.x,
|
|
104
|
+
command.point.y,
|
|
105
|
+
button=MouseButton.LEFT.value,
|
|
106
|
+
)
|
|
107
|
+
elif command.action is DesktopAction.RIGHT_CLICK:
|
|
108
|
+
await sandbox.mouse.click(
|
|
109
|
+
command.point.x,
|
|
110
|
+
command.point.y,
|
|
111
|
+
button=MouseButton.RIGHT.value,
|
|
112
|
+
)
|
|
113
|
+
elif command.action is DesktopAction.DOUBLE_CLICK:
|
|
114
|
+
await sandbox.mouse.click(
|
|
115
|
+
command.point.x,
|
|
116
|
+
command.point.y,
|
|
117
|
+
button=MouseButton.LEFT.value,
|
|
118
|
+
double=True,
|
|
119
|
+
)
|
|
120
|
+
elif command.action is DesktopAction.TRIPLE_CLICK:
|
|
121
|
+
await sandbox.mouse.click(
|
|
122
|
+
command.point.x,
|
|
123
|
+
command.point.y,
|
|
124
|
+
button=MouseButton.LEFT.value,
|
|
125
|
+
double=True,
|
|
126
|
+
)
|
|
127
|
+
await asyncio.sleep(0.05)
|
|
128
|
+
await sandbox.mouse.click(
|
|
129
|
+
command.point.x,
|
|
130
|
+
command.point.y,
|
|
131
|
+
button=MouseButton.LEFT.value,
|
|
132
|
+
)
|
|
133
|
+
elif command.action is DesktopAction.MIDDLE_CLICK:
|
|
134
|
+
await sandbox.mouse.click(
|
|
135
|
+
command.point.x,
|
|
136
|
+
command.point.y,
|
|
137
|
+
button=MouseButton.MIDDLE.value,
|
|
138
|
+
)
|
|
139
|
+
elif command.action is DesktopAction.TYPE:
|
|
140
|
+
await sandbox.keyboard.type(command.text)
|
|
141
|
+
elif command.action is DesktopAction.KEY:
|
|
142
|
+
key = normalize_key(command.key or command.text, key_aliases)
|
|
143
|
+
if "+" in key:
|
|
144
|
+
await sandbox.keyboard.hotkey(key)
|
|
145
|
+
else:
|
|
146
|
+
await sandbox.keyboard.press(key)
|
|
147
|
+
elif command.action is DesktopAction.SCROLL:
|
|
148
|
+
await sandbox.mouse.scroll(
|
|
149
|
+
command.point.x,
|
|
150
|
+
command.point.y,
|
|
151
|
+
command.direction.value,
|
|
152
|
+
command.amount,
|
|
153
|
+
)
|
|
154
|
+
elif command.action is DesktopAction.MOVE:
|
|
155
|
+
await sandbox.mouse.move(command.point.x, command.point.y)
|
|
156
|
+
elif command.action is DesktopAction.DRAG:
|
|
157
|
+
await sandbox.mouse.drag(
|
|
158
|
+
command.start_point.x,
|
|
159
|
+
command.start_point.y,
|
|
160
|
+
command.point.x,
|
|
161
|
+
command.point.y,
|
|
162
|
+
)
|
|
163
|
+
elif command.action is DesktopAction.WAIT:
|
|
164
|
+
await asyncio.sleep(command.duration)
|
|
165
|
+
else:
|
|
166
|
+
return f"Unknown action: {command.label}", None, None
|
|
167
|
+
except httpx.HTTPStatusError as e:
|
|
168
|
+
detail = e.response.text.strip().replace("\n", " ")[:240]
|
|
169
|
+
msg = f"Action '{command.label}' failed: HTTP {e.response.status_code} {detail}"
|
|
170
|
+
_log.warning(msg)
|
|
171
|
+
ss = await _take_screenshot_with_recovery(sandbox, command.label)
|
|
172
|
+
if ss is None:
|
|
173
|
+
return msg, None, None
|
|
174
|
+
(images_dir / img_name).write_bytes(ss)
|
|
175
|
+
return msg, ss, f"images/{img_name}"
|
|
176
|
+
except httpx.HTTPError as e:
|
|
177
|
+
msg = f"Action '{command.label}' failed: {type(e).__name__}: {e}"
|
|
178
|
+
_log.warning(msg)
|
|
179
|
+
ss = await _take_screenshot_with_recovery(sandbox, command.label)
|
|
180
|
+
if ss is None:
|
|
181
|
+
return msg, None, None
|
|
182
|
+
(images_dir / img_name).write_bytes(ss)
|
|
183
|
+
return msg, ss, f"images/{img_name}"
|
|
184
|
+
|
|
185
|
+
action_elapsed = time.monotonic() - t0
|
|
186
|
+
await asyncio.sleep(2)
|
|
187
|
+
ss = await _take_screenshot_with_recovery(sandbox, command.label)
|
|
188
|
+
if ss is None:
|
|
189
|
+
return (
|
|
190
|
+
f"Action '{command.label}' executed; screenshot failed after retries",
|
|
191
|
+
None,
|
|
192
|
+
None,
|
|
193
|
+
)
|
|
194
|
+
annotated = _annotate_click(ss, action)
|
|
195
|
+
(images_dir / img_name).write_bytes(annotated)
|
|
196
|
+
total_elapsed = time.monotonic() - t0
|
|
197
|
+
screenshot_elapsed = total_elapsed - action_elapsed
|
|
198
|
+
_log.info(
|
|
199
|
+
f"action {command.label}: {action_elapsed:.2f}s + "
|
|
200
|
+
f"screenshot {screenshot_elapsed:.2f}s = {total_elapsed:.2f}s total"
|
|
201
|
+
)
|
|
202
|
+
return f"Action '{command.label}' executed", ss, f"images/{img_name}"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
async def _take_screenshot_with_recovery(
|
|
206
|
+
sandbox: ComputerSandbox, action_type: str
|
|
207
|
+
) -> bytes | None:
|
|
208
|
+
attempts = 4
|
|
209
|
+
for attempt in range(1, attempts + 1):
|
|
210
|
+
try:
|
|
211
|
+
return await sandbox.screenshot.take_full_screen()
|
|
212
|
+
except httpx.HTTPStatusError as e:
|
|
213
|
+
if attempt == attempts:
|
|
214
|
+
_log.warning(
|
|
215
|
+
f"screenshot after {action_type} failed after retries: "
|
|
216
|
+
f"HTTP {e.response.status_code} {e.response.text[:160]}"
|
|
217
|
+
)
|
|
218
|
+
return None
|
|
219
|
+
_log.warning(
|
|
220
|
+
f"screenshot after {action_type} failed: HTTP {e.response.status_code}; "
|
|
221
|
+
f"retrying in 10s ({attempt}/{attempts - 1})"
|
|
222
|
+
)
|
|
223
|
+
await asyncio.sleep(10)
|
|
224
|
+
except httpx.HTTPError as e:
|
|
225
|
+
if attempt == attempts:
|
|
226
|
+
_log.warning(
|
|
227
|
+
f"screenshot after {action_type} failed after retries: {type(e).__name__}: {e}"
|
|
228
|
+
)
|
|
229
|
+
return None
|
|
230
|
+
_log.warning(
|
|
231
|
+
f"screenshot after {action_type} failed: {type(e).__name__}; "
|
|
232
|
+
f"retrying in 10s ({attempt}/{attempts - 1})"
|
|
233
|
+
)
|
|
234
|
+
await asyncio.sleep(10)
|