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.
Files changed (46) hide show
  1. gl_computer_use-0.0.0b6/MANIFEST.in +5 -0
  2. gl_computer_use-0.0.0b6/PKG-INFO +336 -0
  3. gl_computer_use-0.0.0b6/README.md +302 -0
  4. gl_computer_use-0.0.0b6/gl_computer_use/__init__.py +125 -0
  5. gl_computer_use-0.0.0b6/gl_computer_use/_setup.py +55 -0
  6. gl_computer_use-0.0.0b6/gl_computer_use/agent/__init__.py +1 -0
  7. gl_computer_use-0.0.0b6/gl_computer_use/agent/_takeover.py +106 -0
  8. gl_computer_use-0.0.0b6/gl_computer_use/agent/agent_s.py +600 -0
  9. gl_computer_use-0.0.0b6/gl_computer_use/agent/base.py +37 -0
  10. gl_computer_use-0.0.0b6/gl_computer_use/agent/cua.py +476 -0
  11. gl_computer_use-0.0.0b6/gl_computer_use/agent/runner.py +423 -0
  12. gl_computer_use-0.0.0b6/gl_computer_use/artifact/__init__.py +1 -0
  13. gl_computer_use-0.0.0b6/gl_computer_use/artifact/base.py +59 -0
  14. gl_computer_use-0.0.0b6/gl_computer_use/artifact/local.py +98 -0
  15. gl_computer_use-0.0.0b6/gl_computer_use/artifact/minio.py +236 -0
  16. gl_computer_use-0.0.0b6/gl_computer_use/client.py +544 -0
  17. gl_computer_use-0.0.0b6/gl_computer_use/config.py +248 -0
  18. gl_computer_use-0.0.0b6/gl_computer_use/errors.py +105 -0
  19. gl_computer_use-0.0.0b6/gl_computer_use/observability/__init__.py +5 -0
  20. gl_computer_use-0.0.0b6/gl_computer_use/observability/logging.py +131 -0
  21. gl_computer_use-0.0.0b6/gl_computer_use/observability/metrics.py +97 -0
  22. gl_computer_use-0.0.0b6/gl_computer_use/observability/retries.py +105 -0
  23. gl_computer_use-0.0.0b6/gl_computer_use/observability/tracing.py +98 -0
  24. gl_computer_use-0.0.0b6/gl_computer_use/recording.py +255 -0
  25. gl_computer_use-0.0.0b6/gl_computer_use/registry.py +223 -0
  26. gl_computer_use-0.0.0b6/gl_computer_use/sandbox/__init__.py +1 -0
  27. gl_computer_use-0.0.0b6/gl_computer_use/sandbox/base.py +270 -0
  28. gl_computer_use-0.0.0b6/gl_computer_use/sandbox/e2b.py +429 -0
  29. gl_computer_use-0.0.0b6/gl_computer_use/sandbox/opensandbox.py +654 -0
  30. gl_computer_use-0.0.0b6/gl_computer_use/session/__init__.py +1 -0
  31. gl_computer_use-0.0.0b6/gl_computer_use/session/accessor.py +490 -0
  32. gl_computer_use-0.0.0b6/gl_computer_use/session/manager.py +278 -0
  33. gl_computer_use-0.0.0b6/gl_computer_use/session/session.py +186 -0
  34. gl_computer_use-0.0.0b6/gl_computer_use/session/state.py +53 -0
  35. gl_computer_use-0.0.0b6/gl_computer_use/stream/__init__.py +1 -0
  36. gl_computer_use-0.0.0b6/gl_computer_use/stream/client.py +186 -0
  37. gl_computer_use-0.0.0b6/gl_computer_use/types.py +293 -0
  38. gl_computer_use-0.0.0b6/gl_computer_use.egg-info/PKG-INFO +336 -0
  39. gl_computer_use-0.0.0b6/gl_computer_use.egg-info/SOURCES.txt +44 -0
  40. gl_computer_use-0.0.0b6/gl_computer_use.egg-info/dependency_links.txt +1 -0
  41. gl_computer_use-0.0.0b6/gl_computer_use.egg-info/entry_points.txt +2 -0
  42. gl_computer_use-0.0.0b6/gl_computer_use.egg-info/requires.txt +34 -0
  43. gl_computer_use-0.0.0b6/gl_computer_use.egg-info/top_level.txt +1 -0
  44. gl_computer_use-0.0.0b6/pyproject.toml +149 -0
  45. gl_computer_use-0.0.0b6/setup.cfg +4 -0
  46. gl_computer_use-0.0.0b6/setup.py +10 -0
@@ -0,0 +1,5 @@
1
+ global-exclude *.key
2
+ global-exclude *.env
3
+ global-exclude *.aes
4
+ prune tests
5
+ prune build
@@ -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.