use-computer 0.0.41__tar.gz → 0.0.43__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.
Files changed (106) hide show
  1. {use_computer-0.0.41 → use_computer-0.0.43}/.env.example +1 -1
  2. {use_computer-0.0.41 → use_computer-0.0.43}/PKG-INFO +25 -14
  3. {use_computer-0.0.41 → use_computer-0.0.43}/README.md +24 -13
  4. {use_computer-0.0.41 → use_computer-0.0.43}/pyproject.toml +1 -1
  5. {use_computer-0.0.41 → use_computer-0.0.43}/scripts/smoke_wheel.py +3 -4
  6. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/__init__.py +18 -7
  7. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/__init__.py +2 -0
  8. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/agent.py +14 -6
  9. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/final_state.py +1 -1
  10. use_computer-0.0.43/use_computer/agents/base/recordings.py +253 -0
  11. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/sandbox.py +1 -1
  12. use_computer-0.0.43/use_computer/agents/base/task_setup.py +277 -0
  13. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/core.py +1 -1
  14. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/actions.py +1 -1
  15. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/agent.py +1 -1
  16. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/ios/agent.py +54 -4
  17. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/ios/tools.py +2 -1
  18. use_computer-0.0.43/use_computer/agents/prompts/osworld_pyautogui.txt +18 -0
  19. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/gemini.py +1 -1
  20. use_computer-0.0.43/use_computer/agents/providers/osworld.py +229 -0
  21. use_computer-0.0.43/use_computer/automation/__init__.py +28 -0
  22. use_computer-0.0.43/use_computer/automation/osworld.py +466 -0
  23. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/automation}/parsers.py +2 -2
  24. use_computer-0.0.43/use_computer/core/__init__.py +64 -0
  25. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/client.py +149 -29
  26. use_computer-0.0.43/use_computer/core/errors.py +103 -0
  27. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/sandbox.py +40 -10
  28. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/harbor/agents.py +12 -0
  29. use_computer-0.0.43/use_computer/ios/__init__.py +33 -0
  30. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/ios/apps.py +1 -1
  31. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/ios/environment.py +1 -1
  32. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/ios/input.py +1 -1
  33. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/macos/keyboard.py +1 -1
  34. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/macos/mouse.py +1 -1
  35. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/accessibility.py +1 -1
  36. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/display.py +1 -1
  37. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/recording.py +1 -1
  38. {use_computer-0.0.41/use_computer/harbor/environment → use_computer-0.0.43/use_computer}/setup_files.py +1 -1
  39. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/__init__.py +3 -3
  40. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/windows/shell.py +1 -1
  41. use_computer-0.0.41/use_computer/accessibility.py +0 -3
  42. use_computer-0.0.41/use_computer/agents/base/recordings.py +0 -62
  43. use_computer-0.0.41/use_computer/display.py +0 -3
  44. use_computer-0.0.41/use_computer/errors.py +0 -74
  45. use_computer-0.0.41/use_computer/harbor/environment/__init__.py +0 -17
  46. use_computer-0.0.41/use_computer/harbor/environment/gateway.py +0 -25
  47. use_computer-0.0.41/use_computer/harbor/environment/platforms/__init__.py +0 -216
  48. use_computer-0.0.41/use_computer/harbor/environment/platforms/ios.py +0 -105
  49. use_computer-0.0.41/use_computer/harbor/environment/platforms/macos.py +0 -293
  50. use_computer-0.0.41/use_computer/harbor/environment/platforms/ubuntu.py +0 -175
  51. use_computer-0.0.41/use_computer/harbor/environment/platforms/windows.py +0 -245
  52. use_computer-0.0.41/use_computer/harbor/environment/use_computer.py +0 -238
  53. use_computer-0.0.41/use_computer/harbor/environment/utils/__init__.py +0 -15
  54. use_computer-0.0.41/use_computer/harbor/environment/utils/verifier.py +0 -36
  55. use_computer-0.0.41/use_computer/ios/__init__.py +0 -14
  56. use_computer-0.0.41/use_computer/recording.py +0 -3
  57. use_computer-0.0.41/use_computer/screenshot.py +0 -3
  58. {use_computer-0.0.41 → use_computer-0.0.43}/.gitignore +0 -0
  59. {use_computer-0.0.41 → use_computer-0.0.43}/.pre-commit-config.yaml +0 -0
  60. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/__init__.py +0 -0
  61. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/actions.py +0 -0
  62. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/artifacts.py +0 -0
  63. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/desktop.py +0 -0
  64. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/prompts.py +0 -0
  65. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/screenshots.py +0 -0
  66. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/base/trajectory.py +0 -0
  67. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/README.md +0 -0
  68. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/__init__.py +0 -0
  69. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/debug/models.py +0 -0
  70. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/ios/__init__.py +0 -0
  71. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/anthropic.txt +0 -0
  72. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/gemini.txt +0 -0
  73. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/ios.txt +0 -0
  74. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/openai.txt +0 -0
  75. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/prompts/pyautogui.txt +0 -0
  76. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/__init__.py +0 -0
  77. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/anthropic.py +0 -0
  78. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/generic.py +0 -0
  79. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/openai.py +0 -0
  80. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/providers/tinker_backend.py +0 -0
  81. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/agents/types.py +0 -0
  82. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/automation}/ax_transpile.py +0 -0
  83. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/models.py +0 -0
  84. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/core}/retry.py +0 -0
  85. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/harbor/__init__.py +0 -0
  86. {use_computer-0.0.41/use_computer → use_computer-0.0.43/use_computer/ios}/simulators.py +0 -0
  87. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/macos/__init__.py +0 -0
  88. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/py.typed +0 -0
  89. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/__init__.py +0 -0
  90. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/resources/screenshot.py +0 -0
  91. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/pre_command.ps1 +0 -0
  92. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/pre_command.sh +0 -0
  93. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/task.toml +0 -0
  94. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ios.sh +0 -0
  95. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
  96. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_macos.sh +0 -0
  97. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_macos_check.sh +0 -0
  98. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
  99. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ubuntu.sh +0 -0
  100. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ubuntu_check.sh +0 -0
  101. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_ubuntu_nograder.sh +0 -0
  102. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_windows.sh +0 -0
  103. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_windows_check.sh +0 -0
  104. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/tasks/templates/test_windows_nograder.sh +0 -0
  105. {use_computer-0.0.41 → use_computer-0.0.43}/use_computer/windows/__init__.py +0 -0
  106. {use_computer-0.0.41 → use_computer-0.0.43}/uv.lock +0 -0
