opencomputer-sdk 0.6.4__tar.gz → 0.6.7__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.
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/PKG-INFO +1 -1
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/__init__.py +4 -1
- opencomputer_sdk-0.6.7/opencomputer/browser.py +260 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/sandbox.py +16 -9
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/pyproject.toml +1 -1
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/.gitignore +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/README.md +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/run_all_tests.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/stream_demo.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_commands.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_concurrent.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_declarative_images.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_default_template.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_disk_size.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_domain_tls.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_environment.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_exec.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_file_ops.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_large_files.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_multi_template.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_python_sdk.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_reconnect.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_secret_store_fork.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_secretstore.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_shell.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/examples/test_timeout.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/agent.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/commands.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/exec.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/filesystem.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/image.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/mounts.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/project.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/pty.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/shell.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/snapshot.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/sse.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/template.py +0 -0
- {opencomputer_sdk-0.6.4 → opencomputer_sdk-0.6.7}/opencomputer/usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencomputer-sdk
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: Python SDK for OpenComputer - cloud sandbox platform
|
|
5
5
|
Project-URL: Homepage, https://github.com/diggerhq/opensandbox
|
|
6
6
|
Project-URL: Repository, https://github.com/diggerhq/opensandbox
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""OpenComputer Python SDK - cloud sandbox platform."""
|
|
2
2
|
|
|
3
3
|
from opencomputer.sandbox import Sandbox, ScalingLockedError, PlanLimitError, SandboxFamilyLimitError
|
|
4
|
+
from opencomputer.browser import Browser, BrowserProfile
|
|
4
5
|
from opencomputer.agent import Agent, AgentEvent, AgentSession, AgentSessionInfo
|
|
5
6
|
from opencomputer.filesystem import Filesystem
|
|
6
7
|
from opencomputer.exec import Exec, ProcessResult, ExecSession, ExecSessionInfo
|
|
@@ -28,6 +29,8 @@ from opencomputer.usage import (
|
|
|
28
29
|
|
|
29
30
|
__all__ = [
|
|
30
31
|
"Sandbox",
|
|
32
|
+
"Browser",
|
|
33
|
+
"BrowserProfile",
|
|
31
34
|
"ScalingLockedError",
|
|
32
35
|
"PlanLimitError",
|
|
33
36
|
"SandboxFamilyLimitError",
|
|
@@ -65,4 +68,4 @@ __all__ = [
|
|
|
65
68
|
"TagKeyInfo",
|
|
66
69
|
]
|
|
67
70
|
|
|
68
|
-
__version__ = "0.6.
|
|
71
|
+
__version__ = "0.6.7"
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Browser entity for OpenComputer's browser API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DEFAULT_BROWSER_API_URL = "https://browser.opencomputer.dev"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _resolve_browser_api_url(api_url: str | None) -> str:
|
|
16
|
+
return (api_url or os.environ.get("OPENCOMPUTER_BROWSER_API_URL") or DEFAULT_BROWSER_API_URL).rstrip("/")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Browser:
|
|
21
|
+
"""Browser session managed through OpenComputer."""
|
|
22
|
+
|
|
23
|
+
id: str
|
|
24
|
+
provider: str
|
|
25
|
+
provider_session_id: str
|
|
26
|
+
status: str
|
|
27
|
+
cdp_ws_url: str
|
|
28
|
+
webdriver_ws_url: str
|
|
29
|
+
live_view_url: str | None = None
|
|
30
|
+
base_url: str | None = None
|
|
31
|
+
headless: bool | None = None
|
|
32
|
+
stealth: bool | None = None
|
|
33
|
+
gpu: bool | None = None
|
|
34
|
+
timeout_seconds: int | None = None
|
|
35
|
+
created_at: str | None = None
|
|
36
|
+
updated_at: str | None = None
|
|
37
|
+
deleted_at: str | None = None
|
|
38
|
+
_api_url: str = ""
|
|
39
|
+
_api_key: str = ""
|
|
40
|
+
_client: httpx.AsyncClient | None = None
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
async def create(
|
|
44
|
+
cls,
|
|
45
|
+
*,
|
|
46
|
+
api_key: str | None = None,
|
|
47
|
+
api_url: str | None = None,
|
|
48
|
+
name: str | None = None,
|
|
49
|
+
tags: dict[str, str] | None = None,
|
|
50
|
+
stealth: bool | None = None,
|
|
51
|
+
headless: bool | None = None,
|
|
52
|
+
gpu: bool | None = None,
|
|
53
|
+
timeout_seconds: int | None = None,
|
|
54
|
+
profile: dict[str, Any] | None = None,
|
|
55
|
+
extensions: list[dict[str, str]] | None = None,
|
|
56
|
+
proxy_id: str | None = None,
|
|
57
|
+
viewport: dict[str, int] | None = None,
|
|
58
|
+
kiosk_mode: bool | None = None,
|
|
59
|
+
start_url: str | None = None,
|
|
60
|
+
chrome_policy: dict[str, Any] | None = None,
|
|
61
|
+
telemetry: dict[str, Any] | None = None,
|
|
62
|
+
) -> Browser:
|
|
63
|
+
"""Create a browser session and return direct connection URLs."""
|
|
64
|
+
url = _resolve_browser_api_url(api_url)
|
|
65
|
+
key = api_key or os.environ.get("OPENCOMPUTER_API_KEY", "")
|
|
66
|
+
client = httpx.AsyncClient(base_url=url, headers=_headers(key), timeout=30.0)
|
|
67
|
+
|
|
68
|
+
body = _create_body(
|
|
69
|
+
name=name,
|
|
70
|
+
tags=tags,
|
|
71
|
+
stealth=stealth,
|
|
72
|
+
headless=headless,
|
|
73
|
+
gpu=gpu,
|
|
74
|
+
timeout_seconds=timeout_seconds,
|
|
75
|
+
profile=profile,
|
|
76
|
+
extensions=extensions,
|
|
77
|
+
proxy_id=proxy_id,
|
|
78
|
+
viewport=viewport,
|
|
79
|
+
kiosk_mode=kiosk_mode,
|
|
80
|
+
start_url=start_url,
|
|
81
|
+
chrome_policy=chrome_policy,
|
|
82
|
+
telemetry=telemetry,
|
|
83
|
+
)
|
|
84
|
+
resp = await client.post("/v1/browsers", json=body)
|
|
85
|
+
resp.raise_for_status()
|
|
86
|
+
return cls._from_data(resp.json(), url, key, client)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
async def connect(
|
|
90
|
+
cls,
|
|
91
|
+
browser_id: str,
|
|
92
|
+
*,
|
|
93
|
+
api_key: str | None = None,
|
|
94
|
+
api_url: str | None = None,
|
|
95
|
+
) -> Browser:
|
|
96
|
+
"""Load an existing browser session by OpenComputer browser ID."""
|
|
97
|
+
url = _resolve_browser_api_url(api_url)
|
|
98
|
+
key = api_key or os.environ.get("OPENCOMPUTER_API_KEY", "")
|
|
99
|
+
client = httpx.AsyncClient(base_url=url, headers=_headers(key), timeout=30.0)
|
|
100
|
+
resp = await client.get(f"/v1/browsers/{browser_id}")
|
|
101
|
+
resp.raise_for_status()
|
|
102
|
+
return cls._from_data(resp.json(), url, key, client)
|
|
103
|
+
|
|
104
|
+
async def delete(self) -> None:
|
|
105
|
+
"""Delete the browser session."""
|
|
106
|
+
client = self._client or httpx.AsyncClient(base_url=self._api_url, headers=_headers(self._api_key), timeout=30.0)
|
|
107
|
+
resp = await client.delete(f"/v1/browsers/{self.id}")
|
|
108
|
+
resp.raise_for_status()
|
|
109
|
+
|
|
110
|
+
async def close(self) -> None:
|
|
111
|
+
"""Close the SDK HTTP client. This does not delete the browser."""
|
|
112
|
+
if self._client is not None:
|
|
113
|
+
await self._client.aclose()
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def _from_data(
|
|
117
|
+
cls,
|
|
118
|
+
data: dict[str, Any],
|
|
119
|
+
api_url: str,
|
|
120
|
+
api_key: str,
|
|
121
|
+
client: httpx.AsyncClient,
|
|
122
|
+
) -> Browser:
|
|
123
|
+
return cls(
|
|
124
|
+
id=data["id"],
|
|
125
|
+
provider=data.get("provider", "kernel"),
|
|
126
|
+
provider_session_id=data["provider_session_id"],
|
|
127
|
+
status=data.get("status", ""),
|
|
128
|
+
cdp_ws_url=data["cdp_ws_url"],
|
|
129
|
+
webdriver_ws_url=data["webdriver_ws_url"],
|
|
130
|
+
live_view_url=data.get("live_view_url"),
|
|
131
|
+
base_url=data.get("base_url"),
|
|
132
|
+
headless=data.get("headless"),
|
|
133
|
+
stealth=data.get("stealth"),
|
|
134
|
+
gpu=data.get("gpu"),
|
|
135
|
+
timeout_seconds=data.get("timeout_seconds"),
|
|
136
|
+
created_at=data.get("created_at"),
|
|
137
|
+
updated_at=data.get("updated_at"),
|
|
138
|
+
deleted_at=data.get("deleted_at"),
|
|
139
|
+
_api_url=api_url,
|
|
140
|
+
_api_key=api_key,
|
|
141
|
+
_client=client,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass
|
|
146
|
+
class BrowserProfile:
|
|
147
|
+
"""Browser profile metadata managed through OpenComputer."""
|
|
148
|
+
|
|
149
|
+
id: str
|
|
150
|
+
provider: str
|
|
151
|
+
provider_profile_id: str
|
|
152
|
+
name: str | None = None
|
|
153
|
+
created_at: str | None = None
|
|
154
|
+
updated_at: str | None = None
|
|
155
|
+
deleted_at: str | None = None
|
|
156
|
+
provider_created_at: str | None = None
|
|
157
|
+
provider_updated_at: str | None = None
|
|
158
|
+
provider_last_used_at: str | None = None
|
|
159
|
+
_api_url: str = ""
|
|
160
|
+
_api_key: str = ""
|
|
161
|
+
_client: httpx.AsyncClient | None = None
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
async def create(
|
|
165
|
+
cls,
|
|
166
|
+
*,
|
|
167
|
+
api_key: str | None = None,
|
|
168
|
+
api_url: str | None = None,
|
|
169
|
+
name: str | None = None,
|
|
170
|
+
) -> BrowserProfile:
|
|
171
|
+
"""Create a browser profile."""
|
|
172
|
+
url = _resolve_browser_api_url(api_url)
|
|
173
|
+
key = api_key or os.environ.get("OPENCOMPUTER_API_KEY", "")
|
|
174
|
+
client = httpx.AsyncClient(base_url=url, headers=_headers(key), timeout=30.0)
|
|
175
|
+
body = {"name": name} if name is not None else {}
|
|
176
|
+
resp = await client.post("/v1/profiles", json=body)
|
|
177
|
+
resp.raise_for_status()
|
|
178
|
+
return cls._from_data(resp.json(), url, key, client)
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
async def list(
|
|
182
|
+
cls,
|
|
183
|
+
*,
|
|
184
|
+
api_key: str | None = None,
|
|
185
|
+
api_url: str | None = None,
|
|
186
|
+
) -> list[BrowserProfile]:
|
|
187
|
+
"""List browser profiles for the authenticated org."""
|
|
188
|
+
url = _resolve_browser_api_url(api_url)
|
|
189
|
+
key = api_key or os.environ.get("OPENCOMPUTER_API_KEY", "")
|
|
190
|
+
client = httpx.AsyncClient(base_url=url, headers=_headers(key), timeout=30.0)
|
|
191
|
+
resp = await client.get("/v1/profiles")
|
|
192
|
+
resp.raise_for_status()
|
|
193
|
+
data = resp.json()
|
|
194
|
+
return [cls._from_data(item, url, key, client) for item in data.get("profiles", [])]
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
async def connect(
|
|
198
|
+
cls,
|
|
199
|
+
id_or_name: str,
|
|
200
|
+
*,
|
|
201
|
+
api_key: str | None = None,
|
|
202
|
+
api_url: str | None = None,
|
|
203
|
+
) -> BrowserProfile:
|
|
204
|
+
"""Load a browser profile by OpenComputer profile ID or name."""
|
|
205
|
+
url = _resolve_browser_api_url(api_url)
|
|
206
|
+
key = api_key or os.environ.get("OPENCOMPUTER_API_KEY", "")
|
|
207
|
+
client = httpx.AsyncClient(base_url=url, headers=_headers(key), timeout=30.0)
|
|
208
|
+
resp = await client.get(f"/v1/profiles/{id_or_name}")
|
|
209
|
+
resp.raise_for_status()
|
|
210
|
+
return cls._from_data(resp.json(), url, key, client)
|
|
211
|
+
|
|
212
|
+
async def delete(self) -> None:
|
|
213
|
+
"""Delete the browser profile."""
|
|
214
|
+
client = self._client or httpx.AsyncClient(base_url=self._api_url, headers=_headers(self._api_key), timeout=30.0)
|
|
215
|
+
resp = await client.delete(f"/v1/profiles/{self.id}")
|
|
216
|
+
resp.raise_for_status()
|
|
217
|
+
|
|
218
|
+
async def close(self) -> None:
|
|
219
|
+
"""Close the SDK HTTP client. This does not delete the profile."""
|
|
220
|
+
if self._client is not None:
|
|
221
|
+
await self._client.aclose()
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def _from_data(
|
|
225
|
+
cls,
|
|
226
|
+
data: dict[str, Any],
|
|
227
|
+
api_url: str,
|
|
228
|
+
api_key: str,
|
|
229
|
+
client: httpx.AsyncClient,
|
|
230
|
+
) -> BrowserProfile:
|
|
231
|
+
return cls(
|
|
232
|
+
id=data["id"],
|
|
233
|
+
provider=data.get("provider", "kernel"),
|
|
234
|
+
provider_profile_id=data["provider_profile_id"],
|
|
235
|
+
name=data.get("name"),
|
|
236
|
+
created_at=data.get("created_at"),
|
|
237
|
+
updated_at=data.get("updated_at"),
|
|
238
|
+
deleted_at=data.get("deleted_at"),
|
|
239
|
+
provider_created_at=data.get("provider_created_at"),
|
|
240
|
+
provider_updated_at=data.get("provider_updated_at"),
|
|
241
|
+
provider_last_used_at=data.get("provider_last_used_at"),
|
|
242
|
+
_api_url=api_url,
|
|
243
|
+
_api_key=api_key,
|
|
244
|
+
_client=client,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _headers(api_key: str) -> dict[str, str]:
|
|
249
|
+
headers = {"Content-Type": "application/json"}
|
|
250
|
+
if api_key:
|
|
251
|
+
headers["X-API-Key"] = api_key
|
|
252
|
+
return headers
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _create_body(**kwargs: Any) -> dict[str, Any]:
|
|
256
|
+
body: dict[str, Any] = {}
|
|
257
|
+
for key, value in kwargs.items():
|
|
258
|
+
if value is not None:
|
|
259
|
+
body[key] = value
|
|
260
|
+
return body
|
|
@@ -288,9 +288,16 @@ class Sandbox:
|
|
|
288
288
|
which handles readiness waiting and proxies to workers."""
|
|
289
289
|
return self._client
|
|
290
290
|
|
|
291
|
-
async def kill(self) -> None:
|
|
292
|
-
"""Kill and remove the sandbox.
|
|
293
|
-
|
|
291
|
+
async def kill(self, delete_secret_store: bool = False) -> None:
|
|
292
|
+
"""Kill and remove the sandbox.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
delete_secret_store: Also delete the secret store currently attached
|
|
296
|
+
to this sandbox. Defaults to ``False``. Only use this for
|
|
297
|
+
single-use stores that should not outlive the sandbox.
|
|
298
|
+
"""
|
|
299
|
+
params = {"deleteSecretStore": "true"} if delete_secret_store else None
|
|
300
|
+
resp = await self._client.delete(f"/sandboxes/{self.sandbox_id}", params=params)
|
|
294
301
|
resp.raise_for_status()
|
|
295
302
|
self.status = "stopped"
|
|
296
303
|
|
|
@@ -606,7 +613,7 @@ class Sandbox:
|
|
|
606
613
|
self,
|
|
607
614
|
name: str,
|
|
608
615
|
kind: str | None = None,
|
|
609
|
-
promote_to_full: bool =
|
|
616
|
+
promote_to_full: bool | None = None,
|
|
610
617
|
retention_policy: dict | None = None,
|
|
611
618
|
) -> dict:
|
|
612
619
|
"""Create a named checkpoint of the running sandbox.
|
|
@@ -615,9 +622,9 @@ class Sandbox:
|
|
|
615
622
|
name: A unique name for this checkpoint.
|
|
616
623
|
kind: Optional checkpoint kind. Use "full" for disk, memory, and
|
|
617
624
|
device state, or "disk_only" for a disk-only checkpoint that
|
|
618
|
-
|
|
619
|
-
promote_to_full:
|
|
620
|
-
|
|
625
|
+
automatically prepares a full artifact for faster future forks.
|
|
626
|
+
promote_to_full: Advanced override for disk-only checkpoints.
|
|
627
|
+
Defaults to server behavior.
|
|
621
628
|
retention_policy: Optional policy such as
|
|
622
629
|
{"mode": "delete_oldest", "maxCount": 10}. When set, the
|
|
623
630
|
server may delete older eligible checkpoints before creating
|
|
@@ -629,8 +636,8 @@ class Sandbox:
|
|
|
629
636
|
body = {"name": name}
|
|
630
637
|
if kind is not None:
|
|
631
638
|
body["kind"] = kind
|
|
632
|
-
if promote_to_full:
|
|
633
|
-
body["promoteToFull"] =
|
|
639
|
+
if promote_to_full is not None:
|
|
640
|
+
body["promoteToFull"] = promote_to_full
|
|
634
641
|
if retention_policy is not None:
|
|
635
642
|
body["retentionPolicy"] = retention_policy
|
|
636
643
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|