agentkernel-sdk 0.19.1__tar.gz → 0.20.0__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.
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/PKG-INFO +10 -10
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/pyproject.toml +12 -8
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/_config.py +3 -1
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/async_client.py +115 -58
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/browser.py +15 -9
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/client.py +123 -64
- agentkernel_sdk-0.20.0/src/agentkernel/sse.py +46 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/types.py +1 -2
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/tests/test_async_client.py +11 -3
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/tests/test_client.py +23 -7
- agentkernel_sdk-0.20.0/tests/test_sse.py +35 -0
- agentkernel_sdk-0.20.0/uv.lock +747 -0
- agentkernel_sdk-0.19.1/src/agentkernel/sse.py +0 -49
- agentkernel_sdk-0.19.1/uv.lock +0 -603
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/.gitignore +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/README.md +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/examples/async_example.py +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/examples/quickstart.py +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/examples/sandbox_session.py +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/examples/streaming.py +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/__init__.py +6 -6
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/errors.py +0 -0
- {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.0}/src/agentkernel/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: agentkernel-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: Python SDK for agentkernel — run AI coding agents in secure, isolated microVMs
|
|
5
5
|
Project-URL: Homepage, https://github.com/thrashr888/agentkernel
|
|
6
6
|
Project-URL: Repository, https://github.com/thrashr888/agentkernel/tree/main/sdk/python
|
|
@@ -18,15 +18,15 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Classifier: Typing :: Typed
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
-
Requires-Dist: httpx-sse>=0.4
|
|
22
|
-
Requires-Dist: httpx>=0.
|
|
23
|
-
Requires-Dist: pydantic>=2.
|
|
21
|
+
Requires-Dist: httpx-sse>=0.4.3
|
|
22
|
+
Requires-Dist: httpx>=0.28.1
|
|
23
|
+
Requires-Dist: pydantic>=2.13.4
|
|
24
24
|
Provides-Extra: dev
|
|
25
|
-
Requires-Dist: mypy>=1
|
|
26
|
-
Requires-Dist: pytest-asyncio>=0
|
|
27
|
-
Requires-Dist: pytest-httpx>=0.
|
|
28
|
-
Requires-Dist: pytest>=
|
|
29
|
-
Requires-Dist: ruff>=0.
|
|
25
|
+
Requires-Dist: mypy>=2.3.1; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-httpx>=0.36.2; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.16.4; extra == 'dev'
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
31
31
|
|
|
32
32
|
# agentkernel
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling>=1.32.0"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentkernel-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.20.0"
|
|
8
8
|
description = "Python SDK for agentkernel — run AI coding agents in secure, isolated microVMs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -23,15 +23,15 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.14",
|
|
24
24
|
"Typing :: Typed",
|
|
25
25
|
]
|
|
26
|
-
dependencies = ["httpx>=0.
|
|
26
|
+
dependencies = ["httpx>=0.28.1", "httpx-sse>=0.4.3", "pydantic>=2.13.4"]
|
|
27
27
|
|
|
28
28
|
[project.optional-dependencies]
|
|
29
29
|
dev = [
|
|
30
|
-
"pytest>=
|
|
31
|
-
"pytest-asyncio>=0
|
|
32
|
-
"pytest-httpx>=0.
|
|
33
|
-
"mypy>=1
|
|
34
|
-
"ruff>=0.
|
|
30
|
+
"pytest>=9.1.1",
|
|
31
|
+
"pytest-asyncio>=1.4.0",
|
|
32
|
+
"pytest-httpx>=0.36.2",
|
|
33
|
+
"mypy>=2.3.1",
|
|
34
|
+
"ruff>=0.16.4",
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
[project.urls]
|
|
@@ -48,6 +48,10 @@ line-length = 100
|
|
|
48
48
|
[tool.ruff.lint]
|
|
49
49
|
select = ["E", "F", "I", "UP", "B"]
|
|
50
50
|
|
|
51
|
+
[tool.ruff.lint.per-file-ignores]
|
|
52
|
+
# This module embeds the canonical browser JavaScript and server scripts verbatim.
|
|
53
|
+
"src/agentkernel/browser.py" = ["E501"]
|
|
54
|
+
|
|
51
55
|
[tool.mypy]
|
|
52
56
|
python_version = "3.10"
|
|
53
57
|
strict = true
|
|
@@ -23,7 +23,9 @@ def resolve_config(
|
|
|
23
23
|
) -> Config:
|
|
24
24
|
"""Resolve config from constructor args > env vars > defaults."""
|
|
25
25
|
return Config(
|
|
26
|
-
base_url=(base_url or os.environ.get("AGENTKERNEL_BASE_URL") or DEFAULT_BASE_URL).rstrip(
|
|
26
|
+
base_url=(base_url or os.environ.get("AGENTKERNEL_BASE_URL") or DEFAULT_BASE_URL).rstrip(
|
|
27
|
+
"/"
|
|
28
|
+
),
|
|
27
29
|
api_key=api_key or os.environ.get("AGENTKERNEL_API_KEY"),
|
|
28
30
|
timeout=timeout if timeout is not None else DEFAULT_TIMEOUT,
|
|
29
31
|
)
|
|
@@ -4,34 +4,33 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from collections.abc import AsyncIterator
|
|
6
6
|
from types import TracebackType
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .browser import AsyncBrowserSession
|
|
13
|
+
|
|
11
14
|
from ._config import resolve_config
|
|
12
15
|
from .errors import AgentKernelError, NetworkError, error_from_status
|
|
13
16
|
from .types import (
|
|
14
17
|
BatchFileWriteResponse,
|
|
15
18
|
BatchRunResponse,
|
|
16
|
-
CreateSandboxOptions,
|
|
17
19
|
DetachedCommand,
|
|
18
20
|
DetachedLogsResponse,
|
|
19
|
-
ExecOptions,
|
|
20
21
|
DurableObject,
|
|
21
22
|
DurableStore,
|
|
22
23
|
DurableStoreCommandResult,
|
|
23
24
|
DurableStoreExecuteResult,
|
|
24
25
|
DurableStoreQueryResult,
|
|
25
|
-
ExtendTtlResponse,
|
|
26
26
|
FileReadResponse,
|
|
27
|
+
Orchestration,
|
|
27
28
|
OrchestrationDefinition,
|
|
28
|
-
RunOptions,
|
|
29
29
|
RunOutput,
|
|
30
30
|
SandboxInfo,
|
|
31
|
+
Schedule,
|
|
31
32
|
SecurityProfile,
|
|
32
|
-
Orchestration,
|
|
33
33
|
SnapshotMeta,
|
|
34
|
-
Schedule,
|
|
35
34
|
StreamEvent,
|
|
36
35
|
)
|
|
37
36
|
|
|
@@ -56,7 +55,11 @@ class AsyncSandboxSession:
|
|
|
56
55
|
) -> RunOutput:
|
|
57
56
|
"""Run a command in this sandbox."""
|
|
58
57
|
return await self._client.exec_in_sandbox(
|
|
59
|
-
self.name,
|
|
58
|
+
self.name,
|
|
59
|
+
command,
|
|
60
|
+
env=env,
|
|
61
|
+
workdir=workdir,
|
|
62
|
+
sudo=sudo,
|
|
60
63
|
)
|
|
61
64
|
|
|
62
65
|
async def info(self) -> SandboxInfo:
|
|
@@ -131,7 +134,7 @@ class AsyncAgentKernel:
|
|
|
131
134
|
|
|
132
135
|
async def health(self) -> str:
|
|
133
136
|
"""Health check. Returns 'ok'."""
|
|
134
|
-
return await self._request("GET", "/health")
|
|
137
|
+
return cast(str, await self._request("GET", "/health"))
|
|
135
138
|
|
|
136
139
|
async def run(
|
|
137
140
|
self,
|
|
@@ -195,9 +198,13 @@ class AsyncAgentKernel:
|
|
|
195
198
|
"POST",
|
|
196
199
|
"/sandboxes",
|
|
197
200
|
json={
|
|
198
|
-
"name": name,
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
+
"name": name,
|
|
202
|
+
"image": image,
|
|
203
|
+
"vcpus": vcpus,
|
|
204
|
+
"memory_mb": memory_mb,
|
|
205
|
+
"profile": profile,
|
|
206
|
+
"source_url": source_url,
|
|
207
|
+
"source_ref": source_ref,
|
|
201
208
|
"volumes": volumes,
|
|
202
209
|
},
|
|
203
210
|
)
|
|
@@ -251,24 +258,27 @@ class AsyncAgentKernel:
|
|
|
251
258
|
encoding: str = "utf8",
|
|
252
259
|
) -> str:
|
|
253
260
|
"""Write a file to a sandbox."""
|
|
254
|
-
return
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
261
|
+
return cast(
|
|
262
|
+
str,
|
|
263
|
+
await self._request(
|
|
264
|
+
"PUT",
|
|
265
|
+
f"/sandboxes/{name}/files/{path}",
|
|
266
|
+
json={"content": content, "encoding": encoding},
|
|
267
|
+
),
|
|
258
268
|
)
|
|
259
269
|
|
|
260
270
|
async def delete_file(self, name: str, path: str) -> str:
|
|
261
271
|
"""Delete a file from a sandbox."""
|
|
262
|
-
return await self._request("DELETE", f"/sandboxes/{name}/files/{path}")
|
|
272
|
+
return cast(str, await self._request("DELETE", f"/sandboxes/{name}/files/{path}"))
|
|
263
273
|
|
|
264
274
|
async def write_files(self, name: str, files: dict[str, str]) -> BatchFileWriteResponse:
|
|
265
275
|
"""Write multiple files to a sandbox in one request."""
|
|
266
276
|
data = await self._request("POST", f"/sandboxes/{name}/files", json={"files": files})
|
|
267
277
|
return BatchFileWriteResponse(**data)
|
|
268
278
|
|
|
269
|
-
async def get_sandbox_logs(self, name: str) -> list[dict]:
|
|
279
|
+
async def get_sandbox_logs(self, name: str) -> list[dict[str, Any]]:
|
|
270
280
|
"""Get audit log entries for a sandbox."""
|
|
271
|
-
return await self._request("GET", f"/sandboxes/{name}/logs")
|
|
281
|
+
return cast(list[dict[str, Any]], await self._request("GET", f"/sandboxes/{name}/logs"))
|
|
272
282
|
|
|
273
283
|
async def batch_run(self, commands: list[list[str]]) -> BatchRunResponse:
|
|
274
284
|
"""Run multiple commands in parallel."""
|
|
@@ -306,14 +316,15 @@ class AsyncAgentKernel:
|
|
|
306
316
|
) -> DetachedLogsResponse:
|
|
307
317
|
"""Get logs from a detached command."""
|
|
308
318
|
query = f"?stream={stream}" if stream == "stderr" else ""
|
|
309
|
-
data = await self._request(
|
|
310
|
-
"GET", f"/sandboxes/{name}/exec/detached/{cmd_id}/logs{query}"
|
|
311
|
-
)
|
|
319
|
+
data = await self._request("GET", f"/sandboxes/{name}/exec/detached/{cmd_id}/logs{query}")
|
|
312
320
|
return DetachedLogsResponse(**data)
|
|
313
321
|
|
|
314
322
|
async def detached_kill(self, name: str, cmd_id: str) -> str:
|
|
315
323
|
"""Kill a detached command."""
|
|
316
|
-
return
|
|
324
|
+
return cast(
|
|
325
|
+
str,
|
|
326
|
+
await self._request("DELETE", f"/sandboxes/{name}/exec/detached/{cmd_id}"),
|
|
327
|
+
)
|
|
317
328
|
|
|
318
329
|
async def detached_list(self, name: str) -> list[DetachedCommand]:
|
|
319
330
|
"""List detached commands in a sandbox."""
|
|
@@ -322,109 +333,150 @@ class AsyncAgentKernel:
|
|
|
322
333
|
|
|
323
334
|
async def list_orchestrations(self) -> list[Orchestration]:
|
|
324
335
|
"""List orchestrations."""
|
|
325
|
-
return await self._request("GET", "/orchestrations")
|
|
336
|
+
return cast(list[Orchestration], await self._request("GET", "/orchestrations"))
|
|
326
337
|
|
|
327
338
|
async def create_orchestration(self, orchestration: Orchestration) -> Orchestration:
|
|
328
339
|
"""Create a new orchestration."""
|
|
329
|
-
return
|
|
340
|
+
return cast(
|
|
341
|
+
Orchestration, await self._request("POST", "/orchestrations", json=orchestration)
|
|
342
|
+
)
|
|
330
343
|
|
|
331
344
|
async def get_orchestration(self, orchestration_id: str) -> Orchestration:
|
|
332
345
|
"""Get an orchestration by identifier."""
|
|
333
|
-
return
|
|
346
|
+
return cast(
|
|
347
|
+
Orchestration, await self._request("GET", f"/orchestrations/{orchestration_id}")
|
|
348
|
+
)
|
|
334
349
|
|
|
335
350
|
async def signal_orchestration(
|
|
336
|
-
self,
|
|
351
|
+
self,
|
|
352
|
+
orchestration_id: str,
|
|
353
|
+
event: dict[str, Any],
|
|
337
354
|
) -> Orchestration:
|
|
338
355
|
"""Raise an external event for an orchestration."""
|
|
339
|
-
return
|
|
340
|
-
|
|
356
|
+
return cast(
|
|
357
|
+
Orchestration,
|
|
358
|
+
await self._request(
|
|
359
|
+
"POST",
|
|
360
|
+
f"/orchestrations/{orchestration_id}/events",
|
|
361
|
+
json=event,
|
|
362
|
+
),
|
|
341
363
|
)
|
|
342
364
|
|
|
343
365
|
async def terminate_orchestration(
|
|
344
|
-
self,
|
|
366
|
+
self,
|
|
367
|
+
orchestration_id: str,
|
|
368
|
+
payload: dict[str, Any] | None = None,
|
|
345
369
|
) -> Orchestration:
|
|
346
370
|
"""Terminate an orchestration."""
|
|
347
|
-
return
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
371
|
+
return cast(
|
|
372
|
+
Orchestration,
|
|
373
|
+
await self._request(
|
|
374
|
+
"POST",
|
|
375
|
+
f"/orchestrations/{orchestration_id}/terminate",
|
|
376
|
+
json=payload or {},
|
|
377
|
+
),
|
|
351
378
|
)
|
|
352
379
|
|
|
353
380
|
async def list_orchestration_definitions(self) -> list[OrchestrationDefinition]:
|
|
354
381
|
"""List orchestration definitions."""
|
|
355
|
-
return
|
|
382
|
+
return cast(
|
|
383
|
+
list[OrchestrationDefinition],
|
|
384
|
+
await self._request("GET", "/orchestrations/definitions"),
|
|
385
|
+
)
|
|
356
386
|
|
|
357
387
|
async def upsert_orchestration_definition(
|
|
358
|
-
self,
|
|
388
|
+
self,
|
|
389
|
+
definition: OrchestrationDefinition,
|
|
359
390
|
) -> OrchestrationDefinition:
|
|
360
391
|
"""Register or update an orchestration definition."""
|
|
361
|
-
return
|
|
392
|
+
return cast(
|
|
393
|
+
OrchestrationDefinition,
|
|
394
|
+
await self._request("POST", "/orchestrations/definitions", json=definition),
|
|
395
|
+
)
|
|
362
396
|
|
|
363
397
|
async def get_orchestration_definition(self, name: str) -> OrchestrationDefinition:
|
|
364
398
|
"""Get an orchestration definition by name."""
|
|
365
|
-
return
|
|
399
|
+
return cast(
|
|
400
|
+
OrchestrationDefinition,
|
|
401
|
+
await self._request("GET", f"/orchestrations/definitions/{name}"),
|
|
402
|
+
)
|
|
366
403
|
|
|
367
404
|
async def delete_orchestration_definition(self, name: str) -> str:
|
|
368
405
|
"""Delete an orchestration definition by name."""
|
|
369
|
-
return await self._request("DELETE", f"/orchestrations/definitions/{name}")
|
|
406
|
+
return cast(str, await self._request("DELETE", f"/orchestrations/definitions/{name}"))
|
|
370
407
|
|
|
371
408
|
async def list_objects(self) -> list[DurableObject]:
|
|
372
409
|
"""List objects."""
|
|
373
|
-
return await self._request("GET", "/objects")
|
|
410
|
+
return cast(list[DurableObject], await self._request("GET", "/objects"))
|
|
374
411
|
|
|
375
412
|
async def create_object(self, obj: DurableObject) -> DurableObject:
|
|
376
413
|
"""Create a new object."""
|
|
377
|
-
return await self._request("POST", "/objects", json=obj)
|
|
414
|
+
return cast(DurableObject, await self._request("POST", "/objects", json=obj))
|
|
378
415
|
|
|
379
416
|
async def get_object(self, object_id: str) -> DurableObject:
|
|
380
417
|
"""Get an object by identifier."""
|
|
381
|
-
return await self._request("GET", f"/objects/{object_id}")
|
|
418
|
+
return cast(DurableObject, await self._request("GET", f"/objects/{object_id}"))
|
|
382
419
|
|
|
383
420
|
async def list_schedules(self) -> list[Schedule]:
|
|
384
421
|
"""List schedules."""
|
|
385
|
-
return await self._request("GET", "/schedules")
|
|
422
|
+
return cast(list[Schedule], await self._request("GET", "/schedules"))
|
|
386
423
|
|
|
387
424
|
async def create_schedule(self, schedule: Schedule) -> Schedule:
|
|
388
425
|
"""Create a new schedule."""
|
|
389
|
-
return await self._request("POST", "/schedules", json=schedule)
|
|
426
|
+
return cast(Schedule, await self._request("POST", "/schedules", json=schedule))
|
|
390
427
|
|
|
391
428
|
async def get_schedule(self, schedule_id: str) -> Schedule:
|
|
392
429
|
"""Get a schedule by identifier."""
|
|
393
|
-
return await self._request("GET", f"/schedules/{schedule_id}")
|
|
430
|
+
return cast(Schedule, await self._request("GET", f"/schedules/{schedule_id}"))
|
|
394
431
|
|
|
395
432
|
async def list_stores(self) -> list[DurableStore]:
|
|
396
433
|
"""List durable stores."""
|
|
397
|
-
return await self._request("GET", "/stores")
|
|
434
|
+
return cast(list[DurableStore], await self._request("GET", "/stores"))
|
|
398
435
|
|
|
399
436
|
async def create_store(self, store: DurableStore) -> DurableStore:
|
|
400
437
|
"""Create a durable store."""
|
|
401
|
-
return await self._request("POST", "/stores", json=store)
|
|
438
|
+
return cast(DurableStore, await self._request("POST", "/stores", json=store))
|
|
402
439
|
|
|
403
440
|
async def get_store(self, store_id: str) -> DurableStore:
|
|
404
441
|
"""Get a durable store by identifier."""
|
|
405
|
-
return await self._request("GET", f"/stores/{store_id}")
|
|
442
|
+
return cast(DurableStore, await self._request("GET", f"/stores/{store_id}"))
|
|
406
443
|
|
|
407
444
|
async def delete_store(self, store_id: str) -> str:
|
|
408
445
|
"""Delete a durable store by identifier."""
|
|
409
|
-
return await self._request("DELETE", f"/stores/{store_id}")
|
|
446
|
+
return cast(str, await self._request("DELETE", f"/stores/{store_id}"))
|
|
410
447
|
|
|
411
448
|
async def query_store(
|
|
412
|
-
self,
|
|
449
|
+
self,
|
|
450
|
+
store_id: str,
|
|
451
|
+
payload: dict[str, Any],
|
|
413
452
|
) -> DurableStoreQueryResult:
|
|
414
453
|
"""Run a read query against a durable store."""
|
|
415
|
-
return
|
|
454
|
+
return cast(
|
|
455
|
+
DurableStoreQueryResult,
|
|
456
|
+
await self._request("POST", f"/stores/{store_id}/query", json=payload),
|
|
457
|
+
)
|
|
416
458
|
|
|
417
459
|
async def execute_store(
|
|
418
|
-
self,
|
|
460
|
+
self,
|
|
461
|
+
store_id: str,
|
|
462
|
+
payload: dict[str, Any],
|
|
419
463
|
) -> DurableStoreExecuteResult:
|
|
420
464
|
"""Run a write statement against a durable store."""
|
|
421
|
-
return
|
|
465
|
+
return cast(
|
|
466
|
+
DurableStoreExecuteResult,
|
|
467
|
+
await self._request("POST", f"/stores/{store_id}/execute", json=payload),
|
|
468
|
+
)
|
|
422
469
|
|
|
423
470
|
async def command_store(
|
|
424
|
-
self,
|
|
471
|
+
self,
|
|
472
|
+
store_id: str,
|
|
473
|
+
payload: dict[str, Any],
|
|
425
474
|
) -> DurableStoreCommandResult:
|
|
426
475
|
"""Run a command against a durable store (Redis-style engines)."""
|
|
427
|
-
return
|
|
476
|
+
return cast(
|
|
477
|
+
DurableStoreCommandResult,
|
|
478
|
+
await self._request("POST", f"/stores/{store_id}/command", json=payload),
|
|
479
|
+
)
|
|
428
480
|
|
|
429
481
|
async def extend_ttl(self, name: str, *, by: str) -> str | None:
|
|
430
482
|
"""Extend a sandbox's TTL. Returns the new expiry time."""
|
|
@@ -477,7 +529,9 @@ class AsyncAgentKernel:
|
|
|
477
529
|
await sb.run(["echo", "hello"])
|
|
478
530
|
# sandbox auto-removed
|
|
479
531
|
"""
|
|
480
|
-
await self.create_sandbox(
|
|
532
|
+
await self.create_sandbox(
|
|
533
|
+
name, image=image, vcpus=vcpus, memory_mb=memory_mb, profile=profile
|
|
534
|
+
)
|
|
481
535
|
return AsyncSandboxSession(name, self)
|
|
482
536
|
|
|
483
537
|
async def browser(
|
|
@@ -498,10 +552,13 @@ class AsyncAgentKernel:
|
|
|
498
552
|
print(page.title, page.links)
|
|
499
553
|
# sandbox auto-removed
|
|
500
554
|
"""
|
|
501
|
-
from .browser import
|
|
555
|
+
from .browser import _SETUP_CMD, AsyncBrowserSession
|
|
502
556
|
|
|
503
557
|
await self.create_sandbox(
|
|
504
|
-
name,
|
|
558
|
+
name,
|
|
559
|
+
image="python:3.12-slim",
|
|
560
|
+
memory_mb=memory_mb,
|
|
561
|
+
profile="moderate",
|
|
505
562
|
)
|
|
506
563
|
await self.exec_in_sandbox(name, _SETUP_CMD)
|
|
507
564
|
return AsyncBrowserSession(name, self)
|
|
@@ -4,9 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
6
|
import json
|
|
7
|
-
import shlex
|
|
8
7
|
from types import TracebackType
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
8
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
10
9
|
|
|
11
10
|
from .types import AriaSnapshot, PageLink, PageResult, RunOutput
|
|
12
11
|
|
|
@@ -245,7 +244,7 @@ _ARIA_SNAPSHOT_JS = r"""
|
|
|
245
244
|
})()
|
|
246
245
|
"""
|
|
247
246
|
|
|
248
|
-
_BROWSER_SERVER_SCRIPT = r
|
|
247
|
+
_BROWSER_SERVER_SCRIPT = r'''
|
|
249
248
|
import asyncio, base64, json, sys, os, signal
|
|
250
249
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
251
250
|
from urllib.parse import urlparse, parse_qs
|
|
@@ -363,7 +362,7 @@ if __name__ == "__main__":
|
|
|
363
362
|
def shutdown(sig, frame): server.shutdown(); sys.exit(0)
|
|
364
363
|
signal.signal(signal.SIGTERM, shutdown); signal.signal(signal.SIGINT, shutdown)
|
|
365
364
|
server.serve_forever()
|
|
366
|
-
|
|
365
|
+
'''
|
|
367
366
|
|
|
368
367
|
_BROWSER_START_SCRIPT = """
|
|
369
368
|
import sys, json, subprocess, time, os, signal, select
|
|
@@ -419,7 +418,7 @@ class BrowserSession:
|
|
|
419
418
|
title=data["title"],
|
|
420
419
|
url=data["url"],
|
|
421
420
|
text=data["text"],
|
|
422
|
-
links=[PageLink(**
|
|
421
|
+
links=[PageLink(**link) for link in data.get("links", [])],
|
|
423
422
|
)
|
|
424
423
|
|
|
425
424
|
def screenshot(self, url: str | None = None) -> bytes:
|
|
@@ -500,7 +499,7 @@ class BrowserSession:
|
|
|
500
499
|
"""List active page names."""
|
|
501
500
|
result = self._browser_request("GET", "/pages")
|
|
502
501
|
data = json.loads(result.output)
|
|
503
|
-
return data.get("pages", [])
|
|
502
|
+
return cast(list[str], data.get("pages", []))
|
|
504
503
|
|
|
505
504
|
def _browser_request(self, method: str, path: str, body: str | None = None) -> RunOutput:
|
|
506
505
|
"""Send a request to the in-sandbox browser server."""
|
|
@@ -588,7 +587,7 @@ class AsyncBrowserSession:
|
|
|
588
587
|
title=data["title"],
|
|
589
588
|
url=data["url"],
|
|
590
589
|
text=data["text"],
|
|
591
|
-
links=[PageLink(**
|
|
590
|
+
links=[PageLink(**link) for link in data.get("links", [])],
|
|
592
591
|
)
|
|
593
592
|
|
|
594
593
|
async def screenshot(self, url: str | None = None) -> bytes:
|
|
@@ -665,7 +664,7 @@ class AsyncBrowserSession:
|
|
|
665
664
|
"""List active page names."""
|
|
666
665
|
result = await self._browser_request("GET", "/pages")
|
|
667
666
|
data = json.loads(result.output)
|
|
668
|
-
return data.get("pages", [])
|
|
667
|
+
return cast(list[str], data.get("pages", []))
|
|
669
668
|
|
|
670
669
|
async def _browser_request(self, method: str, path: str, body: str | None = None) -> RunOutput:
|
|
671
670
|
"""Send a request to the in-sandbox browser server."""
|
|
@@ -690,7 +689,14 @@ class AsyncBrowserSession:
|
|
|
690
689
|
pass
|
|
691
690
|
result = await self._client.exec_in_sandbox(
|
|
692
691
|
self.name,
|
|
693
|
-
[
|
|
692
|
+
[
|
|
693
|
+
"python3",
|
|
694
|
+
"-c",
|
|
695
|
+
_BROWSER_START_SCRIPT,
|
|
696
|
+
_ARIA_SNAPSHOT_JS,
|
|
697
|
+
"9222",
|
|
698
|
+
_BROWSER_SERVER_SCRIPT,
|
|
699
|
+
],
|
|
694
700
|
)
|
|
695
701
|
data = json.loads(result.output)
|
|
696
702
|
if "error" in data:
|