@@ -1,5 +1,5 @@
1
1
  # SDK runtime
2
- USE_COMPUTER_API_KEY=mk_live_...
2
+ USE_COMPUTER_API_KEY=uc_live_...
3
3
  USE_COMPUTER_BASE_URL=https://api.use.computer
4
4
 
5
5
  # Local PyPI publish only. Prefer CI Trusted Publishing when possible.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: use-computer
3
- Version: 0.0.41
3
+ Version: 0.0.43
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
@@ -38,31 +38,31 @@ Python client for [use.computer](https://use.computer) — rent dedicated VMs ac
38
38
 
39
39
  ```bash
40
40
  pip install use-computer
41
- export USE_COMPUTER_API_KEY=mk_live_...
41
+ export USE_COMPUTER_API_KEY=uc_live_...
42
42
  ```
43
43
 
44
44
  Optional agent and Harbor integrations are installed explicitly:
45
45
 
46
46
  ```bash
47
47
  pip install "use-computer[agents]" # computer-use agents and provider SDKs
48
- pip install "use-computer[harbor]" # Harbor environment adapter (Python 3.12+)
49
- pip install "use-computer[harbor,agents]" # Harbor adapter plus agents
48
+ pip install "use-computer[harbor]" # Harbor agent wrapper dependencies
49
+ pip install "use-computer[harbor,agents]" # Harbor agent wrappers plus agents
50
50
  ```
51
51
 
52
52
  Base installs only the SDK client and `httpx`. The `agents` extra installs the
53
- agent runtime plus model-provider dependencies; `harbor` installs Harbor plus
54
- the environment adapter dependencies.
53
+ agent runtime plus model-provider dependencies; `harbor` installs Harbor for
54
+ `use_computer.harbor.agents`.
55
55
 
56
56
  ```python
57
57
  from use_computer.agents import AnthropicComputerAgent
58
- from use_computer.harbor.environment import UseComputerEnvironment
59
58
  ```
60
59
 
61
- In Harbor job YAML, use the SDK paths directly:
60
+ In Harbor job YAML, use Harbor's built-in use.computer environment and the SDK
61
+ agent wrappers:
62
62
 
63
63
  ```yaml
64
64
  environment:
65
- import_path: use_computer.harbor.environment:UseComputerEnvironment
65
+ type: use-computer
66
66
  kwargs:
67
67
  platform: macos
68
68
  agents:
@@ -73,7 +73,10 @@ agents:
73
73
  ```python
74
74
  from use_computer import Computer, SandboxType, SimulatorFamily
75
75
 
76
- with Computer().create() as mac:
76
+ client = Computer()
77
+ reservation = client.reserve(hours=24)
78
+
79
+ with client.create(type="macos", reservation_id=reservation.id) as mac:
77
80
  mac.exec_ssh("open -a TextEdit")
78
81
  mac.keyboard.type("hello")
79
82
  png = mac.screenshot.take_full_screen()
@@ -85,6 +88,10 @@ with Computer().create(type=SandboxType.IOS, family=SimulatorFamily.TV) as tv:
85
88
  with Computer().create(type=SandboxType.IOS) as ios:
86
89
  ios.input.long_press(120, 300, duration=1.0)
87
90
  print(ios.exec("simctl getenv $UDID HOME").stdout) # CoreSimulator exec, not SSH
91
+
92
+ # Only needed when the same account key has more than one active Mac reservation.
93
+ with Computer().create(type="macos", reservation_id="...") as mac:
94
+ print(mac.exec_ssh("uname -a").stdout)
88
95
  ```
89
96
 
90
97
  ### Windows (Beta)
@@ -148,12 +155,16 @@ that snapshot version.
148
155
  ```python
149
156
  client = Computer()
150
157
 
158
+ # Configure the desktop by hand over VNC, then snapshot it.
151
159
  with client.create(type="ubuntu", version="ubuntu-24.04") as ubuntu:
152
- ubuntu.run("google-chrome-stable --no-first-run https://example.com >/tmp/chrome.log 2>&1 &")
160
+ print("Set up the desktop here:", ubuntu.vnc_url)
161
+ # install apps, sign into accounts, pre-open the windows your agent starts from
162
+ input("Press Enter once the desktop is ready to snapshot...")
153
163
  snapshot = ubuntu.snapshot("chrome-seeded-ubuntu")
154
164
 
165
+ # New sandboxes boot from that exact state, no setup needed.
155
166
  with client.create(type="ubuntu", snapshot=snapshot.version) as seeded:
156
- print(seeded.run("pgrep -af 'chrome|chromium' | head").stdout)
167
+ print(seeded.vnc_url) # apps, logins, and open windows restored
157
168
  ```
158
169
 
159
170
  Use `client.snapshots("ubuntu")` or `client.snapshots("windows")` to list saved
@@ -198,7 +209,7 @@ visionOS yet. Use it for read-only flows for now.
198
209
  | [`examples/_9_seeding.py`](examples/_9_seeding.py) | typed setup: files, hosts, open URLs/apps |
199
210
  | [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded Ubuntu/Windows desktop state |
200
211
 
201
- For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook). Its Harbor job YAMLs use `use_computer.harbor.environment:UseComputerEnvironment` and `use_computer.harbor.agents:*` as the canonical import paths.
212
+ For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook). Harbor job YAMLs should use Harbor's `type: use-computer` environment plus `use_computer.harbor.agents:*` agent wrappers.
202
213
 
203
214
  ## Skill for AI coding assistants
204
215
 
@@ -206,4 +217,4 @@ Point your assistant at [`use-computer-cookbook/skills/SKILL.md`](https://github
206
217
 
207
218
  ## HTTP API
208
219
 
209
- Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer mk_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
220
+ Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer uc_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
@@ -4,31 +4,31 @@ Python client for [use.computer](https://use.computer) — rent dedicated VMs ac
4
4
 
5
5
  ```bash
6
6
  pip install use-computer
7
- export USE_COMPUTER_API_KEY=mk_live_...
7
+ export USE_COMPUTER_API_KEY=uc_live_...
8
8
  ```
9
9
 
10
10
  Optional agent and Harbor integrations are installed explicitly:
11
11
 
12
12
  ```bash
13
13
  pip install "use-computer[agents]" # computer-use agents and provider SDKs
14
- pip install "use-computer[harbor]" # Harbor environment adapter (Python 3.12+)
15
- pip install "use-computer[harbor,agents]" # Harbor adapter plus agents
14
+ pip install "use-computer[harbor]" # Harbor agent wrapper dependencies
15
+ pip install "use-computer[harbor,agents]" # Harbor agent wrappers plus agents
16
16
  ```
17
17
 
18
18
  Base installs only the SDK client and `httpx`. The `agents` extra installs the
19
- agent runtime plus model-provider dependencies; `harbor` installs Harbor plus
20
- the environment adapter dependencies.
19
+ agent runtime plus model-provider dependencies; `harbor` installs Harbor for
20
+ `use_computer.harbor.agents`.
21
21
 
22
22
  ```python
23
23
  from use_computer.agents import AnthropicComputerAgent
24
- from use_computer.harbor.environment import UseComputerEnvironment
25
24
  ```
26
25
 
27
- In Harbor job YAML, use the SDK paths directly:
26
+ In Harbor job YAML, use Harbor's built-in use.computer environment and the SDK
27
+ agent wrappers:
28
28
 
29
29
  ```yaml
30
30
  environment:
31
- import_path: use_computer.harbor.environment:UseComputerEnvironment
31
+ type: use-computer
32
32
  kwargs:
33
33
  platform: macos
34
34
  agents:
@@ -39,7 +39,10 @@ agents:
39
39
  ```python
40
40
  from use_computer import Computer, SandboxType, SimulatorFamily
41
41
 
42
- with Computer().create() as mac:
42
+ client = Computer()
43
+ reservation = client.reserve(hours=24)
44
+
45
+ with client.create(type="macos", reservation_id=reservation.id) as mac:
43
46
  mac.exec_ssh("open -a TextEdit")
44
47
  mac.keyboard.type("hello")
45
48
  png = mac.screenshot.take_full_screen()
@@ -51,6 +54,10 @@ with Computer().create(type=SandboxType.IOS, family=SimulatorFamily.TV) as tv:
51
54
  with Computer().create(type=SandboxType.IOS) as ios:
52
55
  ios.input.long_press(120, 300, duration=1.0)
53
56
  print(ios.exec("simctl getenv $UDID HOME").stdout) # CoreSimulator exec, not SSH
57
+
58
+ # Only needed when the same account key has more than one active Mac reservation.
59
+ with Computer().create(type="macos", reservation_id="...") as mac:
60
+ print(mac.exec_ssh("uname -a").stdout)
54
61
  ```
55
62
 
56
63
  ### Windows (Beta)
@@ -114,12 +121,16 @@ that snapshot version.
114
121
  ```python
115
122
  client = Computer()
116
123
 
124
+ # Configure the desktop by hand over VNC, then snapshot it.
117
125
  with client.create(type="ubuntu", version="ubuntu-24.04") as ubuntu:
118
- ubuntu.run("google-chrome-stable --no-first-run https://example.com >/tmp/chrome.log 2>&1 &")
126
+ print("Set up the desktop here:", ubuntu.vnc_url)
127
+ # install apps, sign into accounts, pre-open the windows your agent starts from
128
+ input("Press Enter once the desktop is ready to snapshot...")
119
129
  snapshot = ubuntu.snapshot("chrome-seeded-ubuntu")
120
130
 
131
+ # New sandboxes boot from that exact state, no setup needed.
121
132
  with client.create(type="ubuntu", snapshot=snapshot.version) as seeded:
122
- print(seeded.run("pgrep -af 'chrome|chromium' | head").stdout)
133
+ print(seeded.vnc_url) # apps, logins, and open windows restored
123
134
  ```
124
135
 
125
136
  Use `client.snapshots("ubuntu")` or `client.snapshots("windows")` to list saved
@@ -164,7 +175,7 @@ visionOS yet. Use it for read-only flows for now.
164
175
  | [`examples/_9_seeding.py`](examples/_9_seeding.py) | typed setup: files, hosts, open URLs/apps |
165
176
  | [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded Ubuntu/Windows desktop state |
166
177
 
167
- For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook). Its Harbor job YAMLs use `use_computer.harbor.environment:UseComputerEnvironment` and `use_computer.harbor.agents:*` as the canonical import paths.
178
+ For agent loops and evals: [use-computer-cookbook](https://github.com/josancamon19/use-computer-cookbook). Harbor job YAMLs should use Harbor's `type: use-computer` environment plus `use_computer.harbor.agents:*` agent wrappers.
168
179
 
169
180
  ## Skill for AI coding assistants
170
181
 
@@ -172,4 +183,4 @@ Point your assistant at [`use-computer-cookbook/skills/SKILL.md`](https://github
172
183
 
173
184
  ## HTTP API
174
185
 
175
- Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer mk_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
186
+ Every SDK method wraps `https://api.use.computer/v1/...` with `Authorization: Bearer uc_live_...`. Swagger: [api.use.computer/docs](https://api.use.computer/docs). OpenAPI spec: [api.use.computer/openapi.yaml](https://api.use.computer/openapi.yaml).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "use-computer"
7
- version = "0.0.41"
7
+ version = "0.0.43"
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"
@@ -45,7 +45,7 @@ from use_computer.agents import (
45
45
  OpenAIComputerAgent,
46
46
  )
47
47
  from use_computer.agents.debug import DebugComputerAgent
48
- from use_computer.models import ExecResult
48
+ from use_computer.core.models import ExecResult
49
49
 
50
50
  PNG_BYTES = (
51
51
  b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
@@ -116,14 +116,14 @@ import importlib.util
116
116
 
117
117
  for name in ["harbor", "yaml", "tomli"]:
118
118
  assert importlib.util.find_spec(name) is not None, name
119
- for path in ["use_computer.harbor", "use_computer.harbor.environment"]:
119
+ for path in ["use_computer.harbor"]:
120
120
  importlib.import_module(path)
121
121
  """
122
122
 
123
123
  HARBOR_AGENTS_SMOKE = r"""
124
124
  import importlib
125
125
 
126
- for path in ["use_computer.harbor.environment", "use_computer.harbor.agents"]:
126
+ for path in ["use_computer.harbor", "use_computer.harbor.agents"]:
127
127
  importlib.import_module(path)
128
128
  """
129
129
 
@@ -156,7 +156,6 @@ def _check_wheel_contents(wheel: Path) -> None:
156
156
  "use_computer/agents/prompts/ios.txt",
157
157
  "use_computer/agents/prompts/openai.txt",
158
158
  "use_computer/agents/prompts/pyautogui.txt",
159
- "use_computer/harbor/environment/use_computer.py",
160
159
  "use_computer/harbor/agents.py",
161
160
  }
162
161
  missing = sorted(required - names)
@@ -1,7 +1,13 @@
1
- from use_computer.client import AsyncComputer, Computer, RunStatus
2
- from use_computer.errors import PlatformNotSupportedError, UseComputerError
3
- from use_computer.ios.input import Button, Key, RemoteButton
4
- from use_computer.models import (
1
+ from use_computer.automation.osworld import AsyncOSWorldClient, OSWorldClient
2
+ from use_computer.automation.parsers import Action, parse_pyautogui, parse_xdotool
3
+ from use_computer.core.client import AsyncComputer, Computer, Reservation, RunStatus
4
+ from use_computer.core.errors import (
5
+ BillingRequiredError,
6
+ PlatformNotSupportedError,
7
+ ReservationRequiredError,
8
+ UseComputerError,
9
+ )
10
+ from use_computer.core.models import (
5
11
  AccessibilityTree,
6
12
  ActionResult,
7
13
  ActResult,
@@ -10,8 +16,7 @@ from use_computer.models import (
10
16
  ExecResult,
11
17
  RecordingInfo,
12
18
  )
13
- from use_computer.parsers import Action, parse_pyautogui, parse_xdotool
14
- from use_computer.sandbox import (
19
+ from use_computer.core.sandbox import (
15
20
  AsyncIOSSandbox,
16
21
  AsyncMacOSSandbox,
17
22
  AsyncSandbox,
@@ -26,7 +31,8 @@ from use_computer.sandbox import (
26
31
  UbuntuSandbox,
27
32
  WindowsSandbox,
28
33
  )
29
- from use_computer.simulators import (
34
+ from use_computer.ios.input import Button, Key, RemoteButton
35
+ from use_computer.ios.simulators import (
30
36
  SimulatorChoice,
31
37
  SimulatorFamily,
32
38
  family_for_device,
@@ -45,9 +51,11 @@ __all__ = [
45
51
  "AsyncIOSSandbox",
46
52
  "AsyncMacOSSandbox",
47
53
  "AsyncComputer",
54
+ "AsyncOSWorldClient",
48
55
  "AsyncSandbox",
49
56
  "AsyncUbuntuSandbox",
50
57
  "AsyncWindowsSandbox",
58
+ "BillingRequiredError",
51
59
  "Button",
52
60
  "Computer",
53
61
  "CursorPosition",
@@ -57,8 +65,11 @@ __all__ = [
57
65
  "IOSSandbox",
58
66
  "Key",
59
67
  "MacOSSandbox",
68
+ "OSWorldClient",
60
69
  "PlatformNotSupportedError",
61
70
  "RecordingInfo",
71
+ "Reservation",
72
+ "ReservationRequiredError",
62
73
  "RemoteButton",
63
74
  "RunStatus",
64
75
  "Sandbox",
@@ -26,6 +26,8 @@ _LAZY_EXPORTS = {
26
26
  "IOSComputerAgent": ("use_computer.agents.ios.agent", "IOSComputerAgent"),
27
27
  "OpenAIComputerAgent": ("use_computer.agents.providers.openai", "OpenAIComputerAgent"),
28
28
  "OpenAICUAAgent": ("use_computer.agents.providers.openai", "OpenAICUAAgent"),
29
+ "OSWorldAgent": ("use_computer.agents.providers.osworld", "OSWorldAgent"),
30
+ "OSWorldComputerAgent": ("use_computer.agents.providers.osworld", "OSWorldComputerAgent"),
29
31
  }
30
32
 
31
33
  __all__ = [
@@ -16,6 +16,7 @@ from use_computer.agents.base.desktop import (
16
16
  from use_computer.agents.base.final_state import FinalStateMixin
17
17
  from use_computer.agents.base.recordings import RecordingMixin
18
18
  from use_computer.agents.base.sandbox import ComputerSandbox
19
+ from use_computer.agents.base.task_setup import run_task_setup
19
20
  from use_computer.agents.base.trajectory import write_trajectory
20
21
  from use_computer.agents.core import (
21
22
  AgentRunOptions,
@@ -25,7 +26,7 @@ from use_computer.agents.core import (
25
26
  DirectSandboxEnvironment,
26
27
  )
27
28
  from use_computer.agents.types import Platform
28
- from use_computer.sandbox import AsyncSandbox
29
+ from use_computer.core.sandbox import AsyncSandbox
29
30
 
30
31
 
31
32
  def _task_dir_from_env(environment: Any) -> Path:
@@ -59,6 +60,8 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
59
60
  platform_notes: str | None = None,
60
61
  openai_environment: OpenAIComputerEnvironment | str | None = None,
61
62
  save_on_finish: bool | str = False,
63
+ recording_enabled: bool | str = True,
64
+ final_state_enabled: bool | str = True,
62
65
  **kwargs: Any,
63
66
  ):
64
67
  self.logs_dir = Path(logs_dir or "runs/agent").resolve()
@@ -78,6 +81,8 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
78
81
  self.desktop = self._desktop_profile_for(platform or Platform.MACOS)
79
82
  self.task_dir: Path | None = Path(task_dir) if task_dir else None
80
83
  self._recording_id: str | None = None
84
+ self.recording_enabled = _as_bool(recording_enabled)
85
+ self.final_state_enabled = _as_bool(final_state_enabled)
81
86
  self.sandbox: AsyncSandbox | None = None
82
87
  self.images_dir: Path = (self.logs_dir / "images").resolve()
83
88
  self.steps: list[dict[str, Any]] = []
@@ -120,7 +125,7 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
120
125
  return "1.0.0"
121
126
 
122
127
  async def setup(self, environment: Any) -> None:
123
- pass
128
+ await run_task_setup(environment, self.logger)
124
129
 
125
130
  async def run(
126
131
  self,
@@ -164,7 +169,8 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
164
169
  if self.task_dir is None:
165
170
  self.task_dir = _task_dir_from_env(environment)
166
171
  self.logger.info(f"task_dir={self.task_dir}")
167
- await self.start_recording(sandbox)
172
+ if self.recording_enabled:
173
+ await self.start_recording(sandbox)
168
174
  self.images_dir.mkdir(parents=True, exist_ok=True)
169
175
  self.steps = [{"step_id": 1, "source": "user", "message": ""}]
170
176
  self.total_in = 0
@@ -193,10 +199,12 @@ class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
193
199
  assert self.sandbox is not None
194
200
  if self.save_on_finish:
195
201
  await self._save_focused_app(self.sandbox)
196
- await self._save_final_ax_tree(self.sandbox)
197
- await self._log_final_state_metrics(self.sandbox)
202
+ if self.final_state_enabled:
203
+ await self._save_final_ax_tree(self.sandbox)
204
+ await self._log_final_state_metrics(self.sandbox)
198
205
  await self._capture_artifacts(context)
199
- await self.stop_recording(self.sandbox)
206
+ if self.recording_enabled:
207
+ await self.stop_recording(self.sandbox)
200
208
  self.checkpoint(context, model, agent_name)
201
209
 
202
210
  async def run_direct(
@@ -10,7 +10,7 @@ from typing import Any
10
10
  import httpx
11
11
  from PIL import Image
12
12
 
13
- from use_computer.sandbox import AsyncSandbox
13
+ from use_computer.core.sandbox import AsyncSandbox
14
14
 
15
15
 
16
16
  class FinalStateMixin:
@@ -0,0 +1,253 @@
1
+ """Recording helpers for CUA agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import shutil
8
+ import subprocess
9
+ import tempfile
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from use_computer.core.sandbox import AsyncSandbox
15
+
16
+ _OSWORLD_FFMPEG_RECORDING_ID = "__osworld_ffmpeg__"
17
+
18
+
19
+ class RecordingMixin:
20
+ async def start_recording(self: Any, sandbox: AsyncSandbox) -> None:
21
+ if getattr(getattr(sandbox, "type", None), "value", "") == "ios":
22
+ try:
23
+ await sandbox.screenshot.take_full_screen()
24
+ except Exception as e:
25
+ self.logger.warning(f"iOS recording warm-up screenshot failed: {e}")
26
+ try:
27
+ result = await sandbox.recording.start(name="trial")
28
+ self._recording_id = result.id
29
+ self.logger.info(f"Recording started: {self._recording_id}")
30
+ except Exception as e:
31
+ self.logger.warning(f"Failed to start recording: {e}")
32
+ if await self._start_osworld_ffmpeg_recording(sandbox):
33
+ return
34
+
35
+ async def stop_recording(self: Any, sandbox: AsyncSandbox) -> None:
36
+ if not self._recording_id:
37
+ return
38
+ await asyncio.sleep(3.0)
39
+ rec_path = self.logs_dir / "recording.mp4"
40
+ if self._recording_id == _OSWORLD_FFMPEG_RECORDING_ID:
41
+ await self._stop_osworld_ffmpeg_recording(sandbox, rec_path)
42
+ self._recording_id = None
43
+ return
44
+ try:
45
+ info = await sandbox.recording.stop(self._recording_id)
46
+ self.logger.info(
47
+ f"Recording stopped: id={self._recording_id} "
48
+ f"name={info.name!r} filename={info.filename!r} "
49
+ f"server_size={info.file_size}B "
50
+ f"({info.file_size / (1024 * 1024):.2f} MB)"
51
+ )
52
+ if info.file_size <= 0:
53
+ raise RuntimeError("server recording is empty")
54
+
55
+ await asyncio.sleep(2)
56
+
57
+ await self._download_recording(sandbox, rec_path)
58
+ except Exception as e:
59
+ self.logger.warning(f"Recording save failed: {type(e).__name__}: {e}")
60
+ try:
61
+ await self._download_recording(sandbox, rec_path)
62
+ except Exception as fallback:
63
+ self.logger.warning(
64
+ f"Recording fallback download failed: {type(fallback).__name__}: {fallback}"
65
+ )
66
+ self._write_screenshot_recording(
67
+ rec_path,
68
+ reason=(
69
+ f"server recording failed: {type(e).__name__}: {e}; "
70
+ f"download fallback failed: {type(fallback).__name__}: {fallback}"
71
+ ),
72
+ )
73
+
74
+ async def _download_recording(self: Any, sandbox: AsyncSandbox, rec_path) -> None:
75
+ self.logger.info(f"Downloading recording → {rec_path}")
76
+ t_dl = time.monotonic()
77
+ await sandbox.recording.download(self._recording_id, str(rec_path), timeout=180.0)
78
+ dl_elapsed = time.monotonic() - t_dl
79
+ local_size = rec_path.stat().st_size
80
+ if local_size <= 0:
81
+ raise RuntimeError("downloaded recording is empty")
82
+ self.logger.info(
83
+ f"Recording saved: {rec_path} "
84
+ f"local_size={local_size}B ({local_size / (1024 * 1024):.2f} MB) "
85
+ f"download_elapsed={dl_elapsed:.1f}s"
86
+ )
87
+
88
+ def _write_screenshot_recording(self: Any, rec_path: Path, reason: str) -> bool:
89
+ image_dir = self.logs_dir / "images"
90
+ images = sorted(image_dir.glob("*.png")) if image_dir.exists() else []
91
+ if not images:
92
+ self.logger.warning("Screenshot recording fallback skipped: no saved screenshots")
93
+ return False
94
+
95
+ ffmpeg = shutil.which("ffmpeg")
96
+ if not ffmpeg:
97
+ self.logger.warning("Screenshot recording fallback skipped: ffmpeg not found")
98
+ return False
99
+
100
+ try:
101
+ from PIL import Image
102
+ except Exception as e:
103
+ self.logger.warning(f"Screenshot recording fallback skipped: Pillow unavailable: {e}")
104
+ return False
105
+
106
+ rec_path.parent.mkdir(parents=True, exist_ok=True)
107
+ t0 = time.monotonic()
108
+ try:
109
+ with Image.open(images[0]) as first:
110
+ width, height = first.size
111
+ width -= width % 2
112
+ height -= height % 2
113
+ if width <= 0 or height <= 0:
114
+ raise RuntimeError(f"invalid screenshot size {width}x{height}")
115
+
116
+ with tempfile.TemporaryDirectory(prefix="use-computer-recording-") as tmp:
117
+ tmp_dir = Path(tmp)
118
+ frame_count = 0
119
+ for idx, src in enumerate(images, start=1):
120
+ with Image.open(src) as img:
121
+ frame = img.convert("RGB")
122
+ if frame.size != (width, height):
123
+ frame = frame.resize((width, height))
124
+ frame.save(tmp_dir / f"frame{idx:06d}.png")
125
+ frame_count += 1
126
+
127
+ if frame_count == 1:
128
+ shutil.copyfile(tmp_dir / "frame000001.png", tmp_dir / "frame000002.png")
129
+ frame_count = 2
130
+
131
+ cmd = [
132
+ ffmpeg,
133
+ "-y",
134
+ "-loglevel",
135
+ "error",
136
+ "-framerate",
137
+ "1",
138
+ "-i",
139
+ str(tmp_dir / "frame%06d.png"),
140
+ "-vf",
141
+ "format=yuv420p",
142
+ "-movflags",
143
+ "+faststart",
144
+ str(rec_path),
145
+ ]
146
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
147
+ if result.returncode != 0:
148
+ raise RuntimeError(
149
+ result.stderr.strip() or f"ffmpeg exited {result.returncode}"
150
+ )
151
+
152
+ local_size = rec_path.stat().st_size
153
+ if local_size <= 0:
154
+ raise RuntimeError("ffmpeg wrote an empty recording")
155
+
156
+ meta = {
157
+ "source": "screenshots",
158
+ "reason": reason,
159
+ "frames": len(images),
160
+ "width": width,
161
+ "height": height,
162
+ "file_size": local_size,
163
+ "elapsed_sec": round(time.monotonic() - t0, 3),
164
+ }
165
+ (self.logs_dir / "recording.json").write_text(json.dumps(meta, indent=2))
166
+ self.logger.info(
167
+ f"Screenshot recording fallback saved: {rec_path} "
168
+ f"frames={len(images)} local_size={local_size}B "
169
+ f"({local_size / (1024 * 1024):.2f} MB)"
170
+ )
171
+ return True
172
+ except Exception as e:
173
+ self.logger.warning(f"Screenshot recording fallback failed: {type(e).__name__}: {e}")
174
+ return False
175
+
176
+ async def _start_osworld_ffmpeg_recording(self: Any, sandbox: AsyncSandbox) -> bool:
177
+ osworld: Any = getattr(sandbox, "osworld", None)
178
+ if osworld is None:
179
+ return False
180
+ if getattr(getattr(sandbox, "type", None), "value", "") != "ubuntu":
181
+ self.logger.warning("OSWorld ffmpeg recording fallback is only implemented for Ubuntu")
182
+ return False
183
+
184
+ self.logs_dir.mkdir(parents=True, exist_ok=True)
185
+ script = """
186
+ set -eux
187
+ rm -f /tmp/osworld-recording.mp4 /tmp/osworld-recording.log /tmp/osworld-recording.pid
188
+ export DISPLAY=:0
189
+ nohup ffmpeg -y \
190
+ -video_size 1920x1080 \
191
+ -framerate 2 \
192
+ -f x11grab \
193
+ -i :0 \
194
+ -t 120 \
195
+ -pix_fmt yuv420p \
196
+ /tmp/osworld-recording.mp4 > /tmp/osworld-recording.log 2>&1 &
197
+ echo $! > /tmp/osworld-recording.pid
198
+ cat /tmp/osworld-recording.pid
199
+ """
200
+ try:
201
+ result = await osworld.execute(["bash", "-lc", script], timeout=10)
202
+ except Exception as e:
203
+ self.logger.warning(f"OSWorld ffmpeg recording start failed: {type(e).__name__}: {e}")
204
+ return False
205
+
206
+ (self.logs_dir / "recording-start.json").write_text(json.dumps(result, indent=2))
207
+ if result.get("returncode") != 0:
208
+ self.logger.warning(f"OSWorld ffmpeg recording start failed: {result}")
209
+ return False
210
+
211
+ self._recording_id = _OSWORLD_FFMPEG_RECORDING_ID
212
+ self.logger.info("OSWorld ffmpeg recording started")
213
+ return True
214
+
215
+ async def _stop_osworld_ffmpeg_recording(self: Any, sandbox: AsyncSandbox, rec_path) -> None:
216
+ osworld: Any = getattr(sandbox, "osworld", None)
217
+ if osworld is None:
218
+ return
219
+ stop_script = """
220
+ set -eux
221
+ if [ -f /tmp/osworld-recording.pid ]; then
222
+ pid="$(cat /tmp/osworld-recording.pid)"
223
+ if kill -0 "$pid" 2>/dev/null; then
224
+ kill -INT "$pid" || true
225
+ fi
226
+ for _ in $(seq 1 40); do
227
+ if kill -0 "$pid" 2>/dev/null; then
228
+ sleep 0.25
229
+ else
230
+ break
231
+ fi
232
+ done
233
+ fi
234
+ ls -lh /tmp/osworld-recording.mp4 /tmp/osworld-recording.log
235
+ tail -80 /tmp/osworld-recording.log
236
+ """
237
+ t_dl = time.monotonic()
238
+ try:
239
+ meta: dict[str, Any] = {
240
+ "stop": await osworld.execute(["bash", "-lc", stop_script], timeout=30)
241
+ }
242
+ data = await osworld.file("/tmp/osworld-recording.mp4")
243
+ rec_path.write_bytes(data)
244
+ local_size = rec_path.stat().st_size
245
+ meta["downloaded_size"] = local_size
246
+ (self.logs_dir / "recording.json").write_text(json.dumps(meta, indent=2))
247
+ self.logger.info(
248
+ f"OSWorld ffmpeg recording saved: {rec_path} "
249
+ f"local_size={local_size}B ({local_size / (1024 * 1024):.2f} MB) "
250
+ f"download_elapsed={time.monotonic() - t_dl:.1f}s"
251
+ )
252
+ except Exception as e:
253
+ self.logger.warning(f"OSWorld ffmpeg recording save failed: {type(e).__name__}: {e}")
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from use_computer.sandbox import (
5
+ from use_computer.core.sandbox import (
6
6
  AsyncMacOSSandbox,
7
7
  AsyncUbuntuSandbox,
8
8
  AsyncWindowsSandbox,