use-computer 0.0.44__tar.gz → 0.0.45__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.44 → use_computer-0.0.45}/PKG-INFO +31 -17
- {use_computer-0.0.44 → use_computer-0.0.45}/README.md +29 -15
- {use_computer-0.0.44 → use_computer-0.0.45}/pyproject.toml +1 -1
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/__init__.py +2 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/agent.py +4 -4
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/task_setup.py +64 -1
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/ios/agent.py +3 -4
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/core/__init__.py +2 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/core/client.py +21 -19
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/core/errors.py +31 -2
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/core/sandbox.py +12 -7
- {use_computer-0.0.44 → use_computer-0.0.45}/uv.lock +306 -306
- {use_computer-0.0.44 → use_computer-0.0.45}/.env.example +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/.gitignore +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/.pre-commit-config.yaml +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/scripts/smoke_wheel.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/actions.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/artifacts.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/desktop.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/final_state.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/prompts.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/recordings.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/sandbox.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/screenshots.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/base/trajectory.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/core.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/debug/README.md +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/debug/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/debug/actions.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/debug/agent.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/debug/models.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/ios/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/ios/tools.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/prompts/anthropic.txt +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/prompts/gemini.txt +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/prompts/ios.txt +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/prompts/openai.txt +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/prompts/osworld_pyautogui.txt +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/prompts/pyautogui.txt +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/anthropic.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/gemini.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/generic.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/openai.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/osworld.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/providers/tinker_backend.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/agents/types.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/automation/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/automation/ax_transpile.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/automation/osworld.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/automation/parsers.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/core/models.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/core/retry.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/harbor/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/harbor/agents.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/ios/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/ios/apps.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/ios/environment.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/ios/input.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/ios/simulators.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/macos/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/macos/keyboard.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/macos/mouse.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/py.typed +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/resources/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/resources/accessibility.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/resources/display.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/resources/recording.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/resources/screenshot.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/setup_files.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/pre_command.ps1 +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/pre_command.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/task.toml +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_ios.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_ios_nograder.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_macos.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_macos_check.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_macos_nograder.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_ubuntu.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_ubuntu_check.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_ubuntu_nograder.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_windows.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_windows_check.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/tasks/templates/test_windows_nograder.sh +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/windows/__init__.py +0 -0
- {use_computer-0.0.44 → use_computer-0.0.45}/use_computer/windows/shell.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: use-computer
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.45
|
|
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
|
|
@@ -99,14 +99,19 @@ with Computer().create(type="macos", reservation_id="...") as mac:
|
|
|
99
99
|
print(mac.exec_ssh("uname -a").stdout)
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
Each reserved Mac Mini supports up to 2 parallel macOS VMs. Check
|
|
103
|
+
`Computer().platforms(reservation_id=...)["macos"]["capacity"]` for the selected
|
|
104
|
+
reservation's `max` and `used` counts. iOS capacity is reported separately under
|
|
105
|
+
`["ios"]["capacity"]`.
|
|
106
|
+
|
|
102
107
|
### Windows (Beta)
|
|
103
108
|
|
|
104
109
|
```python
|
|
105
110
|
with Computer().create(type="windows") as win:
|
|
106
|
-
print(win.run("$env:COMPUTERNAME").stdout)
|
|
111
|
+
print(win.run("$env:COMPUTERNAME").stdout) # PowerShell exec (no SSH)
|
|
107
112
|
win.keyboard.type("hello")
|
|
108
113
|
win.screenshot.take_full_screen()
|
|
109
|
-
win.ui_tree()
|
|
114
|
+
win.ui_tree() # native Windows UIAutomation tree
|
|
110
115
|
```
|
|
111
116
|
|
|
112
117
|
Windows sandboxes are **Beta** (admin-only). Same mouse/keyboard/screenshot/
|
|
@@ -132,6 +137,10 @@ with Computer().create(type="ubuntu", version="ubuntu-24.04") as ubuntu:
|
|
|
132
137
|
|
|
133
138
|
Ubuntu sandboxes are **Beta** (admin-only). They use the same KVM/noVNC backend
|
|
134
139
|
as Windows; pass `version="ubuntu-24.04"` plus `resources` to select CPU/RAM/disk.
|
|
140
|
+
Ubuntu and Windows share a per-account KVM concurrency limit. The default is 8
|
|
141
|
+
running desktops; when the limit is hit, `create()` raises
|
|
142
|
+
`SandboxLimitReachedError` with `limit` and `running` fields from the gateway's
|
|
143
|
+
HTTP 429 response.
|
|
135
144
|
|
|
136
145
|
Selectable Ubuntu images are exposed by `Computer().platforms()["ubuntu"]`.
|
|
137
146
|
Current examples include `ubuntu-24.04`, `ubuntu-24.04-4c4g80g`,
|
|
@@ -153,27 +162,32 @@ with client.create(
|
|
|
153
162
|
|
|
154
163
|
### Snapshots (Beta)
|
|
155
164
|
|
|
156
|
-
Windows and Ubuntu snapshots
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
macOS, Windows, and Ubuntu snapshots let you seed a VM once, snapshot it, then
|
|
166
|
+
create new sandboxes from that snapshot version. macOS snapshots preserve disk
|
|
167
|
+
state. Windows and Ubuntu snapshots preserve disk + RAM state, including open
|
|
168
|
+
apps and running processes.
|
|
169
|
+
|
|
170
|
+
macOS snapshot launch is slower because the gateway stores a portable disk-delta
|
|
171
|
+
artifact and transfers it to the Mac Mini that claims the new VM. Windows and
|
|
172
|
+
Ubuntu snapshots restore disk + RAM on their desktop host.
|
|
159
173
|
|
|
160
174
|
```python
|
|
161
175
|
client = Computer()
|
|
162
176
|
|
|
163
177
|
# Configure the desktop by hand over VNC, then snapshot it.
|
|
164
|
-
with client.create(type="
|
|
165
|
-
print("Set up the desktop here:",
|
|
166
|
-
# install apps, sign into accounts,
|
|
178
|
+
with client.create(type="macos") as mac:
|
|
179
|
+
print("Set up the desktop here:", mac.vnc_url)
|
|
180
|
+
# install apps, sign into accounts, create files your agent starts from
|
|
167
181
|
input("Press Enter once the desktop is ready to snapshot...")
|
|
168
|
-
snapshot =
|
|
182
|
+
snapshot = mac.snapshot("chrome-seeded-macos")
|
|
169
183
|
|
|
170
|
-
# New sandboxes boot from that
|
|
171
|
-
with client.create(type="
|
|
172
|
-
print(seeded.vnc_url) # apps,
|
|
184
|
+
# New sandboxes boot from that saved state, no setup needed.
|
|
185
|
+
with client.create(type="macos", snapshot=snapshot.version) as seeded:
|
|
186
|
+
print(seeded.vnc_url) # installed apps, files, and logins restored
|
|
173
187
|
```
|
|
174
188
|
|
|
175
|
-
Use `client.snapshots("
|
|
176
|
-
snapshot versions.
|
|
189
|
+
Use `client.snapshots("macos")`, `client.snapshots("ubuntu")`, or
|
|
190
|
+
`client.snapshots("windows")` to list saved snapshot versions.
|
|
177
191
|
|
|
178
192
|
Full DSL reference: [docs.use.computer/docs/sdk](https://docs.use.computer/docs/sdk)
|
|
179
193
|
|
|
@@ -212,7 +226,7 @@ visionOS yet. Use it for read-only flows for now.
|
|
|
212
226
|
| [`examples/_7_hello_windows.py`](examples/_7_hello_windows.py) | create Windows → run PowerShell → screenshot |
|
|
213
227
|
| [`examples/_8_hello_ubuntu.py`](examples/_8_hello_ubuntu.py) | create Ubuntu → run bash → screenshot |
|
|
214
228
|
| [`examples/_9_seeding.py`](examples/_9_seeding.py) | typed setup: files, hosts, open URLs/apps |
|
|
215
|
-
| [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded Ubuntu/Windows
|
|
229
|
+
| [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded macOS/Ubuntu/Windows state |
|
|
216
230
|
|
|
217
231
|
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.
|
|
218
232
|
|
|
@@ -65,14 +65,19 @@ with Computer().create(type="macos", reservation_id="...") as mac:
|
|
|
65
65
|
print(mac.exec_ssh("uname -a").stdout)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
Each reserved Mac Mini supports up to 2 parallel macOS VMs. Check
|
|
69
|
+
`Computer().platforms(reservation_id=...)["macos"]["capacity"]` for the selected
|
|
70
|
+
reservation's `max` and `used` counts. iOS capacity is reported separately under
|
|
71
|
+
`["ios"]["capacity"]`.
|
|
72
|
+
|
|
68
73
|
### Windows (Beta)
|
|
69
74
|
|
|
70
75
|
```python
|
|
71
76
|
with Computer().create(type="windows") as win:
|
|
72
|
-
print(win.run("$env:COMPUTERNAME").stdout)
|
|
77
|
+
print(win.run("$env:COMPUTERNAME").stdout) # PowerShell exec (no SSH)
|
|
73
78
|
win.keyboard.type("hello")
|
|
74
79
|
win.screenshot.take_full_screen()
|
|
75
|
-
win.ui_tree()
|
|
80
|
+
win.ui_tree() # native Windows UIAutomation tree
|
|
76
81
|
```
|
|
77
82
|
|
|
78
83
|
Windows sandboxes are **Beta** (admin-only). Same mouse/keyboard/screenshot/
|
|
@@ -98,6 +103,10 @@ with Computer().create(type="ubuntu", version="ubuntu-24.04") as ubuntu:
|
|
|
98
103
|
|
|
99
104
|
Ubuntu sandboxes are **Beta** (admin-only). They use the same KVM/noVNC backend
|
|
100
105
|
as Windows; pass `version="ubuntu-24.04"` plus `resources` to select CPU/RAM/disk.
|
|
106
|
+
Ubuntu and Windows share a per-account KVM concurrency limit. The default is 8
|
|
107
|
+
running desktops; when the limit is hit, `create()` raises
|
|
108
|
+
`SandboxLimitReachedError` with `limit` and `running` fields from the gateway's
|
|
109
|
+
HTTP 429 response.
|
|
101
110
|
|
|
102
111
|
Selectable Ubuntu images are exposed by `Computer().platforms()["ubuntu"]`.
|
|
103
112
|
Current examples include `ubuntu-24.04`, `ubuntu-24.04-4c4g80g`,
|
|
@@ -119,27 +128,32 @@ with client.create(
|
|
|
119
128
|
|
|
120
129
|
### Snapshots (Beta)
|
|
121
130
|
|
|
122
|
-
Windows and Ubuntu snapshots
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
macOS, Windows, and Ubuntu snapshots let you seed a VM once, snapshot it, then
|
|
132
|
+
create new sandboxes from that snapshot version. macOS snapshots preserve disk
|
|
133
|
+
state. Windows and Ubuntu snapshots preserve disk + RAM state, including open
|
|
134
|
+
apps and running processes.
|
|
135
|
+
|
|
136
|
+
macOS snapshot launch is slower because the gateway stores a portable disk-delta
|
|
137
|
+
artifact and transfers it to the Mac Mini that claims the new VM. Windows and
|
|
138
|
+
Ubuntu snapshots restore disk + RAM on their desktop host.
|
|
125
139
|
|
|
126
140
|
```python
|
|
127
141
|
client = Computer()
|
|
128
142
|
|
|
129
143
|
# Configure the desktop by hand over VNC, then snapshot it.
|
|
130
|
-
with client.create(type="
|
|
131
|
-
print("Set up the desktop here:",
|
|
132
|
-
# install apps, sign into accounts,
|
|
144
|
+
with client.create(type="macos") as mac:
|
|
145
|
+
print("Set up the desktop here:", mac.vnc_url)
|
|
146
|
+
# install apps, sign into accounts, create files your agent starts from
|
|
133
147
|
input("Press Enter once the desktop is ready to snapshot...")
|
|
134
|
-
snapshot =
|
|
148
|
+
snapshot = mac.snapshot("chrome-seeded-macos")
|
|
135
149
|
|
|
136
|
-
# New sandboxes boot from that
|
|
137
|
-
with client.create(type="
|
|
138
|
-
print(seeded.vnc_url) # apps,
|
|
150
|
+
# New sandboxes boot from that saved state, no setup needed.
|
|
151
|
+
with client.create(type="macos", snapshot=snapshot.version) as seeded:
|
|
152
|
+
print(seeded.vnc_url) # installed apps, files, and logins restored
|
|
139
153
|
```
|
|
140
154
|
|
|
141
|
-
Use `client.snapshots("
|
|
142
|
-
snapshot versions.
|
|
155
|
+
Use `client.snapshots("macos")`, `client.snapshots("ubuntu")`, or
|
|
156
|
+
`client.snapshots("windows")` to list saved snapshot versions.
|
|
143
157
|
|
|
144
158
|
Full DSL reference: [docs.use.computer/docs/sdk](https://docs.use.computer/docs/sdk)
|
|
145
159
|
|
|
@@ -178,7 +192,7 @@ visionOS yet. Use it for read-only flows for now.
|
|
|
178
192
|
| [`examples/_7_hello_windows.py`](examples/_7_hello_windows.py) | create Windows → run PowerShell → screenshot |
|
|
179
193
|
| [`examples/_8_hello_ubuntu.py`](examples/_8_hello_ubuntu.py) | create Ubuntu → run bash → screenshot |
|
|
180
194
|
| [`examples/_9_seeding.py`](examples/_9_seeding.py) | typed setup: files, hosts, open URLs/apps |
|
|
181
|
-
| [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded Ubuntu/Windows
|
|
195
|
+
| [`examples/_10_snapshots.py`](examples/_10_snapshots.py) | snapshot seeded macOS/Ubuntu/Windows state |
|
|
182
196
|
|
|
183
197
|
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.
|
|
184
198
|
|
|
@@ -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.45"
|
|
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"
|
|
@@ -5,6 +5,7 @@ from use_computer.core.errors import (
|
|
|
5
5
|
BillingRequiredError,
|
|
6
6
|
PlatformNotSupportedError,
|
|
7
7
|
ReservationRequiredError,
|
|
8
|
+
SandboxLimitReachedError,
|
|
8
9
|
UseComputerError,
|
|
9
10
|
)
|
|
10
11
|
from use_computer.core.models import (
|
|
@@ -73,6 +74,7 @@ __all__ = [
|
|
|
73
74
|
"RemoteButton",
|
|
74
75
|
"RunStatus",
|
|
75
76
|
"Sandbox",
|
|
77
|
+
"SandboxLimitReachedError",
|
|
76
78
|
"SandboxType",
|
|
77
79
|
"Snapshot",
|
|
78
80
|
"SimulatorChoice",
|
|
@@ -16,7 +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
|
+
from use_computer.agents.base.task_setup import run_task_setup, task_dir_from_env
|
|
20
20
|
from use_computer.agents.base.trajectory import write_trajectory
|
|
21
21
|
from use_computer.agents.core import (
|
|
22
22
|
AgentRunOptions,
|
|
@@ -31,13 +31,13 @@ from use_computer.core.sandbox import AsyncSandbox
|
|
|
31
31
|
|
|
32
32
|
def _task_dir_from_env(environment: Any) -> Path:
|
|
33
33
|
"""Pull the task dir from the environment."""
|
|
34
|
-
p =
|
|
34
|
+
p = task_dir_from_env(environment)
|
|
35
35
|
if p is None:
|
|
36
36
|
raise RuntimeError(
|
|
37
|
-
"environment exposes neither
|
|
37
|
+
"environment exposes neither task_dir nor task environment_dir; "
|
|
38
38
|
"agent can't locate the task directory"
|
|
39
39
|
)
|
|
40
|
-
return
|
|
40
|
+
return p
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class BaseCUAAgent(RecordingMixin, FinalStateMixin, ArtifactCaptureMixin):
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import importlib.util
|
|
7
|
+
import inspect
|
|
6
8
|
import json
|
|
7
9
|
import logging
|
|
8
10
|
import re
|
|
9
11
|
import shlex
|
|
12
|
+
import sys
|
|
10
13
|
from pathlib import Path, PureWindowsPath
|
|
11
14
|
from typing import Any
|
|
12
15
|
|
|
@@ -35,7 +38,11 @@ async def run_task_setup(environment: Any, logger: logging.Logger) -> None:
|
|
|
35
38
|
platform = _platform_from_env(environment)
|
|
36
39
|
|
|
37
40
|
_load_in_process_config(environment, setup_dir)
|
|
41
|
+
await _run_task_local_setup(environment, setup_dir, logger)
|
|
42
|
+
|
|
38
43
|
if platform is Platform.IOS:
|
|
44
|
+
if setup_dir.exists():
|
|
45
|
+
logger.info("CUA task setup complete")
|
|
39
46
|
return
|
|
40
47
|
|
|
41
48
|
await _upload_setup_files(environment, setup_dir, platform, logger)
|
|
@@ -46,8 +53,25 @@ async def run_task_setup(environment: Any, logger: logging.Logger) -> None:
|
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
def _task_dir_from_env(environment: Any) -> Path | None:
|
|
56
|
+
return task_dir_from_env(environment)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def task_dir_from_env(environment: Any) -> Path | None:
|
|
49
60
|
raw = getattr(environment, "_task_dir", None) or getattr(environment, "task_dir", None)
|
|
50
|
-
|
|
61
|
+
if raw:
|
|
62
|
+
return Path(raw).resolve()
|
|
63
|
+
|
|
64
|
+
environment_dir = getattr(environment, "environment_dir", None)
|
|
65
|
+
if environment_dir:
|
|
66
|
+
path = Path(environment_dir).resolve()
|
|
67
|
+
if path.name == "environment" and _looks_like_task_dir(path.parent):
|
|
68
|
+
return path.parent
|
|
69
|
+
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _looks_like_task_dir(path: Path) -> bool:
|
|
74
|
+
return (path / "task.toml").exists() and (path / "instruction.md").exists()
|
|
51
75
|
|
|
52
76
|
|
|
53
77
|
def _platform_from_env(environment: Any) -> Platform:
|
|
@@ -143,6 +167,45 @@ async def _run_pre_command(
|
|
|
143
167
|
await _run_macos_pre_command(environment, setup_dir, logger)
|
|
144
168
|
|
|
145
169
|
|
|
170
|
+
async def _run_task_local_setup(
|
|
171
|
+
environment: Any,
|
|
172
|
+
setup_dir: Path,
|
|
173
|
+
logger: logging.Logger,
|
|
174
|
+
) -> None:
|
|
175
|
+
setup_py = setup_dir / "setup.py"
|
|
176
|
+
if not setup_py.exists():
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
logger.info("running setup.py")
|
|
180
|
+
spec = importlib.util.spec_from_file_location(
|
|
181
|
+
f"_harbor_task_setup_{abs(hash(setup_py.resolve()))}",
|
|
182
|
+
setup_py,
|
|
183
|
+
)
|
|
184
|
+
if spec is None or spec.loader is None:
|
|
185
|
+
raise RuntimeError(f"could not load task setup script: {setup_py}")
|
|
186
|
+
|
|
187
|
+
module = importlib.util.module_from_spec(spec)
|
|
188
|
+
setup_path = str(setup_dir)
|
|
189
|
+
sys.path.insert(0, setup_path)
|
|
190
|
+
try:
|
|
191
|
+
spec.loader.exec_module(module)
|
|
192
|
+
setup_task = getattr(module, "setup_task", None)
|
|
193
|
+
if not callable(setup_task):
|
|
194
|
+
raise RuntimeError(f"task setup script lacks setup_task(): {setup_py}")
|
|
195
|
+
|
|
196
|
+
result = setup_task(environment, logger, setup_dir)
|
|
197
|
+
if inspect.isawaitable(result):
|
|
198
|
+
await result
|
|
199
|
+
finally:
|
|
200
|
+
if sys.path and sys.path[0] == setup_path:
|
|
201
|
+
sys.path.pop(0)
|
|
202
|
+
else:
|
|
203
|
+
try:
|
|
204
|
+
sys.path.remove(setup_path)
|
|
205
|
+
except ValueError:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
|
|
146
209
|
async def _run_windows_pre_command(
|
|
147
210
|
environment: Any,
|
|
148
211
|
setup_dir: Path,
|
|
@@ -32,6 +32,7 @@ from use_computer.agents.base import (
|
|
|
32
32
|
load_prompt,
|
|
33
33
|
scale_screenshot_for_model,
|
|
34
34
|
)
|
|
35
|
+
from use_computer.agents.base.task_setup import task_dir_from_env
|
|
35
36
|
from use_computer.agents.ios.tools import (
|
|
36
37
|
_build_ios_system_prompt,
|
|
37
38
|
_coerce_coord_args,
|
|
@@ -69,12 +70,10 @@ class IOSAgent(BaseCUAAgent):
|
|
|
69
70
|
|
|
70
71
|
self.sandbox = sandbox
|
|
71
72
|
if self.task_dir is None:
|
|
72
|
-
task_dir =
|
|
73
|
-
environment, "task_dir", None
|
|
74
|
-
)
|
|
73
|
+
task_dir = task_dir_from_env(environment)
|
|
75
74
|
if task_dir is None:
|
|
76
75
|
raise RuntimeError(
|
|
77
|
-
"environment exposes neither
|
|
76
|
+
"environment exposes neither task_dir nor task environment_dir; "
|
|
78
77
|
"agent can't locate the task directory"
|
|
79
78
|
)
|
|
80
79
|
self.task_dir = task_dir
|
|
@@ -5,6 +5,7 @@ from use_computer.core.errors import (
|
|
|
5
5
|
BillingRequiredError,
|
|
6
6
|
PlatformNotSupportedError,
|
|
7
7
|
ReservationRequiredError,
|
|
8
|
+
SandboxLimitReachedError,
|
|
8
9
|
UseComputerError,
|
|
9
10
|
)
|
|
10
11
|
from use_computer.core.models import (
|
|
@@ -56,6 +57,7 @@ __all__ = [
|
|
|
56
57
|
"ReservationRequiredError",
|
|
57
58
|
"RunStatus",
|
|
58
59
|
"Sandbox",
|
|
60
|
+
"SandboxLimitReachedError",
|
|
59
61
|
"SandboxType",
|
|
60
62
|
"Snapshot",
|
|
61
63
|
"UbuntuSandbox",
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import os
|
|
5
5
|
import time
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
7
8
|
from typing import Any, Literal, overload
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
@@ -27,6 +28,11 @@ from use_computer.ios.simulators import SimulatorFamily, select_simulator
|
|
|
27
28
|
from use_computer.tasks import TasksClient
|
|
28
29
|
|
|
29
30
|
DEFAULT_BASE_URL = "https://api.use.computer"
|
|
31
|
+
SDK_VERSION = "0.0.0"
|
|
32
|
+
try:
|
|
33
|
+
SDK_VERSION = version("use-computer")
|
|
34
|
+
except PackageNotFoundError:
|
|
35
|
+
pass
|
|
30
36
|
|
|
31
37
|
SandboxTypeInput = SandboxType | Literal["macos", "ios", "windows", "ubuntu"]
|
|
32
38
|
|
|
@@ -42,7 +48,7 @@ def _resolve_base_url(base_url: str | None) -> str:
|
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
def _resolve_headers(api_key: str | None, host_header: str | None = None) -> dict[str, str]:
|
|
45
|
-
headers: dict[str, str] = {}
|
|
51
|
+
headers: dict[str, str] = {"X-Use-Computer-SDK": f"python/{SDK_VERSION}"}
|
|
46
52
|
if api_key:
|
|
47
53
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
48
54
|
host = host_header if host_header is not None else os.getenv("USE_COMPUTER_HOST_HEADER")
|
|
@@ -172,7 +178,7 @@ class Computer:
|
|
|
172
178
|
return resp.json()
|
|
173
179
|
|
|
174
180
|
def snapshots(self, platform: str = "") -> list[dict[str, Any]]:
|
|
175
|
-
"""List
|
|
181
|
+
"""List desktop snapshots visible to this API key."""
|
|
176
182
|
params = {"platform": platform} if platform else None
|
|
177
183
|
resp = self._http.get("/v1/snapshots", params=params)
|
|
178
184
|
resp.raise_for_status()
|
|
@@ -195,6 +201,7 @@ class Computer:
|
|
|
195
201
|
type: Literal["macos", SandboxType.MACOS] = ...,
|
|
196
202
|
reservation_id: str = ...,
|
|
197
203
|
host: str = ...,
|
|
204
|
+
snapshot: str = ...,
|
|
198
205
|
ephemeral: bool = ...,
|
|
199
206
|
) -> MacOSSandbox: ...
|
|
200
207
|
|
|
@@ -260,7 +267,7 @@ class Computer:
|
|
|
260
267
|
runtime: Simulator runtime identifier, for exact pinning.
|
|
261
268
|
version: KVM desktop image version, for example "windows-11",
|
|
262
269
|
"windows-10", "ubuntu-24.04", or "osworld".
|
|
263
|
-
snapshot:
|
|
270
|
+
snapshot: Snapshot version returned by sandbox.snapshot().
|
|
264
271
|
resources: KVM CPU/RAM/disk request used to select the baked image
|
|
265
272
|
profile, for example {"cpus": 4, "memory_mb": 4096, "disk_gb": 40}.
|
|
266
273
|
ephemeral: If true, delete the sandbox after the idle timeout.
|
|
@@ -280,6 +287,8 @@ class Computer:
|
|
|
280
287
|
body["version"] = version
|
|
281
288
|
if resources:
|
|
282
289
|
body["resources"] = resources
|
|
290
|
+
elif sandbox_type == SandboxType.MACOS and snapshot:
|
|
291
|
+
body["snapshot"] = snapshot
|
|
283
292
|
if sandbox_type == SandboxType.IOS:
|
|
284
293
|
if host:
|
|
285
294
|
body["mini_ip"] = host
|
|
@@ -378,17 +387,7 @@ class Computer:
|
|
|
378
387
|
poll: bool = True,
|
|
379
388
|
poll_interval: float = 3.0,
|
|
380
389
|
) -> RunStatus:
|
|
381
|
-
"""Run an ad-hoc task
|
|
382
|
-
|
|
383
|
-
Args:
|
|
384
|
-
instruction: What the agent should do.
|
|
385
|
-
platform: "macos", "ios", "windows", or "ubuntu".
|
|
386
|
-
model: Model to use.
|
|
387
|
-
max_steps: Max agent steps.
|
|
388
|
-
files: Optional list of {remote_path, content_b64} to pre-upload.
|
|
389
|
-
poll: If True, block until the run finishes.
|
|
390
|
-
poll_interval: Seconds between status polls.
|
|
391
|
-
"""
|
|
390
|
+
"""Run an ad-hoc task (requires admin base URL and key)."""
|
|
392
391
|
body: dict[str, Any] = {
|
|
393
392
|
"instruction": instruction,
|
|
394
393
|
"platform": platform,
|
|
@@ -405,7 +404,7 @@ class Computer:
|
|
|
405
404
|
return self._poll_run(status.id, poll_interval)
|
|
406
405
|
|
|
407
406
|
def get_run(self, run_id: str) -> RunStatus:
|
|
408
|
-
"""Get current status of a model run."""
|
|
407
|
+
"""Get current status of a model run (requires admin base URL and key)."""
|
|
409
408
|
resp = self._http.get(f"/admin/tasks/model-runs/{run_id}")
|
|
410
409
|
resp.raise_for_status()
|
|
411
410
|
return RunStatus._from_dict(resp.json())
|
|
@@ -454,7 +453,7 @@ class AsyncComputer:
|
|
|
454
453
|
return resp.json()
|
|
455
454
|
|
|
456
455
|
async def snapshots(self, platform: str = "") -> list[dict[str, Any]]:
|
|
457
|
-
"""List
|
|
456
|
+
"""List desktop snapshots visible to this API key."""
|
|
458
457
|
params = {"platform": platform} if platform else None
|
|
459
458
|
resp = await self._http.get("/v1/snapshots", params=params)
|
|
460
459
|
resp.raise_for_status()
|
|
@@ -477,6 +476,7 @@ class AsyncComputer:
|
|
|
477
476
|
type: Literal["macos", SandboxType.MACOS] = ...,
|
|
478
477
|
reservation_id: str = ...,
|
|
479
478
|
host: str = ...,
|
|
479
|
+
snapshot: str = ...,
|
|
480
480
|
ephemeral: bool = ...,
|
|
481
481
|
) -> AsyncMacOSSandbox: ...
|
|
482
482
|
|
|
@@ -542,7 +542,7 @@ class AsyncComputer:
|
|
|
542
542
|
runtime: Simulator runtime identifier, for exact pinning.
|
|
543
543
|
version: KVM desktop image version, for example "windows-11",
|
|
544
544
|
"windows-10", "ubuntu-24.04", or "osworld".
|
|
545
|
-
snapshot:
|
|
545
|
+
snapshot: Snapshot version returned by sandbox.snapshot().
|
|
546
546
|
resources: KVM CPU/RAM/disk request used to select the baked image
|
|
547
547
|
profile, for example {"cpus": 4, "memory_mb": 4096, "disk_gb": 40}.
|
|
548
548
|
ephemeral: If true, delete the sandbox after the idle timeout.
|
|
@@ -562,6 +562,8 @@ class AsyncComputer:
|
|
|
562
562
|
body["version"] = version
|
|
563
563
|
if resources:
|
|
564
564
|
body["resources"] = resources
|
|
565
|
+
elif sandbox_type == SandboxType.MACOS and snapshot:
|
|
566
|
+
body["snapshot"] = snapshot
|
|
565
567
|
if sandbox_type == SandboxType.IOS:
|
|
566
568
|
if host:
|
|
567
569
|
body["mini_ip"] = host
|
|
@@ -662,7 +664,7 @@ class AsyncComputer:
|
|
|
662
664
|
poll: bool = True,
|
|
663
665
|
poll_interval: float = 3.0,
|
|
664
666
|
) -> RunStatus:
|
|
665
|
-
"""Run an ad-hoc task
|
|
667
|
+
"""Run an ad-hoc task (requires admin base URL and key)."""
|
|
666
668
|
body: dict[str, Any] = {
|
|
667
669
|
"instruction": instruction,
|
|
668
670
|
"platform": platform,
|
|
@@ -679,7 +681,7 @@ class AsyncComputer:
|
|
|
679
681
|
return await self._poll_run(status.id, poll_interval)
|
|
680
682
|
|
|
681
683
|
async def get_run(self, run_id: str) -> RunStatus:
|
|
682
|
-
"""Get current status of a model run."""
|
|
684
|
+
"""Get current status of a model run (requires admin base URL and key)."""
|
|
683
685
|
resp = await self._http.get(f"/admin/tasks/model-runs/{run_id}")
|
|
684
686
|
resp.raise_for_status()
|
|
685
687
|
return RunStatus._from_dict(resp.json())
|
|
@@ -50,6 +50,26 @@ class ReservationRequiredError(UseComputerError):
|
|
|
50
50
|
super().__init__(message or "macOS requires an active Mac Mini reservation")
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
class SandboxLimitReachedError(UseComputerError):
|
|
54
|
+
"""Raised when the account has too many running Ubuntu/Windows sandboxes."""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
message: str,
|
|
59
|
+
*,
|
|
60
|
+
limit: int | None = None,
|
|
61
|
+
running: int | None = None,
|
|
62
|
+
response: httpx.Response | None = None,
|
|
63
|
+
):
|
|
64
|
+
self.message = message
|
|
65
|
+
self.limit = limit
|
|
66
|
+
self.running = running
|
|
67
|
+
self.response = response
|
|
68
|
+
super().__init__(
|
|
69
|
+
message or "Ubuntu/Windows sandbox limit reached. Delete one sandbox to launch another."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
53
73
|
def _typed_error_from_response(resp: httpx.Response) -> UseComputerError | None:
|
|
54
74
|
try:
|
|
55
75
|
data = resp.json()
|
|
@@ -70,6 +90,15 @@ def _typed_error_from_response(resp: httpx.Response) -> UseComputerError | None:
|
|
|
70
90
|
return BillingRequiredError(message=message, response=resp)
|
|
71
91
|
if resp.status_code == 409 and error_code == "macos_reservation_required":
|
|
72
92
|
return ReservationRequiredError(message=message, response=resp)
|
|
93
|
+
if resp.status_code == 429 and error_code == "sandbox_limit_reached":
|
|
94
|
+
limit = data.get("limit")
|
|
95
|
+
running = data.get("running")
|
|
96
|
+
return SandboxLimitReachedError(
|
|
97
|
+
message=message,
|
|
98
|
+
limit=limit if isinstance(limit, int) else None,
|
|
99
|
+
running=running if isinstance(running, int) else None,
|
|
100
|
+
response=resp,
|
|
101
|
+
)
|
|
73
102
|
return None
|
|
74
103
|
|
|
75
104
|
|
|
@@ -80,7 +109,7 @@ def _raise_if_unsupported(resp: httpx.Response) -> None:
|
|
|
80
109
|
only touch the body for statuses we own — anything else is left
|
|
81
110
|
untouched for raise_for_status() to handle normally.
|
|
82
111
|
"""
|
|
83
|
-
if resp.status_code not in (402, 409, 501):
|
|
112
|
+
if resp.status_code not in (402, 409, 429, 501):
|
|
84
113
|
return
|
|
85
114
|
try:
|
|
86
115
|
resp.read()
|
|
@@ -92,7 +121,7 @@ def _raise_if_unsupported(resp: httpx.Response) -> None:
|
|
|
92
121
|
|
|
93
122
|
|
|
94
123
|
async def _araise_if_unsupported(resp: httpx.Response) -> None:
|
|
95
|
-
if resp.status_code not in (402, 409, 501):
|
|
124
|
+
if resp.status_code not in (402, 409, 429, 501):
|
|
96
125
|
return
|
|
97
126
|
try:
|
|
98
127
|
await resp.aread()
|
|
@@ -4,6 +4,7 @@ import base64
|
|
|
4
4
|
import io
|
|
5
5
|
import tarfile
|
|
6
6
|
import threading
|
|
7
|
+
import urllib.error
|
|
7
8
|
import urllib.request
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from enum import Enum
|
|
@@ -41,7 +42,7 @@ class SandboxType(str, Enum):
|
|
|
41
42
|
|
|
42
43
|
@dataclass
|
|
43
44
|
class Snapshot:
|
|
44
|
-
"""
|
|
45
|
+
"""Desktop snapshot."""
|
|
45
46
|
|
|
46
47
|
name: str
|
|
47
48
|
version: str
|
|
@@ -83,6 +84,10 @@ def _start_keepalive_thread(
|
|
|
83
84
|
try:
|
|
84
85
|
req = urllib.request.Request(keepalive_url, method="POST", headers=headers)
|
|
85
86
|
urllib.request.urlopen(req, timeout=10).close()
|
|
87
|
+
except urllib.error.HTTPError as exc:
|
|
88
|
+
if exc.code in (404, 410):
|
|
89
|
+
stop.set()
|
|
90
|
+
break
|
|
86
91
|
except Exception:
|
|
87
92
|
pass
|
|
88
93
|
|
|
@@ -167,12 +172,12 @@ class Sandbox:
|
|
|
167
172
|
local_path.write_bytes(resp.content)
|
|
168
173
|
|
|
169
174
|
def snapshot(self, name: str, *, version: str = "", timeout: int = 900) -> Snapshot:
|
|
170
|
-
"""Capture a
|
|
171
|
-
if self.type not in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
175
|
+
"""Capture a desktop snapshot and return its version."""
|
|
176
|
+
if self.type not in (SandboxType.MACOS, SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
172
177
|
raise PlatformNotSupportedError(
|
|
173
178
|
action="snapshot",
|
|
174
179
|
sandbox_type=self.type.value,
|
|
175
|
-
hint="snapshots are supported for Windows and Ubuntu sandboxes",
|
|
180
|
+
hint="snapshots are supported for macOS, Windows, and Ubuntu sandboxes",
|
|
176
181
|
)
|
|
177
182
|
body = {"name": name}
|
|
178
183
|
if version:
|
|
@@ -601,12 +606,12 @@ class AsyncSandbox:
|
|
|
601
606
|
local_path.write_bytes(resp.content)
|
|
602
607
|
|
|
603
608
|
async def snapshot(self, name: str, *, version: str = "", timeout: int = 900) -> Snapshot:
|
|
604
|
-
"""Capture a
|
|
605
|
-
if self.type not in (SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
609
|
+
"""Capture a desktop snapshot and return its version."""
|
|
610
|
+
if self.type not in (SandboxType.MACOS, SandboxType.WINDOWS, SandboxType.UBUNTU):
|
|
606
611
|
raise PlatformNotSupportedError(
|
|
607
612
|
action="snapshot",
|
|
608
613
|
sandbox_type=self.type.value,
|
|
609
|
-
hint="snapshots are supported for Windows and Ubuntu sandboxes",
|
|
614
|
+
hint="snapshots are supported for macOS, Windows, and Ubuntu sandboxes",
|
|
610
615
|
)
|
|
611
616
|
body = {"name": name}
|
|
612
617
|
if version:
|