gl-computer-use 0.0.0b6__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.
- gl_computer_use-0.0.0b6/MANIFEST.in +5 -0
- gl_computer_use-0.0.0b6/PKG-INFO +336 -0
- gl_computer_use-0.0.0b6/README.md +302 -0
- gl_computer_use-0.0.0b6/gl_computer_use/__init__.py +125 -0
- gl_computer_use-0.0.0b6/gl_computer_use/_setup.py +55 -0
- gl_computer_use-0.0.0b6/gl_computer_use/agent/__init__.py +1 -0
- gl_computer_use-0.0.0b6/gl_computer_use/agent/_takeover.py +106 -0
- gl_computer_use-0.0.0b6/gl_computer_use/agent/agent_s.py +600 -0
- gl_computer_use-0.0.0b6/gl_computer_use/agent/base.py +37 -0
- gl_computer_use-0.0.0b6/gl_computer_use/agent/cua.py +476 -0
- gl_computer_use-0.0.0b6/gl_computer_use/agent/runner.py +423 -0
- gl_computer_use-0.0.0b6/gl_computer_use/artifact/__init__.py +1 -0
- gl_computer_use-0.0.0b6/gl_computer_use/artifact/base.py +59 -0
- gl_computer_use-0.0.0b6/gl_computer_use/artifact/local.py +98 -0
- gl_computer_use-0.0.0b6/gl_computer_use/artifact/minio.py +236 -0
- gl_computer_use-0.0.0b6/gl_computer_use/client.py +544 -0
- gl_computer_use-0.0.0b6/gl_computer_use/config.py +248 -0
- gl_computer_use-0.0.0b6/gl_computer_use/errors.py +105 -0
- gl_computer_use-0.0.0b6/gl_computer_use/observability/__init__.py +5 -0
- gl_computer_use-0.0.0b6/gl_computer_use/observability/logging.py +131 -0
- gl_computer_use-0.0.0b6/gl_computer_use/observability/metrics.py +97 -0
- gl_computer_use-0.0.0b6/gl_computer_use/observability/retries.py +105 -0
- gl_computer_use-0.0.0b6/gl_computer_use/observability/tracing.py +98 -0
- gl_computer_use-0.0.0b6/gl_computer_use/recording.py +255 -0
- gl_computer_use-0.0.0b6/gl_computer_use/registry.py +223 -0
- gl_computer_use-0.0.0b6/gl_computer_use/sandbox/__init__.py +1 -0
- gl_computer_use-0.0.0b6/gl_computer_use/sandbox/base.py +270 -0
- gl_computer_use-0.0.0b6/gl_computer_use/sandbox/e2b.py +429 -0
- gl_computer_use-0.0.0b6/gl_computer_use/sandbox/opensandbox.py +654 -0
- gl_computer_use-0.0.0b6/gl_computer_use/session/__init__.py +1 -0
- gl_computer_use-0.0.0b6/gl_computer_use/session/accessor.py +490 -0
- gl_computer_use-0.0.0b6/gl_computer_use/session/manager.py +278 -0
- gl_computer_use-0.0.0b6/gl_computer_use/session/session.py +186 -0
- gl_computer_use-0.0.0b6/gl_computer_use/session/state.py +53 -0
- gl_computer_use-0.0.0b6/gl_computer_use/stream/__init__.py +1 -0
- gl_computer_use-0.0.0b6/gl_computer_use/stream/client.py +186 -0
- gl_computer_use-0.0.0b6/gl_computer_use/types.py +293 -0
- gl_computer_use-0.0.0b6/gl_computer_use.egg-info/PKG-INFO +336 -0
- gl_computer_use-0.0.0b6/gl_computer_use.egg-info/SOURCES.txt +44 -0
- gl_computer_use-0.0.0b6/gl_computer_use.egg-info/dependency_links.txt +1 -0
- gl_computer_use-0.0.0b6/gl_computer_use.egg-info/entry_points.txt +2 -0
- gl_computer_use-0.0.0b6/gl_computer_use.egg-info/requires.txt +34 -0
- gl_computer_use-0.0.0b6/gl_computer_use.egg-info/top_level.txt +1 -0
- gl_computer_use-0.0.0b6/pyproject.toml +149 -0
- gl_computer_use-0.0.0b6/setup.cfg +4 -0
- gl_computer_use-0.0.0b6/setup.py +10 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gl-computer-use
|
|
3
|
+
Version: 0.0.0b6
|
|
4
|
+
Summary: GL Computer Use SDK — desktop automation via natural-language prompts.
|
|
5
|
+
Author-email: Christopher Julius Limantoro <christopherlimantoro@gmail.com>
|
|
6
|
+
Requires-Python: <3.14,>=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: pydantic<3.0.0,>=2.11.4
|
|
9
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.0.0
|
|
10
|
+
Requires-Dist: python-dotenv<2.0.0,>=1.0.0
|
|
11
|
+
Requires-Dist: e2b-desktop<3.0.0,>=2.3.0
|
|
12
|
+
Requires-Dist: cua-agent<1.0.0,>=0.8.2
|
|
13
|
+
Requires-Dist: aiofiles<25.0.0,>=23.0.0
|
|
14
|
+
Requires-Dist: structlog<26.0.0,>=24.0.0
|
|
15
|
+
Provides-Extra: minio
|
|
16
|
+
Requires-Dist: aiobotocore<3,>=2.13; extra == "minio"
|
|
17
|
+
Provides-Extra: recording
|
|
18
|
+
Requires-Dist: playwright<2.0.0,>=1.40.0; extra == "recording"
|
|
19
|
+
Requires-Dist: pillow<13.0.0,>=12.2.0; extra == "recording"
|
|
20
|
+
Provides-Extra: agents
|
|
21
|
+
Requires-Dist: gui-agents>=0.3.0; extra == "agents"
|
|
22
|
+
Requires-Dist: pyautogui>=0.9.54; extra == "agents"
|
|
23
|
+
Provides-Extra: opensandbox
|
|
24
|
+
Requires-Dist: opensandbox>=0.1.7; extra == "opensandbox"
|
|
25
|
+
Provides-Extra: retries
|
|
26
|
+
Requires-Dist: tenacity<10.0.0,>=8.2.0; extra == "retries"
|
|
27
|
+
Provides-Extra: observability
|
|
28
|
+
Requires-Dist: gl-computer-use[retries]; extra == "observability"
|
|
29
|
+
Requires-Dist: gl-observability-binary==0.1.4; extra == "observability"
|
|
30
|
+
Provides-Extra: otel
|
|
31
|
+
Requires-Dist: gl-computer-use[observability]; extra == "otel"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: gl-computer-use[agents,minio,observability,opensandbox,recording,retries]; extra == "all"
|
|
34
|
+
|
|
35
|
+
# GL Computer Use
|
|
36
|
+
|
|
37
|
+
## Description
|
|
38
|
+
|
|
39
|
+
A typed Python SDK for desktop automation via natural-language prompts. GL Computer Use wraps cloud desktop sandboxes and computer-use agents into a clean async API with live streaming, human-in-the-loop takeover, structured observability, and swappable providers.
|
|
40
|
+
|
|
41
|
+
### Key Features
|
|
42
|
+
|
|
43
|
+
- **Streaming and non-streaming run modes**: `run()` for live events, `run_once()` for a single result, `run_sync()` for non-async scripts and Jupyter notebooks.
|
|
44
|
+
- **Swappable agents**: `cua` (trycua/cua, default) or `agents` (simular-ai/Agent-S).
|
|
45
|
+
- **Swappable sandboxes**: `e2b` (E2B Desktop, default) or `opensandbox` (Alibaba OpenSandbox).
|
|
46
|
+
- **Live desktop URL**: noVNC streaming URL surfaced via the `SANDBOX_READY` event or `StreamClient.stream_url`.
|
|
47
|
+
- **Human-in-the-loop takeover**: pause an agent loop and hand control to a human, then resume with optional guidance.
|
|
48
|
+
- **Artifact storage**: local disk by default, MinIO/S3 via the `minio` extra.
|
|
49
|
+
- **Structured logging** with optional OpenTelemetry tracing/metrics and Sentry via the `observability` extra.
|
|
50
|
+
- **Custom provider registration**: plug in your own sandbox, agent, or artifact store without modifying the SDK.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
Install the core SDK:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install gl-computer-use
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Install optional extras only when you need them:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install "gl-computer-use[recording]" # WebM session recording via Playwright
|
|
66
|
+
pip install "gl-computer-use[agents]" # Agent-S (simular-ai) support
|
|
67
|
+
pip install "gl-computer-use[opensandbox]" # Alibaba OpenSandbox support
|
|
68
|
+
pip install "gl-computer-use[minio]" # MinIO / S3-compatible artifact store
|
|
69
|
+
pip install "gl-computer-use[observability]" # OTLP tracing/metrics + Sentry via gl-observability
|
|
70
|
+
pip install "gl-computer-use[all]" # all of the above
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
API keys required at runtime:
|
|
74
|
+
|
|
75
|
+
1. E2B API key — [e2b.dev](https://e2b.dev) (when using `sandbox="e2b"`)
|
|
76
|
+
2. Anthropic API key (for the default `claude-sonnet-4-6` model) or OpenAI API key
|
|
77
|
+
|
|
78
|
+
### Session recording setup (optional, one-time)
|
|
79
|
+
|
|
80
|
+
WebM recordings require Playwright's Chromium binaries (~130 MB, stored under `~/.cache/ms-playwright/`):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install "gl-computer-use[recording]"
|
|
84
|
+
gl-computer-use-setup
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If you skip this step, the SDK falls back to GIF recording via screenshot stitching.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
### Streaming events
|
|
94
|
+
|
|
95
|
+
`run()` returns a `StreamClient`; iterate it to receive events. The terminal `TASK_COMPLETED` event carries the final `TaskResult`.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import asyncio
|
|
99
|
+
from gl_computer_use import GLComputerUseClient
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def main() -> None:
|
|
103
|
+
client = GLComputerUseClient()
|
|
104
|
+
stream = await client.run("Open Firefox and navigate to google.com")
|
|
105
|
+
|
|
106
|
+
async for event in stream:
|
|
107
|
+
if event.event_type == "SANDBOX_READY" and event.stream_url:
|
|
108
|
+
print(f"Watch live at: {event.stream_url}")
|
|
109
|
+
elif event.event_type == "STEP_COMPLETED":
|
|
110
|
+
print(f"Step {event.step_index}: {event.action.type if event.action else '—'}")
|
|
111
|
+
elif event.event_type == "TASK_COMPLETED":
|
|
112
|
+
print(f"Status: {event.result.status}")
|
|
113
|
+
print(f"Output: {event.result.output}")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
asyncio.run(main())
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Fire-and-forget async
|
|
120
|
+
|
|
121
|
+
`run_once()` returns a `TaskResult` directly when the task finishes. Raises `TaskFailedError` / `TaskCancelledError` on non-`COMPLETED` outcomes.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import asyncio
|
|
125
|
+
from gl_computer_use import GLComputerUseClient
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def main() -> None:
|
|
129
|
+
client = GLComputerUseClient()
|
|
130
|
+
result = await client.run_once("Open a terminal and check Python version")
|
|
131
|
+
print(result.status, result.output, len(result.steps))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Synchronous / Jupyter
|
|
138
|
+
|
|
139
|
+
`run_sync()` is a plain synchronous method — no `asyncio.run()`, no `await`. It detects whether an event loop is already running and dispatches via `ThreadPoolExecutor` when needed, so it works in regular scripts and Jupyter notebooks (no `nest_asyncio` required).
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from gl_computer_use import GLComputerUseClient
|
|
143
|
+
|
|
144
|
+
result = GLComputerUseClient().run_sync("Open the file manager")
|
|
145
|
+
print(result.status)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
Configuration is read from environment variables (prefix `GLCU_`) or by passing a `GLComputerUseConfig` object directly. Create a `.env` file in your working directory:
|
|
153
|
+
|
|
154
|
+
```dotenv
|
|
155
|
+
GLCU_E2B_API_KEY=sk-e2b-...
|
|
156
|
+
GLCU_ANTHROPIC_API_KEY=sk-ant-...
|
|
157
|
+
|
|
158
|
+
# Optional overrides
|
|
159
|
+
GLCU_MODEL=anthropic/claude-sonnet-4-6
|
|
160
|
+
GLCU_TIMEOUT=300
|
|
161
|
+
GLCU_MAX_STEPS=50
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Critical fields:
|
|
165
|
+
|
|
166
|
+
| Variable | Default | Description |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `GLCU_E2B_API_KEY` | `None` | E2B Desktop API key (required when `sandbox="e2b"`) |
|
|
169
|
+
| `GLCU_ANTHROPIC_API_KEY` | `None` | Anthropic API key (required for `anthropic/*` models) |
|
|
170
|
+
| `GLCU_OPENAI_API_KEY` | `None` | OpenAI API key (required for `openai/*` models) |
|
|
171
|
+
| `GLCU_MODEL` | `"anthropic/claude-sonnet-4-6"` | LLM in `provider/name` format |
|
|
172
|
+
| `GLCU_AGENT` | `"cua"` | Agent provider: `"cua"` or `"agents"` |
|
|
173
|
+
| `GLCU_SANDBOX` | `"e2b"` | Sandbox provider: `"e2b"` or `"opensandbox"` |
|
|
174
|
+
| `GLCU_ARTIFACT` | `"local"` | Artifact store: `"local"` or `"minio"` |
|
|
175
|
+
| `GLCU_TIMEOUT` | `300.0` | Task timeout in seconds |
|
|
176
|
+
| `GLCU_MAX_STEPS` | `50` | Maximum agent loop iterations |
|
|
177
|
+
| `GLCU_LOCAL_ARTIFACT_DIR` | `"./artifacts"` | Directory for saved screenshots and recordings |
|
|
178
|
+
| `GLCU_LOG_LEVEL` | `"INFO"` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
|
|
179
|
+
| `GLCU_LOG_FORMAT` | `"json"` | `"json"` (structured) or `"console"` (human-readable) |
|
|
180
|
+
|
|
181
|
+
OpenSandbox, MinIO, Agent-S, and observability (OTLP/Sentry/PII) have additional `GLCU_*` env vars — see `GLComputerUseConfig` in `gl_computer_use/config.py` for the full list.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Provider Agnosticism
|
|
186
|
+
|
|
187
|
+
Swap agents and sandboxes via config alone — no code changes:
|
|
188
|
+
|
|
189
|
+
| Agent | Sandbox | Config |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| CUA (default) | E2B (default) | `GLComputerUseClient()` |
|
|
192
|
+
| CUA | OpenSandbox | `GLComputerUseConfig(sandbox="opensandbox")` |
|
|
193
|
+
| Agent-S | E2B | `GLComputerUseConfig(agent="agents")` |
|
|
194
|
+
| Agent-S | OpenSandbox | `GLComputerUseConfig(agent="agents", sandbox="opensandbox")` |
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from gl_computer_use import GLComputerUseClient, GLComputerUseConfig
|
|
198
|
+
|
|
199
|
+
client = GLComputerUseClient(GLComputerUseConfig(agent="agents", sandbox="opensandbox"))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Runtime API
|
|
205
|
+
|
|
206
|
+
The client exposes three run methods:
|
|
207
|
+
|
|
208
|
+
| Method | Returns | Use when |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `await client.run(prompt, ...)` | `StreamClient` | You need live event streaming or the `SANDBOX_READY` URL before the task finishes |
|
|
211
|
+
| `await client.run_once(prompt, ...)` | `TaskResult` | You only need the final result, async context |
|
|
212
|
+
| `client.run_sync(prompt, ...)` | `TaskResult` | You only need the final result, non-async script or Jupyter notebook |
|
|
213
|
+
|
|
214
|
+
All three methods accept the same parameters:
|
|
215
|
+
|
|
216
|
+
| Parameter | Type | Default | Description |
|
|
217
|
+
|---|---|---|---|
|
|
218
|
+
| `prompt` | `str` | — | Task description |
|
|
219
|
+
| `config` | `GLComputerUseConfig \| None` | `None` | Per-call config override |
|
|
220
|
+
| `timeout` | `float \| None` | `None` | Max seconds (falls back to `config.timeout`) |
|
|
221
|
+
| `files` | `list[File] \| None` | `None` | Files to upload to the sandbox before the task |
|
|
222
|
+
| `retrieve_files` | `list[str] \| None` | `None` | Sandbox paths to download after completion |
|
|
223
|
+
| `on_takeover_needed` | `Callable \| None` | `None` | Takeover callback |
|
|
224
|
+
|
|
225
|
+
`run_once()` and `run_sync()` raise `TaskFailedError` / `TaskCancelledError` directly instead of returning a result with a non-`COMPLETED` status.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Live Desktop (noVNC)
|
|
230
|
+
|
|
231
|
+
When using the E2B sandbox, a noVNC HTTP endpoint is started alongside the desktop. The SDK waits until that endpoint is reachable before surfacing the URL.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# Option A — pre-iteration attribute
|
|
235
|
+
stream = await client.run("do something")
|
|
236
|
+
print(stream.stream_url)
|
|
237
|
+
|
|
238
|
+
# Option B — first SANDBOX_READY event
|
|
239
|
+
async for event in stream:
|
|
240
|
+
if event.event_type == "SANDBOX_READY" and event.stream_url:
|
|
241
|
+
webbrowser.open(event.stream_url)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Takeover
|
|
247
|
+
|
|
248
|
+
Pass `on_takeover_needed` to `run()` / `run_once()` / `run_sync()`. The agent pauses when a takeover condition is detected, and your callback receives a `TakeoverContext` with the session state and a `resume()` function. Without a callback, a `TakeoverRequiredError` is raised. See `examples/takeover.py` and `examples/takeover_caller_initiated.py`.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Errors
|
|
253
|
+
|
|
254
|
+
All SDK exceptions extend `GLComputerUseError`:
|
|
255
|
+
|
|
256
|
+
- `ConfigError` — bad or missing credentials.
|
|
257
|
+
- `SandboxProvisionError` — the sandbox could not be allocated.
|
|
258
|
+
- `GLTimeoutError` — no event received within the configured timeout.
|
|
259
|
+
- `TaskFailedError` — the agent terminated with an error (`TASK_FAILED`).
|
|
260
|
+
- `TaskCancelledError` — the task was cancelled (`TASK_CANCELLED`).
|
|
261
|
+
- `TakeoverRequiredError` — takeover was needed but no callback was supplied.
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from gl_computer_use import (
|
|
265
|
+
GLComputerUseClient,
|
|
266
|
+
ConfigError,
|
|
267
|
+
SandboxProvisionError,
|
|
268
|
+
GLTimeoutError,
|
|
269
|
+
TaskFailedError,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
result = await GLComputerUseClient().run_once("do something", timeout=60.0)
|
|
274
|
+
except ConfigError as e:
|
|
275
|
+
print("Check your API keys:", e)
|
|
276
|
+
except SandboxProvisionError as e:
|
|
277
|
+
print("Sandbox failed to start:", e)
|
|
278
|
+
except GLTimeoutError as e:
|
|
279
|
+
print("Took too long:", e)
|
|
280
|
+
except TaskFailedError as e:
|
|
281
|
+
print("Agent failed:", e)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Observability
|
|
287
|
+
|
|
288
|
+
The SDK uses `structlog` for structured logging (JSON by default; set `GLCU_LOG_FORMAT=console` for human-readable output). Every line carries `session_id`, `task_id`, and `component`. Distributed tracing and metrics via OTLP, plus Sentry error tracking, are available through the `observability` extra and delegated to GDP Labs' [`gl-observability`](../gl-observability) SDK. Optional regex-based PII redaction is enabled with `GLCU_PII_REDACTION_ENABLED=true`.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Custom Providers
|
|
293
|
+
|
|
294
|
+
Plug in alternative sandboxes, agents, or artifact stores without modifying the SDK:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from gl_computer_use import register_sandbox, GLComputerUseClient, GLComputerUseConfig
|
|
298
|
+
from gl_computer_use.sandbox.base import BaseSandbox
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class MyCustomSandbox(BaseSandbox):
|
|
302
|
+
... # implement abstract methods
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
register_sandbox("my-sandbox", MyCustomSandbox)
|
|
306
|
+
client = GLComputerUseClient(config=GLComputerUseConfig(sandbox="my-sandbox"))
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`register_agent` and `register_artifact` work the same way for custom agents and artifact stores.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Local Development Setup
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
git clone git@github.com:GDP-ADMIN/gl-sdk.git
|
|
317
|
+
cd gl-sdk/libs/gl-computer-use
|
|
318
|
+
uv sync --all-extras
|
|
319
|
+
uv run gl-computer-use-setup
|
|
320
|
+
source .venv/bin/activate
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Run checks:
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
uv run pytest # tests
|
|
327
|
+
uv run ruff check . # lint
|
|
328
|
+
uv run ruff check --fix # auto-fix lint
|
|
329
|
+
uv run mypy gl_computer_use/ # type-check
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Contributing
|
|
335
|
+
|
|
336
|
+
Please refer to the [Python Style Guide](https://docs.google.com/document/d/1uRggCrHnVfDPBnG641FyQBwUwLoFw0kTzNqRm92vUwM/edit?usp=sharing) for code style, documentation standards, and SCA requirements.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# GL Computer Use
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
A typed Python SDK for desktop automation via natural-language prompts. GL Computer Use wraps cloud desktop sandboxes and computer-use agents into a clean async API with live streaming, human-in-the-loop takeover, structured observability, and swappable providers.
|
|
6
|
+
|
|
7
|
+
### Key Features
|
|
8
|
+
|
|
9
|
+
- **Streaming and non-streaming run modes**: `run()` for live events, `run_once()` for a single result, `run_sync()` for non-async scripts and Jupyter notebooks.
|
|
10
|
+
- **Swappable agents**: `cua` (trycua/cua, default) or `agents` (simular-ai/Agent-S).
|
|
11
|
+
- **Swappable sandboxes**: `e2b` (E2B Desktop, default) or `opensandbox` (Alibaba OpenSandbox).
|
|
12
|
+
- **Live desktop URL**: noVNC streaming URL surfaced via the `SANDBOX_READY` event or `StreamClient.stream_url`.
|
|
13
|
+
- **Human-in-the-loop takeover**: pause an agent loop and hand control to a human, then resume with optional guidance.
|
|
14
|
+
- **Artifact storage**: local disk by default, MinIO/S3 via the `minio` extra.
|
|
15
|
+
- **Structured logging** with optional OpenTelemetry tracing/metrics and Sentry via the `observability` extra.
|
|
16
|
+
- **Custom provider registration**: plug in your own sandbox, agent, or artifact store without modifying the SDK.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Install the core SDK:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install gl-computer-use
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Install optional extras only when you need them:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install "gl-computer-use[recording]" # WebM session recording via Playwright
|
|
32
|
+
pip install "gl-computer-use[agents]" # Agent-S (simular-ai) support
|
|
33
|
+
pip install "gl-computer-use[opensandbox]" # Alibaba OpenSandbox support
|
|
34
|
+
pip install "gl-computer-use[minio]" # MinIO / S3-compatible artifact store
|
|
35
|
+
pip install "gl-computer-use[observability]" # OTLP tracing/metrics + Sentry via gl-observability
|
|
36
|
+
pip install "gl-computer-use[all]" # all of the above
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
API keys required at runtime:
|
|
40
|
+
|
|
41
|
+
1. E2B API key — [e2b.dev](https://e2b.dev) (when using `sandbox="e2b"`)
|
|
42
|
+
2. Anthropic API key (for the default `claude-sonnet-4-6` model) or OpenAI API key
|
|
43
|
+
|
|
44
|
+
### Session recording setup (optional, one-time)
|
|
45
|
+
|
|
46
|
+
WebM recordings require Playwright's Chromium binaries (~130 MB, stored under `~/.cache/ms-playwright/`):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install "gl-computer-use[recording]"
|
|
50
|
+
gl-computer-use-setup
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If you skip this step, the SDK falls back to GIF recording via screenshot stitching.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
### Streaming events
|
|
60
|
+
|
|
61
|
+
`run()` returns a `StreamClient`; iterate it to receive events. The terminal `TASK_COMPLETED` event carries the final `TaskResult`.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import asyncio
|
|
65
|
+
from gl_computer_use import GLComputerUseClient
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def main() -> None:
|
|
69
|
+
client = GLComputerUseClient()
|
|
70
|
+
stream = await client.run("Open Firefox and navigate to google.com")
|
|
71
|
+
|
|
72
|
+
async for event in stream:
|
|
73
|
+
if event.event_type == "SANDBOX_READY" and event.stream_url:
|
|
74
|
+
print(f"Watch live at: {event.stream_url}")
|
|
75
|
+
elif event.event_type == "STEP_COMPLETED":
|
|
76
|
+
print(f"Step {event.step_index}: {event.action.type if event.action else '—'}")
|
|
77
|
+
elif event.event_type == "TASK_COMPLETED":
|
|
78
|
+
print(f"Status: {event.result.status}")
|
|
79
|
+
print(f"Output: {event.result.output}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
asyncio.run(main())
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Fire-and-forget async
|
|
86
|
+
|
|
87
|
+
`run_once()` returns a `TaskResult` directly when the task finishes. Raises `TaskFailedError` / `TaskCancelledError` on non-`COMPLETED` outcomes.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import asyncio
|
|
91
|
+
from gl_computer_use import GLComputerUseClient
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def main() -> None:
|
|
95
|
+
client = GLComputerUseClient()
|
|
96
|
+
result = await client.run_once("Open a terminal and check Python version")
|
|
97
|
+
print(result.status, result.output, len(result.steps))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
asyncio.run(main())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Synchronous / Jupyter
|
|
104
|
+
|
|
105
|
+
`run_sync()` is a plain synchronous method — no `asyncio.run()`, no `await`. It detects whether an event loop is already running and dispatches via `ThreadPoolExecutor` when needed, so it works in regular scripts and Jupyter notebooks (no `nest_asyncio` required).
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from gl_computer_use import GLComputerUseClient
|
|
109
|
+
|
|
110
|
+
result = GLComputerUseClient().run_sync("Open the file manager")
|
|
111
|
+
print(result.status)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
Configuration is read from environment variables (prefix `GLCU_`) or by passing a `GLComputerUseConfig` object directly. Create a `.env` file in your working directory:
|
|
119
|
+
|
|
120
|
+
```dotenv
|
|
121
|
+
GLCU_E2B_API_KEY=sk-e2b-...
|
|
122
|
+
GLCU_ANTHROPIC_API_KEY=sk-ant-...
|
|
123
|
+
|
|
124
|
+
# Optional overrides
|
|
125
|
+
GLCU_MODEL=anthropic/claude-sonnet-4-6
|
|
126
|
+
GLCU_TIMEOUT=300
|
|
127
|
+
GLCU_MAX_STEPS=50
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Critical fields:
|
|
131
|
+
|
|
132
|
+
| Variable | Default | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `GLCU_E2B_API_KEY` | `None` | E2B Desktop API key (required when `sandbox="e2b"`) |
|
|
135
|
+
| `GLCU_ANTHROPIC_API_KEY` | `None` | Anthropic API key (required for `anthropic/*` models) |
|
|
136
|
+
| `GLCU_OPENAI_API_KEY` | `None` | OpenAI API key (required for `openai/*` models) |
|
|
137
|
+
| `GLCU_MODEL` | `"anthropic/claude-sonnet-4-6"` | LLM in `provider/name` format |
|
|
138
|
+
| `GLCU_AGENT` | `"cua"` | Agent provider: `"cua"` or `"agents"` |
|
|
139
|
+
| `GLCU_SANDBOX` | `"e2b"` | Sandbox provider: `"e2b"` or `"opensandbox"` |
|
|
140
|
+
| `GLCU_ARTIFACT` | `"local"` | Artifact store: `"local"` or `"minio"` |
|
|
141
|
+
| `GLCU_TIMEOUT` | `300.0` | Task timeout in seconds |
|
|
142
|
+
| `GLCU_MAX_STEPS` | `50` | Maximum agent loop iterations |
|
|
143
|
+
| `GLCU_LOCAL_ARTIFACT_DIR` | `"./artifacts"` | Directory for saved screenshots and recordings |
|
|
144
|
+
| `GLCU_LOG_LEVEL` | `"INFO"` | `DEBUG`, `INFO`, `WARNING`, or `ERROR` |
|
|
145
|
+
| `GLCU_LOG_FORMAT` | `"json"` | `"json"` (structured) or `"console"` (human-readable) |
|
|
146
|
+
|
|
147
|
+
OpenSandbox, MinIO, Agent-S, and observability (OTLP/Sentry/PII) have additional `GLCU_*` env vars — see `GLComputerUseConfig` in `gl_computer_use/config.py` for the full list.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Provider Agnosticism
|
|
152
|
+
|
|
153
|
+
Swap agents and sandboxes via config alone — no code changes:
|
|
154
|
+
|
|
155
|
+
| Agent | Sandbox | Config |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| CUA (default) | E2B (default) | `GLComputerUseClient()` |
|
|
158
|
+
| CUA | OpenSandbox | `GLComputerUseConfig(sandbox="opensandbox")` |
|
|
159
|
+
| Agent-S | E2B | `GLComputerUseConfig(agent="agents")` |
|
|
160
|
+
| Agent-S | OpenSandbox | `GLComputerUseConfig(agent="agents", sandbox="opensandbox")` |
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from gl_computer_use import GLComputerUseClient, GLComputerUseConfig
|
|
164
|
+
|
|
165
|
+
client = GLComputerUseClient(GLComputerUseConfig(agent="agents", sandbox="opensandbox"))
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Runtime API
|
|
171
|
+
|
|
172
|
+
The client exposes three run methods:
|
|
173
|
+
|
|
174
|
+
| Method | Returns | Use when |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `await client.run(prompt, ...)` | `StreamClient` | You need live event streaming or the `SANDBOX_READY` URL before the task finishes |
|
|
177
|
+
| `await client.run_once(prompt, ...)` | `TaskResult` | You only need the final result, async context |
|
|
178
|
+
| `client.run_sync(prompt, ...)` | `TaskResult` | You only need the final result, non-async script or Jupyter notebook |
|
|
179
|
+
|
|
180
|
+
All three methods accept the same parameters:
|
|
181
|
+
|
|
182
|
+
| Parameter | Type | Default | Description |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| `prompt` | `str` | — | Task description |
|
|
185
|
+
| `config` | `GLComputerUseConfig \| None` | `None` | Per-call config override |
|
|
186
|
+
| `timeout` | `float \| None` | `None` | Max seconds (falls back to `config.timeout`) |
|
|
187
|
+
| `files` | `list[File] \| None` | `None` | Files to upload to the sandbox before the task |
|
|
188
|
+
| `retrieve_files` | `list[str] \| None` | `None` | Sandbox paths to download after completion |
|
|
189
|
+
| `on_takeover_needed` | `Callable \| None` | `None` | Takeover callback |
|
|
190
|
+
|
|
191
|
+
`run_once()` and `run_sync()` raise `TaskFailedError` / `TaskCancelledError` directly instead of returning a result with a non-`COMPLETED` status.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Live Desktop (noVNC)
|
|
196
|
+
|
|
197
|
+
When using the E2B sandbox, a noVNC HTTP endpoint is started alongside the desktop. The SDK waits until that endpoint is reachable before surfacing the URL.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
# Option A — pre-iteration attribute
|
|
201
|
+
stream = await client.run("do something")
|
|
202
|
+
print(stream.stream_url)
|
|
203
|
+
|
|
204
|
+
# Option B — first SANDBOX_READY event
|
|
205
|
+
async for event in stream:
|
|
206
|
+
if event.event_type == "SANDBOX_READY" and event.stream_url:
|
|
207
|
+
webbrowser.open(event.stream_url)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Takeover
|
|
213
|
+
|
|
214
|
+
Pass `on_takeover_needed` to `run()` / `run_once()` / `run_sync()`. The agent pauses when a takeover condition is detected, and your callback receives a `TakeoverContext` with the session state and a `resume()` function. Without a callback, a `TakeoverRequiredError` is raised. See `examples/takeover.py` and `examples/takeover_caller_initiated.py`.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Errors
|
|
219
|
+
|
|
220
|
+
All SDK exceptions extend `GLComputerUseError`:
|
|
221
|
+
|
|
222
|
+
- `ConfigError` — bad or missing credentials.
|
|
223
|
+
- `SandboxProvisionError` — the sandbox could not be allocated.
|
|
224
|
+
- `GLTimeoutError` — no event received within the configured timeout.
|
|
225
|
+
- `TaskFailedError` — the agent terminated with an error (`TASK_FAILED`).
|
|
226
|
+
- `TaskCancelledError` — the task was cancelled (`TASK_CANCELLED`).
|
|
227
|
+
- `TakeoverRequiredError` — takeover was needed but no callback was supplied.
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from gl_computer_use import (
|
|
231
|
+
GLComputerUseClient,
|
|
232
|
+
ConfigError,
|
|
233
|
+
SandboxProvisionError,
|
|
234
|
+
GLTimeoutError,
|
|
235
|
+
TaskFailedError,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
result = await GLComputerUseClient().run_once("do something", timeout=60.0)
|
|
240
|
+
except ConfigError as e:
|
|
241
|
+
print("Check your API keys:", e)
|
|
242
|
+
except SandboxProvisionError as e:
|
|
243
|
+
print("Sandbox failed to start:", e)
|
|
244
|
+
except GLTimeoutError as e:
|
|
245
|
+
print("Took too long:", e)
|
|
246
|
+
except TaskFailedError as e:
|
|
247
|
+
print("Agent failed:", e)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Observability
|
|
253
|
+
|
|
254
|
+
The SDK uses `structlog` for structured logging (JSON by default; set `GLCU_LOG_FORMAT=console` for human-readable output). Every line carries `session_id`, `task_id`, and `component`. Distributed tracing and metrics via OTLP, plus Sentry error tracking, are available through the `observability` extra and delegated to GDP Labs' [`gl-observability`](../gl-observability) SDK. Optional regex-based PII redaction is enabled with `GLCU_PII_REDACTION_ENABLED=true`.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Custom Providers
|
|
259
|
+
|
|
260
|
+
Plug in alternative sandboxes, agents, or artifact stores without modifying the SDK:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from gl_computer_use import register_sandbox, GLComputerUseClient, GLComputerUseConfig
|
|
264
|
+
from gl_computer_use.sandbox.base import BaseSandbox
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class MyCustomSandbox(BaseSandbox):
|
|
268
|
+
... # implement abstract methods
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
register_sandbox("my-sandbox", MyCustomSandbox)
|
|
272
|
+
client = GLComputerUseClient(config=GLComputerUseConfig(sandbox="my-sandbox"))
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`register_agent` and `register_artifact` work the same way for custom agents and artifact stores.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Local Development Setup
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
git clone git@github.com:GDP-ADMIN/gl-sdk.git
|
|
283
|
+
cd gl-sdk/libs/gl-computer-use
|
|
284
|
+
uv sync --all-extras
|
|
285
|
+
uv run gl-computer-use-setup
|
|
286
|
+
source .venv/bin/activate
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Run checks:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
uv run pytest # tests
|
|
293
|
+
uv run ruff check . # lint
|
|
294
|
+
uv run ruff check --fix # auto-fix lint
|
|
295
|
+
uv run mypy gl_computer_use/ # type-check
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Contributing
|
|
301
|
+
|
|
302
|
+
Please refer to the [Python Style Guide](https://docs.google.com/document/d/1uRggCrHnVfDPBnG641FyQBwUwLoFw0kTzNqRm92vUwM/edit?usp=sharing) for code style, documentation standards, and SCA requirements.
|