agentkernel-sdk 0.19.1__tar.gz → 0.20.1__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 (23) hide show
  1. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/PKG-INFO +10 -10
  2. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/pyproject.toml +12 -8
  3. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/_config.py +3 -1
  4. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/async_client.py +115 -58
  5. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/browser.py +15 -9
  6. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/client.py +123 -64
  7. agentkernel_sdk-0.20.1/src/agentkernel/sse.py +46 -0
  8. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/types.py +1 -2
  9. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/tests/test_async_client.py +11 -3
  10. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/tests/test_client.py +23 -7
  11. agentkernel_sdk-0.20.1/tests/test_sse.py +35 -0
  12. agentkernel_sdk-0.20.1/uv.lock +747 -0
  13. agentkernel_sdk-0.19.1/src/agentkernel/sse.py +0 -49
  14. agentkernel_sdk-0.19.1/uv.lock +0 -603
  15. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/.gitignore +0 -0
  16. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/README.md +0 -0
  17. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/examples/async_example.py +0 -0
  18. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/examples/quickstart.py +0 -0
  19. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/examples/sandbox_session.py +0 -0
  20. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/examples/streaming.py +0 -0
  21. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/__init__.py +6 -6
  22. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/errors.py +0 -0
  23. {agentkernel_sdk-0.19.1 → agentkernel_sdk-0.20.1}/src/agentkernel/py.typed +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: agentkernel-sdk
3
- Version: 0.19.1
3
+ Version: 0.20.1
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.27
23
- Requires-Dist: pydantic>=2.0
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.13; extra == 'dev'
26
- Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
27
- Requires-Dist: pytest-httpx>=0.34; extra == 'dev'
28
- Requires-Dist: pytest>=8.0; extra == 'dev'
29
- Requires-Dist: ruff>=0.8; extra == 'dev'
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.19.1"
7
+ version = "0.20.1"
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.27", "httpx-sse>=0.4", "pydantic>=2.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>=8.0",
31
- "pytest-asyncio>=0.24",
32
- "pytest-httpx>=0.34",
33
- "mypy>=1.13",
34
- "ruff>=0.8",
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, command, env=env, workdir=workdir, sudo=sudo,
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, "image": image, "vcpus": vcpus,
199
- "memory_mb": memory_mb, "profile": profile,
200
- "source_url": source_url, "source_ref": source_ref,
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 await self._request(
255
- "PUT",
256
- f"/sandboxes/{name}/files/{path}",
257
- json={"content": content, "encoding": encoding},
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 await self._request("DELETE", f"/sandboxes/{name}/exec/detached/{cmd_id}")
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 await self._request("POST", "/orchestrations", json=orchestration)
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 await self._request("GET", f"/orchestrations/{orchestration_id}")
346
+ return cast(
347
+ Orchestration, await self._request("GET", f"/orchestrations/{orchestration_id}")
348
+ )
334
349
 
335
350
  async def signal_orchestration(
336
- self, orchestration_id: str, event: dict[str, Any],
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 await self._request(
340
- "POST", f"/orchestrations/{orchestration_id}/events", json=event,
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, orchestration_id: str, payload: dict[str, Any] | None = None,
366
+ self,
367
+ orchestration_id: str,
368
+ payload: dict[str, Any] | None = None,
345
369
  ) -> Orchestration:
346
370
  """Terminate an orchestration."""
347
- return await self._request(
348
- "POST",
349
- f"/orchestrations/{orchestration_id}/terminate",
350
- json=payload or {},
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 await self._request("GET", "/orchestrations/definitions")
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, definition: OrchestrationDefinition,
388
+ self,
389
+ definition: OrchestrationDefinition,
359
390
  ) -> OrchestrationDefinition:
360
391
  """Register or update an orchestration definition."""
361
- return await self._request("POST", "/orchestrations/definitions", json=definition)
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 await self._request("GET", f"/orchestrations/definitions/{name}")
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, store_id: str, payload: dict[str, Any],
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 await self._request("POST", f"/stores/{store_id}/query", json=payload)
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, store_id: str, payload: dict[str, Any],
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 await self._request("POST", f"/stores/{store_id}/execute", json=payload)
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, store_id: str, payload: dict[str, Any],
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 await self._request("POST", f"/stores/{store_id}/command", json=payload)
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(name, image=image, vcpus=vcpus, memory_mb=memory_mb, profile=profile)
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 AsyncBrowserSession, _SETUP_CMD
555
+ from .browser import _SETUP_CMD, AsyncBrowserSession
502
556
 
503
557
  await self.create_sandbox(
504
- name, image="python:3.12-slim", memory_mb=memory_mb, profile="moderate",
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(**l) for l in data.get("links", [])],
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(**l) for l in data.get("links", [])],
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
- ["python3", "-c", _BROWSER_START_SCRIPT, _ARIA_SNAPSHOT_JS, "9222", _BROWSER_SERVER_SCRIPT],
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: